feat:详情页面

This commit is contained in:
YuanXuan 2025-08-28 17:26:47 +08:00
parent 72ca9ca94e
commit bd50a97f8f
5 changed files with 123 additions and 71 deletions

View File

@ -33,3 +33,5 @@
} }
] ]
} }

View File

@ -65,6 +65,7 @@ class YxNetInspectorController extends ChangeNotifier {
queryParameters: queryParameters, queryParameters: queryParameters,
timestamp: DateTime.now(), timestamp: DateTime.now(),
isSuccess: false, // isSuccess: false, //
status: NetworkRequestStatus.pending, //
); );
_logs.insert(0, entry); _logs.insert(0, entry);
@ -98,6 +99,9 @@ class YxNetInspectorController extends ChangeNotifier {
responseData: responseData, responseData: responseData,
duration: duration, duration: duration,
isSuccess: isSuccess, isSuccess: isSuccess,
status: isSuccess
? NetworkRequestStatus.success
: NetworkRequestStatus.failed,
); );
_logs[index] = updatedLog; _logs[index] = updatedLog;
@ -139,6 +143,7 @@ class YxNetInspectorController extends ChangeNotifier {
errorMessage: error, errorMessage: error,
duration: duration, duration: duration,
isSuccess: false, isSuccess: false,
status: NetworkRequestStatus.failed,
); );
_logs[index] = updatedLog; _logs[index] = updatedLog;
@ -216,6 +221,11 @@ class YxNetInspectorController extends ChangeNotifier {
return _logs.where((log) => log.isSuccess == isSuccess).toList(); return _logs.where((log) => log.isSuccess == isSuccess).toList();
} }
///
List<NetworkLogEntry> getLogsByRequestStatus(NetworkRequestStatus status) {
return _logs.where((log) => log.status == status).toList();
}
/// ///
List<NetworkLogEntry> searchLogs(String keyword) { List<NetworkLogEntry> searchLogs(String keyword) {
if (keyword.isEmpty) return _logs.toList(); if (keyword.isEmpty) return _logs.toList();

View File

@ -1,5 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
///
enum NetworkRequestStatus {
///
pending,
///
success,
///
failed,
}
/// ///
class NetworkLogEntry { class NetworkLogEntry {
final String id; final String id;
@ -14,6 +26,7 @@ class NetworkLogEntry {
final DateTime timestamp; final DateTime timestamp;
final Duration? duration; final Duration? duration;
final bool isSuccess; final bool isSuccess;
final NetworkRequestStatus status;
NetworkLogEntry({ NetworkLogEntry({
required this.id, required this.id,
@ -28,22 +41,31 @@ class NetworkLogEntry {
required this.timestamp, required this.timestamp,
this.duration, this.duration,
required this.isSuccess, required this.isSuccess,
this.status = NetworkRequestStatus.pending,
}); });
/// ///
Color get statusColor { Color get statusColor {
if (!isSuccess) return Colors.red; switch (status) {
if (statusCode == null) return Colors.orange; case NetworkRequestStatus.pending:
if (statusCode! >= 200 && statusCode! < 300) return Colors.green; return Colors.blue; //
if (statusCode! >= 400 && statusCode! < 500) return Colors.orange; case NetworkRequestStatus.success:
return Colors.red; return Colors.green; // 绿
case NetworkRequestStatus.failed:
return Colors.red; //
}
} }
/// ///
String get statusText { String get statusText {
if (statusCode != null) return '$statusCode'; switch (status) {
if (!isSuccess) return '错误'; case NetworkRequestStatus.pending:
return '未知'; return '进行中';
case NetworkRequestStatus.success:
return statusCode != null ? '$statusCode' : '成功';
case NetworkRequestStatus.failed:
return statusCode != null ? '$statusCode' : '错误';
}
} }
/// ///
@ -108,6 +130,7 @@ class NetworkLogEntry {
DateTime? timestamp, DateTime? timestamp,
Duration? duration, Duration? duration,
bool? isSuccess, bool? isSuccess,
NetworkRequestStatus? status,
}) { }) {
return NetworkLogEntry( return NetworkLogEntry(
id: id ?? this.id, id: id ?? this.id,
@ -122,6 +145,7 @@ class NetworkLogEntry {
timestamp: timestamp ?? this.timestamp, timestamp: timestamp ?? this.timestamp,
duration: duration ?? this.duration, duration: duration ?? this.duration,
isSuccess: isSuccess ?? this.isSuccess, isSuccess: isSuccess ?? this.isSuccess,
status: status ?? this.status,
); );
} }

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../controller/yx_net_inspector_controller.dart'; import '../controller/yx_net_inspector_controller.dart';
import '../models/inspector_theme.dart'; import '../models/inspector_theme.dart';
import '../models/network_log_entry.dart'; import '../models/network_log_entry.dart';
@ -61,7 +62,9 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
// //
if (_showOnlyErrors) { if (_showOnlyErrors) {
logs = logs.where((log) => !log.isSuccess).toList(); logs = logs
.where((log) => log.status == NetworkRequestStatus.failed)
.toList();
} }
return logs; return logs;
@ -167,6 +170,14 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
), ),
), ),
const Spacer(), const Spacer(),
IconButton(
onPressed: () => _copyLogDetails(_selectedLog!),
icon: const Icon(
Icons.copy,
color: Colors.white,
),
tooltip: '复制详情',
),
IconButton( IconButton(
onPressed: () { onPressed: () {
_isFullScreen = !_isFullScreen; _isFullScreen = !_isFullScreen;
@ -403,10 +414,10 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
color: widget.theme.cardColor, color: widget.theme.cardColor,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all( border: Border.all(
color: log.isSuccess color: log.status == NetworkRequestStatus.failed
? Colors.transparent ? widget.theme.errorColor.withValues(alpha: 0.3)
: widget.theme.errorColor.withValues(alpha: 0.3), : Colors.transparent,
width: log.isSuccess ? 0 : 1, width: log.status == NetworkRequestStatus.failed ? 1 : 0,
), ),
), ),
child: InkWell( child: InkWell(
@ -489,7 +500,9 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
// //
Text( Text(
log.formattedTime.split(' ')[1], // log.formattedTime.contains(' ')
? log.formattedTime.split(' ')[1]
: log.formattedTime, //
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: widget.theme.secondaryTextColor, color: widget.theme.secondaryTextColor,
@ -524,4 +537,57 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
return widget.theme.secondaryTextColor; return widget.theme.secondaryTextColor;
} }
} }
void _copyLogDetails(NetworkLogEntry log) {
final details = '''
Network Request Details
=======================
Basic Information:
- Method: ${log.method}
- Status Code: ${log.statusCode ?? 'N/A'}
- Status: ${log.statusText}
- Duration: ${log.formattedDuration}
- Request Size: ${log.requestSize} B
- Response Size: ${log.responseSize} B
Full URL:
${log.url}
${log.requestData != null ? 'Request Body:\n${_formatRequestBody(log.requestData)}\n\n' : ''}${log.responseData != null ? 'Response Data:\n${log.responseData}\n\n' : ''}${log.errorMessage != null ? 'Error Message:\n${log.errorMessage}\n\n' : ''}Time Information:
- Request Time: ${log.formattedTime}
- Start Time: ${_formatDateTime(log.timestamp)}
${log.duration != null ? '- End Time: ${_formatDateTime(log.timestamp.add(log.duration!))}' : ''}
''';
Clipboard.setData(ClipboardData(text: details));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('详情已复制到剪贴板'),
duration: Duration(seconds: 2),
),
);
}
String _formatRequestBody(dynamic requestData) {
if (requestData == null) return 'No request data';
if (requestData is String) {
return requestData;
} else if (requestData is Map) {
try {
return requestData.toString();
} catch (e) {
return requestData.toString();
}
} else {
return requestData.toString();
}
}
String _formatDateTime(DateTime dateTime) {
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} '
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}';
}
} }

View File

@ -1,6 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/network_log_entry.dart'; import '../models/network_log_entry.dart';
import '../models/inspector_theme.dart'; import '../models/inspector_theme.dart';
@ -13,23 +12,9 @@ class YxLogDetailPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Container(
backgroundColor: theme.backgroundColor, color: theme.backgroundColor,
appBar: AppBar( child: SingleChildScrollView(
title: const Text('请求详情'),
backgroundColor: theme.primaryColor,
foregroundColor: Colors.white,
actions: [
IconButton(
onPressed: () {
_copyToClipboard(context);
},
icon: const Icon(Icons.copy),
tooltip: '复制详情',
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -402,39 +387,4 @@ class YxLogDetailPage extends StatelessWidget {
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ' return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} '
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}'; '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}';
} }
void _copyToClipboard(BuildContext context) {
final details = '''
Network Request Details
=======================
Basic Information:
- Method: ${log.method}
- Status Code: ${log.statusCode ?? 'N/A'}
- Status: ${log.statusText}
- Duration: ${log.formattedDuration}
- Request Size: ${log.requestSize} B
- Response Size: ${log.responseSize} B
Full URL:
${log.url}
${log.requestData != null ? 'Request Body:\n${_formatRequestBody(log.requestData)}\n\n' : ''}
${log.responseData != null ? 'Response Data:\n${log.responseData}\n\n' : ''}
${log.errorMessage != null ? 'Error Message:\n${log.errorMessage}\n\n' : ''}
Time Information:
- Request Time: ${log.formattedTime}
- Start Time: ${_formatDateTime(log.timestamp)}
${log.duration != null ? '- End Time: ${_formatDateTime(log.timestamp.add(log.duration!))}' : ''}
''';
Clipboard.setData(ClipboardData(text: details));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('详情已复制到剪贴板'),
duration: Duration(seconds: 2),
),
);
}
} }