# 班级(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 返回类型(🟢 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`。 - **后果**:若将来函数内部 `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 | 补返回类型 | | 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 拆分) | 更新行数 |