Files
CloudHome-Ops/lib/app/router.dart

356 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../providers/auth_provider.dart';
import '../utils/permissions.dart';
import '../screens/admin/admin_hub_page.dart';
import '../screens/admin/app/app_version_page.dart';
import '../screens/admin/app/tv_app_page.dart';
import '../screens/admin/channel/channel_management_page.dart';
import '../screens/admin/intro/intro_category_page.dart';
import '../screens/admin/intro/intro_store_content_page.dart';
import '../screens/admin/intro/intro_store_list_page.dart';
import '../screens/admin/marquee/marquee_management_page.dart';
import '../screens/admin/media/media_list_page.dart';
import '../screens/admin/media/media_rank_page.dart';
import '../screens/admin/sys/config_page.dart';
import '../screens/admin/sys/log_page.dart';
import '../screens/admin/sys/menu_page.dart';
import '../screens/admin/sys/role_page.dart';
import '../screens/admin/sys/schedule_page.dart';
import '../screens/admin/sys/user_page.dart';
import '../screens/device/device_activation_page.dart';
import '../screens/device/device_group_page.dart';
import '../screens/device/device_scan_page.dart';
import '../screens/device/store_config_page.dart';
import '../screens/home/device_detail_page.dart';
import '../screens/home/device_list_page.dart';
import '../screens/home/home_page.dart';
import '../screens/login/login_page.dart';
import '../models/store_info.dart';
import '../screens/profile/settings_page.dart';
import '../screens/store/org_tree_page.dart';
import '../screens/store/store_detail_page.dart';
import '../screens/store/store_list_page.dart';
import '../widgets/app_bottom_nav.dart';
/// 全局路由配置。
///
/// 路由表:
/// - `/login` 登录页(未登录时重定向到此)
/// - `/` 首页仪表盘(底部导航第 1 个 tab)
/// - `/device` 设备列表(底部导航第 2 个 tab)
/// - `/device/:id` 设备详情
/// - `/device/group` 设备分组
/// - `/device/activation` 激活码管理
/// - `/device/store-config` 门店配置
/// - `/store` 门店管理(底部导航第 3 个 tab)
/// - `/store/org-tree` 组织架构
/// - `/store/:id` 门店详情
/// - `/admin` 管理中心(底部导航第 4 个 tab)
/// - `/admin/sys/*` 系统管理子模块
/// - `/admin/app/*` 应用管理子模块
/// - `/admin/media/*` 媒资管理子模块
/// - `/admin/intro/*` 公司介绍子模块
/// - `/profile` 我的(底部导航第 5 个 tab)
/// - `/profile/settings` 设置
final appRouterProvider = Provider<GoRouter>((ref) {
// 登录态变化时触发路由重定向刷新,而不重建整个 GoRouter。
final refreshNotifier = ValueNotifier<int>(0);
ref
..onDispose(refreshNotifier.dispose)
..listen(authProvider, (_, __) => refreshNotifier.value++);
return GoRouter(
initialLocation: '/',
refreshListenable: refreshNotifier,
redirect: (context, state) {
final auth = ref.read(authProvider);
final loggedIn = auth.isLoggedIn;
final isLoginRoute = state.matchedLocation == '/login';
if (!loggedIn && !isLoginRoute) {
return '/login';
}
if (loggedIn && isLoginRoute) {
return '/';
}
// 功能模块按账号权限收敛(菜单可见性与此处路由可达性由同一套
// [SessionPermissions] 谓词驱动,确保一致):
// - 系统管理中仅 /admin/sys/user(用户管理)对所有已登录账号开放;
// 其余 /admin/sys/*(角色/菜单/配置/日志/定时任务)仅管理员可访问;
// - 媒资 /admin/media/*、应用 /admin/app/*、公司介绍(分类维度)
// /admin/intro/category 仅管理员可见/可访问;
// - 渠道商管理 /admin/channel 对管理员与渠道商开放,代理商不可访问;
// 非管理员(受限账号)即使通过深链/返回键进入上述受限路由也重定向回
// 管理首页,作为入口收敛的兜底,防止越权查看/操作跨组织数据。
// 注:公司介绍门店维度入口(/admin/intro/store-list、/admin/intro/
// store-content/*)按门店维度展示、数据已由 getMyStores 收敛,对所有账号开放。
final loc = state.matchedLocation;
final user = ref.read(authProvider).user;
final cat = SessionPermissions.category(user);
if (cat != AccountCategory.admin) {
final isUserManagement =
loc == '/admin/sys/user' || loc.startsWith('/admin/sys/user/');
final isSysModule = loc.startsWith('/admin/sys') && !isUserManagement;
if (isSysModule ||
loc.startsWith('/admin/media') ||
loc.startsWith('/admin/app') ||
loc.startsWith('/admin/intro/category')) {
return '/admin';
}
// 代理商(非渠道商)访问渠道商管理 → 弹回(其菜单中本就隐藏该入口)。
if (loc.startsWith('/admin/channel') &&
cat != AccountCategory.channelMerchant) {
return '/admin';
}
}
return null;
},
routes: [
GoRoute(
path: '/login',
name: 'login',
builder: (context, state) => const LoginPage(),
),
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) =>
AppBottomNav(shell: navigationShell),
branches: [
// 首页(仪表盘)
StatefulShellBranch(
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomePage(),
),
],
),
// 设备
StatefulShellBranch(
routes: [
GoRoute(
path: '/device',
name: 'device',
builder: (context, state) => const DeviceListPage(),
routes: [
// 具名子路由(group/activation/store-config/scan)必须放在参数路由 :id 之前,
// 否则 `/device/scan` 会被 `:id` 匹配成设备 ID="scan",导致进入错误的详情页。
GoRoute(
path: 'group',
name: 'deviceGroup',
builder: (context, state) => const DeviceGroupPage(),
),
GoRoute(
path: 'activation',
name: 'deviceActivation',
builder: (context, state) =>
const DeviceActivationPage(),
),
GoRoute(
path: 'store-config',
name: 'storeConfig',
builder: (context, state) => const StoreConfigPage(),
),
GoRoute(
path: 'scan',
name: 'deviceScan',
builder: (context, state) {
final mode =
state.uri.queryParameters['mode'] ?? 'query';
final deviceId =
state.uri.queryParameters['deviceId'];
final storeId =
state.uri.queryParameters['storeId'];
final storeName =
state.uri.queryParameters['storeName'];
final groupId =
state.uri.queryParameters['groupId'];
return DeviceScanPage(
mode: mode,
deviceId: deviceId,
storeId: storeId,
storeName: storeName,
groupId: groupId,
);
},
),
GoRoute(
path: ':id',
name: 'deviceDetail',
builder: (context, state) {
final id = state.pathParameters['id'] ?? '';
return DeviceDetailPage(deviceId: id);
},
),
],
),
],
),
// 门店
StatefulShellBranch(
routes: [
GoRoute(
path: '/store',
name: 'store',
builder: (context, state) => const StoreListPage(),
routes: [
GoRoute(
path: 'org-tree',
name: 'orgTree',
builder: (context, state) => const OrgTreePage(),
),
GoRoute(
path: ':id',
name: 'storeDetail',
builder: (context, state) {
final id = state.pathParameters['id'] ?? '';
return StoreDetailPage(storeId: id);
},
),
],
),
],
),
// 管理
StatefulShellBranch(
routes: [
GoRoute(
path: '/admin',
name: 'admin',
builder: (context, state) => const AdminHubPage(),
routes: [
// 管理模块内的设备/激活码别名路由:复用设备分支的页面组件,
// 但在 /admin 分支栈下 push,使底部导航仍停留「管理」tab,
// 返回键可正常 pop 回管理首页,避免此前 push 到 /device 分支栈导致的返回键异常。
GoRoute(
path: 'device',
name: 'adminDevice',
builder: (context, state) => const DeviceListPage(),
),
GoRoute(
path: 'activation',
name: 'adminActivation',
builder: (context, state) =>
const DeviceActivationPage(),
),
// 系统管理
GoRoute(
path: 'sys/user',
name: 'sysUser',
builder: (context, state) => const UserPage(),
),
GoRoute(
path: 'sys/role',
name: 'sysRole',
builder: (context, state) => const RolePage(),
),
GoRoute(
path: 'sys/menu',
name: 'sysMenu',
builder: (context, state) => const MenuPage(),
),
GoRoute(
path: 'sys/config',
name: 'sysConfig',
builder: (context, state) => const ConfigPage(),
),
GoRoute(
path: 'sys/log',
name: 'sysLog',
builder: (context, state) => const LogPage(),
),
GoRoute(
path: 'sys/schedule',
name: 'sysSchedule',
builder: (context, state) => const SchedulePage(),
),
// 应用管理
GoRoute(
path: 'app/version',
name: 'appVersion',
builder: (context, state) => const AppVersionPage(),
),
GoRoute(
path: 'app/tv-app',
name: 'tvApp',
builder: (context, state) => const TvAppPage(),
),
// 滚动字幕管理
GoRoute(
path: 'marquee',
name: 'marqueeManagement',
builder: (context, state) => const MarqueeManagementPage(),
),
// 渠道商管理
GoRoute(
path: 'channel',
name: 'channelManagement',
builder: (context, state) => const ChannelManagementPage(),
),
// 媒资管理
GoRoute(
path: 'media/list',
name: 'mediaList',
builder: (context, state) => const MediaListPage(),
),
GoRoute(
path: 'media/rank',
name: 'mediaRank',
builder: (context, state) => const MediaRankPage(),
),
// 公司介绍(门店维度)
GoRoute(
path: 'intro/store-list',
name: 'introStoreList',
builder: (context, state) => const IntroStoreListPage(),
),
GoRoute(
path: 'intro/store-content/:storeId',
name: 'introStoreContent',
builder: (context, state) {
final storeId = state.pathParameters['storeId'] ?? '';
final store = state.extra is StoreInfo
? state.extra as StoreInfo
: null;
return IntroStoreContentPage(
storeId: storeId,
store: store,
);
},
),
// 公司介绍(原分类维度,保留作为管理备用入口)
GoRoute(
path: 'intro/category',
name: 'introCategory',
builder: (context, state) => const IntroCategoryPage(),
),
],
),
],
),
// 我的
StatefulShellBranch(
routes: [
GoRoute(
path: '/profile',
name: 'profile',
builder: (context, state) => const SettingsPage(),
routes: [
GoRoute(
path: 'settings',
name: 'profileSettings',
builder: (context, state) => const SettingsPage(),
),
],
),
],
),
],
),
],
);
});