# 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 层分页) └─▶ + + teacher/grades/entry/page.tsx └─▶ requirePermission(GRADE_RECORD_MANAGE) └─▶ └─▶ 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 └─▶ + + ``` ### 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 表格 ``、色盲友好双重编码 - ✅ 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`"。 - **原因**: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 ``` - **违反规则**:同 2.9。 - **原因**:虽有前置 `filter(r => r.value.summary)` 保证,但严格违反"禁止 as 断言"规则。 - **直接后果**:类型系统对运行时假设依赖,filter 逻辑变更时可能失效。 ### 2.11 [P2] `student-grade-summary.tsx` / `grade-record-list.tsx` 硬编码 "S" 前缀 两文件均使用 `S{r.semester}` 硬编码 "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` 累积更新字段。 - **违反规则**: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) | 用 `` 包裹独立区块 | ### P2 — 中期改进(质量与一致性) | # | 问题 | 改进方向 | |---|------|---------| | P2-1 | "S" 前缀硬编码(2.11) | 新增 `semester.s1`/`semester.s2` 翻译键 | | P2-2 | 组件函数缺返回值类型(2.12) | 补全 `: JSX.Element` / `: Promise` | | P2-3 | `updateGradeRecord` 类型弱(2.13) | 改用 `Partial` | | 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 遗漏节点 + 重构后节点更新