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
This commit is contained in:
300
apps/portal-shell/src/shared/lib/navigation.ts
Normal file
300
apps/portal-shell/src/shared/lib/navigation.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* 静态导航注册表(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);
|
||||
}
|
||||
Reference in New Issue
Block a user