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

444 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 考勤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` 埋点接口(点名/删除/规则保存等关键操作)。