app_router.dart 15 KB

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