flutter_dashboard/lib/widgets/kodbox_card.dart
jdysya 1db7ced409 feat(widgets): 优化 KodBoxCard 组件的样式和布局
- 增加卡片的阴影和圆角,提升视觉效果
- 调整卡片内各元素的样式和间距,增强可读性
- 优化 KodBox 最近操作部分的样式,增加紫色主题元素
- 改进错误提示和空状态的显示效果
2025-06-10 16:02:52 +08:00

298 lines
9.5 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: 2,
margin: const EdgeInsets.symmetric(vertical: 6.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
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(16)),
elevation: 4,
margin: const EdgeInsets.all(16),
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]);
},
),
),
],
),
),
);
}
}