app_router.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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. GoRouter createAppRouter() => GoRouter(
  57. observers: [routeObserver],
  58. initialLocation: '/',
  59. routes: [
  60. // ── 根 Tab ──
  61. GoRoute(
  62. path: '/',
  63. pageBuilder: (_, _) => const MaterialPage(
  64. child: AppScaffold(showTabBar: true, body: HomePage()),
  65. ),
  66. ),
  67. GoRoute(
  68. path: '/messages',
  69. pageBuilder: (_, _) => const MaterialPage(
  70. child: AppScaffold(showTabBar: true, body: MessageListPage()),
  71. ),
  72. ),
  73. GoRoute(
  74. path: '/profile',
  75. pageBuilder: (_, _) => const MaterialPage(
  76. child: AppScaffold(showTabBar: true, body: ProfilePage()),
  77. ),
  78. ),
  79. // ── 费用报销 ──
  80. GoRoute(
  81. path: '/expense/list',
  82. pageBuilder: (context, state) => MaterialPage(
  83. key: ValueKey(state.uri.toString()),
  84. child: AppScaffold(
  85. navConfig: NavBarConfig(
  86. showBack: true,
  87. showRight: true,
  88. rightWidget: _addButton(context, '/expense/create'),
  89. onBack: _popToNative,
  90. ),
  91. body: const ExpenseListPage(),
  92. ),
  93. ),
  94. ),
  95. GoRoute(
  96. path: '/expense/import-apply',
  97. pageBuilder: (context, _) => MaterialPage(
  98. child: AppScaffold(
  99. navConfig: NavBarConfig(
  100. showBack: true,
  101. onBack: () => _smartBack(context),
  102. ),
  103. body: const ExpenseApplyImportPage(),
  104. resizeToAvoidBottomInset: false,
  105. ),
  106. ),
  107. ),
  108. GoRoute(
  109. path: '/expense/create',
  110. pageBuilder: (context, state) => NoSwipeBackPage(
  111. key: ValueKey(state.uri.toString()),
  112. name: state.uri.toString(),
  113. canPop: false,
  114. child: AppScaffold(
  115. navConfig: NavBarConfig(showBack: true),
  116. body: ExpenseCreatePage(editId: state.uri.queryParameters['id']),
  117. resizeToAvoidBottomInset: false,
  118. ),
  119. ),
  120. ),
  121. GoRoute(
  122. path: '/expense/detail/:billNo',
  123. pageBuilder: (context, state) => MaterialPage(
  124. key: ValueKey(state.uri.toString()),
  125. child: AppScaffold(
  126. navConfig: NavBarConfig(
  127. showBack: true,
  128. onBack: () => _smartBack(context),
  129. ),
  130. body: ExpenseDetailPage(
  131. billNo: state.pathParameters['billNo']!,
  132. queryId:
  133. int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0,
  134. ),
  135. ),
  136. ),
  137. ),
  138. GoRoute(
  139. path: '/expense/edit/:billNo',
  140. pageBuilder: (context, state) => NoSwipeBackPage(
  141. key: ValueKey(state.uri.toString()),
  142. name: state.uri.toString(),
  143. canPop: false,
  144. child: AppScaffold(
  145. navConfig: NavBarConfig(
  146. showBack: true,
  147. onBack: () => _smartBack(context),
  148. ),
  149. body: ExpenseEditPage(billNo: state.pathParameters['billNo']!),
  150. resizeToAvoidBottomInset: false,
  151. ),
  152. ),
  153. ),
  154. // ── 加班 ──
  155. GoRoute(
  156. path: '/overtime-apply/list',
  157. pageBuilder: (context, state) => MaterialPage(
  158. key: ValueKey(state.uri.toString()),
  159. child: AppScaffold(
  160. navConfig: NavBarConfig(
  161. showBack: true,
  162. showRight: true,
  163. rightWidget: _addButton(context, '/overtime-apply/create'),
  164. onBack: _popToNative,
  165. ),
  166. body: const OvertimeApplyListPage(),
  167. ),
  168. ),
  169. ),
  170. GoRoute(
  171. path: '/overtime-apply/create',
  172. pageBuilder: (context, state) => NoSwipeBackPage(
  173. key: ValueKey(state.uri.toString()),
  174. name: state.uri.toString(),
  175. canPop: false,
  176. child: AppScaffold(
  177. navConfig: NavBarConfig(showBack: true),
  178. body: const OvertimeApplyCreatePage(),
  179. resizeToAvoidBottomInset: false,
  180. ),
  181. ),
  182. ),
  183. GoRoute(
  184. path: '/overtime-apply/detail/:billNo',
  185. pageBuilder: (context, state) => MaterialPage(
  186. child: AppScaffold(
  187. navConfig: NavBarConfig(
  188. showBack: true,
  189. onBack: () => _smartBack(context),
  190. ),
  191. body: OvertimeApplyDetailPage(
  192. billNo: state.pathParameters['billNo']!,
  193. ),
  194. ),
  195. ),
  196. ),
  197. GoRoute(
  198. path: '/overtime-apply/edit/:billNo',
  199. pageBuilder: (context, state) => NoSwipeBackPage(
  200. key: ValueKey(state.uri.toString()),
  201. name: state.uri.toString(),
  202. canPop: false,
  203. child: AppScaffold(
  204. navConfig: NavBarConfig(
  205. showBack: true,
  206. onBack: () => _smartBack(context),
  207. ),
  208. body: OvertimeApplyEditPage(billNo: state.pathParameters['billNo']!),
  209. resizeToAvoidBottomInset: false,
  210. ),
  211. ),
  212. ),
  213. // ── 用车 ──
  214. GoRoute(
  215. path: '/vehicle-apply/list',
  216. pageBuilder: (context, state) => MaterialPage(
  217. key: ValueKey(state.uri.toString()),
  218. child: AppScaffold(
  219. navConfig: NavBarConfig(
  220. showBack: true,
  221. showRight: true,
  222. rightWidget: _addButton(context, '/vehicle-apply/create'),
  223. onBack: _popToNative,
  224. ),
  225. body: const VehicleApplyListPage(),
  226. ),
  227. ),
  228. ),
  229. GoRoute(
  230. path: '/vehicle-apply/create',
  231. pageBuilder: (context, state) => NoSwipeBackPage(
  232. key: ValueKey(state.uri.toString()),
  233. name: state.uri.toString(),
  234. canPop: false,
  235. child: AppScaffold(
  236. navConfig: NavBarConfig(showBack: true),
  237. body: const VehicleApplyCreatePage(),
  238. resizeToAvoidBottomInset: false,
  239. ),
  240. ),
  241. ),
  242. GoRoute(
  243. path: '/vehicle-apply/detail/:billNo',
  244. pageBuilder: (context, state) => MaterialPage(
  245. child: AppScaffold(
  246. navConfig: NavBarConfig(
  247. showBack: true,
  248. onBack: () => _smartBack(context),
  249. ),
  250. body: VehicleApplyDetailPage(billNo: state.pathParameters['billNo']!),
  251. ),
  252. ),
  253. ),
  254. GoRoute(
  255. path: '/vehicle-apply/edit/:billNo',
  256. pageBuilder: (context, state) => NoSwipeBackPage(
  257. key: ValueKey(state.uri.toString()),
  258. name: state.uri.toString(),
  259. canPop: false,
  260. child: AppScaffold(
  261. navConfig: NavBarConfig(
  262. showBack: true,
  263. onBack: () => _smartBack(context),
  264. ),
  265. body: VehicleApplyEditPage(billNo: state.pathParameters['billNo']!),
  266. resizeToAvoidBottomInset: false,
  267. ),
  268. ),
  269. ),
  270. // ── 外勤日志 ──
  271. GoRoute(
  272. path: '/outing-log/list',
  273. pageBuilder: (context, state) => MaterialPage(
  274. key: ValueKey(state.uri.toString()),
  275. child: AppScaffold(
  276. navConfig: NavBarConfig(
  277. showBack: true,
  278. showRight: true,
  279. rightWidget: _addButton(context, '/outing-log/create'),
  280. onBack: _popToNative,
  281. ),
  282. body: const OutingLogListPage(),
  283. ),
  284. ),
  285. ),
  286. GoRoute(
  287. path: '/outing-log/create',
  288. pageBuilder: (context, state) => NoSwipeBackPage(
  289. key: ValueKey(state.uri.toString()),
  290. name: state.uri.toString(),
  291. canPop: false,
  292. child: AppScaffold(
  293. navConfig: NavBarConfig(showBack: true),
  294. body: const OutingLogCreatePage(),
  295. ),
  296. ),
  297. ),
  298. GoRoute(
  299. path: '/outing-log/detail/:id',
  300. pageBuilder: (context, state) => MaterialPage(
  301. child: AppScaffold(
  302. navConfig: NavBarConfig(
  303. showBack: true,
  304. onBack: () => _smartBack(context),
  305. ),
  306. body: OutingLogDetailPage(id: state.pathParameters['id']!),
  307. ),
  308. ),
  309. ),
  310. // ── 公告 ──
  311. GoRoute(
  312. path: '/announcement/list',
  313. pageBuilder: (context, state) => MaterialPage(
  314. key: ValueKey(state.uri.toString()),
  315. child: AppScaffold(
  316. navConfig: NavBarConfig(
  317. showBack: true,
  318. showRight: true,
  319. rightWidget: _addButton(context, '/announcement/create'),
  320. onBack: _popToNative,
  321. ),
  322. body: const AnnouncementListPage(),
  323. ),
  324. ),
  325. ),
  326. GoRoute(
  327. path: '/announcement/detail/:id',
  328. pageBuilder: (context, state) => MaterialPage(
  329. child: AppScaffold(
  330. navConfig: NavBarConfig(
  331. showBack: true,
  332. onBack: () => _smartBack(context),
  333. ),
  334. body: AnnouncementDetailPage(id: state.pathParameters['id']!),
  335. ),
  336. ),
  337. ),
  338. GoRoute(
  339. path: '/announcement/create',
  340. pageBuilder: (context, state) => MaterialPage(
  341. key: ValueKey(state.uri.toString()),
  342. child: AppScaffold(
  343. navConfig: NavBarConfig(showBack: true),
  344. body: const AnnouncementCreatePage(),
  345. ),
  346. ),
  347. ),
  348. // ── 费用申请 ──
  349. GoRoute(
  350. path: '/expense-apply/list',
  351. pageBuilder: (context, state) => MaterialPage(
  352. key: ValueKey(state.uri.toString()),
  353. child: AppScaffold(
  354. navConfig: NavBarConfig(
  355. showBack: true,
  356. showRight: true,
  357. rightWidget: _addButton(context, '/expense-apply/create'),
  358. onBack: _popToNative,
  359. ),
  360. body: const ExpenseApplyListPage(),
  361. ),
  362. ),
  363. ),
  364. GoRoute(
  365. path: '/expense-apply/detail/:billNo',
  366. pageBuilder: (context, state) => MaterialPage(
  367. key: ValueKey(state.uri.toString()),
  368. child: AppScaffold(
  369. navConfig: NavBarConfig(
  370. showBack: true,
  371. onBack: () => _smartBack(context),
  372. ),
  373. body: ExpenseApplyDetailPage(
  374. billNo: state.pathParameters['billNo']!,
  375. queryId:
  376. int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0,
  377. ),
  378. ),
  379. ),
  380. ),
  381. GoRoute(
  382. path: '/expense-apply/create',
  383. pageBuilder: (context, state) => NoSwipeBackPage(
  384. key: ValueKey(state.uri.toString()),
  385. name: state.uri.toString(),
  386. canPop: false,
  387. child: AppScaffold(
  388. navConfig: NavBarConfig(showBack: true),
  389. body: ExpenseApplyCreatePage(id: state.uri.queryParameters['id']),
  390. resizeToAvoidBottomInset: false,
  391. ),
  392. ),
  393. ),
  394. GoRoute(
  395. path: '/expense-apply/edit/:billNo',
  396. pageBuilder: (context, state) => NoSwipeBackPage(
  397. key: ValueKey(state.uri.toString()),
  398. name: state.uri.toString(),
  399. canPop: false,
  400. child: AppScaffold(
  401. navConfig: NavBarConfig(
  402. showBack: true,
  403. onBack: () => _smartBack(context),
  404. ),
  405. body: ExpenseApplyEditPage(billNo: state.pathParameters['billNo']!),
  406. resizeToAvoidBottomInset: false,
  407. ),
  408. ),
  409. ),
  410. // ── 报表 ──
  411. GoRoute(
  412. path: '/report/expense-detail',
  413. pageBuilder: (context, _) => MaterialPage(
  414. child: AppScaffold(
  415. navConfig: NavBarConfig(
  416. showBack: true,
  417. onBack: () => _smartBack(context),
  418. ),
  419. body: const ExpenseDetailReportPage(),
  420. ),
  421. ),
  422. ),
  423. GoRoute(
  424. path: '/report/expense-apply-detail',
  425. pageBuilder: (context, _) => MaterialPage(
  426. child: AppScaffold(
  427. navConfig: NavBarConfig(
  428. showBack: true,
  429. onBack: () => _smartBack(context),
  430. ),
  431. body: const ExpenseApplyDetailReportPage(),
  432. ),
  433. ),
  434. ),
  435. GoRoute(
  436. path: '/report/overtime-apply-detail',
  437. pageBuilder: (context, _) => MaterialPage(
  438. child: AppScaffold(
  439. navConfig: NavBarConfig(
  440. showBack: true,
  441. onBack: () => _smartBack(context),
  442. ),
  443. body: const OvertimeDetailReportPage(),
  444. ),
  445. ),
  446. ),
  447. GoRoute(
  448. path: '/report/vehicle-apply-detail',
  449. pageBuilder: (context, _) => MaterialPage(
  450. child: AppScaffold(
  451. navConfig: NavBarConfig(
  452. showBack: true,
  453. onBack: () => _smartBack(context),
  454. ),
  455. body: const VehicleDetailReportPage(),
  456. ),
  457. ),
  458. ),
  459. GoRoute(
  460. path: '/report/outing-log-detail',
  461. pageBuilder: (context, _) => MaterialPage(
  462. child: AppScaffold(
  463. navConfig: NavBarConfig(
  464. showBack: true,
  465. onBack: () => _smartBack(context),
  466. ),
  467. body: const OutingLogDetailReportPage(),
  468. ),
  469. ),
  470. ),
  471. // ── 管理 ──
  472. GoRoute(
  473. path: '/admin/permissions',
  474. pageBuilder: (context, _) => MaterialPage(
  475. child: AppScaffold(
  476. navConfig: NavBarConfig(
  477. showBack: true,
  478. onBack: () => _smartBack(context),
  479. ),
  480. body: const AdminPermissionsPage(),
  481. ),
  482. ),
  483. ),
  484. ],
  485. );