基本实现kodbox的动态展示
This commit is contained in:
parent
89c0dfc1aa
commit
ae49aa13df
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import '../services/auth_service.dart';
|
import '../services/auth_service.dart';
|
||||||
import '../widgets/leetcode_card.dart';
|
import '../widgets/leetcode_card.dart';
|
||||||
import '../widgets/gitea_card.dart';
|
import '../widgets/gitea_card.dart';
|
||||||
|
import '../widgets/kodbox_card.dart';
|
||||||
import 'settings_screen.dart';
|
import 'settings_screen.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
@ -90,7 +91,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
case 'Gitea':
|
case 'Gitea':
|
||||||
return const GiteaCard();
|
return const GiteaCard();
|
||||||
case 'KodBox':
|
case 'KodBox':
|
||||||
return const Center(child: Text('KodBox 数据卡片开发中...'));
|
return const KodBoxCard();
|
||||||
default:
|
default:
|
||||||
return const Center(child: Text('未知平台'));
|
return const Center(child: Text('未知平台'));
|
||||||
}
|
}
|
||||||
|
|||||||
195
lib/widgets/kodbox_card.dart
Normal file
195
lib/widgets/kodbox_card.dart
Normal file
@ -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<KodBoxCard> createState() => _KodBoxCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _KodBoxCardState extends State<KodBoxCard> {
|
||||||
|
List<dynamic> _logs = [];
|
||||||
|
bool _isLoading = false;
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
Future<void> _fetchLogs() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final authService = Provider.of<AuthService>(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<String, dynamic> 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]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user