按 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
22 KiB
年级组域(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 对应路由是否存在
〇、方法论与对比基线
- CICD 单体(Next.js App Router + Server Actions + Drizzle)作为功能基线,反映"老版单体已实现"的年级组功能完整态。
- portal-shell 作为目标态,遵循 ARCH §9.5 的"缺口新增"规划(B2/B5 末,N=新建)。
- CICD 的 5 个页面位于
(dashboard)/management/grade/顶级路由组下,不属于 /teacher 也不属于 /admin,是独立的"年级组管理"维度,按GRADE_MANAGE/GRADE_RECORD_READ/ADAPTIVE_PRACTICE_READ三类权限放行。 - CICD 的
navigation.ts显示该菜单组同时出现在teacher与grade_head两个角色的导航中(permission 均为Permissions.GRADE_MANAGE),即"年级组长"既可以是带grade_head角色的独立账号,也可以是普通教师被授予GRADE_MANAGE权限。 - portal-shell 现有目录结构仅含
/shell/{teacher|admin|student|parent|dev|forbidden}/*,未规划/shell/management/*独立段,且route-permissions.ts中无任何management/grade条目。 - 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(fromnext/navigation) requirePermission共享鉴权工具(@/shared/lib/auth-guard)Permissions.GRADE_MANAGE权限位(grade:manage)export const dynamic = "force-dynamic"— 强制动态渲染(避免静态缓存鉴权信息)
- Next.js
- 权限:
GRADE_MANAGE(grade: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")
- 年级筛选器(FilterBar + ChipNav):拉取
- 技术栈:
- Server Component(
force-dynamic)+generateMetadata动态标题(i18nschool.grades.gradeDashboard.*) next-intl/server的getTranslationsgetParam/SearchParams工具(@/shared/lib/search-params)- 跨模块数据访问:
classes/school/grades/exams/course-plans5 个模块的data-access - 共享 UI:
FilterBar、ChipNav、EmptyState、SectionErrorBoundary - 业务组件:
modules/school/components/grade-dashboard/grade-{distribution,homework,exams,progress}-panel - DataScope 二次过滤:
ctx.dataScope传入查询,按教师可见范围过滤
- Server Component(
- 权限:
GRADE_RECORD_READ(grade_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 Component(
force-dynamic)+generateMetadata(i18nschool.classManagement.grade.*) Promise.all并行数据获取- 客户端组件
GradeClassesClient(modules/classes/components/grade-classes-view.tsx) - 客户端组件依赖:
createGradeClassAction/deleteGradeClassAction/updateGradeClassAction(Server Actions,modules/classes/actions.ts)useClassDataHook(modules/classes/hooks/use-class-data.ts)ClassDeleteDialog/ClassFormDialog/ClassListTable/ClassListToolbar(4 个子组件)
ctx.userId用于 owner 维度过滤
- Server Component(
- 权限:
GRADE_MANAGE(grade: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 + ChipNav):
getGradesForStaff(teacherId)+?gradeId=xxx切换 - 4 个统计卡(StatCard):
- 班级数(
insights.classCount) - 学生数(
studentCounts.total/active/inactive) - 整体平均分(
overallScores.avg) - 最近作业平均分(
insights.latest.scoreStats.avg+ 作业标题)
- 班级数(
- 作业时间线表(Table):每个作业一行,列含:作业名 / 状态 / 创建时间 / 应交 / 已交 / 已批 / 平均分 / 中位数分
- 班级排名表(Table):每个班一行,列含:班级名(含班主任)/ 学生数 / 最近作业平均 / 上次平均 / 涨跌 / 整体平均
- 三段空状态:无年级 / 未选 / 数据为空 / 无 insights 各自 EmptyState
- 移动端适配:
overflow-x-auto包裹两个 Table(v4-P1-11)
- 年级筛选器(FilterBar + ChipNav):
- 技术栈:
- Server Component(
force-dynamic)+generateMetadata(i18nschool.grades.gradeInsights.*) formatDate/formatNumber工具(@/shared/lib/utils)- 共享 UI:
FilterBar/ChipNav/EmptyState/StatCard/Card/Badge/Table全套 - 数据访问:
getGradeHomeworkInsights({ gradeId, limit: 50 })(modules/classes/data-access,limit=50) - 辅助查询:
getTeacherIdForMutations+getGradesForStaff - 图标:
BarChart3(lucide-react)
- Server Component(
- 权限:
GRADE_RECORD_READ(grade_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 + ChipNav):
getGradesForStaff(teacherId)+?gradeId=xxx切换 - 5 个统计卡(StatsGrid):
- 班级总数(
overview.totalClasses,BookOpen 图标) - 练习会话总数(
overview.totalSessions,Activity 图标) - 答题总数(
overview.totalQuestionsAnswered,Target 图标) - 平均正确率(
overview.averageAccuracy * 100%,CheckCircle2 图标) - 参与率(
overview.participationRate * 100%,Users 图标)
- 班级总数(
- 班级练习对比表:
ClassPracticeComparisonTable(modules/adaptive-practice/components)渲染classComparison - 练习类型分布图:
PracticeTypeBreakdownChart(同模块)渲染typeBreakdown - 懒加载:仅
selected存在时执行Promise.all并行 3 查询 - 三段空状态:无年级 / 未选 / 数据为 null 各自 EmptyState
- 年级筛选器(FilterBar + ChipNav):
- 技术栈:
- Server Component(
force-dynamic)+generateMetadata(i18npractice.grade.*) - 双 i18n namespace:
practice(主)+school(年级筛选器复用grades.gradeInsights.selectGrade) - 数据访问:
modules/adaptive-practice/data-access-analytics的 3 个查询:getGradePracticeOverview(gradeId)getGradeClassPracticeComparison(gradeId)getPracticeTypeBreakdown(studentIds)— 需要先调getUserIdsByGradeId(gradeId)取学生 ID 列表
- 共享 UI:
StatsGrid(5 列)、FilterBar、ChipNav、EmptyState - 业务组件:
ClassPracticeComparisonTable+PracticeTypeBreakdownChart - 6 个 lucide-react 图标:
Activity/BarChart3/BookOpen/CheckCircle2/Target/Users
- Server Component(
- 权限:
ADAPTIVE_PRACTICE_READ(adaptive_practice:read)— 自适应练习读权限 - ARCHITECTURE.md 契约:❌(Drizzle 直查,无 GraphQL)
- 批次:B2/B5 末(N)
- 迁移注意:CICD 的
navigation.ts中teacher.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.gradeManagement(permission=GRADE_MANAGE),子项含 classes/dashboard/insightsgrade_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/*
理由:
- CICD 在
teacher与grade_head两角色菜单中都把"年级组管理"挂在教师主菜单下,语义上是"教师的扩展职责" - portal-shell 已有
/shell/teacher/*体系,复用其布局壳、middleware 身份合成、route-permissions 表 - 避免新建
/shell/grade-head/*顶层段(会带来 sidebar 配置、middleware 分发、layout 重复实现等成本) - 路由权限按
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,传入 getGradeDistributionByGradeId 和 getExamsByGradeId 进行数据范围二次过滤(按教师可见的年级/班级范围裁剪结果)。
portal-shell 中需在 hook 层保留 dataScope 参数,避免越权读取非管辖年级数据。
4.4 i18n 命名空间
5 个页面用到 3 个 i18n namespace:
school(dashboard / classes / insights 主命名空间)practice(practice 页主命名空间)school.grades.gradeInsights.selectGrade(practice 页复用)
迁移时需将 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 | 仅用共享 UI(StatCard / 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 子项,按独立入口处理
推荐实施顺序(依赖从轻到重):
page.tsx(入口重定向,最简单,0 数据访问)insights(仅查 1 个 data-access + 共享 UI)classes(3 个 data-access + 1 客户端组件 + 4 子组件)dashboard(4 个 data-access + 4 业务面板组件)practice(3 个 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。