// 暖记路由表 — go_router 20 页面 + 认证守卫 // // 路由守卫逻辑: // - 未认证用户访问受保护路由 → 重定向到 /login // - 已认证用户访问 /login → 重定向到 /home // - 需要角色选择 → 重定向到 /role-selection // - 需要班级码 → 重定向到 /class-code export '../../widgets/responsive_scaffold.dart'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../widgets/responsive_scaffold.dart'; import '../../features/home/views/home_page.dart'; import '../../features/calendar/views/calendar_page.dart'; import '../../features/mood/views/mood_page.dart'; import '../../features/search/views/search_page.dart'; import '../../features/profile/views/profile_page.dart'; import '../../features/editor/views/editor_page.dart'; import '../../features/auth/views/login_page.dart'; import '../../features/auth/views/role_selection_page.dart'; import '../../features/auth/views/class_code_join_page.dart'; import '../../features/class_/views/class_page.dart'; import '../../features/teacher/views/teacher_page.dart'; import '../../features/parent/views/parent_page.dart'; import '../../features/achievement/views/achievement_page.dart'; import '../../features/stickers/views/sticker_library_page.dart'; import '../../features/templates/views/template_gallery_page.dart'; import '../../features/settings/views/settings_page.dart'; import '../../features/auth/bloc/auth_bloc.dart'; // Shell 分支键 final _rootNavigatorKey = GlobalKey(); final _shellNavigatorKey = GlobalKey(); /// 不需要认证的白名单路径 const _publicPaths = ['/login', '/role-selection', '/class-code']; /// 创建路由配置 — 需要注入 AuthBloc GoRouter createAppRouter(AuthBloc authBloc) { return GoRouter( navigatorKey: _rootNavigatorKey, initialLocation: '/home', debugLogDiagnostics: true, // ===== 认证路由守卫 ===== redirect: (context, state) { final authState = authBloc.state; final currentPath = state.uri.path; // 加载中 → 不做重定向 if (authState is AuthInitial || authState is AuthLoading) { return null; } final isAuthenticated = authState is Authenticated; final isPublicPath = _publicPaths.contains(currentPath); // 已认证 + 访问公开页面 → 根据状态重定向 if (isAuthenticated && isPublicPath) { if (authState.needsRoleSelection) return '/role-selection'; if (authState.needsClassCode) return '/class-code'; return '/home'; } // 已认证 + 访问受保护页面 → 检查是否需要额外步骤 if (isAuthenticated) { if (authState.needsRoleSelection && currentPath != '/role-selection') { return '/role-selection'; } if (authState.needsClassCode && currentPath != '/class-code' && currentPath != '/role-selection') { return '/class-code'; } return null; } // 未认证 + 访问公开页面 → 放行 if (isPublicPath) return null; // 未认证 + 访问受保护页面 → 重定向到登录 return '/login'; }, // 监听认证状态变化,自动触发重定向 refreshListenable: _AuthListenable(authBloc), routes: [ // 认证路由(无 Shell) GoRoute( path: '/login', name: 'login', builder: (context, state) => const LoginPage(), ), GoRoute( path: '/role-selection', name: 'roleSelection', builder: (context, state) => const RoleSelectionPage(), ), GoRoute( path: '/class-code', name: 'classCode', builder: (context, state) => const ClassCodeJoinPage(), ), // 主 Shell 路由(底部导航 + 侧边导航) ShellRoute( navigatorKey: _shellNavigatorKey, builder: (context, state, child) { final index = _selectedIndexFromLocation(state.uri.path); return _AppShell( selectedIndex: index, child: child, ); }, routes: [ GoRoute( path: '/home', name: 'home', builder: (context, state) => const HomePage(), ), GoRoute( path: '/calendar', name: 'calendar', builder: (context, state) => const CalendarPage(), ), GoRoute( path: '/mood', name: 'mood', builder: (context, state) => const MoodPage(), ), GoRoute( path: '/search', name: 'search', builder: (context, state) => const SearchPage(), ), GoRoute( path: '/profile', name: 'profile', builder: (context, state) => const ProfilePage(), ), ], ), // 全屏页面(无底部导航) GoRoute( path: '/editor', name: 'editor', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) { final journalId = state.uri.queryParameters['id']; return EditorPage(journalId: journalId); }, ), GoRoute( path: '/class', name: 'class', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const ClassPage(), ), GoRoute( path: '/teacher', name: 'teacher', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const TeacherPage(), ), GoRoute( path: '/parent', name: 'parent', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const ParentPage(), ), GoRoute( path: '/achievements', name: 'achievements', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const AchievementPage(), ), GoRoute( path: '/stickers', name: 'stickers', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const StickerLibraryPage(), ), GoRoute( path: '/templates', name: 'templates', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const TemplateGalleryPage(), ), GoRoute( path: '/settings', name: 'settings', parentNavigatorKey: _rootNavigatorKey, builder: (context, state) => const SettingsPage(), ), ], ); } /// 路径 → Tab index 映射 int _selectedIndexFromLocation(String location) { if (location.startsWith('/calendar')) return 1; if (location.startsWith('/mood')) return 2; if (location.startsWith('/search')) return 3; if (location.startsWith('/profile')) return 4; return 0; } /// AuthBloc 变化监听器 — 驱动 GoRouter refreshListenable class _AuthListenable extends ChangeNotifier { _AuthListenable(AuthBloc authBloc) { _subscription = authBloc.stream.listen((_) { notifyListeners(); }); } late final StreamSubscription _subscription; @override void dispose() { _subscription.cancel(); super.dispose(); } } /// App Shell — 包裹 ResponsiveScaffold class _AppShell extends StatelessWidget { const _AppShell({ required this.selectedIndex, required this.child, }); final int selectedIndex; final Widget child; @override Widget build(BuildContext context) { return ResponsiveScaffold( selectedIndex: selectedIndex, onDestinationSelected: (index) { switch (index) { case 0: context.go('/home'); case 1: context.go('/calendar'); case 2: context.go('/mood'); case 3: context.go('/search'); case 4: context.go('/profile'); } }, body: child, floatingActionButton: selectedIndex == 0 ? FloatingActionButton( onPressed: () => context.go('/editor'), child: const Icon(Icons.edit_rounded), ) : null, ); } }