app_router.dart 15 KB

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