Files
NextEdu/docs/architecture/audit/archive/grades-audit-report.md

458 lines
26 KiB
Markdown
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.
# grades(成绩)模块审计报告
> 审计日期:2026-06-25
> 审计范围:`src/modules/grades/**` + `src/app/(dashboard)/**/grades/**` 共 50 个文件
> 架构图定位:`004_architecture_impact_map.md` § 2.6 / `005_architecture_data.json` `modules.grades`
> 总体结论:该模块在架构图中被标记为"标杆模块(拆分范例)",三层架构、跨模块 data-access 通信、Server Action 权限校验、纯函数抽取、a11y 与 i18n 在主要文件中已落实。但仍存在 **3 处 P0 i18n 严重缺陷**、**2 个文件超行数上限**、**多处英文错误消息未 i18n**、**若干 `as` 断言与缺返回值类型**等问题,需按本报告优先级修复。
---
## 一、现有实现概要
### 1.1 文件分布
grades 模块共 50 个文件,按职责划分为四层:
| 层 | 文件数 | 路径 | 职责 |
|----|--------|------|------|
| 类型/校验 | 2 | `modules/grades/types.ts``schema.ts` | 类型定义 + Zod 输入校验 |
| 数据访问 | 5 | `data-access.ts``data-access-analytics.ts``data-access-ranking.ts``export.ts``stats-service.ts` | DB I/O + Excel 导出 + 统计纯函数 |
| Server Actions | 2 | `actions.ts``actions-analytics.ts` | 编排层(权限校验 → Zod → data-access → 通知 → revalidate) |
| Lib 工具 | 4 | `lib/grade-utils.ts``scope-filter.ts``scope-check.ts``type-guards.ts` | 纯工具函数 + 行级权限过滤 + 类型守卫 |
| 组件 | 19 | `modules/grades/components/**` | RSC + 客户端组件混合 |
| 页面 | 18 | `app/(dashboard)/**/grades/**` | 四种角色页面 + loading/error |
### 1.2 主要数据流
```
teacher/grades/page.tsx
└─▶ requirePermission(GRADE_RECORD_READ)
└─▶ grades/data-access.getGradeRecords(scope, currentUserId, limit, offset)
└─▶ lib/scope-filter.buildScopeClassFilter → 行级过滤
└─▶ db.query.gradeRecords (DB 层分页)
└─▶ <GradeRecordList> + <GradeFilters> + <ExportButton>
teacher/grades/entry/page.tsx
└─▶ requirePermission(GRADE_RECORD_MANAGE)
└─▶ <BatchGradeEntryByExam>
└─▶ exams/data-access.getExamsForGradeEntry
└─▶ actions.batchCreateGradeRecordsByExamAction
└─▶ 单事务写入 grade_records + grade_record_answers
└─▶ 投影到 exam_submissions(status=graded) + submission_answers
└─▶ 并行:diagnostic.updateMasteryFromExamScore + error-book.collectFromExamSubmission
└─▶ notifyGradeEntered(批量通知学生 + 家长)
parent/grades/page.tsx
└─▶ requirePermission(GRADE_RECORD_READ)
└─▶ Promise.allSettled(为每个子女并行查询)
└─▶ grades/data-access.getStudentGradeSummary
└─▶ grades/data-access-analytics.getClassAverageTrend
└─▶ <StudentGradeSummary> + <GradeTrendCard> + <ParentExportButton>
```
### 1.3 架构图完整性核对
经核对 `004_architecture_impact_map.md` § 2.6 与 `005_architecture_data.json` `modules.grades`:
- **已记录且准确**:`exports.dataAccess`(15+ 函数)、`exports.actions`(17 个)、`exports.types`(7 类)、`exports.lib`(4 函数)、`exports.statsService`(8 纯函数)、`exports.export`(3 函数)、`exports.components`(WidgetBoundary/SchoolWideSummaryCard/ScoreCell)、`dependencies`(classes/school/users/exams/error-book/diagnostic/notifications/parent)、`knownIssues`(已修复 28 项)。
- **架构图遗漏**:
- `components/widget-boundary.tsx` 在文件清单中标注为 136 行,但未在 `exports.components` 中列出(仅在 P1-5 修复记录中提及)。
- `lib/scope-check.ts``assertClassInScope` 函数未在 `exports.lib` 节点中列出(仅在 v4-P2-6 修复记录中提及)。
- `parent/grades/page.tsx` 调用的 `getClassAverageTrend` data-access 函数未在架构图 `exports.dataAccess` 中记录。
### 1.4 标杆特性
该模块作为项目标杆,已落实的实践:
- ✅ data-access 按职责拆分为 3 个文件(analytics/ranking/主)
- ✅ Server Actions 全部 `requirePermission()` + Zod 校验
- ✅ 统计逻辑抽取为 `stats-service.ts` 纯函数(8 个),可独立测试
- ✅ 行级权限过滤 `buildScopeClassFilter(scope, currentUserId?)`
- ✅ DB 层分页(`PaginatedGradeRecords`)而非内存分页
- ✅ 事务原子性(`db.transaction`)
- ✅ 跨模块全部通过 data-access 通信,无直查他模块表
- ✅ 类型守卫(`isGradeType`/`isSemester`/`isDistributionTooltipPayload`)替代 `as` 断言
-`handleActionError` 统一错误处理 + `safeActionCall` 包装器
- ✅ a11y:4 图表 `role="img"` + `aria-label`、2 表格 `<caption>`、色盲友好双重编码
- ✅ RSC 优先 + 客户端组件最小化
---
## 二、现存问题与原因分析
### 2.1 [P0] i18n 严重缺失 — 3 个 error.tsx 硬编码中文
| 文件 | 行号 | 硬编码文本 |
|------|------|-----------|
| [parent/grades/error.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/parent/grades/error.tsx) | 16-20 | "子女成绩页面加载失败" / "抱歉,页面加载时发生了意外错误..." / "重试" |
| [admin/school/grades/insights/error.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/error.tsx) | 17-21 | "成绩洞察页面加载失败" / "抱歉..." / "重试" |
| [admin/school/grades/error.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/grades/error.tsx) | 12-16 | "页面加载失败" / "抱歉..." / "重试" |
- **违反规则**:项目规则"所有用户可见文本必须适配 i18n(next-intl),提取翻译键"。
- **原因**:这三个 error.tsx 与 `teacher/grades/error.tsx`(已 i18n)同期创建,但遗漏未接入 `useTranslations`
- **直接后果**:非中文用户在页面出错时看到中文错误提示,国际化体验中断;与 teacher/grades/error.tsx 的良好实践形成明显不一致。
### 2.2 [P0] error.tsx 函数缺返回值类型标注
上述 3 个 error.tsx + `student/grades/error.tsx` + 所有 loading.tsx(共 7 个文件)的函数均未显式标注 `: JSX.Element` 返回值类型。
- **违反规则**:项目规则"TypeScript 严格模式:函数返回值必须显式标注,特别是 `Promise<T>`"。
- **原因**:loading/error 文件通常由脚手架生成,未补类型标注。
- **直接后果**:tsc 严格模式下可能漏检类型错误;代码可读性下降。
### 2.3 [P1] `actions.ts` 超行数上限(856 行)
[actions.ts](file:///e:/Desktop/CICD/src/modules/grades/actions.ts) 当前 856 行,超过 Server Actions 800 行建议上限 56 行。
- **违反规则**:项目规则"Server Actions / Data Access 模块:建议 ≤ 800 行"。
- **原因**:模块职责持续扩张(草稿 CRUD、按试卷录入、撤销、批量删除、通知),所有逻辑集中在单文件。
- **直接后果**:接近 1000 行硬上限,后续新增 Action 将突破硬限;可读性、code review 成本上升。
### 2.4 [P1] `data-access.ts` 接近行数上限(816 行)
[data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) 当前 816 行,超过 800 行建议上限 16 行。
- **违反规则**:同上。
- **原因**:草稿 CRUD(`saveGradeDraft`/`getGradeDraft`/`deleteGradeDraft`)、按试卷录入(`batchCreateGradeRecordsByExam`)混入主文件。
- **直接后果**:与 analytics/ranking 已拆分形成不一致;新增数据访问函数将很快突破硬限。
### 2.5 [P1] `actions.ts` 多处英文错误消息未 i18n
| 行号 | 硬编码英文 |
|------|-----------|
| 148 | "Invalid form data" |
| 180 | "Grade record created" |
| 253 | "Failed to delete grade record" |
| 394-399 | "No records to undo" / "Cannot undo more than 500 records at once" |
| 483-487 | "No records to delete" / "Cannot delete more than 500 records at once" |
| 584-592 | "Can only view your own grades" / "Can only view your children's grades" |
| 654-669 | "Can only view your own grade record" / "Can only view your children's grade records" |
- **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。
- **原因**:这些错误消息通过 `ActionState.error` 返回前端展示,早期未统一接入翻译键。
- **直接后果**:非中文用户看到英文错误提示。
### 2.6 [P1] `lib/scope-check.ts` 返回消息未 i18n
[scope-check.ts](file:///e:/Desktop/CICD/src/modules/grades/lib/scope-check.ts) 第 24/29/31 行返回 "You can only access classes you teach" 等英文,这些消息会通过 Action 层直接返回用户。
- **违反规则**:同上。
- **原因**:`assertClassInScope` 是同步函数,无法调用 `getTranslations`(异步),因此返回原始错误消息交由 Action 层包装。
- **直接后果**:Action 层目前未对返回消息做翻译包装,直接透传给用户。
### 2.7 [P1] `actions-analytics.ts` scope 校验消息未 i18n
[actions-analytics.ts](file:///e:/Desktop/CICD/src/modules/grades/actions-analytics.ts) 第 176/183 行 "Can only view your own ranking trend" / "Can only view your children's ranking trend"。
- 同 2.5 原因与后果。
### 2.8 [P1] `batch-grade-entry.tsx` 超组件行数上限(604 行)
[batch-grade-entry.tsx](file:///e:/Desktop/CICD/src/modules/grades/components/batch-grade-entry.tsx) 604 行,超过组件 500 行建议上限 104 行(复杂表单可放宽至 800,但仍建议拆分)。
- **违反规则**:项目规则"React 组件:建议 ≤ 500 行(复杂表单/大型表格可放宽至 800)"。
- **原因**:Excel 式表格 + 试卷/班级选择器 + 统计栏 + 撤销机制 + AlertDialog 全部集中在单文件。
- **直接后果**:可读性下降;子组件无法独立复用与测试;状态管理复杂度高,易引入 bug。
### 2.9 [P1] `batch-grade-entry.tsx` 第 341 行 `as` 断言无类型守卫
```ts
JSON.parse(raw) as { ids: string[]; timestamp: number }
```
- **违反规则**:项目规则"禁止 `as` 断言(除非从 `unknown` 转换或测试中,需注释原因)"。
- **原因**:sessionStorage 撤销数据反序列化未做类型守卫。
- **直接后果**:数据被篡改时可能运行时报错;类型安全缺失。
### 2.10 [P1] `parent/grades/page.tsx` 第 63 行 `as` 断言
```ts
summary as NonNullable<typeof r.value.summary>
```
- **违反规则**:同 2.9。
- **原因**:虽有前置 `filter(r => r.value.summary)` 保证,但严格违反"禁止 as 断言"规则。
- **直接后果**:类型系统对运行时假设依赖,filter 逻辑变更时可能失效。
### 2.11 [P2] `student-grade-summary.tsx` / `grade-record-list.tsx` 硬编码 "S" 前缀
两文件均使用 `<TableCell>S{r.semester}</TableCell>` 硬编码 "S1"/"S2" 前缀。
- **违反规则**:i18n 规则。
- **原因**:学期显示格式未抽象为翻译键。
- **直接后果**:非英文/中文语境下学期标签不符合本地化习惯。
### 2.12 [P2] 多个组件函数缺返回值类型标注
| 文件 | 函数 |
|------|------|
| `grade-filters.tsx` | `GradeFilters` |
| `grade-query-filters.tsx` | `GradeQueryFilters` |
| `export-button.tsx` | `handleExport` |
| `grade-record-form.tsx` | `handleSubmit` |
| 所有 loading.tsx(7 个) | `Loading` |
- **违反规则**:函数返回值必须显式标注。
- **原因**:脚手架/早期代码遗漏。
- **直接后果**:类型推导依赖,严格模式潜在漏检。
### 2.13 [P2] `data-access.ts` `updateGradeRecord` 类型安全性较弱
第 407 行使用 `Record<string, unknown>` 累积更新字段。
- **违反规则**:TypeScript 严格模式精神(虽未直接违反禁止 any)。
- **原因**:Drizzle 动态更新字段累积时类型推导困难。
- **直接后果**:可能写入非法字段名;类型安全缺失。
### 2.14 [P2] `getStudentGradeSummary` 内部调用 `getClassRanking` 形成 data-access 层内部耦合
[data-access.ts](file:///e:/Desktop/CICD/src/modules/grades/data-access.ts) 第 564 行 `getStudentGradeSummary` 内部调用 `getClassRanking`,形成 data-access 层内部依赖。
- **原因**:学生成绩摘要需要排名信息,直接复用排名查询。
- **直接后果**:语义上 `getStudentGradeSummary` 不应依赖 `getClassRanking`;两者缓存参数不完全一致,可能导致重复查询;测试时需 mock 排名逻辑。
### 2.15 [P2] 组件复用抽象不足
| 现状 | 建议 |
|------|------|
| `GradeFilters`(student,nuqs) 与 `GradeQueryFilters`(teacher,URLSearchParams) 两个独立过滤器 | 抽象为统一组件 + props 配置 |
| `ExportButton`(teacher) 与 `ParentExportButton`(parent) | 可统一为带 `variant` prop 的单一组件 |
| `AnalyticsFilters`(teacher RSC) 与 `GradeInsightsFilters`(school ChipNav) | 可考虑统一 |
- **违反规则**:项目规则"识别四个角色共用的 UI 块和业务逻辑块,抽象为泛型组件和 hooks"。
- **原因**:各角色过滤器实现差异较大(nuqs vs URLSearchParams vs ChipNav),未做抽象。
- **直接后果**:新增角色需重写过滤器;维护成本上升。
### 2.16 [P2] 缺少 Error Boundary 包裹的独立数据区块
`teacher/grades/page.tsx` 列表与统计卡片未用 `WidgetBoundary` 包裹(仅 analytics 页面已包裹)。
- **违反规则**:项目规则"每个独立的数据区块必须用 React Error Boundary 包裹"。
- **原因**:主页面早期未应用 WidgetBoundary。
- **直接后果**:单个数据区块失败会导致整个页面崩溃。
### 2.17 [P2] 撤销机制仅依赖 sessionStorage(5 分钟)
`batch-grade-entry.tsx` 撤销令牌存于 sessionStorage,刷新或跨设备后丢失;`undoBatchCreateGradeRecordsAction` 无时间窗口限制,理论上可撤销任意时间自己录入的记录。
- **违反规则**:项目规则"错误与边界处理:明确处理空数据、无权限、网络异常等边界状态"(隐含)。
- **原因**:撤销机制为 v4 新增,未考虑持久化与时效。
- **直接后果**:误删风险;跨设备无法撤销。
### 2.18 [P2] 通知串行写入
`notifyGradeEntered` 对每个学生/家长串行调用 `createNotification`(actions.ts 第 88-117 行)。
- **违反规则**:性能最佳实践。
- **原因**:通知 data-access 未提供批量接口。
- **直接后果**:批量录入 30+ 学生时通知耗时显著。
### 2.19 [P2] 图表 tooltip a11y 不足
图表组件有 `role="img"` + `aria-label`,但 tooltip(recharts tooltip)的 a11y 支持不足,屏幕阅读器无法读取悬停数据。
### 2.20 [P2] `lib/type-guards.ts` 第 8/12 行 `as` 断言
```ts
(GRADE_TYPES as readonly string[]).includes(v)
```
- **违反规则**:禁止 as 断言(虽属类型收窄,但严格违规)。
- **改进**:改用 `Array.from(GRADE_TYPES).includes(v)``(GRADE_TYPES as readonly unknown[]).includes(v as unknown)` 后做类型守卫。
### 2.21 [P3] 架构图遗漏
- `components/widget-boundary.tsx` 未在 `exports.components` 节点列出
- `lib/scope-check.ts``assertClassInScope` 未在 `exports.lib` 节点列出
- `getClassAverageTrend` data-access 函数未记录
---
## 三、行业差距对比
对标 PowerSchool / Gradebook、Google Classroom、Canvas LMS、智学网、校宝在线等 K12 成绩管理产品,本模块差距如下:
### 3.1 缺失:成绩报告卡(Report Card)PDF 导出
- **行业实践**:PowerSchool / Canvas 支持生成单生/班级 PDF 成绩报告卡,含学期汇总、排名、教师评语、签名区。
- **本模块现状**:仅支持 Excel 导出(`exportGradeRecordsToExcel`),无 PDF。
- **影响**:家长会、学期末场景需手动排版打印,效率低。
### 3.2 缺失:成绩变化智能预警
- **行业实践**:智学网提供"成绩大幅波动预警"(分数下降超过 10 分自动通知班主任 + 家长)。
- **本模块现状**:无预警机制,仅静态统计。
- **影响**:教师无法及时发现学习下滑学生,错过干预窗口。
### 3.3 缺失:知识点维度的成绩诊断
- **行业实践**:Canvas LMS 的 MasteryGradebook 按知识点(competency)展示掌握度,而非仅按考试/科目。
- **本模块现状**:成绩按科目/考试维度,虽 `grade_record_answers` 已存储每题得分,但未提供知识点聚合视图(依赖 diagnostic 模块但未集成到 grades 页面)。
- **影响**:教师无法在成绩页直接看到班级在哪个知识点薄弱。
### 3.4 缺失:学生纵向成长档案(Individual Growth Report)
- **行业实践**:PowerSchool 提供单生多年纵向趋势图,含跨学期/跨年级对比。
- **本模块现状**:`GradeTrendCard` 仅展示单学期/单科目趋势,无跨年级纵向档案。
- **影响**:家长无法全面了解学生长期成长轨迹。
### 3.5 缺失:班级横向对比的统计显著性标注
- **行业实践**:专业 BI 工具(如 Tableau 教育版)在班级对比时标注 p 值、效应量(Cohen's d)。
- **本模块现状**:`ClassComparisonChart` 有"显著性分析区域"(v3-P3-5 新增),但仅基于极差经验规则,未使用统计检验。
- **影响**:教师可能过度解读小样本差异。
### 3.6 缺失:成绩录入的草稿协同
- **行业实践**:Google Classroom 支持多位教师协同录入,草稿云端同步,锁定机制防止冲突。
- **本模块现状**:草稿仅单用户单设备(`grade_drafts` 表按 userId 唯一),无协同。
- **影响**:多教师任教同一班级时无法分工录入。
### 3.7 缺失:成绩申诉与复核流程
- **行业实践**:Canvas 支持学生对成绩提出申诉,教师复核后修改并留痕。
- **本模块现状**:仅 `updateGradeRecord`,无申诉状态机与审计轨迹。
- **影响**:成绩修改无留痕,合规性不足。
### 3.8 缺失:移动端适配深度优化
- **行业实践**:校宝在线移动端针对家长提供"成绩单卡片"简化视图,大表格转为卡片堆叠。
- **本模块现状**:`grade-record-list.tsx``overflow-x-auto`,但未做卡片堆叠适配;图表在移动端可能过小。
- **影响**:家长移动端查看体验一般。
### 3.9 缺失:成绩分布的隐私保护
- **行业实践**:GDPR/FERPA 合规要求学生视角的分布图应隐藏其他学生身份,仅显示相对位置。
- **本模块现状**:学生视角的 `GradeDistributionChart` 显示全班分布,虽未暴露个人身份,但需明确"你在分布中的位置"标注。
- **影响**:隐私合规风险(部分国家/地区)。
### 3.10 缺失:批量导入 Excel 成绩
- **行业实践**:智学网支持下载模板 → 填写 → 上传 Excel 批量导入。
- **本模块现状**:`batch-grade-entry.tsx` 支持下载 CSV 模板 + 粘贴录入,但无完整 Excel 上传解析。
- **影响**:教师需手动粘贴,大批量场景效率低。
---
## 四、改进优先级建议
### P0 — 立即修复(影响用户体验与合规)
| # | 问题 | 改进方向 |
|---|------|---------|
| P0-1 | 3 个 error.tsx 硬编码中文(2.1) | 改为 `useTranslations("grades")` + 新增 `error.*` 翻译键(zh-CN/en 同步) |
| P0-2 | 7 个 loading/error.tsx 函数缺返回值类型(2.2) | 补全 `: JSX.Element` 返回值类型标注 |
### P1 — 本周修复(架构规范与 i18n)
| # | 问题 | 改进方向 |
|---|------|---------|
| P1-1 | `actions.ts` 超 800 行(2.3) | 拆分草稿相关 3 个 Action 到 `actions-draft.ts`;拆分通知逻辑到 `lib/notify.ts` |
| P1-2 | `data-access.ts` 超 800 行(2.4) | 拆分草稿 CRUD 到 `data-access-drafts.ts`;拆分按试卷录入到 `data-access-exam-entry.ts` |
| P1-3 | `actions.ts` 多处英文错误消息未 i18n(2.5) | 新增 `action.error.*` 翻译键,所有错误消息改用 `t("action.error.*")` |
| P1-4 | `lib/scope-check.ts` 返回消息未 i18n(2.6) | 改为返回错误码枚举,由 Action 层翻译;或返回 `null` + Action 层自行抛错 |
| P1-5 | `actions-analytics.ts` scope 消息未 i18n(2.7) | 同 P1-3 |
| P1-6 | `batch-grade-entry.tsx` 超 500 行(2.8) | 拆分为 `BatchGradeEntryTable` + `BatchGradeEntryStats` + `BatchGradeEntryDialogs` 子组件 |
| P1-7 | `batch-grade-entry.tsx` 第 341 行 as 断言(2.9) | 新增 `isUndoData` 类型守卫函数 |
| P1-8 | `parent/grades/page.tsx` 第 63 行 as 断言(2.10) | 改用类型守卫 `isSummaryFulfilled` 或非空检查 |
| P1-9 | `teacher/grades/page.tsx` 列表与统计未 WidgetBoundary 包裹(2.16) | 用 `<WidgetBoundary>` 包裹独立区块 |
### P2 — 中期改进(质量与一致性)
| # | 问题 | 改进方向 |
|---|------|---------|
| P2-1 | "S" 前缀硬编码(2.11) | 新增 `semester.s1`/`semester.s2` 翻译键 |
| P2-2 | 组件函数缺返回值类型(2.12) | 补全 `: JSX.Element` / `: Promise<void>` |
| P2-3 | `updateGradeRecord` 类型弱(2.13) | 改用 `Partial<typeof gradeRecords.$inferInsert>` |
| P2-4 | `getStudentGradeSummary` 内部耦合 `getClassRanking`(2.14) | 将排名计算抽取到 action 层或 stats-service |
| P2-5 | 过滤器组件未抽象(2.15) | 抽象 `GradeFilterBar` 统一组件 + props 配置 |
| P2-6 | 撤销机制 sessionStorage 依赖(2.17) | 服务端存储撤销令牌 + 24 小时时间窗口 |
| P2-7 | 通知串行写入(2.18) | 新增 `notifications/data-access.bulkCreateNotification` 批量接口 |
| P2-8 | 图表 tooltip a11y(2.19) | 添加 `aria-describedby` + 旁路数据表(sr-only) |
| P2-9 | `lib/type-guards.ts` as 断言(2.20) | 改用 `Array.from(GRADE_TYPES).includes(v)` |
### P3 — 长期改进(行业对齐与企业级)
| # | 问题 | 改进方向 |
|---|------|---------|
| P3-1 | 缺 PDF 成绩报告卡(3.1) | 新增 `lib/report-card.ts`,基于 `@react-pdf/renderer` 生成 PDF |
| P3-2 | 缺成绩变化预警(3.2) | 新增 `stats-service.detectScoreAnomaly` 纯函数 + 通知触发 |
| P3-3 | 缺知识点维度诊断集成(3.3) | 在 analytics 页面集成 diagnostic 模块的知识点掌握度视图 |
| P3-4 | 缺学生纵向档案(3.4) | 新增 `getStudentGrowthArchive` data-access + `GrowthArchiveChart` 组件 |
| P3-5 | 缺统计显著性标注(3.5) | 引入 `simple-statistics` 库计算 p 值 + Cohen's d |
| P3-6 | ✅ 缺协同录入(3.6) | `grade_drafts` 表新增 `lockedBy`/`lockedAt`/`lockToken` 字段 + classSubjectLockIdx 索引支持锁机制data-access-drafts.ts 5 个锁函数 + actions-lock.ts 4 个 Server Action + use-draft-lock Hook + DraftLockBanner 组件 + i18n |
| P3-7 | ✅ 缺申诉流程(3.7) | 新增 `grade_appeals` 表(状态机枚举+4 个索引)+ data-access-appeals.ts7 个函数)+ actions-appeal.ts5 个 Server Action+ schema.ts Zod 校验 + i18n |
| P3-8 | 缺移动端卡片堆叠(3.8) | 新增 `GradeRecordCardList` 移动端组件 |
| P3-9 | 缺隐私保护位置标注(3.9) | 学生视角分布图添加"你的位置"标注 |
| P3-10 | 缺 Excel 上传批量导入(3.10) | 集成 `xlsx` 库,新增 `importGradesFromExcel` Action |
| P3-11 | 单元测试覆盖(2.14 关联) | 为 stats-service.ts 纯函数 + lib/scope-check.ts 补充 vitest 测试 |
### 架构图同步
| # | 改进内容 | 架构图更新 |
|---|---------|-----------|
| A-1 | P1-1 拆分 actions.ts | 004 文件清单 + 005 exports.actions 节点 |
| A-2 | P1-2 拆分 data-access.ts | 004 文件清单 + 005 exports.dataAccess 节点 |
| A-3 | P1-6 拆分 batch-grade-entry | 004 文件清单 + 005 exports.components 节点 |
| A-4 | 补全遗漏 | 004/005 补充 widget-boundary、scope-check.assertClassInScope、getClassAverageTrend |
---
## 五、架构图同步说明
### 5.1 本次审计发现的架构图遗漏(需补充)
1. **`components/widget-boundary.tsx`** — 在 004 § 2.6 文件清单中标注为 136 行,但未在 `exports.components` 节点中列出。需在 005 `modules.grades.exports.components` 数组中新增条目。
2. **`lib/scope-check.ts``assertClassInScope`** — 仅在 v4-P2-6 修复记录中提及,未在 `exports.lib` 节点列出。需补充。
3. **`getClassAverageTrend` data-access 函数** — `parent/grades/page.tsx` 调用但架构图 `exports.dataAccess` 未记录。需确认其在 `data-access-analytics.ts` 中的位置并补充。
### 5.2 重构后需同步的节点
- 若执行 P1-1(拆分 actions.ts):004 文件清单更新为 `actions.ts`(barrel)+ `actions-draft.ts` + `actions-exam-entry.ts`;005 `exports.actions` 按文件分组。
- 若执行 P1-2(拆分 data-access.ts):004 文件清单更新为 `data-access.ts` + `data-access-drafts.ts` + `data-access-exam-entry.ts`;005 `exports.dataAccess` 按文件分组。
- 若执行 P1-6(拆分 batch-grade-entry):004 文件清单新增 3 个子组件;005 `exports.components` 补充。
- P0/P1 i18n 修复无需更新架构图(翻译键不影响模块导出结构)。
---
## 六、实施计划
> 本节为审计文档的实施清单,按优先级执行,每步完成后运行 `npm run lint && npx tsc --noEmit` 验证零错误。
### 阶段 1:P0 立即修复(本次会话完成)
- [ ] P0-1: 3 个 error.tsx i18n(parent/grades、admin/school/grades、admin/school/grades/insights)
- [ ] P0-2: 7 个 loading/error.tsx 函数补全返回值类型
### 阶段 2:P1 本周修复(本次会话完成)
- [ ] P1-1: 拆分 `actions.ts``actions-draft.ts` + 通知抽取到 `lib/notify.ts`
- [ ] P1-2: 拆分 `data-access.ts``data-access-drafts.ts` + `data-access-exam-entry.ts`
- [ ] P1-3: `actions.ts` 错误消息 i18n(新增 `action.error.*` 翻译键)
- [ ] P1-4: `lib/scope-check.ts` 改为返回错误码枚举
- [ ] P1-5: `actions-analytics.ts` scope 消息 i18n
- [ ] P1-6: 拆分 `batch-grade-entry.tsx` 为子组件
- [ ] P1-7: `batch-grade-entry.tsx` 第 341 行 as 断言 → 类型守卫
- [ ] P1-8: `parent/grades/page.tsx` 第 63 行 as 断言 → 类型守卫
- [ ] P1-9: `teacher/grades/page.tsx` WidgetBoundary 包裹
### 阶段 3:P2 中期改进(本次会话完成)
- [ ] P2-1: "S" 前缀 i18n
- [ ] P2-2: 组件函数返回值类型补全
- [ ] P2-3: `updateGradeRecord` 类型强化
- [ ] P2-4: 解耦 `getStudentGradeSummary``getClassRanking`
- [ ] P2-5: 过滤器组件抽象(评估后决定是否实施)
- [ ] P2-6: 撤销机制服务端持久化(评估后决定)
- [ ] P2-7: 通知批量接口(依赖 notifications 模块改造,标注为后续)
- [ ] P2-8: 图表 tooltip a11y
- [ ] P2-9: `lib/type-guards.ts` as 断言修复
### 阶段 4:P3 长期改进(记录待排期,不在本次会话实施)
- [ ] P3-1 ~ P3-10:行业差距对齐(需产品评估与设计)
- [ ] P3-11:单元测试覆盖
### 阶段 5:架构图同步(本次会话完成)
- [ ] A-1 ~ A-4:补充 004/005 遗漏节点 + 重构后节点更新