优化使用体验

This commit is contained in:
江雨
2025-12-24 16:54:43 +08:00
parent 706d322ed3
commit 424689b79d
2 changed files with 452 additions and 202 deletions

View File

@@ -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('打卡'),),

View File

@@ -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