# 考勤与选修课(Attendance & Elective)模块审计报告 > 审计日期:2026-06-22 > 审计范围: > - `src/modules/attendance/**`、`src/app/(dashboard)/admin/attendance/**`、`src/app/(dashboard)/teacher/attendance/**`、`src/app/(dashboard)/student/attendance/**`、`src/app/(dashboard)/parent/attendance/**` > - `src/modules/elective/**`、`src/app/(dashboard)/admin/elective/**`、`src/app/(dashboard)/teacher/elective/**`、`src/app/(dashboard)/student/elective/**` > - 跨模块依赖:`src/modules/parent/components/parent-attendance-*.tsx`、`src/shared/i18n/messages/**` > 参照规则:`docs/architecture/004_architecture_impact_map.md`、`docs/architecture/005_architecture_data.json`、`.trae/rules/project_rules.md` --- ## 一、现有实现概要 ### 1.1 文件分布 #### 考勤模块(attendance) | 层 | 文件 | 行数 | 职责 | |------|------|------|------| | Server Actions | [actions.ts](file:///e:/Desktop/CICD/src/modules/attendance/actions.ts) | 271 | 10 个 Server Action(含权限校验、Zod 校验) | | 数据访问 | [data-access.ts](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts) | 309 | 考勤记录 CRUD + 班级学生查询 + 规则 upsert + 总览统计 | | 数据访问 | [data-access-stats.ts](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts) | 145 | 学生/班级考勤汇总(拆分范例) | | Schema | [schema.ts](file:///e:/Desktop/CICD/src/modules/attendance/schema.ts) | 43 | Zod 校验(5 个 schema) | | Types | [types.ts](file:///e:/Desktop/CICD/src/modules/attendance/types.ts) | 103 | 类型定义 + 状态标签/颜色常量 | | 组件 | [components/attendance-sheet.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx) | 353 | 批量点名表单(键盘快捷键、状态按钮组) | | 组件 | [components/attendance-record-list.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-record-list.tsx) | 130 | 考勤记录列表 + 删除对话框 | | 组件 | [components/attendance-filters.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-filters.tsx) | 97 | URL 同步筛选器(班级/状态/日期) | | 组件 | [components/attendance-stats-card.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-card.tsx) | 81 | 单卡片统计(8 指标) | | 组件 | [components/attendance-stats-cards.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-cards.tsx) | 80 | 管理员总览 6 卡片网格 | | 组件 | [components/attendance-stats-class-selector.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-class-selector.tsx) | 27 | 班级筛选 ChipNav | | 组件 | [components/attendance-rules-form.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-rules-form.tsx) | 148 | 考勤规则配置表单 | | 组件 | [components/student-attendance-view.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/student-attendance-view.tsx) | 104 | 学生/家长视图(统计 + 最近记录) | | 页面 | [admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx) | 91 | 管理员考勤总览(RSC) | | 页面 | [teacher/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/page.tsx) | 116 | 教师考勤记录列表(RSC + 分页) | | 页面 | [teacher/attendance/sheet/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/sheet/page.tsx) | 44 | 教师点名页(RSC) | | 页面 | [teacher/attendance/stats/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/stats/page.tsx) | 85 | 教师班级考勤统计(RSC) | | 页面 | [student/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/attendance/page.tsx) | 40 | 学生考勤汇总(RSC) | | 页面 | [parent/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/parent/attendance/page.tsx) | 66 | 家长多子女考勤聚合(RSC) | | 骨架屏 | 2 个 `loading.tsx`(student/parent) | — | 列表骨架屏 | | 错误边界 | 0 个 `error.tsx` | — | **完全缺失** | #### 选修课模块(elective) | 层 | 文件 | 行数 | 职责 | |------|------|------|------| | Server Actions | [actions.ts](file:///e:/Desktop/CICD/src/modules/elective/actions.ts) | 304 | 11 个 Server Action | | 数据访问 | [data-access.ts](file:///e:/Desktop/CICD/src/modules/elective/data-access.ts) | 250 | 课程 CRUD + scope 过滤 + 显示名聚合 | | 数据访问 | [data-access-operations.ts](file:///e:/Desktop/CICD/src/modules/elective/data-access-operations.ts) | 245 | 选课/退课/抽签(事务 + FOR UPDATE 锁) | | 数据访问 | [data-access-selections.ts](file:///e:/Desktop/CICD/src/modules/elective/data-access-selections.ts) | 149 | 选课记录查询 + 学生可选课程 | | Schema | [schema.ts](file:///e:/Desktop/CICD/src/modules/elective/schema.ts) | 132 | Zod 校验(5 个 schema) | | Types | [types.ts](file:///e:/Desktop/CICD/src/modules/elective/types.ts) | 108 | 类型定义 + 4 组标签/颜色常量 | | 组件 | [components/elective-course-list.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-list.tsx) | 233 | 课程卡片网格 + 管理操作 | | 组件 | [components/elective-course-form.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-form.tsx) | 293 | 课程创建/编辑表单 | | 组件 | [components/elective-filters.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/elective-filters.tsx) | 49 | nuqs 筛选栏(搜索 + 模式) | | 组件 | [components/student-selection-view.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/student-selection-view.tsx) | 250 | 学生选课视图(已选 + 可选) | | 页面 | [admin/elective/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/page.tsx) | 46 | 管理员课程列表(RSC) | | 页面 | [admin/elective/create/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/create/page.tsx) | 36 | 创建课程(RSC) | | 页面 | [admin/elective/[id]/edit/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/[id]/edit/page.tsx) | 48 | 编辑课程(RSC) | | 页面 | [teacher/elective/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/elective/page.tsx) | 53 | 教师我的课程(RSC) | | 页面 | [student/elective/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/elective/page.tsx) | 54 | 学生选课中心(RSC) | | 骨架屏 | 1 个 `loading.tsx`(student) | — | 列表骨架屏 | | 错误边界 | 0 个 `error.tsx` | — | **完全缺失** | #### 跨模块依赖(parent 模块消费 attendance 类型) | 文件 | 行数 | 职责 | |------|------|------| | [parent/components/parent-attendance-warning.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-warning.tsx) | 102 | 家长考勤异常预警横幅 | | [parent/components/parent-attendance-rate-card.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-rate-card.tsx) | 114 | 家长出勤率汇总卡片 | | [parent/components/parent-attendance-calendar.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-calendar.tsx) | 194 | 家长考勤月历视图 | ### 1.2 数据流 #### 考勤数据流 ``` page.tsx (RSC) └─ getAttendanceRecords / getStudentAttendanceSummary / getClassAttendanceStats (data-access) └─ db (drizzle) → attendanceRecords / attendanceRules / classEnrollments / users / classes 表 └─ (client) → batchRecordAttendanceAction └─ (client) → deleteAttendanceAction └─ (client) → saveAttendanceRulesAction └─ (server) — 学生/家长只读 └─ (server/client) — 家长聚合视图 ``` #### 选修课数据流 ``` page.tsx (RSC) └─ getElectiveCourses / getElectiveCourseById / getAvailableCoursesForStudent / getStudentSelections (data-access) └─ db (drizzle) → electiveCourses / courseSelections 表 └─ 跨模块 data-access:school.getSubjectOptions / school.getGradeOptions / users.getUserNamesByIds / classes.getStudentActiveGradeId └─ (client) → deleteElectiveCourseAction / openSelectionAction / closeSelectionAction / runLotteryAction └─ (client) → createElectiveCourseAction / updateElectiveCourseAction └─ (client) → selectCourseAction / dropCourseAction ``` ### 1.3 架构图记录完整性 经核对 [004_architecture_impact_map.md](file:///e:/Desktop/CICD/docs/architecture/004_architecture_impact_map.md) §2.10(attendance)与 §2.20(elective)以及 [005_architecture_data.json](file:///e:/Desktop/CICD/docs/architecture/005_architecture_data.json) 中对应节点,架构图记录**存在以下偏差**(详见第五节): - **attendance 行数统计过期**:图记 `actions.ts 271 行 / data-access.ts 309 行`,实际一致;但 `data-access-stats.ts` 图记 145 行,实际 145 行(一致)。组件文件数图记 5 个,实际 8 个组件文件(缺 `attendance-record-list.tsx`、`attendance-rules-form.tsx`、`student-attendance-view.tsx`)。 - **attendance 导出函数名不一致**:图记 Actions 含 `getAttendanceRecordsAction / createAttendanceRecordAction / updateAttendanceRecordAction / deleteAttendanceRecordAction / getStudentAttendanceAction / getAttendanceStatsAction`,实际为 `recordAttendanceAction / batchRecordAttendanceAction / updateAttendanceAction / deleteAttendanceAction / getAttendanceAction / getStudentAttendanceAction / getClassAttendanceStatsAction / getClassAttendanceForDateAction / saveAttendanceRulesAction / getAttendanceRulesAction`(10 个,名称与图不一致)。 - **attendance 缺失组件记录**:图记 `AttendanceStatsCards` 一个组件,实际有 8 个组件(含 `AttendanceSheet`、`AttendanceRecordList`、`AttendanceFilters`、`AttendanceStatsCard`、`AttendanceStatsCards`、`AttendanceStatsClassSelector`、`AttendanceRulesForm`、`StudentAttendanceView`)。 - **attendance 缺失规则功能记录**:架构图未记录 `attendanceRules` 表的 CRUD(实际已实现 `saveAttendanceRulesAction` / `getAttendanceRulesAction` + `upsertAttendanceRules` / `getAttendanceRules`)。 - **elective 行数统计过期**:图记 `actions.ts 304 行 / data-access.ts 250 行 / data-access-operations.ts 245 行 / data-access-selections.ts 189 行`,实际 `data-access-selections.ts` 为 149 行(减少 40 行)。 - **elective 缺失组件记录**:图记组件 3 个(`elective-course-form`、`elective-course-list`、`elective-filters`),实际 4 个(缺 `student-selection-view.tsx`)。 - **elective 缺失 usedBy 信息**:`getStudentSelectionsAction` / `getAvailableCoursesAction` 的 `usedBy` 字段标注为"待扩展",实际已被 `student/elective/page.tsx` 通过 data-access 直接调用(绕过 Action)。 - **parent 跨模块 UI 依赖未记录**:parent 模块的 3 个 attendance 组件直接 import `@/modules/attendance/types`,架构图未在 parent 模块的依赖关系中标注此 UI 层依赖。 --- ## 二、现存问题与原因分析 ### 2.1 架构解耦 #### 问题 2.1.1 | parent 模块跨模块 import attendance 类型(P1) - **位置**: - [parent-attendance-warning.tsx#L5](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-warning.tsx#L5):`import type { StudentAttendanceSummary } from "@/modules/attendance/types"` - [parent-attendance-rate-card.tsx#L5](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-rate-card.tsx#L5):同上 - [parent-attendance-calendar.tsx#L6-L10](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-calendar.tsx#L6):`import type { AttendanceListItem, AttendanceStatus, StudentAttendanceSummary } from "@/modules/attendance/types"` - **现象**:parent 模块的 3 个组件直接依赖 attendance 模块的类型定义,且 `parent-attendance-calendar.tsx` 内部重新定义了 `STATUS_LABEL` / `STATUS_DOT` 常量(与 attendance 模块的 `ATTENDANCE_STATUS_LABELS` / `ATTENDANCE_STATUS_COLORS` 重复)。 - **违反规则**:项目规则"该模块必须作为独立功能单元……模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access(只能通过注入的接口调用)"。虽然此处仅 import 类型,但 parent 模块应通过自身定义的视图模型接口解耦,而非直接消费 attendance 内部类型。 - **原因**:家长考勤视图需要展示 attendance 数据,开发时直接复用 attendance 类型,未做视图模型隔离。 - **后果**:attendance 模块修改 `StudentAttendanceSummary` 字段会破坏 parent 模块编译;parent 模块无法独立测试;新增角色时无法替换 attendance 数据源。 #### 问题 2.1.2 | 考勤页面层绕过 Action 直接调用 data-access(P2) - **位置**: - [admin/attendance/page.tsx#L12](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L12):`import { getAttendanceRecords, getAttendanceStats } from "@/modules/attendance/data-access"` - [teacher/attendance/page.tsx#L10](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/page.tsx#L10):`import { getAttendanceRecords } from "@/modules/attendance/data-access"` - [teacher/attendance/sheet/page.tsx#L3](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/sheet/page.tsx#L3):`import { getClassStudentsForAttendance } from "@/modules/attendance/data-access"` - [teacher/attendance/stats/page.tsx#L3](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/stats/page.tsx#L3):`import { getClassAttendanceStats } from "@/modules/attendance/data-access-stats"` - [student/attendance/page.tsx#L2](file:///e:/Desktop/CICD/src/app/(dashboard)/student/attendance/page.tsx#L2):`import { getStudentAttendanceSummary } from "@/modules/attendance/data-access-stats"` - [parent/attendance/page.tsx#L2](file:///e:/Desktop/CICD/src/app/(dashboard)/parent/attendance/page.tsx#L2):同上 - **现象**:所有读操作页面(admin/teacher/student/parent)均直接调用 data-access,未走 `getAttendanceAction` / `getStudentAttendanceAction` / `getClassAttendanceStatsAction` 等 Server Action。 - **违反规则**:项目规则"`app/` 只能调用 `modules/` 的 Server Actions 和 data-access"——此处虽合规(data-access 允许被 app 调用),但架构图 §2.10 标注的 10 个 Action 中有 6 个读 Action 实际无调用方(死代码),且页面层未享受 Action 的统一错误处理与权限二次校验。 - **原因**:RSC 页面直接调 data-access 性能更优(少一层包装),但导致 Action 层读函数成为死代码。 - **后果**:Action 层 6 个读函数(`getAttendanceAction` / `getStudentAttendanceAction` / `getClassAttendanceStatsAction` / `getClassAttendanceForDateAction` / `getAttendanceRulesAction`)无调用方,维护成本浪费;权限二次校验形同虚设。 #### 问题 2.1.3 | elective 页面层同样绕过 Action(P2) - **位置**: - [admin/elective/page.tsx#L4](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/page.tsx#L4):`import { getElectiveCourses } from "@/modules/elective/data-access"` - [admin/elective/[id]/edit/page.tsx#L5](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/[id]/edit/page.tsx#L5):`import { getElectiveCourseById } from "@/modules/elective/data-access"` - [teacher/elective/page.tsx#L4](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/elective/page.tsx#L4):同 admin - [student/elective/page.tsx#L3](file:///e:/Desktop/CICD/src/app/(dashboard)/student/elective/page.tsx#L3):`import { getAvailableCoursesForStudent, getStudentSelections } from "@/modules/elective/data-access-selections"` - **现象**:与考勤相同,elective 的 3 个读 Action(`getElectiveCoursesAction` / `getStudentSelectionsAction` / `getAvailableCoursesAction`)无调用方。 - **后果**:同 2.1.2。 #### 问题 2.1.4 | elective data-access 跨模块依赖未通过接口抽象(P2) - **位置**: - [data-access.ts#L10-L11](file:///e:/Desktop/CICD/src/modules/elective/data-access.ts#L10):`import { getGradeOptions, getSubjectOptions } from "@/modules/school/data-access"`、`import { getUserNamesByIds } from "@/modules/users/data-access"` - [data-access-selections.ts#L12-L13](file:///e:/Desktop/CICD/src/modules/elective/data-access-selections.ts#L12):`import { getStudentActiveGradeId } from "@/modules/classes/data-access"`、`import { getUserNamesByIds } from "@/modules/users/data-access"` - **现象**:elective data-access 直接静态 import school/users/classes 模块的 data-access。 - **违反规则**:项目规则"模块间只能通过对方 data-access 通信"——此处合规(data-access 层通信),但未通过接口抽象,导致 elective 模块无法独立测试(mock 需拦截具体路径)。 - **原因**:架构图 §2.20 已标注这些跨模块依赖为"已修复"(从直查表改为 data-access),但未进一步抽象为接口。 - **后果**:单测 elective 时需 mock 3 个模块的 data-access 函数;未来替换 school/users/classes 实现需改 elective 源码。 ### 2.2 国际化(i18n) #### 问题 2.2.1 | 考勤模块零 i18n 覆盖(P0) - **位置**:模块全部 13 个源文件 - **现象**:项目已接入 next-intl(见 [i18n/request.ts](file:///e:/Desktop/CICD/src/i18n/request.ts)),但考勤模块**没有任何一处**使用 `useTranslations` / `getTranslations`,所有文案硬编码,且中英文混杂: - 中文硬编码:`"考勤总览"`、`"查看全校所有班级的考勤记录"`、`"统计分析"`、`"暂无考勤记录"`、`"系统中尚未产生任何考勤记录。"`、`"考勤记录"`、`"管理学生考勤记录。"`、`"录入考勤"`、`"统计"`、`"当前班级有未保存的考勤记录,确认切换班级?"`、`"总记录数"`、`"出勤"`、`"缺勤"`、`"迟到"`、`"早退"`、`"出勤率"`([admin/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx)、[teacher/attendance/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/page.tsx)、[attendance-sheet.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx)、[attendance-stats-cards.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-cards.tsx)) - 英文硬编码:`"Attendance Sheet"`、`"Save Attendance"`、`"Saving..."`、`"Class"`、`"Date"`、`"Student"`、`"Email"`、`"Status"`、`"Mark All Present"`、`"Search student..."`、`"No students in this class..."`、`"Attendance Statistics"`、`"Present"`、`"Absent"`、`"Late"`、`"Early Leave"`、`"Excused"`、`"Total Records"`、`"Present Rate"`、`"Late Rate"`、`"No attendance data available."`、`"Recent Attendance"`、`"Attendance Rules"`、`"Save Rules"`、`"Late Threshold (minutes)"`、`"Early Leave Threshold (minutes)"`、`"Enable auto-marking..."`、`"Delete Attendance Record"`、`"Are you sure..."`、`"My Attendance"`、`"View your attendance records and statistics."`、`"No attendance records found."`、`"No data"`、`"Student attendance summary is not available."`、`"Recorded By"`、`"Created"`([attendance-sheet.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx)、[attendance-record-list.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-record-list.tsx)、[attendance-stats-card.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-card.tsx)、[attendance-rules-form.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-rules-form.tsx)、[student-attendance-view.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/student-attendance-view.tsx)、[attendance-filters.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-filters.tsx)) - 状态标签常量硬编码英文:`ATTENDANCE_STATUS_LABELS` 在 [types.ts#L86-L92](file:///e:/Desktop/CICD/src/modules/attendance/types.ts#L86) 直接写死 `"Present"` / `"Absent"` / `"Late"` / `"Early Leave"` / `"Excused"`,未走 i18n。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n(使用 next-intl),提取翻译键"。 - **原因**:模块开发时未跟进 i18n 改造,文案随写随定。 - **后果**:无法切换语言;同一界面中英混杂(管理员页中文、教师点名页英文、统计卡片中文),专业度差;后续做国际化需返工全部组件。 #### 问题 2.2.2 | 选修课模块零 i18n 覆盖(P0) - **位置**:模块全部 10 个源文件 - **现象**:与考勤模块相同,选修课模块无任何 i18n 调用,文案中英混杂: - 中文硬编码:`"选修课程"`、`"管理选修课程、开放/关闭选课与抽签。"`、`"新建选修课程"`、`"创建新的选修课程。"`、`"编辑选修课程"`、`"更新选修课程详情。"`([admin/elective/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/page.tsx)、[admin/elective/create/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/create/page.tsx)、[admin/elective/[id]/edit/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/[id]/edit/page.tsx)) - 英文硬编码:`"My Elective Courses"`、`"View and manage the elective courses you teach."`、`"Elective Courses"`、`"Browse available electives and manage your selections."`、`"New Course"`、`"No elective courses"`、`"There are no elective courses available."`、`"Credit"`、`"Teacher"`、`"Mode"`、`"Capacity"`、`"Room"`、`"Schedule"`、`"Open"`、`"Close"`、`"Lottery"`、`"Edit"`、`"Delete"`、`"New Elective Course"`、`"Edit Elective Course"`、`"Course Name *"`、`"Subject"`、`"Grade"`、`"Capacity"`、`"Classroom"`、`"Schedule"`、`"Credit"`、`"Selection Mode"`、`"First Come First Served"`、`"Lottery"`、`"Start Date"`、`"End Date"`、`"Selection Start"`、`"Selection End"`、`"Description"`、`"Cancel"`、`"Create"`、`"Save"`、`"Saving..."`、`"My Selections"`、`"Available Courses"`、`"No selections yet"`、`"Browse available courses below..."`、`"No available courses"`、`"Drop"`、`"Drop this course?"`、`"You are about to drop..."`、`"Yes, drop course"`、`"Already selected"`、`"Select"`、`"Selecting..."`、`"Search by course name, teacher..."`、`"All Modes"`、`"Selection Mode"`([elective-course-list.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-list.tsx)、[elective-course-form.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-form.tsx)、[student-selection-view.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/student-selection-view.tsx)、[elective-filters.tsx](file:///e:/Desktop/CICD/src/modules/elective/components/elective-filters.tsx)) - 状态标签常量硬编码英文:`ELECTIVE_STATUS_LABELS` / `SELECTION_MODE_LABELS` / `COURSE_SELECTION_STATUS_LABELS` 在 [types.ts#L69-L97](file:///e:/Desktop/CICD/src/modules/elective/types.ts#L69) 直接写死英文。 - **违反规则**:同 2.2.1。 - **后果**:同 2.2.1。 #### 问题 2.2.3 | i18n 翻译文件未注册新命名空间(P1) - **位置**:[src/i18n/request.ts](file:///e:/Desktop/CICD/src/i18n/request.ts) - **现象**:`request.ts` 加载了 12 个命名空间(common/auth/onboarding/classes/errors/dashboard/examHomework/announcements/messages/settings/textbooks/grade),但**未加载 attendance/elective 命名空间**(这两个文件也不存在)。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。 - **后果**:即使组件层加了 `useTranslations("attendance")`,运行时也会因消息缺失而回退到 key 本身。 ### 2.3 类型安全 #### 问题 2.3.1 | `as` 断言与 `as never` 类型逃逸(P1) - **位置**: - [elective-course-form.tsx#L204](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-form.tsx#L204):`setSelectionMode(v as "fcfs" | "lottery")` —— `v` 已是 `string`,应用类型守卫或 `ElectiveSelectionModeEnum` 校验。 - [elective-course-list.tsx#L54](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-list.tsx#L54):`await action(null as never, formData)` —— 用 `as never` 绕过 `prevState` 类型检查,是类型逃逸。 - [attendance-sheet.tsx#L126](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L126):`{} as Record` —— 空对象断言为完整 Record,运行时 `statusCounts[status]` 在未初始化时会 `undefined`。 - **违反规则**:项目规则"禁止 `as` 断言(除非从 `unknown` 转换或测试中,需注释原因)"。 - **后果**:类型系统无法保护运行时错误;`as never` 让编译器失去对 `prevState` 的校验。 #### 问题 2.3.2 | `attendance-sheet.tsx` 使用 `window.confirm` 阻塞 UI(P2) - **位置**:[attendance-sheet.tsx#L107](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L107):`if (!window.confirm("当前班级有未保存的考勤记录,确认切换班级?"))` - **现象**:使用浏览器原生 `confirm`,与模块内其他删除操作使用的 `AlertDialog`/`Dialog` 不一致。 - **违反规则**:项目规则"组合优先"与 UI 一致性;`confirm()` 阻塞主线程且不可定制样式。 - **后果**:交互体验割裂;移动端 `confirm` 表现不一;i18n 文案无法替换。 #### 问题 2.3.3 | `getAttendanceStats` 实现低效且类型不精确(P2) - **位置**:[data-access.ts#L285-L308](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L285) - **现象**:`getAttendanceStats` 注释写"简化实现:基于已有查询统计",实际是先调 `getAttendanceRecords`(默认 pageSize=20)取前 20 条,再 `filter` 统计——**统计结果只基于前 20 条记录**,不是全量。 - **违反规则**:项目规则"函数返回值必须显式标注"(此处已标注,但语义错误)。 - **后果**:管理员考勤总览页的 6 卡片统计**永远是前 20 条记录的统计**,不是全校考勤统计,数据严重失真。 #### 问题 2.3.4 | `getClassStudentsForAttendance` 直查 `classEnrollments`(P1) - **位置**:[data-access.ts#L208-L219](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L208) - **现象**:架构图 §2.10 标注"✅ P1-1 已修复:~~`getClassStudentsForAttendance` 直查 `classEnrollments`~~ 改为通过 classes data-access 获取",但**实际代码仍直接查询 `classEnrollments` 表**(`db.select(...).from(classEnrollments).innerJoin(users, ...)`)。 - **违反规则**:项目规则"模块间只能通过对方 data-access 通信,禁止跨模块直接查询数据库表"。架构图记录与实际代码不一致。 - **原因**:架构图记录错误,或修复后被回退。 - **后果**:classes 模块修改 `classEnrollments` schema 会破坏 attendance 模块;架构图可信度受损。 ### 2.4 错误与边界处理 #### 问题 2.4.1 | 完全缺失 React Error Boundary(P0) - **位置**: - 考勤:`src/app/(dashboard)/admin/attendance/`、`src/app/(dashboard)/teacher/attendance/`、`src/app/(dashboard)/student/attendance/`、`src/app/(dashboard)/parent/attendance/` 均无 `error.tsx` - 选修课:`src/app/(dashboard)/admin/elective/`、`src/app/(dashboard)/teacher/elective/`、`src/app/(dashboard)/student/elective/` 均无 `error.tsx` - **现象**:7 个页面目录均无错误边界,DB 查询失败、Server Action 抛错时整页白屏。 - **违反规则**:项目规则"每个独立的数据区块必须用 React Error Boundary 包裹"。 - **后果**:一次 DB 抖动导致整个考勤/选修课页面崩溃,无法隔离故障域;用户只能手动刷新。 #### 问题 2.4.2 | 骨架屏覆盖不全(P2) - **位置**: - 考勤:仅 `student/attendance/loading.tsx`、`parent/attendance/loading.tsx` 存在;`admin/attendance/`、`teacher/attendance/`、`teacher/attendance/sheet/`、`teacher/attendance/stats/` 均无骨架屏。 - 选修课:仅 `student/elective/loading.tsx` 存在;`admin/elective/`、`admin/elective/create/`、`admin/elective/[id]/edit/`、`teacher/elective/` 均无骨架屏。 - **违反规则**:项目规则"异步数据使用 React Suspense + 骨架屏"。 - **后果**:管理员/教师端首屏白屏时间长,体验差。 #### 问题 2.4.3 | 空状态文案与组件不统一(P2) - **位置**: - [attendance-record-list.tsx#L54-L60](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-record-list.tsx#L54):内联 `
No attendance records found.
` - [attendance-sheet.tsx#L245-L248](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L245):内联 `

No students in this class...

` - 列表页则用 `EmptyState` 组件 - **后果**:同一模块内空状态有两种写法,维护成本高,a11y 属性缺失。 #### 问题 2.4.4 | Server Action 错误消息英文硬编码(P2) - **位置**: - [attendance/actions.ts#L56](file:///e:/Desktop/CICD/src/modules/attendance/actions.ts#L56):`"Attendance recorded"`、`"Invalid form data"`、`"Unexpected error"` - [elective/actions.ts#L88](file:///e:/Desktop/CICD/src/modules/elective/actions.ts#L88):`"Elective course created"`、`"Course not found"`、`"Invalid form data"` - **现象**:所有 Action 的 `message` 字段硬编码英文,未走 i18n。 - **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。 - **后果**:toast 提示无法本地化。 ### 2.5 组件复用与组合 #### 问题 2.5.1 | 考勤状态标签/颜色常量重复定义(P1) - **位置**: - [attendance/types.ts#L86-L103](file:///e:/Desktop/CICD/src/modules/attendance/types.ts#L86):`ATTENDANCE_STATUS_LABELS` / `ATTENDANCE_STATUS_COLORS` - [parent-attendance-calendar.tsx#L14-L28](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-calendar.tsx#L14):`STATUS_DOT` / `STATUS_LABEL`(与 attendance 重复) - [attendance-sheet.tsx#L39-L61](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L39):`STATUS_OPTIONS` / `STATUS_SHORTCUTS` / `STATUS_STYLES`(部分重复) - [attendance-filters.tsx#L21-L27](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-filters.tsx#L21):`STATUS_OPTIONS`(与 sheet 重复) - **现象**:考勤状态枚举的标签、颜色、快捷键、样式在 4 个文件里各写一份。 - **违反规则**:项目规则"最大化复用……抽象为泛型组件和 hooks"。 - **后果**:新增状态需改 4 处;当前已出现不一致(`ATTENDANCE_STATUS_COLORS` 用 `"outline"` 表示 early_leave,但 `STATUS_STYLES` 用 `bg-blue-500`)。 #### 问题 2.5.2 | 选修课状态标签/颜色常量分散(P1) - **位置**: - [elective/types.ts#L69-L108](file:///e:/Desktop/CICD/src/modules/elective/types.ts#L69):4 组常量(`ELECTIVE_STATUS_LABELS` / `ELECTIVE_STATUS_COLORS` / `SELECTION_MODE_LABELS` / `COURSE_SELECTION_STATUS_LABELS` / `COURSE_SELECTION_STATUS_COLORS`) - [elective-course-form.tsx#L208-L213](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-form.tsx#L208):Select 选项硬编码 `"First Come First Served"` / `"Lottery"`(未复用 `SELECTION_MODE_LABELS`) - [elective-filters.tsx#L40-L44](file:///e:/Desktop/CICD/src/modules/elective/components/elective-filters.tsx#L40):Select 选项硬编码(同上) - **现象**:状态标签在 types.ts 集中定义,但表单/筛选组件未复用,重新硬编码。 - **后果**:标签变更需改 3 处;i18n 改造时需同步多处。 #### 问题 2.5.3 | 考勤页面布局重复(P2) - **位置**: - [admin/attendance/page.tsx#L62-L89](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L62) - [teacher/attendance/page.tsx#L63-L114](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/page.tsx#L63) - **现象**:两个页面的标题区 + 筛选区 + 列表区结构几乎相同,仅按钮和分页略有差异。 - **违反规则**:项目规则"最大化复用"。 - **后果**:UI 调整需改多处。 #### 问题 2.5.4 | 选修课列表页布局重复(P2) - **位置**: - [admin/elective/page.tsx#L30-L45](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/page.tsx#L30) - [teacher/elective/page.tsx#L37-L52](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/elective/page.tsx#L37) - **现象**:admin 和 teacher 列表页结构完全相同,仅 `createHref` 不同。 - **后果**:同 2.5.3。 ### 2.6 可访问性(a11y) #### 问题 2.6.1 | 考勤点名表单缺 aria-label(P2) - **位置**:[attendance-sheet.tsx#L215-L226](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L215) - **现象**:班级选择器 `