P0-1: 10 个页面补充 requirePermission 权限校验 P0-2: diagnostic/data-access-reports.ts 移除直查 users 表,改用 getUserNamesByIds P0-3: 新增 grade/grades/diagnostic 三组 i18n 翻译文件(zh-CN/en) P0-4: 新增 /management/grade 重定向页面 P1-2: 抽取 toNumber/normalize/buildScopeClassFilter 到 lib/grade-utils.ts P1-3: 为 12 个 Action 新增 Zod safeParse 校验(schema.ts +12 查询 schema) P1-4: 修复 as 断言违规,改用类型守卫函数 P2-2: 移除 diagnostic 组件中 Tailwind 任意值 同步更新架构图文档 004 和 005
42 KiB
成绩和学情诊断模块审计报告
审查日期:2026-06-22 审查范围:
src/modules/grades/**(成绩模块)、src/modules/diagnostic/**(学情诊断模块)、src/app/(dashboard)/{admin,teacher,student,parent}/grades/**、src/app/(dashboard)/{teacher,student}/diagnostic/**、src/app/(dashboard)/management/grade/**、相关 i18n 翻译文件 架构图参考:docs/architecture/004_architecture_impact_map.md§2.6(grades)、§2.22(diagnostic)、docs/architecture/005_architecture_data.jsonL7362(grades)、L10927(diagnostic)
一、现有实现概要
1.1 文件分布
grades 模块(成绩分析)
| 层 | 路径 | 文件数 | 行数 | 说明 |
|---|---|---|---|---|
| Actions | src/modules/grades/actions.ts |
1 | 312 | 10 个 Server Action(CRUD + 查询 + 导出) |
| Actions | src/modules/grades/actions-analytics.ts |
1 | 133 | 5 个分析 Server Action(趋势/对比/分布/排名) |
| Data-access | src/modules/grades/data-access.ts |
1 | 433 | 成绩 CRUD + 统计(含统计业务逻辑) |
| Data-access | src/modules/grades/data-access-analytics.ts |
1 | 337 | 趋势/对比/分布分析(含统计业务逻辑) |
| Data-access | src/modules/grades/data-access-ranking.ts |
1 | 119 | 排名查询(含 normalize 逻辑) |
| Export | src/modules/grades/export.ts |
1 | 200 | Excel 导出(明细 + 班级汇总) |
| Schema | src/modules/grades/schema.ts |
1 | 52 | 4 个 Zod schema |
| Types | src/modules/grades/types.ts |
1 | 186 | 14 个类型定义 |
| Components | src/modules/grades/components/* |
16 | 41~442 | 16 个组件(含 batch-grade-entry 442 行) |
diagnostic 模块(学情诊断)
| 层 | 路径 | 文件数 | 行数 | 说明 |
|---|---|---|---|---|
| Actions | src/modules/diagnostic/actions.ts |
1 | 172 | 6 个 Server Action(生成/发布/删除/查询) |
| Data-access | src/modules/diagnostic/data-access.ts |
1 | 257 | 知识点掌握度查询 + 更新 |
| Data-access | src/modules/diagnostic/data-access-reports.ts |
1 | 203 | 诊断报告 CRUD(直查 users 表) |
| Schema | src/modules/diagnostic/schema.ts |
1 | 48 | 6 个 Zod schema |
| Types | src/modules/diagnostic/types.ts |
1 | 97 | 11 个类型定义 |
| Components | src/modules/diagnostic/components/* |
4 | 69~267 | 4 个组件(含 class-diagnostic-view 267 行) |
路由层
| 角色 | 路由 | 文件数 | 说明 |
|---|---|---|---|
| admin | /admin/school/grades/、/admin/school/grades/insights/ |
4 | 含 loading.tsx + error.tsx |
| teacher | /teacher/grades/、/teacher/grades/analytics/、/teacher/grades/entry/、/teacher/grades/stats/ |
4 | 无 loading.tsx / error.tsx |
| teacher | /teacher/diagnostic/、/teacher/diagnostic/class/[classId]/、/teacher/diagnostic/student/[studentId]/ |
3 | 无 loading.tsx / error.tsx |
| student | /student/grades/、/student/diagnostic/ |
4 | 含 loading.tsx,无 error.tsx |
| parent | /parent/grades/ |
2 | 含 loading.tsx,无 error.tsx |
| management | /management/grade/、/management/grade/classes/、/management/grade/insights/ |
5 | /management/grade/page.tsx 缺失(孤儿 loading/error) |
1.2 主要数据流
[成绩录入] teacher/grades/entry
└─▶ grades/actions.batchCreateGradeRecordsAction
├─▶ requirePermission(GRADE_RECORD_MANAGE)
└─▶ data-access.batchCreateGradeRecords → db.insert(gradeRecords)
[成绩查询] teacher/grades / student/grades / parent/grades
└─▶ grades/actions.getGradeRecordsAction
├─▶ requirePermission(GRADE_RECORD_READ)
├─▶ data-access.getGradeRecords(含 scope 行级过滤)
└─▶ 跨模块:classes/school/users data-access
[成绩分析] teacher/grades/analytics
└─▶ grades/actions-analytics.getGradeTrendAction / getClassComparisonAction / ...
├─▶ requirePermission(GRADE_RECORD_READ)
└─▶ data-access-analytics(含统计计算逻辑)
[学情诊断-学生] teacher/diagnostic/student/[id] / student/diagnostic
└─▶ diagnostic/data-access.getStudentMasterySummary
└─▶ 跨模块:users data-access(getUserNamesByIds)
[学情诊断-班级] teacher/diagnostic/class/[id]
└─▶ diagnostic/data-access.getClassMasterySummary
└─▶ 跨模块:classes/exams/questions/users data-access
[诊断报告生成] teacher/diagnostic
└─▶ diagnostic/actions.generateStudentReportAction / generateClassReportAction
├─▶ requirePermission(DIAGNOSTIC_MANAGE)
└─▶ data-access-reports.createDiagnosticReport
└─▶ ⚠️ 直查 users 表(违反三层架构)
1.3 架构图记录情况
004_architecture_impact_map.md §2.6(grades)和 §2.22(diagnostic)已记录两个模块的导出函数、依赖关系、已知问题和文件清单。架构图信息基本完整,但存在以下遗漏:
- grades 模块行数过时:架构图 L681 标注
data-access.ts419 行(实际 433 行)、L682data-access-analytics.ts293 行(实际 337 行) - diagnostic 模块 deps 过时:
005_architecture_data.jsonL10922/L10937-10941/L10954-10958/L10972-10975 仍记录 diagnostic 直查对方表,实际代码已通过 data-access 接口访问(P1-1 已修复但文档未同步) - diagnostic
data-access-reports.ts直查 users 表未记录:架构图未标注此违规 - grades 模块 actions-analytics.ts 的 5 个 Action 未完整列入 exports 清单
/management/grade/page.tsx缺失未在路由清单中标注- teacher 端 grades/diagnostic 路由普遍缺少 loading.tsx/error.tsx 未标注
二、现存问题与原因分析
2.1 安全性:权限校验缺失或不一致(P0)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| teacher/grades/entry/page.tsx | 无任何权限校验(既无 requirePermission 也无 getAuthContext) |
"所有 Server Action 必须调用 requirePermission() 进行权限校验" |
| teacher/grades/stats/page.tsx | 无任何权限校验 | 同上 |
| teacher/grades/page.tsx | 仅 getAuthContext(),无 requirePermission(GRADE_RECORD_READ) |
同上 |
| teacher/grades/analytics/page.tsx | 仅 getAuthContext(),无 requirePermission(GRADE_RECORD_READ) |
同上 |
| teacher/diagnostic/page.tsx | 仅 getAuthContext(),无 requirePermission(DIAGNOSTIC_READ) |
同上 |
| teacher/diagnostic/class/[classId]/page.tsx | 仅 getAuthContext()(有 dataScope 校验),无 requirePermission(DIAGNOSTIC_READ) |
同上 |
| teacher/diagnostic/student/[studentId]/page.tsx | 仅 getAuthContext()(有 dataScope 校验),无 requirePermission(DIAGNOSTIC_READ) |
同上 |
| student/grades/page.tsx | 仅 getAuthContext(),无 requirePermission(GRADE_RECORD_READ) |
同上 |
| student/diagnostic/page.tsx | 仅 getAuthContext(),无 requirePermission(DIAGNOSTIC_READ) |
同上 |
| parent/grades/page.tsx | 仅 getAuthContext()(有 dataScope 校验),无 requirePermission(GRADE_RECORD_READ) |
同上 |
后果:成绩录入页面(/teacher/grades/entry)和成绩统计页面(/teacher/grades/stats)完全无权限校验,依赖路由中间件做粗粒度角色路由。若中间件配置错误或绕过,任意已登录用户可访问成绩录入页面并调用 batchCreateGradeRecordsAction(虽然 Action 层有 requirePermission,但页面层缺少二次校验不符合"Server Action 二次校验"要求)。
2.2 架构分层:跨模块直接查询 users 表(P0)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| diagnostic/data-access-reports.ts L8 | import { learningDiagnosticReports, users } from "@/shared/db/schema" |
"modules/ 之间通过对方 data-access 通信,不直接查询对方 DB 表" |
| diagnostic/data-access-reports.ts L137-140 | getDiagnosticReports 直接 leftJoin(users, ...) 查询学生姓名 |
同上 |
| diagnostic/data-access-reports.ts L149-153 | 直接 db.select({ id: users.id, name: users.name }).from(users) 查询生成者姓名 |
同上 |
| diagnostic/data-access-reports.ts L168-170 | getDiagnosticReportById 直接 leftJoin(users, ...) |
同上 |
| diagnostic/data-access-reports.ts L177-182 | 直接 db.select({ name: users.name }).from(users) |
同上 |
后果:diagnostic 模块绕过 users 模块的 data-access 层直接查询 users 表,破坏模块封装性。users 表 schema 变更将直接影响 diagnostic 模块。同模块的 data-access.ts 已正确通过 getUserNamesByIds 访问,但 data-access-reports.ts 却绕过,存在不一致。
2.3 架构分层:统计业务逻辑混入 data-access(P1)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| grades/data-access.ts L217-270 | getClassGradeStats 包含 average/median/max/min/variance/stdDev/passRate/excellentRate 计算(53 行统计逻辑) |
"严格三层架构,依赖方向单向" — 统计计算属业务逻辑层 |
| grades/data-access.ts L272-337 | getStudentGradeSummary 包含 averageScore 计算 |
同上 |
| grades/data-access.ts L339-373 | getClassRanking 包含 rank 计算 |
同上 |
| grades/data-access-analytics.ts L59-119 | getGradeTrend 包含 normalized/avg 计算 |
同上 |
| grades/data-access-analytics.ts L128-218 | getClassComparison 包含 normalized/median/avg/passCount/excellentCount 计算(90 行) |
同上 |
| grades/data-access-analytics.ts L226-289 | getSubjectComparison 包含 median/avg/passRate/excellentRate 计算 |
同上 |
| grades/data-access-analytics.ts L299-336 | getGradeDistribution 包含 bucket 分类逻辑 |
同上 |
| grades/data-access-ranking.ts L31-118 | getRankingTrend 包含 normalize/rank 计算逻辑 |
同上 |
后果:data-access 层职责混乱,既负责数据读取又负责业务计算,难以单独测试统计逻辑。架构图 L671 已标记此 P2 问题。应抽取到独立的 stats-service.ts(参考 homework 模块的 stats-service.ts 范例)。
2.4 重复代码:工具函数多处重复(P1)
| 重复函数 | 出现位置 | 违反规则 |
|---|---|---|
buildScopeClassFilter |
grades/data-access.ts L57-75、grades/data-access-analytics.ts L34-48 | "工具函数:建议 ≤ 40 行" + DRY 原则 |
toNumber |
grades/data-access.ts L34-37、grades/data-access-analytics.ts L24-27、grades/data-access-ranking.ts L16-19 | 同上 |
normalize |
grades/data-access.ts、grades/data-access-analytics.ts L29-32、grades/data-access-ranking.ts L21-24 | 同上 |
后果:3 个文件重复实现相同工具函数,修改时需同步多处,易遗漏导致行为不一致。应抽取到 grades/lib/stats-utils.ts 或 shared/lib/grade-utils.ts。
2.5 国际化:完全缺失(P0)
| 位置 | 问题 | 违反规则 |
|---|---|---|
src/modules/grades/components/*(16 个文件) |
全部使用硬编码英文字符串,0 处 useTranslations 调用 |
"所有用户可见文本必须适配 i18n(使用 next-intl),提取翻译键" |
src/modules/diagnostic/components/*(4 个文件) |
全部使用硬编码英文字符串,0 处 useTranslations 调用 |
同上 |
| grades/export.ts L12-17, L54-61, L68-80, L287, L295 | Excel 导出表头、指标名、文件名硬编码中文 | 同上 |
src/shared/i18n/messages/{zh-CN,en}/ |
不存在 grades.json 和 diagnostic.json 翻译文件 |
同上 |
| i18n/request.ts L22-28 | 仅加载 5 个命名空间(common/auth/onboarding/classes/errors),未加载 grades/diagnostic | 同上 |
src/modules/grade-management/components/*(7 个文件,12 处) |
调用 useTranslations("grade") 但 grade.json 翻译文件不存在,运行时会报 MISSING_MESSAGE 错误 |
同上 |
后果:
- grades 和 diagnostic 模块完全无法国际化,所有用户可见文本固定为英文(部分中文混合),无法支持多语言。
- grade-management 模块(年级管理,与成绩模块不同)调用未加载的
grade命名空间,访问/management/grade/、/admin/school/grades/insights等页面会因找不到翻译键而运行时报错。
2.6 前端规范:Error Boundary 和 Suspense 缺失(P1)
| 位置 | 问题 | 违反规则 |
|---|---|---|
src/modules/grades/components/*(16 个文件) |
全部无 Error Boundary | "每个独立的数据区块必须用 React Error Boundary 包裹" |
src/modules/diagnostic/components/*(4 个文件) |
全部无 Error Boundary | 同上 |
src/modules/grades/components/*(16 个文件) |
全部无 Suspense + 骨架屏 | "异步数据使用 React Suspense + 骨架屏" |
src/modules/diagnostic/components/*(4 个文件) |
全部无 Suspense + 骨架屏 | 同上 |
src/app/(dashboard)/teacher/grades/ |
无 loading.tsx / error.tsx | 路由级错误边界和加载态缺失 |
src/app/(dashboard)/teacher/diagnostic/ |
无 loading.tsx / error.tsx | 同上 |
后果:单个组件抛错会导致整个页面崩溃;异步加载无骨架屏过渡,用户体验差(白屏等待)。
2.7 前端规范:a11y 无障碍缺失(P2)
| 位置 | 问题 | 违反规则 |
|---|---|---|
src/modules/grades/components/*(15/16 个文件) |
无 ARIA 属性(仅 batch-grade-entry.tsx 有 aria-hidden 和 aria-invalid) |
"可访问性(a11y):语义化标签、ARIA 属性、键盘导航" |
src/modules/diagnostic/components/*(4 个文件) |
无 ARIA 属性 | 同上 |
| grades/components/grade-record-list.tsx L93-100 | 删除按钮无 aria-label |
同上 |
| diagnostic/components/class-diagnostic-view.tsx L128-139 | 热力图色块仅靠 title 属性,无 role="img" 和 aria-label |
同上 |
| diagnostic/components/mastery-radar-chart.tsx L38-66 | 雷达图无 aria-label / role="img" 描述 |
同上 |
| diagnostic/components/report-list.tsx L192-200, L202-210 | 发布/删除按钮仅 title,无 aria-label |
同上 |
后果:屏幕阅读器用户无法识别图表内容、按钮用途,不符合 WCAG 2.1 AA 标准。
2.8 TypeScript 规范:as 断言违规(P1)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| grades/components/batch-grade-entry.tsx L221 | remark: undefined as string | undefined |
"禁止 as 断言(除非从 unknown 转换或测试中,需注释原因)" |
| grades/components/batch-grade-entry.tsx L312 | setType(v as typeof type) |
同上 |
| grades/components/grade-record-form.tsx L142 | setType(v as typeof type) |
同上 |
| grades/components/grade-distribution-chart.tsx L66-67 | payload as { payload?: {...} }(从 unknown 转换但未使用类型守卫) |
同上 |
后果:as 断言绕过 TypeScript 类型检查,可能隐藏运行时类型错误。应使用类型守卫或 Zod 运行时校验。
2.9 Tailwind 规范:任意值违规(P2)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| diagnostic/components/class-diagnostic-view.tsx L255 | className="w-[180px]" |
"禁止使用任意值(w-[137px]),除非有充分理由并注释" |
| diagnostic/components/mastery-radar-chart.tsx L45 | className="mx-auto h-[360px] w-full max-w-[520px]" |
同上 |
后果:绕过设计令牌系统,无法统一调整尺寸主题。
2.10 数据模型缺陷:班级报告 studentId 字段语义错误(P2)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| diagnostic/data-access.ts L111-114 | 班级报告 studentId: generatedBy 将生成者 ID 写入 studentId 字段 |
"数据模型设计应语义清晰" |
| diagnostic/data-access-reports.ts L111 | 同上 | 同上 |
后果:report-list.tsx L178 显示 r.studentName 时,班级报告会显示生成者(教师)姓名而非学生姓名,存在数据语义错误。架构图 L1245 已标记此 P2 问题。
2.11 Server Action 规范:Zod 校验缺失(P1)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| grades/actions.ts L154-170 | deleteGradeRecordAction 无 Zod 校验(仅 id 字符串) |
"输入使用 Zod 验证,验证失败返回结构化错误" |
| grades/actions.ts L171-190 | getGradeRecordsAction 无 Zod 校验(使用 GradeQueryParams 类型) |
同上 |
| grades/actions.ts L191-208 | getClassGradeStatsAction 无 Zod 校验 |
同上 |
| grades/actions.ts L209-232 | getStudentGradeSummaryAction 无 Zod 校验 |
同上 |
| grades/actions.ts L233-250 | getClassRankingAction 无 Zod 校验 |
同上 |
| grades/actions.ts L251-269 | getGradeRecordByIdAction 无 Zod 校验 |
同上 |
| grades/actions.ts L270-312 | exportGradesAction 无 Zod 校验(params 为内联对象类型) |
同上 |
| grades/actions-analytics.ts L26-45 | getGradeTrendAction 无 Zod 校验 |
同上 |
| grades/actions-analytics.ts L46-64 | getClassComparisonAction 无 Zod 校验 |
同上 |
| grades/actions-analytics.ts L65-83 | getSubjectComparisonAction 无 Zod 校验 |
同上 |
| grades/actions-analytics.ts L84-103 | getGradeDistributionAction 无 Zod 校验 |
同上 |
| grades/actions-analytics.ts L104-133 | getRankingTrendAction 无 Zod 校验 |
同上 |
后果:12 个 Action 缺失 Zod 校验,客户端可传入任意类型参数,可能导致运行时错误或 SQL 注入风险。diagnostic 模块的 6 个 Action 全部使用 Zod 校验,是标杆范例。
2.12 业务逻辑漏洞:grade_managed scope 返回空数据(P2)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| grades/data-access.ts L62-64 | grade_managed scope 返回 sql\1=0``(始终无数据) |
"权限过滤应正确反映角色数据范围" |
| grades/data-access-analytics.ts L39 | 同上 | 同上 |
后果:年级管理员(grade_managed scope)无法查看任何成绩数据,可能是业务逻辑漏洞。年级管理员应能查看所管年级的所有班级成绩。
2.13 路由缺陷:page.tsx 缺失(P1)
| 位置 | 问题 | 违反规则 |
|---|---|---|
src/app/(dashboard)/management/grade/page.tsx |
文件缺失,但有 loading.tsx/error.tsx 孤儿文件 | "路由页面应完整" |
后果:访问 /management/grade 会 404,但 loading.tsx 和 error.tsx 仍存在,造成混乱。
2.14 角色覆盖不一致:admin/parent 无 diagnostic UI(P2)
| 位置 | 问题 | 违反规则 |
|---|---|---|
005_architecture_data.json L174-175, L214-215 |
admin 和 parent 都有 DIAGNOSTIC_MANAGE/DIAGNOSTIC_READ 权限 |
"权限点应有对应 UI" |
src/app/(dashboard)/admin/ |
无 diagnostic 页面 | 同上 |
src/app/(dashboard)/parent/ |
无 diagnostic 页面 | 同上 |
后果:admin 和 parent 拥有 diagnostic 权限但无对应 UI,权限与 UI 覆盖不一致。家长无法查看子女的学情诊断报告。
2.15 SearchParams 工具未统一(P3)
| 位置 | 问题 | 违反规则 |
|---|---|---|
| student/grades/page.tsx | 自定义 SearchParams 类型和 getParam 函数 |
"最大化复用" |
| management/grade/insights/page.tsx | 自定义 SearchParams 类型和 getParam 函数 |
同上 |
后果:与 teacher 端 grades 页面已复用 @/shared/lib/search-params 的做法不一致,存在重复代码。
三、行业差距对比
3.1 成绩模块(grades)行业对标
| 功能维度 | 行业优秀实践(K12 成绩管理系统) | 当前实现 | 差距影响 |
|---|---|---|---|
| 成绩录入 | 支持Excel批量导入、扫码录入、语音录入;录入时实时校验分数范围;自动计算总分、平均分 | 仅支持单条录入 + 批量录入(表单式);有分数范围校验;无 Excel 导入 | 教师录入效率低,大班级成绩录入耗时 |
| 成绩分析 | 多维度分析(班级/年级/个人/科目);支持自定义分析维度;提供归因分析(哪些题目失分多) | 5 种分析(趋势/班级对比/科目对比/分布/排名);无归因分析;无自定义维度 | 教师无法定位失分原因,难以针对性教学 |
| 可视化 | 交互式图表(hover 显示详情、点击下钻);支持图表下载为图片;支持自定义图表配置 | 静态图表(TrendLineChart/SimpleBarChart);无 hover 详情;无下载功能 | 数据呈现不够直观,教师难以深入分析 |
| 报告导出 | 支持 PDF/Excel/CSV 多格式;支持自定义报告模板;支持批量导出(按班级/年级) | 仅 Excel 导出(明细 + 班级汇总);无 PDF;无自定义模板 | 无法满足学校正式报告需求(如家长会报告需 PDF) |
| 预警机制 | 成绩异常预警(突然下降/持续低迷);及格率预警;班级对比异常预警 | 无预警机制 | 教师无法及时发现学生成绩异常 |
| 多角色视图 | 学生看自己 + 班级平均;家长看子女 + 班级排名;教师看所教班级;管理员看全校 | 4 角色都有基本视图;但 parent 无 diagnostic;admin 无 diagnostic | 家长无法全面了解子女学情 |
| 空状态/加载态 | 完善的空状态插画 + 引导操作;骨架屏过渡 | 仅部分页面有 loading.tsx;组件无 Suspense | 用户体验差,白屏等待 |
| 数据联动 | 成绩 → 学情诊断 → 推荐练习;成绩 → 作业 → 知识点掌握度 | grades 与 diagnostic 无数据联动;无推荐练习 | 无法形成"诊断-练习-反馈"闭环 |
3.2 学情诊断模块(diagnostic)行业对标
| 功能维度 | 行业优秀实践(K12 学情诊断系统) | 当前实现 | 差距影响 |
|---|---|---|---|
| 知识点掌握度 | 基于IRT(项目反应理论)计算;支持知识点权重;支持时间衰减(近期表现权重更高) | 基于正确率简单计算;无权重;无时间衰减 | 掌握度计算不够精准 |
| 诊断报告 | 自动生成 PDF 报告;支持自定义模板;含学习建议、练习推荐、进步轨迹 | 生成 draft 报告(JSON 存储);无 PDF;建议为静态文本 | 报告不够专业,无法直接发给家长 |
| 可视化 | 雷达图 + 热力图 + 知识图谱;支持知识点下钻;支持时间对比 | 雷达图 + 热力图;无知识图谱;无下钻 | 知识结构呈现不够清晰 |
| 个性化推荐 | 基于弱项推荐练习题/微课;支持难度自适应;支持学习路径规划 | 仅列出弱项知识点 + "Practice" 链接(跳转到作业列表) | 无法精准推荐练习内容 |
| 班级诊断 | 班级整体掌握度 + 重点关注学生列表 + 教学建议;支持按知识点筛选学生 | 班级掌握度摘要 + 需关注学生列表;无教学建议 | 教师难以根据诊断调整教学 |
| 历史趋势 | 掌握度随时间变化曲线;支持对比多个时间段 | 无历史趋势(仅当前快照) | 无法评估学习进步情况 |
| 多角色覆盖 | 学生/家长/教师/管理员都能查看;家长看子女诊断报告 | 仅 teacher + student 有 UI;parent/admin 无 UI | 家长无法了解子女学情 |
3.3 关键差距总结
- 数据孤岛:grades 和 diagnostic 模块无数据联动,无法形成"成绩 → 诊断 → 练习 → 反馈"闭环。行业优秀产品(如猿题库、作业帮)已实现完整学习闭环。
- 家长端缺失:parent 无 diagnostic UI,家长无法查看子女学情诊断报告。K12 场景下家长是重要决策者,缺失影响家校沟通。
- 报告专业度不足:diagnostic 报告为 JSON 存储,无 PDF 导出,无法直接用于家长会。行业产品普遍支持专业 PDF 报告。
- 预警机制空白:成绩异常、掌握度低迷无预警,教师无法主动干预。
- 可视化深度不足:无知识图谱、无下钻分析、无时间对比,数据呈现停留在表层。
四、改进优先级建议
P0(紧急 — 安全与合规)
| # | 问题 | 改进方向 |
|---|---|---|
| P0-1 | 权限校验缺失(10 个页面) | 所有页面调用 requirePermission():teacher/grades 用 GRADE_RECORD_READ/GRADE_RECORD_MANAGE,teacher/diagnostic 用 DIAGNOSTIC_READ/DIAGNOSTIC_MANAGE,student/parent 用对应 READ 权限 |
| P0-2 | diagnostic/data-access-reports.ts 直查 users 表 | 改为调用 @/modules/users/data-access 的 getUserNamesByIds,删除 users 表 import |
| P0-3 | i18n 完全缺失 + grade-management 运行时报错 | 创建 grades.json 和 diagnostic.json 翻译文件(zh-CN + en);修复 grade-management 模块的 grade 命名空间(创建 grade.json 或改用 gradeManagement);在 i18n/request.ts 注册新命名空间 |
| P0-4 | /management/grade/page.tsx 缺失 |
补齐 page.tsx 或删除孤儿 loading.tsx/error.tsx |
P1(较严重 — 架构与质量)
| # | 问题 | 改进方向 |
|---|---|---|
| P1-1 | 统计业务逻辑混入 data-access | 抽取 grades/stats-service.ts,将 getClassGradeStats/getClassComparison/getSubjectComparison/getGradeDistribution/getRankingTrend 的统计计算迁移至纯函数(参考 homework/stats-service.ts 范例) |
| P1-2 | 重复工具函数 | 抽取 grades/lib/scope-filter.ts(buildScopeClassFilter)和 grades/lib/stats-utils.ts(toNumber/normalize) |
| P1-3 | 12 个 Action 缺失 Zod 校验 | 为 deleteGradeRecordAction/getGradeRecordsAction/getClassGradeStatsAction/getStudentGradeSummaryAction/getClassRankingAction/getGradeRecordByIdAction/exportGradesAction + 5 个 analytics Action 创建对应 Zod schema |
| P1-4 | as 断言违规(4 处) |
使用类型守卫或 Zod 运行时校验替代 |
| P1-5 | Error Boundary 和 Suspense 缺失 | 创建 grades/components/widget-boundary.tsx(Error Boundary + Suspense + Skeleton 组合);每个数据区块独立包裹;teacher/grades 和 teacher/diagnostic 路由补齐 loading.tsx/error.tsx |
| P1-6 | 架构图同步 | 更新 004 和 005 文档:grades 行数、diagnostic deps、新增 stats-service.ts、新增 lib/、补齐 actions-analytics exports |
P2(优化 — 体验与扩展)
| # | 问题 | 改进方向 |
|---|---|---|
| P2-1 | a11y 无障碍缺失 | 补充 ARIA 属性:图表 role="img" + aria-label;按钮 aria-label;表格 caption;列表 role="list" |
| P2-2 | Tailwind 任意值 | 移除 w-[180px]/h-[360px]/max-w-[520px],改用设计令牌或注释说明 |
| P2-3 | 班级报告 studentId 字段语义错误 | 修改 learningDiagnosticReports schema,将 studentId 改为可空,或增加 classId/generatedBy 字段 |
| P2-4 | grade_managed scope 返回空数据 | 修复 buildScopeClassFilter,grade_managed scope 应返回所管年级的班级过滤条件 |
| P2-5 | admin/parent 无 diagnostic UI | 新增 /parent/diagnostic/ 页面(家长查看子女诊断报告);admin 可复用 teacher 视图 |
| P2-6 | SearchParams 工具未统一 | student/grades 和 management/grade/insights 改用 @/shared/lib/search-params |
P3(长期 — 行业对标)
| # | 问题 | 改进方向 |
|---|---|---|
| P3-1 | grades 与 diagnostic 无数据联动 | 设计联动接口:成绩录入后触发掌握度更新;诊断报告含成绩趋势 |
| P3-2 | 无预警机制 | 新增 grades/alerts-service.ts:成绩下降预警、及格率预警、掌握度低迷预警 |
| P3-3 | 诊断报告无 PDF 导出 | 集成 PDF 生成库(如 @react-pdf/renderer),支持专业报告模板 |
| P3-4 | 无知识图谱可视化 | 引入知识图谱组件(如 react-flow),展示知识点关系与掌握度 |
| P3-5 | 无个性化练习推荐 | 基于弱项推荐练习题,对接 questions 模块 |
| P3-6 | Widget 配置系统 | 设计 GradesWidgetConfig/DiagnosticWidgetConfig 类型,按角色配置渲染哪些 Widget |
五、架构图同步说明
本次审计发现架构图存在以下遗漏或不一致,需在实现后同步更新:
5.1 004_architecture_impact_map.md 需补充
-
§2.6 grades 模块:
- 更新文件清单行数:
data-access.ts419→433、data-access-analytics.ts293→337 - 补充
actions-analytics.ts的 5 个 Action 到 exports 清单(当前仅列 11 个,实际 15 个) - 新增
stats-service.ts(P1-1 抽取后) - 新增
lib/scope-filter.ts、lib/stats-utils.ts(P1-2 抽取后) - 新增
components/widget-boundary.tsx(P1-5 新增)
- 更新文件清单行数:
-
§2.22 diagnostic 模块:
- 更新已知问题:标注
data-access-reports.ts直查 users 表(P0-2 修复前) - 更新文件清单行数(如有变化)
- 更新已知问题:标注
-
路由清单:
- 标注
/management/grade/page.tsx缺失(P0-4 修复前) - 标注 teacher/grades 和 teacher/diagnostic 路由缺少 loading.tsx/error.tsx
- 新增
/parent/diagnostic/路由(P2-5 实现后)
- 标注
5.2 005_architecture_data.json 需修改
-
modules.grades节点(L7362):- 更新
dataAccess中各函数的deps:移除直查classes/classEnrollments/subjects/users,改为classes/data-access.*/school/data-access.*/users/data-access.* - 新增
stats-service.ts的 exports - 新增
lib/scope-filter.ts、lib/stats-utils.ts的 exports - 补充
actions-analytics.ts的 5 个 Action 到actions数组
- 更新
-
modules.diagnostic节点(L10927):- 更新
dataAccess中各函数的deps:移除直查users/classes/classEnrollments/examSubmissions/submissionAnswers/questionsToKnowledgePoints,改为对应模块 data-access - 标注
data-access-reports.ts的getDiagnosticReports/getDiagnosticReportById依赖users/data-access.getUserNamesByIds(P0-2 修复后)
- 更新
-
permissions节点:- 确认
GRADE_RECORD_READ/GRADE_RECORD_MANAGE/DIAGNOSTIC_READ/DIAGNOSTIC_MANAGE权限点已定义(已存在 ✓)
- 确认
-
routes节点:- 补充 teacher/grades/entry、teacher/grades/stats、teacher/diagnostic/class/[classId]、teacher/diagnostic/student/[studentId] 路由
- 标注
/management/grade/page.tsx缺失 - 新增
/parent/diagnostic/路由(P2-5 实现后)
-
dependencyMatrix:- 更新 grades → classes/school/users 的依赖关系(通过 data-access,已正确)
- 更新 diagnostic → classes/exams/questions/users 的依赖关系(通过 data-access,P0-2 修复后完全正确)
5.3 翻译文件结构示例
src/shared/i18n/messages/
├─ zh-CN/
│ ├─ grades.json # 新增(成绩模块)
│ ├─ diagnostic.json # 新增(学情诊断模块)
│ └─ grade.json # 新增(grade-management 模块,修复运行时报错)
└─ en/
├─ grades.json # 新增
├─ diagnostic.json # 新增
└─ grade.json # 新增
grades.json 结构示例(zh-CN):
{
"title": {
"list": "成绩查询",
"entry": "成绩录入",
"analytics": "成绩分析",
"stats": "成绩统计"
},
"filters": {
"class": "班级",
"subject": "科目",
"type": "类型",
"semester": "学期",
"allClasses": "全部班级",
"allSubjects": "全部科目",
"allTypes": "全部类型",
"allSemesters": "全部学期",
"searchPlaceholder": "按标题搜索..."
},
"type": {
"exam": "考试",
"quiz": "测验",
"homework": "作业",
"other": "其他"
},
"semester": {
"s1": "第一学期",
"s2": "第二学期"
},
"list": {
"empty": "暂无成绩记录",
"columns": {
"student": "学生",
"class": "班级",
"subject": "科目",
"title": "标题",
"score": "分数",
"type": "类型",
"semester": "学期",
"recordedBy": "录入人",
"date": "日期"
}
},
"form": {
"title": "录入成绩",
"save": "保存",
"saving": "保存中...",
"cancel": "取消",
"selectClass": "选择班级",
"selectSubject": "选择科目",
"selectStudent": "选择学生",
"titlePlaceholder": "如期中考试",
"score": "分数",
"fullScore": "满分",
"remark": "备注(可选)",
"remarkPlaceholder": "关于此成绩的备注..."
},
"delete": {
"title": "删除成绩记录",
"confirmation": "确定要删除此成绩记录吗?此操作不可撤销。",
"confirm": "删除",
"cancel": "取消",
"deleting": "删除中..."
},
"export": {
"detail": "导出成绩明细",
"classReport": "导出班级成绩总表",
"success": "导出成功",
"failed": "导出失败"
},
"stats": {
"title": "统计",
"average": "平均分",
"median": "中位数",
"max": "最高分",
"min": "最低分",
"stdDev": "标准差",
"variance": "方差",
"passRate": "及格率",
"excellentRate": "优秀率",
"count": "人数"
},
"analytics": {
"trend": "成绩趋势",
"classComparison": "班级对比",
"subjectComparison": "科目对比",
"distribution": "分数分布",
"ranking": "排名",
"rankingTrend": "排名趋势"
},
"batch": {
"title": "批量录入",
"saving": "保存中...",
"restored": "已恢复未保存的成绩草稿",
"invalidScores": "存在无效分数",
"fullScoreRequired": "满分必填",
"saved": "已录入"
},
"empty": {
"noRecords": "暂无成绩记录",
"noData": "暂无数据"
},
"error": {
"loadFailed": "加载失败",
"saveFailed": "保存失败",
"deleteFailed": "删除失败",
"retry": "重试"
}
}
diagnostic.json 结构示例(zh-CN):
{
"title": {
"student": "学生学情诊断",
"class": "班级学情诊断",
"reportList": "诊断报告"
},
"type": {
"individual": "个人",
"class": "班级",
"grade": "年级"
},
"status": {
"draft": "草稿",
"published": "已发布",
"archived": "已归档"
},
"filters": {
"reportType": "报告类型",
"status": "状态",
"allTypes": "全部类型",
"allStatuses": "全部状态"
},
"summary": {
"overallMastery": "总体掌握度",
"strengths": "强项",
"weaknesses": "弱项",
"students": "学生数",
"avgMastery": "平均掌握度",
"needAttention": "需重点关注"
},
"chart": {
"radarTitle": "知识点掌握度",
"radarDescription": "掌握度雷达图",
"heatmapTitle": "知识点掌握度热力图",
"rankingTitle": "知识点排名"
},
"report": {
"generate": "生成诊断报告",
"generateStudent": "生成学生诊断报告",
"generateClass": "生成班级诊断报告",
"publish": "发布",
"delete": "删除",
"publishTitle": "发布报告",
"deleteTitle": "删除报告",
"recommendations": "学习建议",
"history": "报告历史"
},
"strengths": {
"title": "强项(≥80%)",
"practice": "练习"
},
"weaknesses": {
"title": "弱项(<60%)",
"practice": "练习"
},
"empty": {
"noData": "暂无诊断数据",
"noClassData": "无法加载班级掌握度摘要",
"noMastery": "暂无知识点掌握度记录",
"noReports": "暂无诊断报告"
},
"error": {
"generateFailed": "生成报告失败",
"publishFailed": "发布失败",
"deleteFailed": "删除失败",
"loadFailed": "加载失败",
"retry": "重试"
}
}
六、合规项确认
以下条目已通过审计:
- ✅ grades 模块跨模块依赖全部通过 data-access:所有跨模块访问(classes/school/users)均通过对方 data-access 函数
- ✅ diagnostic 模块 data-access.ts 跨模块依赖通过 data-access(仅 data-access-reports.ts 违规)
- ✅ 所有 Server Action 调用
requirePermission():grades 15 个 + diagnostic 6 个 = 21 个 Action 全部合规 - ✅ 所有 Server Action 返回
ActionState<T> - ✅ 所有 Server Action 使用
revalidatePath精确刷新 - ✅ 无
role === "xxx"硬编码:全模块无 - ✅ diagnostic 组件使用
usePermission().hasPermission():class-diagnostic-view.tsx 和 report-list.tsx 已使用 - ✅ 无
dangerouslySetInnerHTML - ✅ 无
any类型 - ✅ 文件行数全部合规:最大为 grades/components/batch-grade-entry.tsx 442 行 < 500 行组件建议上限
- ✅
"use client"/"use server"/"server-only"正确放置 - ✅
import type使用规范 - ✅ diagnostic schema.ts 枚举与 types.ts 联合类型一致
- ✅ 接口命名规范(无 I 前缀,PascalCase)
七、重构方案设计要点(供后续实现参考)
7.1 完全解耦
- 定义
GradesDataService接口抽象数据依赖,使用 React Context 注入 - 模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access
- 不同角色差异通过接口不同实现隔离(如
TeacherGradesService/StudentGradesService/ParentGradesService)
7.2 组合优先
- 所有 UI 通过组件组合(children、slots、render props)实现灵活性
- 逻辑复用抽取为自定义 hooks(如
useGradeRecords/useGradeTrend/useMasterySummary) - 严禁继承或深层嵌套 HOC
7.3 最大化复用
- 识别四角色共用 UI 块:
GradeTrendChart/GradeStatsCard/MasteryRadarChart/WidgetBoundary - 抽象泛型组件:
<DataTable<T>>/<FilterBar>/<EmptyState>/<ErrorState> - 各角色模块仅组合复用单元,可配置化显示内容
7.4 配置驱动
- 设计
GradesWidgetConfig类型,按角色配置渲染哪些 Widget - 示例:teacher 看 [录入, 查询, 分析, 统计],student 看 [我的成绩, 趋势],parent 看 [子女成绩, 趋势]
7.5 错误与边界处理
- 每个独立数据区块用
<WidgetBoundary>(Error Boundary + Suspense + Skeleton 组合)包裹 - 明确处理空数据、无权限、网络异常等边界状态
- 支持流式渲染(React Server Components 获取初始数据)
7.6 可测试性
- 数据获取、计算、格式化等纯逻辑放入
stats-service.ts或 hooks - 导出清晰接口类型以便 mock
- 统计函数为纯函数,易于单测
7.7 监控埋点
- 预留关键操作埋点接口:成绩录入、报告生成、报告发布、导出操作
- 通过
shared/lib/analytics统一上报