ix_developer_tools.dart 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. import 'dart:convert';
  2. import 'package:archive/archive.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:get/get.dart';
  5. import 'package:dio/dio.dart' as dio;
  6. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  7. import '../../app/constants/keys.dart';
  8. import '../crypto.dart';
  9. import '../log/logger.dart';
  10. import 'simple_log_card.dart';
  11. import 'simple_api_card.dart';
  12. /// 简化版开发者工具 - 只包含控制台日志和API监控
  13. class IXDeveloperTools {
  14. static final IXDeveloperTools _instance = IXDeveloperTools._internal();
  15. factory IXDeveloperTools() => _instance;
  16. IXDeveloperTools._internal();
  17. final List<ConsoleLogEntry> _logs = [];
  18. final List<ApiRequestInfo> _apiRequests = [];
  19. static const int maxLogs = 1000;
  20. static const int maxApiRequests = 500;
  21. /// 添加控制台日志
  22. static void addLog(String message, String tag) {
  23. final entry = ConsoleLogEntry(
  24. timestamp: DateTime.now(),
  25. message: message,
  26. tag: tag,
  27. );
  28. _instance._logs.insert(0, entry);
  29. if (_instance._logs.length > maxLogs) {
  30. _instance._logs.removeRange(maxLogs, _instance._logs.length);
  31. }
  32. }
  33. /// 添加API请求信息
  34. static void addApiRequest(ApiRequestInfo request) {
  35. _instance._apiRequests.insert(0, request);
  36. if (_instance._apiRequests.length > maxApiRequests) {
  37. _instance._apiRequests.removeRange(
  38. maxApiRequests,
  39. _instance._apiRequests.length,
  40. );
  41. }
  42. }
  43. /// 获取所有日志
  44. static List<ConsoleLogEntry> get logs => List.unmodifiable(_instance._logs);
  45. /// 获取所有API请求
  46. static List<ApiRequestInfo> get apiRequests =>
  47. List.unmodifiable(_instance._apiRequests);
  48. /// 清除所有日志
  49. static void clearLogs() {
  50. _instance._logs.clear();
  51. }
  52. /// 清除所有API请求
  53. static void clearApiRequests() {
  54. _instance._apiRequests.clear();
  55. }
  56. /// 显示开发者工具
  57. static void show() {
  58. try {
  59. Get.to(
  60. () => const SimpleDeveloperToolsScreen(),
  61. transition: Transition.cupertino,
  62. );
  63. } catch (e) {
  64. log('IXDeveloperTools', 'Unable to open Developer Tools: $e');
  65. }
  66. }
  67. }
  68. /// 控制台日志条目
  69. class ConsoleLogEntry {
  70. final DateTime timestamp;
  71. final String message;
  72. final String tag;
  73. ConsoleLogEntry({
  74. required this.timestamp,
  75. required this.message,
  76. required this.tag,
  77. });
  78. String get formattedTime {
  79. return '${timestamp.hour.toString().padLeft(2, '0')}:'
  80. '${timestamp.minute.toString().padLeft(2, '0')}:'
  81. '${timestamp.second.toString().padLeft(2, '0')}.'
  82. '${timestamp.millisecond.toString().padLeft(3, '0')}';
  83. }
  84. }
  85. /// API请求信息
  86. class ApiRequestInfo {
  87. final String id;
  88. final DateTime timestamp;
  89. final String method;
  90. final String url;
  91. final Map<String, dynamic>? headers;
  92. final dynamic requestData;
  93. final int? statusCode;
  94. final dynamic responseData;
  95. final String? error;
  96. final Duration? duration;
  97. ApiRequestInfo({
  98. required this.id,
  99. required this.timestamp,
  100. required this.method,
  101. required this.url,
  102. this.headers,
  103. this.requestData,
  104. this.statusCode,
  105. this.responseData,
  106. this.error,
  107. this.duration,
  108. });
  109. String get statusText {
  110. if (error != null) return 'ERROR';
  111. if (statusCode == null) return 'PENDING';
  112. if (statusCode! >= 200 && statusCode! < 300) return 'SUCCESS';
  113. if (statusCode! >= 400) return 'ERROR';
  114. return 'UNKNOWN';
  115. }
  116. Color get statusColor {
  117. switch (statusText) {
  118. case 'SUCCESS':
  119. return Colors.green;
  120. case 'ERROR':
  121. return Colors.red;
  122. case 'PENDING':
  123. return Colors.orange;
  124. default:
  125. return Colors.grey;
  126. }
  127. }
  128. }
  129. /// 简化版API监控拦截器
  130. class SimpleApiMonitorInterceptor extends dio.Interceptor {
  131. final Map<String, DateTime> _requestStartTimes = {};
  132. @override
  133. void onRequest(
  134. dio.RequestOptions options,
  135. dio.RequestInterceptorHandler handler,
  136. ) {
  137. final requestId = _generateRequestId();
  138. _requestStartTimes[requestId] = DateTime.now();
  139. final request = ApiRequestInfo(
  140. id: requestId,
  141. timestamp: DateTime.now(),
  142. method: options.method,
  143. url: options.uri.toString(),
  144. headers: options.headers,
  145. requestData: options.data is FormData
  146. ? options.data.fields
  147. : _decryptData(options.data),
  148. );
  149. IXDeveloperTools.addApiRequest(request);
  150. handler.next(options);
  151. }
  152. @override
  153. void onResponse(
  154. dio.Response response,
  155. dio.ResponseInterceptorHandler handler,
  156. ) {
  157. final requestId = _findRequestId(response.requestOptions.uri.toString());
  158. if (requestId != null) {
  159. final startTime = _requestStartTimes.remove(requestId);
  160. final duration = startTime != null
  161. ? DateTime.now().difference(startTime)
  162. : null;
  163. final updatedRequest = ApiRequestInfo(
  164. id: requestId,
  165. timestamp: DateTime.now(),
  166. method: response.requestOptions.method,
  167. url: response.requestOptions.uri.toString(),
  168. headers: response.requestOptions.headers,
  169. requestData: response.requestOptions.data is FormData
  170. ? response.requestOptions.data.fields
  171. : _decryptData(response.requestOptions.data),
  172. statusCode: response.statusCode,
  173. responseData: _decryptData(response.data),
  174. duration: duration,
  175. );
  176. _updateApiRequest(updatedRequest);
  177. }
  178. handler.next(response);
  179. }
  180. @override
  181. void onError(dio.DioException err, dio.ErrorInterceptorHandler handler) {
  182. final requestId = _findRequestId(err.requestOptions.uri.toString());
  183. if (requestId != null) {
  184. final startTime = _requestStartTimes.remove(requestId);
  185. final duration = startTime != null
  186. ? DateTime.now().difference(startTime)
  187. : null;
  188. final updatedRequest = ApiRequestInfo(
  189. id: requestId,
  190. timestamp: DateTime.now(),
  191. method: err.requestOptions.method,
  192. url: err.requestOptions.uri.toString(),
  193. headers: err.requestOptions.headers,
  194. requestData: err.requestOptions.data is FormData
  195. ? err.requestOptions.data.fields
  196. : _decryptData(err.requestOptions.data),
  197. statusCode: err.response?.statusCode,
  198. error: err.message,
  199. duration: duration,
  200. );
  201. _updateApiRequest(updatedRequest);
  202. }
  203. handler.next(err);
  204. }
  205. dynamic _decryptData(dynamic encryptedData) {
  206. try {
  207. // 1. Base64 编码
  208. final base64Data = base64Encode(encryptedData);
  209. // 2. AES 解密
  210. final decryptedBytes = Crypto.decryptBytes(
  211. base64Data,
  212. Keys.aesKey,
  213. Keys.aesIv,
  214. );
  215. // 3. GZip 解压
  216. final gzipDecoder = GZipDecoder();
  217. final decompressedData = gzipDecoder.decodeBytes(decryptedBytes);
  218. // 4. UTF-8 解码
  219. final data = utf8.decode(decompressedData);
  220. // string to json
  221. return jsonDecode(data);
  222. } catch (e) {
  223. return encryptedData;
  224. }
  225. }
  226. String _generateRequestId() {
  227. return '${DateTime.now().millisecondsSinceEpoch}_${IXDeveloperTools.apiRequests.length}';
  228. }
  229. String? _findRequestId(String url) {
  230. try {
  231. final request = IXDeveloperTools.apiRequests.firstWhere(
  232. (r) => r.url == url && r.statusCode == null,
  233. );
  234. return request.id;
  235. } catch (e) {
  236. return null;
  237. }
  238. }
  239. void _updateApiRequest(ApiRequestInfo updatedRequest) {
  240. final requests = IXDeveloperTools._instance._apiRequests;
  241. final index = requests.indexWhere((r) => r.id == updatedRequest.id);
  242. if (index != -1) {
  243. requests[index] = updatedRequest;
  244. }
  245. }
  246. }
  247. class SimpleNoSignApiMonitorInterceptor extends dio.Interceptor {
  248. final Map<String, DateTime> _requestStartTimes = {};
  249. @override
  250. void onRequest(
  251. dio.RequestOptions options,
  252. dio.RequestInterceptorHandler handler,
  253. ) {
  254. final requestId = _generateRequestId();
  255. _requestStartTimes[requestId] = DateTime.now();
  256. final request = ApiRequestInfo(
  257. id: requestId,
  258. timestamp: DateTime.now(),
  259. method: options.method,
  260. url: options.uri.toString(),
  261. headers: options.headers,
  262. requestData: options.data,
  263. );
  264. IXDeveloperTools.addApiRequest(request);
  265. handler.next(options);
  266. }
  267. @override
  268. void onResponse(
  269. dio.Response response,
  270. dio.ResponseInterceptorHandler handler,
  271. ) {
  272. final requestId = _findRequestId(response.requestOptions.uri.toString());
  273. if (requestId != null) {
  274. final startTime = _requestStartTimes.remove(requestId);
  275. final duration = startTime != null
  276. ? DateTime.now().difference(startTime)
  277. : null;
  278. final updatedRequest = ApiRequestInfo(
  279. id: requestId,
  280. timestamp: DateTime.now(),
  281. method: response.requestOptions.method,
  282. url: response.requestOptions.uri.toString(),
  283. headers: response.requestOptions.headers,
  284. requestData: response.requestOptions.data,
  285. statusCode: response.statusCode,
  286. responseData: response.data,
  287. duration: duration,
  288. );
  289. _updateApiRequest(updatedRequest);
  290. }
  291. handler.next(response);
  292. }
  293. @override
  294. void onError(dio.DioException err, dio.ErrorInterceptorHandler handler) {
  295. final requestId = _findRequestId(err.requestOptions.uri.toString());
  296. if (requestId != null) {
  297. final startTime = _requestStartTimes.remove(requestId);
  298. final duration = startTime != null
  299. ? DateTime.now().difference(startTime)
  300. : null;
  301. final updatedRequest = ApiRequestInfo(
  302. id: requestId,
  303. timestamp: DateTime.now(),
  304. method: err.requestOptions.method,
  305. url: err.requestOptions.uri.toString(),
  306. headers: err.requestOptions.headers,
  307. requestData: err.requestOptions.data,
  308. statusCode: err.response?.statusCode,
  309. error: err.message,
  310. duration: duration,
  311. );
  312. _updateApiRequest(updatedRequest);
  313. }
  314. handler.next(err);
  315. }
  316. String _generateRequestId() {
  317. return '${DateTime.now().millisecondsSinceEpoch}_${IXDeveloperTools.apiRequests.length}';
  318. }
  319. String? _findRequestId(String url) {
  320. try {
  321. final request = IXDeveloperTools.apiRequests.firstWhere(
  322. (r) => r.url == url && r.statusCode == null,
  323. );
  324. return request.id;
  325. } catch (e) {
  326. return null;
  327. }
  328. }
  329. void _updateApiRequest(ApiRequestInfo updatedRequest) {
  330. final requests = IXDeveloperTools._instance._apiRequests;
  331. final index = requests.indexWhere((r) => r.id == updatedRequest.id);
  332. if (index != -1) {
  333. requests[index] = updatedRequest;
  334. }
  335. }
  336. }
  337. /// 简化版开发者工具主界面
  338. class SimpleDeveloperToolsScreen extends StatefulWidget {
  339. const SimpleDeveloperToolsScreen({super.key});
  340. @override
  341. State<SimpleDeveloperToolsScreen> createState() =>
  342. _SimpleDeveloperToolsScreenState();
  343. }
  344. class _SimpleDeveloperToolsScreenState extends State<SimpleDeveloperToolsScreen>
  345. with SingleTickerProviderStateMixin {
  346. late TabController _tabController;
  347. @override
  348. void initState() {
  349. super.initState();
  350. _tabController = TabController(length: 2, vsync: this);
  351. _tabController.index = 1;
  352. }
  353. @override
  354. void dispose() {
  355. _tabController.dispose();
  356. super.dispose();
  357. }
  358. @override
  359. Widget build(BuildContext context) {
  360. return Scaffold(
  361. backgroundColor: Get.reactiveTheme.scaffoldBackgroundColor,
  362. appBar: AppBar(
  363. elevation: 0,
  364. iconTheme: IconThemeData(
  365. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  366. ),
  367. title: Text(
  368. '开发者工具',
  369. style: TextStyle(
  370. fontWeight: FontWeight.bold,
  371. fontSize: 18,
  372. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  373. ),
  374. ),
  375. bottom: PreferredSize(
  376. preferredSize: const Size.fromHeight(50),
  377. child: Container(
  378. margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  379. decoration: BoxDecoration(
  380. color: Colors.grey[100],
  381. borderRadius: BorderRadius.circular(25),
  382. boxShadow: [
  383. BoxShadow(
  384. color: Colors.grey[300]!.withValues(alpha: 0.5),
  385. blurRadius: 4,
  386. offset: const Offset(0, 2),
  387. ),
  388. ],
  389. ),
  390. child: TabBar(
  391. controller: _tabController,
  392. indicator: BoxDecoration(
  393. gradient: LinearGradient(
  394. colors: [Colors.blue[500]!, Colors.purple[500]!],
  395. ),
  396. borderRadius: BorderRadius.circular(25),
  397. boxShadow: [
  398. BoxShadow(
  399. color: Colors.blue[200]!.withValues(alpha: 0.5),
  400. blurRadius: 6,
  401. offset: const Offset(0, 2),
  402. ),
  403. ],
  404. ),
  405. indicatorSize: TabBarIndicatorSize.tab,
  406. dividerColor: Colors.transparent,
  407. labelColor: Colors.white,
  408. unselectedLabelColor: Colors.grey[600],
  409. labelStyle: const TextStyle(
  410. fontWeight: FontWeight.bold,
  411. fontSize: 14,
  412. ),
  413. unselectedLabelStyle: const TextStyle(
  414. fontWeight: FontWeight.w500,
  415. fontSize: 14,
  416. ),
  417. tabs: const [
  418. Tab(
  419. child: Row(
  420. mainAxisAlignment: MainAxisAlignment.center,
  421. children: [
  422. Icon(Icons.terminal, size: 18),
  423. SizedBox(width: 6),
  424. Text('控制台日志'),
  425. ],
  426. ),
  427. ),
  428. Tab(
  429. child: Row(
  430. mainAxisAlignment: MainAxisAlignment.center,
  431. children: [
  432. Icon(Icons.network_check, size: 18),
  433. SizedBox(width: 6),
  434. Text('API监控'),
  435. ],
  436. ),
  437. ),
  438. ],
  439. ),
  440. ),
  441. ),
  442. ),
  443. body: Container(
  444. decoration: const BoxDecoration(
  445. gradient: LinearGradient(
  446. begin: Alignment.topCenter,
  447. end: Alignment.bottomCenter,
  448. colors: [Colors.grey, Colors.blue],
  449. ),
  450. ),
  451. child: TabBarView(
  452. controller: _tabController,
  453. children: const [SimpleConsoleLogTab(), SimpleApiMonitorTab()],
  454. ),
  455. ),
  456. );
  457. }
  458. }
  459. /// 简化版开发者工具对话框
  460. class SimpleDeveloperToolsDialog extends StatelessWidget {
  461. const SimpleDeveloperToolsDialog({super.key});
  462. @override
  463. Widget build(BuildContext context) {
  464. return Dialog(
  465. backgroundColor: Colors.transparent,
  466. elevation: 0,
  467. child: Container(
  468. width: Get.width * 0.95,
  469. height: Get.height * 0.9,
  470. decoration: BoxDecoration(
  471. color: Colors.white,
  472. borderRadius: BorderRadius.circular(20),
  473. boxShadow: [
  474. BoxShadow(
  475. color: Colors.black.withValues(alpha: 0.1),
  476. blurRadius: 20,
  477. offset: const Offset(0, 10),
  478. ),
  479. ],
  480. ),
  481. child: ClipRRect(
  482. borderRadius: BorderRadius.circular(20),
  483. child: const SimpleDeveloperToolsScreen(),
  484. ),
  485. ),
  486. );
  487. }
  488. }
  489. /// 简化版控制台日志标签页
  490. class SimpleConsoleLogTab extends StatefulWidget {
  491. const SimpleConsoleLogTab({super.key});
  492. @override
  493. State<SimpleConsoleLogTab> createState() => _SimpleConsoleLogTabState();
  494. }
  495. class _SimpleConsoleLogTabState extends State<SimpleConsoleLogTab> {
  496. final TextEditingController _searchController = TextEditingController();
  497. List<ConsoleLogEntry> _filteredLogs = [];
  498. @override
  499. void initState() {
  500. super.initState();
  501. _updateLogs();
  502. }
  503. void _updateLogs() {
  504. setState(() {
  505. final allLogs = IXDeveloperTools.logs;
  506. if (_searchController.text.isEmpty) {
  507. _filteredLogs = allLogs;
  508. } else {
  509. _filteredLogs = allLogs
  510. .where(
  511. (log) =>
  512. log.message.toLowerCase().contains(
  513. _searchController.text.toLowerCase(),
  514. ) ||
  515. log.tag.toLowerCase().contains(
  516. _searchController.text.toLowerCase(),
  517. ),
  518. )
  519. .toList();
  520. }
  521. });
  522. }
  523. @override
  524. Widget build(BuildContext context) {
  525. return Column(
  526. children: [
  527. // 搜索和操作栏
  528. Container(
  529. margin: const EdgeInsets.all(16),
  530. padding: const EdgeInsets.all(16),
  531. decoration: BoxDecoration(
  532. color: Colors.white,
  533. borderRadius: BorderRadius.circular(16),
  534. boxShadow: [
  535. BoxShadow(
  536. color: Colors.grey[200]!.withValues(alpha: 0.8),
  537. blurRadius: 10,
  538. offset: const Offset(0, 4),
  539. ),
  540. ],
  541. ),
  542. child: Column(
  543. children: [
  544. // 搜索框
  545. Container(
  546. decoration: BoxDecoration(
  547. color: Colors.grey[50],
  548. borderRadius: BorderRadius.circular(12),
  549. border: Border.all(color: Colors.blue[200]!, width: 1),
  550. ),
  551. child: TextField(
  552. controller: _searchController,
  553. decoration: InputDecoration(
  554. hintText: '搜索日志内容...',
  555. hintStyle: TextStyle(color: Colors.grey[500]),
  556. prefixIcon: Icon(Icons.search, color: Colors.blue[600]),
  557. border: InputBorder.none,
  558. contentPadding: const EdgeInsets.symmetric(
  559. horizontal: 16,
  560. vertical: 12,
  561. ),
  562. ),
  563. onChanged: (_) => _updateLogs(),
  564. ),
  565. ),
  566. const SizedBox(height: 12),
  567. // 操作按钮
  568. Row(
  569. children: [
  570. Expanded(
  571. child: ElevatedButton.icon(
  572. icon: const Icon(Icons.refresh, size: 18),
  573. label: const Text('刷新'),
  574. style: ElevatedButton.styleFrom(
  575. backgroundColor: Colors.blue[500],
  576. foregroundColor: Colors.white,
  577. elevation: 2,
  578. padding: const EdgeInsets.symmetric(vertical: 12),
  579. shape: RoundedRectangleBorder(
  580. borderRadius: BorderRadius.circular(10),
  581. ),
  582. ),
  583. onPressed: _updateLogs,
  584. ),
  585. ),
  586. const SizedBox(width: 12),
  587. Expanded(
  588. child: ElevatedButton.icon(
  589. icon: const Icon(Icons.clear_all, size: 18),
  590. label: const Text('清除'),
  591. style: ElevatedButton.styleFrom(
  592. backgroundColor: Colors.red[500],
  593. foregroundColor: Colors.white,
  594. elevation: 2,
  595. padding: const EdgeInsets.symmetric(vertical: 12),
  596. shape: RoundedRectangleBorder(
  597. borderRadius: BorderRadius.circular(10),
  598. ),
  599. ),
  600. onPressed: () {
  601. IXDeveloperTools.clearLogs();
  602. _updateLogs();
  603. },
  604. ),
  605. ),
  606. ],
  607. ),
  608. ],
  609. ),
  610. ),
  611. // 日志列表
  612. Expanded(
  613. child: _filteredLogs.isEmpty
  614. ? Center(
  615. child: Container(
  616. padding: const EdgeInsets.all(32),
  617. child: Column(
  618. mainAxisSize: MainAxisSize.min,
  619. children: [
  620. Container(
  621. padding: const EdgeInsets.all(20),
  622. decoration: BoxDecoration(
  623. gradient: LinearGradient(
  624. colors: [Colors.blue[100]!, Colors.purple[100]!],
  625. ),
  626. shape: BoxShape.circle,
  627. ),
  628. child: Icon(
  629. Icons.terminal,
  630. size: 48,
  631. color: Colors.blue[600],
  632. ),
  633. ),
  634. const SizedBox(height: 16),
  635. Text(
  636. '暂无日志',
  637. style: TextStyle(
  638. fontSize: 18,
  639. fontWeight: FontWeight.bold,
  640. color: Colors.grey[600],
  641. ),
  642. ),
  643. const SizedBox(height: 8),
  644. Text(
  645. '开始使用应用后,日志将显示在这里',
  646. style: TextStyle(
  647. fontSize: 14,
  648. color: Colors.grey[500],
  649. ),
  650. ),
  651. ],
  652. ),
  653. ),
  654. )
  655. : RefreshIndicator(
  656. onRefresh: () async {
  657. _updateLogs();
  658. },
  659. child: ListView.builder(
  660. physics: const AlwaysScrollableScrollPhysics(),
  661. itemCount: _filteredLogs.length,
  662. itemBuilder: (context, index) {
  663. final log = _filteredLogs[index];
  664. return SimpleLogCard(log: log);
  665. },
  666. ),
  667. ),
  668. ),
  669. ],
  670. );
  671. }
  672. }
  673. /// 简化版API监控标签页
  674. class SimpleApiMonitorTab extends StatefulWidget {
  675. const SimpleApiMonitorTab({super.key});
  676. @override
  677. State<SimpleApiMonitorTab> createState() => _SimpleApiMonitorTabState();
  678. }
  679. class _SimpleApiMonitorTabState extends State<SimpleApiMonitorTab> {
  680. @override
  681. Widget build(BuildContext context) {
  682. final requests = IXDeveloperTools.apiRequests;
  683. return Column(
  684. children: [
  685. // 操作栏
  686. Container(
  687. margin: const EdgeInsets.all(16),
  688. padding: const EdgeInsets.all(16),
  689. decoration: BoxDecoration(
  690. color: Colors.white,
  691. borderRadius: BorderRadius.circular(16),
  692. boxShadow: [
  693. BoxShadow(
  694. color: Colors.grey[200]!.withValues(alpha: 0.8),
  695. blurRadius: 10,
  696. offset: const Offset(0, 4),
  697. ),
  698. ],
  699. ),
  700. child: Column(
  701. children: [
  702. // 统计信息
  703. Container(
  704. width: double.infinity,
  705. padding: const EdgeInsets.all(12),
  706. decoration: BoxDecoration(
  707. gradient: LinearGradient(
  708. colors: [Colors.blue[50]!, Colors.purple[50]!],
  709. ),
  710. borderRadius: BorderRadius.circular(12),
  711. border: Border.all(color: Colors.blue[200]!, width: 1),
  712. ),
  713. child: Row(
  714. children: [
  715. Icon(Icons.analytics, color: Colors.blue[600], size: 20),
  716. const SizedBox(width: 8),
  717. Text(
  718. '共 ${requests.length} 个请求',
  719. style: TextStyle(
  720. fontSize: 16,
  721. fontWeight: FontWeight.bold,
  722. color: Colors.blue[700],
  723. ),
  724. ),
  725. const Spacer(),
  726. Container(
  727. padding: const EdgeInsets.symmetric(
  728. horizontal: 8,
  729. vertical: 4,
  730. ),
  731. decoration: BoxDecoration(
  732. color: Colors.blue[100],
  733. borderRadius: BorderRadius.circular(8),
  734. ),
  735. child: Text(
  736. '实时监控',
  737. style: TextStyle(
  738. fontSize: 12,
  739. color: Colors.blue[700],
  740. fontWeight: FontWeight.w500,
  741. ),
  742. ),
  743. ),
  744. ],
  745. ),
  746. ),
  747. const SizedBox(height: 12),
  748. // 操作按钮
  749. Row(
  750. children: [
  751. Expanded(
  752. child: ElevatedButton.icon(
  753. icon: const Icon(Icons.refresh, size: 18),
  754. label: const Text('刷新'),
  755. style: ElevatedButton.styleFrom(
  756. backgroundColor: Colors.green[500],
  757. foregroundColor: Colors.white,
  758. elevation: 2,
  759. padding: const EdgeInsets.symmetric(vertical: 12),
  760. shape: RoundedRectangleBorder(
  761. borderRadius: BorderRadius.circular(10),
  762. ),
  763. ),
  764. onPressed: () => setState(() {}),
  765. ),
  766. ),
  767. const SizedBox(width: 12),
  768. Expanded(
  769. child: ElevatedButton.icon(
  770. icon: const Icon(Icons.clear_all, size: 18),
  771. label: const Text('清除'),
  772. style: ElevatedButton.styleFrom(
  773. backgroundColor: Colors.orange[500],
  774. foregroundColor: Colors.white,
  775. elevation: 2,
  776. padding: const EdgeInsets.symmetric(vertical: 12),
  777. shape: RoundedRectangleBorder(
  778. borderRadius: BorderRadius.circular(10),
  779. ),
  780. ),
  781. onPressed: () {
  782. IXDeveloperTools.clearApiRequests();
  783. setState(() {});
  784. },
  785. ),
  786. ),
  787. ],
  788. ),
  789. ],
  790. ),
  791. ),
  792. // API请求列表
  793. Expanded(
  794. child: requests.isEmpty
  795. ? Center(
  796. child: Container(
  797. padding: const EdgeInsets.all(32),
  798. child: Column(
  799. mainAxisSize: MainAxisSize.min,
  800. children: [
  801. Container(
  802. padding: const EdgeInsets.all(20),
  803. decoration: BoxDecoration(
  804. gradient: LinearGradient(
  805. colors: [Colors.green[100]!, Colors.blue[100]!],
  806. ),
  807. shape: BoxShape.circle,
  808. ),
  809. child: Icon(
  810. Icons.network_check,
  811. size: 48,
  812. color: Colors.green[600],
  813. ),
  814. ),
  815. const SizedBox(height: 16),
  816. Text(
  817. '暂无API请求',
  818. style: TextStyle(
  819. fontSize: 18,
  820. fontWeight: FontWeight.bold,
  821. color: Colors.grey[600],
  822. ),
  823. ),
  824. const SizedBox(height: 8),
  825. Text(
  826. '开始网络请求后,API信息将显示在这里',
  827. style: TextStyle(
  828. fontSize: 14,
  829. color: Colors.grey[500],
  830. ),
  831. ),
  832. ],
  833. ),
  834. ),
  835. )
  836. : RefreshIndicator(
  837. onRefresh: () async {
  838. setState(() {});
  839. },
  840. child: ListView.builder(
  841. physics: const AlwaysScrollableScrollPhysics(),
  842. itemCount: requests.length,
  843. itemBuilder: (context, index) {
  844. final request = requests[index];
  845. return SimpleApiCard(request: request);
  846. },
  847. ),
  848. ),
  849. ),
  850. ],
  851. );
  852. }
  853. }