Files
fuck-zhijiao/lib/main.dart
2025-12-24 09:51:14 +08:00

535 lines
19 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:crypto/crypto.dart';
import 'dart:developer' as developer;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '模拟登录打卡',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _loginStatus = '未登录';
String _userInfo = '';
String _punchCardStatus = '打卡未开启';
String _contentId = '';
// State for date navigation
DateTime _currentDate = DateTime.now();
// Settings variables
String _token = '';
String _account = '';
String _password = '';
String _deviceId = '';
// New state for punch card times
Map<String, String> _punchCardTimes = {};
@override
void initState() {
super.initState();
developer.log('Start of UTC day timestamp: ${_getStartOfDayTimestamp(_currentDate)}', name: 'Lifecycle');
_loadSettings(); // 移除自动登录,仅加载配置
}
int _getStartOfDayTimestamp(DateTime date) {
final localStart = DateTime(date.year, date.month, date.day, 0, 0, 0);
return localStart.millisecondsSinceEpoch;
}
String _formatTimestamp(int timestamp) {
if (timestamp == 0) return "--:--";
// Convert to local time for display
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp).toLocal();
final hour = dateTime.hour.toString().padLeft(2, '0');
final minute = dateTime.minute.toString().padLeft(2, '0');
return '$hour:$minute';
}
String _generateMd5(String input) {
return md5.convert(utf8.encode(input)).toString();
}
Map<String, String> _getHeaders() {
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',
};
}
Widget _buildPunchCardInfoGrid() {
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: 2.5,
padding: const EdgeInsets.all(8.0),
mainAxisSpacing: 8,
crossAxisSpacing: 8,
children: <Widget>[
_buildGridItem("上午首次", _punchCardTimes['上午首次'] ?? "--:--"),
_buildGridItem("上午灵活", _punchCardTimes['上午灵活'] ?? "--:--"),
_buildGridItem("下午首次", _punchCardTimes['下午首次'] ?? "--:--"),
_buildGridItem("下午灵活", _punchCardTimes['下午灵活'] ?? "--:--"),
],
);
}
Widget _buildGridItem(String title, String time) {
return Card(
elevation: 2.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal)),
const SizedBox(height: 5),
Text(time, style: const TextStyle(fontSize: 18, color: Colors.blueGrey, fontWeight: FontWeight.bold)),
],
),
);
}
bool _isToday(DateTime date) {
final now = DateTime.now();
return date.year == now.year && date.month == now.month && date.day == now.day;
}
String _formatDisplayDate(DateTime date) {
if (_isToday(date)) {
return '今天';
}
final yesterday = DateTime.now().subtract(const Duration(days: 1));
if (date.year == yesterday.year && date.month == yesterday.month && date.day == yesterday.day) {
return '昨天';
}
return '${date.month}/${date.day}';
}
Widget _buildDateNavigator() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
icon: const Icon(Icons.chevron_left),
onPressed: () => _changeDate(-1),
),
Text(
_formatDisplayDate(_currentDate),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: _isToday(_currentDate) ? null : () => _changeDate(1),
),
],
);
}
void _changeDate(int days) {
setState(() {
_currentDate = _currentDate.add(Duration(days: days));
// Clear old data while new data is being fetched
_punchCardTimes = {};
});
// Only fetch attendance courses for the new date
_getAttendanceCourses();
}
Future<void> _refreshAllData() async {
if (_loginStatus != '已登录') return;
setState(() {
_punchCardStatus = '加载中...';
_punchCardTimes = {};
});
await _getHomePageData();
await _getAttendanceCourses();
}
Future<void> _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_account = prefs.getString('account') ?? '';
_password = prefs.getString('password') ?? '';
_token = prefs.getString('token') ?? '';
_deviceId = prefs.getString('device_id') ?? '';
});
}
Future<void> _saveString(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
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;
}
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 _logout() {
setState(() {
_loginStatus = '未登录';
_userInfo = '';
_punchCardStatus = '打卡未开启';
_contentId = '';
_punchCardTimes = {}; // Reset punch card times on logout
_currentDate = DateTime.now(); // Reset date to today on logout
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('您已退出登录')),
);
}
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) {
setState(() {
_punchCardStatus = '打卡已开启';
_contentId = punchCardMessage['contentId'];
});
} else {
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
});
}
}
Future<void> _getAttendanceCourses() async {
if (_loginStatus != '已登录') return;
final timestamp = _getStartOfDayTimestamp(_currentDate);
final url = 'https://m-zjt.kefang.net/api/zms/attendance/attendanceCourse/listForStudent?attendanceDate=$timestamp';
final headers = _getHeaders();
developer.log('--- 🚀 [ATTENDANCE COURSES] 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('--- ✅ [ATTENDANCE COURSES] 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) {
final courses = data['data'] as List;
final newTimes = <String, String>{};
for (var course in courses) {
final timeType = course['timeType'] as String?;
final punchTime = course['punchTime'] as int?;
if (timeType != null && punchTime != null && punchTime != 0) {
newTimes[timeType] = _formatTimestamp(punchTime);
}
}
setState(() {
_punchCardTimes = newTimes;
});
}
}
} catch (e) {
developer.log('--- ❌ [ATTENDANCE COURSES] Error ---', error: e, name: 'NetworkDebug');
}
}
Future<void> _punchCard() async {
if (_token.isEmpty || _contentId.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Token或打卡ID为空请检查设置')),
);
return;
}
const url = 'https://m-zjt.kefang.net/api/zms/attendance/attendanceRecord/punchCourseByStudent';
final headers = _getHeaders();
final body = {'attendanceId': _contentId};
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);
if (data['code'] == 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('打卡成功')),
);
setState(() {
_punchCardStatus = '已打卡';
});
await _getAttendanceCourses(); // Refresh punch times after successful punch
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('打卡失败: ${data['message']}')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('打卡请求失败')),
);
}
} catch (e) {
developer.log('--- ❌ [PUNCH CARD] Error ---', error: e, name: 'NetworkDebug');
}
}
void _showSettingsDialog() async {
String tempAccount = _account;
String tempPassword = _password;
String tempToken = _token;
String tempDeviceId = _deviceId;
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,
),
TextField(
decoration: const InputDecoration(labelText: '密码 (明文)'),
controller: TextEditingController(text: tempPassword),
obscureText: true,
onChanged: (value) => tempPassword = value,
),
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,
),
],
),
),
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('保存'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('登录打卡'),//窗口标题
actions: [
IconButton(icon: const Icon(Icons.settings), onPressed: _showSettingsDialog,),
],
),
body: RefreshIndicator(
onRefresh: _refreshAllData,
child: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
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),),
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(),
],
const SizedBox(height: 40),
ElevatedButton(onPressed: () {
if (_loginStatus == '已登录') {
_logout();
} else {
_login(isAutoLogin: false);
}
}, child: Text(_loginStatus == '已登录' ? '退出' : '登录'),
),
const SizedBox(height: 20),
ElevatedButton(onPressed: _punchCardStatus == '打卡已开启' ? _punchCard : null, child: const Text('打卡'),),
],
),
),
),
),
);
}
}