chengc 2 nedēļas atpakaļ
vecāks
revīzija
4bf57fb787
32 mainītis faili ar 305 papildinājumiem un 892 dzēšanām
  1. 68 36
      lib/core/router/app_router.dart
  2. 0 7
      lib/features/admin/admin_permissions_page.dart
  3. 0 18
      lib/features/announcement/announcement_create_page.dart
  4. 0 7
      lib/features/announcement/announcement_detail_page.dart
  5. 0 6
      lib/features/announcement/announcement_list_page.dart
  6. 0 11
      lib/features/expense/expense_apply_import_page.dart
  7. 2 13
      lib/features/expense/expense_create_page.dart
  8. 0 8
      lib/features/expense/expense_detail_page.dart
  9. 1 15
      lib/features/expense/expense_list_page.dart
  10. 2 9
      lib/features/expense_apply/expense_apply_create_page.dart
  11. 0 8
      lib/features/expense_apply/expense_apply_detail_page.dart
  12. 1 15
      lib/features/expense_apply/expense_apply_list_page.dart
  13. 0 9
      lib/features/home/home_page.dart
  14. 0 38
      lib/features/messages/message_list_page.dart
  15. 0 18
      lib/features/outing_log/outing_log_create_page.dart
  16. 0 7
      lib/features/outing_log/outing_log_detail_page.dart
  17. 0 71
      lib/features/outing_log/outing_log_list_page.dart
  18. 0 19
      lib/features/overtime/overtime_create_page.dart
  19. 0 7
      lib/features/overtime/overtime_detail_page.dart
  20. 0 100
      lib/features/overtime/overtime_list_page.dart
  21. 0 9
      lib/features/profile/profile_page.dart
  22. 21 15
      lib/features/report/expense_apply_detail_report_page.dart
  23. 21 11
      lib/features/report/expense_detail_report_page.dart
  24. 0 7
      lib/features/report/outing_log_detail_report_page.dart
  25. 0 7
      lib/features/report/overtime_detail_report_page.dart
  26. 0 7
      lib/features/report/vehicle_detail_report_page.dart
  27. 0 19
      lib/features/vehicle/vehicle_create_page.dart
  28. 0 7
      lib/features/vehicle/vehicle_detail_page.dart
  29. 0 94
      lib/features/vehicle/vehicle_list_page.dart
  30. 90 186
      lib/shared/widgets/app_scaffold.dart
  31. 89 0
      lib/shared/widgets/app_skeletons.dart
  32. 10 108
      lib/shared/widgets/nav_bar_config.dart

+ 68 - 36
lib/core/router/app_router.dart

@@ -1,14 +1,16 @@
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:go_router/go_router.dart';
 import '../../shared/widgets/app_scaffold.dart';
+import '../../shared/widgets/nav_bar_config.dart';
 import 'route_observer.dart';
 import '../../features/home/home_page.dart';
 import '../../features/messages/message_list_page.dart';
 import '../../features/profile/profile_page.dart';
-import '../../features/expense/expense_apply_import_page.dart';
 import '../../features/expense/expense_list_page.dart';
 import '../../features/expense/expense_create_page.dart';
 import '../../features/expense/expense_detail_page.dart';
+import '../../features/expense/expense_apply_import_page.dart';
 import '../../features/overtime/overtime_list_page.dart';
 import '../../features/overtime/overtime_create_page.dart';
 import '../../features/overtime/overtime_detail_page.dart';
@@ -31,39 +33,69 @@ import '../../features/report/expense_apply_detail_report_page.dart';
 import '../../features/report/outing_log_detail_report_page.dart';
 import '../../features/admin/admin_permissions_page.dart';
 
-GoRouter createAppRouter() {
-  return GoRouter(
-    observers: [routeObserver],
-    initialLocation: '/',
-    routes: [
-      GoRoute(path: '/', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(showTabBar: true, body: HomePage()))),
-      GoRoute(path: '/messages', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(showTabBar: true, body: MessageListPage()))),
-      GoRoute(path: '/profile', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(showTabBar: true, body: ProfilePage()))),
-      GoRoute(path: '/expense/list', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: ExpenseListPage()))),
-      GoRoute(path: '/expense/import-apply', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: ExpenseApplyImportPage(), resizeToAvoidBottomInset: false))),
-      GoRoute(path: '/expense/create', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: ExpenseCreatePage(editId: state.uri.queryParameters['id']), resizeToAvoidBottomInset: false))),
-      GoRoute(path: '/expense/detail/:billNo', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: ExpenseDetailPage(billNo: state.pathParameters['billNo']!, queryId: int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0)))),
-      GoRoute(path: '/overtime/list', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: OvertimeListPage()))),
-      GoRoute(path: '/overtime/create', pageBuilder: (_, state) => MaterialPage(child: AppScaffold(body: OvertimeCreatePage(editId: state.uri.queryParameters['id'])))),
-      GoRoute(path: '/overtime/detail/:id', pageBuilder: (_, state) => MaterialPage(child: AppScaffold(body: OvertimeDetailPage(id: state.pathParameters['id']!)))),
-      GoRoute(path: '/vehicle/list', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: VehicleListPage()))),
-      GoRoute(path: '/vehicle/create', pageBuilder: (_, state) => MaterialPage(child: AppScaffold(body: VehicleCreatePage(editId: state.uri.queryParameters['id'])))),
-      GoRoute(path: '/vehicle/detail/:id', pageBuilder: (_, state) => MaterialPage(child: AppScaffold(body: VehicleDetailPage(id: state.pathParameters['id']!)))),
-      GoRoute(path: '/outing-log/list', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: OutingLogListPage()))),
-      GoRoute(path: '/outing-log/create', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: OutingLogCreatePage()))),
-      GoRoute(path: '/outing-log/detail/:id', pageBuilder: (_, state) => MaterialPage(child: AppScaffold(body: OutingLogDetailPage(id: state.pathParameters['id']!)))),
-      GoRoute(path: '/announcement/list', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: AnnouncementListPage()))),
-      GoRoute(path: '/announcement/detail/:id', pageBuilder: (_, state) => MaterialPage(child: AppScaffold(body: AnnouncementDetailPage(id: state.pathParameters['id']!)))),
-      GoRoute(path: '/announcement/create', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: AnnouncementCreatePage()))),
-      GoRoute(path: '/expense-apply/list', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: ExpenseApplyListPage()))),
-      GoRoute(path: '/expense-apply/detail/:billNo', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: ExpenseApplyDetailPage(billNo: state.pathParameters['billNo']!, queryId: int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0)))),
-      GoRoute(path: '/expense-apply/create', pageBuilder: (_, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(body: ExpenseApplyCreatePage(id: state.uri.queryParameters['id']), resizeToAvoidBottomInset: false))),
-      GoRoute(path: '/report/expense-detail', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: ExpenseDetailReportPage()))),
-      GoRoute(path: '/report/expense-apply-detail', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: ExpenseApplyDetailReportPage()))),
-      GoRoute(path: '/report/overtime-detail', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: OvertimeDetailReportPage()))),
-      GoRoute(path: '/report/vehicle-detail', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: VehicleDetailReportPage()))),
-      GoRoute(path: '/report/outing-log-detail', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: OutingLogDetailReportPage()))),
-      GoRoute(path: '/admin/permissions', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(body: AdminPermissionsPage()))),
-    ],
-  );
+Widget _addButton(BuildContext context, String route) => GestureDetector(
+  onTap: () => context.push(route),
+  child: const Icon(Icons.add, color: Color(0xFF333333), size: 22),
+);
+
+void _popToNative() {
+  FocusManager.instance.primaryFocus?.unfocus();
+  SystemNavigator.pop();
 }
+
+void _smartBack(BuildContext context) {
+  final r = GoRouter.of(context);
+  if (r.canPop()) r.pop(); else SystemNavigator.pop();
+}
+
+GoRouter createAppRouter() => GoRouter(
+  observers: [routeObserver],
+  initialLocation: '/',
+  routes: [
+    // ── 根 Tab ──
+    GoRoute(path: '/', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(showTabBar: true, body: HomePage()))),
+    GoRoute(path: '/messages', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(showTabBar: true, body: MessageListPage()))),
+    GoRoute(path: '/profile', pageBuilder: (_, _) => const MaterialPage(child: AppScaffold(showTabBar: true, body: ProfilePage()))),
+
+    // ── 费用报销 ──
+    GoRoute(path: '/expense/list', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, showRight: true, rightWidget: _addButton(context, '/expense/create'), onBack: _popToNative), body: const ExpenseListPage()))),
+    GoRoute(path: '/expense/import-apply', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: const ExpenseApplyImportPage(), resizeToAvoidBottomInset: false))),
+    GoRoute(path: '/expense/create', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true), body: ExpenseCreatePage(editId: state.uri.queryParameters['id']), resizeToAvoidBottomInset: false))),
+    GoRoute(path: '/expense/detail/:billNo', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: ExpenseDetailPage(billNo: state.pathParameters['billNo']!, queryId: int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0)))),
+
+    // ── 加班 ──
+    GoRoute(path: '/overtime/list', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, showRight: true, rightWidget: _addButton(context, '/overtime/create'), onBack: _popToNative), body: const OvertimeListPage()))),
+    GoRoute(path: '/overtime/create', pageBuilder: (context, state) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true), body: OvertimeCreatePage(editId: state.uri.queryParameters['id'])))),
+    GoRoute(path: '/overtime/detail/:id', pageBuilder: (context, state) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: OvertimeDetailPage(id: state.pathParameters['id']!)))),
+
+    // ── 用车 ──
+    GoRoute(path: '/vehicle/list', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, showRight: true, rightWidget: _addButton(context, '/vehicle/create'), onBack: _popToNative), body: const VehicleListPage()))),
+    GoRoute(path: '/vehicle/create', pageBuilder: (context, state) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true), body: VehicleCreatePage(editId: state.uri.queryParameters['id'])))),
+    GoRoute(path: '/vehicle/detail/:id', pageBuilder: (context, state) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: VehicleDetailPage(id: state.pathParameters['id']!)))),
+
+    // ── 外勤日志 ──
+    GoRoute(path: '/outing-log/list', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, showRight: true, rightWidget: _addButton(context, '/outing-log/create'), onBack: _popToNative), body: const OutingLogListPage()))),
+    GoRoute(path: '/outing-log/create', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true), body: const OutingLogCreatePage()))),
+    GoRoute(path: '/outing-log/detail/:id', pageBuilder: (context, state) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: OutingLogDetailPage(id: state.pathParameters['id']!)))),
+
+    // ── 公告 ──
+    GoRoute(path: '/announcement/list', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, showRight: true, rightWidget: _addButton(context, '/announcement/create'), onBack: _popToNative), body: const AnnouncementListPage()))),
+    GoRoute(path: '/announcement/detail/:id', pageBuilder: (context, state) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: AnnouncementDetailPage(id: state.pathParameters['id']!)))),
+    GoRoute(path: '/announcement/create', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true), body: const AnnouncementCreatePage()))),
+
+    // ── 费用申请 ──
+    GoRoute(path: '/expense-apply/list', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, showRight: true, rightWidget: _addButton(context, '/expense-apply/create'), onBack: _popToNative), body: const ExpenseApplyListPage()))),
+    GoRoute(path: '/expense-apply/detail/:billNo', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: ExpenseApplyDetailPage(billNo: state.pathParameters['billNo']!, queryId: int.tryParse(state.uri.queryParameters['queryId'] ?? '0') ?? 0)))),
+    GoRoute(path: '/expense-apply/create', pageBuilder: (context, state) => MaterialPage(key: ValueKey(state.uri.toString()), child: AppScaffold(navConfig: NavBarConfig(showBack: true), body: ExpenseApplyCreatePage(id: state.uri.queryParameters['id']), resizeToAvoidBottomInset: false))),
+
+    // ── 报表 ──
+    GoRoute(path: '/report/expense-detail', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: const ExpenseDetailReportPage()))),
+    GoRoute(path: '/report/expense-apply-detail', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: const ExpenseApplyDetailReportPage()))),
+    GoRoute(path: '/report/overtime-detail', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: const OvertimeDetailReportPage()))),
+    GoRoute(path: '/report/vehicle-detail', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: const VehicleDetailReportPage()))),
+    GoRoute(path: '/report/outing-log-detail', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: const OutingLogDetailReportPage()))),
+
+    // ── 管理 ──
+    GoRoute(path: '/admin/permissions', pageBuilder: (context, _) => MaterialPage(child: AppScaffold(navConfig: NavBarConfig(showBack: true, onBack: () => _smartBack(context)), body: const AdminPermissionsPage()))),
+  ],
+);

+ 0 - 7
lib/features/admin/admin_permissions_page.dart

@@ -1,10 +1,8 @@
 import 'dart:async';
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/i18n/app_localizations.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 
 /// 权限管理 - 页面3.2 【管理员专属】
 class AdminPermissionsPage extends ConsumerStatefulWidget {
@@ -115,11 +113,6 @@ class _AdminPermissionsPageState extends ConsumerState<AdminPermissionsPage> {
   Widget build(BuildContext context) {
     //final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('permissionManagement'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
     return Column(
       children: [
         _buildSearchBar(),

+ 0 - 18
lib/features/announcement/announcement_create_page.dart

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/responsive.dart';
 import '../../shared/widgets/action_bar.dart';
 import '../../shared/widgets/form_section.dart';
@@ -445,23 +444,6 @@ class _AnnouncementCreatePageState
     final r = ResponsiveHelper.of(context);
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('announcementCreate'),
-      showBack: true,
-      showRight: true,
-      rightWidget: GestureDetector(
-        onTap: _showPreview,
-        child: Text(
-          l10n.get('preview'),
-          style: TextStyle(
-            fontSize: 14,
-            color: colors.primary,
-            fontWeight: FontWeight.w500,
-          ),
-        ),
-      ),
-      onBack: () => context.pop(),
-    ));
 
     return Column(
       children: [

+ 0 - 7
lib/features/announcement/announcement_detail_page.dart

@@ -1,9 +1,7 @@
 import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../core/i18n/app_localizations.dart';
 import 'announcement_list_controller.dart';
@@ -51,11 +49,6 @@ class _AnnouncementDetailPageState
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('announcementDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
 
     _item = mockAnnouncements.firstWhere(
       (e) => e.id == widget.id,

+ 0 - 6
lib/features/announcement/announcement_list_page.dart

@@ -4,7 +4,6 @@ import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:easy_refresh/easy_refresh.dart';
 import '../../core/theme/app_colors_extension.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../core/utils/responsive.dart';
 import '../../shared/widgets/empty_state.dart';
@@ -139,11 +138,6 @@ class _AnnouncementListPageState extends ConsumerState<AnnouncementListPage>
       });
     }
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('announcementList'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
     return Center(
       child: ConstrainedBox(
         constraints: BoxConstraints(maxWidth: r.listMaxWidth),

+ 0 - 11
lib/features/expense/expense_apply_import_page.dart

@@ -1,9 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:easy_refresh/easy_refresh.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/i18n/app_localizations.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../shared/widgets/empty_state.dart';
@@ -574,15 +572,6 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
   Widget build(BuildContext context) {
     final l10n = AppLocalizations.of(context);
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('importExpenseApply'),
-      showBack: true,
-      onBack: () {
-        FocusManager.instance.primaryFocus?.unfocus();
-        context.pop();
-      },
-    ));
-
     final grouped = <String, List<ImportableItem>>{};
     for (final item in _items) {
       grouped.putIfAbsent(item.aeNo, () => []).add(item);

+ 2 - 13
lib/features/expense/expense_create_page.dart

@@ -6,11 +6,11 @@ import 'package:flutter/services.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:go_router/go_router.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/responsive.dart';
 import '../../shared/widgets/form_section.dart';
 import '../../shared/widgets/form_field_row.dart';
 import '../../shared/widgets/app_skeletons.dart';
+import '../../shared/widgets/nav_bar_config.dart';
 import 'expense_api.dart';
 import 'expense_create_controller.dart';
 import '../../core/i18n/app_localizations.dart';
@@ -191,18 +191,7 @@ class _ExpenseCreatePageState extends ConsumerState<ExpenseCreatePage> {
 
     if (_firstBuild) return const Scaffold(body: SkeletonFormPage(showImportLink: true));
 
-    setNavBarTitle(
-      context,
-      ref,
-      NavBarConfig(
-        title: widget.editId != null
-            ? l10n.get('editExpense')
-            : l10n.get('expenseApply'),
-        showBack: true,
-        onBack: () => _doPop(),
-      ),
-    );
-
+    Future.microtask(() => ref.read(pageBackProvider.notifier).state = () => _doPop());
     Widget pageContent = PopScope(
       canPop: false,
       onPopInvokedWithResult: (didPop, _) {

+ 0 - 8
lib/features/expense/expense_detail_page.dart

@@ -2,8 +2,6 @@ import 'dart:typed_data';
 import 'package:flutter/material.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../shared/widgets/loading_dialog.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../shared/widgets/form_section.dart';
@@ -84,12 +82,6 @@ class _ExpenseDetailPageState extends ConsumerState<ExpenseDetailPage>
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('expenseDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
-
     if (_isLoading) {
       return const SkeletonDetailPage();
     }

+ 1 - 15
lib/features/expense/expense_list_page.dart

@@ -1,10 +1,8 @@
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:easy_refresh/easy_refresh.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../core/utils/responsive.dart';
@@ -79,6 +77,7 @@ class _ExpenseListPageState extends ConsumerState<ExpenseListPage> {
   int _daysInMonth(int year, int month) => DateTime(year, month + 1, 0).day;
 
   void _applyFilter() {
+    FocusManager.instance.primaryFocus?.unfocus();
     _refreshCtrl.callRefresh();
   }
 
@@ -119,19 +118,6 @@ class _ExpenseListPageState extends ConsumerState<ExpenseListPage> {
       });
     }
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('expenseList'),
-      showBack: true,
-      onBack: () {
-        FocusManager.instance.primaryFocus?.unfocus();
-        SystemNavigator.pop();
-      },
-      showRight: true,
-      rightWidget: GestureDetector(
-        onTap: () => context.push('/expense/create'),
-        child: Icon(Icons.add, color: colors.textPrimary, size: 22),
-      ),
-    ));
     return Scaffold(
       body: Column(children: [
         Container(

+ 2 - 9
lib/features/expense_apply/expense_apply_create_page.dart

@@ -196,15 +196,7 @@ class _ExpenseApplyCreatePageState extends ConsumerState<ExpenseApplyCreatePage>
       return const Scaffold(body: SkeletonFormPage());
     }
 
-    setNavBarTitle(
-      context,
-      ref,
-      NavBarConfig(
-        title: l10n.get('expenseApplyRequest'),
-        showBack: true,
-        onBack: () => _doPop(),
-      ),
-    );
+    Future.microtask(() => ref.read(pageBackProvider.notifier).state = () => _doPop());
 
     return FutureBuilder<bool>(
       future: _draftFuture,
@@ -218,6 +210,7 @@ class _ExpenseApplyCreatePageState extends ConsumerState<ExpenseApplyCreatePage>
           });
         }
 
+        Future.microtask(() => ref.read(pageBackProvider.notifier).state = () => _doPop());
         return PopScope(
           canPop: false,
           onPopInvokedWithResult: (didPop, _) {

+ 0 - 8
lib/features/expense_apply/expense_apply_detail_page.dart

@@ -6,8 +6,6 @@ import 'package:open_filex/open_filex.dart';
 import 'widgets/attachment_preview_page.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../shared/widgets/loading_dialog.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../shared/widgets/form_section.dart';
@@ -96,12 +94,6 @@ class _ExpenseApplyDetailPageState extends ConsumerState<ExpenseApplyDetailPage>
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('expenseApplyDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
-
     if (_loading) {
       return const SkeletonDetailPage();
     }

+ 1 - 15
lib/features/expense_apply/expense_apply_list_page.dart

@@ -1,10 +1,8 @@
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:easy_refresh/easy_refresh.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../core/utils/responsive.dart';
@@ -73,6 +71,7 @@ class _ExpenseApplyListPageState extends ConsumerState<ExpenseApplyListPage> {
   }
 
   void _applyFilter() {
+    FocusManager.instance.primaryFocus?.unfocus();
     _refreshCtrl.callRefresh();
   }
 
@@ -114,19 +113,6 @@ class _ExpenseApplyListPageState extends ConsumerState<ExpenseApplyListPage> {
       });
     }
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('expenseApplyList'),
-      showBack: true,
-      onBack: () {
-        FocusManager.instance.primaryFocus?.unfocus();
-        SystemNavigator.pop();
-      },
-      showRight: true,
-      rightWidget: GestureDetector(
-        onTap: () => context.push('/expense-apply/create'),
-        child: Icon(Icons.add, color: colors.textPrimary, size: 22),
-      ),
-    ));
     return Scaffold(
       body: Column(children: [
         Container(

+ 0 - 9
lib/features/home/home_page.dart

@@ -5,7 +5,6 @@ import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import '../../shared/widgets/section_card.dart';
 import '../../core/i18n/app_localizations.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import 'home_controller.dart';
 import '../../core/theme/app_colors.dart';
 import '../../core/theme/app_colors_extension.dart';
@@ -23,16 +22,8 @@ class HomePage extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final summaryAsync = ref.watch(homeSummaryProvider);
-    final location = GoRouterState.of(context).uri.toString();
     final l10n = AppLocalizations.of(context);
 
-    if (location == '/') {
-      setNavBarTitle(context, ref, NavBarConfig(
-        title: l10n.get('appName'),
-        showBack: true,
-        leadingIcon: Icons.close,
-      ));
-    }
 
     return summaryAsync.when(
       loading: () => const Center(child: CircularProgressIndicator()),

+ 0 - 38
lib/features/messages/message_list_page.dart

@@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import '../../shared/widgets/empty_state.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/i18n/app_localizations.dart';
 import '../../shared/widgets/message_item.dart';
 import '../../shared/widgets/app_skeletons.dart';
@@ -30,45 +29,8 @@ class MessageListPage extends ConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final messagesAsync = ref.watch(messageListProvider);
-    final unreadCount = ref.watch(unreadCountProvider);
-    final location = GoRouterState.of(context).uri.toString();
     final l10n = AppLocalizations.of(context);
 
-    if (location.startsWith('/messages')) {
-      setNavBarTitle(context, ref, NavBarConfig(
-        title: l10n.get('messageNotifications'),
-        showBack: true,
-        leadingIcon: Icons.close,
-        // 仅在有未读时显示"全部已读"按钮
-        showRight: unreadCount > 0,
-        rightWidget: unreadCount > 0
-            ? GestureDetector(
-                onTap: () {
-                  TDMessage.showMessage(
-                    context: context,
-                    content: l10n.get('markAllRead'),
-                    theme: MessageTheme.success,
-                    icon: true,
-                    duration: 2000,
-                  );
-                },
-                child: Padding(
-                  padding: const EdgeInsets.symmetric(
-                    horizontal: 12,
-                    vertical: 8,
-                  ),
-                  child: Text(
-                    l10n.get('markAllRead'),
-                    style: TextStyle(
-                      fontSize: AppFontSizes.body,
-                      color: colors.primary,
-                    ),
-                  ),
-                ),
-              )
-            : null,
-      ));
-    }
 
     // 首次加载:展示骨架屏,不包裹 EasyRefresh
     if (messagesAsync.isLoading && !messagesAsync.hasValue) {

+ 0 - 18
lib/features/outing_log/outing_log_create_page.dart

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../shared/widgets/form_section.dart';
 import '../../shared/widgets/form_field_row.dart';
 import '../../shared/widgets/action_bar.dart';
@@ -329,23 +328,6 @@ class _OutingLogCreatePageState extends ConsumerState<OutingLogCreatePage> {
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('outingLogCreate'),
-      showBack: true,
-      onBack: () {
-        if (_hasUnsaved()) {
-          _showConfirmDialog(
-            l10n.get('confirmExit'),
-            l10n.get('unsavedContentWarning'),
-            l10n.get('continueEditing'),
-            l10n.get('discardAndExit'),
-            () => context.pop(),
-          );
-        } else {
-          context.pop();
-        }
-      },
-    ));
 
     return PopScope(
       canPop: false,

+ 0 - 7
lib/features/outing_log/outing_log_detail_page.dart

@@ -1,7 +1,5 @@
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../core/i18n/app_localizations.dart';
 import 'outing_log_list_controller.dart';
@@ -98,11 +96,6 @@ class _OutingLogDetailPageState extends ConsumerState<OutingLogDetailPage> {
     //final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('outingLogDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
 
     return Column(
       children: [

+ 0 - 71
lib/features/outing_log/outing_log_list_page.dart

@@ -4,13 +4,11 @@ import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:easy_refresh/easy_refresh.dart';
 import '../../core/theme/app_colors_extension.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../core/utils/responsive.dart';
 import '../../shared/widgets/status_tag.dart';
 import '../../shared/widgets/empty_state.dart';
 import '../../shared/widgets/app_skeletons.dart';
-import '../../shared/widgets/list_filter_panel.dart';
 import '../../shared/widgets/list_footer.dart';
 import '../../core/i18n/app_localizations.dart';
 import '../../core/auth/role_provider.dart';
@@ -73,8 +71,6 @@ class _OutingLogListPageState extends ConsumerState<OutingLogListPage>
       });
     }
     final tabIndex = ref.watch(outingLogTabProvider);
-    final dateStart = ref.watch(outingLogDateStartProvider);
-    final dateEnd = ref.watch(outingLogDateEndProvider);
     final r = ResponsiveHelper.of(context);
     final l10n = AppLocalizations.of(context);
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
@@ -87,73 +83,6 @@ class _OutingLogListPageState extends ConsumerState<OutingLogListPage>
       });
     }
 
-    final filterGroups = [
-      FilterGroup(
-        title: l10n.get('filterDateRange'),
-        type: FilterGroupType.dateRange,
-        sections: [
-          FilterSection(
-            label: l10n.get('filterDateRange'),
-            type: FilterSectionType.dateRange,
-            startDate: dateStart,
-            endDate: dateEnd,
-            onStartChanged: (v) =>
-                ref.read(outingLogDateStartProvider.notifier).state = v,
-            onEndChanged: (v) =>
-                ref.read(outingLogDateEndProvider.notifier).state = v,
-          ),
-        ],
-      ),
-    ];
-    final hasFilter = ListFilterPanel.hasActiveFilter(filterGroups);
-    final filterVersion = Object.hash(dateStart, dateEnd);
-    final now = DateTime.now();
-    void onFilterReset() {
-      ref.read(outingLogDateStartProvider.notifier).state = null;
-      ref.read(outingLogDateEndProvider.notifier).state = null;
-    }
-
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('outingLogList'),
-      showBack: true,
-      showRight: true,
-      rightWidget: GestureDetector(
-        onTap: () => ListFilterPanel.show(
-          context,
-          groups: filterGroups,
-          onReset: onFilterReset,
-          onConfirm: () {},
-          defaultStartDate: DateTime(now.year, now.month, 1),
-          defaultEndDate: DateTime(now.year, now.month, now.day),
-        ),
-        child: Stack(
-          clipBehavior: Clip.none,
-          children: [
-            Icon(
-              TDIcons.filter,
-              size: 22,
-              color: hasFilter ? colors.primary : colors.textPrimary,
-            ),
-            if (hasFilter)
-              Positioned(
-                right: -2,
-                top: -2,
-                child: Container(
-                  width: 6,
-                  height: 6,
-                  decoration: BoxDecoration(
-                    color: colors.danger,
-                    shape: BoxShape.circle,
-                  ),
-                ),
-              ),
-          ],
-        ),
-      ),
-      hasFilter: hasFilter,
-      filterVersion: filterVersion,
-      onBack: () => context.pop(),
-    ));
     return Center(
       child: ConstrainedBox(
         constraints: BoxConstraints(maxWidth: r.listMaxWidth),

+ 0 - 19
lib/features/overtime/overtime_create_page.dart

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../shared/widgets/action_bar.dart';
 import '../../shared/widgets/form_section.dart';
@@ -55,24 +54,6 @@ class _OvertimeCreatePageState extends ConsumerState<OvertimeCreatePage> {
     final state = ref.watch(overtimeCreateProvider(widget.editId));
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('overtimeApply'),
-      showBack: true,
-      onBack: () {
-        if (_hasUnsaved()) {
-          _showConfirmDialog(
-            l10n.get('confirmExit'),
-            l10n.get('unsavedContentWarning'),
-            l10n.get('continueEditing'),
-            l10n.get('discardAndExit'),
-            () => context.pop(),
-          );
-        } else {
-          context.pop();
-        }
-      },
-    ));
-
     return PopScope(
       canPop: false,
       onPopInvokedWithResult: (didPop, _) {

+ 0 - 7
lib/features/overtime/overtime_detail_page.dart

@@ -1,7 +1,5 @@
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../shared/widgets/status_banner.dart';
 import '../../shared/widgets/approval_timeline.dart';
@@ -29,11 +27,6 @@ class OvertimeDetailPage extends ConsumerWidget {
       colors,
     );
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('overtimeDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
     return Column(
       children: [
         Expanded(

+ 0 - 100
lib/features/overtime/overtime_list_page.dart

@@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'package:easy_refresh/easy_refresh.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../core/auth/role_provider.dart';
@@ -11,7 +10,6 @@ import '../../shared/widgets/list_card.dart';
 import '../../shared/widgets/status_tag.dart';
 import '../../shared/widgets/empty_state.dart';
 import '../../shared/widgets/app_skeletons.dart';
-import '../../shared/widgets/list_filter_panel.dart';
 import '../../shared/widgets/list_footer.dart';
 import '../../core/i18n/app_localizations.dart';
 import 'overtime_list_controller.dart';
@@ -85,9 +83,6 @@ class _OvertimeListPageState extends ConsumerState<OvertimeListPage>
       });
     }
     final status = ref.watch(overtimeStatusFilterProvider);
-    final dateStart = ref.watch(overtimeDateStartProvider);
-    final dateEnd = ref.watch(overtimeDateEndProvider);
-    final otTypeFilter = ref.watch(overtimeTypeFilterProvider);
     final l10n = AppLocalizations.of(context);
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final isManager = ref.watch(isManagerProvider);
@@ -102,101 +97,6 @@ class _OvertimeListPageState extends ConsumerState<OvertimeListPage>
       });
     }
 
-    final filterGroups = [
-      FilterGroup(
-        title: l10n.get('filterDateRange'),
-        type: FilterGroupType.dateRange,
-        sections: [
-          FilterSection(
-            label: l10n.get('filterDateRange'),
-            type: FilterSectionType.dateRange,
-            startDate: dateStart,
-            endDate: dateEnd,
-            onStartChanged: (v) =>
-                ref.read(overtimeDateStartProvider.notifier).state = v,
-            onEndChanged: (v) =>
-                ref.read(overtimeDateEndProvider.notifier).state = v,
-          ),
-        ],
-      ),
-      FilterGroup(
-        title: l10n.get('other'),
-        type: FilterGroupType.other,
-        sections: [
-          FilterSection(
-            label: l10n.get('overtimeType'),
-            type: FilterSectionType.singleSelect,
-            options: [
-              FilterOption(
-                value: 'workday',
-                label: l10n.get('workdayOvertime'),
-              ),
-              FilterOption(
-                value: 'weekend',
-                label: l10n.get('weekendOvertime'),
-              ),
-              FilterOption(
-                value: 'holiday',
-                label: l10n.get('holidayOvertime'),
-              ),
-            ],
-            selectedValue: otTypeFilter,
-            onChanged: (v) =>
-                ref.read(overtimeTypeFilterProvider.notifier).state = v,
-          ),
-        ],
-      ),
-    ];
-    final hasFilter = ListFilterPanel.hasActiveFilter(filterGroups);
-    final filterVersion = Object.hash(dateStart, dateEnd, otTypeFilter);
-    final now = DateTime.now();
-    void onFilterReset() {
-      ref.read(overtimeDateStartProvider.notifier).state = null;
-      ref.read(overtimeDateEndProvider.notifier).state = null;
-      ref.read(overtimeTypeFilterProvider.notifier).state = null;
-    }
-
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('overtimeList'),
-      showBack: true,
-      showRight: true,
-      rightWidget: GestureDetector(
-        onTap: () => ListFilterPanel.show(
-          context,
-          groups: filterGroups,
-          onReset: onFilterReset,
-          onConfirm: () {},
-          defaultStartDate: DateTime(now.year, now.month, 1),
-          defaultEndDate: DateTime(now.year, now.month, now.day),
-        ),
-        child: Stack(
-          clipBehavior: Clip.none,
-          children: [
-            Icon(
-              TDIcons.filter,
-              size: 22,
-              color: hasFilter ? colors.primary : colors.textPrimary,
-            ),
-            if (hasFilter)
-              Positioned(
-                right: -2,
-                top: -2,
-                child: Container(
-                  width: 6,
-                  height: 6,
-                  decoration: BoxDecoration(
-                    color: colors.danger,
-                    shape: BoxShape.circle,
-                  ),
-                ),
-              ),
-          ],
-        ),
-      ),
-      hasFilter: hasFilter,
-      filterVersion: filterVersion,
-      onBack: () => context.pop(),
-    ));
     return Column(
       children: [
         if (isManager)

+ 0 - 9
lib/features/profile/profile_page.dart

@@ -7,7 +7,6 @@ import '../../core/i18n/locale_provider.dart';
 import '../../core/theme/theme_mode_provider.dart';
 import '../../core/auth/role_provider.dart';
 import '../../shared/widgets/profile_menu_item.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors.dart';
 import '../../core/theme/app_colors_extension.dart';
 
@@ -17,20 +16,12 @@ class ProfilePage extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final l10n = AppLocalizations.of(context);
-    final location = GoRouterState.of(context).uri.toString();
     final currentLocale = ref.watch(localeProvider);
     final themeMode = ref.watch(themeModeProvider);
     final isDark = themeMode == ThemeMode.dark;
     final currentRole = ref.watch(currentRoleProvider);
     final isAdmin = currentRole == 'admin';
 
-    if (location.startsWith('/profile')) {
-      setNavBarTitle(context, ref, NavBarConfig(
-        title: l10n.get('tabProfile'),
-        showBack: true,
-        leadingIcon: Icons.close,
-      ));
-    }
     return SingleChildScrollView(
       physics: const AlwaysScrollableScrollPhysics(),
       child: Column(

+ 21 - 15
lib/features/report/expense_apply_detail_report_page.dart

@@ -1,11 +1,11 @@
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:fl_chart/fl_chart.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import '../../core/i18n/app_localizations.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
+import '../../shared/widgets/app_skeletons.dart';
+import 'package:skeletonizer/skeletonizer.dart';
 import '../expense_apply/expense_apply_api.dart';
 import '../expense_apply/report_model.dart';
 
@@ -23,6 +23,7 @@ class _ExpenseApplyDetailReportPageState
   final _startCtrl = TextEditingController();
   final _endCtrl = TextEditingController();
   bool _loading = true;
+  bool _isInitialLoad = true;
   String? _error;
   ReportData? _data;
   List<ReportDetailItem> _details = [];
@@ -117,6 +118,7 @@ class _ExpenseApplyDetailReportPageState
 
   void _applyFilter() {
     _details = []; _detailPage = 1; _detailTotal = 0;
+    _isInitialLoad = false;
     _loadData();
     _loadDetails();
   }
@@ -125,18 +127,22 @@ class _ExpenseApplyDetailReportPageState
   Widget build(BuildContext context) {
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
-    setNavBarTitle(
-      context,
-      ref,
-      NavBarConfig(
-        title: l10n.get('reportExpenseApplyDetail'),
-        showBack: true,
-        onBack: () => context.pop(),
-      ),
-    );
+    if (_loading && _isInitialLoad) {
+      return const SkeletonReportPage();
+    }
     if (_loading) {
-      return const Center(
-        child: TDLoading(size: TDLoadingSize.large, icon: TDLoadingIcon.activity),
+      return Scaffold(
+        body: SingleChildScrollView(
+          child: Column(children: [
+            _buildDateFilter(),
+            const SizedBox(height: 16),
+            Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Bone(height: 80, borderRadius: BorderRadius.circular(12))),
+            const SizedBox(height: 16),
+            Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Bone(height: 200, borderRadius: BorderRadius.circular(12))),
+            const SizedBox(height: 16),
+            Center(child: TDLoading(size: TDLoadingSize.medium, icon: TDLoadingIcon.activity)),
+          ]),
+        ),
       );
     }
     if (_error != null) {
@@ -195,8 +201,8 @@ class _ExpenseApplyDetailReportPageState
       TextEditingController ctrl, String hint, TDThemeData tdTheme, AppColorsExtension colors) {
     final text = ctrl.text;
     return Container(
-      height: 36, padding: const EdgeInsets.symmetric(horizontal: 8),
-      decoration: BoxDecoration(color: colors.bgSecondaryContainer, borderRadius: BorderRadius.circular(18), border: Border.all(color: tdTheme.componentStrokeColor)),
+      height: 40, padding: const EdgeInsets.symmetric(horizontal: 8),
+      decoration: BoxDecoration(color: colors.bgSecondaryContainer, borderRadius: BorderRadius.circular(20), border: Border.all(color: tdTheme.componentStrokeColor)),
       child: Row(children: [
         Icon(Icons.calendar_today, size: 14, color: colors.textSecondary), const SizedBox(width: 4),
         Expanded(child: Text(text.isNotEmpty ? text : hint, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12, color: text.isNotEmpty ? colors.textPrimary : colors.textSecondary))),

+ 21 - 11
lib/features/report/expense_detail_report_page.dart

@@ -1,11 +1,11 @@
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:fl_chart/fl_chart.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 import '../../core/i18n/app_localizations.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
+import '../../shared/widgets/app_skeletons.dart';
+import 'package:skeletonizer/skeletonizer.dart';
 import '../expense/expense_api.dart';
 import '../expense_apply/report_model.dart';
 
@@ -23,6 +23,7 @@ class _ExpenseDetailReportPageState
   final _startCtrl = TextEditingController();
   final _endCtrl = TextEditingController();
   bool _loading = true;
+  bool _isInitialLoad = true;
   String? _error;
   ReportData? _data;
   List<ReportDetailItem> _details = [];
@@ -122,8 +123,8 @@ class _ExpenseDetailReportPageState
   Widget _dateChip(TextEditingController ctrl, String hint, TDThemeData tdTheme, AppColorsExtension colors) {
     final text = ctrl.text;
     return Container(
-      height: 36, padding: const EdgeInsets.symmetric(horizontal: 8),
-      decoration: BoxDecoration(color: colors.bgSecondaryContainer, borderRadius: BorderRadius.circular(18), border: Border.all(color: tdTheme.componentStrokeColor)),
+      height: 40, padding: const EdgeInsets.symmetric(horizontal: 8),
+      decoration: BoxDecoration(color: colors.bgSecondaryContainer, borderRadius: BorderRadius.circular(20), border: Border.all(color: tdTheme.componentStrokeColor)),
       child: Row(children: [
         Icon(Icons.calendar_today, size: 14, color: colors.textSecondary), const SizedBox(width: 4),
         Expanded(child: Text(text.isNotEmpty ? text : hint, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12, color: text.isNotEmpty ? colors.textPrimary : colors.textSecondary))),
@@ -154,6 +155,7 @@ class _ExpenseDetailReportPageState
           GestureDetector(
             onTap: () {
               _details = []; _detailPage = 1; _detailTotal = 0;
+              _isInitialLoad = false;
               _loadData();
               _loadDetails();
             },
@@ -464,14 +466,22 @@ class _ExpenseDetailReportPageState
   Widget build(BuildContext context) {
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('reportExpenseDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
+    if (_loading && _isInitialLoad) {
+      return const SkeletonReportPage();
+    }
     if (_loading) {
-      return const Center(
-        child: TDLoading(size: TDLoadingSize.large, icon: TDLoadingIcon.activity),
+      return Scaffold(
+        body: SingleChildScrollView(
+          child: Column(children: [
+            _buildDateFilter(),
+            const SizedBox(height: 16),
+            Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Bone(height: 80, borderRadius: BorderRadius.circular(12))),
+            const SizedBox(height: 16),
+            Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Bone(height: 200, borderRadius: BorderRadius.circular(12))),
+            const SizedBox(height: 16),
+            Center(child: TDLoading(size: TDLoadingSize.medium, icon: TDLoadingIcon.activity)),
+          ]),
+        ),
       );
     }
     if (_error != null) {

+ 0 - 7
lib/features/report/outing_log_detail_report_page.dart

@@ -1,9 +1,7 @@
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:fl_chart/fl_chart.dart';
 import '../../core/i18n/app_localizations.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/auth/role_provider.dart';
 
@@ -86,11 +84,6 @@ class _OutingLogDetailReportPageState extends ConsumerState<OutingLogDetailRepor
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
     final role = ref.watch(currentRoleProvider);
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('reportOutingLogDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
     final showDetail = role != 'employee';
     final showExport = role == 'finance' || role == 'admin';
     return Scaffold(

+ 0 - 7
lib/features/report/overtime_detail_report_page.dart

@@ -1,9 +1,7 @@
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:fl_chart/fl_chart.dart';
 import '../../core/i18n/app_localizations.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/auth/role_provider.dart';
 
@@ -94,11 +92,6 @@ class _OvertimeDetailReportPageState
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
     final role = ref.watch(currentRoleProvider);
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('reportOvertimeDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
     final showDetail = role != 'employee';
     final showExport = role == 'finance' || role == 'admin';
     return Scaffold(

+ 0 - 7
lib/features/report/vehicle_detail_report_page.dart

@@ -1,9 +1,7 @@
 import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:fl_chart/fl_chart.dart';
 import '../../core/i18n/app_localizations.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/auth/role_provider.dart';
 
@@ -84,11 +82,6 @@ class _VehicleDetailReportPageState
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
     final role = ref.watch(currentRoleProvider);
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('reportVehicleDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
     final showDetail = role != 'employee';
     final showExport = role == 'finance' || role == 'admin';
     return Scaffold(

+ 0 - 19
lib/features/vehicle/vehicle_create_page.dart

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:go_router/go_router.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../shared/widgets/action_bar.dart';
 import '../../shared/widgets/form_section.dart';
@@ -75,24 +74,6 @@ class _VehicleCreatePageState extends ConsumerState<VehicleCreatePage> {
     final l10n = AppLocalizations.of(context);
     final v = state.vehicle;
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('vehicleApply'),
-      showBack: true,
-      onBack: () {
-        if (_hasUnsaved()) {
-          _showConfirmDialog(
-            l10n.get('confirmExit'),
-            l10n.get('unsavedContentWarning'),
-            l10n.get('continueEditing'),
-            l10n.get('discardAndExit'),
-            () => context.pop(),
-          );
-        } else {
-          context.pop();
-        }
-      },
-    ));
-
     return PopScope(
       canPop: false,
       onPopInvokedWithResult: (didPop, _) {

+ 0 - 7
lib/features/vehicle/vehicle_detail_page.dart

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
 import 'package:go_router/go_router.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../shared/widgets/status_banner.dart';
 import '../../shared/widgets/approval_timeline.dart';
@@ -61,12 +60,6 @@ class _VehicleDetailPageState extends ConsumerState<VehicleDetailPage> {
     );
     final l10n = AppLocalizations.of(context);
 
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('vehicleDetail'),
-      showBack: true,
-      onBack: () => context.pop(),
-    ));
-
     final (icon, color, statusText) = _statusProps(vehicle.status);
 
     return Column(

+ 0 - 94
lib/features/vehicle/vehicle_list_page.dart

@@ -6,11 +6,9 @@ import 'package:easy_refresh/easy_refresh.dart';
 import '../../core/theme/app_colors.dart';
 import '../../core/theme/app_colors_extension.dart';
 import '../../core/auth/role_provider.dart';
-import '../../shared/widgets/nav_bar_config.dart';
 import '../../core/utils/date_utils.dart' as du;
 import '../../shared/widgets/empty_state.dart';
 import '../../shared/widgets/app_skeletons.dart';
-import '../../shared/widgets/list_filter_panel.dart';
 import '../../shared/widgets/list_footer.dart';
 import '../../shared/widgets/status_tag.dart';
 import '../../core/i18n/app_localizations.dart';
@@ -86,9 +84,6 @@ class _VehicleListPageState extends ConsumerState<VehicleListPage>
       });
     }
     final status = ref.watch(vehicleStatusFilterProvider);
-    final dateStart = ref.watch(vehicleDateStartProvider);
-    final dateEnd = ref.watch(vehicleDateEndProvider);
-    final purposeFilter = ref.watch(vehiclePurposeFilterProvider);
     final l10n = AppLocalizations.of(context);
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final isManager = ref.watch(isManagerProvider);
@@ -103,95 +98,6 @@ class _VehicleListPageState extends ConsumerState<VehicleListPage>
       });
     }
 
-    final filterGroups = [
-      FilterGroup(
-        title: l10n.get('filterDateRange'),
-        type: FilterGroupType.dateRange,
-        sections: [
-          FilterSection(
-            label: l10n.get('filterDateRange'),
-            type: FilterSectionType.dateRange,
-            startDate: dateStart,
-            endDate: dateEnd,
-            onStartChanged: (v) =>
-                ref.read(vehicleDateStartProvider.notifier).state = v,
-            onEndChanged: (v) =>
-                ref.read(vehicleDateEndProvider.notifier).state = v,
-          ),
-        ],
-      ),
-      FilterGroup(
-        title: l10n.get('other'),
-        type: FilterGroupType.other,
-        sections: [
-          FilterSection(
-            label: l10n.get('vehiclePurpose'),
-            type: FilterSectionType.singleSelect,
-            options: [
-              FilterOption(
-                value: 'reception',
-                label: l10n.get('customerReception'),
-              ),
-              FilterOption(value: 'business', label: l10n.get('businessTrip')),
-              FilterOption(value: 'official', label: l10n.get('official')),
-            ],
-            selectedValue: purposeFilter,
-            onChanged: (v) =>
-                ref.read(vehiclePurposeFilterProvider.notifier).state = v,
-          ),
-        ],
-      ),
-    ];
-    final hasFilter = ListFilterPanel.hasActiveFilter(filterGroups);
-    final filterVersion = Object.hash(dateStart, dateEnd, purposeFilter);
-    final now = DateTime.now();
-    void onFilterReset() {
-      ref.read(vehicleDateStartProvider.notifier).state = null;
-      ref.read(vehicleDateEndProvider.notifier).state = null;
-      ref.read(vehiclePurposeFilterProvider.notifier).state = null;
-    }
-
-    setNavBarTitle(context, ref, NavBarConfig(
-      title: l10n.get('vehicleList'),
-      showBack: true,
-      showRight: true,
-      rightWidget: GestureDetector(
-        onTap: () => ListFilterPanel.show(
-          context,
-          groups: filterGroups,
-          onReset: onFilterReset,
-          onConfirm: () {},
-          defaultStartDate: DateTime(now.year, now.month, 1),
-          defaultEndDate: DateTime(now.year, now.month, now.day),
-        ),
-        child: Stack(
-          clipBehavior: Clip.none,
-          children: [
-            Icon(
-              TDIcons.filter,
-              size: 22,
-              color: hasFilter ? colors.primary : colors.textPrimary,
-            ),
-            if (hasFilter)
-              Positioned(
-                right: -2,
-                top: -2,
-                child: Container(
-                  width: 6,
-                  height: 6,
-                  decoration: BoxDecoration(
-                    color: colors.danger,
-                    shape: BoxShape.circle,
-                  ),
-                ),
-              ),
-          ],
-        ),
-      ),
-      hasFilter: hasFilter,
-      filterVersion: filterVersion,
-      onBack: () => context.pop(),
-    ));
     return Column(
       children: [
         if (isManager)

+ 90 - 186
lib/shared/widgets/app_scaffold.dart

@@ -9,126 +9,92 @@ import '../../core/theme/app_colors_extension.dart';
 import 'nav_bar_config.dart';
 
 /// 应用级 Scaffold:NavBar + body + 可选 BottomTabBar
-///
-/// 替代原来的 AppShell。每个页面自行包裹,无需 ShellRoute。
 class AppScaffold extends ConsumerWidget {
   final Widget body;
   final bool showTabBar;
   final bool resizeToAvoidBottomInset;
+  final NavBarConfig? navConfig;
 
   const AppScaffold({
     super.key,
     required this.body,
     this.showTabBar = false,
     this.resizeToAvoidBottomInset = true,
+    this.navConfig,
   });
 
   bool _isRootTab(String location) {
-    return location == '/' || location == '/messages' || location == '/profile';
-  }
-
-  NavBarConfig _pageConfig(String location, AppLocalizations l10n, WidgetRef ref) {
-    // 从 provider 读取页面自定义属性(rightWidget 等),标题由路由决定
-    final custom = ref.watch(navBarConfigProvider);
-    final title = _titleForRoute(location, l10n);
-    if (title == null) return custom;
-    return NavBarConfig(
-      title: title,
-      showBack: custom.showBack,
-      showRight: custom.showRight,
-      rightWidget: custom.rightWidget,
-      leadingIcon: custom.leadingIcon,
-      onBack: custom.onBack,
-    );
+    final path = location.split('?').first;
+    return path == '/' || path == '/messages' || path == '/profile';
   }
 
-  /// 路由 → 标题映射,新增路由只需在此添加一行
   String? _titleForRoute(String location, AppLocalizations l10n) {
     final path = location.split('?').first;
     if (path == '/') return l10n.get('appName');
     if (path == '/messages') return l10n.get('tabMessages');
     if (path == '/profile') return l10n.get('tabProfile');
-
-    // ── 费用 ──
     if (path.startsWith('/expense/list')) return l10n.get('expenseList');
+    if (path.startsWith('/expense/import-apply')) return l10n.get('importExpenseApply');
     if (path.startsWith('/expense/detail')) return l10n.get('expenseDetail');
     if (path.startsWith('/expense/create') || path.startsWith('/expense/edit')) return l10n.get('expenseApply');
-
-    // ── 费用申请 ──
     if (path.startsWith('/expense-apply/list')) return l10n.get('expenseApplyList');
     if (path.startsWith('/expense-apply/detail')) return l10n.get('expenseApplyDetail');
     if (path.startsWith('/expense-apply/create')) return l10n.get('expenseApplyRequest');
-
-    // ── 加班 ──
     if (path.startsWith('/overtime/list')) return l10n.get('overtimeList');
     if (path.startsWith('/overtime/detail')) return l10n.get('overtimeDetail');
     if (path.startsWith('/overtime/create')) return l10n.get('overtimeRequest');
-
-    // ── 用车 ──
     if (path.startsWith('/vehicle/list')) return l10n.get('vehicleList');
     if (path.startsWith('/vehicle/detail')) return l10n.get('vehicleDetail');
     if (path.startsWith('/vehicle/create')) return l10n.get('vehicleRequest');
-
-    // ── 外勤日志 ──
     if (path.startsWith('/outing-log/list')) return l10n.get('outingLogList');
     if (path.startsWith('/outing-log/detail')) return l10n.get('outingLogDetail');
     if (path.startsWith('/outing-log/create')) return l10n.get('outingLogCreate');
-
-    // ── 公告 ──
     if (path.startsWith('/announcement/list')) return l10n.get('announcementList');
     if (path.startsWith('/announcement/detail')) return l10n.get('announcementDetail');
     if (path.startsWith('/announcement/create')) return l10n.get('announcementCreate');
-
-    // ── 报表 ──(标题由页面自行设置)
-    if (path.startsWith('/report/overtime')) return l10n.get('overtimeReport');
-    if (path.startsWith('/report/vehicle')) return l10n.get('vehicleReport');
-    if (path.startsWith('/report/outing-log')) return l10n.get('outingLogReport');
-
-    // ── 管理 ──
+    if (path.startsWith('/report/expense-detail')) return l10n.get('reportExpenseDetail');
+    if (path.startsWith('/report/expense-apply-detail')) return l10n.get('reportExpenseApplyDetail');
+    if (path.startsWith('/report/overtime')) return l10n.get('reportOvertimeDetail');
+    if (path.startsWith('/report/vehicle')) return l10n.get('reportVehicleDetail');
+    if (path.startsWith('/report/outing-log')) return l10n.get('reportOutingLogDetail');
     if (path.startsWith('/admin/permissions')) return l10n.get('permissionManagement');
-
-    return null; // 未知路由 → 回退到 provider
+    return null;
   }
 
   NavBarConfig _rootConfig(String location, AppLocalizations l10n) {
-    if (location.startsWith('/messages')) {
-      return NavBarConfig(
-        title: l10n.get('tabMessages'),
-        showBack: true,
-        leadingIcon: Icons.close,
-      );
-    }
-    if (location == '/') {
-      return NavBarConfig(
-        title: l10n.get('appName'),
-        showBack: true,
-        leadingIcon: Icons.close,
-      );
-    }
-    if (location.startsWith('/profile')) {
-      return NavBarConfig(
-        title: l10n.get('tabProfile'),
-        showBack: true,
-        leadingIcon: Icons.close,
-      );
-    }
+    final path = location.split('?').first;
+    if (path == '/messages') return NavBarConfig(title: l10n.get('tabMessages'), showBack: true, leadingIcon: Icons.close);
+    if (path == '/') return NavBarConfig(title: l10n.get('appName'), showBack: true, leadingIcon: Icons.close);
+    if (path == '/profile') return NavBarConfig(title: l10n.get('tabProfile'), showBack: true, leadingIcon: Icons.close);
     return NavBarConfig.home;
   }
 
+  NavBarConfig _resolve(NavBarConfig cfg, String location, AppLocalizations l10n) {
+    final routeTitle = _titleForRoute(location, l10n);
+    if (routeTitle == null) return cfg;
+    return NavBarConfig(
+      title: routeTitle,
+      showBack: cfg.showBack,
+      showRight: cfg.showRight,
+      rightWidget: cfg.rightWidget,
+      onBack: cfg.onBack,
+      leadingIcon: cfg.leadingIcon,
+    );
+  }
+
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     SystemChrome.setSystemUIOverlayStyle(
-      const SystemUiOverlayStyle(
-        statusBarColor: Colors.transparent,
-        statusBarIconBrightness: Brightness.dark,
-      ),
+      const SystemUiOverlayStyle(statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark),
     );
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
     final location = GoRouterState.of(context).uri.toString();
-    final config = _isRootTab(location)
-        ? _rootConfig(location, l10n)
-        : _pageConfig(location, l10n, ref);
+    final config = navConfig != null
+        ? _resolve(navConfig!, location, l10n)
+        : _rootConfig(location, l10n);
+    // 页面可通过 pageBackProvider 覆盖返回按钮行为(如 create 页面的 _doPop)
+    final pageBack = ref.watch(pageBackProvider);
 
     return Scaffold(
       resizeToAvoidBottomInset: resizeToAvoidBottomInset,
@@ -139,20 +105,23 @@ class AppScaffold extends ConsumerWidget {
           _NavBarView(
             config: config,
             location: location,
+            pageBack: pageBack,
             onBack: () {
               if (_isRootTab(location)) {
                 SystemNavigator.pop();
               } else {
-                GoRouter.of(context).pop();
+                final router = GoRouter.of(context);
+                if (router.canPop()) {
+                  router.pop();
+                } else {
+                  SystemNavigator.pop();
+                }
               }
             },
           ),
           Expanded(child: body),
           if (showTabBar)
-            Container(
-              color: colors.bgCard,
-              child: _AppTabBar(location: location),
-            ),
+            Container(color: colors.bgCard, child: _AppTabBar(location: location)),
         ],
       ),
     );
@@ -162,13 +131,9 @@ class AppScaffold extends ConsumerWidget {
 class _NavBarView extends StatelessWidget {
   final NavBarConfig config;
   final String location;
+  final VoidCallback? pageBack;
   final VoidCallback onBack;
-
-  const _NavBarView({
-    required this.config,
-    required this.location,
-    required this.onBack,
-  });
+  const _NavBarView({required this.config, required this.location, this.pageBack, required this.onBack});
 
   @override
   Widget build(BuildContext context) {
@@ -176,21 +141,15 @@ class _NavBarView extends StatelessWidget {
     List<TDNavBarItem>? leftItems;
     if (config.showBack) {
       final icon = config.leadingIcon ?? TDIcons.chevron_left;
-      leftItems = [
-        TDNavBarItem(
-          icon: icon,
-          iconSize: 22,
-          iconColor: colors.textPrimary,
-          action: config.onBack ?? onBack,
-        ),
-      ];
+      // config.onBack(路由级声明)优先于 pageBack(页面级 Provider),
+      // 防止 Provider 残留值泄漏到不相关的页面
+      final backAction = config.onBack ?? pageBack ?? onBack;
+      leftItems = [TDNavBarItem(icon: icon, iconSize: 22, iconColor: colors.textPrimary, action: backAction)];
     }
-
     List<TDNavBarItem>? rightItems;
     if (config.showRight && config.rightWidget != null) {
       rightItems = [TDNavBarItem(iconWidget: config.rightWidget, iconSize: 22)];
     }
-
     return TDNavBar(
       title: config.title,
       titleColor: colors.textPrimary,
@@ -209,16 +168,11 @@ class _NavBarView extends StatelessWidget {
 
 class _AppTabBar extends StatelessWidget {
   final String location;
-
   const _AppTabBar({required this.location});
 
   static int tabIndex(String location) {
     if (location.startsWith('/messages')) return 0;
-    if (location == '/' ||
-        (!location.startsWith('/messages') &&
-            !location.startsWith('/profile'))) {
-      return 1;
-    }
+    if (location == '/' || (!location.startsWith('/messages') && !location.startsWith('/profile'))) return 1;
     return 2;
   }
 
@@ -226,96 +180,46 @@ class _AppTabBar extends StatelessWidget {
   Widget build(BuildContext context) {
     final colors = Theme.of(context).extension<AppColorsExtension>()!;
     final l10n = AppLocalizations.of(context);
-    return LayoutBuilder(
-      builder: (ctx, constraints) {
-        if (constraints.maxWidth <= 0) return const SizedBox.shrink();
-        final bottomPadding = MediaQuery.of(ctx).padding.bottom;
-        return Padding(
-          padding: EdgeInsets.only(top: 0, bottom: 8 + bottomPadding),
-          child: TDBottomTabBar(
-            TDBottomTabBarBasicType.iconText,
-            useSafeArea: false,
-            componentType: TDBottomTabBarComponentType.label,
-            outlineType: TDBottomTabBarOutlineType.filled,
-            backgroundColor: colors.bgCard,
-            dividerColor: Colors.transparent,
-            selectedBgColor: colors.primaryLight,
-            unselectedBgColor: Colors.transparent,
-            currentIndex: tabIndex(location),
-            navigationTabs: [
-              TDBottomTabBarTabConfig(
-                tabText: l10n.get('tabMessages'),
-                selectedIcon: Icon(
-                  Icons.notifications,
-                  size: 22,
-                  color: colors.primary,
-                ),
-                unselectedIcon: Icon(
-                  Icons.notifications_outlined,
-                  size: 22,
-                  color: colors.textSecondary,
-                ),
-                selectTabTextStyle: TextStyle(
-                  fontSize: 10,
-                  fontWeight: FontWeight.w600,
-                  color: colors.primary,
-                ),
-                unselectTabTextStyle: TextStyle(
-                  fontSize: 10,
-                  color: colors.textSecondary,
-                ),
-                onTap: () => context.go('/messages'),
-              ),
-              TDBottomTabBarTabConfig(
-                tabText: l10n.get('tabWorkbench'),
-                selectedIcon: Icon(
-                  Icons.dashboard,
-                  size: 22,
-                  color: colors.primary,
-                ),
-                unselectedIcon: Icon(
-                  Icons.dashboard_outlined,
-                  size: 22,
-                  color: colors.textSecondary,
-                ),
-                selectTabTextStyle: TextStyle(
-                  fontSize: 10,
-                  fontWeight: FontWeight.w600,
-                  color: colors.primary,
-                ),
-                unselectTabTextStyle: TextStyle(
-                  fontSize: 10,
-                  color: colors.textSecondary,
-                ),
-                onTap: () => context.go('/'),
-              ),
-              TDBottomTabBarTabConfig(
-                tabText: l10n.get('tabProfile'),
-                selectedIcon: Icon(
-                  Icons.person,
-                  size: 22,
-                  color: colors.primary,
-                ),
-                unselectedIcon: Icon(
-                  Icons.person_outline,
-                  size: 22,
-                  color: colors.textSecondary,
-                ),
-                selectTabTextStyle: TextStyle(
-                  fontSize: 10,
-                  fontWeight: FontWeight.w600,
-                  color: colors.primary,
-                ),
-                unselectTabTextStyle: TextStyle(
-                  fontSize: 10,
-                  color: colors.textSecondary,
-                ),
-                onTap: () => context.go('/profile'),
-              ),
-            ],
-          ),
-        );
-      },
-    );
+    return LayoutBuilder(builder: (ctx, constraints) {
+      if (constraints.maxWidth <= 0) return const SizedBox.shrink();
+      final bottomPadding = MediaQuery.of(ctx).padding.bottom;
+      return Padding(
+        padding: EdgeInsets.only(top: 0, bottom: 8 + bottomPadding),
+        child: TDBottomTabBar(
+          TDBottomTabBarBasicType.iconText, useSafeArea: false,
+          componentType: TDBottomTabBarComponentType.label,
+          outlineType: TDBottomTabBarOutlineType.filled,
+          backgroundColor: colors.bgCard, dividerColor: Colors.transparent,
+          selectedBgColor: colors.primaryLight, unselectedBgColor: Colors.transparent,
+          currentIndex: tabIndex(location),
+          navigationTabs: [
+            TDBottomTabBarTabConfig(
+              tabText: l10n.get('tabMessages'),
+              selectedIcon: Icon(Icons.notifications, size: 22, color: colors.primary),
+              unselectedIcon: Icon(Icons.notifications_outlined, size: 22, color: colors.textSecondary),
+              selectTabTextStyle: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: colors.primary),
+              unselectTabTextStyle: TextStyle(fontSize: 10, color: colors.textSecondary),
+              onTap: () => context.go('/messages'),
+            ),
+            TDBottomTabBarTabConfig(
+              tabText: l10n.get('tabWorkbench'),
+              selectedIcon: Icon(Icons.dashboard, size: 22, color: colors.primary),
+              unselectedIcon: Icon(Icons.dashboard_outlined, size: 22, color: colors.textSecondary),
+              selectTabTextStyle: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: colors.primary),
+              unselectTabTextStyle: TextStyle(fontSize: 10, color: colors.textSecondary),
+              onTap: () => context.go('/'),
+            ),
+            TDBottomTabBarTabConfig(
+              tabText: l10n.get('tabProfile'),
+              selectedIcon: Icon(Icons.person, size: 22, color: colors.primary),
+              unselectedIcon: Icon(Icons.person_outline, size: 22, color: colors.textSecondary),
+              selectTabTextStyle: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: colors.primary),
+              unselectTabTextStyle: TextStyle(fontSize: 10, color: colors.textSecondary),
+              onTap: () => context.go('/profile'),
+            ),
+          ],
+        ),
+      );
+    });
   }
 }

+ 89 - 0
lib/shared/widgets/app_skeletons.dart

@@ -447,3 +447,92 @@ class _FormCard extends StatelessWidget {
     );
   }
 }
+
+/// 报表页骨架(ExpenseDetailReportPage / ExpenseApplyDetailReportPage)
+/// 匹配 筛选栏 + 统计卡片(2x2) + 图表卡片 + 明细列表 布局
+class SkeletonReportPage extends StatelessWidget {
+  const SkeletonReportPage({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final colors = Theme.of(context).extension<AppColorsExtension>()!;
+    return SingleChildScrollView(
+      child: Skeletonizer(
+        enabled: true,
+        child: Column(
+          children: [
+            // 日期筛选栏
+            Container(
+              padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
+              color: colors.bgCard,
+              child: Row(children: [
+                Expanded(child: Bone(height: 40, borderRadius: BorderRadius.circular(20))),
+                const SizedBox(width: 8),
+                Bone(width: 14, height: 14),
+                const SizedBox(width: 8),
+                Expanded(child: Bone(height: 40, borderRadius: BorderRadius.circular(20))),
+                const SizedBox(width: 8),
+                Bone(width: 40, height: 40, borderRadius: BorderRadius.circular(20)),
+              ]),
+            ),
+            const SizedBox(height: 16),
+            // 统计卡片 — 2行 x 2列
+            Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 12),
+              child: Column(children: [
+                Row(children: [
+                  Expanded(child: Bone(height: 80, borderRadius: BorderRadius.circular(12))),
+                  const SizedBox(width: 8),
+                  Expanded(child: Bone(height: 80, borderRadius: BorderRadius.circular(12))),
+                ]),
+                const SizedBox(height: 8),
+                Row(children: [
+                  Expanded(child: Bone(height: 80, borderRadius: BorderRadius.circular(12))),
+                  const SizedBox(width: 8),
+                  Expanded(child: Bone(height: 80, borderRadius: BorderRadius.circular(12))),
+                ]),
+              ]),
+            ),
+            const SizedBox(height: 16),
+            // 图表卡片 — 标题 + 图表区
+            Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 12),
+              child: Container(
+                padding: const EdgeInsets.all(16),
+                decoration: BoxDecoration(color: colors.bgCard, borderRadius: BorderRadius.circular(12)),
+                child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+                  Row(children: [Bone.square(size: 20, borderRadius: BorderRadius.circular(4)), const SizedBox(width: 8), Bone(width: 120, height: 16)]),
+                  const SizedBox(height: 16),
+                  Bone(height: 200, borderRadius: BorderRadius.circular(8)),
+                ]),
+              ),
+            ),
+            const SizedBox(height: 16),
+            // 明细列表卡片 — 表头 + 行
+            Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 12),
+              child: Container(
+                padding: const EdgeInsets.all(16),
+                decoration: BoxDecoration(color: colors.bgCard, borderRadius: BorderRadius.circular(12)),
+                child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+                  Row(children: [Bone.square(size: 20, borderRadius: BorderRadius.circular(4)), const SizedBox(width: 8), Bone(width: 80, height: 16)]),
+                  const SizedBox(height: 16),
+                  ...List.generate(6, (_) => Padding(
+                    padding: const EdgeInsets.only(bottom: 8),
+                    child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+                      Bone(width: 60, height: 14),
+                      Bone(width: 100, height: 14),
+                      Bone(width: 80, height: 14),
+                      Bone(width: 60, height: 14),
+                    ]),
+                  )),
+                ]),
+              ),
+            ),
+            const SizedBox(height: 24),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 10 - 108
lib/shared/widgets/nav_bar_config.dart

@@ -1,16 +1,13 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
 
-/// 按 BuildContext 缓存 token:同 State 多次 build 复用同一 token,
-/// 新 State(context 对象不同)自动分配新 token,确保 onBack 闭包不被旧 State 覆盖
-final Expando<int> _contextToken = Expando<int>();
-int _globalNavToken = 0;
+/// 页面覆盖导航栏返回按钮的 Provider。
+/// 页面在 build 中设置:`ref.read(pageBackProvider.notifier).state = () => _doPop();`
+final pageBackProvider = StateProvider<VoidCallback?>((ref) => null);
 
-int _tokenForContext(BuildContext context) =>
-    _contextToken[context] ??= ++_globalNavToken;
-
-/// NavBar 配置,由各页面在 build 中设置,AppShell 统一渲染
+/// NavBar 配置——纯数据类,由路由层在 AppScaffold.navConfig 中声明式传入。
+///
+/// 不再需要 Provider 中转:AppScaffold 直接消费,同步渲染,无延迟。
 class NavBarConfig {
   final String title;
   final bool showBack;
@@ -18,113 +15,18 @@ class NavBarConfig {
   final Widget? rightWidget;
   final VoidCallback? onBack;
   final IconData? leadingIcon;
-  final bool hasFilter;
-  final int filterVersion;
-  /// 页面实例标识:每个 State 实例生成唯一 token,确保新 State 的 onBack 闭包不被
-  /// 旧 State 的 config(值相等但闭包指向已销毁 State)覆盖
-  final int? token;
 
   const NavBarConfig({
-    required this.title,
+    this.title = '',
     this.showBack = false,
     this.showRight = false,
     this.rightWidget,
     this.onBack,
     this.leadingIcon,
-    this.hasFilter = false,
-    this.filterVersion = 0,
-    this.token,
   });
 
   /// 根页面默认配置(无返回按钮)
-  static const home = NavBarConfig(title: 'TBOSS 工作台', showBack: false);
-  static const messages = NavBarConfig(title: '消息', showBack: false);
-  static const profile = NavBarConfig(title: '我的', showBack: false);
-
-  /// 带返回按钮的页面快捷构造
-  factory NavBarConfig.withBack(String title, {VoidCallback? onBack}) {
-    return NavBarConfig(title: title, showBack: true, onBack: onBack);
-  }
-
-  /// 带返回按钮 + 右侧按钮的页面快捷构造
-  factory NavBarConfig.withRight(
-    String title, {
-    required Widget rightWidget,
-    VoidCallback? onBack,
-  }) {
-    return NavBarConfig(
-      title: title,
-      showBack: true,
-      showRight: true,
-      rightWidget: rightWidget,
-      onBack: onBack,
-    );
-  }
-
-  @override
-  bool operator ==(Object other) =>
-      other is NavBarConfig &&
-      other.title == title &&
-      other.showBack == showBack &&
-      other.showRight == showRight &&
-      other.hasFilter == hasFilter &&
-      other.filterVersion == filterVersion &&
-      other.token == token;
-  // token 确保不同 State 实例的 config 不相等,onBack/rightWidget 通过 token 间接区分
-
-  @override
-  int get hashCode =>
-      Object.hash(title, showBack, showRight, hasFilter, filterVersion, token);
-}
-
-/// NavBar 配置变更器,内部做相等判断避免无限重建
-///
-/// 通过 [Future.microtask] 将状态更新推迟到事件循环下一个 tick,
-/// 避免子页面在 build 中更新 provider 时与 AppShell watch 冲突。
-class NavBarConfigNotifier extends StateNotifier<NavBarConfig> {
-  NavBarConfigNotifier() : super(NavBarConfig.home);
-
-  void update(NavBarConfig config) {
-    if (state != config) {
-      Future.microtask(() {
-        if (mounted) state = config;
-      });
-    }
-  }
-}
-
-/// NavBar 配置的 Provider —— 各页面在 build 中更新,AppShell 统一消费
-final navBarConfigProvider =
-    StateNotifierProvider<NavBarConfigNotifier, NavBarConfig>(
-      (ref) => NavBarConfigNotifier(),
-    );
-
-/// 更新导航栏配置。使用 GoRouter 判断当前页面是否为可见路由:
-/// - 页面自身的 GoRouterState.uri == GoRouter 注册的当前 uri → 允许更新
-/// - 不匹配 → 被覆盖的旧页面(GoRouter 级联重建导致),跳过
-///
-/// 此方法替代了 ModalRoute.isCurrent,因为缓存引擎下旧路由残留会导致
-/// ModalRoute 误判;GoRouter 的状态不受 Widget 树生命周期影响,结果更可靠。
-void setNavBarTitle(BuildContext context, WidgetRef ref, NavBarConfig config) {
-  try {
-    final pageUri = GoRouterState.of(context).uri.toString();
-    final currentUri = GoRouter.of(context).state.uri.toString();
-    if (pageUri != currentUri) return;
-  } catch (_) {
-    // GoRouter 不可用时直接放行
-  }
-  // 注入 context 绑定的 token:同 State 多次 build 共享,换 State 自动换新 token
-  final token = _tokenForContext(context);
-  final effective = config.token != null ? config : NavBarConfig(
-    title: config.title,
-    showBack: config.showBack,
-    showRight: config.showRight,
-    rightWidget: config.rightWidget,
-    onBack: config.onBack,
-    leadingIcon: config.leadingIcon,
-    hasFilter: config.hasFilter,
-    filterVersion: config.filterVersion,
-    token: token,
-  );
-  ref.read(navBarConfigProvider.notifier).update(effective);
+  static const home = NavBarConfig(title: '', showBack: false);
+  static const messages = NavBarConfig(title: '', showBack: false);
+  static const profile = NavBarConfig(title: '', showBack: false);
 }