ix_developer_tools.dart 28 KB

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