Files
NextEdu/docs/architecture/004_architecture_impact_map.md
SpecialX 27db170c0a docs: update architecture docs, audit reports, and bug tracking
- Update architecture impact map, data, feature checklist, gap audit

- Add audit reports for dashboard, exam-homework, grades-diagnostic, settings-profile, textbooks

- Update bug reports (admin, teacher, lesson-preparation, others, shared)

- Update coding standards, DR plan, design docs, and README
2026-06-23 17:36:18 +08:00

2705 lines
265 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
# Next_Edu 架构影响地图
> 重写日期2026-06-17
> 目标:一次阅读即可直观理解整个项目架构
> 规则:源码修改后须同步更新本文档与 `005_architecture_data.json`
> 审查依据:`docs/architecture/audit/` 下 4 份审查报告
---
## 目录
- [第一部分:全局架构概览](#第一部分全局架构概览)
- [1.1 分层架构图](#11-分层架构图)
- [1.2 模块依赖关系图](#12-模块依赖关系图)
- [1.3 数据流向图(考试流程)](#13-数据流向图考试流程)
- [1.4 核心调用链路](#14-核心调用链路)
- [第二部分:模块清单](#第二部分模块清单)
- [第三部分:已知架构问题和技术债](#第三部分已知架构问题和技术债)
- [附录 A模块间依赖矩阵](#附录-a模块间依赖矩阵)
- [附录 B关键参数影响链](#附录-b关键参数影响链)
- [附录 C核心函数签名索引](#附录-c核心函数签名索引)
---
# 第一部分:全局架构概览
## 1.1 分层架构图
项目采用**严格三层架构**,依赖方向必须单向:`app → modules → shared`
```
┌─────────────────────────────────────────────────────────────────────┐
│ app/ (路由层) │
│ ───────────────────────────────────────────────────────────────── │
│ (auth)/ login / register / privacy / terms │
│ (dashboard)/ admin / teacher / student / parent / management │
│ api/ REST API (auth, ai, upload, files, search, ...) │
└──────────────────────────────┬──────────────────────────────────────┘
│ 调用 Server Actions / data-access
┌─────────────────────────────────────────────────────────────────────┐
│ modules/ (业务模块层) │
│ ───────────────────────────────────────────────────────────────── │
│ 核心教学: exams · homework · questions · textbooks · grades │
│ · lesson-preparation │
│ 教学管理: classes · school · scheduling · attendance · course-plans│
│ 用户与沟通users · messaging · notifications · parent · audit │
│ 扩展功能: elective · proctoring · diagnostic · dashboard │
│ 其他: announcements · files · settings · auth · layout · student│
│ │
│ 每个模块标准结构actions.ts (编排) → data-access.ts (DB) → schema.ts│
└──────────────────────────────┬──────────────────────────────────────┘
│ 依赖
┌─────────────────────────────────────────────────────────────────────┐
│ shared/ (基础设施层) │
│ ───────────────────────────────────────────────────────────────── │
│ db/ schema.ts (54 张表) + relations.ts + index.ts │
│ lib/ auth-guard · permissions · ai · audit-logger · │
│ change-logger · login-logger · password-policy · │
│ rate-limit · excel · file-storage · ... │
│ hooks/ use-permission · use-aria-live · ... │
│ components/ ui/ (shadcn) · a11y/ · global-search · ... │
│ types/ permissions · action-state │
└─────────────────────────────────────────────────────────────────────┘
│ 反向依赖(违规,见 1.2
┌──────────────────────────────┴──────────────────────────────────────┐
│ 根模块src/auth.ts (NextAuth 配置) · src/proxy.ts (中间件) │
│ ✅ shared/lib/{audit-logger, change-logger, auth-guard} 已通过 │
│ shared/lib/session.ts 单一入口获取 session不再直依赖 @/auth │
│ (详见第三部分 P0-2
└─────────────────────────────────────────────────────────────────────┘
```
**分层规则**
- `app/` 只能调用 `modules/` 的 Server Actions 和 data-access不直接访问 DB
- `modules/` 之间通过对方 data-access 通信,不直接查询对方表
- `shared/` 是被依赖方,不应反向依赖 `app/``modules/`
- `src/auth.ts``src/proxy.ts` 位于根目录,属于应用层
---
## 1.2 模块依赖关系图
下图展示模块间的实际依赖关系,**标注依赖类型与合规性**。
### 图例
- `───▶` 合理依赖(通过 data-access 或类型导入)
- `═══▶` 违规依赖(直接查询对方 DB 表)
- `⟳ ` 循环依赖
- `──▷` UI 组件组合(合理)
### 1.2.1 核心业务模块依赖
```
┌──────────────┐
│ textbooks │ ◀── 标杆模块(无跨模块 DB 访问)
└──────┬───────┘
│ ──▷ UI 组合knowledge-point-dialogs
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌────────────┐
│ questions │ │ exams │ │ homework │
└─────┬──────┘ └────┬─────┘ └─────┬──────┘
│ ✅ P1-1 │ ✅ P1-1 │ ✅ P1-1 已修复
│ 通过 textbooks│ 通过 questions│ 通过 exams/classes/
│ data-access │ data-access │ school/users data-access
│ │ 通过 classes │
│ │ data-access │
┌─┴────────┐ │ │
│ grades │◀────┘ 仅外键引用(合理)
│ (成绩) │
└────┬─────┘
│ ✅ P1-1 已修复
│ 通过 classes/school/users data-access
┌──────────┐
│ classes │ ✅ P1-1 已修复:通过 homework/data-access-classes
└────┬─────┘ 获取作业数据,不再直查 homework/exams 表
│ ✅ P0-1 已修复data-access.ts 已拆分为 5 文件
│ ✅ P0-5 已修复classSchedule 写入口统一到 scheduling
┌─────────┼─────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────┐ ┌──────────┐
│scheduling│ │school│ │ attendance│
└────┬─────┘ └──────┘ └──────────┘
│ ✅ P0-5 已修复
│ classSchedule 写入口统一到 scheduling
│ 通过 classes/data-access 校验归属
│ ✅ P1-1 已修复:通过 users data-access
```
### 1.2.2 扩展模块依赖
```
┌─────────────┐ ✅ P0-3 已修复:通过各模块 data-access ┌──────────────┐
│ dashboard │ 获取数据getUsersDashboardStats 等) │ users/classes│
│ (聚合层) │──────────────────────────────────────▶│ /exams/... │
└─────────────┘ ✅ P0-4 已修复Promise.all 并行调用 └──────────────┘
┌─────────────┐ ─── 调用 data-access合理 ┌──────────────┐
│ parent │──────────────────────────────▶│ classes/ │
│ (聚合层) │ ✅ P1-1 已修复:通过各模块 │ homework/grades│
└─────────────┘ data-access 获取数据 └──────────────┘
┌─────────────┐ ✅ P1-1 已修复:通过 exams/questions/ ┌──────────────┐
│ diagnostic │ classes/users data-access 获取数据 │ exams/questions│
│ │──────────────────────────────────────▶│ /classes/users│
└─────────────┘ └──────────────┘
┌─────────────┐ ✅ P1-1 已修复:通过 exams/users ┌──────────────┐
│ proctoring │ data-access 获取数据 │ exams/users │
└─────────────┘──────────────────────────────────────▶└──────────────┘
┌─────────────┐ ─── 调用 notifications dispatcher ┌──────────────┐
│ messaging │ (通知偏好/CRUD 已迁移至 notifications│ notifications │
│ │──────────────────────────────────────▶│ (拥有 │
│ │ ✅ P0-4 / P1-5 已修复:单向依赖 │ messageNotif│
└─────────────┘ │ ications + │
│ preferences)│
└──────────────┘
┌─────────────┐ ─── 调用 messaging Action ┌──────────────┐
│ settings │ (通知偏好表单) │ messaging │
└─────────────┘──────────────────────────────▶└──────────────┘
┌─────────────────┐ ───▶ data-access合理 ┌──────────────┐
│ lesson-prep │──────────────────────────────▶│ textbooks │
│ (备课聚合层) │ 只读章节/知识点树 │ (章节/KP 树) │
│ │──────────────────────────────▶└──────────────┘
│ │──────────────────────────────▶┌──────────────┐
│ │ 创建/查询题目 │ questions │
│ │──────────────────────────────▶└──────────────┘
│ │──────────────────────────────▶┌──────────────┐
│ │ 创建 exam 草稿 │ exams │
│ │──────────────────────────────▶└──────────────┘
│ │──────────────────────────────▶┌──────────────┐
│ │ 创建作业下发 │ homework │
│ │──────────────────────────────▶└──────────────┘
│ │──────────────────────────────▶┌──────────────┐
│ │ 查询教师班级 │ classes │
│ │──────────────────────────────▶└──────────────┘
│ │──────────────────────────────▶┌──────────────┐
│ │ 附件 │ files │
└─────────────────┘──────────────────────────────▶└──────────────┘
```
### 1.2.3 循环依赖详情 ✅ 已修复
```
shared/lib/audit-logger.ts ──┐
shared/lib/change-logger.ts ──┼──▶ shared/lib/session.ts ──▶ (dynamic import) @/auth
shared/lib/auth-guard.ts ──┘
src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
──▶ import { ... } from "@/shared/lib/login-logger"
──▶ import { ... } from "@/shared/lib/password-policy"
──▶ import { ... } from "@/shared/lib/rate-limit"
──▶ import { ... } from "@/shared/lib/role-utils" # P1-3 拆出
──▶ import { ... } from "@/shared/lib/bcrypt-utils" # P1-3 拆出
──▶ import { ... } from "@/shared/lib/http-utils" # P1-3 拆出
──▶ import { ... } from "@/shared/lib/password-security-service" # P1-3 拆出
──▶ import { db, schema } from "@/shared/db"
✅ 修复shared/lib/* 不再静态 import @/auth统一通过 session.ts 单一入口
session.ts 内部使用 dynamic import("@/auth") 打破模块级静态循环
运行时调用链保持不变,模块加载图无环
```
---
## 1.3 数据流向图(考试流程)
以"考试流程"为例,展示数据从创建到成绩统计的完整流向。
```
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 1教师创建考试 │
│ ───────────────────────────────────────────────────────────────── │
│ teacher/exams/create/page.tsx │
│ └─▶ exams/actions.createExamAction │
│ ├─▶ requirePermission(EXAM_CREATE) [shared/auth-guard] │
│ ├─▶ persistExamDraft() [exams/data-access] │
│ │ └─▶ db.insert(exams) [shared/db] │
│ │ └─▶ db.insert(examQuestions) [shared/db] │
│ └─▶ revalidatePath("/teacher/exams") │
│ │
│ 数据写入exams 表 + examQuestions 表 │
│ ✅ P0-1 已修复persistAiGeneratedExamDraft 改为调用 questions/data-access.createQuestionWithRelations不再直查 questions 表 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 2学生作答作业化考试
│ ───────────────────────────────────────────────────────────────── │
│ student/learning/assignments/[assignmentId]/page.tsx │
│ └─▶ homework/actions.startHomeworkSubmissionAction │
│ ├─▶ requirePermission(HOMEWORK_SUBMIT) │
│ ├─▶ data-access-write.startHomeworkSubmission ✅ P1-2 已修复 │
│ │ └─▶ db.insert(homeworkSubmissions) [shared/db] │
│ └─▶ 返回 submissionId │
│ │
│ └─▶ homework/actions.saveHomeworkAnswerAction │
│ └─▶ data-access-write.saveHomeworkAnswer ✅ P1-2 已修复 │
│ └─▶ db.transaction(insert homeworkAnswers) [shared/db] │
│ │
│ └─▶ homework/actions.submitHomeworkAction │
│ └─▶ data-access-write.submitHomework ✅ P1-2 已修复 │
│ └─▶ db.update(homeworkSubmissions.status="submitted") │
│ │
│ 数据写入homeworkSubmissions 表 + homeworkAnswers 表 │
│ ✅ P1-2 已修复actions 层不再直接 DB 操作,已下沉到 data-access-write│
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 3教师批改 │
│ ───────────────────────────────────────────────────────────────── │
│ teacher/homework/submissions/[submissionId]/page.tsx │
│ └─▶ homework/actions.gradeHomeworkSubmissionAction │
│ ├─▶ requirePermission(HOMEWORK_GRADE) │
│ └─▶ data-access-write.gradeHomeworkSubmission ✅ P1-2 已修复│
│ └─▶ db.update(homeworkAnswers) 循环 [shared/db] │
│ │
│ 数据更新homeworkAnswers.isCorrect / score / feedback │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 4成绩统计与诊断 │
│ ───────────────────────────────────────────────────────────────── │
│ teacher/grades/page.tsx │
│ └─▶ grades/data-access.getGradeRecords │
│ └─▶ ✅ P1-1 已修复:通过 classes/school/users data-access │
│ │
│ teacher/diagnostic/page.tsx │
│ └─▶ diagnostic/data-access.updateMasteryFromSubmission │
│ ├─▶ ✅ P1-1 已修复:通过 exams data-access 获取提交 │
│ ├─▶ ✅ P1-1 已修复:通过 questions data-access 获取知识点 │
│ └─▶ 更新 knowledgePointMastery │
│ │
│ 数据读取homeworkSubmissions → grades → knowledgePointMastery │
└─────────────────────────────────────────────────────────────────────┘
```
**关键观察**:考试流程横跨 4 个模块exams → homework → grades → diagnostic✅ P1-1 已修复:所有跨模块查询已改为通过对方 data-access 接口,模块封装性已恢复。
---
## 1.4 核心调用链路
### 1.4.1 调用链路:创建考试(含 AI 出题)
```
[Client] exam-form.tsx
│ FormData
[Route] POST /teacher/exams/create (Server Action)
[Action] exams/actions.createAiExamAction
├─▶ requirePermission(EXAM_CREATE)
│ └─▶ shared/lib/auth-guard.getAuthContext()
│ ├─▶ auth() [src/auth.ts]
│ ├─▶ db.query.usersToRoles [shared/db]
│ ├─▶ db.query.classSubjectTeachers
│ └─▶ 返回 { userId, roles, permissions, dataScope }
├─▶ generateAiCreateDraftFromSource()
│ └─▶ exams/ai-pipeline.ts (912 行)
│ ├─▶ shared/lib/ai.createAiChatCompletion()
│ │ └─▶ OpenAI SDK + db.query.aiProviders
│ └─▶ JSON 解析 + Zod 校验
├─▶ persistAiGeneratedExamDraft()
│ └─▶ exams/data-access.ts
│ ├─▶ db.insert(exams) ✅ 合理
│ ├─▶ db.insert(examQuestions) ✅ 合理
│ └─▶ questions/data-access.createQuestionWithRelations ✅ P0-1 已修复:通过 data-access
└─▶ revalidatePath("/teacher/exams")
```
### 1.4.2 调用链路:学生提交作业
```
[Client] homework-take-view.tsx
[Action] homework/actions.submitHomeworkAction
├─▶ requirePermission(HOMEWORK_SUBMIT)
├─▶ data-access-write.submitHomework ✅ P1-2 已修复
│ (校验 submission 归属 + 更新状态)
│ └─▶ db.update(homeworkSubmissions)
│ SET status = "submitted", submittedAt = now()
└─▶ 返回 ActionState<{ submissionId }>
```
### 1.4.3 调用链路:管理员仪表盘聚合
```
[Route] /admin/dashboard/page.tsx (Server Component)
[DataAccess] dashboard/data-access.getAdminDashboardData
├─▶ users/data-access.getUsersDashboardStats() ✅ 通过模块 data-access
│ ├─ userCount / activeSessionsCount / userRoleCounts
│ └─ recentUsers (含角色解析)
├─▶ classes/data-access.getClassesDashboardStats() ✅ 通过模块 data-access
│ └─ classCount
├─▶ textbooks/data-access.getTextbooksDashboardStats() ✅ 通过模块 data-access
│ └─ textbookCount / chapterCount
├─▶ questions/data-access.getQuestionsDashboardStats() ✅ 通过模块 data-access
│ └─ questionCount
├─▶ exams/data-access.getExamsDashboardStats(scope?) ✅ 通过模块 data-access
│ └─ examCount (含 scope 过滤)
└─▶ homework/stats-service.getHomeworkDashboardStats(scope?) ✅ 通过模块 data-access
├─ homeworkAssignmentCount / homeworkAssignmentPublishedCount
└─ homeworkSubmissionCount / homeworkSubmissionToGradeCount
✅ P0-4 已修复dashboard 改为并行调用各模块 dashboard stats 函数,不再直查跨模块表
```
### 1.4.4 调用链路admin 路由组统一权限守卫
```
[Layout] app/(dashboard)/admin/layout.tsx (Server Component)
[AuthGuard] shared/lib/auth-guard.getAuthContext()
└─▶ getSession() → 校验已登录(未登录抛 PermissionDeniedError
[Children] 各 admin/* 页面page.tsx在函数体首行调用 requirePermission(XXX)
├─▶ /admin/school/schools → requirePermission(SCHOOL_MANAGE)
├─▶ /admin/school/academic-year → requirePermission(SCHOOL_MANAGE)
├─▶ /admin/school/classes → requirePermission(SCHOOL_MANAGE)
├─▶ /admin/school/departments → requirePermission(SCHOOL_MANAGE)
├─▶ /admin/school/grades → requirePermission(SCHOOL_MANAGE)
├─▶ /admin/school/grades/insights → requirePermission(SCHOOL_MANAGE)
├─▶ /admin/users/import → requirePermission(USER_MANAGE)
├─▶ /admin/users → requirePermission(USER_MANAGE)
├─▶ /admin/scheduling/auto → requirePermission(SCHEDULE_AUTO)
├─▶ /admin/scheduling/changes → requirePermission(SCHEDULE_ADJUST)
├─▶ /admin/scheduling/rules → requirePermission(SCHEDULE_ADJUST)
├─▶ /admin/announcements → requirePermission(ANNOUNCEMENT_MANAGE)
├─▶ /admin/announcements/[id] → requirePermission(ANNOUNCEMENT_MANAGE)
└─▶ /admin/audit-logs → requirePermission(AUDIT_LOG_READ)
✅ P0 安全修复admin/layout.tsx 提供登录态统一守卫,
各页面 requirePermission() 提供细粒度权限校验
teacher/classes/* 路由权限校验2026-06-22 审计修复):
├─▶ /teacher/classes/my → requirePermission(CLASS_READ) ✅ 已修复
├─▶ /teacher/classes/my/[id] → requirePermission(CLASS_READ) ✅ 已修复
├─▶ /teacher/classes/schedule → requirePermission(CLASS_READ) ✅ 已修复
└─▶ /teacher/classes/students → requirePermission(CLASS_READ) ✅ 已修复
```
---
# 第二部分:模块清单
> 每个模块包含:职责 · 导出函数 · 依赖关系 · 已知问题 · 文件清单
## 2.1 shared基础设施层
**职责**:提供全项目共享的 DB Schema、工具函数、权限系统、UI 基础组件、通用 Hooks。
**导出函数**(核心):
- `getAuthContext()` / `requirePermission(p)` / `requireAuth()` — 认证与权限
- `resolvePermissions(roles)` / `resolveDataScope(userId, roles)` — 权限解析
- `logAudit()` / `logLoginEvent()` / `logDataChange()` — 日志记录
- `createAiChatCompletion()` / `parseAiChatPayload()` — AI 调用
- `validatePassword()` / `isAccountLocked()` / `rateLimit()` — 安全策略
- `exportToExcel()` / `parseExcel()` / `generateTemplate()` — Excel 工具
- `cn()` / `formatDate()` / `formatFileSize()` — 通用工具
- `getInitials(name)` / `formatDateForFile(d?)` — 通用工具P1-c / P1-a 重构新增:从 parent/lib/utils.ts、grades/export-button.tsx 等多处重复实现抽取)
- `downloadBase64File(base64, filename, mimeType?)` / `downloadBlob(blob, filename)` — 客户端文件下载P1-c 重构新增:从 grades/export-button、users/user-import-dialog、audit/audit-log-export-button 三处重复实现抽取,位于 `lib/download.ts`
- `handleActionError(e)` / `safeActionCall(action, options?)` / `safeJsonParse(json, msg)` / `safeParseDate(v, field)` / `safeParseNumber(v, field)` / `escapeLikePattern(input)` — Server Action 错误处理与客户端调用包装2026-06-23 审计修复新增:位于 `lib/action-utils.ts`,统一所有 Server Action catch 块错误处理,避免内部错误消息暴露给客户端)
- `BusinessError` / `NotFoundError` / `ValidationError` — 错误类2026-06-23 审计修复新增:位于 `lib/action-utils.ts`已知业务错误基类message 可安全返回客户端)
**共享组件导出**P0-b / P1-a / P1-b / P1-c / P2-a / P2-b / P3-a / P3-b / P3-c / P3-d / 第二轮 P0-1/P0-2/P0-3/P1-1/P1-2/P1-3/P1-4 重构新增,按类别组织):
| 类别 | 组件 | 文件 | 用途 | 消费方数量 |
|------|------|------|------|-----------|
| **UI 组件** | `StatCard` | `components/ui/stat-card.tsx` | 统计卡片(标题+数值+图标+描述+跳转+骨架屏) | 8 个P1-a |
| **UI 组件** | `StatItem` | `components/ui/stat-item.tsx` | 紧凑统计项label+icon+value+hint用于统计面板网格 | 8 个P1-a |
| **UI 组件** | `ChipNav` | `components/ui/chip-nav.tsx` | 芯片导航组(通过 URL search params 切换筛选维度Link 跳转) | 3 个P1-b |
| **UI 组件** | `PageHeader` | `components/ui/page-header.tsx` | 页面头部(标题+描述+icon+actions响应式布局 | 2 个P2-b: profile/page.tsx, settings/security/page.tsx |
| **UI 组件** | `FilterBar` / `FilterSearchInput` / `FilterResetButton` | `components/ui/filter-bar.tsx` | 筛选栏容器+搜索框+重置按钮统一布局壳URL 状态由各模块处理) | 5 个P3-b: exam/textbook/question/audit-log/login-log filters |
| **UI 组件** | `ConfirmDeleteDialog` | `components/ui/confirm-delete-dialog.tsx` | 通用删除确认对话框AlertDialog 包装,支持自定义 confirmText/cancelText | 5 个P0-1: announcement-detail, message-detail, course-plan-detail, grade-classes-view, students-table |
| **UI 组件** | `Pagination` | `components/ui/pagination.tsx` | 通用分页 UIShowing X-Y of Z + Page X of Y + 上一页/下一页按钮) | 3 个P0-2: audit-log-table, login-log-table, data-change-log-table |
| **UI 组件** | `EmptyTableRow` | `components/ui/empty-table-row.tsx` | 表格空状态行TableRow + TableCell 居中显示空状态文案) | 3 个P0-3: audit-log-table, login-log-table, data-change-log-table |
| **UI 组件** | `StatusBadge` | `components/ui/status-badge.tsx` | 通用状态徽章Badge + 状态→variant/label/className 映射表,修复 in_progress 颜色不一致 bug | 9+ 个P1-1: audit 3 文件, grades 2 文件, student/learning/assignments, parent/child-homework-summary, student-upcoming-assignments-card, question-columns |
| **表单字段** | `TextField` | `components/form-fields/text-field.tsx` | 通用文本字段FormField + Input 包装,支持 text/number/password/datetime-local 类型 + value 转换器) | 3 个文件 16 处P1-2: profile-settings-form 6, exam-basic-info-form 4, ai-provider-settings-card 4 |
| **表单字段** | `SelectField` | `components/form-fields/select-field.tsx` | 通用选择字段FormField + Select 包装,支持 toSelectValue/fromSelectValue 处理 number↔string | 4 个文件 8 处P1-2: exam-basic-info-form 3, ai-provider-settings-card 1, create-question-dialog 2, profile-settings-form 1 |
| **表单字段** | `TextareaField` | `components/form-fields/textarea-field.tsx` | 通用多行文本字段FormField + Textarea 包装) | 1 个P1-2: create-question-dialog |
| **图表组件** | `ChartCardShell` | `components/charts/chart-card-shell.tsx` | 图表卡片外壳Card+Header+EmptyState+Content 统一结构) | 8 个P3-c |
| **图表组件** | `TrendLineChart` | `components/charts/trend-line-chart.tsx` | 趋势折线图LineChart 统一配置,支持单/多系列) | 8 个P3-c: grade-trend-chart 等) |
| **图表组件** | `SimpleBarChart` | `components/charts/simple-bar-chart.tsx` | 柱状图BarChart 统一配置,支持单/多 Bar + Cell 分桶着色 + defs 自定义 SVG 图案) | 8 个P3-c: grade-distribution-chart 等) |
| **图表组件** | `ComparisonRadarChart` | `components/charts/comparison-radar-chart.tsx` | 对比雷达图RadarChart 统一配置,支持双 Radar 对比) | 8 个P3-c: subject-comparison-chart, mastery-radar-chart 等) |
| **课表组件** | `ScheduleList` / `ScheduleListItem` | `components/schedule/schedule-list.tsx` | 课表列表+列表项(课程+时间+地点+班级徽章separator/card 两种变体) | 3 个P3-a: student-today-schedule-card, child-schedule-card, student-schedule-view |
| **题库组件** | `QuestionBankFilters` | `components/question/question-bank-filters.tsx` | 题库筛选栏(搜索+题型+难度default/compact 两种布局) | 2 个P3-d: exam-assembly, question-bank-picker |
| **设置组件** | `SettingsView` | `modules/settings/components/settings-view.tsx` | 统一设置页布局5 标签页General/Notifications/Appearance/Security/AI角色差异通过 props 注入Tab URL 持久化,登出二次确认) | 4 个P2-a: admin/teacher/student/parent 设置页) |
**共享 Hooks 导出**(第二轮 P1-4 重构新增):
| Hook | 文件 | 签名 | 用途 | 消费方 |
|------|------|------|------|--------|
| `useActionMutation` | `hooks/use-action-mutation.ts` | `useActionMutation<T>(options?): { isWorking, mutate }` | 通用 Server Action mutation Hook替代 50+ 文件中重复的 setIsWorking + try/catch/finally + toast 模式 | 1 个示范P1-4: schools-view潜在影响 50+ 文件 |
| `useActionQuery` | `hooks/use-action-query.ts` | `useActionQuery<T>(action, options?): { data, loading, error, refetch }` | 通用 Server Action 查询 Hook替代 11 个文件中重复的 useEffect + useState(loading) + Action().then().catch().finally() 模式,内置竞态防护 | 1 个示范P1-4: create-question-dialog潜在影响 11 个文件 |
**共享工具函数导出**(第二轮 P1-3 重构新增):
| 函数 | 文件 | 签名 | 用途 | 消费方 |
|------|------|------|------|--------|
| `formatDateTime` | `lib/utils.ts` | `formatDateTime(date, locale?): string` | 国际化日期+时间格式化(含小时、分钟) | 4 个P1-3: lesson-plan-card, version-history-drawer, proctoring-dashboard, exam-ai-generator |
| `formatLongDate` | `lib/utils.ts` | `formatLongDate(date, locale?): string` | 国际化长日期格式化(含星期、完整月份名),默认 zh-CNweekday=short | 1 个P1-3: teacher-dashboard-header |
> 注:`SettingsView` 位于 `modules/settings/components/`(非 shared 层),因仅被 settings 模块消费,未下沉到 shared。此处列出以完整反映本次重构的组件抽取范围。
**依赖关系**
- 被依赖方:**所有模块**依赖 shared
- ✅ 反向依赖已修复:`shared/lib/{audit-logger, change-logger, auth-guard}` 通过 `shared/lib/session.ts` 单一入口获取 session不再直接依赖 `@/auth`
**已知问题**
- ✅ P0~~`shared/lib/*``@/auth` 循环依赖~~ 已修复(新增 `shared/lib/session.ts` 封装 session 获取3 个文件改为 `import { getSession } from "@/shared/lib/session"`
- ⚠️ P1`schema.ts` 1111 行54 张表混合,超 1000 硬上限)
- ✅ P1~~`auth.ts` 293 行混合 5 类职责~~ 已拆分4 个辅助函数组迁移至 `shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service}`auth.ts 仅保留 NextAuth 配置)
- ✅ P2-2 已修复:~~`ai.ts` 218 行混合 5 类职责~~ 已拆分为 `ai/` 目录payload-parser.ts/api-key-crypto.ts/provider-config.ts/client.ts/errors.ts/index.ts`ai.ts` 保留为向后兼容的重导出文件9 行)
- ✅ P2-4 已修复:~~`onboarding-gate.tsx` 业务逻辑泄漏到 shared~~ 已迁移至 `modules/onboarding/`actions/data-access/schema/types/components引导流程改为独立路由 `/onboarding` + middleware 重定向 + Server Action
- ✅ P0-2/P0-3/P0-4/P0-5/P1-1/P1-2/P1-4/P1-5 已修复v3 对标 PowerSchool/Veracross/Auth0家长绑定三因子验证邮箱+生日+手机号后4位、教师多科目循环绑定、审计日志、服务端幂等、URL query 持久化步骤、局部错误收集、家长多子女动态行、跳过机制明确化
- ✅ v3 i18n 体系引入:采用 next-intl 4.xwithout i18n routing 模式cookie 驱动 locale 切换,字典放在 `shared/i18n/messages/{locale}/`,支持 zh-CN/en 两种语言,不破坏现有路由组结构
- ✅ v3 班级邀请码体系引入(对标 Google Classroom / 钉钉教育 / 智学网):新增 `class_invitation_codes` 表(独立表,支持有效期/次数限制/审计/多码并存6 位字母数字(剔除歧义字符 0/O/1/I/L空间 1.13 亿rate limit 防爆破10 次/5 分钟),审计日志全链路记录,懒清理过期码
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `db/schema.ts` | 1111 | 54 张表定义(超硬上限) |
| `db/relations.ts` | - | 表关系定义 |
| `db/index.ts` | - | Drizzle 客户端 |
| `lib/auth-guard.ts` | - | 认证上下文 + 权限校验 + DataScope |
| `lib/permissions.ts` | - | 角色-权限映射 |
| `lib/session.ts` | 38 | session 获取单一入口getSessionserver-onlydynamic import 打破循环) |
| `lib/ai.ts` | 9 | 向后兼容重导出P2-2 已拆分到 `ai/` 目录) |
| `lib/ai/payload-parser.ts` | 78 | 请求负载解析 |
| `lib/ai/api-key-crypto.ts` | 28 | API Key 加密/解密 |
| `lib/ai/provider-config.ts` | 61 | Provider 配置查询 |
| `lib/ai/client.ts` | 58 | AI 客户端创建与调用 |
| `lib/ai/errors.ts` | 8 | 错误格式化 |
| `lib/ai/index.ts` | 5 | 聚合导出 |
| `lib/audit-logger.ts` | - | 操作日志(通过 session.ts 获取 sessionhttp-utils 获取 IP/UA |
| `lib/change-logger.ts` | - | 数据变更日志(通过 session.ts 获取 sessionhttp-utils 获取 IP |
| `lib/login-logger.ts` | - | 登录日志(通过 http-utils 获取 IP/UA |
| `lib/password-policy.ts` | - | 密码策略纯函数 |
| `lib/rate-limit.ts` | - | 内存滑动窗口限流 |
| `lib/role-utils.ts` | 31 | 角色规范化纯函数normalizeRole / resolvePrimaryRole |
| `lib/bcrypt-utils.ts` | 18 | bcrypt 哈希前缀规范化纯函数 |
| `lib/http-utils.ts` | 44 | 请求头解析resolveClientIp / getUserAgentserver-only |
| `lib/password-security-service.ts` | 84 | 密码安全 DB 操作(账户锁定/失败登录追踪server-only |
| `lib/excel.ts` | - | Excel 导入导出 |
| `lib/file-storage.ts` | - | 文件存储抽象 |
| `hooks/use-permission.ts` | - | 客户端权限 Hook |
| `components/ui/*` | 34 文件 | shadcn/ui 标准组件 |
| `components/ui/stat-card.tsx` | 95 | StatCard 统计卡片P1-a 新增) |
| `components/ui/stat-item.tsx` | 38 | StatItem 紧凑统计项P1-a 新增) |
| `components/ui/chip-nav.tsx` | 78 | ChipNav 芯片导航P1-b 新增) |
| `components/ui/page-header.tsx` | 44 | PageHeader 页面头部P2-b 新增,含 icon 属性) |
| `components/ui/filter-bar.tsx` | 124 | FilterBar + FilterSearchInput + FilterResetButtonP3-b 新增) |
| `components/charts/chart-card-shell.tsx` | 90 | ChartCardShell 图表卡片外壳P3-c 新增) |
| `components/charts/trend-line-chart.tsx` | 153 | TrendLineChart 趋势折线图P3-c 新增) |
| `components/charts/simple-bar-chart.tsx` | 162 | SimpleBarChart 柱状图P3-c 新增) |
| `components/charts/comparison-radar-chart.tsx` | 143 | ComparisonRadarChart 对比雷达图P3-c 新增) |
| `components/schedule/schedule-list.tsx` | 112 | ScheduleList + ScheduleListItem 课表列表P3-a 新增) |
| `components/question/question-bank-filters.tsx` | 137 | QuestionBankFilters 题库筛选栏P3-d 新增) |
| `lib/download.ts` | 47 | downloadBase64File + downloadBlob 客户端下载工具P1-c 新增) |
| `lib/utils.ts` | - | 通用工具P1-a/P1-c 新增 getInitials + formatDateForFile |
| `components/onboarding-gate.tsx` | 312 | ~~引导流程(业务泄漏)~~ 已废弃,逻辑迁移至 `modules/onboarding/`P2-4 已修复) |
| `components/global-search.tsx` | 221 | 全局搜索(业务泄漏) |
| `types/permissions.ts` | 157 | 61 个权限点常量 + Role/DataScope/AuthContext 类型 |
---
## 2.2 exams考试模块
**职责**:考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题。
**导出函数**
- Actions`createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction`(✅ P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access
- Data-access`getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` / `getExamCreatorId` / `updateExamWithQuestions` / `deleteExamById` / `duplicateExam` / `getExamPreview` / `getExamSubjects` / `getExamGrades`(后 7 个为 P1-2 新增)
- AI Pipeline`generateAiCreateDraftFromSource` / `generateAiPreviewData` / `regenerateAiQuestionByInstruction`
- Utils`normalizeStructure`v3 新增:将持久化的 `exam.structure` unknown JSON 运行时校验并归一化为类型安全的 `ExamNode[]`,类型守卫模式无 `as` 断言,从 `teacher/exams/[id]/build/page.tsx` 提取)
- Stats-serviceV3-8 新增):`getExamAnalytics`cache 包装,聚合考试所有作业的已批改提交,计算平均分/及格率/分数段分布/逐题错误率与难度等级,对标智学网考试分析)+ `ExamAnalyticsSummary` 类型
- ComponentsV3-8 新增):`ExamAnalyticsDashboard`(考试分析仪表盘:汇总卡片+分数段分布+逐题分析表)
**依赖关系**
- 依赖:`shared/*``@/auth``questions`(✅ P0-1 已修复:通过 data-access.createQuestionWithRelations`classes`(✅ P0-2 已修复:通过 data-access.getClassGradeIdsByClassIds`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions`homework`V3-8 新增stats-service 通过 `homework/data-access.getHomeworkAssignmentsByExamId` / `getGradedSubmissionsByExamId` 获取作业与提交数据,合理跨模块调用)
- 被依赖:`homework`(通过 sourceExamId 外键,合理)、`dashboard`(通过 data-accessP0-4 已修复)、`proctoring`(✅ P1-1 已修复:通过 exams data-access`diagnostic`(✅ P1-1 已修复:通过 exams data-access
**已知问题**
- ✅ P0-1 已修复:~~`persistAiGeneratedExamDraft` 直接 insert 到 `questions` 表~~ 改为调用 `questions/data-access.createQuestionWithRelations`,通过 ID 映射保持 structure 引用一致
- ✅ P0-2 已修复:~~`getExams`/`getExamById`/`getExamsDashboardStats` 直查 `classes` 表~~ 改为调用 `classes/data-access.getClassGradeIdsByClassIds`
- ✅ P1-1 已修复:~~`getSubjectsAction`/`getGradesAction` 直查 `subjects`/`grades` 表~~ 改为调用 `school/data-access.getSubjectOptions` / `getGradeOptions`
- ✅ P1-2 已修复:~~`actions.ts` 832 行(超 800 建议),多处直接 DB 操作~~ DB 操作已下沉到 data-accessactions.ts 现 691 行
- ⚠️ P1`ai-pipeline.ts` 857 行(超 800 建议),混合 4 类职责
- ✅ P2 已修复:`ai-pipeline.ts` 中 3 处非空断言清理(`draft.sections!.forEach` → 安全守卫、`aiParsed.sections!.flatMap``?? []``aiParsed.sections!.map``?? []`
- ✅ V3-5/V3-8/V3-12`exam-actions.tsx` 增强V3-5 角色化菜单按角色显示不同操作项、V3-8 新增 analytics 菜单项BarChart3 图标,跳转 `/teacher/exams/[id]/analytics`、V3-12 移动端触摸目标尺寸优化
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 691 | 10 个 Server ActionP1-2 已修复,无直接 DB 操作) |
| `ai-pipeline.ts` | 857 | AI 出题管线(超限) |
| `data-access.ts` | 473 | 考试 CRUD含 P1-2 新增 7 个写/查询函数P0-1/P0-2 已修复:通过 questions/classes data-access 跨模块通信) |
| `stats-service.ts` | - | V3-8 新增:考试分析数据聚合(`getExamAnalytics` + `ExamAnalyticsSummary` 类型) |
| `types.ts` | 31 | 类型定义 |
| `hooks/use-exam-preview.ts` | 295 | 预览 Hook |
| `utils/normalize-structure.ts` | 57 | v3 新增exam.structure 运行时校验与归一化(从 build/page.tsx 提取) |
| `components/exam-analytics-dashboard.tsx` | - | V3-8 新增:考试分析仪表盘组件 |
| `components/exam-actions.tsx` | - | V3-5/V3-8/V3-12 增强:角色化菜单+analytics 链接+移动端触摸优化 |
| `components/*` | 19 文件 | 考试表单/组卷/预览/分析组件 |
---
## 2.3 homework作业模块
**职责**:作业全生命周期(创建/发布/作答/批改/分析)。
**导出函数**
- Actions`createHomeworkAssignmentAction` / `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction` / `batchAutoGradeSubmissionsAction`V3-7 新增批量自动批改HOMEWORK_GRADE 权限+非管理员仅可批改自己创建的作业)(✅ P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access/data-access-write
- Data-access`getHomeworkAssignments` / `getHomeworkAssignmentById` / `getHomeworkSubmissions` / `getStudentHomeworkAssignments` / `getStudentHomeworkTakeData` / `getHomeworkAssignmentReviewList` / `getHomeworkSubmissionDetails` / `getDemoStudentUser`(已迁移至 users 模块 `getCurrentStudentUser`,此处为 re-export 向后兼容)/ `isRecord` / `toQuestionContent` / `getAssignmentMaxScoreById`(后三者供 stats-service 使用)/ `getHomeworkAssignmentsByExamId`V3-8 新增:按考试 ID 查作业+目标/提交/批改计数)/ `getGradedSubmissionsByExamId`V3-8 新增:按考试 ID 查已批改提交,按学生去重)/ `getStudentSubmissionResult`V3-9 新增:查学生指定作业最新提交,用于结果页)/ `getStudentExamResults`V3-11 新增:查学生考试结果列表,供家长端展示)
- Data-access-classes`getAssignmentIdsForStudents` / `getHomeworkAssignmentsWithSubject` / `getHomeworkAssignmentsByIds` / `getAssignmentTargetCounts` / `getHomeworkSubmissionsForStudents` / `getPublishedHomeworkAssignmentsWithSubject` / `getHomeworkSubmissionsForAssignments`P0-7 新增,供 classes 模块跨模块调用;✅ P1-1 已修复:内部不再直查 exams/subjects 表,改为调用 `exams/data-access.getExamSubjectIdMap` + `school/data-access.getSubjectNameMapByIds`
- Data-access-write11 个写操作函数P1-2 新增 10 个从 actions 下沉 + V3-7 新增 `batchAutoGradeSubmissions`
- Stats-service`getTeacherGradeTrends` / `getHomeworkAssignmentAnalytics` / `getStudentDashboardGrades`(从 data-access.ts re-export 以保持向后兼容)
- ComponentsV3-7/V3-9 新增):`HomeworkBatchGradingView`(批量批改视图:勾选+一键批改+toast 反馈)/ `HomeworkSubmissionResult`(提交后即时反馈:分数汇总+对错分布+错题预览)
**依赖关系**
- 依赖:`shared/*``@/auth``exams`(✅ P1-1 已修复:通过 exams data-access.getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework/getExamForProctoringCrossModule`classes`(✅ P1-1 已修复:通过 classes data-access.getStudentIdsByClassId 等 7 个函数)、`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getSubjectNameMapByIds`users`(✅ P1-1 已修复:通过 users data-access.getUserWithRole/getUserNamesByIds
- 被依赖:`dashboard`(通过 data-access合理`parent`(通过 data-access合理V3-11 新增 `getStudentExamResults` 供 parent 调用)、`classes`(✅ P0-7 已修复classes 通过 `homework/data-access-classes` 获取作业数据,不再反向直查 homework/exams 表)、`exams`V3-8 新增:`exams/stats-service.getExamAnalytics` 调用 `getHomeworkAssignmentsByExamId` / `getGradedSubmissionsByExamId`,合理跨模块调用)
**已知问题**
- ✅ P0 已解决:`data-access.ts` 已拆分至 598 行(原 1038 行超 1000 硬上限),统计函数迁移至 `stats-service.ts`
- ✅ P0 已解决:`getStudentDashboardGrades` 排名计算逻辑迁移至 `stats-service.ts`
- ✅ P0 已解决:`getHomeworkAssignmentAnalytics` 错误率统计逻辑迁移至 `stats-service.ts`
- ✅ P0-7 已修复:新增 `data-access-classes.ts`,将 classes 模块对 homework/exams 表的直查封装为 homework 模块的导出函数,恢复三层架构
- ✅ P1-1 已修复:~~5 处直查 `exams` 表~~ 改为调用 `exams/data-access.getExamIdsByGradeIds` / `getExamSubjectIdMap` / `getExamWithQuestionsForHomework``data-access-classes.ts` 内部 JOIN exams+subjects 也改为调用 `exams/data-access.getExamSubjectIdMap` + `school/data-access.getSubjectNameMapByIds`,彻底消除跨模块直查
- ✅ P0-竞品已修复:新增 `hooks/use-exam-countdown.ts` 倒计时 Hook对标智学网/猿题库),`homework-take-view.tsx` 集成限时/监考模式倒计时显示与到时自动提交;`data-access.ts``getStudentHomeworkTakeData` 新增 `examModeConfig` + `startedAt` 字段供客户端计算截止时间
- ✅ P1-2 已修复:~~`actions.ts` 多处直接 DB 操作(`createHomeworkAssignmentAction` 157 行)~~ DB 操作已下沉到 `data-access-write.ts`actions.ts 现 239 行
- ✅ V3-7新增 `batchAutoGradeSubmissionsAction` + `batchAutoGradeSubmissions` + `HomeworkBatchGradingView`,提交列表页接入批量批改
- ✅ V3-8新增 `getHomeworkAssignmentsByExamId` + `getGradedSubmissionsByExamId`,供 exams 模块跨模块调用
- ✅ V3-9新增 `getStudentSubmissionResult` + `HomeworkSubmissionResult` + 路由 `/student/learning/assignments/[assignmentId]/result``homework-take-view.tsx` 提交后跳转结果页
- ✅ V3-12`homework-take-view.tsx` 移动端触摸目标尺寸优化
- ✅ 2026-06-23 审计修复data-access 层 4 个查询函数新增 `scope?: DataScope` 参数(`getHomeworkAssignments`/`getHomeworkAssignmentById`/`getHomeworkSubmissions`/`getHomeworkAssignmentReviewList`),教师仅查看自己创建的作业,学生/家长仅查看相关作业Server Action catch 块改用 `handleActionError` 统一错误处理
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 598+ | 作业 CRUD + 学生视角 + 批改(含 re-export stats 函数V3-8/V3-9/V3-11 新增 4 个查询函数) |
| `data-access-write.ts` | 285+ | 作业写操作P1-2 新增 10 个写函数从 actions 下沉V3-7 新增 `batchAutoGradeSubmissions` |
| `data-access-classes.ts` | 240+ | 跨模块查询封装P0-7 新增;✅ P1-1 已修复:内部通过 exams/school data-access 获取考试科目信息,不再直查 exams/subjects 表) |
| `stats-service.ts` | 425 | 统计分析(教师趋势/作业分析/学生仪表盘成绩) |
| `actions.ts` | 239+ | 6 个 Server ActionP1-2 已修复,无直接 DB 操作V3-7 新增 `batchAutoGradeSubmissionsAction` |
| `types.ts` | 186 | 类型定义 |
| `schema.ts` | 29 | Zod 校验 |
| `components/homework-batch-grading-view.tsx` | - | V3-7 新增批量批改视图use client |
| `components/homework-submission-result.tsx` | - | V3-9 新增:提交后即时反馈页 |
| `components/homework-take-view.tsx` | - | V3-9/V3-12 增强:提交后跳转结果页+移动端触摸优化;✅ P0-竞品:集成限时/监考模式倒计时useExamCountdown+ 到时自动提交 |
| `hooks/use-exam-countdown.ts` | 122 | P0-竞品新增:考试倒计时 Hook每秒更新、紧急状态高亮、到时回调自动提交 |
---
## 2.4 questions题库模块
**职责**:题库管理(题目 CRUD、知识点关联、题型支持
**导出函数**
- Actions`getQuestionsAction` / `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction`(✅ P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access
- Data-access`getQuestions` / `createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`(后 4 个为 P1-2 新增/迁移)
**依赖关系**
- 依赖:`shared/*``@/auth``textbooks`(✅ P1-1 已修复:通过 textbooks data-access.getKnowledgePointOptions
- 被依赖:`exams`(通过类型导入,合理)、`textbooks`UI 组合,合理)
**已知问题**
- ✅ P1-2 已修复:~~写操作函数错放在 `actions.ts``insertQuestionWithRelations` / `deleteQuestionRecursive`~~ 已下沉到 data-access`createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`
- ✅ P1-1 已修复:~~`getKnowledgePointOptionsAction` 直查 textbooks 模块表~~ 改为调用 `textbooks/data-access.getKnowledgePointOptions`
- ✅ P2 已解决:~~`data-access.ts` 仅 129 行,写操作缺失~~ P1-2 后 data-access.ts 扩充至 260 行
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 149 | 5 个 Server ActionP1-2 已修复,无直接 DB 操作) |
| `data-access.ts` | 260 | 题目 CRUD + 知识点选项(含 P1-2 新增 4 个写/查询函数) |
| `schema.ts` | 18 | Zod 校验 |
| `types.ts` | 34 | 类型定义 |
---
## 2.5 textbooks教材模块— 标杆模块data-access 层)
**职责**:教材与知识体系管理(教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱)。
**导出函数**
- Actions14 个,均为写操作;读操作由 RSC 页面直接调用 data-access`createTextbookAction` / `updateTextbookAction` / `deleteTextbookAction` / `createChapterAction` / `updateChapterContentAction` / `deleteChapterAction` / `reorderChaptersAction` / `createKnowledgePointAction` / `updateKnowledgePointAction` / `deleteKnowledgePointAction` / `getKnowledgePointsByChapterAction` / `getKnowledgeGraphDataAction`(✅ Task 7 新增:知识图谱数据查询,支持 structure/student-mastery/class-mastery 三种视图)/ `createPrerequisiteAction`(✅ Task 7 新增:声明前置依赖,含循环检测)/ `deletePrerequisiteAction`(✅ Task 7 新增:删除前置依赖)
- Data-access`getTextbooks` / `getTextbookById` / `getChaptersByTextbookId` / `getKnowledgePointsByChapterId` / `getKnowledgePointsByTextbookId` / `createTextbook` / `updateTextbook` / `deleteTextbook` / `createChapter` / `updateChapterContent` / `deleteChapter` / `createKnowledgePoint` / `updateKnowledgePoint` / `deleteKnowledgePoint` / `reorderChapters` / `getTextbooksDashboardStats` / `getKnowledgePointOptions`(跨模块接口,供 questions 调用)/ `getTextbooksWithScope`P1-1 新增:按数据范围获取教材列表,学生端强制按年级过滤)/ `verifyChapterBelongsToTextbook`P0-4 新增:资源归属校验)/ `verifyKnowledgePointBelongsToTextbook`P0-4 新增:资源归属校验)/ `verifyKnowledgePointBelongsToChapter`P0-4 新增:校验知识点是否属于指定章节)/ `createPrerequisite`(✅ Task 7 新增:创建前置依赖)/ `deletePrerequisite`(✅ Task 7 新增:删除前置依赖)/ `getPrerequisiteEdgesForTextbook`(✅ Task 7 新增:获取教材下所有前置依赖边,用于循环检测)/ `getSubjectLabelKey` / `getGradeLabelKey`i18n 标签键)
- Data-access-graph✅ Task 5 新增,图谱只读查询):`getKnowledgePointsWithRelations`(知识点+依赖+题目数聚合查询)/ `getStudentKpMastery`(学生掌握度)/ `getClassKpMastery`(班级平均掌握度)/ `getPrerequisitesForKp` / `getSuccessorsForKp`
- Constants✅ 新增):`SUBJECTS` / `GRADES` / `SUBJECT_COLORS` / `getSubjectColor` / `getSubjectLabelKey` / `getGradeLabelKey`
- ✅ v1 测试修复:`SUBJECTS` 新增 `Chinese` 学科、`GRADES` 新增 `Grade 1`/`Grade 2`,与 seed 数据对齐
- ✅ v1 测试修复:`SUBJECT_COLORS` 新增 `Chinese` 主题色rose
- Utils✅ 新增,纯函数 + 单测):`sortChapters` / `buildChapterTree` / `buildChapterIndex` / `findChapterParent` / `filterKnowledgePointsByChapter` / `normalizeOptional` / `highlightKnowledgePoints` / `hasCycleAfterAddingEdge`Task 4 新增循环依赖检测DFS 算法)
- Graph-layout✅ 新增,纯函数 + 单测):`computeGraphLayout`
- Components✅ Task 13 新增):`GraphNodeDetailPanel` — 知识图谱节点详情侧边面板,展示知识点名称/描述/掌握度/关联题目/前置后置知识点,支持跳转与前置关系增删
- Analytics✅ 新增):`TextbookAnalytics` / `TextbookAnalyticsProvider` / `useTextbookAnalytics`
**依赖关系**
- 依赖:`shared/*``@/auth``@xyflow/react`(知识图谱可视化)、`@dagrejs/dagre`(图谱分层布局算法)
- 被依赖:`questions`(✅ P1-1 已修复:通过 textbooks data-access`exams`(通过类型)、`dashboard`(通过 data-accessP0-4 已修复)
- ✅ UI 层跨模块依赖已解耦:`textbooks/components/knowledge-point-dialogs.tsx` 不再直接 import questions 模块,改为通过 render prop 注入创建题目入口
**已知问题**
- ✅ 无跨模块 DB 访问data-access 层)
- ✅ actions 层编排模式标杆(权限校验 → 调用 data-access → revalidatePath
- ✅ data-access 层职责单一
- ✅ P2 已修复:`data-access.ts``byId.get(pid)!.children.push` 非空断言清理为安全守卫;`or(...)!` 非空断言清理为条件 push
- ✅ P0 跨模块 UI 依赖已修复:`knowledge-point-dialogs.tsx` 通过 render prop 解耦,不再直接 import questions 模块
- ✅ P0 前端权限硬编码已修复:改用 `usePermission().hasPermission()`
- ✅ P0 i18n 已基本接入:`chapter-sidebar-list.tsx` / `actions.ts` / `section-error-boundary.tsx`(默认值改英文)均已接入 next-intl
- ✅ P1 Server Action 资源归属校验已修复:新增 `verifyChapterBelongsToTextbook` / `verifyKnowledgePointBelongsToTextbook`
- ✅ P1 data-access 数据范围过滤已修复:新增 `getTextbooksWithScope`,学生端强制按年级过滤
- ✅ P1 Error Boundary 已补齐:新增 `section-error-boundary.tsx`
- ✅ P1 知识点列表/弹窗重复实现已清理:移除无调用方的 `knowledge-point-panel.tsx`
- ✅ P1 学科/年级选项统一:抽取到 `constants.ts``SUBJECTS` / `GRADES` / `SUBJECT_COLORS`
- ✅ v1 测试修复:`textbook-reader.tsx` 移除 `SheetTrigger`(错误置于 `Sheet` 外部导致 `DialogTrigger must be used within Dialog`),改用受控 `Button` + `onClick` 打开移动端抽屉
- ✅ v1 测试修复:新增 `teacher-textbook-reader.tsx` 客户端包装组件,解决 Server Component 向 Client Component 传递函数 prop`renderQuestionCreator`)违反 Next.js 序列化约束的问题
- ✅ v1 测试修复:`scripts/seed.ts` 教材 subject/grade 改为英文 value`constants.ts` 对齐),消除 i18n `MISSING_MESSAGE` 错误
- ✅ P1 纯逻辑已导出并补单测:新增 `utils.ts` / `graph-layout.ts` 及对应 `.test.ts`
- ⚠️ i18n 覆盖率约 95%`chapter-sidebar-list` 已接入,`actions.ts` 已接入,`section-error-boundary` 默认值已改英文)
- ⚠️ 类型断言残留 3 处 `as string`
- ⚠️ P2 图谱方向键导航未实现
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 515 | 14 个 Server Action写操作含 Zod 校验 + 资源归属校验 + 知识图谱查询/前置依赖管理 + class-mastery 视图) |
| `data-access.ts` | 662 | 教材/章节/知识点 CRUD + 跨模块查询接口 + 资源归属校验 + 数据范围过滤 + 前置依赖 CRUD |
| `data-access-graph.ts` | 207 | 知识图谱只读查询(✅ Task 5 新增:知识点关联聚合、学生/班级掌握度、前置后置知识点,标记 `server-only`v2 修复:前置依赖双向 IN 过滤防跨教材污染) |
| `types.ts` | 106 | 类型定义含知识图谱类型GraphViewMode/MasteryInfo/KpWithRelations/GraphNodeData/GraphEdgeData/KnowledgeGraphData/MasteryLevel |
| `schema.ts` | 81 | Zod 校验(含 CreatePrerequisiteSchema/DeletePrerequisiteSchemav2 修复refine 消息改为英文) |
| `constants.ts` | 96 | 学科/年级常量与颜色映射(✅ 新增v1 测试修复:新增 Chinese/Grade 1/Grade 2 |
| `utils.ts` | 225 | 章节树构建/排序/查找等纯函数 + 循环检测(✅ 新增,含单测) |
| `graph-layout.ts` | 121 | 知识图谱布局计算纯函数(✅ Task 8 重写为 dagre 集成,输出 React Flow 格式,含单测) |
| `analytics.tsx` | 43 | 教材分析 Context/Provider/Hook✅ 新增) |
| `hooks/use-knowledge-point-actions.ts` | 49 | 知识点操作门面 Hookv2 拆分:组合 useKpDialogState + useKpCrud对外 API 不变) |
| `hooks/use-kp-dialog-state.ts` | 38 | 知识点对话框状态管理 Hookv2 新增:从 use-knowledge-point-actions 拆分) |
| `hooks/use-kp-crud.ts` | 122 | 知识点 CRUD 操作 Hookv2 新增:从 use-knowledge-point-actions 拆分) |
| `hooks/use-text-selection.ts` | 57 | 文本选区捕获 Hook |
| `hooks/use-graph-data.ts` | 76 | 知识图谱数据加载 Hook✅ Task 11 新增:派生值模式避免 effect 中 setStatev2 修复:区分 isLoading/isRefreshing 避免 UI 闪烁) |
| `components/knowledge-graph.tsx` | 376 | React Flow 知识图谱主组件(✅ Task 10/15 重写:全书视图 + 搜索高亮 + 关联节点高亮 + 章节着色v2 新增:添加/删除前置依赖 Dialog |
| `components/graph-kp-node.tsx` | 92 | React Flow 自定义节点(✅ Task 9 新增:知识点名称+题目数徽章+掌握度进度条v2 修复:节点宽度常量化 NODE_WIDTH |
| `components/graph-prerequisite-edge.tsx` | 48 | React Flow 自定义边(✅ Task 9 新增:虚线+箭头表示前置依赖v2 修复GraphEdgeData 类型安全转换) |
| `components/graph-toolbar.tsx` | 93 | 图谱工具栏(✅ Task 9 新增:视图模式切换 + 搜索 + 重置视图v2 新增isRefreshing 轻量指示器) |
| `components/graph-node-detail-panel.tsx` | 181 | 节点详情侧边面板(✅ Task 13 新增:描述/掌握度/关联题目/前置后置列表v2 修复:移除未使用 textbookId prop |
| `components/chapter-sidebar-list.tsx` | 342 | 章节侧边栏列表v2 修复canEdit 默认 false + orderUpdateFailed i18n key |
| `components/section-error-boundary.tsx` | 71 | 章节内容错误边界v2 修复:默认值改为空字符串 + 条件渲染) |
| `components/*` | 16 文件 | 教材编辑/知识图谱组件(新增 `section-error-boundary.tsx`、5 个 graph-* 组件;`teacher-textbook-reader.tsx` 已移至 app 层) |
---
## 2.6 grades成绩模块— 标杆模块(拆分范例)
**职责**:成绩分析(录入/查询/统计/导出/趋势对比分析)。
**导出函数**
- Actions`getGradeRecordsAction` / `createGradeRecordAction`v4-P1-6 增强:成绩录入后通知学生和家长,调用 `notifyGradeEntered`/ `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction`v4-P1-12 增强:新增可选 `studentId` 参数,支持按学生导出,家长视角调用 `exportStudentGradeRecordsToExcel`,校验 studentId 属于家长子女)/ `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction`v4-P1-6 增强:批量成绩录入后通知学生和家长)/ `assertClassInScope`(✅ P3 新增导出:班级 scope 校验工具,供 actions-analytics 复用)/ `saveGradeDraftAction` / `getGradeDraftAction` / `deleteGradeDraftAction`(✅ v3-P2 新增:成绩录入草稿 Server Actions分别使用 GRADE_RECORD_MANAGE/GRADE_RECORD_READ/GRADE_RECORD_MANAGE 权限)
- Data-access`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getRankingTrend` / `PaginatedGradeRecords`(✅ P3 新增:分页结果接口 `{ records, total }`/ `saveGradeDraft` / `getGradeDraft` / `deleteGradeDraft`(✅ v3-P2 新增:成绩录入草稿 CRUDupsert + 24 小时过期)/ `getExamOptionsForGrades` / `getSchoolWideGradeSummary`(✅ v3-P2 新增:考试选项查询 + 全校各年级成绩汇总,管理员视图按年级聚合平均分/及格率/优秀率/学生数/班级数,加权平均计算全校汇总)
- Types✅ v3-P2 新增):`SchoolWideGradeSummaryItem`全校汇总按年级聚合项gradeId/gradeName/schoolName/classCount/studentCount/averageScore/passRate/excellentRate/recordCount/ `SchoolWideGradeSummary`全校汇总grades 数组 + totals 汇总对象)/ `GradeDraftData`(草稿数据接口:{ scores: Record<string, string>, timestamp: number },位于 data-access.ts
- Lib✅ P1-2 新增,✅ P3 更新签名,✅ P3-26 拆分):`toNumber` / `normalize`(位于 `lib/grade-utils.ts``buildScopeClassFilter(scope, currentUserId?)`P3-26 从 grade-utils.ts 迁移至 `lib/scope-filter.ts`P3 修复:`class_members` scope 内置 studentId 过滤,需传入 currentUserId 参数)
- Stats-service✅ P1-1 新增):`computeGradeStats` / `computeAverageScore` / `buildGradeTrendPoints` / `computeTrendAverage` / `computeClassComparisonStats` / `computeSubjectComparisonStats` / `computeGradeDistribution` / `buildRankingTrendPoints`(从 3 个 data-access 文件抽取的纯函数,使数据层专注 DB I/O统计逻辑可独立测试
- Export✅ v4-P1-12 新增):`exportGradeRecordsToExcel` / `exportClassGradeReportToExcel` / `exportStudentGradeRecordsToExcel`v4-P1-12 新增:导出单个学生成绩单家长视角,仅含成绩明细 + 个人统计不含班级数据scope 为 children 自动按 studentId 过滤)/ `formatDateForFile`(已迁移至 shared/lib/utils
- Components✅ P1-5 新增):`WidgetBoundary`Error Boundary + Suspense + Skeleton 组合,含 a11y 属性)/ `SchoolWideSummaryCard`(✅ v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格)/ `ScoreCell`(✅ v4-P1-7 新增:成绩单元格组件,根据得分率着色——红<60%/黄60-84%/绿≥85%,使用语义化 Tailwind 类名避免动态拼接fullScore<=0 时不着色)
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds
- 被依赖:`parent`(通过 data-access合理`dashboard`
**已知问题**
- ✅ P1-1 已修复:~~多处直查 `classes`/`classEnrollments`/`subjects`/`users` 表~~ 改为调用对应模块 data-access 函数classes/school/users
- ✅ P1-1 已修复:~~统计计算业务逻辑混入 data-access`getClassGradeStats` / `getGradeDistribution`~~ 抽取为纯函数到 `stats-service.ts`,数据层专注 DB I/O
- ✅ P1-2 已修复:~~`toNumber`/`normalize`/`buildScopeClassFilter` 在 3 个 data-access 文件中重复定义~~ 抽取到 `lib/grade-utils.ts` 统一维护
- ✅ P1-3 已修复:~~12 个查询/分析 Action 缺少 Zod 校验~~ 新增 12 个查询 schemaDeleteGradeRecordSchema/GetGradeRecordByIdSchema/GradeQuerySchema/ClassGradeStatsQuerySchema/StudentGradeSummaryQuerySchema/ClassRankingQuerySchema/ExportGradesSchema/GradeTrendQuerySchema/ClassComparisonQuerySchema/SubjectComparisonQuerySchema/GradeDistributionQuerySchema/RankingTrendQuerySchema所有 Action 使用 safeParse 校验
- ✅ P1-4 已修复:~~`batch-grade-entry.tsx`/`grade-record-form.tsx`/`grade-distribution-chart.tsx` 中存在 `as` 断言~~ 改用类型守卫函数isGradeType/isSemester/isDistributionTooltipPayload
- ✅ P1-5 已修复:~~teacher/grades 与 teacher/diagnostic 路由缺少 loading.tsx/error.tsx~~ 已为 7 个路由补齐 loading.tsx + error.tsx并新增 `WidgetBoundary` 通用组件
- ✅ P2-1 已修复:~~图表/表格/列表缺少 a11y ARIA 属性~~ 为 4 个成绩图表添加 `role="img"` + `aria-label`2 个表格添加 `<caption>`3 个图标按钮添加 `aria-label`
- ✅ P2-2 已修复:~~diagnostic 组件中存在 Tailwind 任意值~~ 改用标准 Tailwind 类
- ✅ P2-4 已修复:~~`buildScopeClassFilter``grade_managed` scope 返回 `sql\`1=0\``(空数据)~~ 改为子查询 `classId IN (SELECT id FROM classes WHERE grade_id IN (...))` 过滤所管年级的班级
- ✅ P2-6 已修复:~~student/grades 和 management/grade/insights 各自重复定义 `SearchParams` 类型与 `getParam` 函数~~ 改为统一从 `@/shared/lib/search-params` 导入
- ✅ actions 层无直接 DB 访问(标杆)
- ✅ data-access 按职责拆分为 3 个文件(标杆)
- ✅ P2 已修复:`export.ts``scoreMap.get(r.studentId)!` 非空断言清理为安全守卫(`if (!subjMap) continue`
- ✅ P3 修复2026-06-22~~`schema.ts` 缺少输入边界约束~~ score 字段添加 `.max(1000)`records 数组添加 `.max(500)`,查询 schema 补全 studentId/semester/examId 字段
- ✅ P3 修复2026-06-22~~`buildScopeClassFilter``class_members` scope 未应用 studentId 过滤~~ 新增 `currentUserId` 参数,`class_members` scope 内置 `eq(gradeRecords.studentId, currentUserId)` 过滤
- ✅ P3 修复2026-06-22~~`getGradeRecords` 内存分页(全量查询后切片)~~ 改为 DB 层分页limit/offset并行查询总数与当前页数据返回 `PaginatedGradeRecords { records, total }`
- ✅ P3 修复2026-06-22~~`batchCreateGradeRecords` 非原子操作~~ 包裹 `db.transaction` 确保原子性
- ✅ P3 修复2026-06-22~~`updateGradeRecord`/`deleteGradeRecord` 未检查记录是否存在~~ 新增存在性检查,不存在时抛 `NotFoundError("成绩记录")`
- ✅ P3 修复2026-06-22~~`getClassGradeStats`/`getStudentGradeSummary`/`getClassRanking`/`getClassStudentsForEntry`/`getClassGradeStatsWithMeta` 缺少 scope 过滤~~ 所有函数新增 `scope?`/`currentUserId?` 参数,应用 `buildScopeClassFilter``getStudentGradeSummary`/`getClassStudentsForEntry``class_taught` scope 校验学生/班级归属
- ✅ P3 修复2026-06-22~~`getClassRanking` 未处理并列排名~~ 相同平均分获得相同名次,下一名次跳过占位
- ✅ P3 修复2026-06-22~~`getClassComparison`/`getRankingTrend` 缺少 scope 过滤~~ 应用 `buildScopeClassFilter``getRankingTrend``class_taught` scope 校验
- ✅ P3 修复2026-06-22~~actions.ts/actions-analytics.ts catch 块直接返回 `e.message`~~ 改用 `handleActionError(e)` 统一错误处理;`batchCreateGradeRecordsAction` 使用 `safeJsonParse` 解析 JSON
- ✅ P3 修复2026-06-22~~actions-analytics.ts 缺少 class scope 校验~~ `getGradeTrendAction`/`getSubjectComparisonAction`/`getGradeDistributionAction` 新增 `assertClassInScope` 校验
- ✅ P3 修复2026-06-22~~组件直接 try/catch 调用 Server Action~~ 改用 `safeActionCall` 包装器onError/onFinally 回调);`batch-grade-entry.tsx` localStorage 访问包裹 `typeof window !== "undefined"` 检查;区分"未录入"与"录入 0"
- ✅ P3 修复2026-06-22~~`grade-trend-card.tsx` 排序未处理 NaN 日期、除法未检查 fullScore > 0~~ 新增日期有效性检查与 `fullScore > 0` 守卫
- ✅ P3 修复2026-06-22~~页面文件缺少 scope 传递~~ teacher/grades/page.tsx 使用 DB 层分页analytics/page.tsx 添加 EmptyStateentry/page.tsx 与 stats/page.tsx 过滤 class_taught scope 班级student/grades/page.tsx 与 parent/grades/page.tsx 传递 `ctx.dataScope` 到 data-access
- ✅ v3-P2 改进2026-06-23新增 `grade_drafts`成绩录入草稿userId+classId+subjectId+type 唯一键content JSON 存储 scores24 小时过期schema.ts 第 1444-1469 行);新增 `saveGradeDraft`/`getGradeDraft`/`deleteGradeDraft` data-access 函数 + 对应 3 个 Server Actions支持成绩批量录入草稿服务端持久化
- ✅ v3-P2 改进2026-06-23新增 `getExamOptionsForGrades` data-access 函数,获取指定班级/科目下有成绩记录的考试列表;`AnalyticsFilters` 组件新增 `exams`/`currentExamId`/`currentSemester` props添加学期和考试筛选 ChipNavteacher/grades/analytics/page.tsx 新增 semester/examId 搜索参数解析
- ✅ v3-P2 改进2026-06-23`getGradeTrend`/`getGradeDistribution`/`getSubjectComparison`/`getClassComparison` 均新增 `semester``examId` 可选参数,支持按学期和考试筛选分析
- ✅ v3-P2 改进2026-06-23新增 `getSchoolWideGradeSummary` data-access 函数 + `SchoolWideSummaryCard` 组件 + `SchoolWideGradeSummary`/`SchoolWideGradeSummaryItem` 类型,管理员全校成绩汇总视图(按年级聚合平均分/及格率/优秀率/学生数/班级数加权平均计算全校汇总admin/school/grades/insights/page.tsx 顶部新增 SchoolWideSummaryCard
- ✅ v3-P2 改进2026-06-23parent/grades/page.tsx 为每个子女并行查询 `getClassAverageTrend`,渲染 GradeTrendCard
- ✅ v3-P3-4 改进2026-06-23GradeTrendCard 新增日期范围选择器(全部/近7天/近30天/近90天通过 nuqs `trendRange` URL 参数持久化useEffect 中计算截止时间戳避免渲染阶段调用 Date.now()
- ✅ v4-P3-4 改进2026-06-23GradeDistributionChart 色盲友好双重编码——每个分数段使用不同 SVG pattern条纹/点状/交叉线/反向条纹/网格)+ 颜色,`SimpleBarChart` 新增 `defs` prop 支持自定义 SVG 图案
- ✅ P3 修复2026-06-23~~`lib/grade-utils.ts` 72 行超 40 行工具函数上限~~ P3-26 将 `buildScopeClassFilter` 迁移至 `lib/scope-filter.ts`grade-utils.ts 仅保留 toNumber/normalize20 行)
- ✅ P3 修复2026-06-23~~`stats-service.ts` createDefaultBuckets 不必要导出~~ P3-10 移除 export 关键字,改为内部函数
- ✅ P3 修复2026-06-23~~`stats-service.ts` buildGradeTrendPoints 使用 as 断言~~ P3-24 新增 isGradeTrendType 类型守卫函数替代 as 断言
- ✅ P3 修复2026-06-23~~`export.ts` 局部 avg 函数与 stats-service.computeAverageScore 重复~~ P3-6 删除局部 avg复用 computeAverageScore
- ✅ P3 修复2026-06-23~~`export.ts` TYPE_LABELS 硬编码中文~~ P3-7 改用 next-intl getTranslations新增 export.sheets/columns/metrics i18n 键zh-CN/en 同步)
- ✅ v4-P1-6 改进2026-06-23`createGradeRecordAction` / `batchCreateGradeRecordsAction` 成绩录入后通知学生和家长(调用 `notifyGradeEntered`,内部使用 `parent/data-access.getParentIdsByStudentIds` 批量查询家长 ID通知失败不阻断成绩录入
- ✅ v4-P1-7 改进2026-06-23新增 `ScoreCell` 组件components/score-cell.tsx根据得分率着色红<60%/黄60-84%/绿≥85%),使用语义化 Tailwind 类名避免动态拼接
- ✅ v4-P1-10 改进2026-06-23`grade-record-list.tsx` 使用 `ScoreCell` 替代纯文本分数展示 + 表格 `overflow-x-auto` 水平滚动
- ✅ v4-P1-12 改进2026-06-23`exportGradesAction` 新增可选 `studentId` 参数,支持按学生导出(家长视角);新增 `exportStudentGradeRecordsToExcel` 导出函数(仅含成绩明细 + 个人统计不含班级数据parent/grades/page.tsx 传 studentId 到 ParentExportButtonParentExportButton 接入 exportGradesAction
- ✅ v3-P3-1 改进2026-06-23`batch-grade-entry.tsx` 新增"下载模板"按钮,客户端生成 CSV 模板(含学生姓名/分数/备注列头 + BOM 支持 Excel UTF-8教师可下载填好后粘贴到录入表格
- ✅ v3-P3-2 改进2026-06-23`grade-record-list.tsx` 新增多选复选框(全选/单选)+ 批量删除工具栏 + 批量删除确认对话框;新增 `bulkDeleteGradeRecords` data-access 函数(使用 inArray 一次性删除避免 N+1+ `bulkDeleteGradeRecordsAction` Server Action限制单次最多 500 条)
- ✅ v4-P3-2 改进2026-06-23`batch-grade-entry.tsx` 顶部新增可折叠新手引导提示框4 步使用说明),使用 localStorage 记住用户关闭状态避免重复显示
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 670+ | 19 个 Server Action含 Zod 校验,含 v2-P1-5 安全修复assertClassInScope + 行级 scope 校验P3 修复handleActionError + safeJsonParse + scope 传递 + DB 层分页v3-P2 新增saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftActionv4-P1-6createGradeRecordAction/batchCreateGradeRecordsAction 新增通知v4-P1-12exportGradesAction 新增 studentId 参数v3-P3-2 新增bulkDeleteGradeRecordsAction 批量删除) |
| `actions-analytics.ts` | 170 | 5 个分析 Action含 Zod 校验P3 修复handleActionError + assertClassInScope 校验) |
| `data-access.ts` | 450+ | 成绩 CRUD + 统计 + 草稿(含 v2-P2-9 修复recorderName 批量查询P3 修复PaginatedGradeRecords 接口 + DB 层分页 + 事务 + 存在性检查 + scope 过滤 + 并列排名v3-P2 新增saveGradeDraft/getGradeDraft/deleteGradeDraft + GradeDraftData 接口v3-P3-2 新增bulkDeleteGradeRecords 使用 inArray 批量删除) |
| `data-access-analytics.ts` | 200+ | 趋势/对比分析P3 修复getClassComparison 应用 buildScopeClassFilterv3-P2 新增getExamOptionsForGrades/getSchoolWideGradeSummarygetGradeTrend/getClassComparison/getSubjectComparison/getGradeDistribution 新增 semester/examId 可选参数) |
| `data-access-ranking.ts` | 83 | 排名查询P3 修复getRankingTrend 接受 scope 参数 + class_taught 校验) |
| `stats-service.ts` | 285 | 统计计算纯函数P1-1 新增8 个纯函数 + 2 个常量 + 2 个接口P3-10createDefaultBuckets 改为内部函数P3-24buildGradeTrendPoints 使用 isGradeTrendType 类型守卫替代 as 断言) |
| `export.ts` | 290+ | Excel 导出v2-P1-5 修复:传递 currentUserId 到 data-accessP3 修复:适配 PaginatedGradeRecords 结构 + 传递 scopeP3-6复用 stats-service.computeAverageScore 替代局部 avgP3-7硬编码中文改用 next-intl getTranslationsv4-P1-12 新增exportStudentGradeRecordsToExcel 家长视角单学生导出) |
| `schema.ts` | 113+ | Zod 校验(含 12 个查询 schemaP3 修复score .max(1000) + records .max(500) + 补全查询字段v3-P2 新增grade_drafts 表定义第 1444-1469 行) |
| `lib/grade-utils.ts` | 20 | 公共工具函数toNumber/normalizeP3-26buildScopeClassFilter 迁移至 scope-filter.ts |
| `lib/scope-filter.ts` | 56 | DB 行级权限过滤buildScopeClassFilterP3-26 从 grade-utils.ts 迁移v2-P2-2 修复:改用 classes data-access 子查询P3 修复:新增 currentUserId 参数) |
| `types.ts` | 168+ | 类型定义v3-P2 新增SchoolWideGradeSummaryItem/SchoolWideGradeSummary |
| `components/widget-boundary.tsx` | 136 | Widget 边界组件P1-5 新增v2-P1-1 已在 3 个页面应用) |
| `components/school-wide-summary-card.tsx` | - | v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格) |
| `components/score-cell.tsx` | 41 | v4-P1-7 新增:成绩单元格组件,根据得分率着色(红<60%/黄60-84%/绿≥85%),使用语义化 Tailwind 类名 |
| `components/grade-trend-card.tsx` | 69 | 趋势卡片v2-P2-9 修复a11yv2-P1-4i18nP3 修复NaN 日期检查 + fullScore > 0 守卫) |
| `components/grade-record-list.tsx` | 200+ | 成绩记录列表v2-P1-4i18nP3 修复safeActionCall 包装删除操作v4-P1-7使用 ScoreCellv4-P1-10overflow-x-autov3-P3-2 新增:多选复选框 + 全选 + 批量删除工具栏 + 批量删除确认对话框,接入 bulkDeleteGradeRecordsAction |
| `components/grade-distribution-chart.tsx` | 100 | 分数分布图v2-P1-4i18n |
| `components/subject-comparison-chart.tsx` | 62 | 科目对比图v2-P1-4i18n |
| `components/class-comparison-chart.tsx` | 194 | 班级对比图v2-P1-4i18nv3-P3-5 新增:显著性分析区域,基于极差和样本量的经验规则判断班级间差异,含可折叠详细分析) |
| `components/grade-trend-chart.tsx` | 59 | 趋势图v2-P1-4i18n |
| `components/grade-record-form.tsx` | 177 | 录入表单v2-P2-7 修复Label htmlForv2-P1-4i18nP3 修复safeActionCall 包装提交) |
| `components/batch-grade-entry.tsx` | 500+ | 批量录入v2-P2-7 修复Label htmlForv2-P1-4i18nP3 修复safeActionCall + localStorage 安全检查 + 区分未录入与录入 0v3-P2 新增:接入服务端草稿 saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftActionv3-P3-1 新增:下载 CSV 录入模板按钮含学生姓名列表v4-P3-2 新增可折叠新手引导提示框localStorage 记住关闭状态) |
| `components/grade-filters.tsx` | 76 | 过滤器v2-P1-4i18n |
| `components/student-grade-summary.tsx` | 107 | 学生成绩摘要v2-P1-4i18n |
| `components/export-button.tsx` | 79 | 导出按钮v2-P1-4i18nP3 修复safeActionCall 包装导出操作) |
| `components/analytics-filters.tsx` | 86+ | 分析过滤器v2-P1-4i18nv3-P2 新增exams/currentExamId/currentSemester props添加学期和考试筛选 ChipNav |
| `components/stats-class-selector.tsx` | 40 | 统计班级选择器v2-P1-4i18n |
| `components/grade-stats-card.tsx` | 74 | 统计卡片v2-P1-4i18n |
| `components/class-grade-report.tsx` | 90 | 班级成绩报告v2-P1-4i18n |
| `components/grade-query-filters.tsx` | 96 | 查询过滤器v2-P2-7 修复Label htmlForv2-P1-4i18n |
---
## 2.7 classes班级模块— 耦合最严重
**职责**:班级 CRUD + 学生/教师管理 + 邀请码注册。
**导出函数**
- Actions`createTeacherClassAction` / `updateTeacherClassAction` / `deleteTeacherClassAction` / `createAdminClassAction` / `updateAdminClassAction` / `deleteAdminClassAction` / `createGradeClassAction` / `updateGradeClassAction` / `deleteGradeClassAction`
- Data-access`getAdminClasses` / `getTeacherClasses` / `getGradeManagedClasses` / `getStudentClasses` / `getClassDetails` / `getClassStudents` / `getClassSchedule` / `getClassHomeworkInsights` / `getGradeHomeworkInsights` / `getStudentsSubjectScores` / `verifyTeacherOwnsClass` / `getTeacherIdsByClassIds`(获取多个班级的所有教师 ID班主任 + 任课教师,跨模块接口,供 messaging 模块调用)(✅ P0-5 已修复classSchedule 写函数 createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem 已迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留 classSchedule 读函数;✅ P2 已修复:`getAccessibleClassIdsForTeacher` 使用 `Promise.all` 并行化 ownedIds 与 assignedIds 查询)
- Schema`CreateTeacherClassSchema` / `UpdateTeacherClassSchema` / `DeleteTeacherClassSchema` / `CreateAdminClassSchema` / `UpdateAdminClassSchema` / `DeleteAdminClassSchema` / `CreateGradeClassSchema` / `UpdateGradeClassSchema` / `DeleteGradeClassSchema` / `CreateClassScheduleItemSchema` / `UpdateClassScheduleItemSchema` / `DeleteClassScheduleItemSchema` / `EnrollStudentByEmailSchema`
**依赖关系**
- 依赖:`shared/*``@/auth``school`(✅ P1-1 已修复:通过 school data-access.isGradeHead/isGradeManager/findGradeIdByHeadAndName`homework`(✅ P0-7 已修复:通过 `homework/data-access-classes` 暴露的函数获取作业数据,不再直查 homework/exams 表)
- 被依赖:`exams`/`homework`/`grades`/`attendance`/`scheduling`/`dashboard`(通过 data-accessP0-4 已修复)/`parent`/`course-plans`/`users`(✅ P1-1 已修复8+ 处直查 classes 表改为通过 classes data-access/`messaging`(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId支持学生/家长给班级教师发消息)
**已知问题**
- ✅ P0-1 已修复:`data-access.ts` 已拆分为 5 个文件data-access/data-access-stats/data-access-schedule/data-access-students/data-access-admin所有文件均 ≤800 行
- ✅ P0-5 已修复classSchedule 写函数createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem已迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留 classSchedule 读函数getStudentSchedule/getClassSchedule新增 verifyTeacherOwnsClass 供 scheduling 模块跨模块校验教师班级归属
- ✅ P0-7 已修复:`data-access-stats.ts``data-access-students.ts` 不再直查 `homeworkAssignmentQuestions`/`homeworkAssignmentTargets`/`homeworkAssignments`/`homeworkSubmissions`/`exams` 表,改为调用 `homework/data-access-classes.ts` 暴露的函数(`getAssignmentIdsForStudents`/`getHomeworkAssignmentsWithSubject`/`getHomeworkAssignmentsByIds`/`getAssignmentMaxScoreById`/`getAssignmentTargetCounts`/`getHomeworkSubmissionsForStudents`/`getPublishedHomeworkAssignmentsWithSubject`/`getHomeworkSubmissionsForAssignments`
- ✅ P1-1 已修复:~~`actions.ts` 直查 `grades` 表做权限校验~~ 改为调用 `school/data-access` 函数
- ✅ P1-1 已修复:~~`getSessionTeacherId` 在 data-access 调用 `auth()`~~ 改为通过 `shared/lib/auth-guard.getAuthContext()` 获取
- ✅ P2 已修复:`data-access.ts``idByName.get(name)!` 非空断言清理为 `flatMap` 安全过滤;`data-access-admin.ts` 中同类非空断言清理
- ✅ P0-3 修复2026-06-22~~`actions.ts` 974 行接近 1000 行硬上限~~ 拆分为 6 个文件actions-teacher/actions-admin/actions-grade/actions-invitations/actions-schedule/actions-shared`actions.ts` 改为 50 行 barrel re-export
- ✅ P1-1 修复2026-06-22~~`ctx.roles.includes("admin"/"teacher"/"student")` 角色硬编码~~ 改为 `hasAdminScope(ctx)`/`hasTeacherScope(ctx)`/`hasStudentScope(ctx)` 基于 `dataScope.type` 判断
- ✅ P1-4 修复2026-06-22`types.ts` 中 ClassHomeworkInsights 等跨领域类型保留在 classes 模块(因为是 classes 对 homework 数据的视图),添加注释说明归属决策
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 548 | 核心班级 CRUD + 邀请码 + 教师班级管理(含 re-export 向后兼容) |
| `data-access-stats.ts` | 513 | 作业统计查询(班级/年级作业洞察,通过 homework/data-access-classes 获取数据) |
| `data-access-schedule.ts` | 93 | 课表查询(学生/班级课表只读P0-5 已修复:写函数已迁移至 scheduling 模块) |
| `data-access-students.ts` | 253 | 学生相关查询(科目成绩、学生名单、学生班级,通过 homework/data-access-classes 获取数据) |
| `data-access-admin.ts` | 406 | 管理员班级管理(管理员班级 CRUD、年级管理班级查询 |
| `actions.ts` | 50 | Barrel re-exportP0-3 修复:从 974 行拆分为 6 个文件) |
| `actions-teacher.ts` | 100 | 教师班级 CRUD3 个 Action |
| `actions-admin.ts` | 120 | 管理员班级 CRUD3 个 Action |
| `actions-grade.ts` | 110 | 年级组长班级 CRUD3 个 Action |
| `actions-invitations.ts` | 502 | 邀请码与注册 + 批量操作10 个 ActionP2-4 新增批量导入学生/批量分配教师) |
| `actions-schedule.ts` | 90 | 班级课表 CRUD3 个 Action |
| `actions-shared.ts` | 60 | 共享工具hasAdminScope/hasTeacherScope/hasStudentScope/parseSubjectTeachers/toWeekday |
| `schema.ts` | 152 | Zod 校验13 个 schema教师/管理员/年级班级 CRUD + 课表 CRUD + 邮箱注册) |
| `types.ts` | 201 | 类型定义含跨领域类型说明注释P1-4 修复) |
---
## 2.8 school学校模块
**职责**:学校/学年/部门/年级的 CRUD。
**导出函数**
- Actions`createSchoolAction` / `updateSchoolAction` / `deleteSchoolAction` / `createAcademicYearAction` / `updateAcademicYearAction` / `deleteAcademicYearAction` / `createDepartmentAction` / `updateDepartmentAction` / `deleteDepartmentAction` / `createGradeAction` / `updateGradeAction` / `deleteGradeAction` / `promoteGradesAction`(编排层:权限校验 + Zod 校验 + 调用 data-access + revalidatePath + after(logAudit)`promoteGradesAction` 年级升级,审计日志 `grade.promote`
- Data-access只读查询`getSchools` / `getGrades` / `getDepartments` / `getAcademicYears` / `getStaffOptions` / `getGradesForStaff` / `getOrgTree` / `getSubjectOptions` / `getGradeOptions` / `getSubjectNameMapByIds`P1-1 新增:批量科目名称映射,供 homework/data-access-classes 调用))+ 写操作(`create/update/delete` × `Department/School/Grade/AcademicYear`+ `promoteGrades(schoolId)` 年级升级order +1 + 名称升级,辅助函数 `promoteGradeName`
**依赖关系**
- 依赖:`shared/*``@/auth``users`(⚠️ `getStaffOptions` 直查 users/roles可接受
- 被依赖:`exams`(✅ P1-1 已修复:通过 school data-access`homework`(✅ P1-1 已修复:通过 school data-access`grades`(✅ P1-1 已修复:通过 school data-access`questions`(✅ P1-1 已修复:通过 textbooks data-access`classes`(✅ P1-1 已修复:通过 school data-access`course-plans`(合理)
**已知问题**
- ✅ P0-8 已修复:`actions.ts` 不再直接导入 `db` 和 schema所有 DB 写操作下沉到 `data-access.ts`,符合三层架构
- ✅ P2 已修复:`logAudit()` 通过 Next.js `after()` 异步非阻塞执行
- ✅ P2 已修复:`data-access.ts` 中 8 处 catch 块添加 `console.error` 输出错误上下文getDepartments/getAcademicYears/getSchools/getGrades/getStaffOptions/getGradesForStaff/getSubjectOptions/getGradeOptions
- ⚠️ P2审计日志不一致仅 school 实体记录department/academicYear/grade 未记录)
- ⚠️ P2`getStaffOptions`/`getGrades` 直查 users/roles展示用可接受
- ✅ P0-2 修复2026-06-22~~年级 CRUD 逻辑与 `grade-management` 模块重复定义~~ `grade-management` 死模块已删除,年级 CRUD 统一由 school 模块负责
- ✅ P0-5 修复2026-06-22~~`school/components/*` 4 个组件缺少 i18n~~ 全部 4 个组件schools-view/grades-view/departments-view/academic-year-view已接入 `useTranslations("school")``school.json` i18n 文件已创建并扩充
- ✅ P1-3 修复2026-06-22新增 school-error-boundary.tsxclass component Error Boundary + i18n + router.refresh 重试)和 school-skeleton.tsxSchoolListSkeleton 表格骨架 + SchoolCardSkeleton 卡片骨架4 个页面schools/grades/departments/academic-year均已包裹 SchoolErrorBoundaryschool.json 补充 errors.boundary.* 翻译键
- ✅ P1-5 修复2026-06-22~~`schools-view.tsx` 硬编码 Table+Dialog+AlertDialog~~ 拆分为组合模式SchoolListToolbar + SchoolFormDialog + SchoolDeleteDialog + useSchoolData hook
- ✅ P1-6 修复2026-06-22新增 `getSchoolsForUser(userId)` / `getGradesForUser(userId)` 权限感知查询函数,根据用户角色返回可见数据范围
- ✅ P2-1 修复2026-06-22抽取 `use-school-data` hook将对话框状态管理逻辑与 UI 分离
- ✅ P2-2 修复2026-06-23新增 `getOrgTree()` data-access 函数(学校→年级→班级三级树,通过 `import("@/modules/classes/data-access")` 动态导入获取班级数据避免循环依赖)+ `OrgTreeNode` 类型 + `OrgTreeNav` 组件(三级树形导航,支持搜索过滤/选中高亮/展开折叠/不同节点类型图标school.json 补充 `orgTree.*` 翻译键
- ✅ P2-3 修复2026-06-23新增 `promoteGradesAction` + `promoteGrades(schoolId)` + `promoteGradeName(name)` 辅助函数(中文数字 一→二…十二、阿拉伯数字 1→2…12 识别),按 order 降序逐条 +1 避免唯一约束冲突;含 `after(() => logAudit({ action: "grade.promote" }))` 审计日志
- ✅ P2-4 修复2026-06-23新增 `bulkEnrollStudentsAction`CSV 批量导入学生,复用 enrollStudentByEmail+ `bulkAssignSubjectTeachersAction`CSV 批量分配教师,简化实现含 TODO 待完善查找逻辑classes/actions.ts barrel 导出
- ✅ P2-5 修复2026-06-23为 department/academicYear/grade 的 9 个 CRUD Action 补充 `after(() => logAudit(...))` 审计日志action: department.create/update/delete、academicYear.create/update/delete、grade.create/update/delete与 school 实体审计日志策略一致
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 457 | 13 个 Server Action编排层无 DB 直访;含 `promoteGradesAction` 年级升级) |
| `data-access.ts` | 782 | 只读查询 + 12 个写操作 + `promoteGrades` 年级升级 + `getOrgTree` 组织架构树 + 跨模块查询接口 + 权限感知函数getSchoolsForUser/getGradesForUser |
| `schema.ts` | 56 | Zod 校验(含 `PromoteGradesSchema` |
| `types.ts` | 103 | 类型定义(含 Insert/Update 入参类型 + `OrgTreeNode` |
| components/schools-view.tsx | 132 | 学校列表容器组合模式P1-5 修复) |
| components/school-form-dialog.tsx | 80 | 学校创建/编辑对话框P1-5 修复) |
| components/school-delete-dialog.tsx | 50 | 学校删除确认对话框P1-5 修复) |
| components/school-list-toolbar.tsx | 30 | 学校列表工具栏P1-5 修复) |
| components/school-error-boundary.tsx | 72 | 共享 Error BoundaryP1-3 修复) |
| components/school-skeleton.tsx | 69 | 共享骨架屏P1-3 修复) |
| components/org-tree-nav.tsx | 134 | 学校→年级→班级三级树形导航P2-2 修复:搜索过滤 + 选中高亮 + 展开折叠 + 节点类型图标) |
| hooks/use-school-data.ts | 40 | 学校数据管理 hookP2-1 修复) |
---
## 2.8b grade-management年级管理模块— ✅ 已删除
> **2026-06-22 审计发现**该模块拥有完整的理想架构Service 接口 + Context DI + 角色配置 + Error Boundary + Skeleton + i18n + hooks 分离),但 **13 个相关页面中无任何一个导入此模块**。`management/grade/*` 页面实际依赖 `classes` 和 `school` 模块的 data-access。
>
> **2026-06-22 处置决策P0-1/P0-2 修复)**:该死模块已**完整删除**。年级 CRUD 统一由 `school` 模块负责(`school/actions.ts` + `school/data-access.ts`),避免两套重复实现。详见 `docs/architecture/audit/school-grade-class-audit-report.md`。
---
## 2.9 scheduling排课模块
**职责**:自动排课算法 + 课表调整 + 排课规则管理。
**导出函数**
- Actions`autoScheduleAction` / `applyAutoScheduleAction` / `getSchedulingRulesAction` / `updateSchedulingRulesAction` / `getScheduleChangesAction` / `createScheduleChangeAction` / `updateScheduleChangeAction` / `deleteScheduleChangeAction`
- Data-access`getSchedulingRules` / `getScheduleChanges` / `getAdminClassesForScheduling` / `getTeachersForScheduling` / `getClassroomsForScheduling` / `getClassSubjectsForScheduling`
- Data-access-class-schedule✅ P0-5 新增):`createClassScheduleItem` / `updateClassScheduleItem` / `deleteClassScheduleItem`(从 classes 模块迁移,含教师班级归属校验,通过 `classes/data-access.verifyTeacherOwnsClass` 跨模块校验)
- Data-access低级写入`insertClassScheduleItem` / `updateClassScheduleItemById` / `deleteClassScheduleItemById` / `replaceClassSchedule`(统一 classSchedule DB 写入口)
- 算法:`findOptimalSlot` / `validateSchedule` / `autoSchedule` / `buildDefaultTimeSlots`(纯函数,标杆)
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(✅ P0-5 已修复:通过 `classes/data-access.verifyTeacherOwnsClass` / `getTeacherIdForMutations` 校验教师班级归属,不再直写 classSchedule 表的写入口分散在 classes 模块)、`school`(⚠️ 排课辅助查询,可接受)、`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds
- 被依赖:`classes/actions.ts`(✅ P0-5 已修复:通过 `scheduling/data-access-class-schedule` 调用写函数)
**已知问题**
- ✅ P0-5 已修复:~~`applyAutoScheduleAction` 直接 transaction 写 `classSchedule` 表(第三个写入口)~~ 改为调用 `replaceClassSchedule` 统一写入口classSchedule 所有写函数统一在 scheduling 模块
- ✅ P1-1 已修复:~~`autoScheduleAction` 直查 `users` 表~~ 改为调用 `users/data-access.getUserNamesByIds`
- ⚠️ P2`actions.ts` 末尾 re-export data-access 函数(反模式)
- ✅ P2 已修复:`data-access.ts` 中 3 处非空断言清理(`userIds[0]!``rows[i]!``rows[j]!``auto-scheduler.ts` 中 2 处非空断言清理(`schedule[i]!``schedule[j]!`
-`auto-scheduler.ts` 是算法独立化的最佳实践(纯函数、无 DB、可测试
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `auto-scheduler.ts` | 310 | 排课算法(纯函数,标杆) |
| `actions.ts` | 302 | 8 个 Server Action |
| `data-access.ts` | 398 | 排课辅助查询 + 规则/变更 CRUD + classSchedule 低级写入insert/update/delete/replace |
| `data-access-class-schedule.ts` | 165 | classSchedule 业务写入P0-5 新增,从 classes 模块迁移,含教师归属校验) |
| `schema.ts` | - | Zod 校验 |
| `types.ts` | - | 类型定义(含 P0-5 迁移的 CreateClassScheduleItemInput / UpdateClassScheduleItemInput |
---
## 2.10 attendance考勤模块— 结构典范
**职责**:考勤记录管理 + 统计分析 + 规则配置。
**导出函数**
- Actions10 个):`recordAttendanceAction` / `batchRecordAttendanceAction` / `updateAttendanceAction` / `deleteAttendanceAction` / `getAttendanceAction` / `getStudentAttendanceAction` / `getClassAttendanceStatsAction` / `getClassAttendanceForDateAction` / `saveAttendanceRulesAction` / `getAttendanceRulesAction`(✅ P2-25 个写 Action 错误消息改用 `getTranslations("attendance")` 国际化)
- Data-access`getAttendanceRecords` / `createAttendanceRecord` / `updateAttendanceRecord` / `deleteAttendanceRecord` / `getClassStudentsForAttendance` / `getAttendanceStats`(✅ P2-1 已修复:改用 SQL `COUNT()` + `SUM(CASE WHEN ...)` 聚合查询,不再依赖 `getAttendanceRecords` 分页结果)/ `upsertAttendanceRules` / `getAttendanceRules`
- Data-access-stats`getStudentAttendanceSummary`(✅ P2-6 已修复stats 改用 SQL 聚合查询recentRecords 增加 `recentLimit` 参数默认 20不再加载全部记录/ `getClassAttendanceStats` / `computeStats`
- Import-export`exportAttendanceRecordsToExcel`(✅ P2-11 新增Sheet1 考勤明细 + Sheet2 统计汇总,列头 i18n 化)
- Components`AttendanceSheet`(批量点名表单)/ `AttendanceRecordList`(记录列表 + 删除)/ `AttendanceFilters`URL 同步筛选器)/ `AttendanceStatsCard`(单卡片统计)/ `AttendanceStatsCards`(管理员 6 卡片总览)/ `AttendanceStatsClassSelector`(班级筛选 ChipNav/ `AttendanceRulesForm`(规则配置表单)/ `StudentAttendanceView`(学生/家长只读视图)/ `AttendancePageLayout`(✅ P2-3 新增页面布局骨架admin/teacher 考勤页复用)
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(⚠️ P1-1 未修复:`getClassStudentsForAttendance` 仍直查 `classEnrollments` 表)、`next-intl`(✅ P2-2Server Action 错误消息 i18n
- 被依赖:`parent`(⚠️ 跨模块 UI 类型依赖3 个 parent 组件直接 import `@/modules/attendance/types`)、`app/api/export`(✅ P2-11通过 `exportAttendanceRecordsToExcel`
**已知问题**(详见 `docs/architecture/audit/attendance-elective-audit-report.md`
- ✅ P2-1 已修复:`getAttendanceStats` 改用 SQL 聚合查询,消除分页截断导致统计失真
- ✅ P2-2 已修复5 个写 Action 错误消息改用 `getTranslations("attendance")` 国际化
- ✅ P2-3 已修复:新增 `AttendancePageLayout` 组件admin/teacher 考勤页复用布局
- ✅ P2-5 已修复:`parent-attendance-calendar.tsx` 增加键盘导航ARIA grid 模式,方向键/Home/End
- ✅ P2-6 已修复:`getStudentAttendanceSummary` stats 改用 SQL 聚合 + recentRecords 分页限制
- ✅ P2-11 已修复:新增 `export.ts`,考勤记录/统计可导出 Excel
- ❌ P0`getClassStudentsForAttendance` 仍直查 `classEnrollments`
- ❌ P06 个读 Action 无调用方(页面绕过 Action 直接调用 data-access违反三层架构
- ❌ P0update/delete Action 缺资源归属校验(教师 A 可修改/删除教师 B 的记录)
- ❌ P0i18n 完全缺失(`ATTENDANCE_STATUS_LABELS` 硬编码英文,组件中硬编码中文)
- ❌ P0错误边界完全缺失5 个角色目录均无 `error.tsx`
- ⚠️ P1`computeStats` 未导出,无法单测
- ⚠️ P1`attendance-sheet.tsx` 使用 `window.confirm`(与项目 AlertDialog 模式不一致)
- ⚠️ P1`attendance-sheet.tsx` 存在 `{} as Record<AttendanceStatus, number>` 类型断言
- ⚠️ P1`STATUS_OPTIONS`/`SHORTCUTS`/`STYLES` 常量在 types.ts 与 attendance-sheet.tsx 重复定义
- ✅ stats 独立拆分为 `data-access-stats.ts`(拆分范例)
- ✅ DataScope 完整接入 6 种 scope 类型
- ✅ actions 层无直接 DB 访问
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 271 | 10 个 Server Action含权限校验、Zod 校验P2-2错误消息 i18n 化) |
| `data-access.ts` | 340 | 考勤 CRUD + 班级学生查询 + 规则 upsert + 总览统计P2-1`getAttendanceStats` 改 SQL 聚合) |
| `data-access-stats.ts` | 180 | 学生/班级考勤汇总P2-6stats 改 SQL 聚合 + recentRecords 分页) |
| `export.ts` | 90 | Excel 导出P2-11 新增:考勤明细 + 统计汇总双 Sheet |
| `schema.ts` | 43 | Zod 校验5 个 schema |
| `types.ts` | 103 | 类型定义 + 状态标签/颜色常量(硬编码英文) |
| `components/attendance-sheet.tsx` | 353 | 批量点名表单(键盘快捷键、状态按钮组) |
| `components/attendance-record-list.tsx` | 130 | 考勤记录列表 + 删除对话框 |
| `components/attendance-filters.tsx` | 97 | URL 同步筛选器(班级/状态/日期) |
| `components/attendance-stats-card.tsx` | 81 | 单卡片统计8 指标) |
| `components/attendance-stats-cards.tsx` | 80 | 管理员总览 6 卡片网格(硬编码中文) |
| `components/attendance-stats-class-selector.tsx` | 27 | 班级筛选 ChipNav |
| `components/attendance-rules-form.tsx` | 148 | 考勤规则配置表单 |
| `components/student-attendance-view.tsx` | 104 | 学生/家长视图(统计 + 最近记录) |
| `components/attendance-page-layout.tsx` | 38 | 页面布局骨架P2-3 新增header/stats/filters/children 插槽) |
---
## 2.11 users用户模块
**职责**:用户资料管理 + 批量导入导出 + 管理员用户列表管理。
**导出函数**
- Actions`getUserProfileAction` / `updateUserProfileAction` / `importUsersAction` / `exportUsersAction` / `downloadUserTemplateAction` / `updateUserRoleAction` / `deleteUserAction`
- Data-access`getUserProfile` / `getCurrentStudentUser`(✅ P2-20 已修复:从 homework 模块迁移而来6 个 student 页面通过此函数获取学生身份,不再依赖 homework 模块)/ `getAdminUsers`(管理员用户列表分页查询,支持搜索+角色聚合)/ `getAdminUserRoles`(角色名列表,用于筛选下拉框)
- Import-export`generateUserImportTemplate` / `parseUserImportData` / `exportUsersToExcel`+ re-export `batchImportUsers` / `UserImportResult` 保持向后兼容)
- User-service`batchImportUsers`(用户创建 + 密码哈希 + 角色分配)
- Class-registration`registerStudentByInvitationCode`(委托 classes/data-access 完成班级注册)
- Components`UserImportDialog`(批量导入对话框)/ `AdminUsersView`(管理员用户列表客户端组件,搜索+筛选+分页+删除)
**依赖关系**
- 依赖:`shared/*`(含 `shared/lib/role-utils`,✅ P2 已修复:删除本地 `normalizeRoleName`/`resolvePrimaryRole`/`rolePriority`,统一复用 `shared/lib/role-utils.resolvePrimaryRole`)、`@/auth``classes`(✅ P1-4 已修复:通过 `class-registration.ts` 调用 `classes/data-access.enrollStudentByInvitationCode`,不再直写 classEnrollments
- 被依赖:`dashboard`(通过 data-accessP0-4 已修复)、`grades`(✅ P1-1 已修复:通过 users data-access`homework`(✅ P1-1 已修复:通过 users data-access`scheduling`(✅ P1-1 已修复:通过 users data-access`diagnostic`(✅ P1-1 已修复:通过 users data-access`elective`(✅ P1-1 已修复:通过 users data-access`proctoring`(✅ P1-1 已修复:通过 users data-access`parent`(✅ P1-1 已修复:通过 users data-access
**已知问题**
- ✅ P1 已解决:`import-export.ts` 四重职责已拆分为 `import-export.ts`(解析/生成)+ `user-service.ts`(用户创建)+ `class-registration.ts`(班级注册)
- ✅ P1 已解决:`batchImportUsers` 不再跨模块直写 `classEnrollments`,改为调用 `classes/data-access.enrollStudentByInvitationCode`
- ✅ P2 已解决:删除本地 `normalizeRoleName`/`resolvePrimaryRole`/`rolePriority`,统一复用 `shared/lib/role-utils.resolvePrimaryRole`,消除重复代码
- ✅ P1-1 已修复:~~`updateUserProfile` 绕过 data-access 直接 DB 写~~ 已下沉到 data-access
- ✅ P2-20 已修复:新增 `getCurrentStudentUser` 函数(从 homework 模块迁移6 个 student 页面通过此函数获取学生身份,不再依赖 homework 模块
- ✅ P2 已解决:`data-access.ts` 已扩充写操作updateUserProfile 已下沉)
- ⚠️ 已知限制:`AdminUsersView` 客户端组件的删除操作通过 `fetch("/api/admin/users/:id")` 调用,对应 API 路由尚未实现(`deleteUserAction` Server Action 已就绪,可作为后续 API 路由的实现基础)
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `import-export.ts` | 157 | 文件解析/生成(模板生成 + 解析校验 + Excel 导出)+ re-export 向后兼容 |
| `user-service.ts` | 82 | 用户创建(批量导入 + 密码哈希 + 角色分配) |
| `class-registration.ts` | 21 | 班级注册(委托 classes/data-access |
| `actions.ts` | 218 | 7 个 Server Actionprofile 更新 + 模板下载/导入/导出 + 角色更新 + 删除) |
| `data-access.ts` | 394 | getUserProfile + 用户查询 + 管理员用户列表分页查询getAdminUsers/getAdminUserRoles |
| `components/admin-users-view.tsx` | 290 | 管理员用户列表客户端组件(搜索+筛选+表格+分页+删除对话框) |
---
## 2.12 dashboard仪表盘模块
**职责**:管理员/教师/学生/家长仪表盘数据聚合 + 权限校验 + i18n + 纯逻辑工具函数。
**导出函数**
- Actions`getAdminDashboardAction` / `getTeacherDashboardAction` / `getStudentDashboardAction` / `getParentDashboardAction`(均调用 `requirePermission()` 校验对应 `DASHBOARD_*_READ` 权限)
- Data-access`getAdminDashboardData`(并行调用 6 个模块 stats 函数)
- Lib 纯函数:`toWeekday` / `countStudentAssignments` / `sortUpcomingAssignments` / `filterTodaySchedule` / `computeTeacherMetrics` / `getGreetingKey`
- Components`AdminDashboardView` / `TeacherDashboardView` / `StudentDashboard` / `UserGrowthChart` / `DashboardGreetingHeader`共享问候头部V2 抽象)/ `DashboardSection`(分区 Error Boundary + Suspense + 骨架屏)(均接入 next-intl i18n
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(通过 data-access`homework`(通过 data-access`users`(通过 data-access`parent`(通过 data-access.getParentDashboardData`textbooks`/`questions`/`exams`(通过各模块 dashboard stats 函数)、`recharts``next-intl`
- 被依赖:无
**权限点**
- `DASHBOARD_ADMIN_READ`admin
- `DASHBOARD_TEACHER_READ`teacher
- `DASHBOARD_STUDENT_READ`student
- `DASHBOARD_PARENT_READ`parent
**已知问题**
- ✅ P0-4 已修复:`getAdminDashboardData` 改为并行调用各模块 dashboard stats 函数,不再直查跨模块表
- ✅ P0 已修复2026-06-22所有仪表盘页面通过 `actions.ts` 调用 `requirePermission()` 进行权限校验,不再裸调 data-access
- ✅ P0 已修复2026-06-22根重定向页 `/dashboard` 改用 `resolvePermissions()` + 权限点判断,不再 `role === "xxx"` 硬编码
- ✅ P0 已修复2026-06-22所有仪表盘组件接入 next-intl`useTranslations` / `getTranslations`),翻译文件 `messages/{zh-CN,en}/dashboard.json`
- ✅ P1 已修复2026-06-22业务逻辑weekday 转换、作业统计、教师指标计算、问候语时段)抽取至 `lib/dashboard-utils.ts` 纯函数,与 UI 分离
- ✅ P2 已修复2026-06-22新增 `components/dashboard-section.tsx`,每个独立数据区块用 Error Boundary + Suspense + 骨架屏包裹,单区块崩溃/加载不波及整页5 种骨架变体stats/card/chart/table/list
- V1 新增:`AdminDashboardData` 类型含 `userGrowth`/`homeworkTrend` 字段,`data-access.ts` 当前返回空数组占位,待后续接入真实统计
- parent 仪表盘组件仍位于 `modules/parent/components/parent-dashboard.tsx`,通过 `dashboard/actions.getParentDashboardAction` 调用(架构决策:保留在 parent 模块以避免移动文件破坏其他 import
- ✅ P0 已修复2026-06-22 V210 个子组件 i18n 遗漏全部补齐teacher-quick-actions / teacher-classes-card / teacher-homework-card / teacher-schedule / recent-submissions / teacher-grade-trends / student-grades-card / student-today-schedule-card / student-upcoming-assignments-card / admin-dashboard新增 ~50 个翻译键
- ✅ P1 已修复2026-06-22 V2抽象共享组件 `DashboardGreetingHeader`,消除 teacher/student 头部 90% 重复代码
- ✅ P2 已修复2026-06-22 V2为 6 个纯函数添加 31 个单元测试(`tests/integration/dashboard/dashboard-utils.test.ts`
- ✅ P2 已修复2026-06-22 V2a11y 增强 — admin 表格 `<caption>`、teacher/student 视图语义化标签(`<header>` / `<section aria-label>` / `<aside aria-label>`
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 120 | 4 个 Server Action编排层`requirePermission()` 权限校验) |
| `data-access.ts` | 49 | admin 仪表盘数据聚合(并行调用各模块 stats 函数) |
| `lib/dashboard-utils.ts` | 170 | 纯逻辑工具函数weekday / 统计 / 排序 / 指标计算 / 问候语) |
| `types.ts` | 74 | Admin / Teacher / Student 类型定义 |
| `components/dashboard-section.tsx` | 165 | 分区 Error Boundary + Suspense + 骨架屏5 种变体stats/card/chart/table/list |
| `components/dashboard-greeting-header.tsx` | 36 | 共享问候头部组件V2 抽象,消除 teacher/student 头部重复) |
| `components/admin-dashboard/admin-dashboard.tsx` | 267 | 管理员仪表盘视图i18n + a11y 表格 caption |
| `components/admin-dashboard/user-growth-chart.tsx` | 50 | recharts 折线图i18n |
| `components/teacher-dashboard/*.tsx` | 9 文件 | 教师仪表盘组件i18n + a11y 语义化标签) |
| `components/student-dashboard/*.tsx` | 6 文件 | 学生仪表盘组件i18n + a11y 语义化标签) |
| `tests/integration/dashboard/dashboard-utils.test.ts` | 408 | 6 个纯函数的 31 个单元测试V2 新增) |
---
## 2.13 messaging私信模块
**职责**站内私信messages 表 CRUD+ 消息草稿message_drafts 表 CRUD
**导出函数**
- Actions`sendMessageAction` / `markMessageAsReadAction` / `deleteMessageAction` / `getMessagesAction` / `getMessageDetailAction` / `getRecipientsAction` / `getUnreadMessageCountAction` / `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`(✅ P1-4 已修复:通知 CRUD Action 已迁移至 notifications 模块messaging 仅保留私信和通知偏好 Action✅ V2-P2-13c 新增:`toggleMessageStarAction` 星标切换 / `getMessageDraftsAction` 草稿列表 / `saveMessageDraftAction` 草稿保存(创建或更新)/ `deleteMessageDraftAction` 草稿删除)
- Data-access`getMessages`(✅ V2-P2-13c 新增 starredOnly 星标筛选)/ `getMessageById` / `getMessageThread` / `createMessage` / `markMessageAsRead` / `deleteMessage` / `toggleMessageStar`(✅ V2-P2-13c 新增:接收方星标切换)/ `getUnreadMessageCount` / `getRecipients`(按 DataScope 过滤可发送对象class_taught 教师→学生、grade_managed 年级管理员→教师/学生、all 管理员、class_members 学生→自己班级的任课教师/班主任、children 家长→孩子的班主任/任课教师;通过 classes data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID/ `getMessagesPageData`(✅ P1-5 新增:消息首页编排函数,一次性获取消息列表和通知列表)/ `getMessageDetailPageData`(✅ V2-P1-3 新增:消息详情页编排函数,获取详情并自动标记已读)/ `getMessageDrafts` / `createMessageDraft` / `updateMessageDraft` / `deleteMessageDraft` / `getMessageDraftById`(✅ V2-P2-13c 新增:消息草稿 CRUD操作 message_drafts 表)
- Hooks`useMessageSearch`(✅ P1-7 新增:消息搜索 hook含防抖和请求竞态取消
- Notification-preferences~~re-export shim实际逻辑在 `notifications/preferences.ts`~~ ✅ P0-b 已修复:`notification-preferences.ts` 文件已删除(通知模块去重),消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`
**依赖关系**
- 依赖:`shared/*``@/auth``notifications`(✅ P0-4 / P1-5 已修复:通过 `sendNotification` dispatcher 发送通知,通知 CRUD 和偏好已迁移至 notifications 模块;✅ P1-4 已修复:通知 UI 组件已迁移至 notifications 模块)、`classes`(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID支持学生 class_members 和家长 children 数据范围)、`users`(通过 data-access.getUserNamesByIds 获取用户显示名称)
- 被依赖:`notifications`(✅ 已消除反向依赖)、`settings`(通知偏好表单)、`layout`(✅ P1-4 已修复:通知下拉组件改为从 notifications 模块导入)
**已知问题**
- ✅ P0-4 已修复:~~`sendMessageAction` 绕过 notifications dispatcher 直接调用 `createNotification`~~ 改为调用 `notifications.sendNotification`,通知 CRUD 已迁移至 notifications 模块
- ✅ P0 已修复:~~与 notifications 双向依赖 + 职责重叠~~ 通知相关表messageNotifications / notificationPreferences所有权已移交 notifications 模块messaging 仅保留 messages 表
- ✅ P1-5 已修复:~~同时管理 3 类数据messages + messageNotifications + notificationPreferences~~ 仅管理 messages 表,通知相关数据由 notifications 模块管理
- ✅ P1 已修复:~~通知相关 Action 使用 `requireAuth()` 而非 `requirePermission()`~~ 通知 Action 已迁移至 notifications 模块并改为 `requirePermission(Permissions.MESSAGE_READ)`
- ✅ P1 已修复:~~`markMessageAsReadAction` / `deleteMessageAction` / `getMessageDetailAction` 缺少 Zod 校验~~ 已添加 `MessageIdSchema` 校验 messageId 参数
- ✅ P1 已修复:~~`updateNotificationPreferencesAction` 缺少 Zod 校验~~ 已添加 `UpdateNotificationPreferencesSchema` 校验 8 个布尔字段
- ✅ P2 已修复:`data-access.ts` 中 3 处 `or(...)!` 非空断言清理为安全守卫(条件 push
- ✅ P0-b 已修复:~~`notification-preferences.ts` re-export shim 文件~~ 已删除通知模块去重8 个消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`,消除 messaging 模块对通知偏好的冗余 re-export 层
- ✅ P1 已修复:~~全模块零 i18n中英文案硬编码~~ 所有组件接入 next-intl`useTranslations("messages")`),新增 `src/shared/i18n/messages/{zh-CN,en}/messages.json` 翻译字典title/description/tabs/actions/form/status/meta/notificationType/search/empty/messages/error 共 13 个命名空间);所有页面 `page.tsx` 使用 `generateMetadata` + `getTranslations` 替代硬编码 metadata
- ✅ P1 已修复:~~缺 Error Boundary~~ 新增 3 个 `error.tsx` 错误边界(`/messages``/messages/[id]``/messages/compose`),统一使用 `EmptyState` + i18n 错误文案 + 重试按钮
- ✅ P2 已修复a11y 改进,`message-list.tsx` 添加 `aria-label` / `aria-hidden`
- ✅ P1-4 已修复:~~通知组件notification-list.tsx / notification-dropdown.tsx放在 messaging/components 下,直接 import notifications 模块类型和 messaging/actions~~ 两个组件已迁移至 `notifications/components/`,通知 CRUD Action 已迁移至 `notifications/actions.ts`messaging 模块仅保留私信组件
- ✅ P1-5 已修复:~~页面层 `Promise.all` 编排 messaging 和 notifications 两个模块的 data-access~~ 新增 `getMessagesPageData` 编排函数,页面层仅调用单一函数
- ✅ P1-7 已修复:~~消息列表客户端 `useEffect` + `setTimeout` 防抖搜索未取消已发出的请求~~ 搜索逻辑抽离为 `useMessageSearch` hook含防抖 + 请求竞态取消);~~无分页 UI~~ 新增客户端分页 UIPAGE_SIZE=20ChevronLeft/ChevronRight 按钮)
- ✅ P1-9 已修复:~~`deleteMessage` 两个独立 UPDATE 无事务~~ 改为 `db.transaction` 包裹 senderDeletedAt 和 receiverDeletedAt 更新,保证原子性
- ✅ P2-11 已修复:~~发送/删除消息无埋点~~ `sendMessageAction` / `markMessageAsReadAction` / `deleteMessageAction` 新增 `trackEvent` 埋点message.sent / message.marked_read / message.deleted
- ✅ V2-P0-2 已修复:~~通知标题硬编码~~ `sendMessageAction` 通过 `getTranslations('messages')` 生成 i18n 通知标题(`notification.messageTitle` / `messageTitleNoSubject`
- ✅ V2-P1-2 已修复:~~MessageList 客户端过滤冗余~~ 客户端过滤仅在初始数据type=all时执行搜索结果已由服务端按 tab 过滤
- ✅ V2-P1-3 已修复:~~消息详情页分散编排~~ 新增 `getMessageDetailPageData` 编排函数,替代 page.tsx 中 `after()` + `getMessageById` + `markMessageAsRead` 的分散编排
- ✅ V2-P1-4 已修复:~~表单无服务端校验错误展示~~ `message-compose.tsx` 新增 `fieldErrors` 状态 + `aria-invalid` 字段级错误展示receiverId/subject/content
- ✅ V2-P2-1 已修复:~~轮询间隔魔法数字~~ `unread-message-badge.tsx` 轮询间隔提取为 `POLL_INTERVAL_MS` 常量(~~60_000ms~~ → 30_000ms✅ V2-P3 与通知组件保持一致)
- ✅ V2-P2-13c 已实现:消息星标和草稿功能。`messages` 表新增 `isStarred` 字段(接收方可标记重要消息)+ `messages_receiver_starred_idx` 复合索引;新增 `message_drafts`userId/receiverId/subject/content/parentMessageId + updatedAt/createdAt`message_drafts_user_idx``message_drafts_user_updated_idx` 索引data-access 新增 `toggleMessageStar` / `getMessageDrafts` / `createMessageDraft` / `updateMessageDraft` / `deleteMessageDraft` / `getMessageDraftById`actions 新增 `toggleMessageStarAction` / `getMessageDraftsAction` / `saveMessageDraftAction` / `deleteMessageDraftAction``getMessages` 支持 `starredOnly` 筛选i18n 新增 actions.star/unstar/saveDraft/deleteDraft + messages.draftSaved/draftDeleted/starToggled + empty.noDrafts/noStarred 翻译键;迁移文件 `drizzle/0008_message_star_draft.sql`
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | ~280 | 7 个私信 Server Action + 2 个通知偏好 Action✅ P1-4通知 CRUD Action 已迁移至 notifications 模块;✅ V2-P0-2通知标题 i18n 化) |
| `data-access.ts` | ~290 | 私信 CRUD + `getMessagesPageData` 编排函数(✅ P1-5 新增)+ `getMessageDetailPageData` 编排函数(✅ V2-P1-3 新增) |
| `schema.ts` | 44 | 私信发送校验 + messageId 校验 + 通知偏好更新校验 |
| `types.ts` | 52 | 私信类型 + re-export 通知类型(向后兼容) |
| `hooks/use-message-search.ts` | ~60 | ✅ P1-7 新增:消息搜索 hook防抖 + 请求竞态取消) |
**组件清单**
| 组件 | 职责 |
|------|------|
| `components/message-list.tsx` | 消息列表(✅ P1-7使用 `useMessageSearch` hook + 客户端分页 UIPAGE_SIZE=20✅ V2-P1-2客户端过滤仅在初始数据时执行 |
| `components/message-detail.tsx` | 消息详情(含回复) |
| `components/message-compose.tsx` | 撰写新消息(✅ V2-P1-4fieldErrors + aria-invalid 字段级错误展示) |
| `components/unread-message-badge.tsx` | 未读消息计数徽章(侧边栏,每 30 秒轮询 `getUnreadMessageCountAction`;✅ V2-P2-1POLL_INTERVAL_MS 常量;✅ V2-P3间隔从 60s 缩短为 30s 与通知一致) |
**客户端行为**
- `message-list.tsx`:客户端调用 `getMessagesAction` 搜索消息useMessageSearch hook400ms 防抖请求竞态取消V2-P1-2 优化客户端过滤仅在初始数据type=all时执行
- `unread-message-badge.tsx`:每 `POLL_INTERVAL_MS`60_000ms轮询 `getUnreadMessageCountAction` 刷新未读计数
---
## 2.14 notifications通知分发模块
**职责**多渠道通知分发SMS/Email/WeChat/InApp+ 站内通知 CRUD + 通知偏好管理 + 通知 UI 组件。
**导出函数**
- Actions`sendNotificationAction` / `sendClassNotificationAction` / `getNotificationsAction` / `getUnreadNotificationCountAction` / `markNotificationAsReadAction` / `markAllNotificationsAsReadAction` / `archiveNotificationAction`(✅ P1-4 新增:后 4 个通知 CRUD Action 从 messaging 模块迁移;✅ V2-P2-13b 新增archiveNotificationAction 归档 Action
- Dispatcher`sendNotification(payload)` / `sendBatchNotifications(payloads)`
- Data-access`createNotification` / `getNotifications` / `markNotificationAsRead` / `markAllNotificationsAsRead` / `getUnreadNotificationCount` / `archiveNotification` / `unarchiveNotification` / `getUserContactInfo` / `logNotificationSend` / `logNotificationSendBatch`(✅ P0-4 / P1-5 修复后从 messaging 迁移;✅ V2-P2-13b 新增archiveNotification / unarchiveNotification 归档函数)
- Preferences`getNotificationPreferences` / `upsertNotificationPreferences`(✅ P0-4 / P1-5 修复后从 messaging 迁移)
- Channels`InAppChannelSender` / `SmsChannelSender` / `EmailChannelSender` / `WeChatChannelSender`
- Components`NotificationList` / `NotificationDropdown`(✅ P1-4 新增:从 messaging/components 迁移;✅ V2-P2-13bNotificationList 支持优先级 Badge 显示和归档操作;✅ V2-P2-13cNotificationList 支持按类型筛选)
- Hooks`useNotificationStream`(✅ V2-P3 新增SSE 实时推送 + 轮询降级 Hook`useDesktopNotifications`(✅ V2-P2-13c 新增:浏览器桌面推送 Hook支持权限管理和新通知桌面提醒
- Types`NotificationPriority`(✅ V2-P2-13b 新增:通知优先级类型 low/normal/high/urgent
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getStudentIdsByClassId
- 被依赖:`messaging`(✅ P0-4 / P1-5 已修复messaging 通过 `sendNotification` dispatcher 发送通知;✅ P1-4 已修复:通知 UI 组件由 notifications 模块自持messaging 不再反向依赖)、`layout`(✅ P1-4 已修复:通知下拉组件直接从 notifications 模块导入)、`app/(dashboard)/messages`(✅ P1-4 已修复:通知列表组件直接从 notifications 模块导入)
**已知问题**
- ✅ P0-4 已修复:~~不拥有任何数据,全部依赖 messaging 模块~~ messageNotifications 和 notificationPreferences 表所有权已从 messaging 迁移至 notifications 模块
- ✅ P0 已修复:~~与 messaging 双向依赖~~ notifications 不再反向依赖 messagingin-app-channel 改为静态导入本地 createNotification
- ✅ P1-1 已修复:~~`sendClassNotificationAction` 直查 `classes`/`classEnrollments`~~ 改为调用 `classes/data-access.getClassExists` / `getStudentIdsByClassId`
- ✅ P1-4 已修复:~~通知 UI 组件放在 messaging/components 下,直接 import notifications 类型和 messaging/actions~~ `notification-list.tsx``notification-dropdown.tsx` 已迁移至 `notifications/components/`,通知 CRUD Action 已从 messaging 迁移至 `notifications/actions.ts`,消除 UI 层跨模块耦合
- ✅ P2-11 已修复:~~通知标记已读无埋点~~ `markNotificationAsReadAction` / `markAllNotificationsAsReadAction` 新增 `trackEvent` 埋点notification.marked_read / notification.marked_all_read
- ✅ V2-P0-1 已修复:~~通知 i18n 键混在 messages.json 中~~ 新增独立的 `notifications.json` 命名空间zh-CN/en通知组件 `useTranslations``"messages"` 切换到 `"notifications"``src/i18n/request.ts` 新增 notifications 命名空间加载
- ✅ V2-P2-1 已修复:~~轮询间隔魔法数字~~ `notification-dropdown.tsx` 轮询间隔提取为 `POLL_INTERVAL_MS` 常量30_000ms
- ✅ V2-P3 已优化:~~30 秒轮询~~ `notification-dropdown.tsx` 改为 SSE 实时推送(`/api/notifications/stream`SSE 不可用时自动降级为轮询(调用 Server Actions间隔 30 秒)
- ✅ V2-P2-13b 新增通知优先级和归档功能。schema.ts `messageNotifications` 表新增 `priority`low/normal/high/urgent默认 normal`isArchived`(默认 false字段 + 2 个索引types.ts 新增 `NotificationPriority` 类型data-access.ts 新增 `archiveNotification` / `unarchiveNotification` 函数,`getNotifications` 支持归档和优先级筛选(默认仅返回未归档),`createNotification` 支持 priorityactions.ts 新增 `archiveNotificationAction`(含 trackEvent 埋点 notification.archivedNotificationList 组件支持优先级 Badge 显示和归档按钮i18n 新增 priority/actions.archive/messages.archiveFailed 翻译键
- ✅ V2-P2-13c 新增:通知分类筛选 + 浏览器桌面推送。NotificationList 组件新增按类型筛选按钮组all/message/announcement/homework/grade空状态区分"无通知"和"无筛选结果";新增 `useDesktopNotifications` Hook浏览器 Notification API支持权限管理、自动请求权限、新通知桌面推送、点击跳转NotificationDropdown 集成桌面推送监听新通知并触发桌面提醒首次加载不批量推送i18n 新增 filter.all / empty.noFilterResults / empty.noFilterResultsDesc 翻译键
- ⚠️ P1发送日志仅 console`notification_logs`
- ✅ 渠道抽象优秀(接口 + 工厂 + Mock 实现)
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `dispatcher.ts` | 152 | 渠道选择 + 并行分发 |
| `data-access.ts` | ~250 | 站内通知 CRUD + 用户联系方式 + 发送日志持久化notification_logs 表P0-4 / P1-5 修复后新增通知 CRUD✅ V2-P2-13b新增归档函数 + 优先级/归档筛选) |
| `preferences.ts` | 166 | 通知偏好 CRUDP0-4 / P1-5 修复后从 messaging 迁移) |
| `actions.ts` | ~300 | 7 个 Server Action✅ P1-4新增 4 个通知 CRUD Action✅ V2-P2-13b新增 archiveNotificationAction |
| `types.ts` | ~130 | 通知负载 + 渠道配置 + 通知记录 + 偏好类型P0-4 / P1-5 修复后扩充;✅ V2-P2-13b新增 NotificationPriority 类型 + priority/isArchived 字段) |
| `index.ts` | ~80 | 对外导出入口(✅ P1-4新增组件和 CRUD Action 导出;✅ V2-P2-13b新增归档函数/Action/类型导出;✅ V2-P2-13c新增 useNotificationStream / useDesktopNotifications Hook 导出) |
| `channels/*` | 5 文件 | 4 个渠道实现 |
| `components/notification-list.tsx` | ~190 | ✅ P1-4 新增(从 messaging 迁移):通知列表组件;✅ V2-P2-13b支持优先级 Badge 显示和归档操作;✅ V2-P2-13c支持按类型筛选 + 空状态区分 |
| `components/notification-dropdown.tsx` | ~180 | ✅ P1-4 新增(从 messaging 迁移):通知下拉菜单组件;✅ V2-P3改用 SSE 实时推送 + 轮询降级;✅ V2-P2-13c集成 useDesktopNotifications 桌面推送 |
| `hooks/use-notification-stream.ts` | ~195 | ✅ V2-P3 新增SSE 实时推送 HookEventSource + 轮询降级) |
| `hooks/use-desktop-notifications.ts` | ~100 | ✅ V2-P2-13c 新增:浏览器桌面推送 HookNotification API + 权限管理 + 点击跳转) |
**组件清单**
| 组件 | 职责 |
|------|------|
| `components/notification-list.tsx` | 通知列表(消息页底部,展示所有通知,支持标记已读;✅ V2-P0-1useTranslations 命名空间从 "messages" 切换到 "notifications";✅ V2-P2-13b支持优先级 Badge 显示和归档操作;✅ V2-P2-13c支持按类型筛选 + 空状态区分"无通知"/"无筛选结果" |
| `components/notification-dropdown.tsx` | 通知下拉菜单(站点头部,✅ V2-P3改用 SSE 实时推送 `/api/notifications/stream` + 轮询降级;✅ V2-P0-1useTranslations 命名空间切换;✅ V2-P2-1POLL_INTERVAL_MS 常量;✅ V2-P2-13c集成 useDesktopNotifications 桌面推送,监听新通知并触发桌面提醒) |
**客户端行为**
- `notification-dropdown.tsx`:✅ V2-P3 改用 `useNotificationStream` Hook 消费 SSE 实时推送(`/api/notifications/stream`SSE 不可用时自动降级为轮询(调用 `getNotificationsAction` + `getUnreadNotificationCountAction`,间隔 30 秒);✅ V2-P2-13c 集成 `useDesktopNotifications` Hook通过 `prevNotificationIds` 对比检测新通知并触发桌面推送(首次加载不批量推送)
- `hooks/use-notification-stream.ts`:✅ V2-P3 新增,管理 SSE 连接生命周期EventSource onopen/onmessage/onerror降级时通过 `pollFnRef` 调用 Server Actions 轮询
- `hooks/use-desktop-notifications.ts`:✅ V2-P2-13c 新增,封装浏览器 Notification API权限管理、自动请求权限、桌面推送、点击跳转回调SSR 安全(`typeof window !== "undefined"` 检查)
---
## 2.15 audit审计模块
**职责**:操作日志 + 登录日志 + 数据变更日志查询与导出。
**导出函数**
- Actions`getAuditLogsAction` / `getLoginLogsAction` / `getDataChangeLogsAction` / `exportAuditLogsAction` / `exportLoginLogsAction` / `exportDataChangeLogsAction`
- Data-access`getAuditLogs` / `getLoginLogs` / `getDataChangeLogs` / `getAuditModuleOptions`
**依赖关系**
- 依赖:`shared/*``@/auth`
- 被依赖:无
**已知问题**
- ⚠️ P2Excel 导出逻辑内联在 actions 层(应抽取到 `export.ts`
- ⚠️ P2三个导出 Action 结构高度重复
- ✅ P2 已修复:`data-access.ts` 中 6 处 catch 块添加 `console.error` 输出错误上下文;`toIso`/`clampPageSize`/`clampPage` 工具函数补齐显式返回类型
- ✅ data-access 职责清晰,无跨模块问题
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 212 | 6 个 Server Action含内联导出 |
| `data-access.ts` | 260 | 日志查询 |
| `types.ts` | - | 类型定义 |
---
## 2.16 announcements公告模块
**职责**:公告 CRUD + 发布/归档 + 发布通知。
**导出函数**
- Actions`getAnnouncementsAction` / `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction`(✅ P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access✅ 发布公告时触发通知模块 `sendBatchNotifications`/ `toggleAnnouncementPinAction` / `markAnnouncementAsReadAction` / `getAnnouncementReadStatusAction`(✅ V2-P2-13d 新增:置顶切换、已读标记、批量已读状态查询)
- Data-access`getAnnouncements`(支持 `audience` 受众过滤)/ `getAnnouncementById` / `insertAnnouncement` / `updateAnnouncementById` / `deleteAnnouncementById` / `publishAnnouncementById` / `archiveAnnouncementById`(后 5 个为 P1-2 新增)/ `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData`(✅ P1-5 新增:管理端列表页和编辑页编排函数,页面层仅调用单一函数)/ `toggleAnnouncementPin` / `markAnnouncementAsRead` / `isAnnouncementReadByUser` / `getAnnouncementReadCount` / `getAnnouncementReadStatusForUser`(✅ V2-P2-13d 新增:置顶切换 + 已读回执 CRUD操作 `announcement_reads` 表)
**依赖关系**
- 依赖:`shared/*``@/auth``school`(获取年级列表)、`classes`(获取班级列表 + 解析受众)、`users`(获取目标用户 ID 列表)、`notifications`(发布公告时发送通知)
- 被依赖:无
**已知问题**
- ✅ P1-2 已修复:~~所有写操作直接在 actions 层 `db.insert/update/delete`,未下沉到 data-access~~ 写操作已下沉到 data-access5 个新函数)
- ✅ P2 已修复:~~`getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(ANNOUNCEMENT_READ)`~~ 改为 `requirePermission(Permissions.ANNOUNCEMENT_READ)`
- ✅ 已修复:用户端列表页传入 `audience` 受众过滤school/grade/class管理端返回所有公告
- ✅ 已修复:用户端新增公告详情页 `/announcements/[id]`(只读模式)
- ✅ 已修复:管理端列表页传递 `classes` 数据给 `AdminAnnouncementsView`
- ✅ 已修复:发布公告时(`publishAnnouncementAction` / `createAnnouncementAction` 直接发布 / `updateAnnouncementAction` 状态变为 published触发通知模块 `sendBatchNotifications`
- ✅ 已修复:新增 `loading.tsx` 骨架屏(用户端 + 管理端)
- ✅ P1 已修复:~~全模块零 i18n中英文案硬编码~~ 所有组件接入 next-intl`useTranslations("announcements")`),新增 `src/shared/i18n/messages/{zh-CN,en}/announcements.json` 翻译字典title/description/filter/status/type/form/actions/messages/meta/empty/error 共 11 个命名空间);所有页面 `page.tsx` 使用 `generateMetadata` + `getTranslations` 替代硬编码 metadata
- ✅ P1 已修复:~~缺 Error Boundary~~ 新增 4 个 `error.tsx` 错误边界(`/announcements``/announcements/[id]``/admin/announcements``/admin/announcements/[id]`),统一使用 `EmptyState` + i18n 错误文案 + 重试按钮
- ✅ P2 已修复a11y 改进,`announcement-card.tsx` / `announcement-detail.tsx` 添加 `aria-label`
- ✅ P1-5 已修复:~~页面层 `Promise.all` 编排 announcements/school/classes 三个模块的 data-access~~ 新增 `getAdminAnnouncementsPageData``getEditAnnouncementPageData` 编排函数,页面层仅调用单一函数
- ✅ P1-6 已修复:~~`targetGradeId` / `targetClassId` 为 optional未根据 `type` 做条件必填校验~~ `CreateAnnouncementSchema``UpdateAnnouncementSchema` 添加 `superRefine(refineAudience)`,年级公告强制 `targetGradeId`,班级公告强制 `targetClassId`
- ✅ P2-11 已修复:~~发布/归档/删除公告无埋点~~ `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction` 新增 `trackEvent` 埋点announcement.created / announcement.updated / announcement.published / announcement.deleted / announcement.archived
- ✅ V2-P0-2 已修复:~~通知标题硬编码~~ `createAnnouncementAction` / `updateAnnouncementAction` / `publishAnnouncementAction` 通过 `getTranslations('announcements')` 生成 i18n 通知标题(`notification.publishedTitle` / `publishedContent`
- ✅ V2-P1-1 已修复:~~AnnouncementList 客户端 useState/useMemo 过滤~~ 改为纯服务端过滤模式Select 切换仅更新 URL `?status=` 触发 RSC 重新渲染
- ✅ V2-P1-4 已修复:~~表单无服务端校验错误展示~~ `announcement-form.tsx` 新增 `fieldErrors` 状态 + `aria-invalid` 字段级错误展示title/content/targetGradeId/targetClassId
- ✅ V2-P2-13d 已实现:公告置顶 + 已读回执。`announcements` 表新增 `isPinned` 字段(置顶优先排序,`getAnnouncements` orderBy 改为 `desc(isPinned), desc(createdAt)`);新增 `announcement_reads` 表(唯一索引 announcementId+userId 保证幂等);新增 3 个 Server Action`toggleAnnouncementPinAction` / `markAnnouncementAsReadAction` / `getAnnouncementReadStatusAction`+ 5 个 data-access 函数(`toggleAnnouncementPin` / `markAnnouncementAsRead` / `isAnnouncementReadByUser` / `getAnnouncementReadCount` / `getAnnouncementReadStatusForUser``Announcement` 类型新增 `isPinned` / `isReadByCurrentUser?` / `readCount?` 字段;新增 `AnnouncementRead` 类型;埋点新增 `announcement.pin_toggled` / `announcement.marked_read`
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | ~400 | 9 个 Server Action + 通知触发逻辑 + trackEvent 埋点P1-2 已修复,无直接 DB 操作V2-P2-13d 新增置顶/已读/批量已读状态 3 个 Action |
| `data-access.ts` | ~300 | 公告 CRUD + 发布/归档 + 受众过滤 + `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData` 编排函数(✅ P1-5 新增V2-P2-13d 新增置顶切换 + 已读回执 5 个函数,操作 `announcement_reads` 表) |
| `schema.ts` | ~70 | Zod 校验 + `refineAudience` 条件校验(✅ P1-6 新增 superRefine |
| `types.ts` | ~75 | 类型定义(`GetAnnouncementsParams` 新增 `audience` 字段V2-P2-13d `Announcement` 新增 `isPinned` / `isReadByCurrentUser?` / `readCount?`,新增 `AnnouncementRead` 接口) |
**组件清单**
| 组件 | 职责 |
|------|------|
| `components/announcement-list.tsx` | 公告列表(用户端,支持状态筛选;✅ V2-P1-1纯服务端过滤Select 切换更新 URL ?status= 触发 RSC 重新渲染;✅ V3新增 `detailHrefPrefix` prop 替代 `detailHrefBuilder` 函数 prop解决 Next.js 16 Server→Client 序列化限制) |
| `components/announcement-card.tsx` | 公告卡片(列表项) |
| `components/announcement-detail.tsx` | 公告详情(只读) |
| `components/announcement-form.tsx` | 公告表单(创建/编辑,✅ P1-6条件校验由 schema superRefine 保证;✅ V2-P1-4fieldErrors + aria-invalid 字段级错误展示) |
| `components/admin-announcements-view.tsx` | 管理端公告视图(列表 + 筛选) |
---
## 2.17 files文件模块
**职责**:文件附件 CRUD + 批量删除 + 统计。
**导出函数**
- Data-access`getAllFileAttachments` / `getFileAttachmentsByOwner` / `getFileAttachmentById` / `createFileAttachment` / `updateFileAttachment` / `deleteFileAttachment` / `batchDeleteFileAttachments` / `getFileStats`(✅ P2 已修复7 个读函数使用 `React.cache()` 包装实现请求级 memoization`getFileAttachment` / `getFileAttachmentsByTarget` / `getFileAttachmentsByUploader` / `getAllFileAttachments` / `getFileAttachmentsWithFilters` / `getFileStats` / `getFileAttachmentsByIds`
**依赖关系**
- 依赖:`shared/*``@/auth`
- 被依赖:`app/api/upload` / `app/api/files/[id]` / `app/api/files/batch-delete`
**已知问题**
- ✅ P2-13 已修复:~~所有函数 try-catch 吞错误返回空数组/null~~ 所有 catch 块已添加 `console.error` 输出错误上下文
- ✅ P2 已修复:`getFileAttachmentsWithFilters``or(...)!` 非空断言清理为安全守卫
- ✅ P2 已修复:~~`getFileAttachmentsWithFilters``conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注
- ⚠️ P2`actions.ts`data-access 被路由直接调用
- ✅ 职责单一,不跨模块查询
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 267 | 文件 CRUD + 批量删除 + 统计 |
| `types.ts` | - | 类型定义 |
| `components/*` | 6 文件 | 上传/列表/预览/管理 |
---
## 2.18 course-plans课程计划模块
**职责**:课程计划 CRUD + 周计划项 CRUD + 排序。
**导出函数**
- Actions`getCoursePlansAction` / `getCoursePlanByIdAction` / `createCoursePlanAction` / `updateCoursePlanAction` / `deleteCoursePlanAction` / `createCoursePlanItemAction` / `updateCoursePlanItemAction` / `deleteCoursePlanItemAction` / `toggleCoursePlanItemCompletedAction`
- Data-access与 actions 对应
**依赖关系**
- 依赖:`shared/*``@/auth``classes`合理getAdminClasses/getStaffOptions`school`合理getAcademicYears
- 被依赖:无
**已知问题**
- ⚠️ P2`getSubjectOptions` 直查 `subjects`subjects 无独立模块,可接受)
- ✅ P2 已修复:`data-access.ts` 中 3 处 catch 块添加 `console.error` 输出错误上下文getCoursePlans/getCoursePlanById/getSubjectOptions
- ✅ actions 层使用 `handleError`/`revalidatePlanPaths` 辅助函数(良好范例)
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 320 | 课程计划 + 周计划项 CRUD |
| `actions.ts` | 265 | 9 个 Server Action |
| `schema.ts` | - | Zod 校验 |
| `types.ts` | - | 类型定义 |
---
## 2.19 parent家长模块
**职责**:家长视角的子女数据聚合与展示。
**导出函数**
- Data-access`getChildren` / `getChildBasicInfo` / `getChildDashboardData`V3-11 增强:并行调用 `homework/data-access.getStudentExamResults` 获取考试结果)/ `getParentDashboardData` / `verifyParentChildRelation` / `getChildNameList`(✅ v4 新增:用于详情页头部多子女切换器,一次批量查询避免 N+1
- TypesV3-11 新增):`ChildExamResultItem`单条考试结果submissionId/examId/examTitle/assignmentId/assignmentTitle/score/maxScore/submittedAt/status`ChildDashboardData` 扩展 `examResults: ChildExamResultItem[]` 字段
- Components`ParentDashboard` / `ChildCard` / `ChildDetailHeader` / `ChildDetailPanel`V3-11 增强:新增 exams Tab/ `SiblingSwitcher` / `ChildHomeworkSummary` / `ChildHomeworkDetail`v4 新增)/ `ChildGradeSummary` / `ChildGradeDetail`v4 新增)/ `ChildScheduleCard` / `ChildExamDetail`V3-11 新增:子女考试详情视图,汇总卡片+考试列表)/ `ParentChildrenDataPage` / `ParentNoChildrenPage` / `ParentAttentionBanner`v4 新增)/ `ParentAttendanceWarning`v4 新增)/ `ParentAttendanceRateCard`v4 新增)/ `ParentAttendanceCalendar`v4 新增)/ `ParentExportButton`v4 新增)
**v4 修复(产品/UX 维度)**
- ✅ FEAT-G01新增 `/parent/leave` 请假申请占位页(含 loading.tsx
- ✅ FEAT-G02详情页 Schedule Tab 支持完整周课表(新增 `weeklySchedule` 字段 + `ChildWeeklyScheduleItem` 类型 + `buildWeeklySchedule` 函数)
- ✅ FEAT-G03详情页 Grades Tab 新增 `ChildGradeDetail` 按科目分组展示(平均分、趋势、最近成绩)
- ✅ FEAT-G04详情页 Homework Tab 新增 `ChildHomeworkDetail` 展示完整作业信息(状态、截止、提交时间、尝试次数)
- ✅ FEAT-G05考勤页新增 `ParentAttendanceWarning` 异常预警横幅(聚合缺勤/迟到/低出勤率)
- ✅ FEAT-G06详情页底部新增"Contact Teacher"快捷入口
- ✅ FEAT-G07详情页头部新增 `SiblingSwitcher` 多子女切换器
- ✅ LAYOUT-P01仪表盘新增 `ParentAttentionBanner` 待办事项横幅
- ✅ LAYOUT-P02`ChildCard` 突出 Overdue 异常(红色边框 + AlertTriangle 图标)
- ✅ LAYOUT-P03仪表盘快捷入口改为 4 宫格大图标卡片
- ✅ LAYOUT-P04详情页改为 Tab 布局Overview/Homework/Grades/Schedule/Attendance/Diagnostic
- ✅ LAYOUT-P05详情页新增面包屑导航
- ✅ LAYOUT-P07成绩趋势图 X 轴改用序号,避免日期重叠
- ✅ LAYOUT-P08成绩页新增 `ParentExportButton` 导出按钮(占位)
- ✅ LAYOUT-P09考勤页新增 `ParentAttendanceCalendar` 月历视图(按状态着色,支持按月切换)
- ✅ LAYOUT-P10考勤异常高亮与 FEAT-G05 同步实现)
- ✅ NAV-P02Grades/Attendance 页面描述明确职责(多子女对比 vs 单子女详情)
- ✅ NAV-P03详情页实现 `?tab=` 参数支持
- ✅ NAV-P04所有 parent 路由新增 `loading.tsx` 骨架屏 + `error.tsx` 错误边界
- ✅ DATA-P02成绩卡片新增 TrendIcon 进步/退步/持平标识
- ✅ DATA-P03排名展示新增"Top X%"百分比
- ✅ DATA-P04作业列表新增科目标识 Badge
- ✅ DATA-P05作业分数显示新增"pts"单位
- ✅ DATA-P06考勤页新增 `ParentAttendanceRateCard` 出勤率汇总卡片
- ✅ HABIT-P01仪表盘"一眼定位异常"能力AttentionBanner 聚合)
- ✅ HABIT-P02待办横幅作业项直接跳转详情页 homework tab1 次点击到达)
- ✅ HABIT-P03多子女切换无需返回仪表盘
- ✅ HABIT-P06仪表盘展示未读/待办数量
- ✅ A11Y-P02Overdue 状态增加 AlertTriangle 图标辅助
- ✅ A11Y-P04成绩图表容器新增 aria-label
- ✅ PERF-P01/P02骨架屏 + 错误边界
- ✅ PERF-P03空状态新增"Contact support"引导按钮
- ✅ PERF-P04`ChildCard` Link 添加 `prefetch`
- ✅ MOBILE-P03移动端子女卡片改为水平滑动 Carouselsnap-x
- ✅ MOBILE-P04作业/成绩列表项 `min-h-[44px]` 触摸区域
**V3-11 修复(家长端考试详情)**
- ✅ V3-11`parent/types.ts` 新增 `ChildExamResultItem` 类型;`ChildDashboardData` 扩展 `examResults` 字段
- ✅ V3-11`parent/data-access.ts` `getChildDashboardData` 并行调用 `homework/data-access.getStudentExamResults` 获取子女考试结果(加入现有 `Promise.all`
- ✅ V3-11新增组件 `ChildExamDetail`(考试成绩汇总卡片:已参加考试数/平均分/最高分 + 考试成绩列表:考试标题/分数/得分率 Progress/提交时间)
- ✅ V3-11`child-detail-panel.tsx` 新增 exams Tab对标智学网家长端考试详情
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(合理)、`homework`合理V3-11 新增 `getStudentExamResults` 调用)、`grades`(合理)、`users`(合理)、`school`(合理)、`attendance`v4 新增:考勤页复用 `StudentAttendanceView`;⚠️ 跨模块 UI 类型依赖3 个组件直接 import `@/modules/attendance/types`
- 被依赖:无
**已知问题**
- ⚠️ P13 个 parent 组件(`parent-attendance-warning.tsx`/`parent-attendance-rate-card.tsx`/`parent-attendance-calendar.tsx`)直接 import `@/modules/attendance/types`,违反模块解耦原则(应通过 data-access 接口或 shared 类型抽象)
- ⚠️ P1parent-attendance-calendar.tsx 重新定义 `STATUS_DOT`/`STATUS_LABEL` 常量,与 attendance 模块重复
- ⚠️ P13 个 parent 组件的纯函数(`buildWarnings`/`aggregate`/`rateTone`/`formatDateKey`/`parseDateKey`/`buildCalendarDays`/`isSameDay`)未导出,无法单测
- ✅ P1 已修复:~~`app/(dashboard)/parent/children/[studentId]/page.tsx` 直接访问 DB违反三层架构~~ 改为调用 `verifyParentChildRelation` data-access 函数
- ✅ P1 已修复:~~权限校验未加 parentId 条件,存在信息泄露风险~~ `verifyParentChildRelation` 同时按 parentId + studentId 过滤
- ✅ P2 已修复:~~`getChildBasicInfo` 多次串行查询~~ 改为 `Promise.all` 并行化,并使用 `getStudentActiveClass` 一次 JOIN
- ✅ P2 已修复:~~`getGradeOptions` 全量查询效率低~~ 改为 `getGradeNameById` 按 ID 查询
- ✅ P2 已修复:~~`buildHomeworkSummary``[...assignments].sort()` 不必要拷贝~~ 改为 `toSorted()`
- ✅ P2 已修复:~~`in7Days` 死代码~~ 已删除
- ⚠️ v4 保留:`/parent/leave` 为占位页,待后端实现请假审批流后接入
- ✅ v4-P1-5 改进2026-06-23新增 `getParentIdsByStudentIds` data-access 函数,批量查询多个学生的家长 userId去重供 diagnostic/grades 模块通知场景调用
- ✅ v4-P1-9 改进2026-06-23parent/diagnostic/page.tsx 新增错误卡片展示子女诊断查询失败原因
- ✅ v4-P1-12 改进2026-06-23`ParentExportButton` 接入 `exportGradesAction`,支持按 studentId 导出单个子女成绩parent/grades/page.tsx 传 studentId 到 ParentExportButton
- ⚠️ v4 保留:详情页 Attendance/Diagnostic Tab 为占位提示,待对应功能实现后填充
- ✅ v3-P2 改进2026-06-23parent/grades/page.tsx 为每个子女并行查询 `getClassAverageTrend`,渲染 GradeTrendCardparent/diagnostic/page.tsx 传入 `practiceHrefBase={null}` 隐藏练习按钮
- ✅ 职责单一,正确复用其他模块 data-access
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 243+ | 子女关系 + 仪表盘数据聚合 + 关系校验 + 子女姓名列表v4 新增 `getChildNameList` + `buildWeeklySchedule`V3-11 `getChildDashboardData` 并行调用 `getStudentExamResults` |
| `types.ts` | 79+ | 类型定义(含 JSDocv4 新增 `ChildWeeklyScheduleItem`V3-11 新增 `ChildExamResultItem` + `ChildDashboardData.examResults` |
| `components/parent-dashboard.tsx` | 110 | 仪表盘v4 重构:待办横幅 + 宫格快捷入口 + 移动端水平滑动) |
| `components/parent-attention-banner.tsx` | 128 | v4 新增:待办事项/异常聚合横幅(作业项直接跳转详情页 homework tab |
| `components/parent-attendance-warning.tsx` | 89 | v4 新增:考勤异常预警 |
| `components/parent-attendance-rate-card.tsx` | 105 | v4 新增:考勤出勤率汇总卡片 |
| `components/parent-attendance-calendar.tsx` | 175 | v4 新增考勤月历视图use client |
| `components/parent-export-button.tsx` | 50 | v4 新增成绩导出按钮v4-P1-12 已接入 exportGradesAction支持按 studentId 导出单个子女成绩) |
| `components/child-card.tsx` | 148 | 子女卡片v4 增强:异常突出 + 趋势图标) |
| `components/child-detail-header.tsx` | 78 | 详情页头部v4 增强:面包屑) |
| `components/child-detail-panel.tsx` | 200+ | 详情页 Tab 面板 + SiblingSwitcherv4 重写,集成 Homework/Grade DetailV3-11 新增 exams Tab |
| `components/child-homework-summary.tsx` | 147 | 作业摘要v4 增强:科目标识 + 触摸区域 + pts 单位) |
| `components/child-homework-detail.tsx` | 145 | v4 新增:作业详情视图(完整作业信息) |
| `components/child-grade-summary.tsx` | 159 | 成绩趋势v4 增强:趋势图标 + aria-label |
| `components/child-grade-detail.tsx` | 165 | v4 新增:成绩详情视图(按科目分组分析) |
| `components/child-exam-detail.tsx` | - | V3-11 新增:子女考试详情视图(汇总卡片+考试列表,对标智学网家长端) |
| `components/child-schedule-card.tsx` | 119 | 课表卡片v4 增强:周课表视图) |
| `components/parent-children-data-page.tsx` | 92 | 共享数据页v4 增强headerExtra |
**路由清单**
| 路由 | 文件 | 说明 |
|------|------|------|
| `/parent/dashboard` | `dashboard/page.tsx` + `loading.tsx` | 家长仪表盘 |
| `/parent/grades` | `grades/page.tsx` + `loading.tsx` | 多子女成绩聚合v3-P2并行查询 getClassAverageTrend + GradeTrendCard |
| `/parent/diagnostic` | `diagnostic/page.tsx` + `loading.tsx` + `error.tsx` | P2-5 新增多子女学情诊断聚合v3-P2practiceHrefBase={null} 隐藏练习按钮) |
| `/parent/attendance` | `attendance/page.tsx` + `loading.tsx` | 多子女考勤聚合v4 新增预警横幅) |
| `/parent/leave` | `leave/page.tsx` + `loading.tsx` | v4 新增:请假申请(占位) |
| `/parent/children/[studentId]` | `children/[studentId]/page.tsx` + `loading.tsx` | 子女详情页v4 重构Tab 布局 + 多子女切换) |
| `error.tsx` | `error.tsx` | v4 新增:错误边界 |
---
## 2.20 elective选课模块
**职责**:选修课程管理 + 学生选课 + 抽签。
**导出函数**
- Actions11 个):`getElectiveCoursesAction` / `createElectiveCourseAction` / `updateElectiveCourseAction` / `deleteElectiveCourseAction` / `getStudentSelectionsAction` / `selectCourseAction` / `dropCourseAction` / `runLotteryAction` / `getAvailableCoursesForStudentAction` / `openSelectionAction` / `closeSelectionAction`(✅ P2-28 个写 Action 错误消息改用 `getTranslations("elective")` 国际化)
- Data-access`getElectiveCourses` / `getElectiveCourseById` / `createElectiveCourse` / `updateElectiveCourse` / `deleteElectiveCourse` / `openSelection` / `closeSelection` / `buildCourseSelect` / `mapCourseRow` / `resolveCourseDisplayNames`(✅ P2-7使用 React `cache()` 包装 subject/grade options 请求级去重;✅ P2-8通过 `resolvers.ts` 接口抽象跨模块依赖)/ `CourseCoreRow`
- Data-access-operations`selectCourse`(✅ P2-9新增 `checkScheduleConflict` 时间冲突检测;✅ P2-10新增 `checkCreditLimit` 学分上限校验)/ `dropCourse` / `runLottery` / `buildLotteryRankCase`(⚠️ 未导出,无法单测)/ `checkScheduleConflict`P2-9 新增)/ `checkCreditLimit`P2-10 新增)
- Data-access-selections`getCourseSelections` / `getStudentSelections` / `getStudentGradeId`(✅ P2-8通过 `resolvers.ts` StudentGradeResolver 接口抽象)/ `getAvailableCoursesForStudent` / `resolveStudentDisplayNames`(✅ P2-8通过 `resolvers.ts` CourseDisplayResolver 接口抽象)
- Resolvers✅ P2-8 新增):`CourseDisplayResolver` / `StudentGradeResolver`(接口)/ `getCourseDisplayResolver` / `getStudentGradeResolver` / `setCourseDisplayResolver` / `setStudentGradeResolver` / `resetResolvers`(可注入测试 mock
- Lib 纯函数(✅ P2-9 新增):`parseSchedule`(解析时间段字符串)/ `isScheduleConflict`(判断两个时间段是否冲突)
- Import-export✅ P2-11 新增):`exportElectiveCoursesToExcel`(课程列表导出)/ `exportCourseSelectionsToExcel`(选课名单导出)
- Components`ElectiveCourseList`(课程卡片网格 + 管理操作)/ `ElectiveCourseForm`(课程创建/编辑表单)/ `ElectiveFilters`nuqs 筛选栏)/ `StudentSelectionView`(学生选课视图)/ `ElectivePageLayout`(✅ P2-4 新增页面布局骨架admin/teacher 选课页复用)
**依赖关系**
- 依赖:`shared/*``@/auth``school`(✅ P3 已修复:通过 school data-access.getSubjectOptions/getGradeOptions 获取科目/年级名称;✅ P2-8通过 resolvers.ts 接口抽象)、`users`(✅ P3 已修复:通过 users data-access.getUserNamesByIds✅ P2-8通过 resolvers.ts 接口抽象)、`classes`(通过 classes data-access.getStudentActiveGradeId✅ P2-8通过 resolvers.ts 接口抽象)、`next-intl`(✅ P2-2Server Action 错误消息 i18n
- 被依赖:`app/api/export`(✅ P2-11通过 `exportElectiveCoursesToExcel` / `exportCourseSelectionsToExcel`
**已知问题**(详见 `docs/architecture/audit/attendance-elective-audit-report.md`
- ✅ P2-2 已修复8 个写 Action 错误消息改用 `getTranslations("elective")` 国际化
- ✅ P2-4 已修复:新增 `ElectivePageLayout` 组件admin/teacher 选课页复用布局
- ✅ P2-7 已修复:`resolveCourseDisplayNames` 使用 React `cache()` 包装 subject/grade options 请求级去重
- ✅ P2-8 已修复:新增 `resolvers.ts`跨模块依赖users/school/classes通过接口抽象支持测试注入 mock
- ✅ P2-9 已修复:`selectCourse` 新增时间冲突检测(`parseSchedule` + `isScheduleConflict` 纯函数 + `checkScheduleConflict` 异步查询)
- ✅ P2-10 已修复:`selectCourse` 新增学分上限校验(`checkCreditLimit`MAX_CREDIT_PER_TERM=10
- ✅ P2-11 已修复:新增 `export.ts`,课程列表/选课名单可导出 Excel
- ❌ P03 个读 Action 无调用方(`getElectiveCoursesAction`/`getStudentSelectionsAction`/`getAvailableCoursesForStudentAction`),页面绕过 Action 直接调用 data-access
- ❌ P0update/delete/select/drop/lottery Action 缺资源归属校验(教师 A 可操作教师 B 的课程,学生可退选他人课程)
- ❌ P0i18n 完全缺失4 组标签/颜色常量硬编码英文,组件中硬编码中文)
- ❌ P0错误边界完全缺失3 个角色目录均无 `error.tsx`
- ⚠️ P1`elective-course-form.tsx` 存在 `v as "fcfs" | "lottery"` 类型断言
- ⚠️ P1`elective-course-list.tsx` 存在 `null as never` 类型逃逸
- ⚠️ P1`buildLotteryRankCase` 未导出,无法单测
- ⚠️ P1`SELECTION_MODE_LABELS` 已定义但表单未复用,硬编码 Select 选项
- ✅ P1 已修复:~~`buildCourseSelect` 跨模块 join users/subjects/grades 表~~ 改为只查 electiveCourses 表,通过 `resolveCourseDisplayNames` 调用 school/users data-access 获取显示名称
- ✅ P1 已修复:~~`getSubjectOptions` 本地直查 subjects 表且与 school 模块重复~~ 删除本地实现,改用 `school/data-access.getSubjectOptions`
- ✅ P1 已修复:~~`selectCourse`/`dropCourse` 缺事务包裹~~ 改为 `db.transaction` 包裹FCFS 模式下使用 `FOR UPDATE` 行锁防止并发超卖
- ✅ P2 已修复:~~`mapCourseRow` 在 data-access.ts 与 data-access-selections.ts 重复定义~~ 抽取到 data-access.ts 统一导出data-access-selections.ts 复用
- ✅ P2 已修复:~~`runLottery` 使用 `sort(() => Math.random() - 0.5)` 有偏 shuffle~~ 改为 Fisher-Yates 无偏洗牌算法
- ✅ P2 已修复:~~`selectCourse` FCFS 并发超卖风险~~ 使用 `db.transaction` + `.for("update")` 行锁
- ✅ 权限校验完整ELECTIVE_MANAGE/SELECT/READ
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 304 | 11 个 Server ActionP2-2错误消息 i18n 化) |
| `data-access.ts` | 250 | 课程 CRUD + scope 过滤 + 共享映射函数P2-7cache 包装P2-8resolvers 接口抽象) |
| `data-access-operations.ts` | 370 | 选课操作P2-9时间冲突检测P2-10学分上限校验P3事务 + FOR UPDATE 锁 + Fisher-Yates 洗牌) |
| `data-access-selections.ts` | 149 | 选课记录查询 + 学生可选课程P2-8resolvers 接口抽象) |
| `resolvers.ts` | 83 | 跨模块依赖接口抽象P2-8 新增CourseDisplayResolver/StudentGradeResolver + 注入/重置函数) |
| `export.ts` | 102 | Excel 导出P2-11 新增:课程列表 + 选课名单双函数) |
| `schema.ts` | 132 | Zod 校验 |
| `types.ts` | 108 | 类型定义 + 4 组标签/颜色常量(硬编码英文) |
| `components/elective-course-list.tsx` | 233 | 课程卡片网格 + 管理操作 |
| `components/elective-course-form.tsx` | 293 | 课程创建/编辑表单 |
| `components/elective-filters.tsx` | 49 | nuqs 筛选栏(搜索 + 模式) |
| `components/student-selection-view.tsx` | 250 | 学生选课视图(已选 + 可选) |
| `components/elective-page-layout.tsx` | 30 | 页面布局骨架P2-4 新增header/children 插槽) |
---
## 2.21 proctoring监考模块
**职责**:考试监考事件记录 + 防作弊监控 + 监考面板。
**导出函数**
- Actions`recordProctoringEventAction` / `getProctoringDashboardAction`
- Data-access`recordProctoringEvent` / `getExamSubmissionForProctoring` / `getExamForProctoring` / `getExamProctoringSummary` / `getStudentProctoringStatuses` / `getRecentProctoringEvents`(✅ P2 已修复:`getExamProctoringSummary` 使用 `Promise.all` 并行化考试信息与提交记录查询、事件类型统计与学生事件统计查询;合并两次 filter 为单次循环统计 started/submitted
**依赖关系**
- 依赖:`shared/*``@/auth``exams`(✅ P1-1 已修复:通过 exams data-access.getExamForProctoringCrossModule/getExamSubmissionForProctoringCrossModule/getExamSubmissionsForExam/getExamTitleById`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds
- 被依赖:无
**已知问题**
- ✅ P1-2 已修复:`exam-mode-config.tsx` 已集成到考试表单(`exam-form.tsx` 调用 `<ExamModeConfig />`),移除全部 10 处 `as` 类型断言,改用 `useFormContext` 替代 `Control` prop 避免 `Control<T>` 不变型问题
- ✅ P1-3 已修复:`proctoring-dashboard.tsx` 用类型守卫函数 `isProctoringEventType` + `toProctoringEventTypes` 替代 `Object.keys(...) as ProctoringEventType[]` 断言
- ✅ P0-6 已修复:~~事件上报存在 Server Action 与 REST API 双通道重复~~ 删除 `/api/proctoring/event` REST 路由(移至 deletes/Server Action `recordProctoringEventAction` 为唯一规范路径
- ✅ P1-1 已修复:~~跨模块直查 `exams`/`examSubmissions`/`users`~~ 改为通过 exams/users data-access 函数获取数据
- ✅ P2 已修复:`actions.ts` 不再直接 import `db``examSubmissions`submission 归属校验已下沉到 data-access`recordProctoringEventAction` 改用 `requirePermission(EXAM_SUBMIT)` 并增加 `revalidatePath`
- ✅ P2 已修复:~~`getStudentProctoringStatuses` 串行查询getUserNamesByIds 后再查事件)~~ 改为 `Promise.all` 并行拉取学生姓名与事件记录
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 409 | 事件记录 + 查询 + 摘要统计 + submission 归属校验 |
| `actions.ts` | 139 | 2 个 Server Action |
| `types.ts` | 136 | 类型定义 + 标签常量 + 阈值常量 |
| `components/anti-cheat-monitor.tsx` | - | 学生端防作弊监控 |
| `components/exam-mode-config.tsx` | - | 考试模式配置(✅ P1-2 已修复:已集成到 exam-formuseFormContext 替代 Control prop无 as 断言) |
| `components/proctoring-dashboard.tsx` | - | 教师监考面板(✅ P1-3 已修复:类型守卫函数替代 as 断言) |
---
## 2.22 diagnostic学情诊断模块
**职责**:知识点掌握度查询 + 诊断报告生成。
**导出函数**
- Actions`generateStudentReportAction` / `generateClassReportAction` / `publishReportAction`v4-P1-4 + v4-P1-5 增强:班级报告发布时通知全班学生 + 全班学生家长,个人报告通知学生本人 + 其家长,调用 `parent/data-access.getParentIdsByStudentIds` 批量查询家长 ID/ `deleteReportAction`v2-P2-3 修复:删除死代码 `getDiagnosticReportsAction` / `getDiagnosticReportByIdAction`,页面直接调用 data-access 并自行权限校验)
- Data-access`updateMasteryFromSubmission`v2-P1-8 修复累积模式v2-P2-5 修复db.transaction 包裹)/ `getStudentMastery`P3-19 修复:移除 export改为模块内部函数/ `getStudentMasterySummary`P3-18 修复getUserNamesByIds 与 getStudentMastery 并行查询)/ `getClassMasterySummary`v2-P2-4 修复totalStudents 语义 + 班级平均掌握度按学生平均)/ `getKnowledgePointStats`v2-P1-7 修复:页面先查班级再传参)
- Data-access-reports`generateDiagnosticReport` / `generateClassDiagnosticReport`v2-P2-6 修复校验掌握度数据P3-27 修复:使用 DiagnosticReportError 结构化错误码)/ `getDiagnosticReports`P3-15 修复:支持分页 limit/offset返回 { reports, total } 结构v4-P1-1 增强:新增可选 `scope?: DataScope` 参数,支持 children/class_taught 行级权限过滤class_taught 通过 `getStudentIdsByClassIds` 解析所教班级学生 ID/ `getDiagnosticReportById` / `publishDiagnosticReport` / `deleteDiagnosticReport`(✅ P2 已修复:使用 `React.cache()` 包装实现请求级 memoizationP3-1 修复toNumber 从 grades 模块导入)/ `DiagnosticReportError`P3-27 新增:结构化错误码类)
- Stats-service✅ v2-P1-6 新增):`serializeMasteryWithKp` / `computeAverageMastery` / `classifyStrengthsWeaknesses`P3-16 修复:弱项阈值从 <60 改为 <80消除 60-79 盲区)/ `buildStudentMasterySummary` / `aggregateClassMastery` / `computeKpStats` / `computeClassAverageMastery` / `buildStudentsNeedingAttention` / `buildClassMasterySummary` / `buildStudentReportContent` / `buildClassReportContent` / `computeMasteryLevel` / `serializeMastery`(从 data-access / data-access-reports 抽取的纯统计函数)
- Schema`GenerateStudentReportSchema` / `GenerateClassReportSchema` / `PublishReportSchema` / `DeleteReportSchema`v2-P2-3 修复:删除死代码 `GetDiagnosticReportsSchema` / `GetDiagnosticReportByIdSchema`
**依赖关系**
- 依赖:`shared/*``@/auth``exams`(✅ P1-1 已修复:通过 exams data-access.getExamSubmissionWithAnswers`questions`(✅ P1-1 已修复:通过 questions data-access.getKnowledgePointsForQuestions`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getActiveStudentIdsByClassIdv2-P1-7 新增 getStudentActiveClassId`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId`grades`P3-1 修复:通过 grades/lib/grade-utils.toNumber 复用工具函数)
- 被依赖:无
**已知问题**
- ✅ P1-1 已修复:~~`updateMasteryFromSubmission` 跨模块直查 4 张表~~ 改为调用 `exams/data-access.getExamSubmissionWithAnswers``questions/data-access.getKnowledgePointsForQuestions`
- ✅ P2 已修复:~~`data-access-reports.ts` 有未使用代码(`round2` + `void round2`~~ 已删除死代码
- ✅ P2 已修复:~~`updateMasteryFromSubmission` 循环内串行 await upsert~~ 改为 `Promise.all` 并行执行所有 upsert
- ✅ P2 已修复:~~`getClassMasterySummary` 串行查询~~ 改为两组 `Promise.all` 并行
- ✅ P2 已修复:~~`getDiagnosticReports``conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注
- ✅ P0-2 已修复:~~`data-access-reports.ts` 直查 `users` 表获取姓名~~ 改为通过 `users/data-access.getUserNamesByIds`
- ✅ P2-2 已修复:~~Tailwind 任意值~~ 改用标准 Tailwind 类
- ✅ P2-1 已修复:~~图表/表格/列表缺少 a11y ARIA 属性~~ 为 5 个图表添加 `role="img"` + `aria-label`2 个表格添加 `<caption>`3 个列表添加 `role="list"`3 个图标按钮添加 `aria-label`
- ✅ P2-3 已修复:~~班级报告将生成者 ID 存入 `studentId` 字段~~ schema 改为可空,班级报告 `studentId` 置空
- ✅ v2-P1-6 已修复:~~统计逻辑混在 data-access 层~~ 抽取 `stats-service.ts`352 行12 个纯函数 + 2 个常量 + 4 个接口)
- ✅ v2-P1-7 已修复:~~`getKnowledgePointStats` 无参调用~~ 页面先查 `getStudentActiveClassId` 再传参
- ✅ v2-P1-8 已修复:~~`updateMasteryFromSubmission` 覆盖模式~~ 改为累积计算(读取已有记录后累加)
- ✅ v2-P2-3 已修复:~~死代码 `getDiagnosticReportsAction` / `getDiagnosticReportByIdAction` 全局零调用~~ 已删除,页面直接调用 data-access
- ✅ v2-P2-4 已修复:~~`totalStudents` 语义错误 + 班级平均掌握度计算偏差~~ 改为实际有掌握度记录的学生数;先算学生个人平均再取平均
- ✅ v2-P2-5 已修复:~~多 upsert 无事务包裹~~ 使用 `db.transaction()` 保证原子性
- ✅ v2-P2-6 已修复:~~生成报告未校验掌握度数据~~ 添加 `totalKnowledgePoints === 0``studentCount === 0` 校验
- ✅ v2-P1-4 已修复:~~4 个组件 i18n 完全未接入~~ 全部接入 `useTranslations("diagnostic")`
- ✅ v2-P2-7 已修复:~~`report-list.tsx` 过滤器 Label 缺少 `htmlFor`~~ 添加 `htmlFor``id`
- ✅ 与 grades 模块无职责重叠
- ✅ v3-P2 改进2026-06-23`StudentDiagnosticView` 新增 `practiceHrefBase` propstring | nullnull 时隐藏练习按钮teacher/diagnostic/student/[studentId] 传入 `practiceHrefBase="/teacher/questions"`parent/diagnostic 传入 `practiceHrefBase={null}` 隐藏练习按钮
- ✅ P3-1 已修复2026-06-23~~`data-access-reports.ts` 本地定义 `toNumber``grades/lib/grade-utils.ts` 重复~~ 改为从 grades 模块导入
- ✅ P3-15 已修复2026-06-23~~`getDiagnosticReports` 无分页,可能返回大量数据~~ 添加 limit/offset 分页支持,返回 `{ reports, total }` 结构Promise.all 并行查询总数和数据
- ✅ P3-16 已修复2026-06-23~~强弱项分类存在 60-79 盲区~~ 弱项阈值从 <60 改为 <80确保所有知识点都被分类
- ✅ P3-17 已修复:~~班级报告 strengths 无数量上限~~ `buildClassReportContent` 中 strengths 已限制为前 5 个(按掌握度降序)
- ✅ P3-18 已修复2026-06-23~~`getStudentMasterySummary` 串行查询用户名和掌握度~~ 改为 Promise.all 并行查询
- ✅ P3-19 已修复2026-06-23~~`getStudentMastery` 使用 export 但仅内部使用~~ 移除 export改为模块内部函数
- ✅ P3-27 已修复2026-06-23~~`generateDiagnosticReport` / `generateClassDiagnosticReport``throw new Error("...")` 直接暴露给用户~~ 改为使用 `DiagnosticReportError` 结构化错误码(继承 BusinessError
- ✅ P3-21 已修复2026-06-23`ClassDiagnosticView` View 按钮已有描述性 aria-label`classDiagnostic.viewAriaLabel`,含学生名)
- ✅ P3-22 已修复2026-06-23`StudentDiagnosticView` Practice 按钮添加 aria-label`studentDiagnostic.practiceAriaLabel`,含知识点名)
- ✅ v4-P3-6 已修复2026-06-23`MasteryRadarChart` 雷达图添加 `tabIndex={0}` 键盘可达性 + `focus-visible` 焦点样式(原有 `role="img"` + `aria-label` 保留)
- ✅ v4-P1-1 改进2026-06-23`getDiagnosticReports` 新增可选 `scope?: DataScope` 参数,支持 children/class_taught 行级权限过滤teacher/diagnostic/page.tsx 传入 dataScopestudent/diagnostic/page.tsx 传 status:"published" + dataScopeparent/diagnostic/page.tsx 传 status:"published" + dataScope + 错误卡片
- ✅ v4-P1-3 改进2026-06-23`student-diagnostic-view.tsx` 移除草稿回退逻辑(不再展示 draft 状态报告)
- ✅ v4-P1-4 + v4-P1-5 改进2026-06-23`publishReportAction` 班级报告发布时通知全班学生 + 全班学生家长(通过 `getStudentIdsByClassId` 获取全班学生,通过 `parent/data-access.getParentIdsByStudentIds` 获取家长 ID个人报告通知学生本人 + 其家长
- ✅ v4-P1-8 + v4-P1-11 改进2026-06-23`class-diagnostic-view.tsx` 热力图添加图例 + 表格 `overflow-x-auto` 水平滚动
- ✅ v4-P1-9 改进2026-06-23parent/diagnostic/page.tsx 新增错误卡片Error Card展示子女诊断查询失败原因
- ✅ v4-P1 数据库变更2026-06-23`learningDiagnosticReports` 表新增 `classId` 字段varchar(128), references classes.id, onDelete: set null+ `diagnostic_class_idx` 索引;`DiagnosticReport` 接口新增 `classId: string | null` 字段(班级报告关联班级 ID个人报告为 null
- ✅ v4-P1 师生关系校验2026-06-23teacher/diagnostic/student/[studentId]/page.tsx 新增 class_taught 师生关系校验
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 179 | 知识点掌握度查询 + 更新v2-P1-8 累积模式v2-P2-5 事务v2-P2-4 语义修正v2-P1-6 改用 stats-service 纯函数) |
| `data-access-reports.ts` | 183+ | 诊断报告 CRUDv2-P2-6 校验v2-P1-6 改用 stats-service 纯函数v4-P1-1getDiagnosticReports 新增 scope 参数支持行级权限过滤) |
| `stats-service.ts` | 352 | 统计计算纯函数v2-P1-6 新增12 个纯函数 + 2 个常量 + 4 个接口) |
| `actions.ts` | 165+ | 4 个 Server Actionv2-P2-3 删除 2 个死代码读 Actionv4-P1-4/v4-P1-5publishReportAction 新增全班学生 + 家长通知) |
| `schema.ts` | 23 | Zod 校验4 个 schemav2-P2-3 删除 2 个死代码 schema |
| `types.ts` | 87 | 类型定义 |
| `components/class-diagnostic-view.tsx` | 266 | 班级诊断视图v2-P1-6 热力图 a11yv2-P1-4 i18nv4-P1-8 热力图图例v4-P1-11 表格 overflow-x-auto |
| `components/student-diagnostic-view.tsx` | 225+ | 学生诊断视图v2-P1-4 i18nv3-P2 新增practiceHrefBase propnull 时隐藏练习按钮v4-P1-3 移除草稿回退逻辑) |
| `components/mastery-radar-chart.tsx` | 72 | 雷达图v2-P1-4 i18n |
| `components/report-list.tsx` | 265 | 报告列表v2-P2-7 Label htmlForv2-P1-4 i18n |
---
## 2.23 settings设置模块
**职责**:系统设置(学校信息/安全策略/文件上传/通知配置)+ AI Provider 管理 + 密码修改 + 个人资料 + 主题偏好 + 通知偏好 + 个人信息页(学生/教师概览)。
**导出函数**
- Actions`getAiProvidersAction` / `createAiProviderAction` / `updateAiProviderAction` / `deleteAiProviderAction` / `testAiProviderAction`
- Actions-password`changePasswordAction`(✅ P1 已修复:使用 `requirePermission(USER_PROFILE_UPDATE)` + Zod 校验 + DB 操作下沉到 data-access
- Actions-avatar`updateUserAvatarAction` / `removeUserAvatarAction`(✅ P2-8 新增:头像上传/删除,复用 `/api/upload` 路由)
- Actions-notifications`sendTestNotificationAction`(✅ P2-10 新增:发送测试通知,占位实现待接入真实通知服务)
- Actions-system-settings`getAdminSystemSettingsAction` / `saveAdminSystemSettingsAction`(✅ P0-3 新增:管理员系统设置 CRUD4 分类 Zod 校验)
- Actions-security`getSecurityCenterAction` / `toggleTwoFactorAction`(✅ P2-9 新增2FA 状态查询/切换 + 最近登录历史)
- Data-access`getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange`P1 新增,从 actions 下沉)
- Data-access-system-settings`getSystemSettingsByCategory` / `getAllSystemSettings` / `getSystemSetting` / `upsertSystemSetting` / `upsertSystemSettings`(✅ P0-3 新增system_settings 表 CRUD键值对存储模式
- Components`SettingsView`统一设置页布局5 标签页 General/Notifications/Appearance/Security/AI角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入Tab URL 持久化;每个 TabsContent 包裹 `SettingsSectionErrorBoundary` + `Suspense` 骨架屏AI 标签页条件渲染需 `AI_CONFIGURE` 权限)、`SettingsServiceProvider` / `useSettingsService`Context 注入 `SettingsService` 接口,解耦组件对 users/messaging actions 的直接依赖)、`SettingsSectionErrorBoundary`(分区 Error Boundary局部失败不影响整页`QuickLinksCard`快捷链接卡片i18n 键驱动)、`ProfileStudentOverview` / `ProfileStudentOverviewSkeleton`(学生概览异步 Server Component + 骨架屏)、`ProfileTeacherOverview` / `ProfileTeacherOverviewSkeleton`(教师概览异步 Server Component + 骨架屏)、`AdminSettingsView`(✅ P0-3 已修复:从 mock 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表)、`AvatarUpload`(✅ P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18n`SecurityCenterCard`(✅ P2-9 新增2FA 开关 + 最近登录历史卡片)、`ThemePreferencesCard`(✅ P2-11 已增强:集成 `LocaleSwitcher` 语言切换)
- Config`ROLE_SETTINGS_CONFIG` / `resolveRoleSettingsConfig`(配置驱动角色 → 设置视图映射,新增角色只需添加条目)
- Lib`buildStudentOverviewData` / `computeStudentStats` / `sortUpcomingAssignments` / `filterTodaySchedule` / `toWeekday`(纯数据计算函数,与 UI 分离,便于单元测试)
- Types`AiProviderSummary` / `AiProviderName` / `AiProviderExisting` / `SettingsService` / `ProfileService` / `NotificationPreferenceService`(服务接口定义,用于依赖注入解耦)
**依赖关系**
- 依赖:`shared/*`(含 `shared/lib/bcrypt-utils`)、`@/auth``messaging`(页面层通过 `SettingsService` 接口注入,组件层不直接 import`users`(页面层通过 `SettingsService` 接口注入)、`classes` / `homework` / `dashboard`ProfileStudentOverview 异步组件获取学生概览数据)、`notifications`(页面层获取通知偏好)
- 被依赖:无
**已知问题**
- ⚠️ P2混合 5 类职责AI Provider + 密码 + 资料 + 主题 + 通知偏好)
- ✅ P1 已修复:~~无 `data-access.ts``actions.ts` 直接使用 `db`~~ 新建 `data-access.ts`,所有 DB 操作已下沉
- ✅ P1 已修复:~~`changePasswordAction` 使用 `requireAuth()` 无 Zod 校验~~ 改为 `requirePermission(USER_PROFILE_UPDATE)` + `ChangePasswordSchema` Zod 校验 + 并行查询优化
- ✅ P2 已修复:`actions-password.ts` 删除本地 `normalizeBcryptHash`,统一复用 `shared/lib/bcrypt-utils.normalizeBcryptHash`,消除重复代码
- ✅ P2-a 已修复:~~admin/teacher/student/parent 四个设置视图重复布局~~ 新增 `SettingsView` 统一设置页布局 + `resolveRoleSettingsConfig` 配置驱动角色路由,删除 `parent-settings-view.tsx` / `student-settings-view.tsx` / `teacher-settings-view.tsx`
- ✅ parent 角色路由已修复:通过 `ROLE_SETTINGS_CONFIG` 配置驱动parent 用户正确渲染对应配置
- ✅ Tab URL 持久化已修复:`SettingsView` 改为受控模式,通过 `useSearchParams` 读取 `tab` 参数,`router.push` 更新 URL
- ✅ 登出二次确认已修复:`SettingsView` 的 Log out 按钮使用 `AlertDialog` 包裹,点击时弹出确认对话框
- ✅ AiProviderSettingsCard 已集成:`SettingsView` 新增 AI 标签页,条件渲染需 `AI_CONFIGURE` 权限
- ✅ password-change-form 任意值 Tailwind 类已修复:~~`[&>div]:bg-red-500` 等任意值类~~ Progress 组件新增 `indicatorClassName` prop使用标准颜色类
- ✅ P0 已修复:~~`notification-preferences-form.tsx` 跨模块直接 import messaging/actions~~ 改为通过 `useSettingsService().notifications.updatePreferences` 调用,页面层注入实现
- ✅ P0 已修复:~~`profile-settings-form.tsx` 跨模块直接 import users/actions~~ 改为通过 `useSettingsService().profile.updateProfile` 调用,页面层注入实现
- ✅ P0 已修复:~~i18n 完全缺失~~ 新增 `settings.json` 翻译文件zh-CN + en所有组件改用 `useTranslations` / `getTranslations`
- ✅ P1 已修复:~~缺少 Error Boundary~~ 新增 `SettingsSectionErrorBoundary`,每个 TabsContent + profile 页面角色概览区块均包裹
- ✅ P1 已修复:~~缺少 Suspense 骨架屏~~ 每个 TabsContent 包裹 `Suspense` + `SettingsSectionSkeleton`profile 页面包裹 `ProfileStudentOverviewSkeleton` / `ProfileTeacherOverviewSkeleton`
- ✅ P1 已修复:~~profile/page.tsx 业务逻辑与 UI 混合~~ 抽取 `buildStudentOverviewData` 等纯函数到 `lib/student-overview-data.ts`;拆分 `ProfileStudentOverview` / `ProfileTeacherOverview` 异步组件
- ✅ 密码修改有速率限制
- ✅ AI Provider 操作有 `AI_CONFIGURE` 权限校验
- ✅ P0-3 已修复:~~AdminSettingsView 为 mock 实现,无数据持久化~~ 新增 `system_settings` 表(键值对存储)+ `data-access-system-settings.ts` + `actions-system-settings.ts`AdminSettingsView 改为真实数据层
- ✅ P2-8 已修复:~~无头像上传~~ 新增 `updateUserAvatar` data-access + `actions-avatar.ts` + `AvatarUpload` 组件,复用 `/api/upload` 路由
- ✅ P2-9 已修复:~~无 2FA / 会话管理~~ 新增 `actions-security.ts` + `SecurityCenterCard` 组件2FA 开关占位 + 最近登录历史来自 login_logs 表)
- ✅ P2-10 已修复:~~通知偏好表单无测试通知按钮~~ 新增 `sendTestNotificationAction`,每个已启用渠道旁显示测试按钮
- ✅ P2-11 已修复:~~语言切换未集成到设置页~~ `ThemePreferencesCard` 集成 `LocaleSwitcher` 到 Appearance 标签页
- ✅ v1.1 已修复:~~`/settings` 页面 ErrorBoundary 触发Functions cannot be passed directly to Client Components~~ 新增 `actions-service.ts`"use server" 文件),导出 `updateProfileAction` + `updateNotificationPreferencesAction` 两个 Server Action wrapper`page.tsx` 直接传递 Server Action 引用;`SettingsService` 接口的 `getProfile`/`getPreferences` 改为可选
- ✅ v1.1 已修复:~~i18n 键双重 `settings.` 前缀MISSING_MESSAGE~~ `role-settings-config.tsx``descriptionKey` 去掉 `settings.` 前缀
- ✅ v1.1 已修复:~~AI 标签页 FormLabel 在 Form 上下文外使用getFieldState null~~ `ai-provider-settings-card.tsx` 中两处 `<FormLabel>` 改为 `<Label>`
- ✅ v1.1 已修复:~~非管理员访问 `/settings?tab=ai` 显示空白~~ `settings-view.tsx` 新增 `resolveTab()` 函数,对无权限的 tab 回退到 `general`
- ✅ v1.1 已修复:~~数据库 `notification_preferences` 表缺少 `quiet_hours_*` 字段~~ 通过 ALTER TABLE 添加缺失字段
- ✅ v1.1 已修复:~~数据库 `system_settings` 表不存在~~ 通过 CREATE TABLE 创建
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 160 | AI Provider CRUD + 测试P1 已修复,无直接 DB 操作) |
| `actions-password.ts` | 87 | 修改密码P1 已修复requirePermission + Zod + data-access |
| `actions-avatar.ts` | 56 | 头像上传/删除P2-8 新增requirePermission + revalidatePath |
| `actions-notifications.ts` | 46 | 发送测试通知P2-10 新增:占位实现待接入真实通知服务) |
| `actions-system-settings.ts` | 186 | 管理员系统设置 CRUDP0-3 新增4 分类 Zod 校验 + upsert |
| `actions-security.ts` | 165 | 2FA 状态查询/切换 + 最近登录历史P2-9 新增) |
| `actions-service.ts` | 60 | 设置页 Server Action wrapperv1.1 新增updateProfileAction + updateNotificationPreferencesAction作为 Server Action 引用传递给 Client Component避免 Server→Client 函数传递违规) |
| `data-access.ts` | 158 | AI Provider CRUD + 密码修改 DB 操作P1 新增) |
| `data-access-system-settings.ts` | 119 | system_settings 表 CRUDP0-3 新增:键值对存储模式) |
| `types.ts` | 60 | 类型定义AiProviderSummary + SettingsService/ProfileService/NotificationPreferenceService 接口) |
| `config/role-settings-config.tsx` | 85 | 角色设置页配置驱动映射ROLE_SETTINGS_CONFIG + resolveRoleSettingsConfig |
| `lib/student-overview-data.ts` | 150 | 学生概览纯数据计算buildStudentOverviewData + computeStudentStats 等,便于单测) |
| `components/settings-view.tsx` | 236 | SettingsView 统一设置页布局5 标签页 + Error Boundary + Suspense + i18n + SecurityCenterCard 集成) |
| `components/settings-service-context.tsx` | 39 | SettingsServiceProvider + useSettingsServiceContext 注入服务接口) |
| `components/settings-section-error-boundary.tsx` | 64 | 分区 Error Boundary局部失败不影响整页 |
| `components/quick-links-card.tsx` | 42 | 快捷链接卡片i18n 键驱动) |
| `components/profile-student-overview.tsx` | 91 | 学生概览异步 Server Component + 骨架屏 |
| `components/profile-teacher-overview.tsx` | 115 | 教师概览异步 Server Component + 骨架屏 |
| `components/admin-settings-view.tsx` | 425 | AdminSettingsView 系统设置视图P0-3 已修复:真实数据层 + 4 个 Card + i18n |
| `components/avatar-upload.tsx` | ~150 | 头像上传/预览/删除客户端组件P2-8 新增:文件验证 + i18n |
| `components/security-center-card.tsx` | ~240 | 安全中心卡片P2-9 新增2FA 开关 + 最近登录历史) |
| `components/profile-settings-form.tsx` | 158 | 个人资料表单(通过 SettingsService 注入i18n |
| `components/notification-preferences-form.tsx` | ~160 | 通知偏好表单(通过 SettingsService 注入i18n + P2-10 测试按钮) |
| `components/password-change-form.tsx` | ~130 | 密码修改表单i18n + a11y |
| `components/theme-preferences-card.tsx` | ~80 | 主题偏好卡片i18n + P2-11 LocaleSwitcher 集成) |
| `components/ai-provider-settings-card.tsx` | ~200 | AI 服务商配置卡片i18n |
---
## 2.24 auth认证 UI 模块)
**职责**:认证页面 UI登录/注册/布局)。
**导出函数**:纯 UI 组件(`LoginForm` / `RegisterForm` / `AuthLayout`
**依赖关系**
- 依赖:`shared/*`
- 被依赖:`app/(auth)/*`
**已知问题**
- ✅ 纯 UI 模块,无 data-access/actions/types
- ✅ 认证逻辑由 NextAuth + `shared/lib/auth-guard` 统一处理
**文件清单**
| 文件 | 职责 |
|------|------|
| `components/auth-layout.tsx` | 认证页面布局 |
| `components/login-form.tsx` | 登录表单 |
| `components/register-form.tsx` | 注册表单 |
---
## 2.25 layout布局模块
**职责**:应用骨架(侧边栏 + 顶部导航 + 导航配置 + 多角色切换)。
**导出函数**`AppSidebar` / `SidebarProvider` / `SiteHeader` + `navigation` 配置
**依赖关系**
- 依赖:`shared/hooks/use-permission``@/auth`useSession`messaging`(通知下拉)、`shared/components/ui/select`(角色切换下拉)
- 被依赖:`app/(dashboard)/layout.tsx`
**已知问题**
- ✅ P2 已修复:~~用权限反推角色~~ `app-sidebar.tsx` 改用 `hasRole()` 判断角色,并新增多角色切换机制(`SidebarContext.currentRole`/`setCurrentRole`null 表示自动检测;当用户拥有多个角色时在侧边栏底部显示 `Select` 下拉切换)
- ✅ navigation.ts 无幽灵路由13 个已修复)
**文件清单**
| 文件 | 职责 |
|------|------|
| `components/app-sidebar.tsx` | 侧边栏(根据权限渲染 + 多角色切换下拉) |
| `components/sidebar-provider.tsx` | 侧边栏状态 Context`currentRole`/`setCurrentRole` |
| `components/site-header.tsx` | 顶部导航(含通知下拉) |
| `config/navigation.ts` | 导航配置4 个角色) |
---
## 2.26 student学生 UI 模块)
**职责**:学生端 UI 组件(课程视图 + 课表筛选/视图)。
**导出函数**`StudentCoursesView` / `StudentScheduleFilters` / `StudentScheduleView`
**依赖关系**
- 依赖:`shared/*`
- 被依赖:`app/(dashboard)/student/*`
**已知问题**
- ⚠️ P2与 classes 模块的 `schedule-view.tsx`/`schedule-filters.tsx` 可能功能重叠
- ✅ 纯 UI 模块,数据由页面通过 classes data-access 获取
- ✅ 认证模式已统一:所有 student 页面使用 `getCurrentStudentUser()`users 模块)或 `getAuthContext()`shared 模块),不再直接调用 `auth()``getDemoStudentUser()`
**文件清单**
| 文件 | 职责 |
|------|------|
| `components/student-courses-view.tsx` | 学生课程视图(含 `ClassCard` memo 组件 + 加入班级表单,使用 `useTransition` |
| `components/student-schedule-filters.tsx` | 课表筛选器 |
| `components/student-schedule-view.tsx` | 学生课表视图 |
**路由文件清单**`app/(dashboard)/student/`
| 文件 | 职责 |
|------|------|
| `dashboard/page.tsx` + `loading.tsx` | 学生仪表盘 + 骨架屏 |
| `attendance/page.tsx` + `loading.tsx` | 学生考勤 + 骨架屏 |
| `diagnostic/page.tsx` + `loading.tsx` | 学情诊断 + 骨架屏 |
| `elective/page.tsx` + `loading.tsx` | 选课中心 + 骨架屏 |
| `grades/page.tsx` + `loading.tsx` | 我的成绩 + 骨架屏 |
| `learning/assignments/page.tsx` + `loading.tsx` | 作业列表(含 `AssignmentCard` 组件)+ 骨架屏 |
| `learning/assignments/[assignmentId]/page.tsx` + `loading.tsx` | 作业作答/复习 + 骨架屏 |
| `learning/courses/page.tsx` + `loading.tsx` | 课程列表 + 骨架屏 |
| `learning/textbooks/page.tsx` + `loading.tsx` | 教材列表 + 骨架屏 |
| `learning/textbooks/[id]/page.tsx` + `loading.tsx` | 教材阅读 + 骨架屏 |
| `schedule/page.tsx` + `loading.tsx` | 课表 + 骨架屏 |
| `error.tsx` | 路由组错误边界(提供"重试"按钮) |
---
## 2.27 lesson-preparation备课模块
**职责**:教师备课,基于教材章节创建课案(**节点图编辑器 React Flow**),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。
> 架构变更2026-06-21编辑器从列表式BlockRenderer + @dnd-kit升级为节点图式NodeEditor + @xyflow/react。数据结构从 v1blocks 数组)升级到 v2nodes + edges 节点图),旧数据通过 `migrateV1ToV2()` 自动迁移。
> 架构变更2026-06-23数据结构从 v2 升级到 v3课文锚点画布格式。正文节点textbook_content作为画布中央核心教学节点围绕正文组织。新增锚点系统range/point 两种锚定方式、占位符注入算法、11 种定制节点类型。旧数据通过 `migrateV2ToV3()` 自动迁移。
**数据结构**
- v1已废弃仅向后兼容读取`{ version: 1, blocks: Block[] }`
- v2已废弃仅向后兼容读取`{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }`
- v3当前`{ version: 3, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }`
- `TextbookContentNode`:正文节点(`draggable: false`,画布中央,可缩放)
- `LessonPlanNode`:教学节点(`Block` + `position`,可拖动可连线)
- `NodeAnchor`:锚点(`{ id, nodeId, type: "range"|"point", start, end?, textPreview?, invalid? }`
- `AnchorEdge`:锚点连线(教学节点 → 正文节点,默认 10% 透明度)
- `FlowEdge`:流程连线(教学节点 → 教学节点)
**导出函数**
- Data-access`data-access.ts``getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument`v3 规范化,兼容 v1/v2 旧数据)/ `buildDefaultSkeleton`v3 默认 10 节点骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById`
- Lib`lib/document-migration.ts``defaultDataForType` / `migrateV1ToV2` / `migrateV2ToV3` / `normalizeDocument` / `buildInitialContent` / `buildDefaultSkeleton` / `isTextbookContentNode` / `isAnchorEdge` / `getAnchorsForNode` / `getActiveAnchorIds` / `getAnchorEdges`
- Lib`lib/anchor-injector.ts``markdownToPlainText` / `injectPlaceholders` / `parseAnchoredText` / `toCircledNumber` / `getNextPointIndex` / `relocateAnchors` / `getAnchorColor`
- Lib`lib/node-summary.ts``getNodeSummary` / `getTextbookContentSummary` / `getNodeColor` / `NODE_COLORS`
- Lib`lib/rf-mappers.ts``toRfNodes`(支持 textbook_content 节点)/ `toRfEdges`(区分 anchor/flow 边透明度)/ `fromRfEdges`
- Data-access-versions`data-access-versions.ts``getLessonPlanVersions` / `createLessonPlanVersion` / `getVersionContent` / `revertToVersion` / `pruneAutoVersions`
- Data-access-templates`data-access-templates.ts``getLessonPlanTemplates` / `saveAsTemplate` / `deletePersonalTemplate`
- Data-access-knowledge`data-access-knowledge.ts``getLessonPlansByKnowledgePoint` / `getLessonPlansByQuestion`
- Publish-service`publish-service.ts``publishLessonPlanHomework`
- AI-suggest`ai-suggest.ts``suggestKnowledgePoints`
- Actions`getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction` / `getTextbooksForPickerAction` / `getChaptersForPickerAction`
**依赖关系**
- 依赖:`shared/*``@/auth``shared/lib/ai``@xyflow/react`(节点图编辑器)、`textbooks`(只读章节/知识点树)、`questions`(创建/查询题目)、`exams`(创建 exam 草稿)、`homework`(创建作业下发)、`classes`(查询教师班级)、`files`(附件)
- 被依赖:无
**已知问题**
- ✅ 通过对方 data-access 调用跨模块数据,无直查跨模块表
- ✅ data-access 按职责拆分为 4 个文件data-access/data-access-versions/data-access-templates/data-access-knowledge
- ✅ actions 按职责拆分为 4 个文件actions/actions-publish/actions-ai/actions-kp
- ✅ 编辑器架构升级NodeEditorReact Flow 画布)+ NodeEditPanel侧边内容编辑面板+ LessonNode自定义节点组件支持节点拖拽、连线、画布缩放
- ⚠️ `block-renderer.tsx` 标记为 @deprecated(已被 NodeEditor 替代,保留用于向后兼容)
> 架构变更2026-06-22本次审计修复
> - **P0-1 跨模块直查修复**`publish-service.ts` 不再直接 `db.insert(examQuestions)` 和本地实现 `getStudentIdsByClassIds`,改为调用 `exams/data-access.addExamQuestions` 和 `classes/data-access.getStudentIdsByClassIds`,恢复三层架构约束
> - **P0-2 i18n 接入**:新增 `shared/i18n/messages/zh-CN/lesson-preparation.json` 和 `shared/i18n/messages/en/lesson-preparation.json`,注册 `lessonPreparation` 命名空间到 `src/i18n/request.ts`17 个组件改造为 `useTranslations`/`getTranslations`
> - **P0-3 DataScope 过滤修复**`data-access.ts` 的 `buildScopeCondition` 按 scope 类型精确过滤——`class_taught` 增加 `subjectId IN teacher.subjects AND gradeId IN teacher.grades``grade_managed` 限制 `gradeId IN managedGrades``class_members`/`children` 仅允许查看 `published` 课案
> - **P1-1 类型安全修复**`as never` 断言全部替换为类型守卫函数(`isQuestionType`/`isV1Document`/`isV2Document`+ `validTypes` 数组;`block-renderer.tsx` 使用 `as ExerciseBlockData`/`as TextStudyBlockData` 精确断言;`constants.ts` 的 `BLOCK_TYPE_LABELS`/`LESSON_PLAN_STATUS_LABELS` 改为 i18n 键(`BLOCK_TYPE_KEYS`/`LESSON_PLAN_STATUS_KEYS`
> - **P1 纯函数抽取**:新增 `lib/document-migration.ts`migrateV1ToV2/normalizeDocument/buildInitialContent使用类型守卫替代 as 断言)、`lib/node-summary.ts`getNodeSummary + NODE_COLORS + getNodeColor接受翻译函数注入、`lib/rf-mappers.ts`toRfNodes/toRfEdges/fromRfEdgesdata-access.ts 改为从 lib/ 导入并 re-export 保持向后兼容
> - **P1-2/P1-3 错误边界 + 骨架屏**:新增 `components/lesson-plan-error-boundary.tsx`LessonPlanErrorBoundary 类组件)和 `components/lesson-plan-skeleton.tsx`VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton
> - **P1-4 阻塞式 UI 修复**`alert()` 全部替换为 `sonner` toast`confirm()` 全部替换为 `AlertDialog`shadcn`window.location.reload` 替换为 `router.refresh()`;涉及 lesson-plan-card/version-history-drawer/inline-question-editor/text-study-block/exercise-block
> - **P1-5/P1-7 多实例 + 角色配置驱动**:新增 `providers/lesson-plan-provider.tsx`LessonPlanProvider + Context + 4 个角色配置 TEACHER/ADMIN/STUDENT/PARENT + LessonPlanDataService 接口 + LessonPlanTracker 埋点接口 + useLessonPlanContextSafe/useRoleConfig 等 hooks和 `services/default-data-service.ts`(包装 Server Actions 为 DataService 实现lesson-plan-list/lesson-plan-card 通过 Context 注入数据服务useRoleConfig 控制按钮可见性
> - **P1-8 Block 注册表**:新增 `config/block-registry.tsx`BLOCK_REGISTRY 配置表 + getBlockComponent/isRichTextBlock`node-edit-panel.tsx` 重构为配置驱动渲染,移除 if/else 链
> - **P1-4 window.location.reload 修复**`exercise-block.tsx` 改用 `router.refresh()` 精确刷新缓存
> - **P2-1 a11y 修复**5 个组件question-bank-picker/publish-homework-dialog/knowledge-point-picker/exercise-block/text-study-block添加 `role="dialog"`/`aria-modal`/`aria-label`inline-question-editor 添加 `role="dialog"`/`aria-modal`/`aria-label`
> - **P2-4 监控埋点预留**`providers/lesson-plan-provider.tsx` 定义 `LessonPlanTracker` 接口 + `noopTracker` 默认空实现,生产环境可替换为真实埋点
> 架构变更2026-06-23本次审计修复
> - **Zod schema 补全**`schema.ts` 新增 `publishLessonPlanHomeworkSchema`planId/blockId 必填classIds 至少 1 个availableAt/dueAt 日期格式校验),导出类型 `PublishLessonPlanHomeworkInput`3 个 action 文件actions/actions-publish/actions-ai补全 Zod 校验
> - **data-access-versions.ts 事务修复**`revertToVersion` 包裹 `db.transaction` 确保原子性(回滚版本时同步更新 lessonPlans.content 与版本记录)
> - **统一错误处理**:所有 Server Action catch 块改用 `handleActionError``JSON.parse` 改用 `safeJsonParse`
> - **block-renderer 拖拽 BUG 修复**:修复拖拽时节点位置计算错误
**文件清单**
| 文件 | 职责 |
|------|------|
| `types.ts` | 类型定义(含 v1/v2/v3 文档类型、TextbookContentNode、LessonPlanNode、NodeAnchor、AnchorEdge、FlowEdge、11 种 BlockData 接口) |
| `constants.ts` | 常量定义 |
| `schema.ts` | Zod 验证 |
| `lib/document-migration.ts` | **纯函数**v1→v2migrateV1ToV2/ v2→v3migrateV2ToV3/ 规范化normalizeDocument兼容 v1/v2/v3/ 初始内容buildInitialContent/ 默认骨架buildDefaultSkeleton10 节点 + 正文节点)/ defaultDataForType / 工具函数isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges |
| `lib/anchor-injector.ts` | **纯函数**锚点注入算法markdownToPlainText/injectPlaceholders/parseAnchoredText/toCircledNumber/getNextPointIndex/relocateAnchors/getAnchorColor |
| `lib/node-summary.ts` | **纯函数**getNodeSummary支持 11 种节点类型)+ getTextbookContentSummary + NODE_COLORS + getNodeColor |
| `lib/rf-mappers.ts` | **纯函数**toRfNodes支持 textbook_content 节点 + 锚点回调)/ toRfEdges区分 anchor/flow 边透明度)/ fromRfEdges |
| `config/block-registry.tsx` | **配置驱动**BLOCK_REGISTRY 注册表 + BlockRendererswitch 渲染 11 种定制节点 + textbook_content |
| `providers/lesson-plan-provider.tsx` | **Provider + ContextP1-5/P1-7/P2-4/V2-6**LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置TEACHER/ADMIN/STUDENT/PARENT、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTrackerhooksuseLessonPlanContextSafe返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafeV2-6 新增,返回 noopTracker 不抛错) |
| `services/default-data-service.ts` | **默认数据服务实现**createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现,测试可替换为 mock |
| `data-access.ts` | 课案 CRUD + 模板查询migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容buildScopeCondition 按 scope 类型精确过滤 P0-3V2-1抛出 `LessonPlanDataError` 错误码V2-3mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫) |
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理V2-3mapRowToVersion 显式映射) |
| `data-access-templates.ts` | 个人模板 CRUDV2-3mapRowToTemplate 显式映射 + 类型守卫) |
| `data-access-knowledge.ts` | 按知识点/题目反查课案V2-3显式字段映射替代 `as unknown as` |
| `actions.ts` | 课案 CRUD/版本/模板 Server ActionsV2-1getTranslations i18n + 错误码捕获V2-2createLessonPlanAction 传入 translateTitle 翻译 SYSTEM_TEMPLATES |
| `actions-publish.ts` | 发布作业 Server ActionV2-1getTranslations i18n + PUBLISH_ERROR_KEY_MAP 错误码映射) |
| `actions-ai.ts` | AI 知识点建议 Server ActionV2-1i18n + 错误码) |
| `actions-kp.ts` | 知识点选项 Server ActionV2-1i18n + 错误码) |
| `publish-service.ts` | 发布作业服务(编排 homework/exams/classes通过对方 data-access 调用无直查跨模块表V2-1抛出 `PublishServiceError` 错误码V2-3显式字段映射替代 `as unknown as` |
| `ai-suggest.ts` | AI 知识点建议服务 |
| `seed-templates.ts` | 模板种子数据 |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook基于 zustand支持 nodes/edges/anchors 操作addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode + 锚点操作 addAnchor/removeAnchor/updateAnchor + 正文节点操作 updateTextbookContent/getTextbookContentNode实时拖动 |
| `components/lesson-plan-list.tsx` | 课案列表i18n 已接入) |
| `components/lesson-plan-card.tsx` | 课案卡片i18n 已接入V2-6duplicate/archive 调用 tracker.track |
| `components/lesson-plan-filters.tsx` | 课案筛选器i18n 已接入V2-53 个表单元素 label htmlFor 关联) |
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPaneli18n 已接入V2-6handleManualSave 调用 tracker.trackV3顶部工具栏显示教材/章节标题指示器) |
| `components/node-editor.tsx` | **节点图画布**React Flow使用 lib/rf-mappers + lib/node-summary 纯函数i18n 已接入V2-4MiniMap 复用 getNodeColorV2-5role=application + 键盘导航配置V3注册 textbook_content 节点类型 + 锚点回调 + 实时拖动) |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**(配置驱动渲染 Block通过 BlockRenderer + LessonPlanErrorBoundary 包裹i18n 已接入V3处理 textbook_content 节点,教学节点类型收窄) |
| `components/nodes/lesson-node.tsx` | **自定义教学节点组件**(使用 lib/node-summary 的 getNodeSummary/getNodeColori18n 已接入) |
| `components/nodes/textbook-content-node.tsx` | **正文节点组件**V3 新增ReactMarkdown 渲染正文 + 锚点注入 + 文本选择range 锚定)+ 点击位置point 锚定)+ 缩放控制 + 锚点浮动菜单 |
| `components/lesson-plan-error-boundary.tsx` | **错误边界**LessonPlanErrorBoundary 类组件,支持 fallback 和 onError 回调 |
| `components/lesson-plan-skeleton.tsx` | **骨架屏**VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton |
| `components/block-renderer.tsx` | ⚠️ @deprecated Block 渲染器(已被 NodeEditor 替代,保留向后兼容) |
| `components/template-picker.tsx` | 模板选择器i18n 已接入V2-6create 调用 tracker.track |
| `components/version-history-drawer.tsx` | 版本历史抽屉i18n 已接入V2-6revert 调用 tracker.track |
| `components/knowledge-point-picker.tsx` | 知识点选择器i18n 已接入) |
| `components/question-bank-picker.tsx` | 题库选择器i18n 已接入) |
| `components/inline-question-editor.tsx` | 内联题目编辑器i18n 已接入V2-5type/difficulty select label htmlFor 关联) |
| `components/publish-homework-dialog.tsx` | 发布作业对话框i18n 已接入V2-6publish 调用 tracker.track |
| `components/blocks/rich-text-block.tsx` | 富文本 Block被 NodeEditPanel 复用i18n 已接入) |
| `components/blocks/text-study-block.tsx` | 课文研读 Block被 NodeEditPanel 复用i18n 已接入) |
| `components/blocks/exercise-block.tsx` | 练习 Block被 NodeEditPanel 复用,使用 router.refresh 替代 window.location.reloadi18n 已接入V2-5purpose select label 关联 + 题目列表 ul/li 语义化) |
| `components/blocks/reflection-block.tsx` | 反思 Block被 NodeEditPanel 复用i18n 已接入) |
---
## 2.28 error-book错题本模块
**职责**:自动采集考试/作业错题,基于 SM-2 间隔重复算法科学复习,提供知识点薄弱度分析与多角色视图(学生/教师/家长/管理员)。
**导出函数**
- Actions`getErrorBookItemsAction` / `getErrorBookItemDetailAction` / `getErrorBookStatsAction` / `createErrorBookItemAction` / `updateErrorBookNoteAction` / `reviewErrorBookItemAction` / `deleteErrorBookItemAction` / `archiveErrorBookItemAction` / `collectFromSubmissionAction`
- Data-access`getErrorBookItems` / `getErrorBookItemById` / `getErrorBookStats` / `createErrorBookItem` / `updateErrorBookNote` / `recordReview` / `deleteErrorBookItem` / `archiveErrorBookItem` / `collectFromExamSubmission` / `collectFromHomeworkSubmission` / `getStudentErrorBookSummaries` / `getTopWrongQuestionsByStudentIds` / `getKnowledgePointWeakness` / `getSubjectErrorDistribution` / `getStudentNameMap` / `getStudentIdsByClassIdList`
- Schema`CreateErrorBookItemSchema` / `UpdateErrorBookNoteSchema` / `ReviewErrorBookItemSchema` / `CollectFromSubmissionSchema`
- Types`ErrorBookItem` / `ErrorBookItemDetail` / `ErrorBookReviewRecord` / `ErrorBookStats` / `StudentErrorBookSummary` / `KnowledgePointWeakness` / `SubjectErrorDistribution`
- Components`ErrorBookStatsCards` / `ErrorBookFilters` / `ErrorBookItemCard` / `ReviewButtons` / `ErrorBookDetailDialog` / `ErrorBookList` / `AddErrorBookDialog` / `ClassErrorOverview` / `TopWrongQuestions`
**权限点**
- `ERROR_BOOK_READ``error_book:read`查看错题本student 自己、parent 子女)
- `ERROR_BOOK_MANAGE``error_book:manage`):管理错题(添加/复习/删除/归档student
- `ERROR_BOOK_ANALYTICS_READ``error_book:analytics_read`查看错题分析teacher/admin/grade_head/teaching_head
**依赖关系**
- 依赖:`shared/*`db、auth-guard、types、utils`modules/classes`getStudentIdsByClassIds`modules/questions`getQuestionsAction
- 被依赖:`app/(dashboard)/student/error-book``app/(dashboard)/teacher/error-book``app/(dashboard)/parent/error-book``app/(dashboard)/admin/error-book`
**DataScope 行级权限**
| 角色 | DataScope | 说明 |
|------|-----------|------|
| student | `owned` | 仅查看/管理自己的错题 |
| parent | `children` | 查看子女的错题统计 |
| teacher | `class_taught` | 查看所教班级学生的错题分析 |
| admin | `all` | 查看全校错题分析 |
| grade_head | `grade_managed` | 查看所管年级学生的错题分析 |
| teaching_head | `grade_managed` | 查看所管年级学生的错题分析 |
**核心算法SM-2 间隔重复(简化版)**
- 独立纯函数模块:[sm2-algorithm.ts](file:///e:/Desktop/CICD/src/modules/error-book/sm2-algorithm.ts)(可替换为其他算法如 FSRS
- 4 级评级:`again`(重来)/ `hard`(困难)/ `good`(良好)/ `easy`(简单)
- 初始间隔1/2/4/7 天
- 间隔增长:`again` 重置为 1 天;`hard` ×1.2`good` ×1.5`easy` ×2
- 掌握度0-5 级,`again` -1`hard` ±0`good` +1`easy` +2
- 已掌握判定:掌握度 ≥5 或连续答对 ≥3 次
- 复习时间:次日早上 9 点
- 单元测试:[sm2-algorithm.test.ts](file:///e:/Desktop/CICD/src/modules/error-book/sm2-algorithm.test.ts)39 个测试用例,全部通过)
- 导出函数:`calculateNewInterval` / `calculateNewMastery` / `deriveStatus` / `calculateNextReviewAt` / `calculateNewCorrectStreak` / `calculateSm2Result`
**自动采集机制**
- `collectFromExamSubmission`:从考试提交记录中筛选得分 < 满分的题目,去重后批量插入
- `collectFromHomeworkSubmission`:从作业提交记录中筛选错题,去重后批量插入
- 自动关联知识点(通过 `questionsToKnowledgePoints` 表)
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | ~180 | 9 个 Server Actions全部使用 `requirePermission()` + `ActionState<T>` |
| `data-access.ts` | ~960 | 16 个数据访问函数 + 自动采集逻辑SM-2 算法已提取到独立模块) |
| `sm2-algorithm.ts` | ~180 | SM-2 间隔重复算法(独立纯函数模块,可替换,支持时间注入测试) |
| `sm2-algorithm.test.ts` | ~280 | SM-2 算法单元测试39 个测试用例,覆盖所有函数和边界条件) |
| `schema.ts` | ~60 | 4 个 Zod 验证 schema |
| `types.ts` | ~120 | 6 个类型定义 + 状态映射常量 + 错误标签常量 |
| `components/error-book-stats-cards.tsx` | ~80 | 5 个统计卡片(总数/待学习/学习中/已掌握/待复习) |
| `components/error-book-filters.tsx` | ~100 | 筛选栏(搜索/状态/来源/待复习),使用 nuqs |
| `components/error-book-item-card.tsx` | ~150 | 错题卡片(预览/标签/笔记/掌握度/操作) |
| `components/review-buttons.tsx` | ~80 | 4 按钮复习面板again/hard/good/easy |
| `components/error-book-detail-dialog.tsx` | ~250 | 详情对话框(题目/答案/复习/笔记/历史) |
| `components/error-book-list.tsx` | ~60 | 网格列表 |
| `components/add-error-book-dialog.tsx` | ~180 | 手动添加对话框(题库选择 + 标签) |
| `components/class-error-overview.tsx` | ~200 | 班级错题概览(教师/管理员视图) |
| `components/top-wrong-questions.tsx` | ~80 | 高频错题列表Top 10 |
**路由清单**
| 路由 | 文件 | 说明 |
|------|------|------|
| `/student/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 学生错题本(统计/筛选/列表/手动添加/详情复习) |
| `/teacher/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 教师错题分析(班级概览/薄弱知识点/学科分布/高频错题) |
| `/parent/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 家长错题本(子女错题统计/薄弱知识点/高频错题) |
| `/admin/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 管理员错题分析(全校错题统计/薄弱知识点/学科分布/高频错题) |
**数据库表**
| 表 | 说明 |
|------|------|
| `errorBookItems` | 错题条目主表18 列/4 索引/2 外键studentId、questionId、sourceType、sourceId、studentAnswer、correctAnswer、subjectId、knowledgePointIds、status、masteryLevel、nextReviewAt、reviewInterval、reviewCount、correctStreak、note、errorTags、createdAt、updatedAt |
| `errorBookReviews` | 复习记录表8 列/2 索引/2 外键itemId、studentId、result、reviewedAt、newInterval、newMasteryLevel |
**i18n**
- `src/shared/i18n/messages/zh-CN/error-book.json`
- `src/shared/i18n/messages/en/error-book.json`
**导航配置**
- 6 个角色admin/teacher/student/parent/grade_head/teaching_head均添加「错题分析」导航项
- 图标:`BookX`lucide-react
---
## 2.29 aiAI 模块)— ✅ 新增 / V2 增强 / V3 安全加固+竞品对标
**职责**:统一 AI 能力封装,为备课、错题集、试卷、改题等业务模块提供 AI 服务。V2 增加流式响应、Markdown 渲染、全局助手、内容安全过滤、家长学情摘要、管理员使用统计、学生学习路径推荐。V3 安全加固(原子配额/苏格拉底校验/重试机制)+ 竞品对标(知识图谱集成/苏格拉底式辅导强化)。
**架构定位**
- 位于 `modules/` 层,通过 `shared/lib/ai` 调用底层 AI SDK
- 通过 `AiClientProvider`React Context向客户端组件注入 Server Action 引用
- 业务模块不直接 import `ai/actions`,仅通过 Context 消费
- V2在 dashboard layout 全局注入 `AiClientProvider`,所有页面均可使用 AI 助手
**核心导出**
| 类型 | 名称 | 文件 | 说明 |
|------|------|------|------|
| **Server Actions** | `aiChatAction` | `modules/ai/actions.ts` | AI 对话权限AI_CHAT |
| **Server Actions** | `suggestSimilarQuestionsAction` | `modules/ai/actions.ts` | 相似题推荐权限AI_CHAT + ERROR_BOOK_READ |
| **Server Actions** | `suggestGradingAction` | `modules/ai/actions.ts` | AI 辅助批改权限AI_CHAT + HOMEWORK_GRADE |
| **Server Actions** | `generateLessonContentAction` | `modules/ai/actions.ts` | 备课内容生成权限AI_CHAT + LESSON_PLAN_READ |
| **Server Actions** | `generateQuestionVariantAction` | `modules/ai/actions.ts` | 题目变体生成权限AI_CHAT + EXAM_AI_GENERATE |
| **Server Actions** | `analyzeWeaknessAction` | `modules/ai/actions.ts` | 薄弱点分析权限AI_CHAT + ERROR_BOOK_READ |
| **Server Actions** | `generateChildSummaryAction` | `modules/ai/actions.ts` | 家长学情摘要权限AI_CHAT— V2 新增 |
| **Server Actions** | `recommendStudyPathAction` | `modules/ai/actions.ts` | 学习路径推荐权限AI_CHAT— V2 新增 |
| **Server Actions** | `getAiUsageStatsAction` | `modules/ai/actions.ts` | AI 使用统计权限AI_CONFIGURE— V2 新增 |
| **SSE Route** | `POST /api/ai/chat/stream` | `app/api/ai/chat/stream/route.ts` | 流式 AI 对话SSE— V2 新增 |
| **SSE Route** | `GET /api/notifications/stream` | `app/api/notifications/stream/route.ts` | 通知实时推送SSE15s 心跳 + 5min 超时)— V2-P3 新增 |
| **Service** | `AiService` | `modules/ai/types.ts` | 服务端 AI 服务接口(含 8 个方法) |
| **Service** | `AiClientService` | `modules/ai/types.ts` | 客户端 AI 服务接口Server Action 引用集合) |
| **Provider** | `AiClientProvider` | `modules/ai/context/ai-client-provider.tsx` | React Context Provider注入 AiClientService |
| **Hook** | `useAiClient` | `modules/ai/context/ai-client-provider.tsx` | 消费 AiClientService必须在 Provider 内使用) |
| **Hook** | `useAiClientOptional` | `modules/ai/context/ai-client-provider.tsx` | 可选消费 AiClientService未注入返回 null |
| **Hook** | `useAiChatStream` | `modules/ai/hooks/use-ai-chat-stream.ts` | 流式 AI 对话 HookSSE + AbortController— V2 新增 |
| **Hook** | `useAiChat` | `modules/ai/hooks/use-ai-chat.ts` | 非流式 AI 对话 Hook |
| **Hook** | `useAiSuggestion` | `modules/ai/hooks/use-ai-suggestion.ts` | AI 建议 Hook |
| **Component** | `AiAssistantWidget` | `modules/ai/components/ai-assistant-widget.tsx` | 全局 AI 助手悬浮按钮(上下文感知)— V2 新增 |
| **Component** | `AiChatPanel` | `modules/ai/components/ai-chat-panel.tsx` | AI 对话面板(流式 + Markdown + 复制 + 停止 + 建议)— V2 增强 |
| **Component** | `AiMarkdownRenderer` | `modules/ai/components/ai-markdown-renderer.tsx` | Markdown 渲染器GFM + 复制按钮)— V2 新增 |
| **Component** | `AiGradingAssist` | `modules/ai/components/ai-grading-assist.tsx` | AI 批改辅助(主观题预评分 + 反馈建议) |
| **Component** | `AiErrorBookAnalysis` | `modules/ai/components/ai-error-book-analysis.tsx` | 错题本 AI 分析(相似题 + 薄弱点) |
| **Component** | `AiLessonContentGenerator` | `modules/ai/components/ai-lesson-content-generator.tsx` | 备课内容生成器(活动/评估/讨论题/素材) |
| **Component** | `AiQuestionVariantGenerator` | `modules/ai/components/ai-question-variant-generator.tsx` | 题目变体生成器(同知识点/不同难度/不同题型) |
| **Component** | `AiChildSummary` | `modules/ai/components/ai-child-summary.tsx` | 家长 AI 学情摘要 — V2 新增 |
| **Component** | `AiUsageDashboard` | `modules/ai/components/ai-usage-dashboard.tsx` | 管理员 AI 使用统计仪表盘 — V2 新增 |
| **Component** | `AiStudyPath` | `modules/ai/components/ai-study-path.tsx` | 学生学习路径推荐 — V2 新增 |
| **Component** | `AiErrorBoundary` | `modules/ai/components/ai-error-boundary.tsx` | AI 功能错误边界 |
| **Component** | `AiSuggestionSkeleton` | `modules/ai/components/ai-skeleton.tsx` | AI 建议加载骨架屏 |
| **Component** | `AiProviderSelector` | `modules/ai/components/ai-provider-selector.tsx` | AI 服务商选择器 |
| **Safety** | `filterUserInput` | `modules/ai/services/content-safety.ts` | 输入安全过滤 — V2 新增 |
| **Safety** | `filterAiOutput` | `modules/ai/services/content-safety.ts` | 输出安全过滤 — V2 新增 |
| **Safety** | `checkDailyLimit` | `modules/ai/services/content-safety.ts` | 每日交互限制 — V2 新增 |
| **Safety** | `tryConsumeDailyQuota` | `modules/ai/services/content-safety.ts` | 原子化每日限额(防 TOCTOU 竞态)— V3 新增 |
| **Safety** | `refundDailyQuota` | `modules/ai/services/content-safety.ts` | 配额回退(过滤/失败时不扣配额)— V3 新增 |
| **Safety** | `validateSocraticOutput` | `modules/ai/services/content-safety.ts` | 苏格拉底式辅导输出校验(问号结尾/连续陈述句限制)— V3 新增 |
| **Data Access** | `recordAiEvent` | `modules/ai/data-access.ts` | AI 事件记录(内存存储,生产环境替换为 DB— V3 新增 |
| **Data Access** | `getAiUsageStats` | `modules/ai/data-access.ts` | AI 使用统计聚合(真实聚合,替代硬编码零)— V3 新增 |
| **Prompt** | `SOCRATIC_TUTOR_SYSTEM_PROMPT` | `modules/ai/services/prompt-templates.ts` | 苏格拉底式辅导专用提示词3 级提示升级/禁止直接给答案)— V3 新增 |
| **Util** | `consumeSseStream` / `getStreamErrorKey` / `removeTrailingEmptyAssistant` | `modules/ai/hooks/stream-utils.ts` | SSE 流解析工具(从 hook 抽取)— V3 新增 |
**集成点**
| 业务模块 | 集成组件 | 页面 | 说明 |
|---------|---------|------|------|
| homework | `AiGradingAssist` | `teacher/homework/submissions/[submissionId]` | 主观题辅助评分 |
| error-book | `AiErrorBookAnalysis` | `student/error-book` | 相似题推荐 + 薄弱点分析 |
| lesson-preparation | `AiLessonContentGenerator` | `teacher/lesson-plans/[planId]/edit` | 备课内容生成 |
| exams | `AiQuestionVariantGenerator` | `teacher/exams/[id]/build` | 题目变体生成 |
| **全局** | `AiAssistantWidget` | `app/(dashboard)/layout.tsx` | 全局 AI 助手悬浮按钮(所有页面)— V2 新增 |
| **parent** | `AiChildSummary` | `parent/*` | 家长学情 AI 摘要 — V2 新增 |
| **admin** | `AiUsageDashboard` | `admin/*` | AI 使用统计仪表盘 — V2 新增 |
| **student** | `AiStudyPath` | `student/*` | 学习路径推荐 — V2 新增 |
**依赖关系**
- `modules/ai``shared/lib/ai`AI SDK 封装,含流式 `createAiChatCompletionStream`V3 增加 `withRetry` 重试机制)
- `modules/ai``shared/lib/auth-guard`(权限校验)
- `modules/ai``shared/lib/track-event`(使用量埋点)
- `modules/ai``shared/types/permissions`(权限常量)
- `modules/ai``shared/types/action-state`(返回值类型)
- `modules/ai``modules/textbooks/data-access-graph`(知识图谱+掌握度查询)— V3 新增
- `app/(dashboard)/layout``modules/ai`(全局 Provider + Widget— V2 新增
- `app/api/ai/chat/stream``modules/ai/services/content-safety`SSE 端点安全过滤)— V3 加固
- 业务模块 → `modules/ai/context/ai-client-provider`(通过 Context 注入)
- 业务模块 → `modules/ai/components/*`(组合 AI 组件)
**安全机制V2 新增 / V3 加固)**
- 输入过滤:`filterUserInput` 拦截暴力/自残/色情/PII
- 输出过滤:`filterAiOutput` 扫描 AI 回复
- 每日限制:学生 50 次/天,教师 200 次/天,家长 30 次/天
- 学生 Socratic 模式system prompt 强制不直接给答案
- SSE 端点权限校验:`requirePermission(AI_CHAT)`
- V3 原子配额:`tryConsumeDailyQuota` 解决 TOCTOU 竞态,`refundDailyQuota` 过滤/失败时回退
- V3 苏格拉底校验:`validateSocraticOutput` 检查问号结尾+连续陈述句限制,`SOCRATIC_TUTOR_SYSTEM_PROMPT` 3 级提示升级
- V3 SSE 端点加固Zod 校验 + rate limit + 服务端强制 systemPrompt忽略客户端
- V3 重试机制:`withRetry` 指数退避429/5xx2 次重试1s/2s/4s 延迟)
- V3 知识图谱集成:`recommendStudyPathAction` 自动从 textbooks 模块获取图谱+掌握度注入 prompt
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `modules/ai/types.ts` | ~290 | 类型定义8 个业务场景类型 + AiService/AiClientServiceV3 新增 knowledgeGraph/textbookId |
| `modules/ai/schema.ts` | ~230 | Zod 验证 schema8 个输入 + 8 个输出V3 新增 knowledgeGraph/textbookId |
| `modules/ai/actions.ts` | ~400 | 9 个 Server Actions含权限校验V3 新增知识图谱获取) |
| `modules/ai/data-access.ts` | ~138 | AI 事件存储+统计聚合 — V3 新增 |
| `modules/ai/services/ai-service.ts` | ~430 | DefaultAiService 实现8 个方法V3 新增知识图谱上下文注入) |
| `modules/ai/services/prompt-templates.ts` | ~250 | 9 个系统提示词模板V3 新增 SOCRATIC_TUTOR_SYSTEM_PROMPT |
| `modules/ai/services/usage-tracker.ts` | ~90 | AI 使用量埋点V3 新增 child_summary/study_path 能力) |
| `modules/ai/services/content-safety.ts` | ~290 | 内容安全过滤(输入/输出/每日限制/原子配额/苏格拉底校验)— V2 新增 / V3 加固 |
| `modules/ai/context/ai-client-provider.tsx` | ~62 | React Context Provider + Hooks |
| `modules/ai/components/ai-assistant-widget.tsx` | ~170 | 全局 AI 助手悬浮按钮 — V2 新增 |
| `modules/ai/components/ai-chat-panel.tsx` | ~305 | AI 对话面板(流式 + Markdown— V2 增强 |
| `modules/ai/components/ai-markdown-renderer.tsx` | ~100 | Markdown 渲染器 — V2 新增 |
| `modules/ai/components/ai-child-summary.tsx` | ~170 | 家长学情摘要 — V2 新增 |
| `modules/ai/components/ai-usage-dashboard.tsx` | ~180 | 管理员使用统计 — V2 新增 |
| `modules/ai/components/ai-study-path.tsx` | ~170 | 学生学习路径 — V2 新增 |
| `modules/ai/components/ai-grading-assist.tsx` | 173 | AI 批改辅助组件 |
| `modules/ai/components/ai-error-book-analysis.tsx` | 246 | 错题本 AI 分析组件 |
| `modules/ai/components/ai-lesson-content-generator.tsx` | ~187 | 备课内容生成器 |
| `modules/ai/components/ai-question-variant-generator.tsx` | ~208 | 题目变体生成器 |
| `modules/ai/components/ai-error-boundary.tsx` | ~88 | AI 错误边界 |
| `modules/ai/components/ai-skeleton.tsx` | ~47 | AI 骨架屏 |
| `modules/ai/components/ai-provider-selector.tsx` | ~129 | 服务商选择器 |
| `modules/ai/hooks/use-ai-chat-stream.ts` | ~107 | 流式 AI 对话 HookV3 拆分至 ≤80 行函数体)— V2 新增 / V3 重构 |
| `modules/ai/hooks/stream-utils.ts` | ~120 | SSE 流解析工具consumeSseStream/getStreamErrorKey 等)— V3 新增 |
| `modules/ai/hooks/use-ai-chat.ts` | ~57 | 非流式 AI 对话 Hook |
| `modules/ai/hooks/use-ai-suggestion.ts` | ~72 | AI 建议 Hook |
| `app/api/ai/chat/stream/route.ts` | ~160 | SSE 流式端点 — V2 新增 |
| `app/api/notifications/stream/route.ts` | ~120 | 通知实时推送 SSE 端点 — V2-P3 新增 |
**i18n**
- 翻译文件:`shared/i18n/messages/{locale}/ai.json`
- 命名空间:`ai`
- V2 新增键:`chat.streaming/stopGeneration/copy/clearConfirm/suggestedPrompts``grading.description/batch*``lessonPrep.description/additionalContext/insertContent``exam.variantType.*/targetDifficulty/addVariant``parent.*``admin.*``studyPath.*``widget.*``safety.*`
---
# 第三部分:已知架构问题和技术债
## 3.1 P0 严重问题(必须立即修复)
### P0-1文件超 1000 行硬上限3 个文件)
| 文件 | 行数 | 问题 | 拆分建议 |
|------|------|------|---------|
| `classes/data-access.ts` | ~~2104~~ → 548 | ~~混入 homework/scheduling/grades 逻辑~~ ✅ 已拆分 | 已拆为 5 个文件data-access.ts(548行) + data-access-stats.ts(531行) + data-access-schedule.ts(194行) + data-access-students.ts(244行) + data-access-admin.ts(406行),通过 re-export 保持向后兼容 |
| `homework/data-access.ts` | ~~1038~~ → 598 | ~~混入排名计算业务逻辑~~ ✅ 已拆分 | 已拆为 data-access.ts(598行) + stats-service.ts(425行),统计函数迁移至 stats-service.ts |
| `shared/db/schema.ts` | 1111 | 54 张表混合 | 按业务域拆分为 schema/auth.ts + schema/academic.ts + schema/exam.ts + ...,通过 index.ts 聚合 |
### P0-2shared/lib ↔ auth 循环依赖 ✅ 已修复
```
shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
```
**影响**shared 层无法独立测试/复用;架构上基础设施不应反向依赖应用层。
**修复方案**(已实施):
- ✅ 创建 `shared/lib/session.ts` 封装 session 获取(`getSession()`server-only
-`session.ts` 内部使用 `dynamic import("@/auth")` 打破模块级静态循环
-`audit-logger.ts` / `change-logger.ts` / `auth-guard.ts` 改为 `import { getSession } from "@/shared/lib/session"`,不再直接依赖 `@/auth`
- ✅ 运行时调用链保持不变,模块加载图无环
### P0-3dashboard 跨模块直接查询 11 张表 ✅ 已修复
`dashboard/data-access.ts``getAdminDashboardData` 原直查 sessions/users/usersToRoles/roles/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions。
**修复方案**(已实施):
- 各模块新增 dashboard stats 函数:
- `users/data-access.ts``getUsersDashboardStats()`userCount/activeSessionsCount/userRoleCounts/recentUsers
- `classes/data-access.ts``getClassesDashboardStats()`classCount
- `textbooks/data-access.ts``getTextbooksDashboardStats()`textbookCount/chapterCount
- `questions/data-access.ts``getQuestionsDashboardStats()`questionCount
- `exams/data-access.ts``getExamsDashboardStats(scope?)`examCount支持 scope 过滤)
- `homework/stats-service.ts``getHomeworkDashboardStats(scope?)`4 个计数,支持 scope 过滤)
- dashboard 改为并行调用:`Promise.all([getUsersDashboardStats(), getClassesDashboardStats(), ...])`
- 返回值结构保持不变,调用方无需修改
### P0-4messaging 绕过 notifications 直接写通知 ✅ 已修复
`messaging/actions.ts` 原直接调用 `createNotification``messageNotifications` 表,导致用户通知偏好失效、多渠道通知无效。`notifications/data-access.ts``in-app-channel.ts` 反向依赖 messaging 模块。
**修复方案**(已实施):
-`messageNotifications` 表的 CRUD 函数(`createNotification` / `getNotifications` / `markNotificationAsRead` / `markAllNotificationsAsRead` / `getUnreadNotificationCount`)从 `messaging/data-access.ts` 迁移到 `notifications/data-access.ts`
-`notificationPreferences` 表的 CRUD 函数(`getNotificationPreferences` / `upsertNotificationPreferences`)从 `messaging/notification-preferences.ts` 迁移到新文件 `notifications/preferences.ts`
-`NotificationType` / `Notification` / `NotificationPreferences` / `UpdateNotificationPreferencesInput` / `CreateNotificationInput` / `GetNotificationsParams` / `PaginatedResult` 类型从 `messaging/types.ts` 迁移到 `notifications/types.ts`
- `notifications/channels/in-app-channel.ts` 改为静态导入本地 `createNotification`(不再动态 import messaging
- `notifications/dispatcher.ts` 改为从 `notifications/preferences.ts` 导入 `getNotificationPreferences`(不再通过 `getUserNotificationPreferences` 包装器反向依赖 messaging
- messaging 模块通过 re-export 保持向后兼容:`messaging/data-access.ts` re-export 通知 CRUD`messaging/notification-preferences.ts` 转为 re-export shim`messaging/types.ts` re-export 通知类型
- 依赖方向变为单向messaging → notificationsmessaging 调用 `sendNotification` dispatcher 发送通知)
### P0-5classSchedule 表三处写入口 ✅ 已修复
- ~~`classes/data-access.ts`createClassScheduleItem 等)~~
- ~~`scheduling/actions.ts`applyAutoScheduleAction 直接 transaction 写入)~~
- ~~`scheduling/data-access.ts`(间接)~~
**影响**:数据完整性高风险。
**修复方案**(已实施):
- 将 classSchedule 写函数(`createClassScheduleItem` / `updateClassScheduleItem` / `deleteClassScheduleItem`)从 `classes/data-access-schedule.ts` 迁移到新文件 `scheduling/data-access-class-schedule.ts`
- classes 模块仅保留 READ 函数(`getStudentSchedule` / `getClassSchedule`),不再有任何 classSchedule 写入口
- scheduling 模块通过 `classes/data-access.verifyTeacherOwnsClass` 跨模块校验教师班级归属(合理依赖)
- `classes/actions.ts` 改为从 `@/modules/scheduling/data-access-class-schedule` 导入写函数
- 类型 `CreateClassScheduleItemInput` / `UpdateClassScheduleItemInput``classes/types.ts` 迁移到 `scheduling/types.ts`
- 所有 classSchedule DB 写入统一由 scheduling 模块管理(`insertClassScheduleItem` / `updateClassScheduleItemById` / `deleteClassScheduleItemById` / `replaceClassSchedule`
### P0-6proctoring 死代码与重复实现
- `exam-mode-config.tsx` 未集成到考试表单(监考功能无法启用)
- ~~事件上报存在 Server Action 与 REST API 双通道重复~~ ✅ 已修复
**修复方案**(已实施):
-`src/app/api/proctoring/event/route.ts` 移至 `deletes/api/proctoring/event/route.ts`,消除事件上报的 REST API 重复通道
- Server Action `recordProctoringEventAction``proctoring/actions.ts`)为唯一规范路径
- `exam-mode-config.tsx` 暂保留原位(集成到考试表单属于功能新增,不在本次修复范围)
### P0-7proxy.ts 路由权限跨角色访问漏洞 ✅ 已修复webapp-testing 发现)
**问题**`proxy.ts``/teacher``/parent` 路由前缀都使用 `EXAM_READ` 权限校验,但 `student``parent` 角色也拥有 `EXAM_READ` 权限,导致:
- 学生可访问 `/teacher/*` 所有页面
- 教师可访问 `/parent/*` 所有页面
**修复方案**(已实施):
- `/teacher` 改为 `Permissions.EXAM_CREATE`(仅 teacher/admin 拥有)
- `/parent` 改为 `Permissions.DASHBOARD_PARENT_READ`(仅 parent 拥有)
- 新增 `DASHBOARD_ROUTE_PERMISSIONS` 细粒度仪表盘权限表,覆盖各角色 dashboard 路由
- 跨角色访问测试验证teacher/student/parent 访问其他角色路由均被重定向回各自 dashboard
### P0-8list-pagination.tsx 客户端/服务端边界错误 ✅ 已修复webapp-testing 发现)
**问题**`shared/components/ui/list-pagination.tsx` 文件顶部声明 `"use client"`,但导出的 `computePagination()``paginate()` 纯工具函数被 4 个服务端组件页面直接调用:
- `teacher/attendance/page.tsx`
- `teacher/homework/assignments/page.tsx`
- `teacher/homework/submissions/page.tsx`
- `teacher/grades/page.tsx`
导致 Next.js 16 / React 19 抛出错误:`Attempted to call computePagination() from the server but computePagination is on the client`,页面渲染时触发 ErrorBoundary。
**修复方案**(已实施):
- 移除 `list-pagination.tsx``"use client"` 指令
- `ListPagination` 组件仅使用 `Link``Button` 和图标,无需客户端交互,可作为服务端组件渲染
- `computePagination``paginate` 恢复为服务端可调用的纯函数
---
## 3.2 P1 较严重问题(短期执行)
### P1-1跨模块直接 DB 查询普遍存在 ✅ 已修复
| 被访问表 | 访问次数 | 应归属模块 | 主要违规者 |
|---------|---------|-----------|-----------|
| `classes` | 8+ | classes | ✅ exams/homework/grades/dashboard 已改为通过 classes data-access |
| `classEnrollments` | 6+ | classes | ✅ homework/grades/attendance/users 已改为通过 classes data-access |
| `users` | 6+ | users | ✅ 多个模块已改为通过 users data-access |
| `subjects` | 6+ | school | ✅ exams/homework/questions/grades 已改为通过 school data-access |
| `exams` | 5+ | exams | ✅ homework/grades/dashboard/classes 已改为通过 exams data-access |
| `homeworkAssignments` | 5+ | homework | ✅ classes反向直查已改为通过 homework/data-access-classes |
**修复方案**(已实施):
- ✅ 各模块 data-access 暴露查询接口:
- `classes/data-access`~~`getClassGradeIdsByClassIds`~~ ✅ 已实现 / `getClassStudentsByClassId` / `getActiveClassStudents` / `getClassExists` / `getClassNameById` / `getClassNamesByIds` / `getActiveStudentIdsByClassId` / `getStudentActiveClassId` / `getClassesByGradeId` / `verifyTeacherOwnsClass` / `getTeacherIdForMutations`
- `exams/data-access`~~`getExamForHomeworkCreation`~~ ✅ 已实现(`getExamIdsByGradeIds` / `getExamSubjectIdMap` / `getExamWithQuestionsForHomework` / `getExamSubmissionWithAnswers` / `getExamForProctoringCrossModule` / `getExamSubmissionForProctoringCrossModule` / `getExamSubmissionsForExam` / `getExamTitleById`
- `school/data-access`~~`getSubjectOptions` / `getGradeOptions`~~ ✅ 已实现(含 `getSubjectNameMapByIds` 批量科目名称映射P1-1 新增供 homework/data-access-classes 调用)
- `users/data-access`~~`getUserNameByIds` / `getStudentInfo`~~ ✅ 已实现(`getUserNamesByIds` / `getUserWithRole` / `getUserBasicInfo` / `getUserIdsByGradeId` / `getCurrentStudentUser`
- `textbooks/data-access`~~`getKnowledgePointOptions`~~ ✅ 已实现
- `questions/data-access`~~`insertQuestionWithRelations`~~ ✅ 已通过 `createQuestionWithRelations` 供 exams 调用 / `getKnowledgePointsForQuestions`
- `homework/data-access-classes`:✅ 新增 7 个函数供 classes 模块跨模块调用
### P1-2actions 层混入数据访问逻辑 ✅ 已修复
**已完成修复**2026-06-17commit 84d66364 个模块的 actions 层 DB 操作全部下沉到 data-access
| 模块 | 问题 Action | 修复内容 |
|------|------------|---------|
| exams | `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` | ✅ 新增 7 个 data-access 函数getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGradesactions.ts 831→691 行data-access.ts 374→471 行 |
| homework | `createHomeworkAssignmentAction`157 行)/ `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction` | ✅ 新建 `data-access-write.ts`285 行10 个写函数actions.ts 387→239 行 |
| questions | `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction` | ✅ 新增 4 个 data-access 函数createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptionsactions.ts 294→149 行data-access.ts 138→260 行 |
| announcements | 所有写操作 Action | ✅ 新增 5 个 data-access 函数insertAnnouncement/updateAnnouncementById/deleteAnnouncementById/publishAnnouncementById/archiveAnnouncementByIdactions.ts 242→197 行data-access.ts 120→171 行 |
**剩余未修复模块**(不在本次 P1-2 范围):
- ✅ users~~`updateUserProfileAction` 直接 db.update~~ 已下沉到 data-accessP1-1 修复)
- ✅ scheduling~~`applyAutoScheduleAction` / `autoScheduleAction` 直接 db.transaction + db.select~~ 已改为调用 `replaceClassSchedule` 统一写入口;`autoScheduleAction` 直查 users 表已改为通过 users data-accessP0-5 / P1-1 修复)
### P1-3auth.ts 混合 5 类职责 ✅ 已完成
`src/auth.ts` 原 293 行混合NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数。
**已完成拆分**auth.ts 现 193 行,仅保留 NextAuth 配置):
- ✅ 密码安全 DB 操作 → `shared/lib/password-security-service.ts`getOrCreatePasswordSecurity / recordFailedLogin / resetFailedLoginserver-only
- ✅ 角色规范化 → `shared/lib/role-utils.ts`normalizeRole / resolvePrimaryRole纯函数
- ✅ bcrypt 哈希规范化 → `shared/lib/bcrypt-utils.ts`normalizeBcryptHash纯函数
- ✅ IP 解析 → `shared/lib/http-utils.ts`resolveClientIpserver-only
**后续可选优化**(未执行,需保持 NextAuth 配置不变原则下评估):
- `authorize` 回调可进一步拆分为 `checkRateLimit` / `checkAccountLockout` / `verifyPassword` / `loadUserRoles`,使 auth.ts 降至 ≤150 行
### P1-4users/import-export.ts 四重职责 ✅ 已完成
`users/import-export.ts` 原 291 行混合:导入解析 + 导出 + 用户创建(含密码哈希)+ 班级注册(跨模块写 classEnrollments
**已完成拆分**import-export.ts 现 157 行,仅保留文件解析/生成):
- ✅ 用户创建(含密码哈希、角色分配)→ `user-service.ts``batchImportUsers`82 行server-only
- ✅ 班级注册 → `class-registration.ts``registerStudentByInvitationCode`21 行server-only
-`batchImportUsers` 不再直写 `classEnrollments`,改为调用 `classes/data-access.enrollStudentByInvitationCode`
-`import-export.ts` 通过 re-export `batchImportUsers` / `UserImportResult` 保持向后兼容(`actions.ts``app/api/export/route.ts` 无需修改)
### P1-5notifications 反向依赖 messaging ✅ 已完成
`notifications/data-access.ts``in-app-channel.ts` 原反向依赖 messaging 模块的偏好和 createNotification。
**已完成修复**(与 P0-4 一并解决):
- ✅ 将 `messageNotifications``notificationPreferences` 表所有权移交 notifications 模块
-`notifications/data-access.ts` 不再 import messaging 模块
-`notifications/channels/in-app-channel.ts` 改为静态导入本地 `createNotification`(不再动态 import messaging
-`notifications/dispatcher.ts` 改为从本地 `preferences.ts` 导入偏好函数
- ✅ messaging 模块通过 re-export 保持向后兼容
### P1-6三个 logger 重复实现 IP/Header 提取 ✅ 已修复
`audit-logger.ts` / `change-logger.ts` / `login-logger.ts` / `auth.ts` 四处重复实现 IP/User-Agent 提取逻辑,且实现略有差异。
**修复方案**(已实施):
-`shared/lib/http-utils.ts` 新增 `getUserAgent()` 函数(与已有 `resolveClientIp()` 配套)
-`audit-logger.ts` / `change-logger.ts` / `login-logger.ts` 改为从 `@/shared/lib/http-utils` 导入 `resolveClientIp``getUserAgent`,删除本地重复实现
-`auth.ts` 已在 P1-3 中改用 `resolveClientIp`
- ✅ 四处实现统一,消除不一致风险(`resolveClientIp``x-forwarded-for` 第一段,更准确)
---
## 3.3 P2 代码质量问题(机会修复)
| 序号 | 问题 | 模块 |
|------|------|------|
| P2-1 | `exams/ai-pipeline.ts` 857 行,混合 4 类职责 | exams |
| ~~P2-2~~ | ~~`exams/actions.ts` 832 行(超 800 建议)~~ ✅ 已修复P1-2 后降至 691 行) | exams |
| ~~P2-3~~ | ~~`shared/lib/ai.ts` 218 行,混合 5 类职责~~ ✅ 已修复P2-2 已拆分为 `ai/` 目录) | shared |
| ~~P2-4~~ | ~~`onboarding-gate.tsx` 业务逻辑泄漏到 shared~~ ✅ 已修复(迁移至 `modules/onboarding/`,改用独立路由 + Server Action + middleware 重定向) | shared/onboarding |
| P2-5 | `global-search.tsx` 业务类型硬编码在 shared | shared |
| ~~P2-6~~ | ~~`proxy.ts` 硬编码权限字符串,未复用 Permissions 常量~~ ✅ 已修复(改用 `Permissions` 常量) | proxy |
| ~~P2-7~~ | ~~`useA11yId` Hook 错放在 lib/ 而非 hooks/~~ ✅ 已修复(文件已不存在;`use-aria-live.ts` 已在 `hooks/` 目录) | shared |
| ~~P2-8~~ | ~~`schema.ts` 分节编号混乱section 12 出现在 14b 之后)~~ ✅ 已修复(重新编号为连续 1-24 | shared/db |
| P2-9 | `audit/actions.ts` Excel 导出逻辑内联 | audit |
| P2-10 | school 模块审计日志不一致(仅 school 实体记录) | school |
| ~~P2-11~~ | ~~`announcements` 死代码 `void wasPublished`~~ ✅ 已修复(代码中已不存在) | announcements |
| ~~P2-12~~ | ~~`announcements` 权限模式不一致requireAuth vs requirePermission~~ ✅ 已修复 | announcements |
| P2-13 | ~~`files` try-catch 吞错误~~ ✅ 已修复(所有 catch 块已添加 console.errorconditions 隐式 any[] 改为 SQL[] | files |
| ~~P2-14~~ | ~~`elective` runLottery 使用 Math.random~~ ✅ 已修复(改为 Fisher-Yates 无偏洗牌) | elective |
| ~~P2-15~~ | ~~`elective` selectCourse FCFS 并发超卖风险~~ ✅ 已修复db.transaction + FOR UPDATE 行锁) | elective |
| P2-16 | `diagnostic` 班级报告 studentId 字段复用 | diagnostic |
| ~~P2-17~~ | ~~`layout` 用权限反推角色~~ ✅ 已修复(`app-sidebar.tsx` 改用 `hasRole()` 判断角色N3 新增多角色切换机制:`SidebarContext.currentRole`/`setCurrentRole` + 侧边栏底部 Select 下拉) | layout |
| ~~P2-18~~ | ~~`scheduling/actions.ts` 末尾 re-export data-access~~ ✅ 已修复(移除 re-export4 个页面改为从 `data-access` 导入) | scheduling |
| P2-19 | `ExamAssembly` / `ExamPreviewQuestionEditor` 10 个 props | exams |
| P2-20 | ~~`homework/data-access.getDemoStudentUser` 使用 `auth()` 而非 auth-guard~~ ✅ 已修复(已迁移至 `users/data-access.getCurrentStudentUser`6 个 student 页面改用 users 模块;`elective` 页面改用 `getAuthContext()`homework 保留 re-export 向后兼容) | homework |
---
## 3.4 解耦优先级路线图
### 立即执行P0
1. ~~拆分 `classes/data-access.ts`2104 行 → 按职责拆 3-4 个文件)~~ ✅ 已完成(拆为 5 个文件data-access.ts 548行 + data-access-stats.ts 531行 + data-access-schedule.ts 194行 + data-access-students.ts 244行 + data-access-admin.ts 406行
2. ~~拆分 `homework/data-access.ts`1038 行 → 分离排名逻辑)~~ ✅ 已完成(拆为 data-access.ts 598行 + stats-service.ts 425行
3. ~~修复 `shared/lib` ↔ `auth` 循环依赖~~ ✅ 已完成(新增 `shared/lib/session.ts` 单一入口3 个 shared/lib 文件改为通过 getSession 获取 sessiondynamic import 打破静态循环)
4. ~~dashboard 改为通过各模块 data-access 获取数据~~ ✅ 已完成P0-3 修复:并行调用各模块 dashboard stats 函数)
5. ~~messaging 写通知改为通过 notifications dispatcher~~ ✅ 已完成P0-4 / P1-5 修复:通知 CRUD 和偏好迁移至 notifications 模块messaging 通过 dispatcher 发送通知)
6. ~~统一 classSchedule 写入口到 scheduling 模块~~ ✅ 已完成P0-5 修复classSchedule 写函数从 classes/data-access-schedule.ts 迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留读函数)
7. ~~集成 proctoring/exam-mode-config 到考试表单~~ 部分完成P0-6 修复:删除重复的 /api/proctoring/event REST 路由Server Action 为唯一规范路径exam-mode-config.tsx 集成属于功能新增,暂保留原位)
### 短期执行P1
8. ~~actions 层移除直接 DB 操作exams/homework/questions/announcements/users/scheduling~~ ✅ 已完成(全部 6 个模块均已修复)
9. ~~拆分 `auth.ts`~~ ✅ 已完成4 个辅助函数组迁移至 shared/libauth.ts 保留 NextAuth 配置)
10. ~~拆分 `users/import-export.ts`~~ ✅ 已完成(拆为 import-export.ts 157行 + user-service.ts 82行 + class-registration.ts 21行班级注册改为调用 classes/data-access
11. ~~消除 notifications → messaging 反向依赖~~ ✅ 已完成P0-4 / P1-5 修复:通知表所有权迁移至 notificationsin-app-channel 改为静态导入)
12. ~~提取 `shared/lib/http-utils.ts` 统一 IP 提取~~ ✅ 已完成(新增 `getUserAgent`,三个 logger 统一复用 `resolveClientIp` / `getUserAgent`
13. ~~各模块暴露跨模块查询接口(见 P1-1~~ ✅ 已完成(所有跨模块直查已改为通过对方 data-access 接口)
### 中期执行P2
14. ~~建立模块间数据访问规范(通过对方 data-access 或导出查询函数)~~ ✅ 已完成P1-1 修复)
15. ~~`schema.ts` 按业务域分节~~ ✅ 已完成P2-8 修复:重新编号为连续 1-24消除 8b/14b/乱序问题)
16. 拆分 `exams/ai-pipeline.ts`
17. ~~拆分 `shared/lib/ai.ts`~~ ✅ 已完成P2-2commit 6588f74拆分为 `ai/` 目录 6 个文件,原 ai.ts 保留为重导出)
18. shared 层业务逻辑下沉到 modules 层
19. 代码质量问题逐项修复(✅ 大部分已修复React.cache 包装、Promise.all 并行化、错误吞没清理、非空断言清理、函数返回类型补齐、重复代码提取、合并 filter 遍历、Set/Map 优化、P2-6 proxy.ts 权限常量复用、P2-11 announcements 死代码清理、P2-17 layout 角色判断、P2-18 scheduling re-export 移除)
---
## 3.5 标杆实践(建议推广)
| 实践 | 模块 | 说明 |
|------|------|------|
| 算法纯函数化 | `scheduling/auto-scheduler.ts` | 无 DB 依赖,可独立测试,应作为算法抽取模板 |
| stats 文件拆分 | `attendance/data-access-stats.ts` | 统计逻辑独立成文件classes 应效仿 |
| data-access 多文件拆分 | `grades/data-access*.ts` | 按职责拆分为 3 个文件CRUD/分析/排名) |
| actions 辅助函数 | `course-plans/actions.ts` | handleError / revalidatePlanPaths 消除重复 |
| actions 编排模式 | `textbooks/actions.ts` | 权限校验 → 调用 data-access → revalidatePath标杆 |
| DataScope 接入 | `attendance/actions.ts` | 6 种数据范围完整支持 |
| 权限统一接入 | school / attendance / course-plans | 全部 Action 使用 requirePermission |
| 跨模块解耦 | `grades` | 通过外键引用 exams/homework不直接访问其表 |
| 渠道抽象 | `notifications/channels/` | 接口 + 工厂 + Mock 实现 |
---
## 3.6 教师端全模块审计修复2026-06-23
> 本次审计修复覆盖教师端 13 个模块组、约 80+ 个文件,聚焦越权访问漏洞、错误处理一致性、类型安全与 React 性能。以下仅记录涉及架构图同步的变更(函数签名/导出/新增文件),其余纯实现修复(如 try/catch 补齐、useMemo/useCallback、key prop、as 断言清理)不在此列出。
### 3.6.1 新增共享文件 `src/shared/lib/action-utils.ts`
统一 Server Action 错误处理与客户端 Action 调用模式,避免内部错误消息暴露给客户端。
| 导出 | 类型 | 签名 | 用途 |
|------|------|------|------|
| `handleActionError` | function | `(e: unknown) => ActionState<never>` | 统一 Server Action catch 块错误处理PermissionDeniedError/BusinessError 返回其 message其他 Error 返回通用消息并 console.error 记录 |
| `safeActionCall` | function | `<T>(action: () => Promise<ActionState<T>>, options?: { onError?, onFinally? }) => Promise<ActionState<T> \| null>` | 客户端调用 Server Action 的 try/catch/finally 包装器,防止 UI 永久卡 loading |
| `safeJsonParse` | function | `<T>(json: string, errorMessage: string) => T` | 安全 JSON.parse失败抛 ValidationError替代裸 JSON.parse |
| `safeParseDate` | function | `(value: string, fieldName: string) => Date` | 校验日期字符串有效性,无效抛 ValidationError |
| `safeParseNumber` | function | `(value: string, fieldName: string) => number` | 校验数字字符串,无效抛 ValidationError |
| `escapeLikePattern` | function | `(input: string) => string` | 转义 SQL LIKE 通配符(% _ \\),防止用户输入干扰模糊查询 |
| `BusinessError` | class | `extends Error` | 已知业务错误基类message 可安全返回客户端(含可选 code |
| `NotFoundError` | class | `extends BusinessError` | 资源不存在错误(自动生成 `${resource} 不存在` 消息code: not_found |
| `ValidationError` | class | `extends BusinessError` | 输入校验错误code: validation_error |
### 3.6.2 grades 模块签名变更
**data-access 层新增 scope 参数**P3 修复:所有查询函数应用 `buildScopeClassFilter` 进行行级 scope 过滤):
| 函数 | 文件 | 旧签名 | 新签名 |
|------|------|--------|--------|
| `getGradeRecords` | `data-access.ts` | `(params: GradeQueryParams) => Promise<GradeRecordListItem[]>` | `(params: GradeQueryParams & { scope: DataScope; currentUserId?: string; limit?: number; offset?: number }) => Promise<PaginatedGradeRecords>` |
| `getClassGradeStats` | `data-access.ts` | `(classId, subjectId?, examId?) => Promise<GradeStats>` | `(classId, subjectId?, examId?, scope?: DataScope, currentUserId?: string) => Promise<GradeStats \| null>` |
| `getStudentGradeSummary` | `data-access.ts` | `(studentId) => Promise<StudentGradeSummary>` | `(studentId, scope?: DataScope) => Promise<StudentGradeSummary \| null>`class_taught scope 校验学生归属) |
| `getClassRanking` | `data-access.ts` | `(classId, subjectId?, examId?) => Promise<ClassRankingItem[]>` | `(classId, subjectId?, examId?, scope?: DataScope, currentUserId?: string) => Promise<ClassRankingItem[]>`(含并列排名处理) |
| `getClassStudentsForEntry` | `data-access.ts` | `(classId) => Promise<{id,name}[]>` | `(classId, scope?: DataScope) => Promise<{id,name,email}[]>`class_taught scope 校验 classId |
| `getRankingTrend` | `data-access-ranking.ts` | `(studentId, subjectId?, semester?) => Promise<RankingTrendResult \| null>` | `(studentId, subjectId?, semester?, scope?: DataScope) => Promise<RankingTrendResult \| null>`class_taught scope 校验) |
**lib 层签名变更**
- `buildScopeClassFilter(scope: DataScope, currentUserId?: string): SQL | null`(新增 `currentUserId` 参数,`class_members` scope 内置 `eq(gradeRecords.studentId, currentUserId)` 过滤P3-26`lib/grade-utils.ts` 迁移至 `lib/scope-filter.ts`
**新增导出**
- `assertClassInScope(scope: DataScope, classId: string): string | null``actions.ts`,校验 classId 是否在 scope 允许范围内,供 actions.ts 与 actions-analytics.ts 复用)
- `PaginatedGradeRecords` 接口(`data-access.ts``{ records: GradeRecordListItem[]; total: number }`,配合 DB 层分页)
### 3.6.3 homework 模块签名变更
**data-access 层新增 scope 参数**P3 修复:教师仅查看自己创建的作业,学生/家长仅查看相关作业):
| 函数 | 文件 | 新签名 |
|------|------|--------|
| `getHomeworkAssignments` | `data-access.ts` | `(params?: { creatorId?, ids?, classId?, scope?: DataScope }) => Promise<HomeworkAssignmentListItem[]>` |
| `getHomeworkAssignmentById` | `data-access.ts` | `(id: string, scope?: DataScope) => Promise<HomeworkAssignmentListItem \| null>` |
| `getHomeworkSubmissions` | `data-access.ts` | `(params?: { assignmentId?, classId?, creatorId?, scope?: DataScope }) => Promise<HomeworkSubmissionListItem[]>` |
| `getHomeworkAssignmentReviewList` | `data-access.ts` | `(params: { creatorId: string; scope?: DataScope }) => Promise<HomeworkReviewListItem[]>` |
### 3.6.4 lesson-preparation 模块变更
**新增 Zod schema**`schema.ts`
- `publishLessonPlanHomeworkSchema`发布作业输入校验planId/blockId 必填classIds 至少 1 个availableAt/dueAt 日期格式校验)
- 导出类型 `PublishLessonPlanHomeworkInput = z.infer<typeof publishLessonPlanHomeworkSchema>`
**data-access-versions.ts 事务变更**
- `revertToVersion` 包裹 `db.transaction` 确保原子性(回滚版本时同步更新 lessonPlans.content 与版本记录)
- `createLessonPlanVersion` 已使用 `db.transaction`(版本号自增 + 插入版本记录原子化)
### 3.6.5 其他修复(不涉及签名变更,仅记录范围)
- 越权访问修复15+ 处页面添加 `requirePermission`data-access 层添加 scope 过滤
- 功能性 BUG 修复question-actions 删除字段名、question-columns 时间显示、textbook-card 删除按钮、block-renderer 拖拽
- 统一错误处理:所有 Server Action catch 块改用 `handleActionError`
- 客户端 Action 调用添加 try/catch/finally20+ 处,使用 `safeActionCall`
- `JSON.parse` 改用 `safeJsonParse`8+ 处)
- Date 解析添加校验15+ 处,使用 `safeParseDate`
- 数据库事务添加:`batchCreateGradeRecords``deleteChapter``revertToVersion`
- 性能优化:`Promise.all` 并行化、DB 层分页grades `getGradeRecords`
- TypeScript `as` 断言清理30+ 处,改用类型守卫)
- React 性能修复:`useMemo``useCallback``key` prop、渲染期间副作用移除
---
# 附录 A模块间依赖矩阵
> 行表示使用方,列表示被使用方。`✅` 合理依赖,`❌` 违规直查,`⟳` 循环依赖。
> ✅ P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口。
| ↓ 使用 → | shared | auth | exams | homework | questions | textbooks | classes | school | dashboard | users | grades | messaging | notifications | lesson-prep | error-book | 其他 |
|----------|--------|------|-------|----------|-----------|-----------|---------|--------|-----------|-------|--------|-----------|---------------|-------------|------------|------|
| **shared** | - | ⟳✅已修复 | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| **auth(root)** | ✅ db/lib | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| **exams** | ✅ | ✅ | - | - | ✅data-access | - | ✅data-access | ✅data-access | - | - | - | - | - | - | - | - |
| **homework** | ✅ | ✅ | ✅data-access | - | ✅关系 | - | ✅data-access | ✅data-access | - | ✅data-access | - | - | - | - | - | - |
| **questions** | ✅ | ✅ | - | - | - | ✅data-access | - | - | - | - | - | - | - | - | - | - |
| **textbooks** | ✅ | ✅ | - | - | ✅UI | - | - | - | - | - | - | - | - | - | - | - |
| **classes** | ✅ | ✅ | ✅data-access | ✅data-access | - | - | - | ✅data-access | - | - | - | - | - | - | - | ✅schedulingP0-5data-access-class-schedule 写函数) |
| **school** | ✅ | ✅ | - | - | - | - | - | - | - | ⚠️可接受 | - | - | - | - | - | - |
| **grades** | ✅ | ✅ | ✅外键 | ✅外键 | - | - | ✅data-access | ✅data-access | - | ✅data-access | - | - | - | - | - | - |
| **dashboard** | ✅ | ✅ | ✅data-access | ✅data-access | ✅data-access | ✅data-access | ✅data-access | - | - | ✅data-access | - | - | - | - | - | - |
| **users** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | - | - | - | - |
| **messaging** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | ✅dispatcher | - | - | - |
| **notifications** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | - | - | - | - |
| **attendance** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | - | - | - | - |
| **scheduling** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | ✅data-access | - | - | - | - | - | - |
| **proctoring** | ✅ | ✅ | ✅data-access | - | - | - | - | - | - | ✅data-access | - | - | - | - | - | - |
| **diagnostic** | ✅ | ✅ | ✅data-access | - | ✅data-access | - | ✅data-access | - | - | ✅data-access | - | - | - | - | - | - |
| **parent** | ✅ | ✅ | - | ✅data-access | - | - | ✅data-access | ✅data-access | - | ✅data-access | ✅data-access | - | - | - | - | - |
| **elective** | ✅ | ✅ | - | - | - | - | ✅data-access | ✅data-access | - | ✅data-access | - | - | - | - | - | - |
| **course-plans** | ✅ | ✅ | - | - | - | - | ✅ | ✅ | - | ✅ | - | - | - | - | - | - |
| **audit** | ✅ | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| **announcements** | ✅ | ✅ | - | - | - | - | - | ✅ | - | - | - | - | - | - | - | - |
| **files** | ✅ | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| **settings** | ✅ | ✅ | - | - | - | - | - | - | - | - | - | ✅ | - | - | - | - |
| **layout** | ✅ | ✅ | - | - | - | - | - | - | - | - | - | ✅ | - | - | - | - |
| **lesson-preparation** | ✅ | ✅ | ✅data-access | ✅data-access | ✅data-access | ✅data-access | ✅data-access | - | - | - | - | - | - | - | - | ✅files/ai |
| **error-book** | ✅ | ✅ | - | - | ✅actions | - | ✅data-access | - | - | - | - | - | - | - | - | - |
---
# 附录 B关键参数影响链
### `userId`
1.`auth.ts` JWT callback 从 `users` 表查询产生,存入 JWT
2. 通过 `session.user.id` 传递到所有 Server/Client Components
3. 通过 `getAuthContext().userId` 传递到所有 Server Actions
4.`auth-guard.ts` 中用于查询 `usersToRoles`(角色)和 `classSubjectTeachers`/`grades`DataScope
5.`exams/actions.ts` 中作为 `creatorId` 写入 `exams`
6.`homework/actions.ts` 中作为 `creatorId` 写入 `homeworkAssignments`
7.`classes/data-access.ts` 中查询 `getTeacherClasses(teacherId)` / `getGradeManagedClasses(userId)`
8.`elective/actions.ts` 中作为 `teacherId`/`studentId` 用于选课过滤
### `examId`
1.`exams/actions.createExamAction` 产生CUID2写入 `exams`
2.`exams/data-access.getExamById(id)` 读取
3.`exams/actions``updateExamAction`/`deleteExamAction`/`duplicateExamAction` 用于定位考试
4. 传入 `homework/actions.createHomeworkAssignmentAction``sourceExamId` 参数
5.`homeworkAssignments` 表中作为外键关联到源考试
6.`homework/data-access.getHomeworkAssignmentAnalytics` 用于追溯作业来源
### `classId`
1.`classes/actions``createTeacherClassAction`/`createAdminClassAction` 产生
2.`classes/data-access.getClassStudents(classId)` 读取学生列表
3.`classes/data-access.getClassSchedule(classId)` 读取课表
4.`classes/data-access.getClassHomeworkInsights(classId)` 读取作业洞察(✅ P0-7 已修复:通过 `homework/data-access-classes` 获取数据)
5.`homework/data-access.getHomeworkAssignments({ classId })` 过滤作业列表
6.`auth-guard.ts` 中通过 `classSubjectTeachers` 查询教师关联的 classIds构建 `DataScope.class_taught`
### `permission`
1.`shared/types/permissions.ts``Permissions` 常量定义61 个权限点)
2.`shared/lib/permissions.ts` 中通过 `ROLE_PERMISSIONS` 映射角色到权限列表
3.`auth.ts` JWT callback 中通过 `resolvePermissions(roleNames)` 合并多角色权限,存入 JWT
4.`proxy.ts` middleware 中通过 `token.permissions` 检查路由访问权限
5.`shared/lib/auth-guard.ts` 中通过 `requirePermission(permission)` 在 Server Action 层断言权限
6.`shared/hooks/use-permission.ts` 中通过 `hasPermission(permission)` 在客户端组件中条件渲染
7.`layout/config/navigation.ts` 中作为 `NavItem.permission` 字段过滤侧边栏菜单
### `DataScope`
1.`auth-guard.ts``resolveDataScope(userId, roles)` 根据用户角色和 DB 关系动态计算
2. 支持类型:`all` / `grade_managed` / `class_taught` / `class_members` / `children` / `owned`
3. 传递到 `exams`/`homework`/`grades`/`attendance`/`elective`/`dashboard` 的 data-access 进行行级过滤
4. 对 parent 角色,查询 `parentStudentRelations` 表构建 `{ type: "children", childrenIds: string[] }`
5.`parent/children/[studentId]/page.tsx` 中通过 `ctx.dataScope.childrenIds.includes(studentId)` 二次校验
---
# 附录 C核心函数签名索引
> 完整函数签名见 `005_architecture_data.json`。本附录仅列出关键函数。
### shared 层核心函数
```typescript
// shared/lib/auth-guard.ts
getAuthContext(): Promise<AuthContext>
requirePermission(permission: Permission): Promise<AuthContext>
requireAuth(): Promise<AuthContext>
checkPermission(permission: Permission): Promise<{ allowed: boolean; ctx: AuthContext }>
resolveDataScope(userId: string, roleNames: string[]): Promise<DataScope>
// shared/lib/permissions.ts
resolvePermissions(roleNames: string[]): Permission[]
// shared/lib/audit-logger.ts
logAudit(params: LogAuditParams): Promise<void>
// shared/lib/login-logger.ts
logLoginEvent(params: LogLoginEventParams): Promise<void>
// shared/lib/change-logger.ts
logDataChange(params: LogDataChangeParams): Promise<void>
// shared/lib/session.ts (P0-2 新增session 获取单一入口)
getSession(): Promise<AppSession>
// shared/lib/http-utils.ts (P1-6 完成,统一 IP/UA 提取)
resolveClientIp(): Promise<string>
getUserAgent(): Promise<string>
// shared/lib/ai/ (P2-2 已拆分,原 ai.ts 保留为重导出)
// ai/client.ts
createAiChatCompletion(input: AiChatRequest): Promise<{ content, usage }>
// ai/payload-parser.ts
parseAiChatPayload(body: unknown): AiChatRequest
// ai/api-key-crypto.ts
encryptAiApiKey(value: string): string
decryptAiApiKey(value: string): string
// shared/lib/password-policy.ts
validatePassword(password: string): { valid: boolean; errors: string[] }
getPasswordStrength(password: string): "weak" | "medium" | "strong"
isAccountLocked(failedAttempts: number, lastFailedAt: Date | null): boolean
// shared/lib/rate-limit.ts
rateLimit(params: { key: string; limit: number; windowMs: number }): RateLimitResult
rateLimitKey(prefix: string, identifier: string): string
rateLimitHeaders(result: RateLimitResult): Record<string, string>
// shared/lib/excel.ts
exportToExcel(params: { sheets: ExcelSheet[] }): Promise<Buffer>
parseExcel(buffer: Buffer): Promise<ParsedSheet[]>
generateTemplate(params: { sheets: TemplateSheet[] }): Promise<Buffer>
// shared/lib/file-storage.ts
isAllowedMimeType(mimeType: string): boolean
generateStoragePath(originalName: string): string
formatFileSize(bytes: number): string
// shared/lib/utils.ts
cn(...inputs: ClassValue[]): string
formatDate(date: string | Date, locale?: string): string
getSearchParam(params: SearchParams, key: string): string | undefined
formatNumber(v: number | null | undefined, digits?: number): string
// shared/lib/search-params.ts (re-export from utils.ts)
getParam(params: SearchParams, key: string): string | undefined // = getSearchParam
```
### 业务模块核心 Actions
```typescript
// exams/actions.ts
createExamAction(prevState: ActionState, formData: FormData): Promise<ActionState<Exam>>
createAiExamAction(prevState: ActionState, formData: FormData): Promise<ActionState<{ examId: string }>>
updateExamAction(prevState: ActionState, formData: FormData): Promise<ActionState>
deleteExamAction(prevState: ActionState, formData: FormData): Promise<ActionState>
duplicateExamAction(prevState: ActionState, formData: FormData): Promise<ActionState<{ examId: string }>>
// homework/actions.ts
createHomeworkAssignmentAction(prevState: ActionState, formData: FormData): Promise<ActionState>
startHomeworkSubmissionAction(prevState: ActionState, formData: FormData): Promise<ActionState<{ submissionId: string }>>
saveHomeworkAnswerAction(prevState: ActionState, formData: FormData): Promise<ActionState>
submitHomeworkAction(prevState: ActionState, formData: FormData): Promise<ActionState>
gradeHomeworkSubmissionAction(prevState: ActionState, formData: FormData): Promise<ActionState>
// classes/actions.ts
createTeacherClassAction(prevState: ActionState, formData: FormData): Promise<ActionState>
createAdminClassAction(prevState: ActionState, formData: FormData): Promise<ActionState>
createGradeClassAction(prevState: ActionState, formData: FormData): Promise<ActionState>
// + update/delete 各 3 个,共 9 个
// grades/actions.ts
getGradeRecordsAction(params: GetGradeRecordsParams): Promise<ActionState<PaginatedGradeRecords>>
createGradeRecordAction(prevState: ActionState, formData: FormData): Promise<ActionState>
exportGradesAction(params: ExportGradesParams): Promise<ActionState<Buffer>>
// scheduling/actions.ts
autoScheduleAction(prevState: ActionState, formData: FormData): Promise<ActionState>
applyAutoScheduleAction(prevState: ActionState, formData: FormData): Promise<ActionState>
```
### 业务模块核心 Data-access
```typescript
// exams/data-access.ts
getExams(params: GetExamsParams & { scope: DataScope }): Promise<{ items: Exam[]; total: number }>
getExamById(id: string, scope: DataScope): Promise<Exam | null>
persistExamDraft(input: ExamDraftInput): Promise<{ examId: string }>
persistAiGeneratedExamDraft(input: AiExamDraftInput): Promise<{ examId: string }>
// homework/data-access.ts
getHomeworkAssignments(params: GetHomeworkAssignmentsParams & { scope: DataScope }): Promise<{ items, total }>
getStudentHomeworkAssignments(studentId: string): Promise<StudentHomeworkAssignment[]>
getStudentDashboardGrades(studentId: string): Promise<StudentDashboardGrades>
getHomeworkAssignmentAnalytics(assignmentId: string): Promise<HomeworkAnalytics>
// classes/data-access.ts
getAdminClasses(scope: DataScope): Promise<Class[]>
getTeacherClasses(teacherId: string): Promise<Class[]>
getStudentClasses(studentId: string): Promise<Class[]>
getClassStudents(classId: string): Promise<Student[]>
getClassHomeworkInsights(classId: string): Promise<ClassHomeworkInsights> // ✅ P0-7 已修复:通过 homework/data-access-classes 获取数据
// grades/data-access.ts
getGradeRecords(params: GetGradeRecordsParams & { scope: DataScope; currentUserId?: string; limit?: number; offset?: number }): Promise<PaginatedGradeRecords>
getStudentGradeSummary(studentId: string, scope?: DataScope): Promise<StudentGradeSummary | null>
getClassRanking(classId: string, subjectId?: string, examId?: string, scope?: DataScope, currentUserId?: string): Promise<ClassRankingItem[]>
// scheduling/auto-scheduler.ts纯函数标杆
findOptimalSlot(input: FindOptimalSlotInput): ScheduleSlot | null
validateSchedule(schedule: ScheduleItem[]): ValidationResult
autoSchedule(input: AutoScheduleInput): AutoScheduleResult
buildDefaultTimeSlots(): TimeSlot[]
```
---
## 文档维护说明
- **修改源码后**:同步更新本文档对应模块章节 + `005_architecture_data.json`
- **新增模块**:在第二部分添加模块清单 + 更新 1.1 分层架构图 + 更新附录 A 依赖矩阵
- **新增/删除导出函数**:更新对应模块的"导出函数"清单 + 附录 C
- **修改依赖关系**:更新 1.2 模块依赖关系图 + 附录 A 依赖矩阵
- **新增路由**:更新 `005_architecture_data.json``routes` 节点
- **新增数据库表**:更新 `shared/db/schema.ts` 分节 + `005``dbTables` 节点
> 完整路由表、DevOps 脚本、E2E 测试等信息见 `005_architecture_data.json`。