- 移除 KodBox API Token 相关代码 - 添加 KodBox 用户名和密码输入字段 - 实现 KodBox 有效 token 获取逻辑 - 更新 KodBox 日志获取和展示逻辑 - 添加文件路径点击打开功能 - 更新相关测试和依赖
232 lines
6.9 KiB
Dart
232 lines
6.9 KiB
Dart
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(
|
||
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
||
child: InkWell(
|
||
onTap: sourceID != null ? () => _launchFileUrl(sourceID) : null,
|
||
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) ...[
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'文件路径: ${_getFilePath(desc)}',
|
||
style: TextStyle(
|
||
color: sourceID != null ? Colors.blue : Colors.black,
|
||
),
|
||
),
|
||
],
|
||
const SizedBox(height: 8),
|
||
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(
|
||
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]);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|