将教材模块图谱从基本无用状态升级为完整知识图谱可视化系统。 数据层:新增 knowledgePointPrerequisites 表(复合主键+双外键 cascade);新增 data-access-graph.ts(server-only)知识点关联聚合、学生/班级掌握度查询;utils.ts 新增 hasCycleAfterAddingEdge(DFS 循环依赖检测)。 业务层:3 个新 Server Action(getKnowledgeGraphDataAction 三视图模式、createPrerequisiteAction 含循环检测、deletePrerequisiteAction);graph-layout.ts 重写为 dagre 分层有向图布局。 视图层:knowledge-graph.tsx 重写为 React Flow 主组件(全书视图+搜索高亮+关联节点高亮+章节着色);4 个新组件(graph-kp-node/graph-prerequisite-edge/graph-toolbar/graph-node-detail-panel);use-graph-data.ts 派生值模式避免 effect 中 setState。 架构:严格三层架构,客户端通过 Server Action 间接访问 server-only 数据层;权限校验+ i18n 全覆盖;架构文档 004/005 同步。 测试:utils.test.ts 新增 5 个循环检测测试,graph-layout.test.ts 重写 5 个 dagre 布局测试,全部 30 个教材模块单元测试通过。 附带提交 drizzle/0005 error-book 迁移文件以保持 journal 一致性。
2359 lines
207 KiB
Markdown
2359 lines
207 KiB
Markdown
# 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`)
|
||
|
||
**共享组件导出**(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` | 通用分页 UI(Showing 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 分桶着色) | 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-CN,weekday=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.x(without 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 获取单一入口(getSession,server-only,dynamic 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 获取 session,http-utils 获取 IP/UA) |
|
||
| `lib/change-logger.ts` | - | 数据变更日志(通过 session.ts 获取 session,http-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 / getUserAgent,server-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 + FilterResetButton(P3-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` 提取)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`、`questions`(✅ P0-1 已修复:通过 data-access.createQuestionWithRelations)、`classes`(✅ P0-2 已修复:通过 data-access.getClassGradeIdsByClassIds)、`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions)
|
||
- 被依赖:`homework`(通过 sourceExamId 外键,合理)、`dashboard`(通过 data-access,P0-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-access,actions.ts 现 691 行
|
||
- ⚠️ P1:`ai-pipeline.ts` 857 行(超 800 建议),混合 4 类职责
|
||
- ✅ P2 已修复:`ai-pipeline.ts` 中 3 处非空断言清理(`draft.sections!.forEach` → 安全守卫、`aiParsed.sections!.flatMap` → `?? []`、`aiParsed.sections!.map` → `?? []`)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 691 | 10 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
||
| `ai-pipeline.ts` | 857 | AI 出题管线(超限) |
|
||
| `data-access.ts` | 473 | 考试 CRUD(含 P1-2 新增 7 个写/查询函数,P0-1/P0-2 已修复:通过 questions/classes data-access 跨模块通信) |
|
||
| `types.ts` | 31 | 类型定义 |
|
||
| `hooks/use-exam-preview.ts` | 295 | 预览 Hook |
|
||
| `utils/normalize-structure.ts` | 57 | v3 新增:exam.structure 运行时校验与归一化(从 build/page.tsx 提取) |
|
||
| `components/*` | 18 文件 | 考试表单/组卷/预览组件 |
|
||
|
||
---
|
||
|
||
## 2.3 homework(作业模块)
|
||
|
||
**职责**:作业全生命周期(创建/发布/作答/批改/分析)。
|
||
|
||
**导出函数**:
|
||
- Actions:`createHomeworkAssignmentAction` / `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction`(✅ 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 使用)
|
||
- Data-access-classes:`getAssignmentIdsForStudents` / `getHomeworkAssignmentsWithSubject` / `getHomeworkAssignmentsByIds` / `getAssignmentTargetCounts` / `getHomeworkSubmissionsForStudents` / `getPublishedHomeworkAssignmentsWithSubject` / `getHomeworkSubmissionsForAssignments`(P0-7 新增,供 classes 模块跨模块调用,封装 homework/exams 表查询)
|
||
- Data-access-write:10 个写操作函数(P1-2 新增,从 actions 下沉)
|
||
- Stats-service:`getTeacherGradeTrends` / `getHomeworkAssignmentAnalytics` / `getStudentDashboardGrades`(从 data-access.ts re-export 以保持向后兼容)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`、`exams`(✅ P1-1 已修复:通过 exams data-access.getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework)、`classes`(✅ P1-1 已修复:通过 classes data-access.getStudentIdsByClassId 等 7 个函数)、`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions)、`users`(✅ P1-1 已修复:通过 users data-access.getUserWithRole/getUserNamesByIds)
|
||
- 被依赖:`dashboard`(通过 data-access,合理)、`parent`(通过 data-access,合理)、`classes`(✅ P0-7 已修复:classes 通过 `homework/data-access-classes` 获取作业数据,不再反向直查 homework/exams 表)
|
||
|
||
**已知问题**:
|
||
- ✅ 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`
|
||
- ✅ P1-2 已修复:~~`actions.ts` 多处直接 DB 操作(`createHomeworkAssignmentAction` 157 行)~~ DB 操作已下沉到 `data-access-write.ts`,actions.ts 现 239 行
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `data-access.ts` | 598 | 作业 CRUD + 学生视角 + 批改(含 re-export stats 函数) |
|
||
| `data-access-write.ts` | 285 | 作业写操作(P1-2 新增,10 个写函数从 actions 下沉) |
|
||
| `data-access-classes.ts` | 232 | 跨模块查询封装(P0-7 新增,供 classes 模块调用,封装 homework/exams 表查询) |
|
||
| `stats-service.ts` | 425 | 统计分析(教师趋势/作业分析/学生仪表盘成绩) |
|
||
| `actions.ts` | 239 | 5 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
||
| `types.ts` | 186 | 类型定义 |
|
||
| `schema.ts` | 29 | Zod 校验 |
|
||
|
||
---
|
||
|
||
## 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 Action(P1-2 已修复,无直接 DB 操作) |
|
||
| `data-access.ts` | 260 | 题目 CRUD + 知识点选项(含 P1-2 新增 4 个写/查询函数) |
|
||
| `schema.ts` | 18 | Zod 校验 |
|
||
| `types.ts` | 34 | 类型定义 |
|
||
|
||
---
|
||
|
||
## 2.5 textbooks(教材模块)— 标杆模块(data-access 层)
|
||
|
||
**职责**:教材与知识体系管理(教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱)。
|
||
|
||
**导出函数**:
|
||
- Actions(14 个,均为写操作;读操作由 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 新增:资源归属校验)/ `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-access,P0-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` | 502 | 14 个 Server Action(写操作,含 Zod 校验 + 资源归属校验 + 知识图谱查询/前置依赖管理) |
|
||
| `data-access.ts` | 586 | 教材/章节/知识点 CRUD + 跨模块查询接口 + 资源归属校验 + 数据范围过滤 + 前置依赖 CRUD |
|
||
| `data-access-graph.ts` | 184 | 知识图谱只读查询(✅ Task 5 新增:知识点关联聚合、学生/班级掌握度、前置后置知识点,标记 `server-only`) |
|
||
| `types.ts` | 94 | 类型定义(含知识图谱类型:GraphViewMode/MasteryInfo/KpWithRelations/GraphNodeData/GraphEdgeData/KnowledgeGraphData/MasteryLevel) |
|
||
| `schema.ts` | 62 | Zod 校验(含 CreatePrerequisiteSchema/DeletePrerequisiteSchema) |
|
||
| `constants.ts` | 99 | 学科/年级常量与颜色映射(✅ 新增;v1 测试修复:新增 Chinese/Grade 1/Grade 2) |
|
||
| `utils.ts` | 203 | 章节树构建/排序/查找等纯函数 + 循环检测(✅ 新增,含单测) |
|
||
| `graph-layout.ts` | 105 | 知识图谱布局计算纯函数(✅ Task 8 重写为 dagre 集成,输出 React Flow 格式,含单测) |
|
||
| `analytics.tsx` | 43 | 教材分析 Context/Provider/Hook(✅ 新增) |
|
||
| `hooks/use-knowledge-point-actions.ts` | 121 | 知识点操作 Hook |
|
||
| `hooks/use-text-selection.ts` | 57 | 文本选区捕获 Hook |
|
||
| `hooks/use-graph-data.ts` | 58 | 知识图谱数据加载 Hook(✅ Task 11 新增:派生值模式避免 effect 中 setState) |
|
||
| `components/teacher-textbook-reader.tsx` | 41 | 教师端 TextbookReader 客户端包装(✅ v1 测试修复:解决 Server→Client 函数 prop 序列化问题) |
|
||
| `components/knowledge-graph.tsx` | 249 | React Flow 知识图谱主组件(✅ Task 10/15 重写:全书视图 + 搜索高亮 + 关联节点高亮 + 章节着色) |
|
||
| `components/graph-kp-node.tsx` | 80 | React Flow 自定义节点(✅ Task 9 新增:知识点名称+题目数徽章+掌握度进度条) |
|
||
| `components/graph-prerequisite-edge.tsx` | 40 | React Flow 自定义边(✅ Task 9 新增:虚线+箭头表示前置依赖) |
|
||
| `components/graph-toolbar.tsx` | 77 | 图谱工具栏(✅ Task 9 新增:视图模式切换 + 搜索 + 重置视图) |
|
||
| `components/graph-node-detail-panel.tsx` | 171 | 节点详情侧边面板(✅ Task 13 新增:描述/掌握度/关联题目/前置后置列表) |
|
||
| `components/*` | 17 文件 | 教材编辑/知识图谱组件(新增 `section-error-boundary.tsx`、`teacher-textbook-reader.tsx`、5 个 graph-* 组件) |
|
||
|
||
---
|
||
|
||
## 2.6 grades(成绩模块)— 标杆模块(拆分范例)
|
||
|
||
**职责**:成绩分析(录入/查询/统计/导出/趋势对比分析)。
|
||
|
||
**导出函数**:
|
||
- Actions:`getGradeRecordsAction` / `createGradeRecordAction` / `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction` / `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction` / `assertClassInScope`(✅ P3 新增导出:班级 scope 校验工具,供 actions-analytics 复用)
|
||
- Data-access:`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getRankingTrend` / `PaginatedGradeRecords`(✅ P3 新增:分页结果接口 `{ records, total }`)
|
||
- Lib(✅ P1-2 新增,✅ P3 更新签名):`toNumber` / `normalize` / `buildScopeClassFilter(scope, currentUserId?)`(P3 修复:`class_members` scope 内置 studentId 过滤,需传入 currentUserId 参数)
|
||
- Stats-service(✅ P1-1 新增):`computeGradeStats` / `computeAverageScore` / `buildGradeTrendPoints` / `computeTrendAverage` / `computeClassComparisonStats` / `computeSubjectComparisonStats` / `computeGradeDistribution` / `buildRankingTrendPoints`(从 3 个 data-access 文件抽取的纯函数,使数据层专注 DB I/O,统计逻辑可独立测试)
|
||
- Components(✅ P1-5 新增):`WidgetBoundary`(Error Boundary + Suspense + Skeleton 组合,含 a11y 属性)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`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 个查询 schema(DeleteGradeRecordSchema/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 添加 EmptyState;entry/page.tsx 与 stats/page.tsx 过滤 class_taught scope 班级;student/grades/page.tsx 与 parent/grades/page.tsx 传递 `ctx.dataScope` 到 data-access
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 396 | 15 个 Server Action(含 Zod 校验,含 v2-P1-5 安全修复:assertClassInScope + 行级 scope 校验;P3 修复:handleActionError + safeJsonParse + scope 传递 + DB 层分页) |
|
||
| `actions-analytics.ts` | 170 | 5 个分析 Action(含 Zod 校验,P3 修复:handleActionError + assertClassInScope 校验) |
|
||
| `data-access.ts` | 428 | 成绩 CRUD + 统计(含 v2-P2-9 修复:recorderName 批量查询;P3 修复:PaginatedGradeRecords 接口 + DB 层分页 + 事务 + 存在性检查 + scope 过滤 + 并列排名) |
|
||
| `data-access-analytics.ts` | 200 | 趋势/对比分析(P3 修复:getClassComparison 应用 buildScopeClassFilter) |
|
||
| `data-access-ranking.ts` | 83 | 排名查询(P3 修复:getRankingTrend 接受 scope 参数 + class_taught 校验) |
|
||
| `stats-service.ts` | 279 | 统计计算纯函数(P1-1 新增:8 个纯函数 + 2 个常量 + 2 个接口) |
|
||
| `export.ts` | 189 | Excel 导出(v2-P1-5 修复:传递 currentUserId 到 data-access;P3 修复:适配 PaginatedGradeRecords 结构 + 传递 scope) |
|
||
| `schema.ts` | 113 | Zod 校验(含 12 个查询 schema;P3 修复:score .max(1000) + records .max(500) + 补全查询字段) |
|
||
| `lib/grade-utils.ts` | 66 | 公共工具函数(toNumber/normalize/buildScopeClassFilter,v2-P2-2 修复:改用 classes data-access 子查询;P3 修复:buildScopeClassFilter 新增 currentUserId 参数) |
|
||
| `components/widget-boundary.tsx` | 136 | Widget 边界组件(P1-5 新增,v2-P1-1 已在 3 个页面应用) |
|
||
| `components/grade-trend-card.tsx` | 69 | 趋势卡片(v2-P2-9 修复:a11y;v2-P1-4:i18n;P3 修复:NaN 日期检查 + fullScore > 0 守卫) |
|
||
| `components/grade-record-list.tsx` | 125 | 成绩记录列表(v2-P1-4:i18n;P3 修复:safeActionCall 包装删除操作) |
|
||
| `components/grade-distribution-chart.tsx` | 100 | 分数分布图(v2-P1-4:i18n) |
|
||
| `components/subject-comparison-chart.tsx` | 62 | 科目对比图(v2-P1-4:i18n) |
|
||
| `components/class-comparison-chart.tsx` | 58 | 班级对比图(v2-P1-4:i18n) |
|
||
| `components/grade-trend-chart.tsx` | 59 | 趋势图(v2-P1-4:i18n) |
|
||
| `components/grade-record-form.tsx` | 177 | 录入表单(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;P3 修复:safeActionCall 包装提交) |
|
||
| `components/batch-grade-entry.tsx` | 435 | 批量录入(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;P3 修复:safeActionCall + localStorage 安全检查 + 区分未录入与录入 0) |
|
||
| `components/grade-filters.tsx` | 76 | 过滤器(v2-P1-4:i18n) |
|
||
| `components/student-grade-summary.tsx` | 107 | 学生成绩摘要(v2-P1-4:i18n) |
|
||
| `components/export-button.tsx` | 79 | 导出按钮(v2-P1-4:i18n;P3 修复:safeActionCall 包装导出操作) |
|
||
| `components/analytics-filters.tsx` | 86 | 分析过滤器(v2-P1-4:i18n) |
|
||
| `components/stats-class-selector.tsx` | 40 | 统计班级选择器(v2-P1-4:i18n) |
|
||
| `components/grade-stats-card.tsx` | 74 | 统计卡片(v2-P1-4:i18n) |
|
||
| `components/class-grade-report.tsx` | 90 | 班级成绩报告(v2-P1-4:i18n) |
|
||
| `components/grade-query-filters.tsx` | 96 | 查询过滤器(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n) |
|
||
| `types.ts` | 168 | 类型定义 |
|
||
|
||
---
|
||
|
||
## 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.ts,classes 模块仅保留 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-access,P0-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.ts,classes 模块仅保留 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-export(P0-3 修复:从 974 行拆分为 6 个文件) |
|
||
| `actions-teacher.ts` | 100 | 教师班级 CRUD(3 个 Action) |
|
||
| `actions-admin.ts` | 120 | 管理员班级 CRUD(3 个 Action) |
|
||
| `actions-grade.ts` | 110 | 年级组长班级 CRUD(3 个 Action) |
|
||
| `actions-invitations.ts` | 280 | 邀请码与注册(8 个 Action) |
|
||
| `actions-schedule.ts` | 90 | 班级课表 CRUD(3 个 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`(编排层:权限校验 + Zod 校验 + 调用 data-access + revalidatePath + after(logAudit))
|
||
- Data-access:只读查询(`getSchools` / `getGrades` / `getDepartments` / `getAcademicYears` / `getStaffOptions` / `getGradesForStaff`)+ 写操作(`create/update/delete` × `Department/School/Grade/AcademicYear`)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`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.tsx(class component Error Boundary + i18n + router.refresh 重试)和 school-skeleton.tsx(SchoolListSkeleton 表格骨架 + SchoolCardSkeleton 卡片骨架);4 个页面(schools/grades/departments/academic-year)均已包裹 SchoolErrorBoundary;school.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 分离
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 349 | 12 个 Server Action(编排层,无 DB 直访) |
|
||
| `data-access.ts` | 504+ | 只读查询 + 12 个写操作 + 跨模块查询接口 + 权限感知函数(getSchoolsForUser/getGradesForUser) |
|
||
| `schema.ts` | 51 | Zod 校验 |
|
||
| `types.ts` | 96 | 类型定义(含 Insert/Update 入参类型) |
|
||
| 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 Boundary(P1-3 修复) |
|
||
| components/school-skeleton.tsx | 69 | 共享骨架屏(P1-3 修复) |
|
||
| hooks/use-school-data.ts | 40 | 学校数据管理 hook(P2-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(考勤模块)— 结构典范
|
||
|
||
**职责**:考勤记录管理 + 统计分析 + 规则配置。
|
||
|
||
**导出函数**:
|
||
- Actions(10 个):`recordAttendanceAction` / `batchRecordAttendanceAction` / `updateAttendanceAction` / `deleteAttendanceAction` / `getAttendanceAction` / `getStudentAttendanceAction` / `getClassAttendanceStatsAction` / `getClassAttendanceForDateAction` / `saveAttendanceRulesAction` / `getAttendanceRulesAction`
|
||
- Data-access:`getAttendanceRecords` / `createAttendanceRecord` / `updateAttendanceRecord` / `deleteAttendanceRecord` / `getClassStudentsForAttendance` / `getAttendanceStats`(管理员考勤总览页统计概览,基于 `getAttendanceRecords` 聚合)/ `upsertAttendanceRules` / `getAttendanceRules`
|
||
- Data-access-stats:`getStudentAttendanceSummary` / `getClassAttendanceStats` / `computeStats`(⚠️ 未导出,无法单测)
|
||
- Components:`AttendanceSheet`(批量点名表单)/ `AttendanceRecordList`(记录列表 + 删除)/ `AttendanceFilters`(URL 同步筛选器)/ `AttendanceStatsCard`(单卡片统计)/ `AttendanceStatsCards`(管理员 6 卡片总览)/ `AttendanceStatsClassSelector`(班级筛选 ChipNav)/ `AttendanceRulesForm`(规则配置表单)/ `StudentAttendanceView`(学生/家长只读视图)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`、`classes`(⚠️ P1-1 未修复:`getClassStudentsForAttendance` 仍直查 `classEnrollments` 表)
|
||
- 被依赖:`parent`(⚠️ 跨模块 UI 类型依赖:3 个 parent 组件直接 import `@/modules/attendance/types`)
|
||
|
||
**已知问题**(详见 `docs/architecture/audit/attendance-elective-audit-report.md`):
|
||
- ❌ P0:`getAttendanceStats` 统计失真——调用 `getAttendanceRecords`(默认 pageSize=20)后对 `items` 聚合,仅基于前 20 条记录计算总览数据
|
||
- ❌ P0:`getClassStudentsForAttendance` 仍直查 `classEnrollments` 表(架构图此前声称已修复,实际未修复)
|
||
- ❌ P0:6 个读 Action 无调用方(页面绕过 Action 直接调用 data-access),违反三层架构
|
||
- ❌ P0:update/delete Action 缺资源归属校验(教师 A 可修改/删除教师 B 的记录)
|
||
- ❌ P0:i18n 完全缺失(`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 校验) |
|
||
| `data-access.ts` | 309 | 考勤 CRUD + 班级学生查询 + 规则 upsert + 总览统计 |
|
||
| `data-access-stats.ts` | 145 | 学生/班级考勤汇总(拆分范例,`computeStats` 未导出) |
|
||
| `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 | 学生/家长视图(统计 + 最近记录) |
|
||
|
||
---
|
||
|
||
## 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-access,P0-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 Action(profile 更新 + 模板下载/导入/导出 + 角色更新 + 删除) |
|
||
| `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 V2):10 个子组件 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 V2):a11y 增强 — 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)。
|
||
|
||
**导出函数**:
|
||
- Actions:`sendMessageAction` / `markMessageAsReadAction` / `deleteMessageAction` / `getMessagesAction` / `getMessageDetailAction` / `getRecipientsAction` / `getUnreadMessageCountAction` / `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`(✅ P1-4 已修复:通知 CRUD Action 已迁移至 notifications 模块,messaging 仅保留私信和通知偏好 Action)
|
||
- Data-access:`getMessages` / `getMessageById` / `getMessageThread` / `createMessage` / `markMessageAsRead` / `deleteMessage` / `getUnreadMessageCount` / `getRecipients`(按 DataScope 过滤可发送对象:class_taught 教师→学生、grade_managed 年级管理员→教师/学生、all 管理员、class_members 学生→自己班级的任课教师/班主任、children 家长→孩子的班主任/任课教师;通过 classes data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID)/ `getMessagesPageData`(✅ P1-5 新增:消息首页编排函数,一次性获取消息列表和通知列表)/ `getMessageDetailPageData`(✅ V2-P1-3 新增:消息详情页编排函数,获取详情并自动标记已读)
|
||
- 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~~ 新增客户端分页 UI(PAGE_SIZE=20,ChevronLeft/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)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `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 + 客户端分页 UI,PAGE_SIZE=20;✅ V2-P1-2:客户端过滤仅在初始数据时执行) |
|
||
| `components/message-detail.tsx` | 消息详情(含回复) |
|
||
| `components/message-compose.tsx` | 撰写新消息(✅ V2-P1-4:fieldErrors + aria-invalid 字段级错误展示) |
|
||
| `components/unread-message-badge.tsx` | 未读消息计数徽章(侧边栏,每 60 秒轮询 `getUnreadMessageCountAction`;✅ V2-P2-1:POLL_INTERVAL_MS 常量) |
|
||
|
||
**客户端行为**:
|
||
- `message-list.tsx`:客户端调用 `getMessagesAction` 搜索消息(useMessageSearch hook,400ms 防抖,请求竞态取消);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`(✅ P1-4 新增:后 4 个通知 CRUD Action 从 messaging 模块迁移)
|
||
- Dispatcher:`sendNotification(payload)` / `sendBatchNotifications(payloads)`
|
||
- Data-access:`createNotification` / `getNotifications` / `markNotificationAsRead` / `markAllNotificationsAsRead` / `getUnreadNotificationCount` / `getUserContactInfo` / `logNotificationSend` / `logNotificationSendBatch`(✅ P0-4 / P1-5 修复后从 messaging 迁移)
|
||
- Preferences:`getNotificationPreferences` / `upsertNotificationPreferences`(✅ P0-4 / P1-5 修复后从 messaging 迁移)
|
||
- Channels:`InAppChannelSender` / `SmsChannelSender` / `EmailChannelSender` / `WeChatChannelSender`
|
||
- Components:`NotificationList` / `NotificationDropdown`(✅ P1-4 新增:从 messaging/components 迁移)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`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 不再反向依赖 messaging,in-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)
|
||
- ⚠️ P1:发送日志仅 console,无 `notification_logs` 表
|
||
- ✅ 渠道抽象优秀(接口 + 工厂 + Mock 实现)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `dispatcher.ts` | 152 | 渠道选择 + 并行分发 |
|
||
| `data-access.ts` | 177 | 站内通知 CRUD + 用户联系方式 + 日志(P0-4 / P1-5 修复后新增通知 CRUD) |
|
||
| `preferences.ts` | 166 | 通知偏好 CRUD(P0-4 / P1-5 修复后从 messaging 迁移) |
|
||
| `actions.ts` | ~260 | 6 个 Server Action(✅ P1-4:新增 4 个通知 CRUD Action) |
|
||
| `types.ts` | 120 | 通知负载 + 渠道配置 + 通知记录 + 偏好类型(P0-4 / P1-5 修复后扩充) |
|
||
| `index.ts` | ~75 | 对外导出入口(✅ P1-4:新增组件和 CRUD Action 导出) |
|
||
| `channels/*` | 5 文件 | 4 个渠道实现 |
|
||
| `components/notification-list.tsx` | ~140 | ✅ P1-4 新增(从 messaging 迁移):通知列表组件 |
|
||
| `components/notification-dropdown.tsx` | ~180 | ✅ P1-4 新增(从 messaging 迁移):通知下拉菜单组件 |
|
||
|
||
**组件清单**:
|
||
| 组件 | 职责 |
|
||
|------|------|
|
||
| `components/notification-list.tsx` | 通知列表(消息页底部,展示所有通知,支持标记已读;✅ V2-P0-1:useTranslations 命名空间从 "messages" 切换到 "notifications") |
|
||
| `components/notification-dropdown.tsx` | 通知下拉菜单(站点头部,每 30 秒轮询 `getNotificationsAction` + `getUnreadNotificationCountAction`;✅ V2-P0-1:useTranslations 命名空间切换;✅ V2-P2-1:POLL_INTERVAL_MS 常量) |
|
||
|
||
**客户端行为**:
|
||
- `notification-dropdown.tsx`:每 `POLL_INTERVAL_MS`(30_000ms)轮询 `getNotificationsAction`(pageSize=10)和 `getUnreadNotificationCountAction` 刷新通知和未读计数
|
||
|
||
---
|
||
|
||
## 2.15 audit(审计模块)
|
||
|
||
**职责**:操作日志 + 登录日志 + 数据变更日志查询与导出。
|
||
|
||
**导出函数**:
|
||
- Actions:`getAuditLogsAction` / `getLoginLogsAction` / `getDataChangeLogsAction` / `exportAuditLogsAction` / `exportLoginLogsAction` / `exportDataChangeLogsAction`
|
||
- Data-access:`getAuditLogs` / `getLoginLogs` / `getDataChangeLogs` / `getAuditModuleOptions`
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`
|
||
- 被依赖:无
|
||
|
||
**已知问题**:
|
||
- ⚠️ P2:Excel 导出逻辑内联在 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`)
|
||
- Data-access:`getAnnouncements`(支持 `audience` 受众过滤)/ `getAnnouncementById` / `insertAnnouncement` / `updateAnnouncementById` / `deleteAnnouncementById` / `publishAnnouncementById` / `archiveAnnouncementById`(后 5 个为 P1-2 新增)/ `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData`(✅ P1-5 新增:管理端列表页和编辑页编排函数,页面层仅调用单一函数)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`、`school`(获取年级列表)、`classes`(获取班级列表 + 解析受众)、`users`(获取目标用户 ID 列表)、`notifications`(发布公告时发送通知)
|
||
- 被依赖:无
|
||
|
||
**已知问题**:
|
||
- ✅ P1-2 已修复:~~所有写操作直接在 actions 层 `db.insert/update/delete`,未下沉到 data-access~~ 写操作已下沉到 data-access(5 个新函数)
|
||
- ✅ 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)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | ~330 | 6 个 Server Action + 通知触发逻辑 + trackEvent 埋点(P1-2 已修复,无直接 DB 操作) |
|
||
| `data-access.ts` | ~230 | 公告 CRUD + 发布/归档 + 受众过滤 + `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData` 编排函数(✅ P1-5 新增) |
|
||
| `schema.ts` | ~70 | Zod 校验 + `refineAudience` 条件校验(✅ P1-6 新增 superRefine) |
|
||
| `types.ts` | ~65 | 类型定义(`GetAnnouncementsParams` 新增 `audience` 字段) |
|
||
|
||
**组件清单**:
|
||
| 组件 | 职责 |
|
||
|------|------|
|
||
| `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-4:fieldErrors + 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` / `getParentDashboardData` / `verifyParentChildRelation` / `getChildNameList`(✅ v4 新增:用于详情页头部多子女切换器,一次批量查询避免 N+1)
|
||
- Components:`ParentDashboard` / `ChildCard` / `ChildDetailHeader` / `ChildDetailPanel` / `SiblingSwitcher` / `ChildHomeworkSummary` / `ChildHomeworkDetail`(v4 新增)/ `ChildGradeSummary` / `ChildGradeDetail`(v4 新增)/ `ChildScheduleCard` / `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-P02:Grades/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 tab(1 次点击到达)
|
||
- ✅ HABIT-P03:多子女切换无需返回仪表盘
|
||
- ✅ HABIT-P06:仪表盘展示未读/待办数量
|
||
- ✅ A11Y-P02:Overdue 状态增加 AlertTriangle 图标辅助
|
||
- ✅ A11Y-P04:成绩图表容器新增 aria-label
|
||
- ✅ PERF-P01/P02:骨架屏 + 错误边界
|
||
- ✅ PERF-P03:空状态新增"Contact support"引导按钮
|
||
- ✅ PERF-P04:`ChildCard` Link 添加 `prefetch`
|
||
- ✅ MOBILE-P03:移动端子女卡片改为水平滑动 Carousel(snap-x)
|
||
- ✅ MOBILE-P04:作业/成绩列表项 `min-h-[44px]` 触摸区域
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`、`classes`(合理)、`homework`(合理)、`grades`(合理)、`users`(合理)、`school`(合理)、`attendance`(v4 新增:考勤页复用 `StudentAttendanceView`;⚠️ 跨模块 UI 类型依赖:3 个组件直接 import `@/modules/attendance/types`)
|
||
- 被依赖:无
|
||
|
||
**已知问题**:
|
||
- ⚠️ P1:3 个 parent 组件(`parent-attendance-warning.tsx`/`parent-attendance-rate-card.tsx`/`parent-attendance-calendar.tsx`)直接 import `@/modules/attendance/types`,违反模块解耦原则(应通过 data-access 接口或 shared 类型抽象)
|
||
- ⚠️ P1:parent-attendance-calendar.tsx 重新定义 `STATUS_DOT`/`STATUS_LABEL` 常量,与 attendance 模块重复
|
||
- ⚠️ P1:3 个 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 保留:`ParentExportButton` 为占位,待后端实现成绩导出 Server Action 后接入
|
||
- ⚠️ v4 保留:详情页 Attendance/Diagnostic Tab 为占位提示,待对应功能实现后填充
|
||
- ✅ 职责单一,正确复用其他模块 data-access
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `data-access.ts` | 243 | 子女关系 + 仪表盘数据聚合 + 关系校验 + 子女姓名列表(v4 新增 `getChildNameList` + `buildWeeklySchedule`) |
|
||
| `types.ts` | 79 | 类型定义(含 JSDoc,v4 新增 `ChildWeeklyScheduleItem`) |
|
||
| `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 新增:成绩导出按钮(占位) |
|
||
| `components/child-card.tsx` | 148 | 子女卡片(v4 增强:异常突出 + 趋势图标) |
|
||
| `components/child-detail-header.tsx` | 78 | 详情页头部(v4 增强:面包屑) |
|
||
| `components/child-detail-panel.tsx` | 200 | 详情页 Tab 面板 + SiblingSwitcher(v4 重写,集成 Homework/Grade Detail) |
|
||
| `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-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` | 多子女成绩聚合 |
|
||
| `/parent/diagnostic` | `diagnostic/page.tsx` + `loading.tsx` + `error.tsx` | P2-5 新增:多子女学情诊断聚合 |
|
||
| `/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(选课模块)
|
||
|
||
**职责**:选修课程管理 + 学生选课 + 抽签。
|
||
|
||
**导出函数**:
|
||
- Actions(11 个):`getElectiveCoursesAction` / `createElectiveCourseAction` / `updateElectiveCourseAction` / `deleteElectiveCourseAction` / `getStudentSelectionsAction` / `selectCourseAction` / `dropCourseAction` / `runLotteryAction` / `getAvailableCoursesForStudentAction` / `openSelectionAction` / `closeSelectionAction`
|
||
- Data-access:`getElectiveCourses` / `getElectiveCourseById` / `createElectiveCourse` / `updateElectiveCourse` / `deleteElectiveCourse` / `openSelection` / `closeSelection` / `buildCourseSelect` / `mapCourseRow` / `resolveCourseDisplayNames` / `CourseCoreRow`(P3 新增导出,供 data-access-selections 复用)
|
||
- Data-access-operations:`selectCourse` / `dropCourse` / `runLottery` / `buildLotteryRankCase`(⚠️ 未导出,无法单测)
|
||
- Data-access-selections:`getCourseSelections` / `getStudentSelections` / `getStudentGradeId` / `getAvailableCoursesForStudent`
|
||
- Components:`ElectiveCourseList`(课程卡片网格 + 管理操作)/ `ElectiveCourseForm`(课程创建/编辑表单)/ `ElectiveFilters`(nuqs 筛选栏)/ `StudentSelectionView`(学生选课视图)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`、`school`(✅ P3 已修复:通过 school data-access.getSubjectOptions/getGradeOptions 获取科目/年级名称,不再直查 subjects/grades 表)、`users`(✅ P3 已修复:通过 users data-access.getUserNamesByIds 获取教师姓名,不再直查 users 表)、`classes`(通过 classes data-access.getStudentActiveGradeId 获取学生年级)
|
||
- 被依赖:无
|
||
|
||
**已知问题**(详见 `docs/architecture/audit/attendance-elective-audit-report.md`):
|
||
- ❌ P0:3 个读 Action 无调用方(`getElectiveCoursesAction`/`getStudentSelectionsAction`/`getAvailableCoursesForStudentAction`),页面绕过 Action 直接调用 data-access
|
||
- ❌ P0:update/delete/select/drop/lottery Action 缺资源归属校验(教师 A 可操作教师 B 的课程,学生可退选他人课程)
|
||
- ❌ P0:i18n 完全缺失(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 Action |
|
||
| `data-access.ts` | 250 | 课程 CRUD + scope 过滤 + 共享映射函数(P3 重构:移除跨模块 join,通过 school/users data-access 获取显示名称) |
|
||
| `data-access-operations.ts` | 245 | 选课操作(select/drop/lottery,P3 重构:事务包裹 + FOR UPDATE 锁 + Fisher-Yates 洗牌) |
|
||
| `data-access-selections.ts` | 149 | 选课记录查询 + 学生可选课程 |
|
||
| `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 | 学生选课视图(已选 + 可选) |
|
||
|
||
---
|
||
|
||
## 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)
|
||
- 被依赖:无
|
||
|
||
**已知问题**:
|
||
- ❌ P0:`exam-mode-config.tsx` 未集成到考试表单(死代码,监考功能无法启用)
|
||
- ✅ 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` | - | 考试模式配置(**未集成**) |
|
||
| `components/proctoring-dashboard.tsx` | - | 教师监考面板 |
|
||
|
||
---
|
||
|
||
## 2.22 diagnostic(学情诊断模块)
|
||
|
||
**职责**:知识点掌握度查询 + 诊断报告生成。
|
||
|
||
**导出函数**:
|
||
- Actions:`generateStudentReportAction` / `generateClassReportAction` / `publishReportAction` / `deleteReportAction`(v2-P2-3 修复:删除死代码 `getDiagnosticReportsAction` / `getDiagnosticReportByIdAction`,页面直接调用 data-access 并自行权限校验)
|
||
- Data-access:`updateMasteryFromSubmission`(v2-P1-8 修复:累积模式;v2-P2-5 修复:db.transaction 包裹)/ `getStudentMastery` / `getStudentMasterySummary` / `getClassMasterySummary`(v2-P2-4 修复:totalStudents 语义 + 班级平均掌握度按学生平均)/ `getKnowledgePointStats`(v2-P1-7 修复:页面先查班级再传参)
|
||
- Data-access-reports:`generateDiagnosticReport` / `generateClassDiagnosticReport`(v2-P2-6 修复:校验掌握度数据)/ `getDiagnosticReports` / `getDiagnosticReportById` / `publishDiagnosticReport` / `deleteDiagnosticReport`(✅ P2 已修复:使用 `React.cache()` 包装实现请求级 memoization)
|
||
- Stats-service(✅ v2-P1-6 新增):`serializeMasteryWithKp` / `computeAverageMastery` / `classifyStrengthsWeaknesses` / `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/getActiveStudentIdsByClassId;v2-P1-7 新增 getStudentActiveClassId)、`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId)
|
||
- 被依赖:无
|
||
|
||
**已知问题**:
|
||
- ✅ 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 模块无职责重叠
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `data-access.ts` | 179 | 知识点掌握度查询 + 更新(v2-P1-8 累积模式;v2-P2-5 事务;v2-P2-4 语义修正;v2-P1-6 改用 stats-service 纯函数) |
|
||
| `data-access-reports.ts` | 160 | 诊断报告 CRUD(v2-P2-6 校验;v2-P1-6 改用 stats-service 纯函数) |
|
||
| `stats-service.ts` | 352 | 统计计算纯函数(v2-P1-6 新增:12 个纯函数 + 2 个常量 + 4 个接口) |
|
||
| `actions.ts` | 111 | 4 个 Server Action(v2-P2-3 删除 2 个死代码读 Action) |
|
||
| `schema.ts` | 23 | Zod 校验(4 个 schema,v2-P2-3 删除 2 个死代码 schema) |
|
||
| `types.ts` | 87 | 类型定义 |
|
||
| `components/class-diagnostic-view.tsx` | 266 | 班级诊断视图(v2-P1-6 热力图 a11y;v2-P1-4 i18n) |
|
||
| `components/student-diagnostic-view.tsx` | 225 | 学生诊断视图(v2-P1-4 i18n) |
|
||
| `components/mastery-radar-chart.tsx` | 72 | 雷达图(v2-P1-4 i18n) |
|
||
| `components/report-list.tsx` | 265 | 报告列表(v2-P2-7 Label htmlFor;v2-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 新增:管理员系统设置 CRUD,4 分类 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 | 管理员系统设置 CRUD(P0-3 新增:4 分类 Zod 校验 + upsert) |
|
||
| `actions-security.ts` | 165 | 2FA 状态查询/切换 + 最近登录历史(P2-9 新增) |
|
||
| `actions-service.ts` | 60 | 设置页 Server Action wrapper(v1.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 表 CRUD(P0-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 + useSettingsService(Context 注入服务接口) |
|
||
| `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)。数据结构从 v1(blocks 数组)升级到 v2(nodes + edges 节点图),旧数据通过 `migrateV1ToV2()` 自动迁移。
|
||
|
||
**数据结构**:
|
||
- v1(已废弃,仅向后兼容读取):`{ version: 1, blocks: Block[] }`
|
||
- v2(当前):`{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }`
|
||
- `LessonPlanNode`:`Block` + `position: { x, y }`(画布坐标)
|
||
- `LessonPlanEdge`:`{ id, source, target, sourceHandle?, targetHandle? }`(节点间连线)
|
||
|
||
**导出函数**:
|
||
- Data-access(`data-access.ts`):`getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2`(v1→v2 迁移:blocks 数组转换为 nodes + 线性 edges)/ `normalizeDocument`(规范化:确保 content 为 v2 格式,兼容旧数据)
|
||
- 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`
|
||
|
||
**依赖关系**:
|
||
- 依赖:`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)
|
||
- ✅ 编辑器架构升级:NodeEditor(React 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/fromRfEdges),data-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` 默认空实现,生产环境可替换为真实埋点
|
||
|
||
**文件清单**:
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `types.ts` | 类型定义(含 v1/v2 文档类型、LessonPlanNode、LessonPlanEdge) |
|
||
| `constants.ts` | 常量定义 |
|
||
| `schema.ts` | Zod 验证 |
|
||
| `lib/document-migration.ts` | **纯函数**:v1→v2 迁移(migrateV1ToV2)/ 规范化(normalizeDocument)/ 初始内容构建(buildInitialContent),使用类型守卫 isV1Document/isV2Document 替代 as 断言 |
|
||
| `lib/node-summary.ts` | **纯函数**:getNodeSummary(接受翻译函数注入,支持 i18n)+ NODE_COLORS + getNodeColor |
|
||
| `lib/rf-mappers.ts` | **纯函数**:toRfNodes/toRfEdges/fromRfEdges(LessonPlanNode/Edge ↔ React Flow Node/Edge 映射) |
|
||
| `config/block-registry.tsx` | **配置驱动**:BLOCK_REGISTRY 注册表 + getBlockComponent/isRichTextBlock,node-edit-panel 通过配置渲染 Block |
|
||
| `providers/lesson-plan-provider.tsx` | **Provider + Context(P1-5/P1-7/P2-4/V2-6)**:LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置(TEACHER/ADMIN/STUDENT/PARENT)、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker;hooks:useLessonPlanContextSafe(返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe(V2-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-3;V2-1:抛出 `LessonPlanDataError` 错误码;V2-3:mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫) |
|
||
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理;V2-3:mapRowToVersion 显式映射) |
|
||
| `data-access-templates.ts` | 个人模板 CRUD(V2-3:mapRowToTemplate 显式映射 + 类型守卫) |
|
||
| `data-access-knowledge.ts` | 按知识点/题目反查课案(V2-3:显式字段映射替代 `as unknown as`) |
|
||
| `actions.ts` | 课案 CRUD/版本/模板 Server Actions(V2-1:getTranslations i18n + 错误码捕获;V2-2:createLessonPlanAction 传入 translateTitle 翻译 SYSTEM_TEMPLATES) |
|
||
| `actions-publish.ts` | 发布作业 Server Action(V2-1:getTranslations i18n + PUBLISH_ERROR_KEY_MAP 错误码映射) |
|
||
| `actions-ai.ts` | AI 知识点建议 Server Action(V2-1:i18n + 错误码) |
|
||
| `actions-kp.ts` | 知识点选项 Server Action(V2-1:i18n + 错误码) |
|
||
| `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 操作:addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode) |
|
||
| `components/lesson-plan-list.tsx` | 课案列表(i18n 已接入) |
|
||
| `components/lesson-plan-card.tsx` | 课案卡片(i18n 已接入;V2-6:duplicate/archive 调用 tracker.track) |
|
||
| `components/lesson-plan-filters.tsx` | 课案筛选器(i18n 已接入;V2-5:3 个表单元素 label htmlFor 关联) |
|
||
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel,i18n 已接入;V2-6:handleManualSave 调用 tracker.track) |
|
||
| `components/node-editor.tsx` | **节点图画布**(React Flow,使用 lib/rf-mappers + lib/node-summary 纯函数,i18n 已接入;V2-4:MiniMap 复用 getNodeColor;V2-5:role=application + 键盘导航配置) |
|
||
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**(配置驱动渲染 Block,通过 getBlockComponent + LessonPlanErrorBoundary 包裹,i18n 已接入) |
|
||
| `components/nodes/lesson-node.tsx` | **自定义节点组件**(使用 lib/node-summary 的 getNodeSummary/getNodeColor,i18n 已接入) |
|
||
| `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-6:create 调用 tracker.track) |
|
||
| `components/version-history-drawer.tsx` | 版本历史抽屉(i18n 已接入;V2-6:revert 调用 tracker.track) |
|
||
| `components/knowledge-point-picker.tsx` | 知识点选择器(i18n 已接入) |
|
||
| `components/question-bank-picker.tsx` | 题库选择器(i18n 已接入) |
|
||
| `components/inline-question-editor.tsx` | 内联题目编辑器(i18n 已接入;V2-5:type/difficulty select label htmlFor 关联) |
|
||
| `components/publish-homework-dialog.tsx` | 发布作业对话框(i18n 已接入;V2-6:publish 调用 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.reload,i18n 已接入;V2-5:purpose 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 间隔重复(简化版)**:
|
||
- 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 点
|
||
|
||
**自动采集机制**:
|
||
- `collectFromExamSubmission`:从考试提交记录中筛选得分 < 满分的题目,去重后批量插入
|
||
- `collectFromHomeworkSubmission`:从作业提交记录中筛选错题,去重后批量插入
|
||
- 自动关联知识点(通过 `questionsToKnowledgePoints` 表)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | ~180 | 9 个 Server Actions,全部使用 `requirePermission()` + `ActionState<T>` |
|
||
| `data-access.ts` | ~960 | 16 个数据访问函数 + SM-2 算法实现 + 自动采集逻辑 |
|
||
| `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)
|
||
|
||
---
|
||
|
||
# 第三部分:已知架构问题和技术债
|
||
|
||
## 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-2:shared/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-3:dashboard 跨模块直接查询 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-4:messaging 绕过 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 → notifications(messaging 调用 `sendNotification` dispatcher 发送通知)
|
||
|
||
### P0-5:classSchedule 表三处写入口 ✅ 已修复
|
||
|
||
- ~~`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-6:proctoring 死代码与重复实现
|
||
|
||
- `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-7:proxy.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-8:list-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`~~ ✅ 已实现
|
||
- `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-2:actions 层混入数据访问逻辑 ✅ 已修复
|
||
|
||
**已完成修复**(2026-06-17,commit 84d6636):4 个模块的 actions 层 DB 操作全部下沉到 data-access:
|
||
|
||
| 模块 | 问题 Action | 修复内容 |
|
||
|------|------------|---------|
|
||
| exams | `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` | ✅ 新增 7 个 data-access 函数(getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGrades),actions.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/getKnowledgePointOptions),actions.ts 294→149 行,data-access.ts 138→260 行 |
|
||
| announcements | 所有写操作 Action | ✅ 新增 5 个 data-access 函数(insertAnnouncement/updateAnnouncementById/deleteAnnouncementById/publishAnnouncementById/archiveAnnouncementById),actions.ts 242→197 行,data-access.ts 120→171 行 |
|
||
|
||
**剩余未修复模块**(不在本次 P1-2 范围):
|
||
- ✅ users:~~`updateUserProfileAction` 直接 db.update~~ 已下沉到 data-access(P1-1 修复)
|
||
- ✅ scheduling:~~`applyAutoScheduleAction` / `autoScheduleAction` 直接 db.transaction + db.select~~ 已改为调用 `replaceClassSchedule` 统一写入口;`autoScheduleAction` 直查 users 表已改为通过 users data-access(P0-5 / P1-1 修复)
|
||
|
||
### P1-3:auth.ts 混合 5 类职责 ✅ 已完成
|
||
|
||
`src/auth.ts` 原 293 行混合:NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数。
|
||
|
||
**已完成拆分**(auth.ts 现 193 行,仅保留 NextAuth 配置):
|
||
- ✅ 密码安全 DB 操作 → `shared/lib/password-security-service.ts`(getOrCreatePasswordSecurity / recordFailedLogin / resetFailedLogin,server-only)
|
||
- ✅ 角色规范化 → `shared/lib/role-utils.ts`(normalizeRole / resolvePrimaryRole,纯函数)
|
||
- ✅ bcrypt 哈希规范化 → `shared/lib/bcrypt-utils.ts`(normalizeBcryptHash,纯函数)
|
||
- ✅ IP 解析 → `shared/lib/http-utils.ts`(resolveClientIp,server-only)
|
||
|
||
**后续可选优化**(未执行,需保持 NextAuth 配置不变原则下评估):
|
||
- `authorize` 回调可进一步拆分为 `checkRateLimit` / `checkAccountLockout` / `verifyPassword` / `loadUserRoles`,使 auth.ts 降至 ≤150 行
|
||
|
||
### P1-4:users/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-5:notifications 反向依赖 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.error;conditions 隐式 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-export,4 个页面改为从 `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 获取 session,dynamic 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.ts,classes 模块仅保留读函数)
|
||
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/lib,auth.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 修复:通知表所有权迁移至 notifications,in-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-2,commit 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 实现 |
|
||
|
||
---
|
||
|
||
# 附录 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 | - | - | - | - | - | - | - | ✅scheduling(P0-5:data-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`。
|