458 lines
26 KiB
Markdown
458 lines
26 KiB
Markdown
# 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.ts(7 个函数)+ actions-appeal.ts(5 个 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 遗漏节点 + 重构后节点更新
|