feat:详情页面
This commit is contained in:
parent
72ca9ca94e
commit
bd50a97f8f
|
|
@ -33,3 +33,5 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
@ -428,7 +439,7 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
||||||
// 方法标签
|
// 方法标签
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
|
@ -446,7 +457,7 @@ class _YxInspectorPanelState extends State<YxInspectorPanel> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
||||||
// URL和状态
|
// URL和状态
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -486,10 +497,12 @@ 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')}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue