flutter_dashboard/lib/widgets/kodbox_card.dart
jdysya 6b64d1a0cd refactor(widgets): 重构卡片组件的样式
- 统一了卡片的阴影、边框和圆角样式
- 调整了卡片的外边距
- 优化了部分卡片的内边距
- 统一了难度等级的命名方式
2025-06-10 16:33:54 +08:00

306 lines
9.9 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:ffi';
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';
import '../services/kodbox_service.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:url_launcher/url_launcher.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;
final _kodboxService = KodBoxService();
Future<void> _fetchLogs() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final authService = Provider.of<AuthService>(context, listen: false);
final token = await _kodboxService.getValidToken(authService);
// 计算时间范围最近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('${KodBoxService.baseUrl}/?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 _getFilePath(Map<String, dynamic> desc) {
if (desc['sourceInfo'] == false) {
// 处理删除文件的情况
final parentInfo = desc['parentInfo'];
final fileName = desc['desc']?['content'] ?? '未知文件';
return '${parentInfo['pathDisplay']}$fileName';
} else if (desc['sourceInfo'] != null) {
return desc['sourceInfo']['pathDisplay'] ?? '未知路径';
}
return '未知路径';
}
int? _getSourceID(Map<String, dynamic> desc) {
if (desc['sourceInfo'] == false) {
// 处理删除文件的情况
return desc['parentInfo']?['sourceID'];
} else if (desc['sourceInfo'] != null) {
return desc['sourceInfo']?['sourceID'];
}
return null;
}
Future<void> _launchFileUrl(int sourceID) async {
final url = Uri.parse('${KodBoxService.baseUrl}/#explorer&sidf=$sourceID');
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无法打开文件链接')),
);
}
}
}
Widget _buildLogItem(Map<String, dynamic> log) {
final desc = log['desc'];
final isFileOperation = desc != null &&
(desc['sourceInfo'] != null || desc['parentInfo'] != null);
final sourceID = isFileOperation ? _getSourceID(desc) : null;
return Card(
elevation: 4,
shadowColor: Colors.deepPurple.withOpacity(0.3),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: Colors.deepPurple.withOpacity(0.1)),
),
margin: const EdgeInsets.symmetric(vertical: 5.0),
child: InkWell(
onTap: sourceID != null ? () => _launchFileUrl(sourceID) : null,
splashColor: Colors.deepPurple.withAlpha(30),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: Colors.deepPurple.shade200,
radius: 18,
child: Text(
log['nickName']?[0].toUpperCase() ?? '?',
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
log['nickName'] ?? log['name'] ?? '未知用户',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
if (log['title'] != null)
Text(
log['title']!,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
),
],
),
if (isFileOperation) ...[
const SizedBox(height: 12),
Row(
children: [
const Icon(Icons.insert_drive_file,
size: 18, color: Colors.deepPurple),
const SizedBox(width: 8),
Expanded(
child: Text(
'文件路径: ${_getFilePath(desc)}',
style: TextStyle(
color: sourceID != null ? Colors.blue : Colors.black,
fontSize: 14,
),
),
),
],
),
],
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.access_time, size: 16, color: Colors.grey),
const SizedBox(width: 6),
Text(
'时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(int.parse(log['createTime']) * 1000), locale: 'zh_CN')}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
],
),
),
),
);
}
@override
void initState() {
super.initState();
_fetchLogs();
}
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: Colors.deepPurple.withOpacity(0.15)),
),
elevation: 6,
shadowColor: Colors.deepPurple.withOpacity(0.4),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
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: 20,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
Container(
decoration: BoxDecoration(
color: Colors.deepPurple.shade100,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.all(8),
child: IconButton(
icon: const Icon(Icons.refresh, color: Colors.deepPurple),
onPressed: _isLoading ? null : _fetchLogs,
),
),
],
),
if (_isLoading)
const Center(
child: Padding(
padding: EdgeInsets.all(24.0),
child: CircularProgressIndicator(color: Colors.deepPurple),
),
)
else if (_error != null)
Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, color: Colors.red),
const SizedBox(width: 8),
Expanded(
child: Text(
_error!,
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
),
],
),
),
)
else if (_logs.isEmpty)
const Center(
child: Padding(
padding: EdgeInsets.all(24.0),
child: Text(
'暂无操作记录',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
),
)
else
Expanded(
child: ListView.builder(
itemCount: _logs.length,
itemBuilder: (context, index) {
return _buildLogItem(_logs[index]);
},
),
),
],
),
),
);
}
}