/** * 路由权限配置 — 权限声明的单一真相源 * * 规则: * 1. 每个受保护路由必须在此声明至少一个权限码(TypeScript 强制非空数组) * 2. 子路由(如 /health/patients/:id)通过前缀匹配自动继承父路由权限,无需重复声明 * 3. 新增路由必须在此添加对应条目,否则 PrivateRoute 默认 403 * 4. 冻结路由标记 frozen: true,自动归入 FROZEN_ROUTES * * 排列规则:精确路径优先于前缀路径(如 /plugins/admin 在 /plugins 之前) */ // 非空数组类型 — 确保每个路由至少有一个权限码 type Permissions = [string, ...string[]]; interface RoutePermissionEntry { path: string; permissions: Permissions; frozen?: boolean; } const ENTRIES: RoutePermissionEntry[] = [ // ===== 基础模块 ===== { path: "/users", permissions: ["user.list", "user.update"] }, { path: "/roles", permissions: ["role.list", "role.update"] }, { path: "/organizations", permissions: ["organization.list", "organization.update"], }, { path: "/workflow", permissions: ["workflow.list", "workflow.read"] }, { path: "/messages", permissions: ["message.list"] }, { path: "/settings", permissions: ["setting.read", "setting.update"] }, // ===== 插件模块(精确路径优先于前缀通配) ===== { path: "/plugins/admin", permissions: ["plugin.admin"] }, { path: "/plugins/market", permissions: ["plugin.admin"] }, // 动态路由 catch-all: /plugins/:pluginId/:entityName 等 { path: "/plugins", permissions: ["plugin.list", "plugin.admin"] }, // ===== 健康管理 — 患者与医生 ===== { path: "/health/patients", permissions: ["health.patient.list", "health.patient.manage"], }, { path: "/health/tags", permissions: ["health.patient.list", "health.patient.manage"], }, { path: "/health/doctors", permissions: ["health.doctor.list", "health.doctor.manage"], }, { path: "/health/appointments", permissions: ["health.appointment.list", "health.appointment.manage"], }, // ===== 健康管理 — 随访与咨询 ===== { path: "/health/follow-up-tasks", permissions: ["health.follow-up.list", "health.follow-up.manage"], }, { path: "/health/follow-up-records", permissions: ["health.follow-up.list", "health.follow-up.manage"], }, { path: "/health/follow-up-templates", permissions: [ "health.follow-up-templates.list", "health.follow-up-templates.manage", ], }, { path: "/health/consultations", permissions: ["health.consultation.list", "health.consultation.manage"], }, { path: "/health/action-inbox", permissions: ["health.action-inbox.list", "health.action-inbox.manage"], }, // ===== 健康管理 — 告警与设备 ===== { path: "/health/alerts", permissions: ["health.alerts.list", "health.alerts.manage"], }, { path: "/health/alert-dashboard", permissions: ["health.alerts.list", "health.alerts.manage"], }, { path: "/health/alert-rules", permissions: ["health.alert-rules.list", "health.alert-rules.manage"], }, { path: "/health/devices", permissions: ["health.devices.list", "health.devices.manage"], }, { path: "/health/realtime-monitor", permissions: [ "health.device-readings.list", "health.device-readings.manage", ], }, { path: "/health/ble-gateways", permissions: ["health.ble-gateways.list", "health.ble-gateways.manage"], }, { path: "/health/critical-value-thresholds", permissions: [ "health.critical-value-thresholds.list", "health.critical-value-thresholds.manage", ], }, { path: "/health/daily-monitoring", permissions: [ "health.daily-monitoring.list", "health.daily-monitoring.manage", ], }, // ===== 健康管理 — 诊断与知情同意 ===== { path: "/health/diagnoses", permissions: ["health.health-data.list", "health.health-data.manage"], }, { path: "/health/consents", permissions: ["health.consent.list", "health.consent.manage"], }, // ===== 健康管理 — AI 模块 ===== { path: "/health/ai-prompts", permissions: ["ai.prompt.list", "ai.prompt.manage"], }, { path: "/health/ai-analysis", permissions: ["ai.analysis.list", "ai.analysis.manage"], }, { path: "/health/ai-usage", permissions: ["ai.usage.list"] }, { path: "/health/ai-config", permissions: ["ai.config.read", "ai.config.manage"], }, { path: "/health/ai-knowledge", permissions: ["ai.knowledge.list", "ai.knowledge.manage"], }, // ===== 健康管理 — 积分商城 ===== { path: "/health/points-rules", permissions: ["health.points.list", "health.points.manage"], }, { path: "/health/points-products", permissions: ["health.points.list", "health.points.manage"], }, { path: "/health/points-orders", permissions: ["health.points.list", "health.points.manage"], }, { path: "/health/offline-events", permissions: ["health.points.list", "health.points.manage"], }, // ===== 健康管理 — 内容管理 ===== { path: "/health/articles", permissions: ["health.articles.list", "health.articles.manage"], }, { path: "/health/article-categories", permissions: ["health.articles.list", "health.articles.manage"], }, { path: "/health/article-tags", permissions: ["health.articles.list", "health.articles.manage"], }, { path: "/health/banners", permissions: ["health.banners.list", "health.banners.manage"], }, { path: "/health/media-library", permissions: ["health.media.list", "health.media.manage"], }, // ===== 健康管理 — 其他 ===== { path: "/health/oauth-clients", permissions: ["health.oauth.list", "health.oauth.manage"], }, { path: "/health/statistics", permissions: ["health.health-data.list", "health.dashboard.manage"], }, { path: "/health/medication-records", permissions: ["health.medication-records.list", "health.medication-records.manage"], }, // ===== 冻结路由 ===== { path: "/health/care-plans", permissions: ["health.care-plan.list", "health.care-plan.manage"], frozen: true, }, { path: "/health/shifts", permissions: ["health.shifts.list", "health.shifts.manage"], frozen: true, }, { path: "/health/family-proxy", permissions: ["health.family-proxy.list", "health.family-proxy.manage"], frozen: true, }, { path: "/health/medications", permissions: [ "health.medication-records.list", "health.medication-records.manage", ], frozen: true, }, { path: "/health/dialysis", permissions: ["health.dialysis.list", "health.dialysis.manage"], frozen: true, }, { path: "/health/dialysis-prescriptions", permissions: ["health.dialysis-prescription.list", "health.dialysis-prescription.manage"], frozen: true, }, { path: "/health/schedules", permissions: ["health.appointment.list", "health.appointment.manage"], }, // ===== AI 聊天 ===== { path: "/ai/chat", permissions: ["ai.chat.session.list", "ai.chat.session.manage"], }, ]; /** 活跃路由的权限映射 — 自动从配置生成,供 PrivateRoute 使用 */ export const ROUTE_PERMISSIONS: Record = Object.fromEntries( ENTRIES.filter((e) => !e.frozen).map((e) => [e.path, [...e.permissions]]), ); /** 冻结路由路径列表 — 自动从配置生成 */ export const FROZEN_ROUTES: string[] = ENTRIES.filter((e) => e.frozen).map( (e) => e.path, ); /** 开发模式下校验:检查是否有路由路径重复 */ if (import.meta.env.DEV) { const paths = ENTRIES.map((e) => e.path); const dupes = paths.filter((p, i) => paths.indexOf(p) !== i); if (dupes.length > 0) { console.error("[routeConfig] 检测到重复路径:", dupes); } } /** * Tab 级权限映射 — 详情页内 Tab 可见性的单一真相源 * * key 格式: "{routePrefix}#{tabKey}" * value: 权限码(undefined 表示始终可见) * * 新增详情页 Tab 时必须在此声明,否则 Tab 默认不可见(安全默认)。 */ const TAB_PERM_ENTRIES: Array<{ key: string; permission?: string }> = [ // 患者详情 /health/patients/:id { key: "patient#info", permission: undefined }, { key: "patient#family", permission: "health.patient.manage" }, { key: "patient#health", permission: "health.health-data.list" }, { key: "patient#followup", permission: "health.follow-up.list" }, { key: "patient#points", permission: "health.points.list" }, { key: "patient#ai", permission: "ai.analysis.list" }, ]; export const TAB_PERMISSIONS: Record = Object.fromEntries( TAB_PERM_ENTRIES.map((e) => [e.key, e.permission]), ); /** * DEV 模式路由覆盖率校验。 * 在 App.tsx 挂载后调用,检查所有实际路由是否都有权限声明。 * 忽略带参数的子路由(如 /health/patients/:id),它们通过前缀匹配继承权限。 */ export function validateRouteCoverage(registeredPaths: string[]): void { if (!import.meta.env.DEV) return; const allConfigPaths = ENTRIES.map((e) => e.path); const uncovered = registeredPaths.filter((p) => { if (p === "/" || p === "/login" || p === "/*") return false; if (p.includes(":")) return false; // 子路由通过前缀继承 return !allConfigPaths.includes(p); }); if (uncovered.length > 0) { console.warn( "[routeConfig] 以下路由未在 routeConfig.ts 中声明权限,将被 PrivateRoute 默认 403 拦截:", uncovered, ); } }