优化使用体验
This commit is contained in:
652
lib/main.dart
652
lib/main.dart
@@ -1,4 +1,5 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
@@ -6,6 +7,69 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
// Network Service for API calls
|
||||
class ApiService {
|
||||
final String _baseUrl = 'https://m-zjt.kefang.net/api';
|
||||
|
||||
Map<String, String> _getHeaders(String token) {
|
||||
return {
|
||||
'User-Agent': 'Dart/3.6 (dart:io)',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Cookie': 'token=$token',
|
||||
'host': 'm-zjt.kefang.net',
|
||||
};
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getHomePageData(String token) async {
|
||||
final url = Uri.parse('$_baseUrl/zms/zhijiaotong/page/homePageData');
|
||||
final headers = _getHeaders(token);
|
||||
developer.log('--- 🚀 [HOME PAGE] Request ---', name: 'NetworkDebug');
|
||||
developer.log('URL: $url', name: 'NetworkDebug');
|
||||
developer.log('Headers: $headers', name: 'NetworkDebug');
|
||||
try {
|
||||
final response = await http.get(url, headers: headers);
|
||||
developer.log('--- ✅ [HOME PAGE] Response ---', name: 'NetworkDebug');
|
||||
developer.log('Status Code: ${response.statusCode}', name: 'NetworkDebug');
|
||||
developer.log('Body: ${response.body}', name: 'NetworkDebug');
|
||||
developer.log('------------------------------', name: 'NetworkDebug');
|
||||
if (response.statusCode == 200) {
|
||||
return json.decode(response.body);
|
||||
} else {
|
||||
return {'code': response.statusCode, 'message': 'HTTP Error'};
|
||||
}
|
||||
} catch (e) {
|
||||
developer.log('--- ❌ [HOME PAGE] Error ---', error: e, name: 'NetworkDebug');
|
||||
return {'code': -1, 'message': e.toString()};
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> punchCard(String token, String attendanceId) async {
|
||||
final url = Uri.parse('$_baseUrl/zms/attendance/attendanceRecord/punchCourseByStudent');
|
||||
final headers = _getHeaders(token);
|
||||
final body = {'attendanceId': attendanceId};
|
||||
developer.log('--- 🚀 [PUNCH CARD] Request ---', name: 'NetworkDebug');
|
||||
developer.log('URL: $url', name: 'NetworkDebug');
|
||||
developer.log('Headers: $headers', name: 'NetworkDebug');
|
||||
developer.log('Body: $body', name: 'NetworkDebug');
|
||||
try {
|
||||
final response = await http.put(url, headers: headers, body: body);
|
||||
developer.log('--- ✅ [PUNCH CARD] Response ---', name: 'NetworkDebug');
|
||||
developer.log('Status Code: ${response.statusCode}', name: 'NetworkDebug');
|
||||
developer.log('Body: ${response.body}', name: 'NetworkDebug');
|
||||
developer.log('-------------------------------', name: 'NetworkDebug');
|
||||
if (response.statusCode == 200) {
|
||||
return json.decode(response.body);
|
||||
} else {
|
||||
return {'code': response.statusCode, 'message': 'HTTP Error'};
|
||||
}
|
||||
} catch (e) {
|
||||
developer.log('--- ❌ [PUNCH CARD] Error ---', error: e, name: 'NetworkDebug');
|
||||
return {'code': -1, 'message': e.toString()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
@@ -16,7 +80,7 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '模拟登录打卡',
|
||||
title: '打卡',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
@@ -33,6 +97,7 @@ class MyHomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final ApiService _apiService = ApiService();
|
||||
String _loginStatus = '未登录';
|
||||
String _userInfo = '';
|
||||
String _punchCardStatus = '打卡未开启';
|
||||
@@ -43,9 +108,14 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
|
||||
// Settings variables
|
||||
String _token = '';
|
||||
String _tokens = '';
|
||||
String _account = '';
|
||||
String _password = '';
|
||||
String _deviceId = '';
|
||||
bool _autoLoginEnabled = false;
|
||||
bool _isTokenMode = true;
|
||||
bool _pollingEnabled = false;
|
||||
Timer? _pollingTimer;
|
||||
|
||||
// New state for punch card times
|
||||
Map<String, String> _punchCardTimes = {};
|
||||
@@ -54,7 +124,13 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
developer.log('Start of UTC day timestamp: ${_getStartOfDayTimestamp(_currentDate)}', name: 'Lifecycle');
|
||||
_loadSettings(); // 移除自动登录,仅加载配置
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stopPolling();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
int _getStartOfDayTimestamp(DateTime date) {
|
||||
@@ -183,8 +259,16 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
_account = prefs.getString('account') ?? '';
|
||||
_password = prefs.getString('password') ?? '';
|
||||
_token = prefs.getString('token') ?? '';
|
||||
_tokens = prefs.getString('tokens') ?? '';
|
||||
_deviceId = prefs.getString('device_id') ?? '';
|
||||
_autoLoginEnabled = prefs.getBool('auto_login_enabled') ?? false;
|
||||
_isTokenMode = prefs.getBool('is_token_mode') ?? true;
|
||||
_pollingEnabled = prefs.getBool('polling_enabled') ?? false;
|
||||
});
|
||||
|
||||
if (_autoLoginEnabled) {
|
||||
_login(isAutoLogin: true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveString(String key, String value) async {
|
||||
@@ -192,71 +276,162 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
await prefs.setString(key, value);
|
||||
}
|
||||
|
||||
Future<void> _login({bool isAutoLogin = false}) async {
|
||||
if (_account.isEmpty || _password.isEmpty || _token.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('请先在右上角设置中填写账号、密码和Token')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
Future<void> _saveBool(String key, bool value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(key, value);
|
||||
}
|
||||
|
||||
const url = 'https://m-zjt.kefang.net/api/ums/user/user/signIn';
|
||||
final hashedPassword = _generateMd5(_password);
|
||||
final headers = _getHeaders();
|
||||
final body = {
|
||||
'phone': _account,
|
||||
'password': hashedPassword,
|
||||
'platform': '职教智慧云',
|
||||
'deviceId': _deviceId,
|
||||
'os': 'android',
|
||||
};
|
||||
|
||||
developer.log('--- 🚀 [LOGIN] Request ---', name: 'NetworkDebug');
|
||||
developer.log('URL: $url', name: 'NetworkDebug');
|
||||
developer.log('Headers: $headers', name: 'NetworkDebug');
|
||||
developer.log('Body: $body', name: 'NetworkDebug');
|
||||
|
||||
try {
|
||||
final response = await http.post(Uri.parse(url), headers: headers, body: body);
|
||||
developer.log('--- ✅ [LOGIN] Response ---', name: 'NetworkDebug');
|
||||
developer.log('Status Code: ${response.statusCode}', name: 'NetworkDebug');
|
||||
developer.log('Body: ${response.body}', name: 'NetworkDebug');
|
||||
developer.log('--------------------------', name: 'NetworkDebug');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
if (data['code'] == 0) {
|
||||
setState(() {
|
||||
_loginStatus = '已登录';
|
||||
_userInfo = '欢迎您,${data['data']['name']}';
|
||||
_currentDate = DateTime.now(); // Reset date to today on login
|
||||
});
|
||||
await _refreshAllData(); // Fetch all data on login
|
||||
} else {
|
||||
if (!isAutoLogin) {
|
||||
setState(() {
|
||||
_loginStatus = '登录失败: ${data['message']}';
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isAutoLogin) {
|
||||
setState(() {
|
||||
_loginStatus = '登录失败: HTTP ${response.statusCode}';
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
developer.log('--- ❌ [LOGIN] Error ---', error: e, name: 'NetworkDebug');
|
||||
if (!isAutoLogin) {
|
||||
setState(() {
|
||||
_loginStatus = '登录异常: $e';
|
||||
});
|
||||
}
|
||||
void _updatePollingState() {
|
||||
if (_pollingEnabled && _loginStatus == '已登录') {
|
||||
_startPolling();
|
||||
} else {
|
||||
_stopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
void _startPolling() {
|
||||
_stopPolling(); // Ensure no multiple timers
|
||||
_pollingTimer = Timer.periodic(const Duration(seconds: 2), (timer) {
|
||||
_getHomePageData();
|
||||
});
|
||||
}
|
||||
|
||||
void _stopPolling() {
|
||||
_pollingTimer?.cancel();
|
||||
}
|
||||
|
||||
Future<void> _login({bool isAutoLogin = false}) async {
|
||||
if (_isTokenMode) {
|
||||
await _loginWithTokens(isAutoLogin: isAutoLogin);
|
||||
} else {
|
||||
await _loginWithAccount(isAutoLogin: isAutoLogin);
|
||||
}
|
||||
_updatePollingState();
|
||||
}
|
||||
|
||||
List<String> _parseTokens(String tokens) {
|
||||
return tokens.split('\n').where((t) => t.trim().isNotEmpty).map((t) => t.trim()).toList();
|
||||
}
|
||||
|
||||
Future<void> _loginWithTokens({bool isAutoLogin = false}) async {
|
||||
final tokenList = _parseTokens(_tokens);
|
||||
if (tokenList.isEmpty) {
|
||||
if (!isAutoLogin) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('请在设置中填写Token')),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
for (final token in tokenList) {
|
||||
// Set current token for ApiService to use
|
||||
setState(() {
|
||||
_token = token;
|
||||
_loginStatus = '已登录'; // Tentatively set to logged in
|
||||
});
|
||||
|
||||
await _getHomePageData();
|
||||
|
||||
// _getHomePageData will set _userInfo to contain '请求成功' on success
|
||||
if (_userInfo.contains('请求成功')) {
|
||||
success = true;
|
||||
break; // Stop on first successful token
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
await _getAttendanceCourses();
|
||||
} else {
|
||||
setState(() {
|
||||
_loginStatus = '未登录';
|
||||
_userInfo = '所有Token均无效';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loginWithAccount({bool isAutoLogin = false}) async {
|
||||
if (_token.isEmpty) {
|
||||
if (!isAutoLogin) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('请先在右上角设置中填写Token')),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_userInfo = '';
|
||||
});
|
||||
|
||||
if (_account.isNotEmpty && _password.isNotEmpty && _deviceId.isNotEmpty) {
|
||||
const url = 'https://m-zjt.kefang.net/api/ums/user/user/signIn';
|
||||
final hashedPassword = _generateMd5(_password);
|
||||
final headers = _getHeaders();
|
||||
final body = {
|
||||
'phone': _account,
|
||||
'password': hashedPassword,
|
||||
'platform': '职教智慧云',
|
||||
'deviceId': _deviceId,
|
||||
'os': 'android',
|
||||
};
|
||||
|
||||
developer.log('--- 🚀 [LOGIN] Request ---', name: 'NetworkDebug');
|
||||
developer.log('URL: $url', name: 'NetworkDebug');
|
||||
developer.log('Headers: $headers', name: 'NetworkDebug');
|
||||
developer.log('Body: $body', name: 'NetworkDebug');
|
||||
|
||||
try {
|
||||
final response = await http.post(Uri.parse(url), headers: headers, body: body);
|
||||
developer.log('--- ✅ [LOGIN] Response ---', name: 'NetworkDebug');
|
||||
developer.log('Status Code: ${response.statusCode}', name: 'NetworkDebug');
|
||||
developer.log('Body: ${response.body}', name: 'NetworkDebug');
|
||||
developer.log('--------------------------', name: 'NetworkDebug');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
if (data['code'] == 0) {
|
||||
setState(() {
|
||||
_loginStatus = '已登录';
|
||||
_currentDate = DateTime.now();
|
||||
});
|
||||
await _refreshAllData();
|
||||
} else {
|
||||
if (!isAutoLogin) {
|
||||
setState(() {
|
||||
_loginStatus = '登录失败: ${data['message']}';
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isAutoLogin) {
|
||||
setState(() {
|
||||
_loginStatus = '登录失败: HTTP ${response.statusCode}';
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
developer.log('--- ❌ [LOGIN] Error ---', error: e, name: 'NetworkDebug');
|
||||
if (!isAutoLogin) {
|
||||
setState(() {
|
||||
_loginStatus = '登录异常: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_loginStatus = '已登录';
|
||||
_userInfo = 'token模式';
|
||||
_currentDate = DateTime.now();
|
||||
});
|
||||
await _refreshAllData();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _logout() {
|
||||
_stopPolling();
|
||||
setState(() {
|
||||
_loginStatus = '未登录';
|
||||
_userInfo = '';
|
||||
@@ -272,53 +447,55 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
|
||||
Future<void> _getHomePageData() async {
|
||||
if (_loginStatus != '已登录') return;
|
||||
const url = 'https://m-zjt.kefang.net/api/zms/zhijiaotong/page/homePageData';
|
||||
final headers = _getHeaders();
|
||||
developer.log('--- 🚀 [HOME PAGE] Request ---', name: 'NetworkDebug');
|
||||
developer.log('URL: $url', name: 'NetworkDebug');
|
||||
developer.log('Headers: $headers', name: 'NetworkDebug');
|
||||
try {
|
||||
final response = await http.get(Uri.parse(url), headers: headers,);
|
||||
developer.log('--- ✅ [HOME PAGE] Response ---', name: 'NetworkDebug');
|
||||
developer.log('Status Code: ${response.statusCode}', name: 'NetworkDebug');
|
||||
developer.log('Body: ${response.body}', name: 'NetworkDebug');
|
||||
developer.log('------------------------------', name: 'NetworkDebug');
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
if (data['code'] == 0 && data['data'] != null && data['data']['messages'] != null) {
|
||||
final messages = data['data']['messages'] as List;
|
||||
final punchCardMessage = messages.firstWhere((m) => m['type'] == '打卡' && m['tradeType'] == '到课打卡', orElse: () => null);
|
||||
if (punchCardMessage != null) {
|
||||
|
||||
final isTokenLoginMode = _account.isEmpty || _password.isEmpty || _deviceId.isEmpty;
|
||||
final data = await _apiService.getHomePageData(_token);
|
||||
|
||||
if (data['code'] == 0 && data['data'] != null) {
|
||||
setState(() {
|
||||
_loginStatus = '已登录'; // Mark as logged in on successful data fetch
|
||||
});
|
||||
if (isTokenLoginMode) {
|
||||
setState(() {
|
||||
_userInfo = 'token模式 - 请求成功';
|
||||
});
|
||||
} else {
|
||||
final headerTeacher = data['data']['headerTeacher'];
|
||||
setState(() {
|
||||
_userInfo = '欢迎您,${headerTeacher ?? ''}';
|
||||
});
|
||||
}
|
||||
|
||||
if (data['data']['messages'] != null) {
|
||||
final messages = data['data']['messages'] as List;
|
||||
final punchCardMessage = messages.firstWhere((m) => m['type'] == '打卡' && m['tradeType'] == '到课打卡', orElse: () => null);
|
||||
if (punchCardMessage != null) {
|
||||
if (_punchCardStatus != '打卡已开启') {
|
||||
setState(() {
|
||||
_punchCardStatus = '打卡已开启';
|
||||
_contentId = punchCardMessage['contentId'];
|
||||
});
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
if (_punchCardStatus != '打卡未开启') {
|
||||
setState(() {
|
||||
_punchCardStatus = '打卡未开启';
|
||||
_contentId = ''; // Clear contentId if no punch card message is found
|
||||
});
|
||||
}
|
||||
} else if (data['code'] != 0) {
|
||||
setState(() {
|
||||
_loginStatus = '会话过期,请重新登录';
|
||||
_userInfo = '';
|
||||
_punchCardStatus = '打卡未开启';
|
||||
_contentId = ''; // Also clear contentId on session expiration
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_punchCardStatus = '打卡未开启';
|
||||
_contentId = ''; // Clear contentId if messages are null
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
developer.log('--- ❌ [HOME PAGE] Error ---', error: e, name: 'NetworkDebug');
|
||||
setState(() {
|
||||
_punchCardStatus = '获取主页数据异常';
|
||||
_contentId = ''; // Clear contentId on error
|
||||
});
|
||||
} else {
|
||||
if (_loginStatus == '已登录') { // Only show error if we were logged in
|
||||
setState(() {
|
||||
if (isTokenLoginMode) {
|
||||
_userInfo = 'token模式 - 请求失败';
|
||||
}
|
||||
_loginStatus = '会话过期,请重新登录';
|
||||
_punchCardStatus = '打卡未开启';
|
||||
_contentId = ''; // Also clear contentId on session expiration
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,122 +541,186 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
}
|
||||
|
||||
Future<void> _punchCard() async {
|
||||
if (_token.isEmpty || _contentId.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Token或打卡ID为空,请检查设置')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_isTokenMode) {
|
||||
final tokenList = _parseTokens(_tokens);
|
||||
if (tokenList.isEmpty || _contentId.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Tokens或打卡ID为空,请检查设置')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = 'https://m-zjt.kefang.net/api/zms/attendance/attendanceRecord/punchCourseByStudent';
|
||||
final headers = _getHeaders();
|
||||
final body = {'attendanceId': _contentId};
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
String lastErrorMessage = '';
|
||||
|
||||
developer.log('--- 🚀 [PUNCH CARD] Request ---', name: 'NetworkDebug');
|
||||
developer.log('URL: $url', name: 'NetworkDebug');
|
||||
developer.log('Headers: $headers', name: 'NetworkDebug');
|
||||
developer.log('Body: $body', name: 'NetworkDebug');
|
||||
|
||||
try {
|
||||
final response = await http.put(Uri.parse(url), headers: headers, body: body);
|
||||
developer.log('--- ✅ [PUNCH CARD] Response ---', name: 'NetworkDebug');
|
||||
developer.log('Status Code: ${response.statusCode}', name: 'NetworkDebug');
|
||||
developer.log('Body: ${response.body}', name: 'NetworkDebug');
|
||||
developer.log('-------------------------------', name: 'NetworkDebug');
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
for (final token in tokenList) {
|
||||
final data = await _apiService.punchCard(token, _contentId);
|
||||
if (data['code'] == 0) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('打卡成功')),
|
||||
);
|
||||
setState(() {
|
||||
_punchCardStatus = '已打卡';
|
||||
});
|
||||
await _getAttendanceCourses(); // Refresh punch times after successful punch
|
||||
successCount++;
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('打卡失败: ${data['message']}')),
|
||||
);
|
||||
failureCount++;
|
||||
lastErrorMessage = data['message'] ?? '未知错误';
|
||||
}
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('批量打卡完成:$successCount 成功,$failureCount 失败。${failureCount > 0 ? '最后错误: $lastErrorMessage' : ''}')),
|
||||
);
|
||||
|
||||
if (successCount > 0) {
|
||||
setState(() {
|
||||
_punchCardStatus = '已打卡';
|
||||
});
|
||||
await _getAttendanceCourses();
|
||||
}
|
||||
|
||||
} else {
|
||||
if (_token.isEmpty || _contentId.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Token或打卡ID为空,请检查设置')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final data = await _apiService.punchCard(_token, _contentId);
|
||||
|
||||
if (data['code'] == 0) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('打卡成功')),
|
||||
);
|
||||
setState(() {
|
||||
_punchCardStatus = '已打卡';
|
||||
});
|
||||
await _getAttendanceCourses();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('打卡请求失败')),
|
||||
SnackBar(content: Text('打卡失败: ${data['message']}')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
developer.log('--- ❌ [PUNCH CARD] Error ---', error: e, name: 'NetworkDebug');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _showSettingsDialog() async {
|
||||
String tempAccount = _account;
|
||||
String tempPassword = _password;
|
||||
String tempToken = _token;
|
||||
String tempTokens = _tokens;
|
||||
String tempDeviceId = _deviceId;
|
||||
bool tempAutoLogin = _autoLoginEnabled;
|
||||
bool tempIsTokenMode = _isTokenMode;
|
||||
bool tempPollingEnabled = _pollingEnabled;
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('设置'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: '账号 (手机号)'),
|
||||
controller: TextEditingController(text: tempAccount),
|
||||
onChanged: (value) => tempAccount = value,
|
||||
return StatefulBuilder(
|
||||
builder: (context, setStateInDialog) {
|
||||
return AlertDialog(
|
||||
title: const Text('设置'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: const Text('登录模式'),
|
||||
subtitle: Text(tempIsTokenMode ? 'Token模式' : '账号密码模式'),
|
||||
value: tempIsTokenMode,
|
||||
onChanged: (value) {
|
||||
setStateInDialog(() {
|
||||
tempIsTokenMode = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (tempIsTokenMode)
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'Tokens (每行一个)', alignLabelWithHint: true),
|
||||
controller: TextEditingController(text: tempTokens),
|
||||
onChanged: (value) => tempTokens = value,
|
||||
maxLines: 5,
|
||||
)
|
||||
else ...[
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'Token (Cookie)'),
|
||||
controller: TextEditingController(text: tempToken),
|
||||
onChanged: (value) => tempToken = value,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: '账号 (手机号) - 可选'),
|
||||
controller: TextEditingController(text: tempAccount),
|
||||
onChanged: (value) => tempAccount = value,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: '密码 (明文) - 可选'),
|
||||
controller: TextEditingController(text: tempPassword),
|
||||
obscureText: true,
|
||||
onChanged: (value) => tempPassword = value,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: '设备ID - 可选'),
|
||||
controller: TextEditingController(text: tempDeviceId),
|
||||
onChanged: (value) => tempDeviceId = value,
|
||||
),
|
||||
],
|
||||
SwitchListTile(
|
||||
title: const Text('打卡状态轮询'),
|
||||
value: tempPollingEnabled,
|
||||
onChanged: (value) {
|
||||
setStateInDialog(() {
|
||||
tempPollingEnabled = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('自动登录'),
|
||||
value: tempAutoLogin,
|
||||
onChanged: (value) {
|
||||
setStateInDialog(() {
|
||||
tempAutoLogin = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: '密码 (明文)'),
|
||||
controller: TextEditingController(text: tempPassword),
|
||||
obscureText: true,
|
||||
onChanged: (value) => tempPassword = value,
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('取消'),),
|
||||
TextButton(child: const Text('清空配置'), onPressed: () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.clear();
|
||||
await _loadSettings();
|
||||
setState(() {
|
||||
_loginStatus = '未登录';
|
||||
_userInfo = '';
|
||||
_punchCardStatus = '打卡未开启';
|
||||
_contentId = '';
|
||||
_punchCardTimes = {};
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('所有配置已清空')),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: 'Token (Cookie)'),
|
||||
controller: TextEditingController(text: tempToken),
|
||||
onChanged: (value) => tempToken = value,
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(labelText: '设备ID'),
|
||||
controller: TextEditingController(text: tempDeviceId),
|
||||
onChanged: (value) => tempDeviceId = value,
|
||||
TextButton(onPressed: () async {
|
||||
await _saveString('token', tempToken);
|
||||
await _saveString('tokens', tempTokens);
|
||||
await _saveString('account', tempAccount);
|
||||
await _saveString('password', tempPassword);
|
||||
await _saveString('device_id', tempDeviceId);
|
||||
await _saveBool('auto_login_enabled', tempAutoLogin);
|
||||
await _saveBool('is_token_mode', tempIsTokenMode);
|
||||
await _saveBool('polling_enabled', tempPollingEnabled);
|
||||
await _loadSettings();
|
||||
_updatePollingState();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('保存'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('取消'),),
|
||||
TextButton(child: const Text('清空配置'), onPressed: () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.clear();
|
||||
await _loadSettings();
|
||||
setState(() {
|
||||
_loginStatus = '未登录';
|
||||
_userInfo = '';
|
||||
_punchCardStatus = '打卡未开启';
|
||||
_contentId = '';
|
||||
_punchCardTimes = {};
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('所有配置已清空')),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton(onPressed: () async {
|
||||
await _saveString('account', tempAccount);
|
||||
await _saveString('password', tempPassword);
|
||||
await _saveString('token', tempToken);
|
||||
await _saveString('device_id', tempDeviceId);
|
||||
await _loadSettings();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('保存'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -489,7 +730,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('登录打卡'),//窗口标题
|
||||
title: const Text('打卡'),
|
||||
actions: [
|
||||
IconButton(icon: const Icon(Icons.settings), onPressed: _showSettingsDialog,),
|
||||
],
|
||||
@@ -502,25 +743,34 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(_loginStatus, style: const TextStyle(fontSize: 20), textAlign: TextAlign.center,),
|
||||
const SizedBox(height: 20),
|
||||
if (_loginStatus == '已登录') ...[
|
||||
Text(_userInfo, style: const TextStyle(fontSize: 18),),
|
||||
if (_userInfo.isNotEmpty) ...[
|
||||
Text(_userInfo, style: const TextStyle(fontSize: 18), textAlign: TextAlign.center,),
|
||||
const SizedBox(height: 20),
|
||||
if (_isToday(_currentDate)) // Only show punch status for today
|
||||
Text(_punchCardStatus, style: const TextStyle(fontSize: 18, color: Colors.green),),
|
||||
const SizedBox(height: 10),
|
||||
_buildDateNavigator(),
|
||||
_buildPunchCardInfoGrid(),
|
||||
] else if (_loginStatus == '已登录') ...[ // Show loading indicator only when logged in but no user info yet
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 20),
|
||||
const Text('登录中...')
|
||||
],
|
||||
const SizedBox(height: 40),
|
||||
ElevatedButton(onPressed: () {
|
||||
if (_loginStatus == '已登录') {
|
||||
_logout();
|
||||
} else {
|
||||
_login(isAutoLogin: false);
|
||||
}
|
||||
}, child: Text(_loginStatus == '已登录' ? '退出' : '登录'),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(onPressed: () {
|
||||
if (_loginStatus == '已登录') {
|
||||
_logout();
|
||||
} else {
|
||||
_login(isAutoLogin: false);
|
||||
}
|
||||
}, child: Text(_loginStatus == '已登录' ? '退出' : '登录'),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
ElevatedButton(onPressed: _loginStatus == '已登录' ? _getHomePageData : null, child: const Text('刷新打卡状态'),),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(onPressed: _punchCardStatus == '打卡已开启' ? _punchCard : null, child: const Text('打卡'),),
|
||||
|
||||
@@ -62,7 +62,7 @@ flutter:
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/images/ # 2. 声明资源文件夹
|
||||
- assets/ # 2. 声明资源文件夹
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
Reference in New Issue
Block a user