Files
Edu/apps/portal-shell/src/shared/lib/navigation.ts
SpecialX f92fdf8efe feat(portal-shell): wire AppFrame + navigation + permission filter (P1-1)
实现 ARCHITECTURE.md §10 P1-1:
- 新增 src/shared/lib/navigation.ts:27 项静态导航注册表,
  按 teacher/student/parent/admin 四角色分区,group 字段用于角色过滤
- 新增 src/app/shell/layout.tsx:RSC AppFrame,从 headers() 读取
  middleware 注入的身份头,batchCheckRoutePermission 按位图二次过滤
- 新增 src/shared/components/layout/user-menu.tsx:顶部用户菜单,
  显示 userId + role,登出 POST /api/auth/logout
- 新增 src/shared/lib/__tests__/navigation.test.ts:P1-1 验收单测
  (7 用例:href 登记一致性 + 4 角色隔离 + 权限检查)
- 修改 src/shared/lib/route-permissions.ts:补全 7 个列表页根路由
  的 EXACT 登记(/shell/admin/announcements、/shell/admin/classes、
  /shell/teacher/exams 等),与 PREFIX 表互补避免 catch-all 拒绝

验收:
- vitest run navigation → 7/7 passed
- tsc --noEmit 通过;eslint(5 文件)通过

回填 ARCHITECTURE.md §10 P1-1 状态为 ,附验收证据。

关联:ARCHITECTURE.md §7.2 AppFrame / §10 P1-1
2026-07-22 12:17:16 +08:00

301 lines
7.7 KiB
TypeScript
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.
/**
* 静态导航注册表ARCHITECTURE.md §7.2 AppFrame / §10 P1-1
*
* 导航项按角色分组group 字段),渲染前由 layout.tsx 调
* batchCheckRoutePermission 按用户位图二次过滤。
*
* 导航项 href 与 route-permissions.ts 四张表对齐:
* - 仪表盘路由 → DASHBOARD_ROUTE_PERMISSIONS
* - 精确路由 → EXACT_ROUTE_PERMISSIONS
* - 前缀路由(如 /shell/teacher/exams→ PREFIX_ROUTE_PERMISSIONS
* (前缀以 "/" 结尾layout.tsx 检查时补 "/" 匹配)
*
* 关联portal-shell ARCHITECTURE.md §7.2、§10 P1-1
*/
import type { LucideIcon } from "lucide-react";
import {
Activity,
AlertCircle,
Bot,
BookMarked,
BookOpen,
Building2,
CalendarCheck,
ClipboardList,
FileCheck,
FileText,
GraduationCap,
HelpCircle,
LayoutDashboard,
ListChecks,
Lock,
Megaphone,
Puzzle,
Route,
School,
ScrollText,
Shield,
Ticket,
Users,
} from "lucide-react";
import type { Role } from "@edu/shared-ts/contracts";
/** 导航项定义 */
export interface NavigationItem {
/** 路由路径(与 route-permissions 表对齐) */
href: string;
/** 显示名称P1-4 i18n 后替换为 i18n key */
label: string;
/** 图标lucide-react */
icon: LucideIcon;
/** 所属角色区(用于分组渲染与角色过滤) */
group: Role;
/** 所需角色(元数据,与 route-permissions 对齐) */
requiredRoles?: Role[];
/** 所需权限点(元数据,与 route-permissions 对齐) */
requiredPermissions?: string[];
}
/**
* 全量导航项(按角色分区,扁平数组)
*
* group 字段区分角色区teacher / student / parent / admin
*/
export const NAVIGATION_ITEMS: NavigationItem[] = [
// ── teacher 组 ──────────────────────────────────────────
{
href: "/shell/teacher",
label: "仪表盘",
icon: LayoutDashboard,
group: "teacher",
requiredRoles: ["teacher"],
requiredPermissions: ["DASHBOARD_TEACHER_READ"],
},
{
href: "/shell/teacher/exams",
label: "考试管理",
icon: FileText,
group: "teacher",
requiredRoles: ["teacher", "admin"],
requiredPermissions: ["EXAM_READ"],
},
{
href: "/shell/teacher/homework",
label: "作业管理",
icon: BookOpen,
group: "teacher",
requiredRoles: ["teacher", "admin"],
requiredPermissions: ["HOMEWORK_READ"],
},
{
href: "/shell/teacher/grades",
label: "成绩录入",
icon: GraduationCap,
group: "teacher",
requiredRoles: ["teacher", "admin"],
requiredPermissions: ["GRADE_RECORD_READ"],
},
{
href: "/shell/teacher/attendance",
label: "考勤",
icon: CalendarCheck,
group: "teacher",
requiredRoles: ["teacher", "admin"],
requiredPermissions: ["ATTENDANCE_READ"],
},
{
href: "/shell/teacher/diagnostics",
label: "学情诊断",
icon: Activity,
group: "teacher",
requiredRoles: ["teacher", "admin"],
requiredPermissions: ["DIAGNOSTIC_READ"],
},
{
href: "/shell/teacher/lesson-plans",
label: "备课",
icon: ClipboardList,
group: "teacher",
requiredRoles: ["teacher"],
requiredPermissions: ["LESSON_PLAN_READ"],
},
{
href: "/shell/teacher/question-bank",
label: "题库",
icon: HelpCircle,
group: "teacher",
requiredRoles: ["teacher"],
requiredPermissions: ["QUESTION_READ"],
},
{
href: "/shell/teacher/textbooks",
label: "教材",
icon: BookMarked,
group: "teacher",
requiredRoles: ["teacher", "admin"],
requiredPermissions: ["TEXTBOOK_READ"],
},
// ── student 组 ──────────────────────────────────────────
{
href: "/shell/student",
label: "仪表盘",
icon: LayoutDashboard,
group: "student",
requiredRoles: ["student"],
requiredPermissions: ["DASHBOARD_STUDENT_READ"],
},
{
href: "/shell/student/error-book",
label: "错题本",
icon: AlertCircle,
group: "student",
requiredRoles: ["student"],
requiredPermissions: ["ERROR_BOOK_READ"],
},
{
href: "/shell/student/learning-path",
label: "学习路径",
icon: Route,
group: "student",
requiredRoles: ["student"],
requiredPermissions: ["LEARNING_PATH_READ"],
},
{
href: "/shell/student/electives",
label: "选课",
icon: ListChecks,
group: "student",
requiredRoles: ["student"],
requiredPermissions: ["ELECTIVE_READ"],
},
{
href: "/shell/student/ai-tutor",
label: "AI 辅导",
icon: Bot,
group: "student",
requiredRoles: ["student"],
requiredPermissions: ["AI_TUTOR_USE"],
},
// ── parent 组 ───────────────────────────────────────────
{
href: "/shell/parent",
label: "仪表盘",
icon: LayoutDashboard,
group: "parent",
requiredRoles: ["parent"],
requiredPermissions: ["DASHBOARD_PARENT_READ"],
},
{
href: "/shell/parent/children",
label: "孩子信息",
icon: Users,
group: "parent",
requiredRoles: ["parent"],
requiredPermissions: ["GRADE_READ_CHILD"],
},
{
href: "/shell/parent/leave-approval",
label: "请假审批",
icon: FileCheck,
group: "parent",
requiredRoles: ["parent"],
requiredPermissions: ["LEAVE_APPROVAL_MANAGE"],
},
// ── admin 组 ────────────────────────────────────────────
{
href: "/shell/admin",
label: "仪表盘",
icon: LayoutDashboard,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["DASHBOARD_ADMIN_READ"],
},
{
href: "/shell/admin/users",
label: "用户管理",
icon: Users,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["USER_MANAGE"],
},
{
href: "/shell/admin/roles",
label: "角色管理",
icon: Shield,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["ROLE_MANAGE"],
},
{
href: "/shell/admin/permissions",
label: "权限管理",
icon: Lock,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["PERMISSION_MANAGE"],
},
{
href: "/shell/admin/audit-logs",
label: "审计日志",
icon: ScrollText,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["AUDIT_LOG_READ"],
},
{
href: "/shell/admin/school",
label: "学校管理",
icon: Building2,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["SCHOOL_MANAGE"],
},
{
href: "/shell/admin/plugins",
label: "插件管理",
icon: Puzzle,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["PLUGIN_REGISTRY_MANAGE"],
},
{
href: "/shell/admin/invitation-codes",
label: "邀请码",
icon: Ticket,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["INVITATION_CODE_MANAGE"],
},
{
href: "/shell/admin/announcements",
label: "公告管理",
icon: Megaphone,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["ANNOUNCEMENT_MANAGE"],
},
{
href: "/shell/admin/classes",
label: "班级管理",
icon: School,
group: "admin",
requiredRoles: ["admin"],
requiredPermissions: ["CLASS_READ"],
},
];
/**
* 返回指定角色可见的导航项
*
* 按 group 字段过滤,实现"4 角色各见各菜单"。
* 调用方layout.tsx再用 batchCheckRoutePermission 按权限位图二次过滤。
*/
export function getNavigationItemsForRole(role: Role): NavigationItem[] {
return NAVIGATION_ITEMS.filter((item) => item.group === role);
}