docs(audit): add audit reports for grades, homework, lesson-preparation, messaging, permissions, question-bank, settings, textbooks

- 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
This commit is contained in:
SpecialX
2026-07-03 10:23:34 +08:00
parent 365c36d97b
commit 89b9e181d2
29 changed files with 13009 additions and 230 deletions

View File

@@ -0,0 +1,443 @@
# 考勤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 节)与 JSONL14681 起)**整体覆盖** attendance 模块,但存在 **6 处信息过时/不准确**(详见第五节),需同步更新。模块导出、权限点、依赖关系、路由已记录。
---
## 二、现存问题与原因分析
### 2.1 三层架构合规性
#### 问题 2.1.1data-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.2parent 模块直接 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.3parent-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.1teacher 子页面缺失权限校验【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.2teacher/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.1safeParseDate 中文 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.2action-utils shared 层中文兜底消息【P1】
- **位置**`shared/lib/action-utils.ts:32,70,74,143`
- **描述**`NotFoundError(\`${resource} 不存在\`)`、`"操作失败,请稍后重试"`、`${fieldName} 格式无效` 等。
- **违反规则**:同上。
- **后果**:所有调用 shared 层的模块(含 attendance均受影响。
#### 问题 2.3.3Excel 导出英文列头硬编码【P1】
- **位置**[export.ts:83-84](file:///e:/Desktop/CICD/src/modules/attendance/export.ts#L83-L84)
- **描述**`"Metric"``"Value"` 硬编码。
#### 问题 2.3.4calendar 硬编码 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.5child-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.7error.tsx title/description 重复【P2】
- **位置**4 个 error.tsx
- **描述**title 和 description 均用 `t("errors.unexpected")`,完全相同。
### 2.4 类型安全
#### 问题 2.4.1export.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.2child-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.1teacher 子路由缺失 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.2student 空状态文案错误【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.24 个 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.1data-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.1getClassAttendanceStats 未用 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.2teacher 分页基于截断数据计算【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.3parent 组件可降级为 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.4statusCounts 未 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 缺权限校验且未传 scope2.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 主页面缺 requirePermission2.2.2 | 增加 `requirePermission(ATTENDANCE_READ)` |
| P1-2 | parent 直接 import attendance 组件2.1.2 | 通过接口抽象 + Context 注入,或将共享视图下沉为可注入组件 |
| P1-3 | parent-attendance-calendar 依赖 attendance/constants2.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-US2.3.4 | 使用 `useLocale()`/`getLocale()` |
| P1-8 | teacher 子路由缺 error.tsx2.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 组件可降级 RSC2.10.3 | 改用 getTranslations |
| P2-9 | statusCounts 未 memoize2.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` 埋点接口(点名/删除/规则保存等关键操作)。