§9.1 line 629 教师域 lesson-plans 行:
- /lesson-plans (列表) / /new (表单) / /library (教案库)
- /calendar (日历) / /heatmap (热力图) / /[planId]/edit (工作台)
契约:全 ❌ schema 无 → MSW 兜底 + @contract-pending
新增文件:
- src/lib/api/lesson-plans.ts (8 hooks)
- src/lib/api/operations/lesson-plans.graphql.ts (8 documents)
- src/features/teacher/lesson-plans/ (clients + transformations + tests)
- src/app/shell/teacher/lesson-plans/ (6 page.tsx + loading.tsx + error.tsx)
修改文件:
- src/mocks/graphql-data.ts (mock 数据 + handler cases)
- src/messages/{zh-CN,en}.json (lessonPlans i18n 命名空间)
- src/lib/api/{index,operations/index}.ts (导出 lesson-plans)
- src/shared/lib/route-permissions.ts (lesson-plans 路由权限声明)
- scripts/check-page-count.ts (baseline 31 → 37)
DoD 验收(§11.3 11 项):
- typecheck 0 errors
- lint 0 errors
- vitest 405 tests passed (新增 48 tests)
- lint:tokens 0 errors
- check:pages 37 PASS
- route-permissions 已声明
- 三态(loading/error/empty)齐备
- @contract-pending + MSW 兜底
- i18n zh-CN + en 同步
关联:ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §10 P2 / §11.3 / §11.4
契约工单:docs/architecture/issues/contracts/core-edu_contract.md
566 lines
17 KiB
TypeScript
566 lines
17 KiB
TypeScript
/**
|
||
* 路由权限配置表(对齐 CICD 项目 route-permissions.ts)
|
||
*
|
||
* 4 张表按优先级顺序匹配(精确 > 前缀 > 仪表盘 > API):
|
||
* 1. EXACT_ROUTE_PERMISSIONS:精确路由(如 /shell/admin/users)
|
||
* 2. PREFIX_ROUTE_PERMISSIONS:前缀路由(如 /shell/admin/*)
|
||
* 3. DASHBOARD_ROUTE_PERMISSIONS:仪表盘路由(按角色分发)
|
||
* 4. API_ROUTE_PERMISSIONS:Next.js API Route(/api/*)
|
||
*
|
||
* 三层安全边界(portal-shell README v2.0 §3.3):
|
||
* - L1 角色门禁:requiredRoles(4 角色之一)
|
||
* - L2 权限点门禁:requiredPermissions(AND 语义,必须全部满足)
|
||
* - L3 数据范围:运行时由插件/page 内 usePermission 校验
|
||
*
|
||
* 使用方式(middleware / page / layout):
|
||
* ```ts
|
||
* import { checkRoutePermission } from "@/shared/lib/route-permissions";
|
||
*
|
||
* const result = checkRoutePermission(pathname, userBitmap, userRole);
|
||
* if (!result.allowed) redirect("/shell/forbidden");
|
||
* ```
|
||
*
|
||
* 关联:portal-shell README v2.0 §3.3、project_rules §3.1(禁止 role === "xxx" 硬编码)
|
||
*/
|
||
|
||
import type { Role } from "@edu/shared-ts/contracts";
|
||
import {
|
||
hasAllPermissionsInBitmap,
|
||
hasAnyPermissionInBitmap,
|
||
isValidPermission,
|
||
} from "@edu/shared-ts/permission-bitmap";
|
||
|
||
/**
|
||
* 路由权限配置项
|
||
*/
|
||
export interface RoutePermissionConfig {
|
||
/** 所需角色(任一满足即可;空数组表示不限制角色) */
|
||
requiredRoles?: Role[];
|
||
/**
|
||
* 所需权限点(AND 语义,必须全部满足)
|
||
* 权限点必须来自 PERMISSION_BITMAP_ORDER
|
||
*/
|
||
requiredPermissions?: string[];
|
||
/**
|
||
* 所需权限点(OR 语义,任一满足即可)
|
||
* 与 requiredPermissions 同时存在时,先 AND 再 OR
|
||
*/
|
||
anyOfPermissions?: string[];
|
||
}
|
||
|
||
/**
|
||
* 路由权限检查结果
|
||
*/
|
||
export interface RoutePermissionResult {
|
||
/** 是否允许访问 */
|
||
allowed: boolean;
|
||
/** 拒绝原因(allowed=false 时填充) */
|
||
reason?: "missing_role" | "missing_permission" | "no_config";
|
||
/** 匹配到的配置(用于调试) */
|
||
matchedPath?: string;
|
||
/** 缺失的权限点(allowed=false 且 reason=missing_permission 时填充) */
|
||
missingPermissions?: string[];
|
||
}
|
||
|
||
/**
|
||
* 1. 精确路由权限表
|
||
*
|
||
* 高优先级,pathname 完全匹配时生效。
|
||
* 适用于功能明确、URL 固定的页面(用户管理、RBAC、审计日志等)。
|
||
*/
|
||
export const EXACT_ROUTE_PERMISSIONS: Record<string, RoutePermissionConfig> = {
|
||
// ── admin 专属 ────────────────────────────────────────────
|
||
"/shell/admin/users": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["USER_MANAGE"],
|
||
},
|
||
"/shell/admin/roles": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["ROLE_MANAGE"],
|
||
},
|
||
"/shell/admin/permissions": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["PERMISSION_MANAGE"],
|
||
},
|
||
"/shell/admin/audit-logs": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["AUDIT_LOG_READ"],
|
||
},
|
||
"/shell/admin/school": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["SCHOOL_MANAGE"],
|
||
},
|
||
"/shell/admin/plugins": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["PLUGIN_REGISTRY_MANAGE"],
|
||
},
|
||
"/shell/admin/invitation-codes": {
|
||
requiredRoles: ["admin"],
|
||
anyOfPermissions: ["INVITATION_CODE_MANAGE", "INVITATION_CODE_CREATE"],
|
||
},
|
||
// P1-1:列表页根路由(无尾斜杠)需在 EXACT 表登记,
|
||
// 否则 middleware checkRoutePermission 落入 /shell/** catch-all 拒绝
|
||
"/shell/admin/announcements": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["ANNOUNCEMENT_MANAGE"],
|
||
},
|
||
"/shell/admin/classes": {
|
||
requiredRoles: ["admin"],
|
||
anyOfPermissions: ["CLASS_READ", "CLASS_MANAGE"],
|
||
},
|
||
|
||
// ── teacher 专属 ──────────────────────────────────────────
|
||
"/shell/teacher/lesson-plans": {
|
||
requiredRoles: ["teacher"],
|
||
anyOfPermissions: [
|
||
"LESSON_PLAN_READ",
|
||
"LESSON_PLAN_CREATE",
|
||
"LESSON_PLAN_UPDATE",
|
||
],
|
||
},
|
||
"/shell/teacher/question-bank": {
|
||
requiredRoles: ["teacher"],
|
||
anyOfPermissions: ["QUESTION_READ", "QUESTION_CREATE"],
|
||
},
|
||
"/shell/teacher/textbooks": {
|
||
requiredRoles: ["teacher", "admin"],
|
||
requiredPermissions: ["TEXTBOOK_READ"],
|
||
},
|
||
"/shell/teacher/scheduling-rules": {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["SCHEDULE_AUTO", "SCHEDULE_ADJUST", "SCHEDULE_MANAGE"],
|
||
},
|
||
// P1-1:列表页根路由(无尾斜杠)需在 EXACT 表登记,
|
||
// 与 PREFIX 表(带尾斜杠,匹配子路由如 /shell/teacher/exams/123)互补
|
||
"/shell/teacher/exams": {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["EXAM_READ", "EXAM_CREATE", "EXAM_UPDATE", "EXAM_GRADE"],
|
||
},
|
||
"/shell/teacher/homework": {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["HOMEWORK_READ", "HOMEWORK_CREATE", "HOMEWORK_GRADE"],
|
||
},
|
||
"/shell/teacher/grades": {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["GRADE_RECORD_MANAGE", "GRADE_RECORD_READ"],
|
||
},
|
||
"/shell/teacher/attendance": {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["ATTENDANCE_READ", "ATTENDANCE_MANAGE"],
|
||
},
|
||
"/shell/teacher/diagnostics": {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["DIAGNOSTIC_READ", "DIAGNOSTIC_MANAGE"],
|
||
},
|
||
|
||
// ── student 专属 ──────────────────────────────────────────
|
||
"/shell/student/error-book": {
|
||
requiredRoles: ["student"],
|
||
requiredPermissions: ["ERROR_BOOK_READ"],
|
||
},
|
||
"/shell/student/learning-path": {
|
||
requiredRoles: ["student"],
|
||
requiredPermissions: ["LEARNING_PATH_READ"],
|
||
},
|
||
"/shell/student/electives": {
|
||
requiredRoles: ["student"],
|
||
anyOfPermissions: ["ELECTIVE_READ", "ELECTIVE_SELECT"],
|
||
},
|
||
"/shell/student/ai-tutor": {
|
||
requiredRoles: ["student"],
|
||
requiredPermissions: ["AI_TUTOR_USE"],
|
||
},
|
||
|
||
// ── parent 专属 ───────────────────────────────────────────
|
||
"/shell/parent/children": {
|
||
requiredRoles: ["parent"],
|
||
requiredPermissions: ["GRADE_READ_CHILD"],
|
||
},
|
||
"/shell/parent/leave-approval": {
|
||
requiredRoles: ["parent"],
|
||
requiredPermissions: ["LEAVE_APPROVAL_MANAGE"],
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 2. 前缀路由权限表
|
||
*
|
||
* 中优先级,pathname 以指定前缀开头时生效。
|
||
* 适用于功能集合下的所有子路由(/shell/admin/* /shell/teacher/exams/* 等)。
|
||
*
|
||
* 注意:前缀必须以 / 结尾,避免误匹配(如 /shell/admin 不能匹配 /shell/admin-users)。
|
||
*/
|
||
export const PREFIX_ROUTE_PERMISSIONS: Array<{
|
||
prefix: string;
|
||
config: RoutePermissionConfig;
|
||
}> = [
|
||
// admin 区所有子路由默认要求 admin 角色
|
||
{
|
||
prefix: "/shell/admin/",
|
||
config: { requiredRoles: ["admin"] },
|
||
},
|
||
// 考试管理
|
||
{
|
||
prefix: "/shell/teacher/exams/",
|
||
config: {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: [
|
||
"EXAM_READ",
|
||
"EXAM_CREATE",
|
||
"EXAM_UPDATE",
|
||
"EXAM_GRADE",
|
||
],
|
||
},
|
||
},
|
||
// 作业管理
|
||
{
|
||
prefix: "/shell/teacher/homework/",
|
||
config: {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["HOMEWORK_READ", "HOMEWORK_CREATE", "HOMEWORK_GRADE"],
|
||
},
|
||
},
|
||
// 教案管理(P2 迁移)
|
||
{
|
||
prefix: "/shell/teacher/lesson-plans/",
|
||
config: {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: [
|
||
"LESSON_PLAN_READ",
|
||
"LESSON_PLAN_CREATE",
|
||
"LESSON_PLAN_UPDATE",
|
||
],
|
||
},
|
||
},
|
||
// 成绩录入
|
||
{
|
||
prefix: "/shell/teacher/grades/",
|
||
config: {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["GRADE_RECORD_MANAGE", "GRADE_RECORD_READ"],
|
||
},
|
||
},
|
||
// 考勤
|
||
{
|
||
prefix: "/shell/teacher/attendance/",
|
||
config: {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["ATTENDANCE_READ", "ATTENDANCE_MANAGE"],
|
||
},
|
||
},
|
||
// 班级管理
|
||
{
|
||
prefix: "/shell/admin/classes/",
|
||
config: {
|
||
requiredRoles: ["admin"],
|
||
anyOfPermissions: ["CLASS_READ", "CLASS_MANAGE"],
|
||
},
|
||
},
|
||
// 学情诊断
|
||
{
|
||
prefix: "/shell/teacher/diagnostics/",
|
||
config: {
|
||
requiredRoles: ["teacher", "admin"],
|
||
anyOfPermissions: ["DIAGNOSTIC_READ", "DIAGNOSTIC_MANAGE"],
|
||
},
|
||
},
|
||
// 公告管理
|
||
{
|
||
prefix: "/shell/admin/announcements/",
|
||
config: {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["ANNOUNCEMENT_MANAGE"],
|
||
},
|
||
},
|
||
// P1-3:dev 模板预览页(仅登录可访问,运行时由 notFound() 拒绝生产访问)
|
||
// ARCHITECTURE.md §10 P1-3:/shell/dev/templates/* 仅 dev 可见
|
||
// 注:Next.js 私有文件夹以 _ 开头会被排除路由,故使用 `dev` 而非 `_dev`
|
||
{
|
||
prefix: "/shell/dev/",
|
||
config: {}, // 空 config = 仅校验登录身份
|
||
},
|
||
];
|
||
|
||
/**
|
||
* 3. 仪表盘路由权限表
|
||
*
|
||
* 低优先级,按角色分发的根仪表盘。
|
||
* 当 pathname 不匹配前两张表时,检查是否为角色仪表盘根路径。
|
||
*/
|
||
export const DASHBOARD_ROUTE_PERMISSIONS: Record<
|
||
string,
|
||
RoutePermissionConfig
|
||
> = {
|
||
"/shell/admin": {
|
||
requiredRoles: ["admin"],
|
||
requiredPermissions: ["DASHBOARD_ADMIN_READ"],
|
||
},
|
||
"/shell/teacher": {
|
||
requiredRoles: ["teacher"],
|
||
requiredPermissions: ["DASHBOARD_TEACHER_READ"],
|
||
},
|
||
"/shell/student": {
|
||
requiredRoles: ["student"],
|
||
requiredPermissions: ["DASHBOARD_STUDENT_READ"],
|
||
},
|
||
"/shell/parent": {
|
||
requiredRoles: ["parent"],
|
||
requiredPermissions: ["DASHBOARD_PARENT_READ"],
|
||
},
|
||
// 通用仪表盘
|
||
"/shell": {
|
||
requiredPermissions: ["DASHBOARD_READ"],
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 4. Next.js API Route 权限表
|
||
*
|
||
* 用于 /api/* 路径的权限校验。
|
||
* 注意:API Route 通常需要更严格的权限校验,因为它们直接操作数据。
|
||
*
|
||
* P0-2(ARCHITECTURE.md §6.2 §3.4 V3-A3):新增 auth/graphql 公开端点。
|
||
*/
|
||
export const API_ROUTE_PERMISSIONS: Record<string, RoutePermissionConfig> = {
|
||
// 错误上报端点:所有登录用户可访问(空 config 表示登录即可)
|
||
"/api/log": {},
|
||
// 健康检查:公开
|
||
"/api/healthz": {},
|
||
"/api/health": {},
|
||
"/api/ready": {},
|
||
// 认证端点:公开(未登录也要能调登录接口)
|
||
"/api/auth/login": {},
|
||
"/api/auth/logout": {},
|
||
// GraphQL 同域代理:登录即可(细粒度由后端 resolver 把关)
|
||
"/api/graphql": {},
|
||
};
|
||
|
||
/**
|
||
* 5. 公共路由白名单(P0-2,ARCHITECTURE.md §6.2 §3.4 V3-A3 / §11.7 红线 #5)
|
||
*
|
||
* 这些路由允许匿名访问(登录前/无身份也能访问)。
|
||
* middleware 对白名单路由跳过身份校验,直接放行。
|
||
*
|
||
* 注意:白名单外的路由,未登录访问 → middleware 重定向到 /login。
|
||
*/
|
||
export const PUBLIC_ROUTES: readonly string[] = [
|
||
"/",
|
||
"/login",
|
||
"/shell/forbidden",
|
||
"/api/health",
|
||
"/api/healthz",
|
||
"/api/ready",
|
||
"/api/log",
|
||
"/api/auth/login",
|
||
"/api/auth/logout",
|
||
"/api/graphql",
|
||
];
|
||
|
||
/**
|
||
* 校验权限配置的合法性(开发时辅助)
|
||
*
|
||
* 检查所有声明的权限点是否在 PERMISSION_BITMAP_ORDER 中。
|
||
* 在 dev 模式下打 warning,生产构建时可阻断。
|
||
*
|
||
* @returns 非法权限点列表(空数组表示全部合法)
|
||
*/
|
||
export function validateRoutePermissionConfigs(): string[] {
|
||
const invalid: string[] = [];
|
||
const allConfigs: Array<{ source: string; config: RoutePermissionConfig }> = [
|
||
...Object.entries(EXACT_ROUTE_PERMISSIONS).map(([path, config]) => ({
|
||
source: `EXACT:${path}`,
|
||
config,
|
||
})),
|
||
...PREFIX_ROUTE_PERMISSIONS.map(({ prefix, config }) => ({
|
||
source: `PREFIX:${prefix}`,
|
||
config,
|
||
})),
|
||
...Object.entries(DASHBOARD_ROUTE_PERMISSIONS).map(([path, config]) => ({
|
||
source: `DASHBOARD:${path}`,
|
||
config,
|
||
})),
|
||
...Object.entries(API_ROUTE_PERMISSIONS).map(([path, config]) => ({
|
||
source: `API:${path}`,
|
||
config,
|
||
})),
|
||
];
|
||
|
||
for (const { source, config } of allConfigs) {
|
||
for (const perm of config.requiredPermissions ?? []) {
|
||
if (!isValidPermission(perm)) {
|
||
invalid.push(`${source}:requiredPermissions:${perm}`);
|
||
}
|
||
}
|
||
for (const perm of config.anyOfPermissions ?? []) {
|
||
if (!isValidPermission(perm)) {
|
||
invalid.push(`${source}:anyOfPermissions:${perm}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
return invalid;
|
||
}
|
||
|
||
/**
|
||
* 路由权限检查主函数
|
||
*
|
||
* 按优先级顺序匹配 4 张表,返回检查结果。
|
||
*
|
||
* @param pathname 当前路径(如 /shell/admin/users)
|
||
* @param userBitmap 用户权限位图(base36 字符串,从 JWT cookie 解析)
|
||
* @param userRole 用户角色
|
||
* @returns 检查结果,allowed=true 表示放行
|
||
*
|
||
* @example
|
||
* ```ts
|
||
* const result = checkRoutePermission("/shell/admin/users", "abc123", "admin");
|
||
* if (!result.allowed) {
|
||
* redirect("/shell/forbidden");
|
||
* }
|
||
* ```
|
||
*/
|
||
export function checkRoutePermission(
|
||
pathname: string,
|
||
userBitmap: string,
|
||
userRole: Role,
|
||
): RoutePermissionResult {
|
||
// 0. 公共路由白名单优先(含 /shell/forbidden 自身,避免循环重定向)
|
||
if (PUBLIC_ROUTES.includes(pathname)) {
|
||
return { allowed: true, matchedPath: "PUBLIC" };
|
||
}
|
||
|
||
// 1. 匹配精确路由
|
||
const exactConfig = EXACT_ROUTE_PERMISSIONS[pathname];
|
||
if (exactConfig) {
|
||
return evaluateConfig(exactConfig, userBitmap, userRole, pathname);
|
||
}
|
||
|
||
// 2. 匹配前缀路由
|
||
for (const { prefix, config } of PREFIX_ROUTE_PERMISSIONS) {
|
||
if (pathname.startsWith(prefix)) {
|
||
return evaluateConfig(config, userBitmap, userRole, prefix);
|
||
}
|
||
}
|
||
|
||
// 3. 匹配仪表盘路由
|
||
const dashboardConfig = DASHBOARD_ROUTE_PERMISSIONS[pathname];
|
||
if (dashboardConfig) {
|
||
return evaluateConfig(dashboardConfig, userBitmap, userRole, pathname);
|
||
}
|
||
|
||
// 4. 匹配 API 路由
|
||
if (pathname.startsWith("/api/")) {
|
||
const apiConfig = API_ROUTE_PERMISSIONS[pathname];
|
||
if (apiConfig) {
|
||
return evaluateConfig(apiConfig, userBitmap, userRole, pathname);
|
||
}
|
||
// 未配置的 API 路由默认拒绝(fail-closed,§11.7 红线 #5)
|
||
return {
|
||
allowed: false,
|
||
reason: "no_config",
|
||
};
|
||
}
|
||
|
||
// 5. /shell/** 下未登记路由 → 默认拒绝(fail-closed,§3.4 V3-A3 / §11.7 红线 #5)
|
||
// 防止"幽灵路由"绕过门禁;新增路由必须显式登记到 EXACT/PREFIX/DASHBOARD 表
|
||
if (pathname.startsWith("/shell/") || pathname === "/shell") {
|
||
return {
|
||
allowed: false,
|
||
reason: "no_config",
|
||
};
|
||
}
|
||
|
||
// 6. 其他未匹配路由(如 /favicon.ico / 静态资源)默认放行
|
||
return { allowed: true };
|
||
}
|
||
|
||
/**
|
||
* 评估单个权限配置
|
||
*/
|
||
function evaluateConfig(
|
||
config: RoutePermissionConfig,
|
||
userBitmap: string,
|
||
userRole: Role,
|
||
matchedPath: string,
|
||
): RoutePermissionResult {
|
||
// L1 角色门禁
|
||
if (config.requiredRoles && config.requiredRoles.length > 0) {
|
||
if (!config.requiredRoles.includes(userRole)) {
|
||
return {
|
||
allowed: false,
|
||
reason: "missing_role",
|
||
matchedPath,
|
||
};
|
||
}
|
||
}
|
||
|
||
// L2 权限点门禁 - AND 语义
|
||
const missingPermissions: string[] = [];
|
||
if (config.requiredPermissions && config.requiredPermissions.length > 0) {
|
||
for (const perm of config.requiredPermissions) {
|
||
// 使用 hasAllPermissionsInBitmap 不合适(它返回 boolean 不告知哪些缺失)
|
||
// 这里手动遍历以便收集缺失项
|
||
const bit = hasPermissionInBitmapSimple(userBitmap, perm);
|
||
if (!bit) {
|
||
missingPermissions.push(perm);
|
||
}
|
||
}
|
||
if (missingPermissions.length > 0) {
|
||
return {
|
||
allowed: false,
|
||
reason: "missing_permission",
|
||
matchedPath,
|
||
missingPermissions,
|
||
};
|
||
}
|
||
}
|
||
|
||
// L2 权限点门禁 - OR 语义
|
||
if (config.anyOfPermissions && config.anyOfPermissions.length > 0) {
|
||
if (!hasAnyPermissionInBitmap(userBitmap, config.anyOfPermissions)) {
|
||
return {
|
||
allowed: false,
|
||
reason: "missing_permission",
|
||
matchedPath,
|
||
missingPermissions: config.anyOfPermissions,
|
||
};
|
||
}
|
||
}
|
||
|
||
return { allowed: true, matchedPath };
|
||
}
|
||
|
||
/**
|
||
* 简化版单权限检查(避免循环依赖 hasAllPermissionsInBitmap)
|
||
*
|
||
* 直接调用 hasAllPermissionsInBitmap 检查单个权限点
|
||
*/
|
||
function hasPermissionInBitmapSimple(
|
||
bitmap: string,
|
||
permission: string,
|
||
): boolean {
|
||
return hasAllPermissionsInBitmap(bitmap, [permission]);
|
||
}
|
||
|
||
/**
|
||
* 批量检查用户是否拥有所有指定路由的访问权限
|
||
*
|
||
* 用于侧边栏导航项过滤:一次性检查多个路由,避免重复调用。
|
||
*
|
||
* @param paths 路径列表
|
||
* @param userBitmap 用户权限位图
|
||
* @param userRole 用户角色
|
||
* @returns 路径 → 是否允许 的映射
|
||
*/
|
||
export function batchCheckRoutePermission(
|
||
paths: readonly string[],
|
||
userBitmap: string,
|
||
userRole: Role,
|
||
): Record<string, boolean> {
|
||
const result: Record<string, boolean> = {};
|
||
for (const path of paths) {
|
||
result[path] = checkRoutePermission(path, userBitmap, userRole).allowed;
|
||
}
|
||
return result;
|
||
}
|