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

No students in this class...

` + - 列表页则用 `EmptyState` 组件 +- **后果**:同一模块内空状态有两种写法,维护成本高,a11y 属性缺失。 + +#### 问题 2.4.4 | Server Action 错误消息英文硬编码(P2) + +- **位置**: + - [attendance/actions.ts#L56](file:///e:/Desktop/CICD/src/modules/attendance/actions.ts#L56):`"Attendance recorded"`、`"Invalid form data"`、`"Unexpected error"` + - [elective/actions.ts#L88](file:///e:/Desktop/CICD/src/modules/elective/actions.ts#L88):`"Elective course created"`、`"Course not found"`、`"Invalid form data"` +- **现象**:所有 Action 的 `message` 字段硬编码英文,未走 i18n。 +- **违反规则**:项目规则"所有用户可见文本必须适配 i18n"。 +- **后果**:toast 提示无法本地化。 + +### 2.5 组件复用与组合 + +#### 问题 2.5.1 | 考勤状态标签/颜色常量重复定义(P1) + +- **位置**: + - [attendance/types.ts#L86-L103](file:///e:/Desktop/CICD/src/modules/attendance/types.ts#L86):`ATTENDANCE_STATUS_LABELS` / `ATTENDANCE_STATUS_COLORS` + - [parent-attendance-calendar.tsx#L14-L28](file:///e:/Desktop/CICD/src/modules/parent/components/parent-attendance-calendar.tsx#L14):`STATUS_DOT` / `STATUS_LABEL`(与 attendance 重复) + - [attendance-sheet.tsx#L39-L61](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L39):`STATUS_OPTIONS` / `STATUS_SHORTCUTS` / `STATUS_STYLES`(部分重复) + - [attendance-filters.tsx#L21-L27](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-filters.tsx#L21):`STATUS_OPTIONS`(与 sheet 重复) +- **现象**:考勤状态枚举的标签、颜色、快捷键、样式在 4 个文件里各写一份。 +- **违反规则**:项目规则"最大化复用……抽象为泛型组件和 hooks"。 +- **后果**:新增状态需改 4 处;当前已出现不一致(`ATTENDANCE_STATUS_COLORS` 用 `"outline"` 表示 early_leave,但 `STATUS_STYLES` 用 `bg-blue-500`)。 + +#### 问题 2.5.2 | 选修课状态标签/颜色常量分散(P1) + +- **位置**: + - [elective/types.ts#L69-L108](file:///e:/Desktop/CICD/src/modules/elective/types.ts#L69):4 组常量(`ELECTIVE_STATUS_LABELS` / `ELECTIVE_STATUS_COLORS` / `SELECTION_MODE_LABELS` / `COURSE_SELECTION_STATUS_LABELS` / `COURSE_SELECTION_STATUS_COLORS`) + - [elective-course-form.tsx#L208-L213](file:///e:/Desktop/CICD/src/modules/elective/components/elective-course-form.tsx#L208):Select 选项硬编码 `"First Come First Served"` / `"Lottery"`(未复用 `SELECTION_MODE_LABELS`) + - [elective-filters.tsx#L40-L44](file:///e:/Desktop/CICD/src/modules/elective/components/elective-filters.tsx#L40):Select 选项硬编码(同上) +- **现象**:状态标签在 types.ts 集中定义,但表单/筛选组件未复用,重新硬编码。 +- **后果**:标签变更需改 3 处;i18n 改造时需同步多处。 + +#### 问题 2.5.3 | 考勤页面布局重复(P2) + +- **位置**: + - [admin/attendance/page.tsx#L62-L89](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/attendance/page.tsx#L62) + - [teacher/attendance/page.tsx#L63-L114](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/attendance/page.tsx#L63) +- **现象**:两个页面的标题区 + 筛选区 + 列表区结构几乎相同,仅按钮和分页略有差异。 +- **违反规则**:项目规则"最大化复用"。 +- **后果**:UI 调整需改多处。 + +#### 问题 2.5.4 | 选修课列表页布局重复(P2) + +- **位置**: + - [admin/elective/page.tsx#L30-L45](file:///e:/Desktop/CICD/src/app/(dashboard)/admin/elective/page.tsx#L30) + - [teacher/elective/page.tsx#L37-L52](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/elective/page.tsx#L37) +- **现象**:admin 和 teacher 列表页结构完全相同,仅 `createHref` 不同。 +- **后果**:同 2.5.3。 + +### 2.6 可访问性(a11y) + +#### 问题 2.6.1 | 考勤点名表单缺 aria-label(P2) + +- **位置**:[attendance-sheet.tsx#L215-L226](file:///e:/Desktop/CICD/src/modules/attendance/components/attendance-sheet.tsx#L215) +- **现象**:班级选择器 ` setSelectionMode(v as "fcfs" | "lottery")}> + + diff --git a/src/modules/elective/components/elective-course-list.tsx b/src/modules/elective/components/elective-course-list.tsx index 1740b4a..7c323f6 100644 --- a/src/modules/elective/components/elective-course-list.tsx +++ b/src/modules/elective/components/elective-course-list.tsx @@ -2,6 +2,7 @@ import { useState, useTransition } from "react" import { useRouter } from "next/navigation" +import { useTranslations } from "next-intl" import { toast } from "sonner" import { Plus, Pencil, Lock, Unlock, Shuffle, Trash2 } from "lucide-react" @@ -10,6 +11,7 @@ import { Button } from "@/shared/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" import { EmptyState } from "@/shared/components/ui/empty-state" import { usePermission } from "@/shared/hooks/use-permission" +import type { ActionState } from "@/shared/types/action-state" import { Permissions } from "@/shared/types/permissions" import { @@ -37,13 +39,14 @@ export function ElectiveCourseList({ canManage?: boolean }) { const router = useRouter() + const t = useTranslations("elective") const { hasPermission } = usePermission() const manageResolved = canManage ?? hasPermission(Permissions.ELECTIVE_MANAGE) const [pendingId, setPendingId] = useState(null) const [isPending, startTransition] = useTransition() - const runAction = async ( - action: (prevState: never, formData: FormData) => Promise<{ success: boolean; message?: string }>, + const runAction = async ( + action: (prevState: ActionState | null, formData: FormData) => Promise>, courseId: string, successMsg: string ) => { @@ -51,7 +54,7 @@ export function ElectiveCourseList({ startTransition(async () => { const formData = new FormData() formData.set("courseId", courseId) - const res = await action(null as never, formData) + const res = await action(null, formData) if (res.success) { toast.success(res.message ?? successMsg) router.refresh() @@ -82,13 +85,13 @@ export function ElectiveCourseList({

- {courses.length} course{courses.length === 1 ? "" : "s"} + {courses.length} {t("title.adminList")}

{manageResolved && createHref ? ( ) : null} @@ -96,8 +99,8 @@ export function ElectiveCourseList({ {courses.length === 0 ? ( diff --git a/src/shared/i18n/messages/en/attendance.json b/src/shared/i18n/messages/en/attendance.json new file mode 100644 index 0000000..f562c13 --- /dev/null +++ b/src/shared/i18n/messages/en/attendance.json @@ -0,0 +1,95 @@ +{ + "title": { + "adminOverview": "Attendance Overview", + "teacherRecords": "Attendance Records", + "teacherStats": "Attendance Statistics", + "sheet": "Record Attendance", + "student": "My Attendance", + "parent": "Children Attendance", + "rules": "Attendance Rules" + }, + "description": { + "adminOverview": "View attendance records for all classes school-wide.", + "teacherRecords": "Manage student attendance records.", + "teacherStats": "View class attendance statistics and analysis.", + "student": "View your attendance summary and records.", + "parent": "View your children's attendance summary and warnings." + }, + "status": { + "present": "Present", + "absent": "Absent", + "late": "Late", + "early_leave": "Early Leave", + "excused": "Excused" + }, + "stats": { + "totalRecords": "Total Records", + "present": "Present", + "absent": "Absent", + "late": "Late", + "earlyLeave": "Early Leave", + "excused": "Excused", + "attendanceRate": "Attendance Rate", + "lateRate": "Late Rate", + "recentRecords": "Recent Records" + }, + "filters": { + "class": "Class", + "status": "Status", + "date": "Date", + "allClasses": "All Classes", + "allStatuses": "All Statuses" + }, + "actions": { + "record": "Record Attendance", + "stats": "Statistics", + "save": "Save", + "delete": "Delete", + "cancel": "Cancel", + "markAllPresent": "Mark All Present" + }, + "list": { + "empty": "No attendance records", + "emptyDescription": "No attendance records have been generated yet.", + "emptyTeacherDescription": "Start recording attendance for your class.", + "columns": { + "student": "Student", + "class": "Class", + "date": "Date", + "status": "Status", + "remark": "Remark", + "recorder": "Recorder", + "createdAt": "Created At" + } + }, + "sheet": { + "selectClass": "Select Class", + "selectDate": "Select Date", + "noStudents": "No students in this class", + "confirmDelete": "Are you sure you want to delete this attendance record?", + "saved": "Attendance saved", + "updated": "Attendance updated", + "deleted": "Attendance record deleted" + }, + "rules": { + "lateThreshold": "Late Threshold (minutes)", + "earlyLeaveThreshold": "Early Leave Threshold (minutes)", + "enableAutoMark": "Enable Auto Mark", + "saved": "Attendance rules saved" + }, + "errors": { + "notFound": "Attendance record not found", + "noOwnership": "You do not own this attendance record", + "invalidForm": "Invalid form data", + "unexpected": "Unexpected error" + }, + "parent": { + "warningTitle": "Attendance Warnings", + "rateCardTitle": "Attendance Rate Summary", + "calendarTitle": "Attendance Calendar", + "noWarnings": "No attendance warnings", + "absentWarning": "{count} absence(s)", + "lateWarning": "{count} late arrival(s)", + "lowRateWarning": "Attendance rate {rate}% below threshold" + } +} diff --git a/src/shared/i18n/messages/en/elective.json b/src/shared/i18n/messages/en/elective.json new file mode 100644 index 0000000..90a8d12 --- /dev/null +++ b/src/shared/i18n/messages/en/elective.json @@ -0,0 +1,96 @@ +{ + "title": { + "adminList": "Elective Courses", + "create": "Create Course", + "edit": "Edit Course", + "teacher": "My Elective Courses", + "student": "Course Selection" + }, + "description": { + "adminList": "Manage elective courses, open/close selection and lottery.", + "teacher": "View and manage the elective courses you teach.", + "student": "Browse available courses and make selections." + }, + "status": { + "draft": "Draft", + "open": "Open", + "closed": "Closed", + "cancelled": "Cancelled" + }, + "selectionMode": { + "fcfs": "First Come First Served", + "lottery": "Lottery" + }, + "selectionStatus": { + "selected": "Selected", + "enrolled": "Enrolled", + "waitlist": "Waitlist", + "dropped": "Dropped", + "rejected": "Rejected" + }, + "fields": { + "name": "Course Name", + "subject": "Subject", + "teacher": "Teacher", + "grade": "Grade", + "description": "Description", + "capacity": "Capacity", + "enrolled": "Enrolled", + "classroom": "Classroom", + "schedule": "Schedule", + "startDate": "Start Date", + "endDate": "End Date", + "selectionStart": "Selection Start", + "selectionEnd": "Selection End", + "selectionMode": "Selection Mode", + "credit": "Credit" + }, + "actions": { + "create": "Create Course", + "edit": "Edit", + "delete": "Delete", + "openSelection": "Open Selection", + "closeSelection": "Close Selection", + "runLottery": "Run Lottery", + "select": "Select", + "drop": "Drop", + "cancel": "Cancel", + "save": "Save" + }, + "list": { + "empty": "No elective courses", + "emptyStudent": "No available courses", + "emptyDescription": "No elective courses have been created yet." + }, + "form": { + "createTitle": "Create Elective Course", + "editTitle": "Edit Elective Course", + "namePlaceholder": "Enter course name", + "descriptionPlaceholder": "Enter course description" + }, + "student": { + "mySelections": "My Selections", + "availableCourses": "Available Courses", + "selected": "Selected", + "enrolled": "Enrolled", + "waitlist": "Waitlist", + "capacityFull": "Capacity full", + "selectSuccess": "Course selected successfully", + "dropSuccess": "Course dropped successfully", + "confirmDrop": "Are you sure you want to drop this course?" + }, + "lottery": { + "result": "Lottery result: {enrolled} enrolled, {waitlist} waitlisted", + "running": "Running lottery..." + }, + "errors": { + "notFound": "Course not found", + "noOwnership": "You do not own this course", + "capacityFull": "Course capacity is full", + "alreadySelected": "You have already selected this course", + "selectionClosed": "Selection is closed", + "gradeMismatch": "Your grade does not match the course requirement", + "invalidForm": "Invalid form data", + "unexpected": "Unexpected error" + } +} diff --git a/src/shared/i18n/messages/zh-CN/attendance.json b/src/shared/i18n/messages/zh-CN/attendance.json new file mode 100644 index 0000000..d78c02c --- /dev/null +++ b/src/shared/i18n/messages/zh-CN/attendance.json @@ -0,0 +1,95 @@ +{ + "title": { + "adminOverview": "考勤总览", + "teacherRecords": "考勤记录", + "teacherStats": "考勤统计", + "sheet": "录入考勤", + "student": "我的考勤", + "parent": "子女考勤", + "rules": "考勤规则" + }, + "description": { + "adminOverview": "查看全校所有班级的考勤记录。", + "teacherRecords": "管理学生考勤记录。", + "teacherStats": "查看班级考勤统计分析。", + "student": "查看个人考勤汇总与记录。", + "parent": "查看子女考勤汇总与异常预警。" + }, + "status": { + "present": "出勤", + "absent": "缺勤", + "late": "迟到", + "early_leave": "早退", + "excused": "请假" + }, + "stats": { + "totalRecords": "总记录数", + "present": "出勤", + "absent": "缺勤", + "late": "迟到", + "earlyLeave": "早退", + "excused": "请假", + "attendanceRate": "出勤率", + "lateRate": "迟到率", + "recentRecords": "最近记录" + }, + "filters": { + "class": "班级", + "status": "状态", + "date": "日期", + "allClasses": "全部班级", + "allStatuses": "全部状态" + }, + "actions": { + "record": "录入考勤", + "stats": "统计分析", + "save": "保存", + "delete": "删除", + "cancel": "取消", + "markAllPresent": "全部标记到场" + }, + "list": { + "empty": "暂无考勤记录", + "emptyDescription": "系统中尚未产生任何考勤记录。", + "emptyTeacherDescription": "开始为您的班级录入考勤。", + "columns": { + "student": "学生", + "class": "班级", + "date": "日期", + "status": "状态", + "remark": "备注", + "recorder": "记录人", + "createdAt": "创建时间" + } + }, + "sheet": { + "selectClass": "选择班级", + "selectDate": "选择日期", + "noStudents": "该班级暂无学生", + "confirmDelete": "确定删除此条考勤记录吗?", + "saved": "考勤已保存", + "updated": "考勤已更新", + "deleted": "考勤记录已删除" + }, + "rules": { + "lateThreshold": "迟到阈值(分钟)", + "earlyLeaveThreshold": "早退阈值(分钟)", + "enableAutoMark": "启用自动标记", + "saved": "考勤规则已保存" + }, + "errors": { + "notFound": "考勤记录不存在", + "noOwnership": "您无权操作此考勤记录", + "invalidForm": "表单数据无效", + "unexpected": "发生未知错误" + }, + "parent": { + "warningTitle": "考勤异常预警", + "rateCardTitle": "出勤率汇总", + "calendarTitle": "考勤月历", + "noWarnings": "暂无考勤异常", + "absentWarning": "{count} 次缺勤", + "lateWarning": "{count} 次迟到", + "lowRateWarning": "出勤率 {rate}% 低于阈值" + } +} diff --git a/src/shared/i18n/messages/zh-CN/elective.json b/src/shared/i18n/messages/zh-CN/elective.json new file mode 100644 index 0000000..5d2532d --- /dev/null +++ b/src/shared/i18n/messages/zh-CN/elective.json @@ -0,0 +1,96 @@ +{ + "title": { + "adminList": "选修课程", + "create": "创建课程", + "edit": "编辑课程", + "teacher": "我的选修课", + "student": "选课中心" + }, + "description": { + "adminList": "管理选修课程、开放/关闭选课与抽签。", + "teacher": "查看和管理您教授的选修课程。", + "student": "浏览可选课程并进行选课。" + }, + "status": { + "draft": "草稿", + "open": "开放选课", + "closed": "已关闭", + "cancelled": "已取消" + }, + "selectionMode": { + "fcfs": "先到先得", + "lottery": "抽签" + }, + "selectionStatus": { + "selected": "已选", + "enrolled": "已录取", + "waitlist": "候补", + "dropped": "已退选", + "rejected": "已拒绝" + }, + "fields": { + "name": "课程名称", + "subject": "科目", + "teacher": "授课教师", + "grade": "年级", + "description": "课程描述", + "capacity": "容量", + "enrolled": "已录取", + "classroom": "教室", + "schedule": "上课时间", + "startDate": "开始日期", + "endDate": "结束日期", + "selectionStart": "选课开始", + "selectionEnd": "选课结束", + "selectionMode": "选课模式", + "credit": "学分" + }, + "actions": { + "create": "创建课程", + "edit": "编辑", + "delete": "删除", + "openSelection": "开放选课", + "closeSelection": "关闭选课", + "runLottery": "执行抽签", + "select": "选课", + "drop": "退课", + "cancel": "取消", + "save": "保存" + }, + "list": { + "empty": "暂无选修课程", + "emptyStudent": "暂无可选课程", + "emptyDescription": "系统中尚未创建任何选修课程。" + }, + "form": { + "createTitle": "创建选修课程", + "editTitle": "编辑选修课程", + "namePlaceholder": "请输入课程名称", + "descriptionPlaceholder": "请输入课程描述" + }, + "student": { + "mySelections": "我的选课", + "availableCourses": "可选课程", + "selected": "已选", + "enrolled": "已录取", + "waitlist": "候补", + "capacityFull": "名额已满", + "selectSuccess": "选课成功", + "dropSuccess": "退课成功", + "confirmDrop": "确定退选此课程吗?" + }, + "lottery": { + "result": "抽签结果:录取 {enrolled} 人,候补 {waitlist} 人", + "running": "抽签进行中..." + }, + "errors": { + "notFound": "课程不存在", + "noOwnership": "您无权操作此课程", + "capacityFull": "课程名额已满", + "alreadySelected": "您已选过此课程", + "selectionClosed": "选课已关闭", + "gradeMismatch": "您的年级不符合课程要求", + "invalidForm": "表单数据无效", + "unexpected": "发生未知错误" + } +}