ix_developer_tools.dart 28 KB

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