app_router.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'package:go_router/go_router.dart';
  4. import '../../shared/widgets/app_scaffold.dart';
  5. import '../../shared/widgets/nav_bar_config.dart';
  6. import 'route_observer.dart';
  7. import '../../features/home/home_page.dart';
  8. import '../../features/messages/message_list_page.dart';
  9. import '../../features/profile/profile_page.dart';
  10. import '../../features/expense/expense_list_page.dart';
  11. import '../../features/expense/expense_create_page.dart';
  12. import '../../features/expense/expense_detail_page.dart';
  13. import '../../features/expense/expense_apply_import_page.dart';
  14. import '../../features/overtime/overtime_apply_list_page.dart';
  15. import '../../features/overtime/overtime_apply_create_page.dart';
  16. import '../../features/overtime/overtime_apply_detail_page.dart';
  17. import '../../features/overtime/overtime_apply_edit_page.dart';
  18. import '../../features/vehicle/vehicle_apply_list_page.dart';
  19. import '../../features/vehicle/vehicle_apply_create_page.dart';
  20. import '../../features/vehicle/vehicle_apply_detail_page.dart';
  21. import '../../features/vehicle/vehicle_apply_edit_page.dart';
  22. import '../../features/outing_log/outing_log_list_page.dart';
  23. import '../../features/outing_log/outing_log_create_page.dart';
  24. import '../../features/outing_log/outing_log_detail_page.dart';
  25. import '../../features/announcement/announcement_list_page.dart';
  26. import '../../features/announcement/announcement_detail_page.dart';
  27. import '../../features/announcement/announcement_create_page.dart';
  28. import '../../features/expense_apply/expense_apply_list_page.dart';
  29. import '../../features/expense_apply/expense_apply_detail_page.dart';
  30. import '../../features/expense_apply/expense_apply_create_page.dart';
  31. import '../../features/expense_apply/expense_apply_edit_page.dart';
  32. import '../../features/expense/expense_edit_page.dart';
  33. import '../../features/report/expense_detail_report_page.dart';
  34. import '../../features/report/overtime_detail_report_page.dart';
  35. import '../../features/report/vehicle_detail_report_page.dart';
  36. import '../../features/report/expense_apply_detail_report_page.dart';
  37. import '../../features/report/outing_log_detail_report_page.dart';
  38. import '../../features/admin/admin_permissions_page.dart';
  39. import 'no_swipe_back_page.dart';
  40. Widget _addButton(BuildContext context, String route) => GestureDetector(
  41. onTap: () => context.push(route),
  42. child: const Icon(Icons.add, color: Color(0xFF333333), size: 22),
  43. );
  44. void _popToNative() {
  45. FocusManager.instance.primaryFocus?.unfocus();
  46. SystemNavigator.pop();
  47. }
  48. void _smartBack(BuildContext context) {
  49. final r = GoRouter.of(context);
  50. if (r.canPop()) {
  51. r.pop();
  52. } else {
  53. SystemNavigator.pop();
  54. }
  55. }
  56. final rootNavigatorKey = GlobalKey<NavigatorState>();
  57. GoRouter createAppRouter() => GoRouter(
  58. navigatorKey: rootNavigatorKey,
  59. observers: [routeObserver],
  60. initialLocation: '/',
  61. routes: [
  62. // ── 根 Tab ──
  63. GoRoute(
  64. path: '/',
  65. pageBuilder: (_, _) => const MaterialPage(
  66. child: AppScaffold(showTabBar: true, body: HomePage()),
  67. ),
  68. ),
  69. GoRoute(
  70. path: '/messages',
  71. pageBuilder: (_, _) => const MaterialPage(
  72. child: AppScaffold(showTabBar: true, body: MessageListPage()),
  73. ),
  74. ),
  75. GoRoute(
  76. path: '/profile',
  77. pageBuilder: (_, _) => const MaterialPage(
  78. child: AppScaffold(showTabBar: true, body: ProfilePage()),
  79. ),
  80. ),
  81. // ── 费用报销 ──
  82. GoRoute(
  83. path: '/expense/list',
  84. pageBuilder: (context, state) => MaterialPage(
  85. key: ValueKey(state.uri.toString()),
  86. child: AppScaffold(
  87. navConfig: NavBarConfig(
  88. showBack: true,
  89. showRight: true,
  90. rightWidget: _addButton(context, '/expense/create'),
  91. onBack: _popToNative,
  92. ),
  93. body: const ExpenseListPage(),
  94. ),
  95. ),
  96. ),
  97. GoRoute(
  98. path: '/expense/import-apply',
  99. pageBuilder: (context, _) => MaterialPage(
  100. child: AppScaffold(
  101. navConfig: NavBarConfig(
  102. showBack: true,
  103. onBack: () => _smartBack(context),
  104. ),
  105. body: const ExpenseApplyImportPage(),
  106. resizeToAvoidBottomInset: false,
  107. ),
  108. ),
  109. ),
  110. GoRoute(
  111. path: '/expense/create',
  112. pageBuilder: (context, state) => NoSwipeBackPage(
  113. key: ValueKey(state.uri.toString()),
  114. name: state.uri.toString(),
  115. canPop: false,
  116. child: AppScaffold(
  117. navConfig: NavBarConfig(showBack: true),
  118. body: ExpenseCreatePage(editId: state.uri.queryParameters['id']),
  119. resizeToAvoidBottomInset: false,
  120. ),
  121. ),
  122. ),
  123. GoRoute(
  124. path: '/expense/detail/:billNo',
  125. pageBuilder: (context, state) => MaterialPage(
  126. key: ValueKey(state.uri.toString()),
  127. child: AppScaffold(
  128. navConfig: NavBarConfig(
  129. showBack: true,
  130. onBack: () => _smartBack(context),
  131. ),
  132. body: ExpenseDetailPage(
  133. billNo: state.pathParameters['billNo']!,
  134. queryId:
  135. int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0,
  136. ),
  137. ),
  138. ),
  139. ),
  140. GoRoute(
  141. path: '/expense/edit/:billNo',
  142. pageBuilder: (context, state) => NoSwipeBackPage(
  143. key: ValueKey(state.uri.toString()),
  144. name: state.uri.toString(),
  145. canPop: false,
  146. child: AppScaffold(
  147. navConfig: NavBarConfig(
  148. showBack: true,
  149. onBack: () => _smartBack(context),
  150. ),
  151. body: ExpenseEditPage(billNo: state.pathParameters['billNo']!),
  152. resizeToAvoidBottomInset: false,
  153. ),
  154. ),
  155. ),
  156. // ── 加班 ──
  157. GoRoute(
  158. path: '/overtime-apply/list',
  159. pageBuilder: (context, state) => MaterialPage(
  160. key: ValueKey(state.uri.toString()),
  161. child: AppScaffold(
  162. navConfig: NavBarConfig(
  163. showBack: true,
  164. showRight: true,
  165. rightWidget: _addButton(context, '/overtime-apply/create'),
  166. onBack: _popToNative,
  167. ),
  168. body: const OvertimeApplyListPage(),
  169. ),
  170. ),
  171. ),
  172. GoRoute(
  173. path: '/overtime-apply/create',
  174. pageBuilder: (context, state) => NoSwipeBackPage(
  175. key: ValueKey(state.uri.toString()),
  176. name: state.uri.toString(),
  177. canPop: false,
  178. child: AppScaffold(
  179. navConfig: NavBarConfig(showBack: true),
  180. body: const OvertimeApplyCreatePage(),
  181. resizeToAvoidBottomInset: false,
  182. ),
  183. ),
  184. ),
  185. GoRoute(
  186. path: '/overtime-apply/detail/:billNo',
  187. pageBuilder: (context, state) => MaterialPage(
  188. child: AppScaffold(
  189. navConfig: NavBarConfig(
  190. showBack: true,
  191. onBack: () => _smartBack(context),
  192. ),
  193. body: OvertimeApplyDetailPage(
  194. billNo: state.pathParameters['billNo']!,
  195. ),
  196. ),
  197. ),
  198. ),
  199. GoRoute(
  200. path: '/overtime-apply/edit/:billNo',
  201. pageBuilder: (context, state) => NoSwipeBackPage(
  202. key: ValueKey(state.uri.toString()),
  203. name: state.uri.toString(),
  204. canPop: false,
  205. child: AppScaffold(
  206. navConfig: NavBarConfig(
  207. showBack: true,
  208. onBack: () => _smartBack(context),
  209. ),
  210. body: OvertimeApplyEditPage(billNo: state.pathParameters['billNo']!),
  211. resizeToAvoidBottomInset: false,
  212. ),
  213. ),
  214. ),
  215. // ── 用车 ──
  216. GoRoute(
  217. path: '/vehicle-apply/list',
  218. pageBuilder: (context, state) => MaterialPage(
  219. key: ValueKey(state.uri.toString()),
  220. child: AppScaffold(
  221. navConfig: NavBarConfig(
  222. showBack: true,
  223. showRight: true,
  224. rightWidget: _addButton(context, '/vehicle-apply/create'),
  225. onBack: _popToNative,
  226. ),
  227. body: const VehicleApplyListPage(),
  228. ),
  229. ),
  230. ),
  231. GoRoute(
  232. path: '/vehicle-apply/create',
  233. pageBuilder: (context, state) => NoSwipeBackPage(
  234. key: ValueKey(state.uri.toString()),
  235. name: state.uri.toString(),
  236. canPop: false,
  237. child: AppScaffold(
  238. navConfig: NavBarConfig(showBack: true),
  239. body: const VehicleApplyCreatePage(),
  240. resizeToAvoidBottomInset: false,
  241. ),
  242. ),
  243. ),
  244. GoRoute(
  245. path: '/vehicle-apply/detail/:billNo',
  246. pageBuilder: (context, state) => MaterialPage(
  247. child: AppScaffold(
  248. navConfig: NavBarConfig(
  249. showBack: true,
  250. onBack: () => _smartBack(context),
  251. ),
  252. body: VehicleApplyDetailPage(billNo: state.pathParameters['billNo']!),
  253. ),
  254. ),
  255. ),
  256. GoRoute(
  257. path: '/vehicle-apply/edit/:billNo',
  258. pageBuilder: (context, state) => NoSwipeBackPage(
  259. key: ValueKey(state.uri.toString()),
  260. name: state.uri.toString(),
  261. canPop: false,
  262. child: AppScaffold(
  263. navConfig: NavBarConfig(
  264. showBack: true,
  265. onBack: () => _smartBack(context),
  266. ),
  267. body: VehicleApplyEditPage(billNo: state.pathParameters['billNo']!),
  268. resizeToAvoidBottomInset: false,
  269. ),
  270. ),
  271. ),
  272. // ── 外勤日志 ──
  273. GoRoute(
  274. path: '/outing-log/list',
  275. pageBuilder: (context, state) => MaterialPage(
  276. key: ValueKey(state.uri.toString()),
  277. child: AppScaffold(
  278. navConfig: NavBarConfig(
  279. showBack: true,
  280. showRight: true,
  281. rightWidget: _addButton(context, '/outing-log/create'),
  282. onBack: _popToNative,
  283. ),
  284. body: const OutingLogListPage(),
  285. ),
  286. ),
  287. ),
  288. GoRoute(
  289. path: '/outing-log/create',
  290. pageBuilder: (context, state) => NoSwipeBackPage(
  291. key: ValueKey(state.uri.toString()),
  292. name: state.uri.toString(),
  293. canPop: false,
  294. child: AppScaffold(
  295. navConfig: NavBarConfig(showBack: true),
  296. body: const OutingLogCreatePage(),
  297. ),
  298. ),
  299. ),
  300. GoRoute(
  301. path: '/outing-log/detail/:id',
  302. pageBuilder: (context, state) => MaterialPage(
  303. child: AppScaffold(
  304. navConfig: NavBarConfig(
  305. showBack: true,
  306. onBack: () => _smartBack(context),
  307. ),
  308. body: OutingLogDetailPage(id: state.pathParameters['id']!),
  309. ),
  310. ),
  311. ),
  312. // ── 公告 ──
  313. GoRoute(
  314. path: '/announcement/list',
  315. pageBuilder: (context, state) => MaterialPage(
  316. key: ValueKey(state.uri.toString()),
  317. child: AppScaffold(
  318. navConfig: NavBarConfig(
  319. showBack: true,
  320. showRight: true,
  321. rightWidget: _addButton(context, '/announcement/create'),
  322. onBack: _popToNative,
  323. ),
  324. body: const AnnouncementListPage(),
  325. ),
  326. ),
  327. ),
  328. GoRoute(
  329. path: '/announcement/detail/:id',
  330. pageBuilder: (context, state) => MaterialPage(
  331. child: AppScaffold(
  332. navConfig: NavBarConfig(
  333. showBack: true,
  334. onBack: () => _smartBack(context),
  335. ),
  336. body: AnnouncementDetailPage(id: state.pathParameters['id']!),
  337. ),
  338. ),
  339. ),
  340. GoRoute(
  341. path: '/announcement/create',
  342. pageBuilder: (context, state) => MaterialPage(
  343. key: ValueKey(state.uri.toString()),
  344. child: AppScaffold(
  345. navConfig: NavBarConfig(showBack: true),
  346. body: const AnnouncementCreatePage(),
  347. ),
  348. ),
  349. ),
  350. // ── 费用申请 ──
  351. GoRoute(
  352. path: '/expense-apply/list',
  353. pageBuilder: (context, state) => MaterialPage(
  354. key: ValueKey(state.uri.toString()),
  355. child: AppScaffold(
  356. navConfig: NavBarConfig(
  357. showBack: true,
  358. showRight: true,
  359. rightWidget: _addButton(context, '/expense-apply/create'),
  360. onBack: _popToNative,
  361. ),
  362. body: const ExpenseApplyListPage(),
  363. ),
  364. ),
  365. ),
  366. GoRoute(
  367. path: '/expense-apply/detail/:billNo',
  368. pageBuilder: (context, state) => MaterialPage(
  369. key: ValueKey(state.uri.toString()),
  370. child: AppScaffold(
  371. navConfig: NavBarConfig(
  372. showBack: true,
  373. onBack: () => _smartBack(context),
  374. ),
  375. body: ExpenseApplyDetailPage(
  376. billNo: state.pathParameters['billNo']!,
  377. queryId:
  378. int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0,
  379. ),
  380. ),
  381. ),
  382. ),
  383. GoRoute(
  384. path: '/expense-apply/create',
  385. pageBuilder: (context, state) => NoSwipeBackPage(
  386. key: ValueKey(state.uri.toString()),
  387. name: state.uri.toString(),
  388. canPop: false,
  389. child: AppScaffold(
  390. navConfig: NavBarConfig(showBack: true),
  391. body: ExpenseApplyCreatePage(id: state.uri.queryParameters['id']),
  392. resizeToAvoidBottomInset: false,
  393. ),
  394. ),
  395. ),
  396. GoRoute(
  397. path: '/expense-apply/edit/:billNo',
  398. pageBuilder: (context, state) => NoSwipeBackPage(
  399. key: ValueKey(state.uri.toString()),
  400. name: state.uri.toString(),
  401. canPop: false,
  402. child: AppScaffold(
  403. navConfig: NavBarConfig(
  404. showBack: true,
  405. onBack: () => _smartBack(context),
  406. ),
  407. body: ExpenseApplyEditPage(billNo: state.pathParameters['billNo']!),
  408. resizeToAvoidBottomInset: false,
  409. ),
  410. ),
  411. ),
  412. // ── 报表 ──
  413. GoRoute(
  414. path: '/report/expense-detail',
  415. pageBuilder: (context, _) => MaterialPage(
  416. child: AppScaffold(
  417. navConfig: NavBarConfig(
  418. showBack: true,
  419. onBack: () => _smartBack(context),
  420. ),
  421. body: const ExpenseDetailReportPage(),
  422. ),
  423. ),
  424. ),
  425. GoRoute(
  426. path: '/report/expense-apply-detail',
  427. pageBuilder: (context, _) => MaterialPage(
  428. child: AppScaffold(
  429. navConfig: NavBarConfig(
  430. showBack: true,
  431. onBack: () => _smartBack(context),
  432. ),
  433. body: const ExpenseApplyDetailReportPage(),
  434. ),
  435. ),
  436. ),
  437. GoRoute(
  438. path: '/report/overtime-apply-detail',
  439. pageBuilder: (context, _) => MaterialPage(
  440. child: AppScaffold(
  441. navConfig: NavBarConfig(
  442. showBack: true,
  443. onBack: () => _smartBack(context),
  444. ),
  445. body: const OvertimeDetailReportPage(),
  446. ),
  447. ),
  448. ),
  449. GoRoute(
  450. path: '/report/vehicle-apply-detail',
  451. pageBuilder: (context, _) => MaterialPage(
  452. child: AppScaffold(
  453. navConfig: NavBarConfig(
  454. showBack: true,
  455. onBack: () => _smartBack(context),
  456. ),
  457. body: const VehicleDetailReportPage(),
  458. ),
  459. ),
  460. ),
  461. GoRoute(
  462. path: '/report/outing-log-detail',
  463. pageBuilder: (context, _) => MaterialPage(
  464. child: AppScaffold(
  465. navConfig: NavBarConfig(
  466. showBack: true,
  467. onBack: () => _smartBack(context),
  468. ),
  469. body: const OutingLogDetailReportPage(),
  470. ),
  471. ),
  472. ),
  473. // ── 管理 ──
  474. GoRoute(
  475. path: '/admin/permissions',
  476. pageBuilder: (context, _) => MaterialPage(
  477. child: AppScaffold(
  478. navConfig: NavBarConfig(
  479. showBack: true,
  480. onBack: () => _smartBack(context),
  481. ),
  482. body: const AdminPermissionsPage(),
  483. ),
  484. ),
  485. ),
  486. ],
  487. );