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

441 lines
34 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.
# 班级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 个 widgetheader/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 个邀请码 actionexports.dataAccess 漏 6 个工具函数 | **不一致** |
| 数据库表 | ✅ classes/classSubjectTeachers/classEnrollments/classInvitationCodes | ⚠️ classInvitationCodes 字段未详细登记classSchedule 的 usedBy 字段未含 scheduling | **部分遗漏** |
| 权限点 | ✅ 6 个 CLASS_* 权限 | ✅ permissions 常量定义完整 | 一致 |
| 路由 | ✅ 11 条路由登记 | ✅ routes 节点登记 | 一致 |
| 文件清单 | ✅ 33 个文件 | ⚠️ modules.classes.files 仅列 21 个,漏 12 个组件文件 | **不一致** |
**结论**:架构图总体覆盖较完整,但存在 7 处需同步更新(详见第五章)。
## 二、现存问题与原因分析
### 2.1 三层架构合规性
#### 问题 A1data-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 权限校验
#### 问题 B1listClassInvitationCodesAction 无班级归属校验(🔴 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 字符串),可能引发越权获取加入凭证,破坏邀请码体系的安全性。
#### 问题 B23 个 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 路径,归属校验会被跳过。
#### 问题 B4data-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 处;魔法字符串降低可读性。
#### 问题 B5student 三个页面未调用 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
#### 问题 C1class-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 中文产品定位严重冲突;架构图存在错误声明。
#### 问题 C2schedule-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 强制要求。
- **后果**:中文用户看到全英文错误提示,体验差。
#### 问题 C430+ 个 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+ 处重复字符串维护成本高。
#### 问题 C5DEFAULT_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` 未在架构图记录。
#### 问题 C6getSubjectColor 用英文匹配中文科目(🔴 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 错误处理
#### 问题 D1catch 块未记录错误,仅显示 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、网络错误、权限错误全部显示同一文案开发者无法从用户截图定位错误线上排查困难。
#### 问题 D2data-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` 失败时返回空数组,会让教师看到"无班级"假象,区分不出"数据库错误"与"无数据"。
- **违反规则**:错误处理最佳实践。
- **后果**:教师班级列表假性空数据,难以排查。
#### 问题 D3actions 层错误处理两套风格混用(🟡 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 会转为结构化失败;维护成本高。
#### 问题 D4teacher/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 与站点风格不一致。
#### 问题 D510 个页面缺 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 类型安全
#### 问题 E1formData.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 而静默丢弃数据。
#### 问题 E2data-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。
#### 问题 E3class-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 中不会收窄类型,跳过空值检查。
#### 问题 E414 个组件事件处理函数缺 Promise<void> 返回类型(🟢 P2
- **位置**my-classes-grid.tsx4 处、schedule-filters.tsx、schedule-view.tsx4 处、students-filters.tsx、students-table.tsx、class-invitation-manager.tsx3 处、edit-class-dialog.tsx
- **违反规则**:函数返回值必须显式标注,特别是 `Promise<T>`。
- **后果**:若将来函数内部 `return` 一个值(如返回 boolean 表示是否成功),调用方无类型提示。
### 2.6 文件大小
#### 问题 F1schedule-view.tsx 527 行超出组件 500 行建议(🟡 P1
- **位置**[schedule-view.tsx](file:///e:/Desktop/CICD/src/modules/classes/components/schedule-view.tsx)
- **现状**:同时承担"周历视图渲染 + 创建对话框 + 编辑对话框 + 删除确认对话框"4 个职责。
- **违反规则**React 组件 ≤ 500 行。
- **后果**:可读性差、修改易引入回归。
#### 问题 F2my-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 组件复用性
#### 问题 G13 个 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 缺失
#### 问题 I18 个 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 | A1data-access.ts 与拆分文件 3 对同名函数重复定义 | 删除 data-access.ts 中的本地定义,统一从拆分文件导出(保持 barrel 入口兼容) |
| P0-2 | B1listClassInvitationCodesAction 无归属校验 | 在 actions 层调用 `verifyTeacherOwnsClass(classId, ctx.userId)`admin scope 跳过) |
| P0-3 | B23 个 schedule action 无归属校验 | 同 P0-2调用 `verifyTeacherOwnsClass` |
| P0-4 | C6getSubjectColor 中文科目不匹配 | 改用科目 ID 或类型守卫匹配;纯函数抽出到 `schedule-utils.ts` 并补单测 |
| P0-5 | B3教师 update/delete/enroll action 未在 actions 层做归属校验 | actions 层显式判断 `hasAdminScope(ctx)` 否则校验 `classes.teacherId === ctx.userId` |
### P1 — 重要合规性(本期实施)
| # | 问题 | 改进方向 |
|---|---|---|
| P1-1 | C1class-detail/ 8 子组件硬编码英文 | 全部接入 `useTranslations("classes.detail.*")` |
| P1-2 | C2schedule-view / schedule-filters / students-filters 硬编码英文 | 接入 i18n |
| P1-3 | C3所有 actions message 英文硬编码 | 在 actions 层使用 `getTranslations()` 或返回错误码由组件层翻译 |
| P1-4 | C430+ error.tsx 硬编码中文 | 抽取 `shared/components/ErrorState` + i18n key `common.error.boundary.*` |
| P1-5 | C5DEFAULT_CLASS_SUBJECTS 与 excludeSubjects 硬编码 | 改为从 `subjects` 表查询;统一单一来源 |
| P1-6 | B4data-access 中 7 处 roles.name 硬编码 | 抽出 `ROLE_TEACHER` 常量 |
| P1-7 | B5student 三个页面未调用 requirePermission | 补 `requirePermission(Permissions.HOMEWORK_SUBMIT)` 或新增 STUDENT_READ 权限 |
| P1-8 | D110 处 catch 块未记录错误 | 改为 `catch (error) { console.error("[classes] xxx:", error); toast.error(...) }` |
| P1-9 | D2getTeacherClasses 静默返回空数组 | 区分"DB 错误"与"无数据"DB 错误抛出 |
| P1-10 | D3actions 层错误处理两套风格 | 统一为 `handleActionError` |
| P1-11 | D4teacher/classes/my/[id] 缺 loading.tsx 和 error.tsx | 新增 |
| P1-12 | D510 个页面缺 error.tsx | 补齐 |
| P1-13 | F1schedule-view.tsx 527 行超限 | 抽出 3 个对话框子组件 |
### P2 — 工程优化(中长期)
| # | 问题 | 改进方向 |
|---|---|---|
| P2-1 | E1/E2/E35 处 as 断言 | 改用类型守卫 |
| P2-2 | E414 个事件处理函数缺 Promise<void> | 补返回类型 |
| P2-3 | G13 个 view CRUD handler 重复 | 抽 `useClassFormHandlers` hook |
| P2-4 | H1纯函数内嵌组件 | 抽到 `class-stats-utils.ts` / `schedule-utils.ts` 并补单测 |
| P2-5 | F2my-classes-grid ClassTicket 210 行 | 拆分为 `ClassTicketHeader` / `ClassTicketInvitation` / `ClassTicketTrend` |
| P2-6 | A2class-invitation-manager 绝对路径 | 改为相对路径 |
| P2-7 | I18 个页面缺 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. **企业级补充**:补 a11yfocus-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 拆分) | 更新行数 |