diff --git a/lib/main.dart b/lib/main.dart index 1328739..16b7426 100644 --- a/lib/main.dart +++ b/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 _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> 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> 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 { + final ApiService _apiService = ApiService(); String _loginStatus = '未登录'; String _userInfo = ''; String _punchCardStatus = '打卡未开启'; @@ -43,9 +108,14 @@ class _MyHomePageState extends State { // 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 _punchCardTimes = {}; @@ -54,7 +124,13 @@ class _MyHomePageState extends State { 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 { _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 _saveString(String key, String value) async { @@ -192,71 +276,162 @@ class _MyHomePageState extends State { await prefs.setString(key, value); } - Future _login({bool isAutoLogin = false}) async { - if (_account.isEmpty || _password.isEmpty || _token.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('请先在右上角设置中填写账号、密码和Token')), - ); - return; - } + Future _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 _login({bool isAutoLogin = false}) async { + if (_isTokenMode) { + await _loginWithTokens(isAutoLogin: isAutoLogin); + } else { + await _loginWithAccount(isAutoLogin: isAutoLogin); + } + _updatePollingState(); + } + + List _parseTokens(String tokens) { + return tokens.split('\n').where((t) => t.trim().isNotEmpty).map((t) => t.trim()).toList(); + } + + Future _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 _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 { Future _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 { } Future _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 { 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 { child: Column( mainAxisSize: MainAxisSize.min, children: [ - 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('打卡'),), diff --git a/pubspec.yaml b/pubspec.yaml index 3050c12..f7e9eee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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