P0-1 跨模块直查修复:publish-service 不再直查 examQuestions 表,新增 exams/data-access.addExamQuestions 接口,复用 classes/data-access.getStudentIdsByClassIds P0-2 i18n 接入:新增 zh-CN/en 翻译文件,注册 lessonPreparation 命名空间,17 个组件改造为 useTranslations/getTranslations P1 纯函数抽取:lib/document-migration.ts(类型守卫替代 as 断言)、lib/node-summary.ts(翻译函数注入)、lib/rf-mappers.ts P1 错误边界+骨架屏:新增 LessonPlanErrorBoundary 和 4 个 Skeleton 组件 P1 Block 注册表:新增 config/block-registry.tsx(BlockRenderer 组件),node-edit-panel 重构为配置驱动渲染 P1 其他修复:exercise-block 改用 router.refresh(),node-editor/lesson-node 复用 lib/ 纯函数 架构图同步:更新 004 和 005 文档 Refs: docs/architecture/audit/lesson-preparation-audit-report.md
2038 lines
162 KiB
Markdown
2038 lines
162 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() 提供细粒度权限校验
|
||
```
|
||
|
||
---
|
||
|
||
# 第二部分:模块清单
|
||
|
||
> 每个模块包含:职责 · 导出函数 · 依赖关系 · 已知问题 · 文件清单
|
||
|
||
## 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(10 个,均为写操作;读操作由 RSC 页面直接调用 data-access):`createTextbookAction` / `updateTextbookAction` / `deleteTextbookAction` / `createChapterAction` / `updateChapterContentAction` / `deleteChapterAction` / `reorderChaptersAction` / `createKnowledgePointAction` / `updateKnowledgePointAction` / `deleteKnowledgePointAction`
|
||
- Data-access:`getTextbooks` / `getTextbookById` / `getChaptersByTextbookId` / `getKnowledgePointsByChapterId` / `getKnowledgePointsByTextbookId` / `createTextbook` / `updateTextbook` / `deleteTextbook` / `createChapter` / `updateChapterContent` / `deleteChapter` / `createKnowledgePoint` / `updateKnowledgePoint` / `deleteKnowledgePoint` / `reorderChapters` / `getTextbooksDashboardStats` / `getKnowledgePointOptions`(跨模块接口,供 questions 调用)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`
|
||
- 被依赖:`questions`(✅ P1-1 已修复:通过 textbooks data-access)、`exams`(通过类型)、`dashboard`(通过 data-access,P0-4 已修复)
|
||
- ⚠️ UI 层跨模块依赖:`textbooks/components/knowledge-point-dialogs.tsx` 直接 import `questions/components/create-question-dialog`(P0 待解耦,详见 [textbooks-audit-report.md](audit/textbooks-audit-report.md))
|
||
|
||
**已知问题**:
|
||
- ✅ 无跨模块 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` 直接 import questions 模块组件
|
||
- ⚠️ P0 前端权限硬编码:`canEdit={true}` 按路由写死,未用 `usePermission().hasPermission()`
|
||
- ⚠️ P0 全模块零 i18n:中英文文案硬编码,未接入 next-intl
|
||
- ⚠️ P1 Server Action 未校验资源归属(chapterId 是否属于 textbookId)
|
||
- ⚠️ P1 data-access 缺数据范围过滤(学生端未按年级过滤)
|
||
- ⚠️ P1 缺 Error Boundary(无 error.tsx)
|
||
- ⚠️ P1 知识点列表/弹窗存在重复实现(knowledge-point-panel.tsx 无调用方)
|
||
- ⚠️ P1 学科/年级选项硬编码三处且彼此不一致
|
||
- ⚠️ P1 纯逻辑未导出,零单测
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 317 | 10 个 Server Action(写操作) |
|
||
| `data-access.ts` | 514 | 教材/章节/知识点 CRUD + 跨模块查询接口 |
|
||
| `types.ts` | 45 | 类型定义 |
|
||
| `schema.ts` | 64 | Zod 校验 |
|
||
| `hooks/use-knowledge-point-actions.ts` | 121 | 知识点操作 Hook |
|
||
| `hooks/use-text-selection.ts` | 57 | 文本选区捕获 Hook |
|
||
| `components/*` | 11 文件 | 教材编辑/知识图谱组件 |
|
||
|
||
---
|
||
|
||
## 2.6 grades(成绩模块)— 标杆模块(拆分范例)
|
||
|
||
**职责**:成绩分析(录入/查询/统计/导出/趋势对比分析)。
|
||
|
||
**导出函数**:
|
||
- Actions:`getGradeRecordsAction` / `createGradeRecordAction` / `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction` / `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction`
|
||
- Data-access:`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getRankingTrend`
|
||
- Lib(✅ P1-2 新增):`toNumber` / `normalize` / `buildScopeClassFilter`(从 3 个 data-access 文件抽取的公共工具函数)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`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-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)
|
||
- ✅ P2-2 已修复:~~diagnostic 组件中存在 Tailwind 任意值~~ 改用标准 Tailwind 类
|
||
- ⚠️ P2:统计计算业务逻辑混入 data-access(`getClassGradeStats` / `getGradeDistribution`)
|
||
- ✅ actions 层无直接 DB 访问(标杆)
|
||
- ✅ data-access 按职责拆分为 3 个文件(标杆)
|
||
- ✅ P2 已修复:`export.ts` 中 `scoreMap.get(r.studentId)!` 非空断言清理为安全守卫(`if (!subjMap) continue`)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 359 | 7 个 Server Action(含 Zod 校验) |
|
||
| `actions-analytics.ts` | 175 | 5 个分析 Action(含 Zod 校验) |
|
||
| `data-access.ts` | 361 | 成绩 CRUD + 统计 |
|
||
| `data-access-analytics.ts` | 266 | 趋势/对比分析 |
|
||
| `data-access-ranking.ts` | 96 | 排名查询 |
|
||
| `export.ts` | 214 | Excel 导出 |
|
||
| `schema.ts` | 100 | Zod 校验(含 12 个查询 schema) |
|
||
| `lib/grade-utils.ts` | 46 | 公共工具函数(toNumber/normalize/buildScopeClassFilter) |
|
||
| `types.ts` | - | 类型定义 |
|
||
|
||
---
|
||
|
||
## 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` 中同类非空断言清理
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `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` | 785 | 17 个 Server Action(三组重复,使用 Zod schema 校验) |
|
||
| `schema.ts` | 152 | Zod 校验(13 个 schema:教师/管理员/年级班级 CRUD + 课表 CRUD + 邮箱注册) |
|
||
| `types.ts` | 201 | 类型定义(含跨领域类型污染) |
|
||
|
||
---
|
||
|
||
## 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(展示用,可接受)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 326 | 12 个 Server Action(编排层,无 DB 直访) |
|
||
| `data-access.ts` | 320 | 只读查询 + 12 个写操作(CRUD) |
|
||
| `schema.ts` | 51 | Zod 校验 |
|
||
| `types.ts` | 96 | 类型定义(含 Insert/Update 入参类型) |
|
||
|
||
---
|
||
|
||
## 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`(均接入 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)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `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/admin-dashboard/admin-dashboard.tsx` | 267 | 管理员仪表盘视图(i18n) |
|
||
| `components/admin-dashboard/user-growth-chart.tsx` | 50 | recharts 折线图(i18n) |
|
||
| `components/teacher-dashboard/*.tsx` | 9 文件 | 教师仪表盘组件(i18n) |
|
||
| `components/student-dashboard/*.tsx` | 6 文件 | 学生仪表盘组件(i18n) |
|
||
|
||
---
|
||
|
||
## 2.13 messaging(私信模块)
|
||
|
||
**职责**:站内私信(messages 表 CRUD)。
|
||
|
||
**导出函数**:
|
||
- Actions:`sendMessageAction` / `getMessagesAction` / `getMessageAction` / `deleteMessageAction` / `getNotificationsAction` / `markNotificationReadAction` / `markAllNotificationsReadAction` / `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`
|
||
- 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)(通知 CRUD 通过 re-export 从 notifications 模块重导出,保持向后兼容)
|
||
- 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 模块)、`classes`(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID,支持学生 class_members 和家长 children 数据范围)、`users`(通过 data-access.getUserNamesByIds 获取用户显示名称)
|
||
- 被依赖:`notifications`(✅ 已消除反向依赖)、`settings`(通知偏好表单)、`layout`(通知下拉)
|
||
|
||
**已知问题**:
|
||
- ✅ 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()`~~ 5 个通知 Action(getNotifications / markNotificationAsRead / markAllNotificationsAsRead / getNotificationPreferences / updateNotificationPreferences)已改为 `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` / `notification-dropdown.tsx` 添加 `aria-label` / `aria-hidden`
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 276 | 9 个 Server Action(通知相关 Action 委托 notifications 模块) |
|
||
| `data-access.ts` | 199 | 私信 CRUD + re-export 通知 CRUD(向后兼容) |
|
||
| ~~`notification-preferences.ts`~~ | ~~11~~ | ~~re-export shim~~ ✅ P0-b 已删除(消费方改为直接从 notifications/preferences 导入) |
|
||
| `schema.ts` | 41 | 私信发送校验 + messageId 校验 + 通知偏好更新校验 |
|
||
| `types.ts` | 72 | 私信类型 + re-export 通知类型(向后兼容) |
|
||
|
||
---
|
||
|
||
## 2.14 notifications(通知分发模块)
|
||
|
||
**职责**:多渠道通知分发(SMS/Email/WeChat/InApp)+ 站内通知 CRUD + 通知偏好管理。
|
||
|
||
**导出函数**:
|
||
- Actions:`sendNotificationAction` / `sendClassNotificationAction`
|
||
- 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`
|
||
|
||
**依赖关系**:
|
||
- 依赖:`shared/*`、`@/auth`、`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getStudentIdsByClassId)
|
||
- 被依赖:`messaging`(✅ P0-4 / P1-5 已修复:messaging 通过 `sendNotification` dispatcher 发送通知,通知 CRUD 和偏好通过 re-export 保持向后兼容)
|
||
|
||
**已知问题**:
|
||
- ✅ 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:发送日志仅 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` | 119 | 2 个 Server Action |
|
||
| `types.ts` | 120 | 通知负载 + 渠道配置 + 通知记录 + 偏好类型(P0-4 / P1-5 修复后扩充) |
|
||
| `index.ts` | 61 | 对外导出入口 |
|
||
| `channels/*` | 5 文件 | 4 个渠道实现 |
|
||
|
||
---
|
||
|
||
## 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 新增)
|
||
|
||
**依赖关系**:
|
||
- 依赖:`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`
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | ~270 | 6 个 Server Action + 通知触发逻辑(P1-2 已修复,无直接 DB 操作) |
|
||
| `data-access.ts` | ~190 | 公告 CRUD + 发布/归档 + 受众过滤(含 P1-2 新增 5 个写函数) |
|
||
| `schema.ts` | - | Zod 校验 |
|
||
| `types.ts` | - | 类型定义(`GetAnnouncementsParams` 新增 `audience` 字段) |
|
||
|
||
---
|
||
|
||
## 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/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:`generateStudentDiagnosticReportAction` / `generateClassDiagnosticReportAction` / `publishDiagnosticReportAction` / `deleteDiagnosticReportAction` / `getDiagnosticReportsAction` / `getStudentMasteryAction`
|
||
- Data-access:`updateMasteryFromSubmission` / `getStudentMastery` / `getClassMasteryOverview`
|
||
- Data-access-reports:`createDiagnosticReport` / `getDiagnosticReport` / `getDiagnosticReports` / `deleteDiagnosticReport` / `publishDiagnosticReport`(✅ P2 已修复:`getDiagnosticReports` 和 `getDiagnosticReportById` 使用 `React.cache()` 包装实现请求级 memoization)
|
||
- Schema:`GenerateStudentReportSchema` / `GenerateClassReportSchema` / `PublishReportSchema` / `DeleteReportSchema` / `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)、`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId)
|
||
- 被依赖:无
|
||
|
||
**已知问题**:
|
||
- ✅ P1-1 已修复:~~`updateMasteryFromSubmission` 跨模块直查 4 张表(与 exams/homework/questions 紧耦合)~~ 改为调用 `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` 串行查询(className → studentIds → userMap → masteryRows)~~ 改为两组 `Promise.all` 并行(className+studentIds,userMap+masteryRows)
|
||
- ✅ P2 已修复:~~`getDiagnosticReports` 中 `conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注
|
||
- ⚠️ P2:班级报告将生成者 ID 存入 `studentId` 字段(schema 设计缺陷 workaround)
|
||
- ✅ 与 grades 模块无职责重叠(grades 管分数,diagnostic 管知识点掌握度)
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `data-access.ts` | 254 | 知识点掌握度查询 + 更新 |
|
||
| `data-access-reports.ts` | 202 | 诊断报告 CRUD |
|
||
| `actions.ts` | 172 | 6 个 Server Action(使用 Zod schema 校验) |
|
||
| `schema.ts` | 56 | Zod 校验(6 个 schema:生成/发布/删除/查询报告) |
|
||
| `types.ts` | 97 | 类型定义 |
|
||
| `components/*` | 4 文件 | 学生/班级诊断视图 + 雷达图 |
|
||
|
||
---
|
||
|
||
## 2.23 settings(设置模块)
|
||
|
||
**职责**:系统设置(学校信息/安全策略/文件上传/通知配置)+ AI Provider 管理 + 密码修改 + 个人资料 + 主题偏好 + 通知偏好 + 个人信息页(学生/教师概览)。
|
||
|
||
**导出函数**:
|
||
- Actions:`getAiProvidersAction` / `createAiProviderAction` / `updateAiProviderAction` / `deleteAiProviderAction` / `testAiProviderAction`
|
||
- Actions-password:`changePasswordAction`(✅ P1 已修复:使用 `requirePermission(USER_PROFILE_UPDATE)` + Zod 校验 + DB 操作下沉到 data-access)
|
||
- Data-access:`getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange`(P1 新增,从 actions 下沉)
|
||
- 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`(系统设置视图,4 个 Card)
|
||
- 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` 权限校验
|
||
|
||
**文件清单**:
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 160 | AI Provider CRUD + 测试(P1 已修复,无直接 DB 操作) |
|
||
| `actions-password.ts` | 87 | 修改密码(P1 已修复:requirePermission + Zod + data-access) |
|
||
| `data-access.ts` | 158 | AI Provider CRUD + 密码修改 DB 操作(P1 新增) |
|
||
| `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) |
|
||
| `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` | 195 | AdminSettingsView 系统设置视图(4 个 Card,i18n) |
|
||
| `components/profile-settings-form.tsx` | 158 | 个人资料表单(通过 SettingsService 注入,i18n) |
|
||
| `components/notification-preferences-form.tsx` | ~140 | 通知偏好表单(通过 SettingsService 注入,i18n) |
|
||
| `components/password-change-form.tsx` | ~130 | 密码修改表单(i18n + a11y) |
|
||
| `components/theme-preferences-card.tsx` | ~60 | 主题偏好卡片(i18n) |
|
||
| `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`
|
||
> - **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 错误边界 + 骨架屏**:新增 `components/lesson-plan-error-boundary.tsx`(LessonPlanErrorBoundary 类组件)和 `components/lesson-plan-skeleton.tsx`(VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton)
|
||
> - **P1 Block 注册表**:新增 `config/block-registry.tsx`(BLOCK_REGISTRY 配置表 + getBlockComponent/isRichTextBlock),`node-edit-panel.tsx` 重构为配置驱动渲染,移除 if/else 链
|
||
> - **P1 window.location.reload 修复**:`exercise-block.tsx` 改用 `router.refresh()` 精确刷新缓存
|
||
|
||
**文件清单**:
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `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 |
|
||
| `data-access.ts` | 课案 CRUD + 模板查询(migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容) |
|
||
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理) |
|
||
| `data-access-templates.ts` | 个人模板 CRUD |
|
||
| `data-access-knowledge.ts` | 按知识点/题目反查课案 |
|
||
| `actions.ts` | 课案 CRUD/版本/模板 Server Actions |
|
||
| `actions-publish.ts` | 发布作业 Server Action |
|
||
| `actions-ai.ts` | AI 知识点建议 Server Action |
|
||
| `actions-kp.ts` | 知识点选项 Server Action |
|
||
| `publish-service.ts` | 发布作业服务(编排 homework/exams/classes,通过对方 data-access 调用,无直查跨模块表) |
|
||
| `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 已接入) |
|
||
| `components/lesson-plan-filters.tsx` | 课案筛选器(i18n 已接入) |
|
||
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel,i18n 已接入) |
|
||
| `components/node-editor.tsx` | **节点图画布**(React Flow,使用 lib/rf-mappers + lib/node-summary 纯函数,i18n 已接入) |
|
||
| `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 已接入) |
|
||
| `components/version-history-drawer.tsx` | 版本历史抽屉(i18n 已接入) |
|
||
| `components/knowledge-point-picker.tsx` | 知识点选择器(i18n 已接入) |
|
||
| `components/question-bank-picker.tsx` | 题库选择器(i18n 已接入) |
|
||
| `components/inline-question-editor.tsx` | 内联题目编辑器(i18n 已接入) |
|
||
| `components/publish-homework-dialog.tsx` | 发布作业对话框(i18n 已接入) |
|
||
| `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 已接入) |
|
||
| `components/blocks/reflection-block.tsx` | 反思 Block(被 NodeEditPanel 复用,i18n 已接入) |
|
||
|
||
---
|
||
|
||
# 第三部分:已知架构问题和技术债
|
||
|
||
## 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` 暂保留原位(集成到考试表单属于功能新增,不在本次修复范围)
|
||
|
||
---
|
||
|
||
## 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 | 其他 |
|
||
|----------|--------|------|-------|----------|-----------|-----------|---------|--------|-----------|-------|--------|-----------|---------------|-------------|------|
|
||
| **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 |
|
||
|
||
---
|
||
|
||
# 附录 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<GradeRecord[]>>
|
||
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 }): Promise<GradeRecord[]>
|
||
getStudentGradeSummary(studentId: string): Promise<StudentGradeSummary>
|
||
getClassRanking(classId: string, examId?: string): Promise<ClassRanking[]>
|
||
|
||
// 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`。
|