app_router.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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. ),
  180. ),
  181. ),
  182. GoRoute(
  183. path: '/overtime-apply/detail/:billNo',
  184. pageBuilder: (context, state) => MaterialPage(
  185. child: AppScaffold(
  186. navConfig: NavBarConfig(
  187. showBack: true,
  188. onBack: () => _smartBack(context),
  189. ),
  190. body: OvertimeApplyDetailPage(
  191. billNo: state.pathParameters['billNo']!,
  192. ),
  193. ),
  194. ),
  195. ),
  196. GoRoute(
  197. path: '/overtime-apply/edit/:billNo',
  198. pageBuilder: (context, state) => NoSwipeBackPage(
  199. key: ValueKey(state.uri.toString()),
  200. name: state.uri.toString(),
  201. canPop: false,
  202. child: AppScaffold(
  203. navConfig: NavBarConfig(
  204. showBack: true,
  205. onBack: () => _smartBack(context),
  206. ),
  207. body: OvertimeApplyEditPage(billNo: state.pathParameters['billNo']!),
  208. resizeToAvoidBottomInset: false,
  209. ),
  210. ),
  211. ),
  212. // ── 用车 ──
  213. GoRoute(
  214. path: '/vehicle-apply/list',
  215. pageBuilder: (context, state) => MaterialPage(
  216. key: ValueKey(state.uri.toString()),
  217. child: AppScaffold(
  218. navConfig: NavBarConfig(
  219. showBack: true,
  220. showRight: true,
  221. rightWidget: _addButton(context, '/vehicle-apply/create'),
  222. onBack: _popToNative,
  223. ),
  224. body: const VehicleApplyListPage(),
  225. ),
  226. ),
  227. ),
  228. GoRoute(
  229. path: '/vehicle-apply/create',
  230. pageBuilder: (context, state) => NoSwipeBackPage(
  231. key: ValueKey(state.uri.toString()),
  232. name: state.uri.toString(),
  233. canPop: false,
  234. child: AppScaffold(
  235. navConfig: NavBarConfig(showBack: true),
  236. body: const VehicleApplyCreatePage(),
  237. ),
  238. ),
  239. ),
  240. GoRoute(
  241. path: '/vehicle-apply/detail/:billNo',
  242. pageBuilder: (context, state) => MaterialPage(
  243. child: AppScaffold(
  244. navConfig: NavBarConfig(
  245. showBack: true,
  246. onBack: () => _smartBack(context),
  247. ),
  248. body: VehicleApplyDetailPage(billNo: state.pathParameters['billNo']!),
  249. ),
  250. ),
  251. ),
  252. GoRoute(
  253. path: '/vehicle-apply/edit/:billNo',
  254. pageBuilder: (context, state) => NoSwipeBackPage(
  255. key: ValueKey(state.uri.toString()),
  256. name: state.uri.toString(),
  257. canPop: false,
  258. child: AppScaffold(
  259. navConfig: NavBarConfig(
  260. showBack: true,
  261. onBack: () => _smartBack(context),
  262. ),
  263. body: VehicleApplyEditPage(billNo: state.pathParameters['billNo']!),
  264. resizeToAvoidBottomInset: false,
  265. ),
  266. ),
  267. ),
  268. // ── 外勤日志 ──
  269. GoRoute(
  270. path: '/outing-log/list',
  271. pageBuilder: (context, state) => MaterialPage(
  272. key: ValueKey(state.uri.toString()),
  273. child: AppScaffold(
  274. navConfig: NavBarConfig(
  275. showBack: true,
  276. showRight: true,
  277. rightWidget: _addButton(context, '/outing-log/create'),
  278. onBack: _popToNative,
  279. ),
  280. body: const OutingLogListPage(),
  281. ),
  282. ),
  283. ),
  284. GoRoute(
  285. path: '/outing-log/create',
  286. pageBuilder: (context, state) => NoSwipeBackPage(
  287. key: ValueKey(state.uri.toString()),
  288. name: state.uri.toString(),
  289. canPop: false,
  290. child: AppScaffold(
  291. navConfig: NavBarConfig(showBack: true),
  292. body: const OutingLogCreatePage(),
  293. ),
  294. ),
  295. ),
  296. GoRoute(
  297. path: '/outing-log/detail/:id',
  298. pageBuilder: (context, state) => MaterialPage(
  299. child: AppScaffold(
  300. navConfig: NavBarConfig(
  301. showBack: true,
  302. onBack: () => _smartBack(context),
  303. ),
  304. body: OutingLogDetailPage(id: state.pathParameters['id']!),
  305. ),
  306. ),
  307. ),
  308. // ── 公告 ──
  309. GoRoute(
  310. path: '/announcement/list',
  311. pageBuilder: (context, state) => MaterialPage(
  312. key: ValueKey(state.uri.toString()),
  313. child: AppScaffold(
  314. navConfig: NavBarConfig(
  315. showBack: true,
  316. showRight: true,
  317. rightWidget: _addButton(context, '/announcement/create'),
  318. onBack: _popToNative,
  319. ),
  320. body: const AnnouncementListPage(),
  321. ),
  322. ),
  323. ),
  324. GoRoute(
  325. path: '/announcement/detail/:id',
  326. pageBuilder: (context, state) => MaterialPage(
  327. child: AppScaffold(
  328. navConfig: NavBarConfig(
  329. showBack: true,
  330. onBack: () => _smartBack(context),
  331. ),
  332. body: AnnouncementDetailPage(id: state.pathParameters['id']!),
  333. ),
  334. ),
  335. ),
  336. GoRoute(
  337. path: '/announcement/create',
  338. pageBuilder: (context, state) => MaterialPage(
  339. key: ValueKey(state.uri.toString()),
  340. child: AppScaffold(
  341. navConfig: NavBarConfig(showBack: true),
  342. body: const AnnouncementCreatePage(),
  343. ),
  344. ),
  345. ),
  346. // ── 费用申请 ──
  347. GoRoute(
  348. path: '/expense-apply/list',
  349. pageBuilder: (context, state) => MaterialPage(
  350. key: ValueKey(state.uri.toString()),
  351. child: AppScaffold(
  352. navConfig: NavBarConfig(
  353. showBack: true,
  354. showRight: true,
  355. rightWidget: _addButton(context, '/expense-apply/create'),
  356. onBack: _popToNative,
  357. ),
  358. body: const ExpenseApplyListPage(),
  359. ),
  360. ),
  361. ),
  362. GoRoute(
  363. path: '/expense-apply/detail/:billNo',
  364. pageBuilder: (context, state) => MaterialPage(
  365. key: ValueKey(state.uri.toString()),
  366. child: AppScaffold(
  367. navConfig: NavBarConfig(
  368. showBack: true,
  369. onBack: () => _smartBack(context),
  370. ),
  371. body: ExpenseApplyDetailPage(
  372. billNo: state.pathParameters['billNo']!,
  373. queryId:
  374. int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0,
  375. ),
  376. ),
  377. ),
  378. ),
  379. GoRoute(
  380. path: '/expense-apply/create',
  381. pageBuilder: (context, state) => NoSwipeBackPage(
  382. key: ValueKey(state.uri.toString()),
  383. name: state.uri.toString(),
  384. canPop: false,
  385. child: AppScaffold(
  386. navConfig: NavBarConfig(showBack: true),
  387. body: ExpenseApplyCreatePage(id: state.uri.queryParameters['id']),
  388. resizeToAvoidBottomInset: false,
  389. ),
  390. ),
  391. ),
  392. GoRoute(
  393. path: '/expense-apply/edit/:billNo',
  394. pageBuilder: (context, state) => NoSwipeBackPage(
  395. key: ValueKey(state.uri.toString()),
  396. name: state.uri.toString(),
  397. canPop: false,
  398. child: AppScaffold(
  399. navConfig: NavBarConfig(
  400. showBack: true,
  401. onBack: () => _smartBack(context),
  402. ),
  403. body: ExpenseApplyEditPage(billNo: state.pathParameters['billNo']!),
  404. resizeToAvoidBottomInset: false,
  405. ),
  406. ),
  407. ),
  408. // ── 报表 ──
  409. GoRoute(
  410. path: '/report/expense-detail',
  411. pageBuilder: (context, _) => MaterialPage(
  412. child: AppScaffold(
  413. navConfig: NavBarConfig(
  414. showBack: true,
  415. onBack: () => _smartBack(context),
  416. ),
  417. body: const ExpenseDetailReportPage(),
  418. ),
  419. ),
  420. ),
  421. GoRoute(
  422. path: '/report/expense-apply-detail',
  423. pageBuilder: (context, _) => MaterialPage(
  424. child: AppScaffold(
  425. navConfig: NavBarConfig(
  426. showBack: true,
  427. onBack: () => _smartBack(context),
  428. ),
  429. body: const ExpenseApplyDetailReportPage(),
  430. ),
  431. ),
  432. ),
  433. GoRoute(
  434. path: '/report/overtime-apply-detail',
  435. pageBuilder: (context, _) => MaterialPage(
  436. child: AppScaffold(
  437. navConfig: NavBarConfig(
  438. showBack: true,
  439. onBack: () => _smartBack(context),
  440. ),
  441. body: const OvertimeDetailReportPage(),
  442. ),
  443. ),
  444. ),
  445. GoRoute(
  446. path: '/report/vehicle-apply-detail',
  447. pageBuilder: (context, _) => MaterialPage(
  448. child: AppScaffold(
  449. navConfig: NavBarConfig(
  450. showBack: true,
  451. onBack: () => _smartBack(context),
  452. ),
  453. body: const VehicleDetailReportPage(),
  454. ),
  455. ),
  456. ),
  457. GoRoute(
  458. path: '/report/outing-log-detail',
  459. pageBuilder: (context, _) => MaterialPage(
  460. child: AppScaffold(
  461. navConfig: NavBarConfig(
  462. showBack: true,
  463. onBack: () => _smartBack(context),
  464. ),
  465. body: const OutingLogDetailReportPage(),
  466. ),
  467. ),
  468. ),
  469. // ── 管理 ──
  470. GoRoute(
  471. path: '/admin/permissions',
  472. pageBuilder: (context, _) => MaterialPage(
  473. child: AppScaffold(
  474. navConfig: NavBarConfig(
  475. showBack: true,
  476. onBack: () => _smartBack(context),
  477. ),
  478. body: const AdminPermissionsPage(),
  479. ),
  480. ),
  481. ),
  482. ],
  483. );