Files
Edu/apps/portal-shell/docs/needtodo/management-grade-NeedTodo.md
SpecialX 062d9e9582 feat(portal-shell): 管理域 §9.4 B5 全量迁移(24 + 21 补充批次共 44 页 + 42 features)
按 ARCHITECTURE.md §9.4 规划口径 + admin-NeedTodo.md §四补充批次完成管理域全量页面迁移:

【§9.4 规划 24 页(B5)】
- users(2) + roles(1) + permissions(1) + audit-logs(4) + invitation-codes(1)
- school(6: redirect/schools/classes/departments/academic-year/grades)
- announcements(1) + files(1) + ai-settings(1) + system(1) + viewports(1)
- students(1) + teachers(1) + organization(1) + plugins(1, config-service)
- 仪表盘已存在(/shell/admin/page.tsx)

【§四补充批次 21 页】
- course-plans(4) + elective(4) + questions(1) + lesson-plans(2) + error-book(1)
- scheduling(3: auto/changes/rules) + attendance(1) + curriculum-map(1)
- announcements 详情/编辑(2) + roles/[id] 详情(1) + users/import(1)

【实现要点】
- 全部使用 ListPageShell + loading/error/empty 三态规范(§11.3 DoD)
- 走 lib/api hooks;未就绪契约走 MSW + @contract-pending 注释(§11.4)
- 文案走 useTranslations(zh-CN + en 两份同步更新)
- 42 个 features/<domain>/transformations.ts 纯函数 + 配套 vitest 单测
- catch 块统一 notify.error;无空 catch;lint:tokens 通过
- 路由全部登记到 route-permissions.ts(39 EXACT + 8 PREFIX)

【验收】
- tsc --noEmit: 0 errors
- ESLint src: 0 errors (4 generated-files warnings, pre-existing)
- lint:tokens: 0 errors
- vitest: 1639/1639 passed (含 23 admin 测试文件 671 用例)
- check:routes: PASS (143 routes, 4 ghost entries pre-existing)
- check:pages: PASS (146 pages)
- check:codegen: PASS
- arch:scan: 24 modules, 8262 symbols

关联:ARCHITECTURE.md §9.4 / §10 P5 / §11.3 DoD / §11.6
2026-07-24 23:07:20 +08:00

22 KiB
Raw Blame History

年级组域Management/Grade待完成功能分析

参考项目:e:\desktop\CICD\src\app\(dashboard)\management\grade\5 个 page.tsx 当前项目:e:\Desktop\Edu\apps\portal-shell\无对应页面,全部缺失 规划依据:apps\portal-shell\ARCHITECTURE.md §9.5 缺口新增5 页B2/B5 末 N 分析日期2026-07-24 分析方式:逐页面读取 CICD page.tsx 源码 + 检查 portal-shell 对应路由是否存在


〇、方法论与对比基线

  1. CICD 单体Next.js App Router + Server Actions + Drizzle作为功能基线,反映"老版单体已实现"的年级组功能完整态。
  2. portal-shell 作为目标态,遵循 ARCH §9.5 的"缺口新增"规划B2/B5 末N=新建)。
  3. CICD 的 5 个页面位于 (dashboard)/management/grade/ 顶级路由组下,不属于 /teacher 也不属于 /admin,是独立的"年级组管理"维度,按 GRADE_MANAGE / GRADE_RECORD_READ / ADAPTIVE_PRACTICE_READ 三类权限放行。
  4. CICD 的 navigation.ts 显示该菜单组同时出现在 teachergrade_head 两个角色的导航中permission 均为 Permissions.GRADE_MANAGE),即"年级组长"既可以是带 grade_head 角色的独立账号,也可以是普通教师被授予 GRADE_MANAGE 权限。
  5. portal-shell 现有目录结构仅含 /shell/{teacher|admin|student|parent|dev|forbidden}/*未规划 /shell/management/* 独立段,且 route-permissions.ts 中无任何 management/grade 条目。
  6. ARCH §9.5 表格仅以"缺口新增"一行汇总 10 页5 grade + 5 register/onboarding/privacy/terms未细化每页的目标路由与契约本文档补充细化。

一、页面完成度总览

状态 数量 说明
缺失 5 5 个页面在 portal-shell 中均无对应实现
🟡 部分 0
完成 0

portal-shell 路径检查:apps/portal-shell/src/app/shell/**/management/**apps/portal-shell/src/app/shell/**/grade/** Glob 均返回 "No file found"。route-permissions.ts 亦无 management / grade 关键字命中。


二、缺失页面清单

2.1 年级组入口重定向page.tsx

  • CICD 路径e:\desktop\CICD\src\app\(dashboard)\management\grade\page.tsx
  • 建议目标路由/shell/teacher/management/grade(见 §三角色归属分析)
  • 功能描述:空页面入口,仅做权限校验与重定向:
    • await requirePermission(Permissions.GRADE_MANAGE) — 校验年级管理权限
    • redirect("/management/grade/classes") — 重定向到班级列表页
  • 技术栈
    • Next.js redirect (from next/navigation)
    • requirePermission 共享鉴权工具(@/shared/lib/auth-guard
    • Permissions.GRADE_MANAGE 权限位(grade:manage
    • export const dynamic = "force-dynamic" — 强制动态渲染(避免静态缓存鉴权信息)
  • 权限GRADE_MANAGEgrade:manage)— 年级组长专属权限
  • ARCHITECTURE.md 契约§9.5 缺口新增,无 GraphQL schema
  • 批次B2/B5 末N

2.2 年级组仪表盘dashboard

  • CICD 路径e:\desktop\CICD\src\app\(dashboard)\management\grade\dashboard\page.tsx
  • 建议目标路由/shell/teacher/management/grade/dashboard
  • 功能描述:年级组长仪表盘,包含 4 个 Tab 切换的概览面板:
    • 年级筛选器FilterBar + ChipNav拉取 getGradesForStaff(teacherId) 得到该教师可见年级列表URL ?gradeId=xxx 即时切换,无整页刷新
    • Tab distribution成绩分布:调用 getGradeDistributionByGradeId({ gradeId, scope: ctx.dataScope }),渲染 GradeDistributionPanel
    • Tab homework作业洞察:调用 getGradeHomeworkInsights({ gradeId, limit: 50 }),渲染 GradeHomeworkPanel
    • Tab exams考试列表:调用 getExamsByGradeId({ gradeId, scope: ctx.dataScope }),渲染 GradeExamsPanel
    • Tab progress课程计划进度:调用 getGradeCoursePlanProgress({ gradeId }),渲染 GradeProgressPanel
    • 懒加载策略:仅渲染当前激活 Tab 的数据(按 tab 参数选择性 fetch其他 Tab 不预取
    • 空状态:无可见年级 / 未选年级 / 数据为 null 三种 EmptyState 兜底
    • 错误边界SectionErrorBoundary 包裹面板区域namespace="school"
  • 技术栈
    • Server Componentforce-dynamic+ generateMetadata 动态标题i18n school.grades.gradeDashboard.*
    • next-intl/servergetTranslations
    • getParam / SearchParams 工具(@/shared/lib/search-params
    • 跨模块数据访问:classes / school / grades / exams / course-plans 5 个模块的 data-access
    • 共享 UIFilterBarChipNavEmptyStateSectionErrorBoundary
    • 业务组件:modules/school/components/grade-dashboard/grade-{distribution,homework,exams,progress}-panel
    • DataScope 二次过滤:ctx.dataScope 传入查询,按教师可见范围过滤
  • 权限GRADE_RECORD_READgrade_record:read)— 比入口权限宽松,允许只读查看成绩的教师访问
  • ARCHITECTURE.md 契约5 个 data-access 全部走 Drizzle 直查 DB未走 GraphQL schema
  • 批次B2/B5 末N

2.3 年级组班级列表classes

  • CICD 路径e:\desktop\CICD\src\app\(dashboard)\management\grade\classes\page.tsx
  • 建议目标路由/shell/teacher/management/grade/classes
  • 功能描述:年级组长管辖的班级管理页,并行拉取三组数据:
    • getGradeManagedClasses(userId) — 该用户作为年级组长管辖的班级列表
    • getTeacherOptions() — 教师下拉选项(用于班主任分配)
    • getManagedGrades(userId) — 该用户管辖的年级列表
    • 渲染 GradeClassesClient 客户端组件,承载班级 CRUD建/删/改)交互
  • 技术栈
    • Server Componentforce-dynamic+ generateMetadatai18n school.classManagement.grade.*
    • Promise.all 并行数据获取
    • 客户端组件 GradeClassesClientmodules/classes/components/grade-classes-view.tsx
    • 客户端组件依赖:
      • createGradeClassAction / deleteGradeClassAction / updateGradeClassActionServer Actionsmodules/classes/actions.ts
      • useClassData Hookmodules/classes/hooks/use-class-data.ts
      • ClassDeleteDialog / ClassFormDialog / ClassListTable / ClassListToolbar4 个子组件)
    • ctx.userId 用于 owner 维度过滤
  • 权限GRADE_MANAGEgrade:manage)— 年级组长写权限
  • ARCHITECTURE.md 契约5 个 data-access 全走 Drizzle无 GraphQL
  • 批次B2/B5 末N
  • 迁移注意portal-shell 现有 /shell/teacher/classes 是普通教师班级列表(教师授课班级),与年级组长管辖的班级列表语义不同(前者按 teacherId 关联 class_teachers,后者按年级组管辖范围 grade_head)。不可合并

2.4 年级组作业洞察insights

  • CICD 路径e:\desktop\CICD\src\app\(dashboard)\management\grade\insights\page.tsx
  • 建议目标路由/shell/teacher/management/grade/insights
  • 功能描述:年级组作业洞察页,单年级作业统计与班级排名:
    • 年级筛选器FilterBar + ChipNavgetGradesForStaff(teacherId) + ?gradeId=xxx 切换
    • 4 个统计卡StatCard
      • 班级数(insights.classCount
      • 学生数(studentCounts.total / active / inactive
      • 整体平均分(overallScores.avg
      • 最近作业平均分(insights.latest.scoreStats.avg + 作业标题)
    • 作业时间线表Table:每个作业一行,列含:作业名 / 状态 / 创建时间 / 应交 / 已交 / 已批 / 平均分 / 中位数分
    • 班级排名表Table:每个班一行,列含:班级名(含班主任)/ 学生数 / 最近作业平均 / 上次平均 / 涨跌 / 整体平均
    • 三段空状态:无年级 / 未选 / 数据为空 / 无 insights 各自 EmptyState
    • 移动端适配overflow-x-auto 包裹两个 Tablev4-P1-11
  • 技术栈
    • Server Componentforce-dynamic+ generateMetadatai18n school.grades.gradeInsights.*
    • formatDate / formatNumber 工具(@/shared/lib/utils
    • 共享 UIFilterBar / ChipNav / EmptyState / StatCard / Card / Badge / Table 全套
    • 数据访问:getGradeHomeworkInsights({ gradeId, limit: 50 })modules/classes/data-accesslimit=50
    • 辅助查询:getTeacherIdForMutations + getGradesForStaff
    • 图标:BarChart3lucide-react
  • 权限GRADE_RECORD_READgrade_record:read)— 只读权限
  • ARCHITECTURE.md 契约Drizzle 直查,无 GraphQL
  • 批次B2/B5 末N

2.5 年级组练习概览practice

  • CICD 路径e:\desktop\CICD\src\app\(dashboard)\management\grade\practice\page.tsx
  • 建议目标路由/shell/teacher/management/grade/practice
  • 功能描述:年级组自适应练习概览页,三段式数据可视化:
    • 年级筛选器FilterBar + ChipNavgetGradesForStaff(teacherId) + ?gradeId=xxx 切换
    • 5 个统计卡StatsGrid
      • 班级总数(overview.totalClassesBookOpen 图标)
      • 练习会话总数(overview.totalSessionsActivity 图标)
      • 答题总数(overview.totalQuestionsAnsweredTarget 图标)
      • 平均正确率(overview.averageAccuracy * 100%CheckCircle2 图标)
      • 参与率(overview.participationRate * 100%Users 图标)
    • 班级练习对比表ClassPracticeComparisonTablemodules/adaptive-practice/components)渲染 classComparison
    • 练习类型分布图PracticeTypeBreakdownChart(同模块)渲染 typeBreakdown
    • 懒加载:仅 selected 存在时执行 Promise.all 并行 3 查询
    • 三段空状态:无年级 / 未选 / 数据为 null 各自 EmptyState
  • 技术栈
    • Server Componentforce-dynamic+ generateMetadatai18n practice.grade.*
    • 双 i18n namespacepractice(主)+ school(年级筛选器复用 grades.gradeInsights.selectGrade
    • 数据访问:modules/adaptive-practice/data-access-analytics 的 3 个查询:
      • getGradePracticeOverview(gradeId)
      • getGradeClassPracticeComparison(gradeId)
      • getPracticeTypeBreakdown(studentIds) — 需要先调 getUserIdsByGradeId(gradeId) 取学生 ID 列表
    • 共享 UIStatsGrid5 列)、FilterBarChipNavEmptyState
    • 业务组件:ClassPracticeComparisonTable + PracticeTypeBreakdownChart
    • 6 个 lucide-react 图标:Activity / BarChart3 / BookOpen / CheckCircle2 / Target / Users
  • 权限ADAPTIVE_PRACTICE_READadaptive_practice:read)— 自适应练习读权限
  • ARCHITECTURE.md 契约Drizzle 直查,无 GraphQL
  • 批次B2/B5 末N
  • 迁移注意CICD 的 navigation.tsteacher.gradeManagement 菜单组只列了 classes/dashboard/insights 3 项,未列 practice 子菜单。但 page.tsx 实际存在,应作为隐藏/直接 URL 访问入口保留。

三、角色归属分析

3.1 CICD 中的权限校验

页面 requirePermission 权限语义
page.tsx (入口) Permissions.GRADE_MANAGE 年级组管理(写)
classes Permissions.GRADE_MANAGE 年级组管理(写)
dashboard Permissions.GRADE_RECORD_READ 成绩记录读
insights Permissions.GRADE_RECORD_READ 成绩记录读
practice Permissions.ADAPTIVE_PRACTICE_READ 自适应练习读

5 个页面共用 3 类权限,无 admin:* 权限校验。年级组页面是"年级组长"维度,不属于"管理员"维度

3.2 CICD 导航归属

e:\desktop\CICD\src\modules\layout\config\navigation.ts 中:

  • teacher 角色菜单组:包含 teacher.gradeManagementpermission=GRADE_MANAGE),子项含 classes/dashboard/insights
  • grade_head 角色菜单组:同样包含 teacher.gradeManagement,子项相同
  • 两个角色都把年级组放在与"teacher.grades"(成绩录入)平级的位置,不在 admin.* 之下

3.3 portal-shell 角色与目录约定

  • apps\portal-shell\ARCHITECTURE.md §9.1 教师域规划到 /shell/teacher/*§9.4 管理域规划到 /shell/admin/*
  • portal-shell 现有目录仅 /shell/{teacher|admin|student|parent|dev|forbidden}/*,无独立 grade-head
  • src/middleware.ts 默认合成身份为 role: "teacher",未对 grade_head 角色做特殊路由分发
  • src/shared/lib/route-permissions.ts 中无 management / grade 路由条目(需新增)

3.4 建议归属

推荐方案:归入教师域子路径 /shell/teacher/management/grade/*

理由:

  1. CICD 在 teachergrade_head 两角色菜单中都把"年级组管理"挂在教师主菜单下,语义上是"教师的扩展职责"
  2. portal-shell 已有 /shell/teacher/* 体系复用其布局壳、middleware 身份合成、route-permissions 表
  3. 避免新建 /shell/grade-head/* 顶层段(会带来 sidebar 配置、middleware 分发、layout 重复实现等成本)
  4. 路由权限按 GRADE_MANAGE / GRADE_RECORD_READ / ADAPTIVE_PRACTICE_READ 精确声明,与角色解耦——拥有这些权限位的教师(无论 role 字段是 teacher 还是 grade_head)都可访问

备选方案:若后续 portal-shell 引入 grade_head 独立角色段,可平滑迁移到 /shell/grade-head/*page.tsx 实现完全可复用,仅需调整路由前缀)。

目标路由建议

CICD 源路径 建议目标路由 权限
/management/grade /shell/teacher/management/grade GRADE_MANAGE
/management/grade/classes /shell/teacher/management/grade/classes GRADE_MANAGE
/management/grade/dashboard /shell/teacher/management/grade/dashboard GRADE_RECORD_READ
/management/grade/insights /shell/teacher/management/grade/insights GRADE_RECORD_READ
/management/grade/practice /shell/teacher/management/grade/practice ADAPTIVE_PRACTICE_READ

四、迁移注意事项

4.1 契约缺口5 页全部

5 个页面的数据访问在 CICD 中全部走 Drizzle ORM 直查 DB,未通过 GraphQL schema。迁移到 portal-shell 时按 ARCH §9.5 "节奏原则"处理:

  • MSW 先行:用 Mock Service Worker 提供假数据,先把 5 个页面的 UI 跑起来
  • 契约工单:向后端开 5 类查询的 GraphQL schema 工单
    • gradeManagedClasses(userId) + managedGrades(userId) + teacherOptions()
    • gradesForStaff(teacherId)(年级列表)
    • gradeDistributionByGradeId(gradeId, scope) + gradeHomeworkInsights(gradeId, limit) + examsByGradeId(gradeId, scope) + gradeCoursePlanProgress(gradeId)
    • gradePracticeOverview(gradeId) + gradeClassPracticeComparison(gradeId) + practiceTypeBreakdown(studentIds) + userIdsByGradeId(gradeId)
  • 切换策略:契约就绪后,只改 hook 的 fetcher 指向(从 MSW 切到 Apollo Client页面不动

4.2 跨模块依赖5 个领域模块)

年级组页面是跨模块聚合页,依赖 5 个领域模块的 data-access

依赖模块 用到的查询
classes getGradeManagedClasses / getManagedGrades / getTeacherIdForMutations / getGradeHomeworkInsights / getTeacherOptions
school getGradesForStaff
grades getGradeDistributionByGradeId
exams getExamsByGradeId
course-plans getGradeCoursePlanProgress
adaptive-practice getGradePracticeOverview / getGradeClassPracticeComparison / getPracticeTypeBreakdown
users getUserIdsByGradeId

迁移时需确保 portal-shell 对应的 7 个领域模块契约齐备或 MSW 兜底覆盖。

4.3 DataScope 二次过滤

CICD 的 requirePermission 返回 ctx.dataScope,传入 getGradeDistributionByGradeIdgetExamsByGradeId 进行数据范围二次过滤(按教师可见的年级/班级范围裁剪结果)。

portal-shell 中需在 hook 层保留 dataScope 参数,避免越权读取非管辖年级数据。

4.4 i18n 命名空间

5 个页面用到 3 个 i18n namespace

  • schooldashboard / classes / insights 主命名空间)
  • practicepractice 页主命名空间)
  • school.grades.gradeInsights.selectGradepractice 页复用)

迁移时需将 CICD 的 messages/zh-CN/school.json / practice.json 中相关键同步到 portal-shell 的 i18n 资源。

4.5 业务组件迁移

以下业务组件需要从 CICD 迁移到 portal-shell按页面分组

页面 组件 来源模块
dashboard GradeDistributionPanel / GradeHomeworkPanel / GradeExamsPanel / GradeProgressPanel modules/school/components/grade-dashboard
classes GradeClassesClient + 4 个子组件(ClassDeleteDialog / ClassFormDialog / ClassListTable / ClassListToolbar modules/classes/components
practice ClassPracticeComparisonTable / PracticeTypeBreakdownChart modules/adaptive-practice/components
insights 仅用共享 UIStatCard / Card / Table无业务组件

4.6 route-permissions.ts 同步

迁移完成后需在 apps/portal-shell/src/shared/lib/route-permissions.ts 新增 5 条路由权限声明:

// 示例(实际实现时按真实 schema 编写)
"/shell/teacher/management/grade": { permission: "grade:manage", ... },
"/shell/teacher/management/grade/classes": { permission: "grade:manage", ... },
"/shell/teacher/management/grade/dashboard": { permission: "grade_record:read", ... },
"/shell/teacher/management/grade/insights": { permission: "grade_record:read", ... },
"/shell/teacher/management/grade/practice": { permission: "adaptive_practice:read", ... },

4.7 ARCHITECTURE.md 与 arch.db 同步

e:\Desktop\Edu\.trae\rules\project_rules.md §1.3「改码必同步图」要求:

  • 完成 5 页迁移后运行 pnpm run arch:scan 更新 arch.db
  • apps/portal-shell/ARCHITECTURE.md §9.5 表格中将"缺口新增"行的"契约已就绪"列从 0 更新为实际就绪数(按契约工单进度)
  • docs/troubleshooting/known-issues.md 对应模块分区追加"场景→技术"映射(索引式一行,不标 AI 身份)

4.8 批次与顺序

按 ARCH §9.5 规划5 页归入 B2/B5 末 批次:

  • B2 末教师域二期与教师主功能一起补齐classes / dashboard / insights — 因 CICD navigation 中挂在 teacher.gradeManagement 菜单下
  • B5 末管理域与管理员功能一起补齐practice — 因 CICD navigation 未列 practice 子项,按独立入口处理

推荐实施顺序(依赖从轻到重):

  1. page.tsx入口重定向最简单0 数据访问)
  2. insights(仅查 1 个 data-access + 共享 UI
  3. classes3 个 data-access + 1 客户端组件 + 4 子组件)
  4. dashboard4 个 data-access + 4 业务面板组件)
  5. practice3 个 data-access + 2 业务组件 + 1 辅助查询)

五、附录CICD 文件清单

e:\desktop\CICD\src\app\(dashboard)\management\grade\
├─ page.tsx                       # 入口重定向 → /classes
├─ error.tsx                      # 错误边界
├─ loading.tsx                   # 加载骨架
├─ classes\
│  ├─ page.tsx                    # 年级组班级列表
│  ├─ error.tsx
│  └─ loading.tsx
├─ dashboard\
│  ├─ page.tsx                    # 年级组仪表盘4 Tab
│  └─ loading.tsx
├─ insights\
│  ├─ page.tsx                    # 年级组作业洞察
│  ├─ error.tsx
│  └─ loading.tsx
└─ practice\
   ├─ page.tsx                    # 年级组练习概览
   ├─ error.tsx
   └─ loading.tsx

portal-shell 已有统一的 loading.tsx / error.tsx 模板(见 apps/portal-shell/src/app/shell/teacher/* 各目录),迁移时复用模板即可,无需单独迁 error/loading。