- Add grades-audit-report - Add homework-audit-report and homework-exams-audit-report - Add lesson-preparation-audit-report-v3 and v4 - Add messaging-audit-report - Add permissions-audit-report - Add question-bank-audit-report - Add settings-profile-audit-report-v3 - Add textbooks-audit-report-v3
444 lines
32 KiB
Markdown
444 lines
32 KiB
Markdown
# 考勤(Attendance)模块审计报告
|
||
|
||
> 审计日期:2026-06-25
|
||
> 审计范围:`src/modules/attendance/**`、`src/app/(dashboard)/{admin,teacher,student,parent}/attendance/**`、跨模块依赖 `src/modules/parent/components/parent-attendance-*.tsx` 及 `child-detail-panel.tsx`、i18n `src/shared/i18n/messages/{en,zh-CN}/attendance.json`
|
||
> 参照规则:`docs/architecture/004_architecture_impact_map.md`(第 2.10 节)、`docs/architecture/005_architecture_data.json`(L14681 起)、`.trae/rules/project_rules.md`
|
||
|
||
---
|
||
|
||
## 一、现有实现概要
|
||
|
||
### 1.1 文件分布
|
||
|
||
| 层 | 文件 | 行数 | 职责 |
|
||
|------|------|------|------|
|
||
| Server Actions | [actions.ts](file:///e:/Desktop/CICD/src/modules/attendance/actions.ts) | 258 | 5 个写 Action(含权限校验、Zod 校验、归属校验) |
|
||
| 数据访问 | [data-access.ts](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts) | 340 | 考勤记录 CRUD + 规则 upsert + 总览统计 + recorder 解析 |
|
||
| 数据访问 | [data-access-stats.ts](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts) | 206 | 学生/班级考勤汇总(纯函数 `computeStats` + SQL 聚合) |
|
||
| 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 | 类型定义 |
|
||
| Constants | [constants.ts](file:///e:/Desktop/CICD/src/modules/attendance/constants.ts) | 64 | 状态选项/快捷键/颜色映射 |
|
||
| Export | [export.ts](file:///e:/Desktop/CICD/src/modules/attendance/export.ts) | 90 | Excel 导出 |
|
||
| 组件 | [components/attendance-page-layout.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-page-layout.tsx) | 38 | admin/teacher 共用布局插槽 |
|
||
| 组件 | [components/attendance-sheet.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx) | 418 | 批量点名表单(快捷键、AlertDialog 确认) |
|
||
| 组件 | [components/attendance-record-list.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-record-list.tsx) | 142 | 记录列表 + 删除对话框 |
|
||
| 组件 | [components/attendance-filters.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-filters.tsx) | 94 | URL 同步筛选器 |
|
||
| 组件 | [components/attendance-stats-card.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-card.tsx) | 82 | 单卡片 8 指标 |
|
||
| 组件 | [components/attendance-stats-cards.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-cards.tsx) | 83 | admin 总览 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) | 155 | 规则配置表单 |
|
||
| 组件 | [components/student-attendance-view.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/student-attendance-view.tsx) | 111 | 学生/家长视图 |
|
||
| 页面 | [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) |
|
||
| 错误边界 | 4 个 `error.tsx`(admin/teacher/student/parent) | ~96 | **重复严重** |
|
||
| 跨模块 | [parent/components/parent-attendance-calendar.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-calendar.tsx) | 220 | 家长月历视图 |
|
||
| 跨模块 | [parent/components/parent-attendance-warning.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-warning.tsx) | 110 | 异常预警横幅 |
|
||
| 跨模块 | [parent/components/parent-attendance-rate-card.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-rate-card.tsx) | 114 | 出勤率汇总卡片 |
|
||
| 跨模块 | [parent/components/child-detail-panel.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/child-detail-panel.tsx) | 190 | 子女详情面板(含考勤 Tab) |
|
||
|
||
### 1.2 数据流
|
||
|
||
```
|
||
page.tsx (RSC)
|
||
└─ getAttendanceRecords / getStudentAttendanceSummary / getClassAttendanceStats (data-access)
|
||
└─ db (drizzle) → attendanceRecords / attendanceRules 表
|
||
└─ ⚠ 直接 JOIN users / classes 表(跨模块表查询)
|
||
└─ 调用 classes/data-access.getClassActiveStudentsWithInfo(合规)
|
||
└─ <AttendanceSheet> (client) → batchRecordAttendanceAction
|
||
└─ <AttendanceRecordList> (client) → deleteAttendanceAction
|
||
└─ <AttendanceRulesForm> (client) → saveAttendanceRulesAction
|
||
```
|
||
|
||
### 1.3 架构图完整性评估
|
||
|
||
架构影响地图(004 第 2.10 节)与 JSON(L14681 起)**整体覆盖** attendance 模块,但存在 **6 处信息过时/不准确**(详见第五节),需同步更新。模块导出、权限点、依赖关系、路由已记录。
|
||
|
||
---
|
||
|
||
## 二、现存问题与原因分析
|
||
|
||
### 2.1 三层架构合规性
|
||
|
||
#### 问题 2.1.1:data-access 层跨模块直接 JOIN 外部表【P0】
|
||
- **位置**:[data-access.ts:118-119](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L118-L119)、[data-access.ts:77-81](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L77-L81)、[data-access-stats.ts:87-91](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts#L87-L91)、[data-access-stats.ts:161-165](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts#L161-L165)、[data-access-stats.ts:177-178](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts#L177-L178)
|
||
- **描述**:`getAttendanceRecords` 直接 `leftJoin(users)`、`leftJoin(classes)`;`resolveRecorderNames` 直接 `select from users`;`getStudentAttendanceSummary`/`getClassAttendanceStats` 直接查询 `users`/`classes` 表。
|
||
- **违反规则**:项目规则「`modules/` 之间通过对方 data-access 通信,**不直接查询对方 DB 表**」。
|
||
- **原因**:为减少查询往返,在 attendance data-access 内联 JOIN 获取 studentName/className/recorderName。
|
||
- **后果**:users/classes 模块 schema 变更(如 `users.name` 重命名)会直接破坏 attendance 查询;模块边界失效,无法独立演进。
|
||
|
||
#### 问题 2.1.2:parent 模块直接 import attendance 组件【P1】
|
||
- **位置**:[parent/attendance/page.tsx:4](file:///e:/Desktop/CICD/src/app/(dashboard)/parent/attendance/page.tsx#L4)
|
||
- **描述**:`parent/attendance/page.tsx` 直接 `import { StudentAttendanceView } from "@/modules/attendance/components/student-attendance-view"`,跨模块 UI 组件依赖。
|
||
- **违反规则**:项目规则「该模块必须作为独立功能单元,模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access」。
|
||
- **原因**:parent 复用 student 视图组件以减少重复。
|
||
- **后果**:parent 模块与 attendance 模块 UI 强耦合,attendance 调整 StudentAttendanceView 会影响 parent 页面。
|
||
|
||
#### 问题 2.1.3:parent-attendance-calendar 直接依赖 attendance/constants【P1】
|
||
- **位置**:[parent-attendance-calendar.tsx:9-12](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-calendar.tsx#L9-L12)
|
||
- **描述**:直接 import `ATTENDANCE_STATUS_DOT_COLORS`、`ATTENDANCE_STATUS_LABEL_KEYS`。
|
||
- **违反规则**:同上「完全解耦」原则。
|
||
- **原因**:parent 类型已解耦(`parent/types.ts` 自声明类型),但常量仍直接依赖。
|
||
- **后果**:attendance 常量变更影响 parent 月历渲染。
|
||
|
||
#### 问题 2.1.4:架构图信息过时【P2】
|
||
- **位置**:架构图 004 第 1152、1154、1159、1175、1176、1195 行
|
||
- **描述**:6 处描述与实际代码不符(详见第五节)。
|
||
- **违反规则**:项目规则「改码必同步图」。
|
||
- **后果**:架构图可信度下降,误导后续开发。
|
||
|
||
### 2.2 权限校验
|
||
|
||
#### 问题 2.2.1:teacher 子页面缺失权限校验【P0】
|
||
- **位置**:[teacher/attendance/sheet/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/sheet/page.tsx)、[teacher/attendance/stats/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/stats/page.tsx)
|
||
- **描述**:两个页面**无任何权限校验**,未调用 `requirePermission(Permissions.ATTENDANCE_READ)`,也未通过 `getAuthContext().dataScope` 过滤。
|
||
- **违反规则**:项目规则「所有 Server Action 必须调用 `requirePermission()` 进行权限校验」+ 项目记忆「Parent routes must include permission checks with both parentId and studentId」。
|
||
- **原因**:RSC 页面非 Server Action,开发者认为 data-access 内的 `buildScopeFilter` 会兜底。
|
||
- **后果**:sheet 页调用 `getTeacherClasses()` **未传入 scope**([teacher/attendance/sheet/page.tsx:9](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/sheet/page.tsx#L9)),任何已登录用户访问 URL 即可获取教师班级学生列表;stats 页同理。**存在数据越权风险**。
|
||
|
||
#### 问题 2.2.2:teacher/student/parent 主页面权限校验不一致【P1】
|
||
- **位置**:[teacher/attendance/page.tsx:39](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/page.tsx#L39)、[student/attendance/page.tsx:11](file:///e:/Desktop/CICD/src/app/(dashboard)/student/attendance/page.tsx#L11)
|
||
- **描述**:仅 `getAuthContext()`,未 `requirePermission(ATTENDANCE_READ)`;而 [admin/attendance/page.tsx:30](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L30) 已建立该惯例。
|
||
- **违反规则**:权限校验应统一。
|
||
- **后果**:权限点缺失,无法通过权限矩阵精确控制 teacher/student 是否可访问考勤页。
|
||
|
||
#### 问题 2.2.3:前端删除按钮无权限点控制【P2】
|
||
- **位置**:[attendance-record-list.tsx:106-114](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-record-list.tsx#L106-L114)
|
||
- **描述**:删除按钮对所有能看到列表的用户可见,未使用 `usePermission().hasPermission(Permissions.ATTENDANCE_MANAGE)` 控制显隐。
|
||
- **违反规则**:项目规则「前端权限判断统一使用 `usePermission().hasPermission()`」。
|
||
- **后果**:无权用户看到删除按钮,点击后才被 Server Action 拒绝,体验差。
|
||
|
||
### 2.3 i18n 国际化
|
||
|
||
#### 问题 2.3.1:safeParseDate 中文 fieldName 硬编码【P0】
|
||
- **位置**:[data-access.ts:100-102](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L100-L102)、[data-access.ts:167](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L167)、[data-access.ts:185](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L185)、[data-access.ts:308](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L308)、[data-access-stats.ts:95-96](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts#L95-L96)、[data-access-stats.ts:169-170](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts#L169-L170)
|
||
- **描述**:`safeParseDate(value, "日期")`、`safeParseDate(value, "开始日期")` 等 10 处中文 fieldName,经 `handleActionError` 返回客户端为用户可见错误消息。
|
||
- **违反规则**:项目规则「所有用户可见文本必须适配 i18n」。
|
||
- **后果**:英文环境下显示中文错误。
|
||
|
||
#### 问题 2.3.2:action-utils shared 层中文兜底消息【P1】
|
||
- **位置**:`shared/lib/action-utils.ts:32,70,74,143`
|
||
- **描述**:`NotFoundError(\`${resource} 不存在\`)`、`"操作失败,请稍后重试"`、`${fieldName} 格式无效` 等。
|
||
- **违反规则**:同上。
|
||
- **后果**:所有调用 shared 层的模块(含 attendance)均受影响。
|
||
|
||
#### 问题 2.3.3:Excel 导出英文列头硬编码【P1】
|
||
- **位置**:[export.ts:83-84](file:///e:/Desktop/CICD/src/modules/attendance/export.ts#L83-L84)
|
||
- **描述**:`"Metric"`、`"Value"` 硬编码。
|
||
|
||
#### 问题 2.3.4:calendar 硬编码 en-US locale【P1】
|
||
- **位置**:[parent-attendance-calendar.tsx:102](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-calendar.tsx#L102)
|
||
- **描述**:`toLocaleDateString("en-US")`,未使用当前 locale。
|
||
|
||
#### 问题 2.3.5:child-detail-panel 英文硬编码【P1】
|
||
- **位置**:[child-detail-panel.tsx:50-56](file:///e:/Desktop/CICD/src/modules/parent/components/child-detail-panel.tsx#L50-L56)、L111、L137-143、L152、L160-163、L182、L44、L186
|
||
- **描述**:Tab 标签、区块标题、占位提示、按钮文案共 20+ 处英文硬编码。
|
||
|
||
#### 问题 2.3.6:翻译键误用【P0】
|
||
- **位置**:
|
||
- [parent/attendance/error.tsx:17](file:///e:/Desktop/CICD/src/app/(dashboard)/parent/attendance/error.tsx#L17)、[teacher/attendance/error.tsx:17](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/error.tsx#L17)、[admin/attendance/error.tsx:17](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/error.tsx#L17):重试按钮使用 `t("actions.save")` 而非 `t("actions.retry")`,显示"保存"。
|
||
- [attendance-record-list.tsx:127](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-record-list.tsx#L127):删除确认对话框描述使用 `t("errors.unexpected")`("发生未知错误"),应为 `t("sheet.confirmDelete")`。
|
||
- [attendance-rules-form.tsx:32](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-rules-form.tsx#L32):保存按钮显示 `t("rules.saved")`("考勤规则已保存"),应为 `t("actions.save")`。
|
||
- [attendance-sheet.tsx:395](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L395):切换班级确认对话框标题误用 `t("sheet.confirmDelete")`,应为 `t("sheet.confirmClassSwitch")`。
|
||
- **违反规则**:i18n 正确性。
|
||
- **后果**:用户看到错误/误导文案。
|
||
|
||
#### 问题 2.3.7:error.tsx title/description 重复【P2】
|
||
- **位置**:4 个 error.tsx
|
||
- **描述**:title 和 description 均用 `t("errors.unexpected")`,完全相同。
|
||
|
||
### 2.4 类型安全
|
||
|
||
#### 问题 2.4.1:export.ts 无类型守卫的 as 断言【P1】
|
||
- **位置**:[export.ts:28-34](file:///e:/Desktop/CICD/src/modules/attendance/export.ts#L28-L34)
|
||
- **描述**:`params.status as "present" | "absent" | ...`,`params.status` 为 `string | undefined`,无类型守卫。
|
||
- **违反规则**:项目规则「禁止 `as` 断言(除非从 `unknown` 转换)」。
|
||
- **后果**:非法 status 值绕过类型检查。
|
||
|
||
#### 问题 2.4.2:child-detail-panel as 断言【P2】
|
||
- **位置**:[child-detail-panel.tsx:29](file:///e:/Desktop/CICD/src/modules/parent/components/child-detail-panel.tsx#L29)、L65
|
||
- **描述**:`(VALID_TABS as string[]).includes(v)`、`v as ChildDetailTab`,已有 `isTab` 守卫但未在 `onValueChange` 使用。
|
||
|
||
### 2.5 错误处理与边界
|
||
|
||
#### 问题 2.5.1:teacher 子路由缺失 error.tsx【P1】
|
||
- **位置**:`teacher/attendance/sheet/`、`teacher/attendance/stats/`
|
||
- **描述**:缺失 error.tsx,运行时错误冒泡到 `teacher/attendance/error.tsx`,错误上下文不准确。
|
||
- **违反规则**:项目记忆「All student routes must include loading.tsx and error.tsx」。
|
||
|
||
#### 问题 2.5.2:student 空状态文案错误【P2】
|
||
- **位置**:[student/attendance/page.tsx:24-25](file:///e:/Desktop/CICD/src/app/(dashboard)/student/attendance/page.tsx#L24-L25)
|
||
- **描述**:summary 为 null 时 EmptyState description 用 `t("errors.unexpected")`("发生未知错误"),实际原因可能是无考勤记录。
|
||
|
||
### 2.6 组件复用性
|
||
|
||
#### 问题 2.6.1:常量在 constants.ts 与 attendance-sheet.tsx 重复定义【P1】
|
||
- **位置**:[attendance-sheet.tsx:50-83](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L50-L83)
|
||
- **描述**:`STATUS_OPTIONS`、`STATUS_SHORTCUTS`、`createInitialStatusCounts` 与 constants.ts 重复(部分复用、部分重复的混乱状态)。
|
||
- **违反规则**:DRY 原则。
|
||
- **后果**:状态选项变更需同步两处,易遗漏。
|
||
|
||
#### 问题 2.6.2:4 个 error.tsx 近乎完全重复【P1】
|
||
- **位置**:4 个 error.tsx
|
||
- **描述**:结构完全相同,共约 96 行重复代码。
|
||
- **违反规则**:项目记忆「Shared components must be extracted when page duplication exceeds 90%」。
|
||
- **后果**:修改一处需同步四处。
|
||
|
||
#### 问题 2.6.3:两个 stats 卡片组件数据结构分裂【P2】
|
||
- **位置**:[attendance-stats-card.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-card.tsx)、[attendance-stats-cards.tsx](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-stats-cards.tsx)
|
||
- **描述**:同一概念两套数据结构(`AttendanceStats` vs `AttendanceOverviewStats`,`stats.present`/`stats.presentRate` vs `stats.presentCount`/`stats.attendanceRate`)。
|
||
|
||
### 2.7 数据注入与解耦
|
||
|
||
#### 问题 2.7.1:无接口抽象与 Context 注入【P1】
|
||
- **描述**:attendance 模块未定义任何 `AttendanceDataService` 接口,无 `AttendanceContext`/`AttendanceProvider`,无角色差异的接口多态实现。
|
||
- **违反规则**:项目规则「通过定义 TypeScript 接口抽象数据依赖,使用 React Context 注入数据服务」。
|
||
- **后果**:角色间无统一契约约束,参数/返回处理可能不一致;无法通过接口 mock 做单测。
|
||
|
||
### 2.8 可测试性
|
||
|
||
#### 问题 2.8.1:data-access 无接口类型可供 mock【P2】
|
||
- **描述**:data-access 函数直接导出为具体函数,无 `AttendanceRepository` 接口。
|
||
- **违反规则**:项目规则「导出清晰的接口类型以便 mock」。
|
||
|
||
### 2.9 a11y 可访问性
|
||
|
||
#### 问题 2.9.1:全局 keydown 监听可能冲突【P2】
|
||
- **位置**:[attendance-sheet.tsx:164-186](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L164-L186)
|
||
- **描述**:keydown 绑定在 window,仅排除 input/textarea,未排除 Select 等可交互组件。
|
||
|
||
### 2.10 性能
|
||
|
||
#### 问题 2.10.1:getClassAttendanceStats 未用 SQL 聚合【P1】
|
||
- **位置**:[data-access-stats.ts:172-182](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts#L172-L182)
|
||
- **描述**:仍用全量查询 + 内存 `computeStats`,而 `getStudentAttendanceSummary`/`getAttendanceStats` 已改用 SQL 聚合。
|
||
- **违反规则**:性能最佳实践。
|
||
- **后果**:大班级统计查询慢。
|
||
|
||
#### 问题 2.10.2:teacher 分页基于截断数据计算【P0】
|
||
- **位置**:[teacher/attendance/page.tsx:46-63](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/page.tsx#L46-L63)
|
||
- **描述**:先获取 `result.items`(pageSize=20),再对**仅 20 条**做前端分页计算 totalPages。
|
||
- **后果**:分页页数错误,用户无法访问第 2 页之后数据。
|
||
|
||
#### 问题 2.10.3:parent 组件可降级为 RSC【P2】
|
||
- **位置**:[parent-attendance-warning.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-warning.tsx)、[parent-attendance-rate-card.tsx](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-rate-card.tsx)
|
||
- **描述**:仅因 `useTranslations` 标记 `"use client"`,可用 `getTranslations` 改为 RSC。
|
||
|
||
#### 问题 2.10.4:statusCounts 未 memoize【P2】
|
||
- **位置**:[attendance-sheet.tsx:151-157](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L151-L157)
|
||
- **描述**:每次 render 全量 reduce,大班级性能损耗。
|
||
|
||
---
|
||
|
||
## 三、行业差距对比
|
||
|
||
基于 K12 教育系统考勤模块的主流实践(参照 PowerSchool、Infinite Campus、Veracross、校长推荐系统等),当前模块相比优秀实践的差距:
|
||
|
||
| 维度 | 优秀实践 | 当前状态 | 差距影响 |
|
||
|------|---------|---------|---------|
|
||
| **考勤状态细分** | present/absent/late/early-leave/excused-absent/school-activity + 事假/病假/公假原因 | 仅 present/absent/late/excused 4 态,无原因字段 | 学校无法区分病假/事假,无法生成请假原因统计 |
|
||
| **实时家长通知** | 学生缺席自动推送家长 App 通知/短信 | 仅被动查看,无主动推送 | 家长无法及时获知子女缺勤,错过干预窗口 |
|
||
| **出勤率阈值预警** | 自动识别出勤率低于阈值的学生并通知班主任 | 有 parent-attendance-warning 但仅家长端展示,教师端缺失 | 教师无法主动获取需关注学生名单 |
|
||
| **考勤趋势可视化** | 折线图展示个人/班级出勤率周/月趋势 | 仅数字统计,无趋势图 | 无法直观看出勤率变化趋势 |
|
||
| **请假申请流程** | 家长在线提交请假申请,教师/管理员审批,自动同步考勤 | 完全缺失 | 请假流程线下化,考勤数据与请假记录脱节 |
|
||
| **补签/补录** | 学生事后提交补签申请,教师审核修正 | 缺失,仅管理员/教师可手动修改 | 考勤纠错流程繁琐 |
|
||
| **跨日/跨节次考勤** | 按课节(早读/上午/下午/晚自习)多次点名 | 仅按日单次记录 | 无法精确到节次,缺勤定位不精确 |
|
||
| **考勤与成绩关联** | 出勤率与学业成绩相关性分析 | 无关联 | 无法识别"低出勤→低成绩"风险学生 |
|
||
| **班级对比分析** | 同年级班级出勤率横向对比 | 缺失 | 管理员无法横向评估各班考勤管理水平 |
|
||
| **导出报告多样性** | PDF 周报/月报、Excel 明细、家长签字单 | 仅 Excel 单一导出 | 无法满足不同场景报告需求 |
|
||
| **移动端适配** | 移动端点名(平板/手机) | 未验证移动端体验 | 教师课堂点名不便携 |
|
||
| **考勤日历视图** | 学生/家长端月历视图(已有 parent-attendance-calendar) | 已实现且 a11y 良好 | ✅ 已达行业水准 |
|
||
| **批量操作效率** | 一键全勤、快捷键、批量按学号录入 | 已实现快捷键 + 一键全勤 | ✅ 已达行业水准 |
|
||
| **空状态/骨架屏** | 每个数据区块 EmptyState + Skeleton | 已基本覆盖 | ✅ 基本达标 |
|
||
| **a11y 可访问性** | 语义化、ARIA、键盘导航 | 已实现且较完善 | ✅ 基本达标 |
|
||
|
||
**核心差距总结**:
|
||
1. **功能完整性**:缺请假流程、节次考勤、原因分类、趋势可视化、跨班级对比——这些是 K12 学校的刚需。
|
||
2. **主动通知机制**:被动展示→主动预警的转变。
|
||
3. **数据联动**:考勤与成绩、请假、通知模块的联动缺失。
|
||
|
||
---
|
||
|
||
## 四、改进优先级建议
|
||
|
||
### P0(紧急,影响安全/数据正确性,立即修复)
|
||
|
||
| # | 问题 | 改进方向 |
|
||
|---|------|---------|
|
||
| P0-1 | teacher/sheet、teacher/stats 缺权限校验且未传 scope(2.2.1) | 页面级增加 `requirePermission(ATTENDANCE_READ)` + 传入 `dataScope`;data-access 函数强制要求 scope 参数 |
|
||
| P0-2 | teacher 分页基于截断数据计算(2.10.2) | 分页 totalPages 使用后端返回的 total,勿基于 items 长度计算 |
|
||
| P0-3 | safeParseDate 中文 fieldName 硬编码(2.3.1) | 改用 i18n key 或错误 code,前端按 code 本地化 |
|
||
| P0-4 | 翻译键误用:error 重试按钮显示"保存"(2.3.6) | 3 个 error.tsx 改用 `t("actions.retry")` |
|
||
| P0-5 | data-access 跨模块直 JOIN users/classes 表(2.1.1) | 委托 `users/data-access`、`classes/data-access` 提供姓名查询接口 |
|
||
|
||
### P1(重要,影响可维护性/合规性,短期修复)
|
||
|
||
| # | 问题 | 改进方向 |
|
||
|---|------|---------|
|
||
| P1-1 | teacher/student 主页面缺 requirePermission(2.2.2) | 增加 `requirePermission(ATTENDANCE_READ)` |
|
||
| P1-2 | parent 直接 import attendance 组件(2.1.2) | 通过接口抽象 + Context 注入,或将共享视图下沉为可注入组件 |
|
||
| P1-3 | parent-attendance-calendar 依赖 attendance/constants(2.1.3) | 常量下沉到 shared 或通过 props 注入 |
|
||
| P1-4 | action-utils shared 中文兜底(2.3.2) | 返回错误 code 而非中文消息 |
|
||
| P1-5 | child-detail-panel 英文硬编码(2.3.5) | 提取 i18n 键 |
|
||
| P1-6 | export.ts 英文列头 + as 断言(2.3.3、2.4.1) | i18n + 类型守卫 |
|
||
| P1-7 | calendar 硬编码 en-US(2.3.4) | 使用 `useLocale()`/`getLocale()` |
|
||
| P1-8 | teacher 子路由缺 error.tsx(2.5.1) | 新增 error.tsx |
|
||
| P1-9 | 常量重复定义(2.6.1) | attendance-sheet 统一使用 constants.ts |
|
||
| P1-10 | 4 个 error.tsx 重复(2.6.2) | 抽取 shared `ErrorBoundary` 组件 |
|
||
| P1-11 | getClassAttendanceStats 未用 SQL 聚合(2.10.1) | 改用 SQL GROUP BY 聚合 |
|
||
| P1-12 | 无接口抽象/Context 注入(2.7.1) | 定义 `AttendanceDataService` 接口 + Provider |
|
||
|
||
### P2(优化,提升体验/性能,中期演进)
|
||
|
||
| # | 问题 | 改进方向 |
|
||
|---|------|---------|
|
||
| P2-1 | 删除按钮无前端权限控制(2.2.3) | `usePermission().hasPermission(ATTENDANCE_MANAGE)` |
|
||
| P2-2 | error.tsx title/description 重复(2.3.7) | 区分 title/description 键 |
|
||
| P2-3 | student 空状态文案错误(2.5.2) | 改用 `t("list.emptyDescription")` |
|
||
| P2-4 | child-detail-panel as 断言(2.4.2) | 使用 isTab 守卫 |
|
||
| P2-5 | stats 卡片组件数据结构分裂(2.6.3) | 统一为单一数据结构 |
|
||
| P2-6 | data-access 无接口类型(2.8.1) | 导出 `AttendanceRepository` 接口 |
|
||
| P2-7 | 全局 keydown 冲突(2.9.1) | 限制监听范围 |
|
||
| P2-8 | parent 组件可降级 RSC(2.10.3) | 改用 getTranslations |
|
||
| P2-9 | statusCounts 未 memoize(2.10.4) | useMemo |
|
||
| P2-10 | 架构图同步(2.1.4) | 更新 004/005 文档 |
|
||
|
||
### 中长期演进(功能补齐,对齐行业实践)
|
||
|
||
| # | 功能 | 方向 |
|
||
|---|------|------|
|
||
| L-1 | 考勤状态细分 + 原因字段 | 扩展 schema 增加 reason 字段,新增 early-leave/school-activity 状态 |
|
||
| L-2 | 实时家长通知 | 接入 notifications 模块,缺勤自动推送 |
|
||
| L-3 | 出勤率阈值预警(教师端) | 配置驱动阈值,自动生成需关注学生名单 |
|
||
| L-4 | 考勤趋势可视化 | 折线图组件,周/月趋势 |
|
||
| L-5 | 在线请假流程 | 新增 leave-requests 子模块,审批流 + 考勤同步 |
|
||
| L-6 | 节次考勤 | 扩展数据模型支持按节次记录 |
|
||
| L-7 | 跨班级对比分析 | 同年级班级出勤率横向对比图 |
|
||
| L-8 | 导出报告多样化 | PDF 周报/月报 + 家长签字单 |
|
||
| L-9 | 考勤与成绩关联分析 | 跨模块数据联动分析 |
|
||
|
||
---
|
||
|
||
## 五、架构图同步说明
|
||
|
||
本次审计发现架构图(004 第 2.10 节、005 JSON L14681 起)存在以下不一致,**需要同步更新**:
|
||
|
||
| # | 架构图描述 | 实际代码 | 更新动作 |
|
||
|---|-----------|---------|---------|
|
||
| 1 | 004 L1159: `getClassStudentsForAttendance` 仍直查 `classEnrollments` | [data-access.ts:226](file:///e:/Desktop/CICD/src/modules/attendance/data-access.ts#L226) 已委托 `classes/data-access.getClassActiveStudentsWithInfo` | 更新 004 描述为"已委托 classes data-access" |
|
||
| 2 | 004 L1152: 10 个 Actions(含 5 个读 Action) | actions.ts 仅 5 个写 Action | 更新 Actions 计数为 5,读操作标注为直接调 data-access |
|
||
| 3 | 004 L1154: `getClassAttendanceStats` 改用 SQL 聚合 | [data-access-stats.ts:172-182](file:///e:/Desktop/CICD/src/modules/attendance/data-access-stats.ts#L172-L182) 仍用全量查询 + computeStats | 待本次重构改为 SQL 聚合后同步更新 |
|
||
| 4 | 004 L1175: `attendance-sheet.tsx` 使用 `window.confirm` | 已改为 `AlertDialog`(L392-415) | 更新为 AlertDialog |
|
||
| 5 | 004 L1176: 存在 `{} as Record` 断言 | 已改为 `createInitialStatusCounts()` 函数(L75-83) | 更新描述 |
|
||
| 6 | 004 L1195: `attendance-stats-cards.tsx` 硬编码中文 | 已全部使用 `t()` i18n | 更新为已 i18n |
|
||
|
||
**JSON 同步**:005 中 attendance 节点的 exports、dependencies、permissions 需在本次重构后统一更新(新增 `AttendanceDataService` 接口、`AttendanceProvider`、抽取的 shared `ErrorBoundary`、新增 error.tsx 等)。
|
||
|
||
---
|
||
|
||
## 六、重构方案设计
|
||
|
||
### 6.1 完全解耦:接口抽象 + Context 注入
|
||
|
||
**设计目标**:attendance 模块作为独立功能单元,parent/student 等消费方通过接口契约消费,不直接 import 业务实现。
|
||
|
||
```typescript
|
||
// src/modules/attendance/services/attendance-data-service.ts
|
||
// 接口抽象:定义数据契约,可被不同角色实现
|
||
export interface AttendanceDataService {
|
||
getStudentSummary(studentId: string, range?: DateRange): Promise<AttendanceStats | null>;
|
||
getRecentRecords(studentId: string, limit: number): Promise<AttendanceListItem[]>;
|
||
getRecordsByDate(date: string, classId?: string): Promise<AttendanceListItem[]>;
|
||
getClassStats(classId: string, range?: DateRange): Promise<AttendanceStats | null>;
|
||
}
|
||
|
||
// src/modules/attendance/services/attendance-context.tsx
|
||
// React Context 注入
|
||
const AttendanceServiceContext = createContext<AttendanceDataService | null>(null);
|
||
|
||
export function AttendanceProvider({ service, children }: {
|
||
service: AttendanceDataService;
|
||
children: ReactNode;
|
||
}) {
|
||
return (
|
||
<AttendanceServiceContext.Provider value={service}>
|
||
{children}
|
||
</AttendanceServiceContext.Provider>
|
||
);
|
||
}
|
||
|
||
export function useAttendanceService(): AttendanceDataService {
|
||
const service = useContext(AttendanceServiceContext);
|
||
if (!service) throw new Error("AttendanceProvider missing");
|
||
return service;
|
||
}
|
||
|
||
// 角色实现(示例)
|
||
// src/modules/attendance/services/student-service.ts —— 学生/家长视角实现
|
||
// src/modules/attendance/services/teacher-service.ts —— 教师视角实现
|
||
// src/modules/attendance/services/admin-service.ts —— 管理员视角实现
|
||
```
|
||
|
||
**消费方改造**:`parent/attendance/page.tsx` 注入 `StudentAttendanceService` 实现后渲染 `<StudentAttendanceView>`,不再直接 import attendance 组件;或 attendance 导出纯展示组件,由 parent 通过 props 注入数据。
|
||
|
||
### 6.2 组合优先:组件组合与 hooks
|
||
|
||
- 所有 UI 通过 `children`/slots/render props 组合,禁止 HOC 深层嵌套。
|
||
- 逻辑复用抽取为 hooks:`useAttendanceSheet`、`useAttendanceStats`、`useAttendanceFilters`。
|
||
- `AttendancePageLayout` 已采用插槽模式(header/stats/filters/children),推广至所有角色页面。
|
||
|
||
### 6.3 国际化就绪
|
||
|
||
**翻译文件结构示例**:
|
||
```json
|
||
// src/shared/i18n/messages/zh-CN/attendance.json
|
||
{
|
||
"title": "考勤管理",
|
||
"status": { "present": "出勤", "absent": "缺勤", "late": "迟到", "excused": "请假" },
|
||
"stats": { "total": "总人次", "presentRate": "出勤率", ... },
|
||
"errors": {
|
||
"invalidDate": "日期格式无效",
|
||
"invalidStartDate": "开始日期格式无效",
|
||
"unexpected": "发生未知错误,请稍后重试",
|
||
"forbidden": "无权限执行此操作",
|
||
"notFound": "考勤记录不存在"
|
||
},
|
||
"actions": { "save": "保存", "retry": "重试", "delete": "删除", ... }
|
||
}
|
||
```
|
||
|
||
**shared 层错误改造**:`action-utils.ts` 返回 `{ code: "INVALID_DATE", field: "date" }` 结构化错误,前端按 code 查 i18n key 本地化,消除中文硬编码。
|
||
|
||
### 6.4 最大化复用
|
||
|
||
- 抽取 shared `ErrorBoundary` 组件(替代 4 个重复 error.tsx)。
|
||
- 统一 stats 数据结构为单一 `AttendanceStats`。
|
||
- 常量统一从 `constants.ts` 导出。
|
||
- `StudentAttendanceView` 改为接收 `AttendanceDataService` 注入,支持 student/parent 复用。
|
||
|
||
### 6.5 错误与边界处理
|
||
|
||
- 每个独立数据区块用 React Error Boundary 包裹。
|
||
- 异步数据用 Suspense + 骨架屏。
|
||
- 明确处理空数据、无权限、网络异常。
|
||
|
||
### 6.6 可测试性
|
||
|
||
- data-access 导出 `AttendanceRepository` 接口类型供 mock。
|
||
- 纯逻辑(`computeStats`、`buildWarnings`、`aggregateStats` 等)已分离,保持。
|
||
|
||
### 6.7 可扩展性
|
||
|
||
- 角色配置驱动:`attendanceRoleConfig[role]` 决定渲染哪些 Widget/子模块。
|
||
- 新增角色仅修改配置。
|
||
|
||
### 6.8 企业级补充
|
||
|
||
- **a11y**:保持现有 ARIA/键盘导航水平,优化 keydown 监听范围。
|
||
- **性能**:RSC 获取初始数据,客户端组件最小化,支持流式渲染。
|
||
- **安全**:data-access 层结合 `dataScope` 过滤,Server Action 二次校验。
|
||
- **监控**:预留 `trackEvent` 埋点接口(点名/删除/规则保存等关键操作)。
|