# 考勤(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(合规) └─ (client) → batchRecordAttendanceAction └─ (client) → deleteAttendanceAction └─ (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; getRecentRecords(studentId: string, limit: number): Promise; getRecordsByDate(date: string, classId?: string): Promise; getClassStats(classId: string, range?: DateRange): Promise; } // src/modules/attendance/services/attendance-context.tsx // React Context 注入 const AttendanceServiceContext = createContext(null); export function AttendanceProvider({ service, children }: { service: AttendanceDataService; children: ReactNode; }) { return ( {children} ); } 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` 实现后渲染 ``,不再直接 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` 埋点接口(点名/删除/规则保存等关键操作)。