# 班级（classes）模块审计报告

> 审计时间：2026-06-25
> 审计范围：`src/modules/classes/` 全部 32 个文件 + `src/app/(dashboard)/` 下 11 个相关路由分组
> 架构图依据：`docs/architecture/004_architecture_impact_map.md` §2.7、`docs/architecture/005_architecture_data.json` modules.classes 节点
> 审计基线：项目规则（三层架构、权限校验、i18n、TypeScript 严格模式、文件行数、可测试性）

## 一、现有实现概要

### 1.1 文件分布

classes 模块位于 `src/modules/classes/`，共 **32 个文件**：

| 层 | 文件数 | 关键文件 |
|---|---|---|
| Server Actions | 7 | `actions.ts`（barrel）+ `actions-admin.ts` / `actions-grade.ts` / `actions-teacher.ts` / `actions-invitations.ts` / `actions-schedule.ts` / `actions-shared.ts` |
| Data-access | 7 | `data-access.ts`（聚合）+ `data-access-admin.ts` / `data-access-teacher.ts` / `data-access-students.ts` / `data-access-stats.ts` / `data-access-schedule.ts` / `data-access-invitations.ts` |
| Schema/Types | 2 | `schema.ts`（13 个 Zod schema）、`types.ts`（19 个类型） |
| Components | 21 | `admin-classes-view.tsx` / `grade-classes-view.tsx` / `class-list-table.tsx` / `class-form-dialog.tsx` / `class-delete-dialog.tsx` / `class-list-toolbar.tsx` / `class-form-utils.ts` / `class-error-boundary.tsx` / `class-invitation-manager.tsx` / `class-skeleton.tsx` / `my-classes-grid.tsx` / `schedule-view.tsx` / `schedule-filters.tsx` / `students-filters.tsx` / `students-table.tsx` + `class-detail/` 下 7 个 widget（header/overview-stats/quick-actions/schedule-widget/students-widget/assignments-widget/trends-widget）+ `class-detail/edit-class-dialog.tsx` |
| Hooks | 2 | `use-class-data.ts`、`use-class-filters.ts` |

### 1.2 主要数据流

```
admin/school/classes        ─┐
management/grade/classes    ─┤  ─▶ actions-admin / actions-grade
                              │     │
teacher/classes/my          ─┤     ▼
teacher/classes/schedule    ─┤  data-access-admin / data-access-teacher
teacher/classes/students    ─┤     │
                              │     ▼  (跨模块通过对方 data-access)
student/learning/courses    ─┤  homework/data-access-classes（作业统计）
student/schedule            ─┤  scheduling/data-access-class-schedule（课表写）
parent/children/[id]        ─┘  school/data-access（年级管理权限校验）
```

### 1.3 架构图覆盖完整性评估

| 维度 | 004 文档 | 005 JSON | 一致性 |
|---|---|---|---|
| 模块职责 | ✅ §2.7 | ✅ modules.classes.description | 一致 |
| 依赖关系（出向） | ✅ shared/auth/school/homework/scheduling | ✅ dependencyMatrix 5 条边 | 一致 |
| 被依赖关系（入向） | ⚠️ 列出 10 个，遗漏 elective/error-book/adaptive-practice | ✅ 10 条边覆盖完整 | **不一致** |
| 导出函数 | ✅ 17 actions + 33 data-access | ⚠️ exports.actions 漏 3 个邀请码 action；exports.dataAccess 漏 6 个工具函数 | **不一致** |
| 数据库表 | ✅ classes/classSubjectTeachers/classEnrollments/classInvitationCodes | ⚠️ classInvitationCodes 字段未详细登记；classSchedule 的 usedBy 字段未含 scheduling | **部分遗漏** |
| 权限点 | ✅ 6 个 CLASS_* 权限 | ✅ permissions 常量定义完整 | 一致 |
| 路由 | ✅ 11 条路由登记 | ✅ routes 节点登记 | 一致 |
| 文件清单 | ✅ 33 个文件 | ⚠️ modules.classes.files 仅列 21 个，漏 12 个组件文件 | **不一致** |

**结论**：架构图总体覆盖较完整，但存在 7 处需同步更新（详见第五章）。

## 二、现存问题与原因分析

### 2.1 三层架构合规性

#### 问题 A1：data-access.ts 与拆分文件 3 对同名函数重复定义（🔴 P0）

- **位置**：
  - [data-access.ts:400](file:///e:/Desktop/CICD/src/modules/classes/data-access.ts#L400) `getTeacherScopeData` ↔ [data-access-teacher.ts:592](file:///e:/Desktop/CICD/src/modules/classes/data-access-teacher.ts#L592)
  - [data-access.ts:428](file:///e:/Desktop/CICD/src/modules/classes/data-access.ts#L428) `getStudentScopeData` ↔ [data-access-students.ts:309](file:///e:/Desktop/CICD/src/modules/classes/data-access-students.ts#L309)
  - [data-access.ts:455](file:///e:/Desktop/CICD/src/modules/classes/data-access.ts#L455) `getGradeIdsForStudentIds` ↔ [data-access-students.ts:336](file:///e:/Desktop/CICD/src/modules/classes/data-access-students.ts#L336)
- **现状**：`data-access.ts:383-386` 同时 `export * from "./data-access-*"` 与本地 `export const`，ES 模块语义下本地定义优先，子文件同名导出对聚合入口而言是死代码。
- **违反规则**：架构分层规则「data-access 拆分应避免职责重叠」+ DRY 原则。
- **后果**：两份实现目前逻辑一致，但任何一方修改都不会自动同步。若消费者直接 `import { getStudentScopeData } from "@/modules/classes/data-access-students"`，会得到另一份实现，是高风险维护陷阱。

#### 问题 A2：组件使用绝对路径导入本模块 actions（🟢 P2）

- **位置**：[class-invitation-manager.tsx:30-32](file:///e:/Desktop/CICD/src/modules/classes/components/class-invitation-manager.tsx#L30)
- **现状**：`import { createClassInvitationCodeAction } from "@/modules/classes/actions"`，而 `my-classes-grid.tsx`、`admin-classes-view.tsx` 等同模块其他组件使用 `"../actions"` 相对路径。
- **违反规则**：编码规范一致性。
- **后果**：模块迁移/重命名成本上升。

### 2.2 权限校验

#### 问题 B1：listClassInvitationCodesAction 无班级归属校验（🔴 P0 越权漏洞）

- **位置**：[actions-invitations.ts:312-351](file:///e:/Desktop/CICD/src/modules/classes/actions-invitations.ts#L312)
- **现状**：
  ```ts
  export async function listClassInvitationCodesAction(classId: string) {
    await requirePermission(Permissions.CLASS_ENROLL)
    // ❌ 任何拥有 CLASS_ENROLL 权限的用户都可以列出任意 classId 的所有邀请码
    const codes = await listClassInvitationCodes(classId)
  }
  ```
- **违反规则**：安全规范「data-access 查询是否结合当前用户权限过滤（防越权）」+ Server Action 规范。
- **后果**：教师 A 可通过传入教师 B 的 classId 枚举其邀请码（含 code 字符串），可能引发越权获取加入凭证，破坏邀请码体系的安全性。

#### 问题 B2：3 个 schedule action 无班级归属校验（🔴 P0 越权漏洞）

- **位置**：[actions-schedule.ts:21-59](file:///e:/Desktop/CICD/src/modules/classes/actions-schedule.ts#L21)（create）、[61-101](file:///e:/Desktop/CICD/src/modules/classes/actions-schedule.ts#L61)（update）、[103-122](file:///e:/Desktop/CICD/src/modules/classes/actions-schedule.ts#L103)
- **现状**：3 个 action 仅调用 `requirePermission(CLASS_SCHEDULE)` 后直接调用 scheduling 模块 data-access，未校验当前用户对 `classId` 的归属。
- **违反规则**：安全规范越权防护。
- **后果**：教师 A 可为教师 B 的班级添加/修改/删除课表项，破坏排课数据完整性。

#### 问题 B3：教师 update/delete/enroll action 未在 actions 层做归属校验（🟡 P1）

- **位置**：[actions-teacher.ts:79-122](file:///e:/Desktop/CICD/src/modules/classes/actions-teacher.ts#L79)（update）、[125-146](file:///e:/Desktop/CICD/src/modules/classes/actions-teacher.ts#L125)（delete）、[actions-invitations.ts:21-49](file:///e:/Desktop/CICD/src/modules/classes/actions-invitations.ts#L21)、[353-377](file:///e:/Desktop/CICD/src/modules/classes/actions-invitations.ts#L353)、[383-440](file:///e:/Desktop/CICD/src/modules/classes/actions-invitations.ts#L383)
- **现状**：actions 层仅 `requirePermission`，依赖 data-access 内部 `getTeacherIdForMutations()` + `eq(classes.teacherId, teacherId)` 校验。
- **违反规则**：Server Action 规范「权限校验应在 actions 层完成」。
- **后果**：管理员（拥有 CLASS_UPDATE 权限）调用时，因 data-access 内部强制按 teacherId 过滤而失败，错误信息为 "Teacher not found" 不友好；如未来重构 data-access 暴露 admin 路径，归属校验会被跳过。

#### 问题 B4：data-access 中 7 处硬编码角色名字符串（🟡 P1）

- **位置**：
  - [data-access.ts:26](file:///e:/Desktop/CICD/src/modules/classes/data-access.ts#L26) `eq(roles.name, "teacher")`
  - [data-access-admin.ts:346](file:///e:/Desktop/CICD/src/modules/classes/data-access-admin.ts#L346)、[425](file:///e:/Desktop/CICD/src/modules/classes/data-access-admin.ts#L425)
  - [data-access-teacher.ts:125](file:///e:/Desktop/CICD/src/modules/classes/data-access-teacher.ts#L125)、[308](file:///e:/Desktop/CICD/src/modules/classes/data-access-teacher.ts#L308)、[472](file:///e:/Desktop/CICD/src/modules/classes/data-access-teacher.ts#L472)、[545](file:///e:/Desktop/CICD/src/modules/classes/data-access-teacher.ts#L545)
- **违反规则**：命名规范「常量：UPPER_SNAKE_CASE」+ DRY。
- **后果**：若角色名变更（中英切换、复数化），需修改 7 处；魔法字符串降低可读性。

#### 问题 B5：student 三个页面未调用 requirePermission 显式声明权限点（🟡 P1）

- **位置**：
  - [student/learning/courses/page.tsx:19](file:///e:/Desktop/CICD/src/app/(dashboard)/student/learning/courses/page.tsx#L19)
  - [student/learning/courses/[classId]/page.tsx:40-41](file:///e:/Desktop/CICD/src/app/(dashboard)/student/learning/courses/[classId]/page.tsx#L40)
  - [student/schedule/page.tsx:19](file:///e:/Desktop/CICD/src/app/(dashboard)/student/schedule/page.tsx#L19)
- **现状**：仅 `getCurrentStudentUser()` 软校验学生身份，未显式声明所需权限点；`[classId]` 页面在权限不足时返回 `notFound()`，错误语义混淆（404 vs 403）。
- **违反规则**：Server Action 必须使用 `requirePermission()`。
- **后果**：权限审计与文档化困难；404/403 错误语义混淆影响用户体验与监控告警。

### 2.3 国际化（i18n）

#### 问题 C1：class-detail/ 子组件普遍硬编码英文（🟡 P1）

- **位置**：
  - [class-assignments-widget.tsx:40,46,64,84,88,101](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/class-assignments-widget.tsx#L40)
  - [class-overview-stats.tsx:28,33,39,45](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/class-overview-stats.tsx#L28)
  - [class-quick-actions.tsx:26,32,36](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/class-quick-actions.tsx#L26)
  - [class-schedule-widget.tsx:17,102,110](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/class-schedule-widget.tsx#L17)
  - [class-students-widget.tsx:36,41](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/class-students-widget.tsx#L36)
  - [class-trends-widget.tsx:41-55,145,164,174,182,188,282,288,301,392](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/class-trends-widget.tsx#L41)
- **现状**：8 个详情子组件几乎全部使用硬编码英文字符串。
- **违反规则**：i18n 强制要求 + 架构图 004:998 已声明"13 个组件全部接入 i18n"——与现状不一致。
- **后果**：详情页完全无法中文化，与 K12 中文产品定位严重冲突；架构图存在错误声明。

#### 问题 C2：schedule-view / schedule-filters / students-filters 硬编码英文（🟡 P1）

- **位置**：
  - [schedule-view.tsx:111,117,165,166,180,192,310,343,348,357,369,386,435,448,469,489,505-508](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-view.tsx#L111)
  - [schedule-filters.tsx:111,117,164,171,180,192](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-filters.tsx#L111)
  - [students-filters.tsx:81,110,141,169,173,174,197,204,211](file:///e:/Desktop/CICD/src/modules/classes/components/students-filters.tsx#L81)
- **违反规则**：i18n 强制要求。
- **后果**：教师端课表/学生管理页全部英文，破坏产品一致性。

#### 问题 C3：所有 actions 返回的 message 为英文硬编码（🟡 P1）

- **位置**：全部 7 个 actions 文件
- **示例**：[actions-admin.ts:39](file:///e:/Desktop/CICD/src/modules/classes/actions-admin.ts#L39) `"Class name, grade and teacher are required"` / [actions-admin.ts:59](file:///e:/Desktop/CICD/src/modules/classes/actions-admin.ts#L59) `"Class created successfully"` / [actions-invitations.ts:432](file:///e:/Desktop/CICD/src/modules/classes/actions-invitations.ts#L432) `` `Imported ${imported} students, ${failed} failed` ``
- **现状**：组件层 `toast.success(res.message)` 直接显示后端字符串。
- **违反规则**：i18n 强制要求。
- **后果**：中文用户看到全英文错误提示，体验差。

#### 问题 C4：30+ 个 error.tsx 使用硬编码中文文案（🟡 P1）

- **位置**：`src/app/(dashboard)/` 下 30 处 error.tsx（含 `admin/school/classes/error.tsx`、`management/grade/classes/error.tsx` 等）
- **示例**：[admin/school/classes/error.tsx:12-16](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/school/classes/error.tsx#L12) `title="页面加载失败"` / `description="抱歉，页面加载时发生了意外错误。请稍后重试。"`
- **违反规则**：i18n 强制要求。
- **后果**：英文用户在错误页仍看到中文，破坏语言一致性；30+ 处重复字符串维护成本高。

#### 问题 C5：DEFAULT_CLASS_SUBJECTS 与 excludeSubjects 业务常量硬编码中文（🟡 P1）

- **位置**：
  - [types.ts:46](file:///e:/Desktop/CICD/src/modules/classes/types.ts#L46) `export const DEFAULT_CLASS_SUBJECTS = ["语文", "数学", "英语", "美术", "体育", "科学", "社会", "音乐"] as const`
  - [data-access-students.ts:41](file:///e:/Desktop/CICD/src/modules/classes/data-access-students.ts#L41) `const excludeSubjects = ["体育", "音乐", "美术"]`
- **违反规则**：DRY（两份科目清单）+ i18n（业务规则硬编码字符串）。
- **后果**：与 `subjects` 表查询重复；不同学校配置无法适配；`excludeSubjects` 未在架构图记录。

#### 问题 C6：getSubjectColor 用英文匹配中文科目（🔴 P0 功能 bug）

- **位置**：[schedule-view.tsx:186-195](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-view.tsx#L186)
- **现状**：
  ```ts
  const getSubjectColor = (subject: string) => {
    const s = subject.toLowerCase()
    if (s.includes('math')) return 'bg-blue-500/10 ...'
    if (s.includes('physics') || s.includes('science')) return '...'
    if (s.includes('english') || s.includes('lit')) return '...'
  ```
- **问题**：`DEFAULT_CLASS_SUBJECTS` 是中文（"数学"、"英语"等），但 `getSubjectColor` 用英文 'math'/'english' 匹配，所有中文科目都会落到 default 分支，颜色视觉分组完全失效。
- **违反规则**：i18n + 功能正确性。
- **后果**：课表颜色视觉分组完全失效，不仅是 i18n 问题。

### 2.4 错误处理

#### 问题 D1：catch 块未记录错误，仅显示 toast（🟡 P1）

- **位置**：10 处
  - [my-classes-grid.tsx:75-77](file:///e:/Desktop/CICD/src/modules/classes/components/my-classes-grid.tsx#L75)
  - [schedule-filters.tsx:65-67](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-filters.tsx#L65)
  - [schedule-view.tsx:113-115](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-view.tsx#L113)
  - [students-filters.tsx:73-75](file:///e:/Desktop/CICD/src/modules/classes/components/students-filters.tsx#L73)
  - [admin-classes-view.tsx:52-54](file:///e:/Desktop/CICD/src/modules/classes/components/admin-classes-view.tsx#L52)
  - [grade-classes-view.tsx:42-44](file:///e:/Desktop/CICD/src/modules/classes/components/grade-classes-view.tsx#L42)
  - [class-invitation-manager.tsx:101-103](file:///e:/Desktop/CICD/src/modules/classes/components/class-invitation-manager.tsx#L101)、[263-265](file:///e:/Desktop/CICD/src/modules/classes/components/class-invitation-manager.tsx#L263)
  - [edit-class-dialog.tsx:55-57](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/edit-class-dialog.tsx#L55)
  - [students-table.tsx:41-43](file:///e:/Desktop/CICD/src/modules/classes/components/students-table.tsx#L41)
- **现状**：`} catch { toast.error(t("list.failedCreate")) }`
- **违反规则**：错误处理最佳实践。
- **后果**：服务端 500、网络错误、权限错误全部显示同一文案，开发者无法从用户截图定位错误；线上排查困难。

#### 问题 D2：data-access 内 catch 静默返回空数组（🟡 P1）

- **位置**：
  - [data-access-admin.ts:88-124](file:///e:/Desktop/CICD/src/modules/classes/data-access-admin.ts#L88)（getAdminClasses fallback 合理降级）
  - [data-access-students.ts:159-177](file:///e:/Desktop/CICD/src/modules/classes/data-access-students.ts#L159)（getStudentClasses fallback 合理降级）
  - [data-access-teacher.ts:70-73](file:///e:/Desktop/CICD/src/modules/classes/data-access-teacher.ts#L70)（getTeacherClasses 直接返回 `[]`）
- **问题**：`getTeacherClasses` 失败时返回空数组，会让教师看到"无班级"假象，区分不出"数据库错误"与"无数据"。
- **违反规则**：错误处理最佳实践。
- **后果**：教师班级列表假性空数据，难以排查。

#### 问题 D3：actions 层错误处理两套风格混用（🟡 P1）

- **位置**：
  - 风格 A（手动 try/catch + `PermissionDeniedError`）：[actions-admin.ts:63-66](file:///e:/Desktop/CICD/src/modules/classes/actions-admin.ts#L63)、[actions-grade.ts](file:///e:/Desktop/CICD/src/modules/classes/actions-grade.ts)、[actions-invitations.ts](file:///e:/Desktop/CICD/src/modules/classes/actions-invitations.ts)
  - 风格 B（`handleActionError`）：[actions-teacher.ts:74-76](file:///e:/Desktop/CICD/src/modules/classes/actions-teacher.ts#L74)、[actions-schedule.ts](file:///e:/Desktop/CICD/src/modules/classes/actions-schedule.ts)
- **违反规则**：编码规范一致性。
- **后果**：权限拒绝场景下风格 A 会 `throw e` 冒泡到 Next.js 错误边界（用户体验差），风格 B 会转为结构化失败；维护成本高。

#### 问题 D4：teacher/classes/my/[id] 缺 loading.tsx 和 error.tsx（🟡 P1）

- **位置**：`src/app/(dashboard)/teacher/classes/my/[id]/`
- **现状**：页面内部 4 个 `Promise.all` 并行数据获取，但完全没有 loading.tsx 和 error.tsx。
- **违反规则**：项目规则「学生路由必须包含 loading.tsx 和 error.tsx」+ 企业级规范。
- **后果**：无骨架屏感知性能差；任一 data-access 抛错冒泡至上层；`notFound()` 触发时展示默认 404 与站点风格不一致。

#### 问题 D5：10 个页面缺 error.tsx（🟡 P1）

- **位置**：teacher/classes/my、teacher/classes/schedule、teacher/classes/students、student/learning/courses、student/learning/courses/[classId]、student/schedule 等
- **违反规则**：企业级规范要求主要路由必须有 error.tsx。
- **后果**：错误上下文丢失，错误冒泡至上层。

### 2.5 类型安全

#### 问题 E1：formData.get 强转 string 应使用类型守卫（🟢 P2）

- **位置**：[actions-admin.ts:93](file:///e:/Desktop/CICD/src/modules/classes/actions-admin.ts#L93)、[actions-grade.ts:98](file:///e:/Desktop/CICD/src/modules/classes/actions-grade.ts#L98)
- **现状**：`formData.get("subjectTeachers") as string | null`
- **违反规则**：禁止 `as` 断言（除类型收窄外）。
- **后果**：若前端意外提交 `File` 对象（如通过 FormData.append 上传文件），`parseSubjectTeachers` 会因 `typeof raw !== "string"` 返回 null 而静默丢弃数据。

#### 问题 E2：data-access-invitations.ts 中两处 string → union 强转（🟢 P2）

- **位置**：[data-access-invitations.ts:274](file:///e:/Desktop/CICD/src/modules/classes/data-access-invitations.ts#L274)、[363](file:///e:/Desktop/CICD/src/modules/classes/data-access-invitations.ts#L363)
- **现状**：`record.status as ValidationResult["reason"]` / `status: row.status as InvitationCodeStatus`
- **违反规则**：禁止 `as` 断言。
- **后果**：DB 中出现意外值（如 "pending"）不会触发类型错误，运行时返回错误 reason。

#### 问题 E3：class-trends-widget.tsx 中 as string[] 应使用类型守卫（🟢 P2）

- **位置**：[class-trends-widget.tsx:129](file:///e:/Desktop/CICD/src/modules/classes/components/class-detail/class-trends-widget.tsx#L129)
- **现状**：`Array.from(new Set(assignments.map(a => a.subject).filter(Boolean))) as string[]`
- **违反规则**：禁止 `as` 断言。
- **后果**：`filter(Boolean)` 在 TypeScript 中不会收窄类型，跳过空值检查。

#### 问题 E4：14 个组件事件处理函数缺 Promise<void> 返回类型（🟢 P2）

- **位置**：my-classes-grid.tsx（4 处）、schedule-filters.tsx、schedule-view.tsx（4 处）、students-filters.tsx、students-table.tsx、class-invitation-manager.tsx（3 处）、edit-class-dialog.tsx
- **违反规则**：函数返回值必须显式标注，特别是 `Promise<T>`。
- **后果**：若将来函数内部 `return` 一个值（如返回 boolean 表示是否成功），调用方无类型提示。

### 2.6 文件大小

#### 问题 F1：schedule-view.tsx 527 行超出组件 500 行建议（🟡 P1）

- **位置**：[schedule-view.tsx](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-view.tsx)
- **现状**：同时承担"周历视图渲染 + 创建对话框 + 编辑对话框 + 删除确认对话框"4 个职责。
- **违反规则**：React 组件 ≤ 500 行。
- **后果**：可读性差、修改易引入回归。

#### 问题 F2：my-classes-grid.tsx 内含 210 行巨型组件 ClassTicket（🟢 P2）

- **位置**：[my-classes-grid.tsx:208-417](file:///e:/Desktop/CICD/src/modules/classes/components/my-classes-grid.tsx#L208)
- **现状**：单一组件管理 4 段视觉职责（票据左侧信息+邀请码+趋势图+周历嵌入式渲染）。
- **违反规则**：组件规范。
- **后果**：调试困难。

### 2.7 组件复用性

#### 问题 G1：3 个 view 的 CRUD handler 几乎重复（🟢 P2）

- **位置**：admin-classes-view.tsx:41-95、grade-classes-view.tsx:31-85、schedule-view.tsx:100-158
- **现状**：相同的 try/catch + toast + router.refresh 模式重复 9 次（每个 view 3 个 handler）。
- **违反规则**：DRY。
- **后果**：错误处理改进（如 D1 加 console.error）需修改 9 处。

### 2.8 可测试性

#### 问题 H1：纯函数内嵌组件未导出（🟢 P2）

- **位置**：
  - [my-classes-grid.tsx:40-48](file:///e:/Desktop/CICD/src/modules/classes/components/my-classes-grid.tsx#L40) `getSeededValue`
  - [my-classes-grid.tsx:268-272](file:///e:/Desktop/CICD/src/modules/classes/components/my-classes-grid.tsx#L268) `performanceChange` 计算
  - [schedule-view.tsx:160-181](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-view.tsx#L160) `getPositionStyle`
  - [schedule-view.tsx:186-195](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-view.tsx#L186) `getSubjectColor`
- **违反规则**：可测试性「纯逻辑是否与 UI 分离」。
- **后果**：业务计算逻辑（环比变化率、课表块定位、颜色映射）无法独立单测；`getSubjectColor` 还存在 C6 提到的功能 bug，但因内嵌而难以被发现。

### 2.9 i18n metadata 缺失

#### 问题 I1：8 个 teacher/student 页面缺 generateMetadata（🟢 P2）

- **位置**：teacher/classes/my、teacher/classes/my/[id]、teacher/classes/schedule、teacher/classes/students、student/learning/courses、student/learning/courses/[classId]、student/schedule、parent/children/[studentId]
- **违反规则**：页面级 metadata.title 应走 i18n。
- **后果**：浏览器标签栏、社交分享卡片等场景文案不本地化，SEO 友好度差。

## 三、行业差距对比

参考 Google Classroom、钉钉教育、智学网、ClassDojo、PowerSchool 等 K12 班级管理产品的主流设计模式，对比当前实现差距：

### 3.1 班级列表与详情

| 维度 | 优秀实践 | 当前实现 | 差距 |
|---|---|---|---|
| 班级卡片信息密度 | Google Classroom 卡片含教师头像、学生数、最近活动时间、未读作业数 | `my-classes-grid.tsx` 含邀请码、提交率趋势、周历嵌入，信息密度高但**未提供"最近活动"时间线** | 缺少"班级最近动态"feed |
| 班级封面图 | Google Classroom / ClassDojo 支持自定义班级主题图 | 仅色彩区分 | 视觉识别度弱 |
| 班级详情布局 | 智学网采用"Tab 切换（学生/作业/成绩/课表/设置）+ 顶部 sticky header" | `class-detail/` 采用 widget 网格布局 | widget 平铺在班级数多时滚动疲劳；可考虑 Tab 化 |
| 班级归档 | Google Classroom 支持"归档班级"，归档后只读但保留数据 | 无归档功能 | 学年结束后历史班级污染列表 |
| 班级复制 | Google Classroom 支持复制班级（含学生/科目配置） | 无 | 新学年建班成本高 |

### 3.2 学生管理

| 维度 | 优秀实践 | 当前实现 | 差距 |
|---|---|---|---|
| 学生加入方式 | 邀请码 + 邮件 + 批量导入 + 班级链接 | 已实现邀请码 + 邮件 + 批量导入 | 缺少"班级加入链接"（点击即加入） |
| 学生列表筛选 | 智学网支持按科目成绩、出勤率、活跃度多维筛选 | `students-filters.tsx` 仅按班级 + 状态 | 缺少按学业表现筛选 |
| 学生卡片信息 | Google Classroom 显示学生头像、最近提交、整体进度 | `students-table.tsx` 显示头像+科目成绩 | 缺少"最近提交/整体进度"时间维度 |
| 学生迁移 | 钉钉教育支持"批量迁班"（学年升级时） | 无 | 学年升级时手动逐个调整 |
| 学生邀请码状态可视化 | ClassDojo 显示邀请码扫描情况（已加入/待加入） | `class-invitation-manager.tsx` 显示邀请码列表 | 缺少"已扫码未加入"中间状态 |

### 3.3 课表

| 维度 | 优秀实践 | 当前实现 | 差距 |
|---|---|---|---|
| 课表视图 | 智学网周课表 + 日课表 + 月课表三视图切换 | `schedule-view.tsx` 仅周课表 | 缺少日/月视图 |
| 课表冲突检测 | PowerSchool 在添加时自动检测教室/教师/时段冲突 | 依赖 scheduling 模块外部检测 | 当前 actions-schedule 未触发检测（B2） |
| 课表颜色编码 | 钉钉教育按科目自动配色 | `getSubjectColor` 中文失效（C6） | **功能 bug，必须修复** |
| 课表导出/打印 | 智学网支持导出 PDF/Excel、打印 | 无 | 教师打印课表需求未满足 |
| 课表提醒 | Google Classroom 课前 5 分钟推送提醒 | 无 | 缺少课表提醒集成 |

### 3.4 多角色协作

| 维度 | 优秀实践 | 当前实现 | 差距 |
|---|---|---|---|
| 家长视角班级信息 | ClassDojo 家长看到班级公告、教师动态、孩子表现 | parent 仅看到孩子课表 + 班级概览 | 缺少"班级动态 feed" |
| 学生视角班级首页 | Google Classroom 学生首页是"待办作业流" | student/learning/courses 是班级列表 | 缺少"班级作业待办流"聚合视图 |
| 跨班级协作 | 钉钉教育支持"年级主任一键查看所有班级对比" | management/grade 已有 insights | ✅ 已实现，对比图较完善 |
| 班级消息 | Google Classroom 班级内消息流 | messaging 模块支持 `class_members` scope | ✅ 已通过 messaging 模块实现 |

### 3.5 数据洞察

| 维度 | 优秀实践 | 当前实现 | 差距 |
|---|---|---|---|
| 班级健康度评分 | PowerSchool 综合出勤+成绩+参与度给出班级健康分 | `class-trends-widget.tsx` 仅展示提交率/平均分 | 缺少综合健康度评分 |
| 早期预警 | ClassDojo 识别"低参与度学生"自动预警 | 无 | 缺少学生风险预警 |
| 班级对比 | 智学网支持同年级班级多维度对比 | `management/grade/insights` 已有 | ✅ |
| 趋势同比环比 | PowerSchool 提供周/月/学期同比 | `class-trends-widget.tsx` 仅"Latest"指标 | 缺少时间维度对比 |

### 3.6 可访问性与性能

| 维度 | 优秀实践 | 当前实现 | 差距 |
|---|---|---|---|
| 键盘导航 | WCAG 2.1 AA 要求所有交互可键盘操作 | 班级列表/课表键盘可访问但无 focus-visible 样式优化 | a11y 待加强 |
| 流式渲染 | React 18+ Suspense 流式渲染 | 全部 RSC 同步获取，无 Suspense 边界 | 班级详情 4 个并行查询可流式 |
| 骨架屏精确度 | 骨架屏应反映实际布局 | `class-skeleton.tsx` 5 个 skeleton 布局匹配 | ✅ 良好 |

## 四、改进优先级建议

### P0 — 安全/功能阻断（立即修复）

| # | 问题 | 改进方向 |
|---|---|---|
| P0-1 | A1：data-access.ts 与拆分文件 3 对同名函数重复定义 | 删除 data-access.ts 中的本地定义，统一从拆分文件导出（保持 barrel 入口兼容） |
| P0-2 | B1：listClassInvitationCodesAction 无归属校验 | 在 actions 层调用 `verifyTeacherOwnsClass(classId, ctx.userId)`（admin scope 跳过） |
| P0-3 | B2：3 个 schedule action 无归属校验 | 同 P0-2，调用 `verifyTeacherOwnsClass` |
| P0-4 | C6：getSubjectColor 中文科目不匹配 | 改用科目 ID 或类型守卫匹配；纯函数抽出到 `schedule-utils.ts` 并补单测 |
| P0-5 | B3：教师 update/delete/enroll action 未在 actions 层做归属校验 | actions 层显式判断 `hasAdminScope(ctx)` 否则校验 `classes.teacherId === ctx.userId` |

### P1 — 重要合规性（本期实施）

| # | 问题 | 改进方向 |
|---|---|---|
| P1-1 | C1：class-detail/ 8 子组件硬编码英文 | 全部接入 `useTranslations("classes.detail.*")` |
| P1-2 | C2：schedule-view / schedule-filters / students-filters 硬编码英文 | 接入 i18n |
| P1-3 | C3：所有 actions message 英文硬编码 | 在 actions 层使用 `getTranslations()` 或返回错误码由组件层翻译 |
| P1-4 | C4：30+ error.tsx 硬编码中文 | 抽取 `shared/components/ErrorState` + i18n key `common.error.boundary.*` |
| P1-5 | C5：DEFAULT_CLASS_SUBJECTS 与 excludeSubjects 硬编码 | 改为从 `subjects` 表查询；统一单一来源 |
| P1-6 | B4：data-access 中 7 处 roles.name 硬编码 | 抽出 `ROLE_TEACHER` 常量 |
| P1-7 | B5：student 三个页面未调用 requirePermission | 补 `requirePermission(Permissions.HOMEWORK_SUBMIT)` 或新增 STUDENT_READ 权限 |
| P1-8 | D1：10 处 catch 块未记录错误 | 改为 `catch (error) { console.error("[classes] xxx:", error); toast.error(...) }` |
| P1-9 | D2：getTeacherClasses 静默返回空数组 | 区分"DB 错误"与"无数据"，DB 错误抛出 |
| P1-10 | D3：actions 层错误处理两套风格 | 统一为 `handleActionError` |
| P1-11 | D4：teacher/classes/my/[id] 缺 loading.tsx 和 error.tsx | 新增 |
| P1-12 | D5：10 个页面缺 error.tsx | 补齐 |
| P1-13 | F1：schedule-view.tsx 527 行超限 | 抽出 3 个对话框子组件 |

### P2 — 工程优化（中长期）

| # | 问题 | 改进方向 |
|---|---|---|
| P2-1 | E1/E2/E3：5 处 as 断言 | 改用类型守卫 |
| P2-2 | E4：14 个事件处理函数缺 Promise<void> | 补返回类型 |
| P2-3 | G1：3 个 view CRUD handler 重复 | 抽 `useClassFormHandlers` hook |
| P2-4 | H1：纯函数内嵌组件 | 抽到 `class-stats-utils.ts` / `schedule-utils.ts` 并补单测 |
| P2-5 | F2：my-classes-grid ClassTicket 210 行 | 拆分为 `ClassTicketHeader` / `ClassTicketInvitation` / `ClassTicketTrend` |
| P2-6 | A2：class-invitation-manager 绝对路径 | 改为相对路径 |
| P2-7 | I1：8 个页面缺 generateMetadata | 补齐 |
| P2-8 | 行业差距：班级归档/复制 | 中长期产品规划 |
| P2-9 | 行业差距：班级健康度评分/早期预警 | 中长期产品规划 |
| P2-10 | 行业差距：课表多视图/导出/提醒 | 中长期产品规划 |

### 重构设计原则（强制满足）

为达成"完全解耦 + 组合优先 + 国际化就绪 + 最大化复用 + 错误边界 + 可测试 + 可扩展 + 企业级补充"八项原则，本次实施遵循：

1. **完全解耦**：classes 模块内部组件不直接 import 其他业务模块（exams/grades/homework/scheduling）的 actions/data-access；跨模块数据通过本模块 data-access 暴露的接口调用（已基本达成，仅需修复 P0-2/P0-3 的越权问题）。
2. **组合优先**：错误处理抽 `useClassFormHandlers` hook；纯逻辑抽 `class-stats-utils.ts`、`schedule-utils.ts`；详情 widget 通过配置驱动（`ClassDetailWidgetConfig`）。
3. **国际化就绪**：所有修复项必须使用 i18n key；新增翻译键写入 `messages/{locale}/classes.json` 的 `detail.*` / `schedule.*` / `students.*` 命名空间。
4. **最大化复用**：admin/grade/teacher/student 共用 `ClassListTable` / `ClassFormDialog` / `ClassDeleteDialog` / `useClassData` / `useClassFilters` / `useClassFormHandlers`。
5. **错误与边界**：每个详情 widget 独立用 `ClassErrorBoundary` 包裹；RSC 数据获取用 React Suspense + 骨架屏；空数据/无权限/网络异常状态明确处理。
6. **可测试性**：纯函数导出至 `*.ts` 文件，便于 vitest 单测；接口类型显式标注。
7. **可扩展性**：角色差异通过 `hasAdminScope` / `hasTeacherScope` 抽象，新增角色只改 actions-shared.ts。
8. **企业级补充**：补 a11y（focus-visible、ARIA）、性能（Suspense 流式）、安全（actions 层归属校验）、监控（埋点接口预留 `trackClassEvent` 工具函数）。

## 五、架构图同步说明

本次审计发现架构图存在以下遗漏或不一致，需在实施修复时同步更新：

| # | 位置 | 问题 | 修复动作 |
|---|---|---|---|
| 1 | 005 `modules.classes.files` | 仅列 21 个文件，漏 schema.ts/types.ts/12 个组件文件 | 补全至 33 个文件 |
| 2 | 005 `modules.classes.exports.actions` | 漏 `createClassInvitationCodeAction` / `revokeClassInvitationCodesAction` / `listClassInvitationCodesAction` 3 个 v3 邀请码 action | 补全至 20 个 |
| 3 | 005 `modules.classes.exports.dataAccess` | 漏 `compareClassLike` / `isDuplicateInvitationCodeError` / `generateUniqueInvitationCode` / `getAccessibleClassIdsForTeacher` / `getSessionTeacherId` / `getTeacherSubjectIdsForClass` 6 个工具函数 | 补全 |
| 4 | 005 `dbTables` | `classInvitationCodes` 表字段未详细登记 | 补全字段定义 |
| 5 | 005 `dbTables` | `classSchedule` 的 `usedBy` 字段未含 scheduling | 补充 `["classes", "scheduling"]` |
| 6 | 005 `knownIssues.P0-1` | 仍记录原始问题状态，未标注「已修复」 | 更新 status 字段 |
| 7 | 004 §2.7「被依赖」 | 遗漏 `elective` / `error-book` / `adaptive-practice` 三个模块 | 补全至 13 个 |
| 8 | 005 `dependencyMatrix` | `exams → classes` 与 `homework → classes` 边未明确登记 | 补全边定义 |
| 9 | 004 §2.7 已知问题 | P0-2/P0-3/P0-4/P0-5 等本次新增修复 | 同步新增修复记录 |
| 10 | 004 §2.7 文件清单 | 修复后行数变化（如 schedule-view.tsx 拆分） | 更新行数 |
