| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import '../../core/theme/app_colors.dart';
- import 'package:go_router/go_router.dart';
- import 'package:tdesign_flutter/tdesign_flutter.dart';
- import '../../core/i18n/app_localizations.dart';
- import '../../core/theme/app_colors_extension.dart';
- import 'nav_bar_config.dart';
- /// 应用级 Scaffold:NavBar + body + 可选 BottomTabBar
- 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) {
- 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/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;
- }
- NavBarConfig _rootConfig(String location, AppLocalizations l10n) {
- 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),
- );
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- final l10n = AppLocalizations.of(context);
- final location = GoRouterState.of(context).uri.toString();
- final config = navConfig != null
- ? _resolve(navConfig!, location, l10n)
- : _rootConfig(location, l10n);
- // 页面可通过 pageBackProvider 覆盖返回按钮行为(如 create 页面的 _doPop)
- final pageBack = ref.watch(pageBackProvider);
- return Scaffold(
- resizeToAvoidBottomInset: resizeToAvoidBottomInset,
- backgroundColor: colors.bgPage,
- body: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- _NavBarView(
- config: config,
- location: location,
- pageBack: pageBack,
- onBack: () {
- if (_isRootTab(location)) {
- SystemNavigator.pop();
- } else {
- 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)),
- ],
- ),
- );
- }
- }
- class _NavBarView extends StatelessWidget {
- final NavBarConfig config;
- final String location;
- final VoidCallback? pageBack;
- final VoidCallback onBack;
- const _NavBarView({required this.config, required this.location, this.pageBack, required this.onBack});
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- List<TDNavBarItem>? leftItems;
- if (config.showBack) {
- final icon = config.leadingIcon ?? TDIcons.chevron_left;
- // 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,
- titleFontWeight: FontWeight.w600,
- titleFont: Font(size: AppFontSizes.title.toInt(), lineHeight: 26),
- backgroundColor: colors.bgCard,
- height: 56,
- centerTitle: true,
- useDefaultBack: false,
- screenAdaptation: true,
- leftBarItems: leftItems,
- rightBarItems: rightItems,
- );
- }
- }
- 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;
- return 2;
- }
- @override
- 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'),
- ),
- ],
- ),
- );
- });
- }
- }
|