From ae49aa13dfd4447d5c9c28605feae6802bf216b1 Mon Sep 17 00:00:00 2001 From: jdysya <1912377458@qq.com> Date: Mon, 9 Jun 2025 17:05:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=9E=E7=8E=B0kodbox?= =?UTF-8?q?=E7=9A=84=E5=8A=A8=E6=80=81=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/screens/home_screen.dart | 3 +- lib/widgets/kodbox_card.dart | 195 +++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 lib/widgets/kodbox_card.dart diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index b65e437..00139db 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import '../services/auth_service.dart'; import '../widgets/leetcode_card.dart'; import '../widgets/gitea_card.dart'; +import '../widgets/kodbox_card.dart'; import 'settings_screen.dart'; class HomeScreen extends StatefulWidget { @@ -90,7 +91,7 @@ class _HomeScreenState extends State { case 'Gitea': return const GiteaCard(); case 'KodBox': - return const Center(child: Text('KodBox 数据卡片开发中...')); + return const KodBoxCard(); default: return const Center(child: Text('未知平台')); } diff --git a/lib/widgets/kodbox_card.dart b/lib/widgets/kodbox_card.dart new file mode 100644 index 0000000..359ed17 --- /dev/null +++ b/lib/widgets/kodbox_card.dart @@ -0,0 +1,195 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '../services/auth_service.dart'; + +class KodBoxCard extends StatefulWidget { + const KodBoxCard({super.key}); + + @override + State createState() => _KodBoxCardState(); +} + +class _KodBoxCardState extends State { + List _logs = []; + bool _isLoading = false; + String? _error; + + Future _fetchLogs() async { + setState(() { + _isLoading = true; + _error = null; + }); + + try { + final authService = Provider.of(context, listen: false); + final token = authService.credentials['kodbox']; + + if (token == null || token.isEmpty) { + throw Exception('未配置 KodBox Token'); + } + + // 计算时间范围(最近7天) + final now = DateTime.now(); + final sevenDaysAgo = now.subtract(const Duration(days: 7)); + final timeTo = (now.millisecondsSinceEpoch / 1000).round(); + final timeFrom = (sevenDaysAgo.millisecondsSinceEpoch / 1000).round(); + + final request = http.MultipartRequest( + 'POST', + Uri.parse('https://cloud.jdysya.top/?admin/log/get&accessToken=$token'), + ); + + request.fields.addAll({ + 'page': '1', + 'pageNum': '50', + 'sortField': 'createTime', + 'sortType': 'down', + 'timeFrom': timeFrom.toString(), + 'timeTo': timeTo.toString(), + }); + + final response = await request.send(); + final responseBody = await response.stream.bytesToString(); + + if (response.statusCode == 200) { + final data = jsonDecode(responseBody); + if (data['code'] == true) { + setState(() { + _logs = data['data']; + _isLoading = false; + }); + } else { + throw Exception('请求失败: ${data['info'] ?? '未知错误'}'); + } + } else { + throw Exception('请求失败: ${response.statusCode}'); + } + } catch (e) { + setState(() { + _error = e.toString(); + _isLoading = false; + }); + } + } + + String _formatTimestamp(String timestamp) { + final date = DateTime.fromMillisecondsSinceEpoch( + (double.parse(timestamp) * 1000).round(), + ); + return date.toString().substring(0, 19); + } + + Widget _buildLogItem(Map log) { + final desc = log['desc']; + final isFileOperation = + desc != null && + (desc['sourceInfo'] != null || desc['parentInfo'] != null); + + return Card( + margin: const EdgeInsets.symmetric(vertical: 4.0), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + radius: 16, + child: Text(log['nickName']?[0].toUpperCase() ?? '?'), + ), + const SizedBox(width: 8), + Text( + log['nickName'] ?? log['name'] ?? '未知用户', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(width: 8), + Text(log['title'] ?? '未知操作'), + ], + ), + if (isFileOperation && desc['sourceInfo'] != null) ...[ + const SizedBox(height: 8), + Text( + '文件路径: ${desc['sourceInfo']['pathDisplay']}', + style: const TextStyle(color: Colors.blue), + ), + ], + const SizedBox(height: 8), + Text( + '时间: ${_formatTimestamp(log['createTime'])}', + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + _fetchLogs(); + } + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'KodBox 最近操作', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + IconButton( + icon: const Icon(Icons.refresh), + onPressed: _isLoading ? null : _fetchLogs, + ), + ], + ), + if (_isLoading) + const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ) + else if (_error != null) + Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + _error!, + style: const TextStyle(color: Colors.red), + ), + ), + ) + else if (_logs.isEmpty) + const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Text('暂无操作记录'), + ), + ) + else + Expanded( + child: ListView.builder( + itemCount: _logs.length, + itemBuilder: (context, index) { + return _buildLogItem(_logs[index]); + }, + ), + ), + ], + ), + ), + ); + } +}