# Next_Edu 架构影响地图

> 重写日期：2026-06-17
> 目标：一次阅读即可直观理解整个项目架构
> 规则：源码修改后须同步更新本文档与 `005_architecture_data.json`
> 审查依据：`docs/architecture/audit/` 下 4 份审查报告

---

## 目录

- [第一部分：全局架构概览](#第一部分全局架构概览)
  - [1.1 分层架构图](#11-分层架构图)
  - [1.2 模块依赖关系图](#12-模块依赖关系图)
  - [1.3 数据流向图（考试流程）](#13-数据流向图考试流程)
  - [1.4 核心调用链路](#14-核心调用链路)
- [第二部分：模块清单](#第二部分模块清单)
- [第三部分：已知架构问题和技术债](#第三部分已知架构问题和技术债)
- [附录 A：模块间依赖矩阵](#附录-a模块间依赖矩阵)
- [附录 B：关键参数影响链](#附录-b关键参数影响链)
- [附录 C：核心函数签名索引](#附录-c核心函数签名索引)

---

# 第一部分：全局架构概览

## 1.1 分层架构图

项目采用**严格三层架构**，依赖方向必须单向：`app → modules → shared`。

```
┌─────────────────────────────────────────────────────────────────────┐
│  app/ (路由层)                                                       │
│  ─────────────────────────────────────────────────────────────────  │
│  (auth)/          login / register / privacy / terms                │
│  (dashboard)/     admin / teacher / student / parent / management   │
│  api/             REST API (auth, ai, upload, files, search, ...)   │
└──────────────────────────────┬──────────────────────────────────────┘
                               │ 调用 Server Actions / data-access
                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│  modules/ (业务模块层)                                               │
│  ─────────────────────────────────────────────────────────────────  │
│  核心教学：  exams · homework · questions · textbooks · grades       │
│              · lesson-preparation                                     │
│  教学管理：  classes · school · scheduling · attendance · course-plans│
│  用户与沟通：users · messaging · notifications · parent · audit      │
│  扩展功能：  elective · proctoring · diagnostic · dashboard          │
│  其他：      announcements · files · settings · auth · layout · student│
│                                                                     │
│  每个模块标准结构：actions.ts (编排) → data-access.ts (DB) → schema.ts│
└──────────────────────────────┬──────────────────────────────────────┘
                               │ 依赖
                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│  shared/ (基础设施层)                                                │
│  ─────────────────────────────────────────────────────────────────  │
│  db/              schema.ts (54 张表) + relations.ts + index.ts     │
│  lib/             auth-guard · permissions · ai · audit-logger ·    │
│                   change-logger · login-logger · password-policy ·  │
│                   rate-limit · excel · file-storage · ...           │
│  hooks/           use-permission · use-aria-live · ...              │
│  components/      ui/ (shadcn) · a11y/ · global-search · ...        │
│  types/           permissions · action-state                        │
└─────────────────────────────────────────────────────────────────────┘
                               ▲
                               │ 反向依赖（违规，见 1.2）
┌──────────────────────────────┴──────────────────────────────────────┐
│  根模块：src/auth.ts (NextAuth 配置) · src/proxy.ts (中间件)         │
│  ✅ shared/lib/{audit-logger, change-logger, auth-guard} 已通过       │
│     shared/lib/session.ts 单一入口获取 session，不再直依赖 @/auth     │
│     （详见第三部分 P0-2）                                              │
└─────────────────────────────────────────────────────────────────────┘
```

**分层规则**：
- `app/` 只能调用 `modules/` 的 Server Actions 和 data-access，不直接访问 DB
- `modules/` 之间通过对方 data-access 通信，不直接查询对方表
- `shared/` 是被依赖方，不应反向依赖 `app/` 或 `modules/`
- `src/auth.ts` 和 `src/proxy.ts` 位于根目录，属于应用层

---

## 1.2 模块依赖关系图

下图展示模块间的实际依赖关系，**标注依赖类型与合规性**。

### 图例

- `───▶` 合理依赖（通过 data-access 或类型导入）
- `═══▶` 违规依赖（直接查询对方 DB 表）
- `⟳  ` 循环依赖
- `──▷` UI 组件组合（合理）

### 1.2.1 核心业务模块依赖

```
                    ┌──────────────┐
                    │  textbooks   │ ◀── 标杆模块（无跨模块 DB 访问）
                    └──────┬───────┘
                           │ ──▷ UI 组合（knowledge-point-dialogs）
                           │
            ┌──────────────┼──────────────┐
            │              │              │
            ▼              ▼              ▼
     ┌────────────┐  ┌──────────┐  ┌────────────┐
     │ questions  │  │  exams   │  │  homework  │
     └─────┬──────┘  └────┬─────┘  └─────┬──────┘
           │ ✅ P1-1      │ ✅ P1-1      │ ✅ P1-1 已修复
           │ 通过 textbooks│ 通过 questions│ 通过 exams/classes/
           │ data-access  │ data-access  │   school/users data-access
           │              │ 通过 classes  │
           │              │ data-access   │
           ┌─┴────────┐     │              │
           │ grades   │◀────┘ 仅外键引用（合理）
           │ (成绩)   │
           └────┬─────┘
                │ ✅ P1-1 已修复
                │ 通过 classes/school/users data-access
                ▼
           ┌──────────┐
           │ classes  │ ✅ P1-1 已修复：通过 homework/data-access-classes
           └────┬─────┘   获取作业数据，不再直查 homework/exams 表
                │ ✅ P0-1 已修复：data-access.ts 已拆分为 5 文件
                │ ✅ P0-5 已修复：classSchedule 写入口统一到 scheduling
                │
                    ┌─────────┼─────────┐
                    │         │         │
                    ▼         ▼         ▼
              ┌──────────┐ ┌──────┐ ┌──────────┐
              │scheduling│ │school│ │ attendance│
              └────┬─────┘ └──────┘ └──────────┘
                   │ ✅ P0-5 已修复
                   │ classSchedule 写入口统一到 scheduling
                   │ 通过 classes/data-access 校验归属
                   │ ✅ P1-1 已修复：通过 users data-access
```

### 1.2.2 扩展模块依赖

```
┌─────────────┐  ✅ P0-3 已修复：通过各模块 data-access    ┌──────────────┐
│ dashboard   │  获取数据（getUsersDashboardStats 等）       │ users/classes│
│ (聚合层)    │──────────────────────────────────────▶│ /exams/...   │
└─────────────┘  ✅ P0-4 已修复：Promise.all 并行调用     └──────────────┘

┌─────────────┐  ─── 调用 data-access（合理）   ┌──────────────┐
│ parent      │──────────────────────────────▶│ classes/      │
│ (聚合层)    │  ✅ P1-1 已修复：通过各模块     │ homework/grades│
└─────────────┘   data-access 获取数据          └──────────────┘

┌─────────────┐  ✅ P1-1 已修复：通过 exams/questions/      ┌──────────────┐
│ diagnostic  │  classes/users data-access 获取数据          │ exams/questions│
│             │──────────────────────────────────────▶│ /classes/users│
└─────────────┘                                       └──────────────┘

┌─────────────┐  ✅ P1-1 已修复：通过 exams/users           ┌──────────────┐
│ proctoring  │  data-access 获取数据                       │ exams/users   │
└─────────────┘──────────────────────────────────────▶└──────────────┘

┌─────────────┐  ─── 调用 notifications dispatcher     ┌──────────────┐
│ messaging   │  （通知偏好/CRUD 已迁移至 notifications）│ notifications │
│             │──────────────────────────────────────▶│ (拥有         │
│             │  ✅ P0-4 / P1-5 已修复：单向依赖       │  messageNotif│
└─────────────┘                                       │  ications +  │
                                                      │  preferences)│
                                                      └──────────────┘

┌─────────────┐  ─── 调用 messaging Action       ┌──────────────┐
│ settings    │  （通知偏好表单）                 │ messaging     │
└─────────────┘──────────────────────────────▶└──────────────┘

┌─────────────────┐  ───▶ data-access（合理）       ┌──────────────┐
│ lesson-prep     │──────────────────────────────▶│ textbooks    │
│ (备课聚合层)    │  只读章节/知识点树              │ (章节/KP 树) │
│                 │──────────────────────────────▶└──────────────┘
│                 │──────────────────────────────▶┌──────────────┐
│                 │  创建/查询题目                  │ questions    │
│                 │──────────────────────────────▶└──────────────┘
│                 │──────────────────────────────▶┌──────────────┐
│                 │  创建 exam 草稿                │ exams        │
│                 │──────────────────────────────▶└──────────────┘
│                 │──────────────────────────────▶┌──────────────┐
│                 │  创建作业下发                  │ homework     │
│                 │──────────────────────────────▶└──────────────┘
│                 │──────────────────────────────▶┌──────────────┐
│                 │  查询教师班级                  │ classes      │
│                 │──────────────────────────────▶└──────────────┘
│                 │──────────────────────────────▶┌──────────────┐
│                 │  附件                          │ files        │
└─────────────────┘──────────────────────────────▶└──────────────┘
```

### 1.2.3 循环依赖详情 ✅ 已修复

```
shared/lib/audit-logger.ts   ──┐
shared/lib/change-logger.ts  ──┼──▶ shared/lib/session.ts ──▶ (dynamic import) @/auth
shared/lib/auth-guard.ts     ──┘

src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
              ──▶ import { ... } from "@/shared/lib/login-logger"
              ──▶ import { ... } from "@/shared/lib/password-policy"
              ──▶ import { ... } from "@/shared/lib/rate-limit"
              ──▶ import { ... } from "@/shared/lib/role-utils"        # P1-3 拆出
              ──▶ import { ... } from "@/shared/lib/bcrypt-utils"      # P1-3 拆出
              ──▶ import { ... } from "@/shared/lib/http-utils"        # P1-3 拆出
              ──▶ import { ... } from "@/shared/lib/password-security-service"  # P1-3 拆出
              ──▶ import { db, schema } from "@/shared/db"

  ✅ 修复：shared/lib/* 不再静态 import @/auth，统一通过 session.ts 单一入口
     session.ts 内部使用 dynamic import("@/auth") 打破模块级静态循环
     运行时调用链保持不变，模块加载图无环
```

---

## 1.3 数据流向图（考试流程）

以"考试流程"为例，展示数据从创建到成绩统计的完整流向。

```
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 1：教师创建考试                                                 │
│ ─────────────────────────────────────────────────────────────────  │
│ teacher/exams/create/page.tsx                                       │
│   └─▶ exams/actions.createExamAction                                │
│         ├─▶ requirePermission(EXAM_CREATE)  [shared/auth-guard]     │
│         ├─▶ persistExamDraft()              [exams/data-access]     │
│         │     └─▶ db.insert(exams)           [shared/db]            │
│         │     └─▶ db.insert(examQuestions)   [shared/db]            │
│         └─▶ revalidatePath("/teacher/exams")                        │
│                                                                     │
│ 数据写入：exams 表 + examQuestions 表                                │
│ ✅ P0-1 已修复：persistAiGeneratedExamDraft 改为调用 questions/data-access.createQuestionWithRelations，不再直查 questions 表 │
└─────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 2：学生作答（作业化考试）                                        │
│ ─────────────────────────────────────────────────────────────────  │
│ student/learning/assignments/[assignmentId]/page.tsx                │
│   └─▶ homework/actions.startHomeworkSubmissionAction                │
│         ├─▶ requirePermission(HOMEWORK_SUBMIT)                      │
│         ├─▶ data-access-write.startHomeworkSubmission  ✅ P1-2 已修复 │
│         │     └─▶ db.insert(homeworkSubmissions)  [shared/db]       │
│         └─▶ 返回 submissionId                                       │
│                                                                     │
│   └─▶ homework/actions.saveHomeworkAnswerAction                     │
│         └─▶ data-access-write.saveHomeworkAnswer  ✅ P1-2 已修复     │
│              └─▶ db.transaction(insert homeworkAnswers) [shared/db] │
│                                                                     │
│   └─▶ homework/actions.submitHomeworkAction                         │
│         └─▶ data-access-write.submitHomework  ✅ P1-2 已修复         │
│              └─▶ db.update(homeworkSubmissions.status="submitted")  │
│                                                                     │
│ 数据写入：homeworkSubmissions 表 + homeworkAnswers 表                │
│ ✅ P1-2 已修复：actions 层不再直接 DB 操作，已下沉到 data-access-write│
└─────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 3：教师批改                                                     │
│ ─────────────────────────────────────────────────────────────────  │
│ teacher/homework/submissions/[submissionId]/page.tsx                │
│   └─▶ homework/actions.gradeHomeworkSubmissionAction                │
│         ├─▶ requirePermission(HOMEWORK_GRADE)                       │
│         └─▶ data-access-write.gradeHomeworkSubmission  ✅ P1-2 已修复│
│              └─▶ db.update(homeworkAnswers) 循环  [shared/db]       │
│                                                                     │
│ 数据更新：homeworkAnswers.isCorrect / score / feedback               │
└─────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 阶段 4：成绩统计与诊断                                               │
│ ─────────────────────────────────────────────────────────────────  │
│ teacher/grades/page.tsx                                             │
│   └─▶ grades/data-access.getGradeRecords                            │
│         └─▶ ✅ P1-1 已修复：通过 classes/school/users data-access    │
│                                                                     │
│ teacher/diagnostic/page.tsx                                         │
│   └─▶ diagnostic/data-access.updateMasteryFromSubmission            │
│         ├─▶ ✅ P1-1 已修复：通过 exams data-access 获取提交          │
│         ├─▶ ✅ P1-1 已修复：通过 questions data-access 获取知识点    │
│         └─▶ 更新 knowledgePointMastery                              │
│                                                                     │
│ 数据读取：homeworkSubmissions → grades → knowledgePointMastery        │
└─────────────────────────────────────────────────────────────────────┘
```

**关键观察**：考试流程横跨 4 个模块（exams → homework → grades → diagnostic），✅ P1-1 已修复：所有跨模块查询已改为通过对方 data-access 接口，模块封装性已恢复。

---

## 1.4 核心调用链路

### 1.4.1 调用链路：创建考试（含 AI 出题）

```
[Client] exam-form.tsx
   │ FormData
   ▼
[Route]  POST /teacher/exams/create (Server Action)
   │
   ▼
[Action] exams/actions.createAiExamAction
   │
   ├─▶ requirePermission(EXAM_CREATE)
   │     └─▶ shared/lib/auth-guard.getAuthContext()
   │           ├─▶ auth()  [src/auth.ts]
   │           ├─▶ db.query.usersToRoles  [shared/db]
   │           ├─▶ db.query.classSubjectTeachers
   │           └─▶ 返回 { userId, roles, permissions, dataScope }
   │
   ├─▶ generateAiCreateDraftFromSource()
   │     └─▶ exams/ai-pipeline.ts (912 行)
   │           ├─▶ shared/lib/ai.createAiChatCompletion()
   │           │     └─▶ OpenAI SDK + db.query.aiProviders
   │           └─▶ JSON 解析 + Zod 校验
   │
   ├─▶ persistAiGeneratedExamDraft()
   │     └─▶ exams/data-access.ts
   │           ├─▶ db.insert(exams)              ✅ 合理
   │           ├─▶ db.insert(examQuestions)      ✅ 合理
   │           └─▶ questions/data-access.createQuestionWithRelations  ✅ P0-1 已修复：通过 data-access
   │
   └─▶ revalidatePath("/teacher/exams")
```

### 1.4.2 调用链路：学生提交作业

```
[Client] homework-take-view.tsx
   │
   ▼
[Action] homework/actions.submitHomeworkAction
   │
   ├─▶ requirePermission(HOMEWORK_SUBMIT)
   │
   ├─▶ data-access-write.submitHomework  ✅ P1-2 已修复
   │     （校验 submission 归属 + 更新状态）
   │     └─▶ db.update(homeworkSubmissions)
   │           SET status = "submitted", submittedAt = now()
   │
   └─▶ 返回 ActionState<{ submissionId }>
```

### 1.4.3 调用链路：管理员仪表盘聚合

```
[Route]  /admin/dashboard/page.tsx (Server Component)
   │
   ▼
[DataAccess] dashboard/data-access.getAdminDashboardData
   │
   ├─▶ users/data-access.getUsersDashboardStats()           ✅ 通过模块 data-access
   │     ├─ userCount / activeSessionsCount / userRoleCounts
   │     └─ recentUsers (含角色解析)
   ├─▶ classes/data-access.getClassesDashboardStats()       ✅ 通过模块 data-access
   │     └─ classCount
   ├─▶ textbooks/data-access.getTextbooksDashboardStats()   ✅ 通过模块 data-access
   │     └─ textbookCount / chapterCount
   ├─▶ questions/data-access.getQuestionsDashboardStats()   ✅ 通过模块 data-access
   │     └─ questionCount
   ├─▶ exams/data-access.getExamsDashboardStats(scope?)     ✅ 通过模块 data-access
   │     └─ examCount (含 scope 过滤)
   └─▶ homework/stats-service.getHomeworkDashboardStats(scope?)  ✅ 通过模块 data-access
         ├─ homeworkAssignmentCount / homeworkAssignmentPublishedCount
         └─ homeworkSubmissionCount / homeworkSubmissionToGradeCount

   ✅ P0-4 已修复：dashboard 改为并行调用各模块 dashboard stats 函数，不再直查跨模块表
```

### 1.4.4 调用链路：admin 路由组统一权限守卫

```
[Layout]  app/(dashboard)/admin/layout.tsx (Server Component)
   │
   ▼
[AuthGuard] shared/lib/auth-guard.getAuthContext()
   │
   └─▶ getSession() → 校验已登录（未登录抛 PermissionDeniedError）
   │
   ▼
[Children] 各 admin/* 页面（page.tsx）在函数体首行调用 requirePermission(XXX)
   │
   ├─▶ /admin/school/schools         → requirePermission(SCHOOL_MANAGE)
   ├─▶ /admin/school/academic-year   → requirePermission(SCHOOL_MANAGE)
   ├─▶ /admin/school/classes         → requirePermission(SCHOOL_MANAGE)
   ├─▶ /admin/school/departments     → requirePermission(SCHOOL_MANAGE)
   ├─▶ /admin/school/grades          → requirePermission(SCHOOL_MANAGE)
   ├─▶ /admin/school/grades/insights → requirePermission(SCHOOL_MANAGE)
   ├─▶ /admin/users/import           → requirePermission(USER_MANAGE)
   ├─▶ /admin/users                  → requirePermission(USER_MANAGE)
   ├─▶ /admin/scheduling/auto        → requirePermission(SCHEDULE_AUTO)
   ├─▶ /admin/scheduling/changes     → requirePermission(SCHEDULE_ADJUST)
   ├─▶ /admin/scheduling/rules       → requirePermission(SCHEDULE_ADJUST)
   ├─▶ /admin/announcements          → requirePermission(ANNOUNCEMENT_MANAGE)
   ├─▶ /admin/announcements/[id]     → requirePermission(ANNOUNCEMENT_MANAGE)
   └─▶ /admin/audit-logs             → requirePermission(AUDIT_LOG_READ)

   ✅ P0 安全修复：admin/layout.tsx 提供登录态统一守卫，
      各页面 requirePermission() 提供细粒度权限校验

   teacher/classes/* 路由权限校验（2026-06-22 审计修复）：
   ├─▶ /teacher/classes/my           → requirePermission(CLASS_READ)  ✅ 已修复
   ├─▶ /teacher/classes/my/[id]      → requirePermission(CLASS_READ)  ✅ 已修复
   ├─▶ /teacher/classes/schedule     → requirePermission(CLASS_READ)  ✅ 已修复
   └─▶ /teacher/classes/students     → requirePermission(CLASS_READ)  ✅ 已修复
```

---

# 第二部分：模块清单

> 每个模块包含：职责 · 导出函数 · 依赖关系 · 已知问题 · 文件清单

## 2.1 shared（基础设施层）

**职责**：提供全项目共享的 DB Schema、工具函数、权限系统、UI 基础组件、通用 Hooks。

**导出函数**（核心）：
- `getAuthContext()` / `requirePermission(p)` / `requireAuth()` — 认证与权限
- `resolvePermissions(roles)` / `resolveDataScope(userId, roles)` — 权限解析
- `logAudit()` / `logLoginEvent()` / `logDataChange()` — 日志记录
- `createAiChatCompletion()` / `parseAiChatPayload()` — AI 调用
- `validatePassword()` / `isAccountLocked()` / `rateLimit()` — 安全策略
- `exportToExcel()` / `parseExcel()` / `generateTemplate()` — Excel 工具
- `cn()` / `formatDate()` / `formatFileSize()` — 通用工具
- `getInitials(name)` / `formatDateForFile(d?)` — 通用工具（P1-c / P1-a 重构新增：从 parent/lib/utils.ts、grades/export-button.tsx 等多处重复实现抽取）
- `downloadBase64File(base64, filename, mimeType?)` / `downloadBlob(blob, filename)` — 客户端文件下载（P1-c 重构新增：从 grades/export-button、users/user-import-dialog、audit/audit-log-export-button 三处重复实现抽取，位于 `lib/download.ts`）

**共享组件导出**（P0-b / P1-a / P1-b / P1-c / P2-a / P2-b / P3-a / P3-b / P3-c / P3-d / 第二轮 P0-1/P0-2/P0-3/P1-1/P1-2/P1-3/P1-4 重构新增，按类别组织）：

| 类别 | 组件 | 文件 | 用途 | 消费方数量 |
|------|------|------|------|-----------|
| **UI 组件** | `StatCard` | `components/ui/stat-card.tsx` | 统计卡片（标题+数值+图标+描述+跳转+骨架屏） | 8 个（P1-a） |
| **UI 组件** | `StatItem` | `components/ui/stat-item.tsx` | 紧凑统计项（label+icon+value+hint，用于统计面板网格） | 8 个（P1-a） |
| **UI 组件** | `ChipNav` | `components/ui/chip-nav.tsx` | 芯片导航组（通过 URL search params 切换筛选维度，Link 跳转） | 3 个（P1-b） |
| **UI 组件** | `PageHeader` | `components/ui/page-header.tsx` | 页面头部（标题+描述+icon+actions，响应式布局） | 2 个（P2-b: profile/page.tsx, settings/security/page.tsx） |
| **UI 组件** | `FilterBar` / `FilterSearchInput` / `FilterResetButton` | `components/ui/filter-bar.tsx` | 筛选栏容器+搜索框+重置按钮（统一布局壳，URL 状态由各模块处理） | 5 个（P3-b: exam/textbook/question/audit-log/login-log filters） |
| **UI 组件** | `ConfirmDeleteDialog` | `components/ui/confirm-delete-dialog.tsx` | 通用删除确认对话框（AlertDialog 包装，支持自定义 confirmText/cancelText） | 5 个（P0-1: announcement-detail, message-detail, course-plan-detail, grade-classes-view, students-table） |
| **UI 组件** | `Pagination` | `components/ui/pagination.tsx` | 通用分页 UI（Showing X-Y of Z + Page X of Y + 上一页/下一页按钮） | 3 个（P0-2: audit-log-table, login-log-table, data-change-log-table） |
| **UI 组件** | `EmptyTableRow` | `components/ui/empty-table-row.tsx` | 表格空状态行（TableRow + TableCell 居中显示空状态文案） | 3 个（P0-3: audit-log-table, login-log-table, data-change-log-table） |
| **UI 组件** | `StatusBadge` | `components/ui/status-badge.tsx` | 通用状态徽章（Badge + 状态→variant/label/className 映射表，修复 in_progress 颜色不一致 bug） | 9+ 个（P1-1: audit 3 文件, grades 2 文件, student/learning/assignments, parent/child-homework-summary, student-upcoming-assignments-card, question-columns） |
| **表单字段** | `TextField` | `components/form-fields/text-field.tsx` | 通用文本字段（FormField + Input 包装，支持 text/number/password/datetime-local 类型 + value 转换器） | 3 个文件 16 处（P1-2: profile-settings-form 6, exam-basic-info-form 4, ai-provider-settings-card 4） |
| **表单字段** | `SelectField` | `components/form-fields/select-field.tsx` | 通用选择字段（FormField + Select 包装，支持 toSelectValue/fromSelectValue 处理 number↔string） | 4 个文件 8 处（P1-2: exam-basic-info-form 3, ai-provider-settings-card 1, create-question-dialog 2, profile-settings-form 1） |
| **表单字段** | `TextareaField` | `components/form-fields/textarea-field.tsx` | 通用多行文本字段（FormField + Textarea 包装） | 1 个（P1-2: create-question-dialog） |
| **图表组件** | `ChartCardShell` | `components/charts/chart-card-shell.tsx` | 图表卡片外壳（Card+Header+EmptyState+Content 统一结构） | 8 个（P3-c） |
| **图表组件** | `TrendLineChart` | `components/charts/trend-line-chart.tsx` | 趋势折线图（LineChart 统一配置，支持单/多系列） | 8 个（P3-c: grade-trend-chart 等） |
| **图表组件** | `SimpleBarChart` | `components/charts/simple-bar-chart.tsx` | 柱状图（BarChart 统一配置，支持单/多 Bar + Cell 分桶着色） | 8 个（P3-c: grade-distribution-chart 等） |
| **图表组件** | `ComparisonRadarChart` | `components/charts/comparison-radar-chart.tsx` | 对比雷达图（RadarChart 统一配置，支持双 Radar 对比） | 8 个（P3-c: subject-comparison-chart, mastery-radar-chart 等） |
| **课表组件** | `ScheduleList` / `ScheduleListItem` | `components/schedule/schedule-list.tsx` | 课表列表+列表项（课程+时间+地点+班级徽章，separator/card 两种变体） | 3 个（P3-a: student-today-schedule-card, child-schedule-card, student-schedule-view） |
| **题库组件** | `QuestionBankFilters` | `components/question/question-bank-filters.tsx` | 题库筛选栏（搜索+题型+难度，default/compact 两种布局） | 2 个（P3-d: exam-assembly, question-bank-picker） |
| **设置组件** | `SettingsView` | `modules/settings/components/settings-view.tsx` | 统一设置页布局（5 标签页：General/Notifications/Appearance/Security/AI，角色差异通过 props 注入，Tab URL 持久化，登出二次确认） | 4 个（P2-a: admin/teacher/student/parent 设置页） |

**共享 Hooks 导出**（第二轮 P1-4 重构新增）：

| Hook | 文件 | 签名 | 用途 | 消费方 |
|------|------|------|------|--------|
| `useActionMutation` | `hooks/use-action-mutation.ts` | `useActionMutation<T>(options?): { isWorking, mutate }` | 通用 Server Action mutation Hook，替代 50+ 文件中重复的 setIsWorking + try/catch/finally + toast 模式 | 1 个示范（P1-4: schools-view），潜在影响 50+ 文件 |
| `useActionQuery` | `hooks/use-action-query.ts` | `useActionQuery<T>(action, options?): { data, loading, error, refetch }` | 通用 Server Action 查询 Hook，替代 11 个文件中重复的 useEffect + useState(loading) + Action().then().catch().finally() 模式，内置竞态防护 | 1 个示范（P1-4: create-question-dialog），潜在影响 11 个文件 |

**共享工具函数导出**（第二轮 P1-3 重构新增）：

| 函数 | 文件 | 签名 | 用途 | 消费方 |
|------|------|------|------|--------|
| `formatDateTime` | `lib/utils.ts` | `formatDateTime(date, locale?): string` | 国际化日期+时间格式化（含小时、分钟） | 4 个（P1-3: lesson-plan-card, version-history-drawer, proctoring-dashboard, exam-ai-generator） |
| `formatLongDate` | `lib/utils.ts` | `formatLongDate(date, locale?): string` | 国际化长日期格式化（含星期、完整月份名），默认 zh-CN，weekday=short | 1 个（P1-3: teacher-dashboard-header） |

> 注：`SettingsView` 位于 `modules/settings/components/`（非 shared 层），因仅被 settings 模块消费，未下沉到 shared。此处列出以完整反映本次重构的组件抽取范围。

**依赖关系**：
- 被依赖方：**所有模块**依赖 shared
- ✅ 反向依赖已修复：`shared/lib/{audit-logger, change-logger, auth-guard}` 通过 `shared/lib/session.ts` 单一入口获取 session，不再直接依赖 `@/auth`

**已知问题**：
- ✅ P0：~~`shared/lib/*` ↔ `@/auth` 循环依赖~~ 已修复（新增 `shared/lib/session.ts` 封装 session 获取，3 个文件改为 `import { getSession } from "@/shared/lib/session"`）
- ⚠️ P1：`schema.ts` 1111 行（54 张表混合，超 1000 硬上限）
- ✅ P1：~~`auth.ts` 293 行混合 5 类职责~~ 已拆分（4 个辅助函数组迁移至 `shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service}`，auth.ts 仅保留 NextAuth 配置）
- ✅ P2-2 已修复：~~`ai.ts` 218 行混合 5 类职责~~ 已拆分为 `ai/` 目录（payload-parser.ts/api-key-crypto.ts/provider-config.ts/client.ts/errors.ts/index.ts），原 `ai.ts` 保留为向后兼容的重导出文件（9 行）
- ✅ P2-4 已修复：~~`onboarding-gate.tsx` 业务逻辑泄漏到 shared~~ 已迁移至 `modules/onboarding/`（actions/data-access/schema/types/components），引导流程改为独立路由 `/onboarding` + middleware 重定向 + Server Action
- ✅ P0-2/P0-3/P0-4/P0-5/P1-1/P1-2/P1-4/P1-5 已修复（v3 对标 PowerSchool/Veracross/Auth0）：家长绑定三因子验证（邮箱+生日+手机号后4位）、教师多科目循环绑定、审计日志、服务端幂等、URL query 持久化步骤、局部错误收集、家长多子女动态行、跳过机制明确化
- ✅ v3 i18n 体系引入：采用 next-intl 4.x（without i18n routing 模式），cookie 驱动 locale 切换，字典放在 `shared/i18n/messages/{locale}/`，支持 zh-CN/en 两种语言，不破坏现有路由组结构
- ✅ v3 班级邀请码体系引入（对标 Google Classroom / 钉钉教育 / 智学网）：新增 `class_invitation_codes` 表（独立表，支持有效期/次数限制/审计/多码并存），6 位字母数字（剔除歧义字符 0/O/1/I/L，空间 1.13 亿），rate limit 防爆破（10 次/5 分钟），审计日志全链路记录，懒清理过期码

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `db/schema.ts` | 1111 | 54 张表定义（超硬上限） |
| `db/relations.ts` | - | 表关系定义 |
| `db/index.ts` | - | Drizzle 客户端 |
| `lib/auth-guard.ts` | - | 认证上下文 + 权限校验 + DataScope |
| `lib/permissions.ts` | - | 角色-权限映射 |
| `lib/session.ts` | 38 | session 获取单一入口（getSession，server-only，dynamic import 打破循环） |
| `lib/ai.ts` | 9 | 向后兼容重导出（P2-2 已拆分到 `ai/` 目录） |
| `lib/ai/payload-parser.ts` | 78 | 请求负载解析 |
| `lib/ai/api-key-crypto.ts` | 28 | API Key 加密/解密 |
| `lib/ai/provider-config.ts` | 61 | Provider 配置查询 |
| `lib/ai/client.ts` | 58 | AI 客户端创建与调用 |
| `lib/ai/errors.ts` | 8 | 错误格式化 |
| `lib/ai/index.ts` | 5 | 聚合导出 |
| `lib/audit-logger.ts` | - | 操作日志（通过 session.ts 获取 session，http-utils 获取 IP/UA） |
| `lib/change-logger.ts` | - | 数据变更日志（通过 session.ts 获取 session，http-utils 获取 IP） |
| `lib/login-logger.ts` | - | 登录日志（通过 http-utils 获取 IP/UA） |
| `lib/password-policy.ts` | - | 密码策略纯函数 |
| `lib/rate-limit.ts` | - | 内存滑动窗口限流 |
| `lib/role-utils.ts` | 31 | 角色规范化纯函数（normalizeRole / resolvePrimaryRole） |
| `lib/bcrypt-utils.ts` | 18 | bcrypt 哈希前缀规范化纯函数 |
| `lib/http-utils.ts` | 44 | 请求头解析（resolveClientIp / getUserAgent，server-only） |
| `lib/password-security-service.ts` | 84 | 密码安全 DB 操作（账户锁定/失败登录追踪，server-only） |
| `lib/excel.ts` | - | Excel 导入导出 |
| `lib/file-storage.ts` | - | 文件存储抽象 |
| `hooks/use-permission.ts` | - | 客户端权限 Hook |
| `components/ui/*` | 34 文件 | shadcn/ui 标准组件 |
| `components/ui/stat-card.tsx` | 95 | StatCard 统计卡片（P1-a 新增） |
| `components/ui/stat-item.tsx` | 38 | StatItem 紧凑统计项（P1-a 新增） |
| `components/ui/chip-nav.tsx` | 78 | ChipNav 芯片导航（P1-b 新增） |
| `components/ui/page-header.tsx` | 44 | PageHeader 页面头部（P2-b 新增，含 icon 属性） |
| `components/ui/filter-bar.tsx` | 124 | FilterBar + FilterSearchInput + FilterResetButton（P3-b 新增） |
| `components/charts/chart-card-shell.tsx` | 90 | ChartCardShell 图表卡片外壳（P3-c 新增） |
| `components/charts/trend-line-chart.tsx` | 153 | TrendLineChart 趋势折线图（P3-c 新增） |
| `components/charts/simple-bar-chart.tsx` | 162 | SimpleBarChart 柱状图（P3-c 新增） |
| `components/charts/comparison-radar-chart.tsx` | 143 | ComparisonRadarChart 对比雷达图（P3-c 新增） |
| `components/schedule/schedule-list.tsx` | 112 | ScheduleList + ScheduleListItem 课表列表（P3-a 新增） |
| `components/question/question-bank-filters.tsx` | 137 | QuestionBankFilters 题库筛选栏（P3-d 新增） |
| `lib/download.ts` | 47 | downloadBase64File + downloadBlob 客户端下载工具（P1-c 新增） |
| `lib/utils.ts` | - | 通用工具（P1-a/P1-c 新增 getInitials + formatDateForFile） |
| `components/onboarding-gate.tsx` | 312 | ~~引导流程（业务泄漏）~~ 已废弃，逻辑迁移至 `modules/onboarding/`（P2-4 已修复） |
| `components/global-search.tsx` | 221 | 全局搜索（业务泄漏） |
| `types/permissions.ts` | 157 | 61 个权限点常量 + Role/DataScope/AuthContext 类型 |

---

## 2.2 exams（考试模块）

**职责**：考试全生命周期管理（创建/编辑/预览/发布/删除/复制）+ AI 辅助出题。

**导出函数**：
- Actions：`createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction`（✅ P1-2 已修复：actions 层不再直接访问 DB，全部下沉到 data-access）
- Data-access：`getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` / `getExamCreatorId` / `updateExamWithQuestions` / `deleteExamById` / `duplicateExam` / `getExamPreview` / `getExamSubjects` / `getExamGrades`（后 7 个为 P1-2 新增）
- AI Pipeline：`generateAiCreateDraftFromSource` / `generateAiPreviewData` / `regenerateAiQuestionByInstruction`
- Utils：`normalizeStructure`（v3 新增：将持久化的 `exam.structure` unknown JSON 运行时校验并归一化为类型安全的 `ExamNode[]`，类型守卫模式无 `as` 断言，从 `teacher/exams/[id]/build/page.tsx` 提取）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`questions`（✅ P0-1 已修复：通过 data-access.createQuestionWithRelations）、`classes`（✅ P0-2 已修复：通过 data-access.getClassGradeIdsByClassIds）、`school`（✅ P1-1 已修复：通过 school data-access.getSubjectOptions/getGradeOptions）
- 被依赖：`homework`（通过 sourceExamId 外键，合理）、`dashboard`（通过 data-access，P0-4 已修复）、`proctoring`（✅ P1-1 已修复：通过 exams data-access）、`diagnostic`（✅ P1-1 已修复：通过 exams data-access）

**已知问题**：
- ✅ P0-1 已修复：~~`persistAiGeneratedExamDraft` 直接 insert 到 `questions` 表~~ 改为调用 `questions/data-access.createQuestionWithRelations`，通过 ID 映射保持 structure 引用一致
- ✅ P0-2 已修复：~~`getExams`/`getExamById`/`getExamsDashboardStats` 直查 `classes` 表~~ 改为调用 `classes/data-access.getClassGradeIdsByClassIds`
- ✅ P1-1 已修复：~~`getSubjectsAction`/`getGradesAction` 直查 `subjects`/`grades` 表~~ 改为调用 `school/data-access.getSubjectOptions` / `getGradeOptions`
- ✅ P1-2 已修复：~~`actions.ts` 832 行（超 800 建议），多处直接 DB 操作~~ DB 操作已下沉到 data-access，actions.ts 现 691 行
- ⚠️ P1：`ai-pipeline.ts` 857 行（超 800 建议），混合 4 类职责
- ✅ P2 已修复：`ai-pipeline.ts` 中 3 处非空断言清理（`draft.sections!.forEach` → 安全守卫、`aiParsed.sections!.flatMap` → `?? []`、`aiParsed.sections!.map` → `?? []`）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 691 | 10 个 Server Action（P1-2 已修复，无直接 DB 操作） |
| `ai-pipeline.ts` | 857 | AI 出题管线（超限） |
| `data-access.ts` | 473 | 考试 CRUD（含 P1-2 新增 7 个写/查询函数，P0-1/P0-2 已修复：通过 questions/classes data-access 跨模块通信） |
| `types.ts` | 31 | 类型定义 |
| `hooks/use-exam-preview.ts` | 295 | 预览 Hook |
| `utils/normalize-structure.ts` | 57 | v3 新增：exam.structure 运行时校验与归一化（从 build/page.tsx 提取） |
| `components/*` | 18 文件 | 考试表单/组卷/预览组件 |

---

## 2.3 homework（作业模块）

**职责**：作业全生命周期（创建/发布/作答/批改/分析）。

**导出函数**：
- Actions：`createHomeworkAssignmentAction` / `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction`（✅ P1-2 已修复：actions 层不再直接访问 DB，全部下沉到 data-access/data-access-write）
- Data-access：`getHomeworkAssignments` / `getHomeworkAssignmentById` / `getHomeworkSubmissions` / `getStudentHomeworkAssignments` / `getStudentHomeworkTakeData` / `getHomeworkAssignmentReviewList` / `getHomeworkSubmissionDetails` / `getDemoStudentUser`（已迁移至 users 模块 `getCurrentStudentUser`，此处为 re-export 向后兼容）/ `isRecord` / `toQuestionContent` / `getAssignmentMaxScoreById`（后三者供 stats-service 使用）
- Data-access-classes：`getAssignmentIdsForStudents` / `getHomeworkAssignmentsWithSubject` / `getHomeworkAssignmentsByIds` / `getAssignmentTargetCounts` / `getHomeworkSubmissionsForStudents` / `getPublishedHomeworkAssignmentsWithSubject` / `getHomeworkSubmissionsForAssignments`（P0-7 新增，供 classes 模块跨模块调用，封装 homework/exams 表查询）
- Data-access-write：10 个写操作函数（P1-2 新增，从 actions 下沉）
- Stats-service：`getTeacherGradeTrends` / `getHomeworkAssignmentAnalytics` / `getStudentDashboardGrades`（从 data-access.ts re-export 以保持向后兼容）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`exams`（✅ P1-1 已修复：通过 exams data-access.getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework）、`classes`（✅ P1-1 已修复：通过 classes data-access.getStudentIdsByClassId 等 7 个函数）、`school`（✅ P1-1 已修复：通过 school data-access.getSubjectOptions）、`users`（✅ P1-1 已修复：通过 users data-access.getUserWithRole/getUserNamesByIds）
- 被依赖：`dashboard`（通过 data-access，合理）、`parent`（通过 data-access，合理）、`classes`（✅ P0-7 已修复：classes 通过 `homework/data-access-classes` 获取作业数据，不再反向直查 homework/exams 表）

**已知问题**：
- ✅ P0 已解决：`data-access.ts` 已拆分至 598 行（原 1038 行超 1000 硬上限），统计函数迁移至 `stats-service.ts`
- ✅ P0 已解决：`getStudentDashboardGrades` 排名计算逻辑迁移至 `stats-service.ts`
- ✅ P0 已解决：`getHomeworkAssignmentAnalytics` 错误率统计逻辑迁移至 `stats-service.ts`
- ✅ P0-7 已修复：新增 `data-access-classes.ts`，将 classes 模块对 homework/exams 表的直查封装为 homework 模块的导出函数，恢复三层架构
- ✅ P1-1 已修复：~~5 处直查 `exams` 表~~ 改为调用 `exams/data-access.getExamIdsByGradeIds` / `getExamSubjectIdMap` / `getExamWithQuestionsForHomework`
- ✅ P1-2 已修复：~~`actions.ts` 多处直接 DB 操作（`createHomeworkAssignmentAction` 157 行）~~ DB 操作已下沉到 `data-access-write.ts`，actions.ts 现 239 行

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 598 | 作业 CRUD + 学生视角 + 批改（含 re-export stats 函数） |
| `data-access-write.ts` | 285 | 作业写操作（P1-2 新增，10 个写函数从 actions 下沉） |
| `data-access-classes.ts` | 232 | 跨模块查询封装（P0-7 新增，供 classes 模块调用，封装 homework/exams 表查询） |
| `stats-service.ts` | 425 | 统计分析（教师趋势/作业分析/学生仪表盘成绩） |
| `actions.ts` | 239 | 5 个 Server Action（P1-2 已修复，无直接 DB 操作） |
| `types.ts` | 186 | 类型定义 |
| `schema.ts` | 29 | Zod 校验 |

---

## 2.4 questions（题库模块）

**职责**：题库管理（题目 CRUD、知识点关联、题型支持）。

**导出函数**：
- Actions：`getQuestionsAction` / `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction`（✅ P1-2 已修复：actions 层不再直接访问 DB，全部下沉到 data-access）
- Data-access：`getQuestions` / `createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`（后 4 个为 P1-2 新增/迁移）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`textbooks`（✅ P1-1 已修复：通过 textbooks data-access.getKnowledgePointOptions）
- 被依赖：`exams`（通过类型导入，合理）、`textbooks`（UI 组合，合理）

**已知问题**：
- ✅ P1-2 已修复：~~写操作函数错放在 `actions.ts`（`insertQuestionWithRelations` / `deleteQuestionRecursive`）~~ 已下沉到 data-access（`createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`）
- ✅ P1-1 已修复：~~`getKnowledgePointOptionsAction` 直查 textbooks 模块表~~ 改为调用 `textbooks/data-access.getKnowledgePointOptions`
- ✅ P2 已解决：~~`data-access.ts` 仅 129 行，写操作缺失~~ P1-2 后 data-access.ts 扩充至 260 行

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 149 | 5 个 Server Action（P1-2 已修复，无直接 DB 操作） |
| `data-access.ts` | 260 | 题目 CRUD + 知识点选项（含 P1-2 新增 4 个写/查询函数） |
| `schema.ts` | 18 | Zod 校验 |
| `types.ts` | 34 | 类型定义 |

---

## 2.5 textbooks（教材模块）— 标杆模块（data-access 层）

**职责**：教材与知识体系管理（教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱）。

**导出函数**：
- Actions（11 个，均为写操作；读操作由 RSC 页面直接调用 data-access）：`createTextbookAction` / `updateTextbookAction` / `deleteTextbookAction` / `createChapterAction` / `updateChapterContentAction` / `deleteChapterAction` / `reorderChaptersAction` / `createKnowledgePointAction` / `updateKnowledgePointAction` / `deleteKnowledgePointAction` / `getKnowledgePointsByChapterAction`
- Data-access：`getTextbooks` / `getTextbookById` / `getChaptersByTextbookId` / `getKnowledgePointsByChapterId` / `getKnowledgePointsByTextbookId` / `createTextbook` / `updateTextbook` / `deleteTextbook` / `createChapter` / `updateChapterContent` / `deleteChapter` / `createKnowledgePoint` / `updateKnowledgePoint` / `deleteKnowledgePoint` / `reorderChapters` / `getTextbooksDashboardStats` / `getKnowledgePointOptions`（跨模块接口，供 questions 调用）/ `getTextbooksWithScope`（P1-1 新增：按数据范围获取教材列表，学生端强制按年级过滤）/ `verifyChapterBelongsToTextbook`（P0-4 新增：资源归属校验）/ `verifyKnowledgePointBelongsToTextbook`（P0-4 新增：资源归属校验）/ `getSubjectLabelKey` / `getGradeLabelKey`（i18n 标签键）
- Constants（✅ 新增）：`SUBJECTS` / `GRADES` / `SUBJECT_COLORS` / `getSubjectColor` / `getSubjectLabelKey` / `getGradeLabelKey`
- Utils（✅ 新增，纯函数 + 单测）：`sortChapters` / `buildChapterTree` / `buildChapterIndex` / `findChapterParent` / `filterKnowledgePointsByChapter` / `normalizeOptional` / `highlightKnowledgePoints`
- Graph-layout（✅ 新增，纯函数 + 单测）：`computeGraphLayout`
- Analytics（✅ 新增）：`TextbookAnalytics` / `TextbookAnalyticsProvider` / `useTextbookAnalytics`

**依赖关系**：
- 依赖：`shared/*`、`@/auth`
- 被依赖：`questions`（✅ P1-1 已修复：通过 textbooks data-access）、`exams`（通过类型）、`dashboard`（通过 data-access，P0-4 已修复）
- ✅ UI 层跨模块依赖已解耦：`textbooks/components/knowledge-point-dialogs.tsx` 不再直接 import questions 模块，改为通过 render prop 注入创建题目入口

**已知问题**：
- ✅ 无跨模块 DB 访问（data-access 层）
- ✅ actions 层编排模式标杆（权限校验 → 调用 data-access → revalidatePath）
- ✅ data-access 层职责单一
- ✅ P2 已修复：`data-access.ts` 中 `byId.get(pid)!.children.push` 非空断言清理为安全守卫；`or(...)!` 非空断言清理为条件 push
- ✅ P0 跨模块 UI 依赖已修复：`knowledge-point-dialogs.tsx` 通过 render prop 解耦，不再直接 import questions 模块
- ✅ P0 前端权限硬编码已修复：改用 `usePermission().hasPermission()`
- ✅ P0 i18n 已基本接入：`chapter-sidebar-list.tsx` / `actions.ts` / `section-error-boundary.tsx`（默认值改英文）均已接入 next-intl
- ✅ P1 Server Action 资源归属校验已修复：新增 `verifyChapterBelongsToTextbook` / `verifyKnowledgePointBelongsToTextbook`
- ✅ P1 data-access 数据范围过滤已修复：新增 `getTextbooksWithScope`，学生端强制按年级过滤
- ✅ P1 Error Boundary 已补齐：新增 `section-error-boundary.tsx`
- ✅ P1 知识点列表/弹窗重复实现已清理：移除无调用方的 `knowledge-point-panel.tsx`
- ✅ P1 学科/年级选项统一：抽取到 `constants.ts`（`SUBJECTS` / `GRADES` / `SUBJECT_COLORS`）
- ✅ P1 纯逻辑已导出并补单测：新增 `utils.ts` / `graph-layout.ts` 及对应 `.test.ts`
- ⚠️ i18n 覆盖率约 95%（`chapter-sidebar-list` 已接入，`actions.ts` 已接入，`section-error-boundary` 默认值已改英文）
- ⚠️ 类型断言残留 3 处 `as string`
- ⚠️ P2 图谱方向键导航未实现

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 377 | 11 个 Server Action（写操作，含 Zod 校验 + 资源归属校验） |
| `data-access.ts` | 619 | 教材/章节/知识点 CRUD + 跨模块查询接口 + 资源归属校验 + 数据范围过滤 |
| `types.ts` | 45 | 类型定义 |
| `schema.ts` | 64 | Zod 校验 |
| `constants.ts` | 91 | 学科/年级常量与颜色映射（✅ 新增） |
| `utils.ts` | 181 | 章节树构建/排序/查找等纯函数（✅ 新增，含单测） |
| `graph-layout.ts` | 141 | 知识图谱布局计算纯函数（✅ 新增，含单测） |
| `analytics.tsx` | 43 | 教材分析 Context/Provider/Hook（✅ 新增） |
| `hooks/use-knowledge-point-actions.ts` | 121 | 知识点操作 Hook |
| `hooks/use-text-selection.ts` | 57 | 文本选区捕获 Hook |
| `components/*` | 12 文件 | 教材编辑/知识图谱组件（新增 `section-error-boundary.tsx`） |

---

## 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 文件抽取的公共工具函数）
- Stats-service（✅ P1-1 新增）：`computeGradeStats` / `computeAverageScore` / `buildGradeTrendPoints` / `computeTrendAverage` / `computeClassComparisonStats` / `computeSubjectComparisonStats` / `computeGradeDistribution` / `buildRankingTrendPoints`（从 3 个 data-access 文件抽取的纯函数，使数据层专注 DB I/O，统计逻辑可独立测试）
- Components（✅ P1-5 新增）：`WidgetBoundary`（Error Boundary + Suspense + Skeleton 组合，含 a11y 属性）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`classes`（✅ P1-1 已修复：通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId）、`school`（✅ P1-1 已修复：通过 school data-access.getSubjectOptions/getGradeOptions）、`users`（✅ P1-1 已修复：通过 users data-access.getUserNamesByIds）
- 被依赖：`parent`（通过 data-access，合理）、`dashboard`

**已知问题**：
- ✅ P1-1 已修复：~~多处直查 `classes`/`classEnrollments`/`subjects`/`users` 表~~ 改为调用对应模块 data-access 函数（classes/school/users）
- ✅ P1-1 已修复：~~统计计算业务逻辑混入 data-access（`getClassGradeStats` / `getGradeDistribution`）~~ 抽取为纯函数到 `stats-service.ts`，数据层专注 DB I/O
- ✅ P1-2 已修复：~~`toNumber`/`normalize`/`buildScopeClassFilter` 在 3 个 data-access 文件中重复定义~~ 抽取到 `lib/grade-utils.ts` 统一维护
- ✅ P1-3 已修复：~~12 个查询/分析 Action 缺少 Zod 校验~~ 新增 12 个查询 schema（DeleteGradeRecordSchema/GetGradeRecordByIdSchema/GradeQuerySchema/ClassGradeStatsQuerySchema/StudentGradeSummaryQuerySchema/ClassRankingQuerySchema/ExportGradesSchema/GradeTrendQuerySchema/ClassComparisonQuerySchema/SubjectComparisonQuerySchema/GradeDistributionQuerySchema/RankingTrendQuerySchema），所有 Action 使用 safeParse 校验
- ✅ P1-4 已修复：~~`batch-grade-entry.tsx`/`grade-record-form.tsx`/`grade-distribution-chart.tsx` 中存在 `as` 断言~~ 改用类型守卫函数（isGradeType/isSemester/isDistributionTooltipPayload）
- ✅ P1-5 已修复：~~teacher/grades 与 teacher/diagnostic 路由缺少 loading.tsx/error.tsx~~ 已为 7 个路由补齐 loading.tsx + error.tsx，并新增 `WidgetBoundary` 通用组件
- ✅ P2-1 已修复：~~图表/表格/列表缺少 a11y ARIA 属性~~ 为 4 个成绩图表添加 `role="img"` + `aria-label`，2 个表格添加 `<caption>`，3 个图标按钮添加 `aria-label`
- ✅ P2-2 已修复：~~diagnostic 组件中存在 Tailwind 任意值~~ 改用标准 Tailwind 类
- ✅ P2-4 已修复：~~`buildScopeClassFilter` 中 `grade_managed` scope 返回 `sql\`1=0\``（空数据）~~ 改为子查询 `classId IN (SELECT id FROM classes WHERE grade_id IN (...))` 过滤所管年级的班级
- ✅ P2-6 已修复：~~student/grades 和 management/grade/insights 各自重复定义 `SearchParams` 类型与 `getParam` 函数~~ 改为统一从 `@/shared/lib/search-params` 导入
- ✅ actions 层无直接 DB 访问（标杆）
- ✅ data-access 按职责拆分为 3 个文件（标杆）
- ✅ P2 已修复：`export.ts` 中 `scoreMap.get(r.studentId)!` 非空断言清理为安全守卫（`if (!subjMap) continue`）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `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 | 排名查询 |
| `stats-service.ts` | - | 统计计算纯函数（P1-1 新增：8 个纯函数 + 2 个常量 + 2 个接口） |
| `export.ts` | 214 | Excel 导出 |
| `schema.ts` | 100 | Zod 校验（含 12 个查询 schema） |
| `lib/grade-utils.ts` | 46 | 公共工具函数（toNumber/normalize/buildScopeClassFilter） |
| `components/widget-boundary.tsx` | - | Widget 边界组件（P1-5 新增） |
| `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` 中同类非空断言清理
- ✅ P0-3 修复（2026-06-22）：~~`actions.ts` 974 行接近 1000 行硬上限~~ 拆分为 6 个文件（actions-teacher/actions-admin/actions-grade/actions-invitations/actions-schedule/actions-shared），原 `actions.ts` 改为 50 行 barrel re-export
- ✅ P1-1 修复（2026-06-22）：~~`ctx.roles.includes("admin"/"teacher"/"student")` 角色硬编码~~ 改为 `hasAdminScope(ctx)`/`hasTeacherScope(ctx)`/`hasStudentScope(ctx)` 基于 `dataScope.type` 判断
- ✅ P1-4 修复（2026-06-22）：`types.ts` 中 ClassHomeworkInsights 等跨领域类型保留在 classes 模块（因为是 classes 对 homework 数据的视图），添加注释说明归属决策

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 548 | 核心班级 CRUD + 邀请码 + 教师班级管理（含 re-export 向后兼容） |
| `data-access-stats.ts` | 513 | 作业统计查询（班级/年级作业洞察，通过 homework/data-access-classes 获取数据） |
| `data-access-schedule.ts` | 93 | 课表查询（学生/班级课表只读，P0-5 已修复：写函数已迁移至 scheduling 模块） |
| `data-access-students.ts` | 253 | 学生相关查询（科目成绩、学生名单、学生班级，通过 homework/data-access-classes 获取数据） |
| `data-access-admin.ts` | 406 | 管理员班级管理（管理员班级 CRUD、年级管理班级查询） |
| `actions.ts` | 50 | Barrel re-export（P0-3 修复：从 974 行拆分为 6 个文件） |
| `actions-teacher.ts` | 100 | 教师班级 CRUD（3 个 Action） |
| `actions-admin.ts` | 120 | 管理员班级 CRUD（3 个 Action） |
| `actions-grade.ts` | 110 | 年级组长班级 CRUD（3 个 Action） |
| `actions-invitations.ts` | 280 | 邀请码与注册（8 个 Action） |
| `actions-schedule.ts` | 90 | 班级课表 CRUD（3 个 Action） |
| `actions-shared.ts` | 60 | 共享工具（hasAdminScope/hasTeacherScope/hasStudentScope/parseSubjectTeachers/toWeekday） |
| `schema.ts` | 152 | Zod 校验（13 个 schema：教师/管理员/年级班级 CRUD + 课表 CRUD + 邮箱注册） |
| `types.ts` | 201 | 类型定义（含跨领域类型说明注释，P1-4 修复） |

---

## 2.8 school（学校模块）

**职责**：学校/学年/部门/年级的 CRUD。

**导出函数**：
- Actions：`createSchoolAction` / `updateSchoolAction` / `deleteSchoolAction` / `createAcademicYearAction` / `updateAcademicYearAction` / `deleteAcademicYearAction` / `createDepartmentAction` / `updateDepartmentAction` / `deleteDepartmentAction` / `createGradeAction` / `updateGradeAction` / `deleteGradeAction`（编排层：权限校验 + Zod 校验 + 调用 data-access + revalidatePath + after(logAudit)）
- Data-access：只读查询（`getSchools` / `getGrades` / `getDepartments` / `getAcademicYears` / `getStaffOptions` / `getGradesForStaff`）+ 写操作（`create/update/delete` × `Department/School/Grade/AcademicYear`）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`users`（⚠️ `getStaffOptions` 直查 users/roles，可接受）
- 被依赖：`exams`（✅ P1-1 已修复：通过 school data-access）、`homework`（✅ P1-1 已修复：通过 school data-access）、`grades`（✅ P1-1 已修复：通过 school data-access）、`questions`（✅ P1-1 已修复：通过 textbooks data-access）、`classes`（✅ P1-1 已修复：通过 school data-access）、`course-plans`（合理）

**已知问题**：
- ✅ P0-8 已修复：`actions.ts` 不再直接导入 `db` 和 schema，所有 DB 写操作下沉到 `data-access.ts`，符合三层架构
- ✅ P2 已修复：`logAudit()` 通过 Next.js `after()` 异步非阻塞执行
- ✅ P2 已修复：`data-access.ts` 中 8 处 catch 块添加 `console.error` 输出错误上下文（getDepartments/getAcademicYears/getSchools/getGrades/getStaffOptions/getGradesForStaff/getSubjectOptions/getGradeOptions）
- ⚠️ P2：审计日志不一致（仅 school 实体记录，department/academicYear/grade 未记录）
- ⚠️ P2：`getStaffOptions`/`getGrades` 直查 users/roles（展示用，可接受）
- ✅ P0-2 修复（2026-06-22）：~~年级 CRUD 逻辑与 `grade-management` 模块重复定义~~ `grade-management` 死模块已删除，年级 CRUD 统一由 school 模块负责
- ✅ P0-5 修复（2026-06-22）：~~`school/components/*` 4 个组件缺少 i18n~~ 全部 4 个组件（schools-view/grades-view/departments-view/academic-year-view）已接入 `useTranslations("school")`；`school.json` i18n 文件已创建并扩充
- ✅ P1-3 修复（2026-06-22）：新增 school-error-boundary.tsx（class component Error Boundary + i18n + router.refresh 重试）和 school-skeleton.tsx（SchoolListSkeleton 表格骨架 + SchoolCardSkeleton 卡片骨架）；4 个页面（schools/grades/departments/academic-year）均已包裹 SchoolErrorBoundary；school.json 补充 errors.boundary.* 翻译键
- ✅ P1-5 修复（2026-06-22）：~~`schools-view.tsx` 硬编码 Table+Dialog+AlertDialog~~ 拆分为组合模式：SchoolListToolbar + SchoolFormDialog + SchoolDeleteDialog + useSchoolData hook
- ✅ P1-6 修复（2026-06-22）：新增 `getSchoolsForUser(userId)` / `getGradesForUser(userId)` 权限感知查询函数，根据用户角色返回可见数据范围
- ✅ P2-1 修复（2026-06-22）：抽取 `use-school-data` hook，将对话框状态管理逻辑与 UI 分离

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 349 | 12 个 Server Action（编排层，无 DB 直访） |
| `data-access.ts` | 504+ | 只读查询 + 12 个写操作 + 跨模块查询接口 + 权限感知函数（getSchoolsForUser/getGradesForUser） |
| `schema.ts` | 51 | Zod 校验 |
| `types.ts` | 96 | 类型定义（含 Insert/Update 入参类型） |
| components/schools-view.tsx | 132 | 学校列表容器（组合模式，P1-5 修复） |
| components/school-form-dialog.tsx | 80 | 学校创建/编辑对话框（P1-5 修复） |
| components/school-delete-dialog.tsx | 50 | 学校删除确认对话框（P1-5 修复） |
| components/school-list-toolbar.tsx | 30 | 学校列表工具栏（P1-5 修复） |
| components/school-error-boundary.tsx | 72 | 共享 Error Boundary（P1-3 修复） |
| components/school-skeleton.tsx | 69 | 共享骨架屏（P1-3 修复） |
| hooks/use-school-data.ts | 40 | 学校数据管理 hook（P2-1 修复） |

---

## 2.8b grade-management（年级管理模块）— ✅ 已删除

> **2026-06-22 审计发现**：该模块拥有完整的理想架构（Service 接口 + Context DI + 角色配置 + Error Boundary + Skeleton + i18n + hooks 分离），但 **13 个相关页面中无任何一个导入此模块**。`management/grade/*` 页面实际依赖 `classes` 和 `school` 模块的 data-access。
>
> **2026-06-22 处置决策（P0-1/P0-2 修复）**：该死模块已**完整删除**。年级 CRUD 统一由 `school` 模块负责（`school/actions.ts` + `school/data-access.ts`），避免两套重复实现。详见 `docs/architecture/audit/school-grade-class-audit-report.md`。

---

## 2.9 scheduling（排课模块）

**职责**：自动排课算法 + 课表调整 + 排课规则管理。

**导出函数**：
- Actions：`autoScheduleAction` / `applyAutoScheduleAction` / `getSchedulingRulesAction` / `updateSchedulingRulesAction` / `getScheduleChangesAction` / `createScheduleChangeAction` / `updateScheduleChangeAction` / `deleteScheduleChangeAction`
- Data-access：`getSchedulingRules` / `getScheduleChanges` / `getAdminClassesForScheduling` / `getTeachersForScheduling` / `getClassroomsForScheduling` / `getClassSubjectsForScheduling`
- Data-access-class-schedule（✅ P0-5 新增）：`createClassScheduleItem` / `updateClassScheduleItem` / `deleteClassScheduleItem`（从 classes 模块迁移，含教师班级归属校验，通过 `classes/data-access.verifyTeacherOwnsClass` 跨模块校验）
- Data-access（低级写入）：`insertClassScheduleItem` / `updateClassScheduleItemById` / `deleteClassScheduleItemById` / `replaceClassSchedule`（统一 classSchedule DB 写入口）
- 算法：`findOptimalSlot` / `validateSchedule` / `autoSchedule` / `buildDefaultTimeSlots`（纯函数，标杆）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`classes`（✅ P0-5 已修复：通过 `classes/data-access.verifyTeacherOwnsClass` / `getTeacherIdForMutations` 校验教师班级归属，不再直写 classSchedule 表的写入口分散在 classes 模块）、`school`（⚠️ 排课辅助查询，可接受）、`users`（✅ P1-1 已修复：通过 users data-access.getUserNamesByIds）
- 被依赖：`classes/actions.ts`（✅ P0-5 已修复：通过 `scheduling/data-access-class-schedule` 调用写函数）

**已知问题**：
- ✅ P0-5 已修复：~~`applyAutoScheduleAction` 直接 transaction 写 `classSchedule` 表（第三个写入口）~~ 改为调用 `replaceClassSchedule` 统一写入口；classSchedule 所有写函数统一在 scheduling 模块
- ✅ P1-1 已修复：~~`autoScheduleAction` 直查 `users` 表~~ 改为调用 `users/data-access.getUserNamesByIds`
- ⚠️ P2：`actions.ts` 末尾 re-export data-access 函数（反模式）
- ✅ P2 已修复：`data-access.ts` 中 3 处非空断言清理（`userIds[0]!`、`rows[i]!`、`rows[j]!`）；`auto-scheduler.ts` 中 2 处非空断言清理（`schedule[i]!`、`schedule[j]!`）
- ✅ `auto-scheduler.ts` 是算法独立化的最佳实践（纯函数、无 DB、可测试）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `auto-scheduler.ts` | 310 | 排课算法（纯函数，标杆） |
| `actions.ts` | 302 | 8 个 Server Action |
| `data-access.ts` | 398 | 排课辅助查询 + 规则/变更 CRUD + classSchedule 低级写入（insert/update/delete/replace） |
| `data-access-class-schedule.ts` | 165 | classSchedule 业务写入（P0-5 新增，从 classes 模块迁移，含教师归属校验） |
| `schema.ts` | - | Zod 校验 |
| `types.ts` | - | 类型定义（含 P0-5 迁移的 CreateClassScheduleItemInput / UpdateClassScheduleItemInput） |

---

## 2.10 attendance（考勤模块）— 结构典范

**职责**：考勤记录管理 + 统计分析 + 规则配置。

**导出函数**：
- Actions（10 个）：`recordAttendanceAction` / `batchRecordAttendanceAction` / `updateAttendanceAction` / `deleteAttendanceAction` / `getAttendanceAction` / `getStudentAttendanceAction` / `getClassAttendanceStatsAction` / `getClassAttendanceForDateAction` / `saveAttendanceRulesAction` / `getAttendanceRulesAction`
- Data-access：`getAttendanceRecords` / `createAttendanceRecord` / `updateAttendanceRecord` / `deleteAttendanceRecord` / `getClassStudentsForAttendance` / `getAttendanceStats`（管理员考勤总览页统计概览，基于 `getAttendanceRecords` 聚合）/ `upsertAttendanceRules` / `getAttendanceRules`
- Data-access-stats：`getStudentAttendanceSummary` / `getClassAttendanceStats` / `computeStats`（⚠️ 未导出，无法单测）
- Components：`AttendanceSheet`（批量点名表单）/ `AttendanceRecordList`（记录列表 + 删除）/ `AttendanceFilters`（URL 同步筛选器）/ `AttendanceStatsCard`（单卡片统计）/ `AttendanceStatsCards`（管理员 6 卡片总览）/ `AttendanceStatsClassSelector`（班级筛选 ChipNav）/ `AttendanceRulesForm`（规则配置表单）/ `StudentAttendanceView`（学生/家长只读视图）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`classes`（⚠️ P1-1 未修复：`getClassStudentsForAttendance` 仍直查 `classEnrollments` 表）
- 被依赖：`parent`（⚠️ 跨模块 UI 类型依赖：3 个 parent 组件直接 import `@/modules/attendance/types`）

**已知问题**（详见 `docs/architecture/audit/attendance-elective-audit-report.md`）：
- ❌ P0：`getAttendanceStats` 统计失真——调用 `getAttendanceRecords`（默认 pageSize=20）后对 `items` 聚合，仅基于前 20 条记录计算总览数据
- ❌ P0：`getClassStudentsForAttendance` 仍直查 `classEnrollments` 表（架构图此前声称已修复，实际未修复）
- ❌ P0：6 个读 Action 无调用方（页面绕过 Action 直接调用 data-access），违反三层架构
- ❌ P0：update/delete Action 缺资源归属校验（教师 A 可修改/删除教师 B 的记录）
- ❌ P0：i18n 完全缺失（`ATTENDANCE_STATUS_LABELS` 硬编码英文，组件中硬编码中文）
- ❌ P0：错误边界完全缺失（5 个角色目录均无 `error.tsx`）
- ⚠️ P1：`computeStats` 未导出，无法单测
- ⚠️ P1：`attendance-sheet.tsx` 使用 `window.confirm`（与项目 AlertDialog 模式不一致）
- ⚠️ P1：`attendance-sheet.tsx` 存在 `{} as Record<AttendanceStatus, number>` 类型断言
- ⚠️ P1：`STATUS_OPTIONS`/`SHORTCUTS`/`STYLES` 常量在 types.ts 与 attendance-sheet.tsx 重复定义
- ✅ stats 独立拆分为 `data-access-stats.ts`（拆分范例）
- ✅ DataScope 完整接入 6 种 scope 类型
- ✅ actions 层无直接 DB 访问

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 271 | 10 个 Server Action（含权限校验、Zod 校验） |
| `data-access.ts` | 309 | 考勤 CRUD + 班级学生查询 + 规则 upsert + 总览统计 |
| `data-access-stats.ts` | 145 | 学生/班级考勤汇总（拆分范例，`computeStats` 未导出） |
| `schema.ts` | 43 | Zod 校验（5 个 schema） |
| `types.ts` | 103 | 类型定义 + 状态标签/颜色常量（硬编码英文） |
| `components/attendance-sheet.tsx` | 353 | 批量点名表单（键盘快捷键、状态按钮组） |
| `components/attendance-record-list.tsx` | 130 | 考勤记录列表 + 删除对话框 |
| `components/attendance-filters.tsx` | 97 | URL 同步筛选器（班级/状态/日期） |
| `components/attendance-stats-card.tsx` | 81 | 单卡片统计（8 指标） |
| `components/attendance-stats-cards.tsx` | 80 | 管理员总览 6 卡片网格（硬编码中文） |
| `components/attendance-stats-class-selector.tsx` | 27 | 班级筛选 ChipNav |
| `components/attendance-rules-form.tsx` | 148 | 考勤规则配置表单 |
| `components/student-attendance-view.tsx` | 104 | 学生/家长视图（统计 + 最近记录） |

---

## 2.11 users（用户模块）

**职责**：用户资料管理 + 批量导入导出 + 管理员用户列表管理。

**导出函数**：
- Actions：`getUserProfileAction` / `updateUserProfileAction` / `importUsersAction` / `exportUsersAction` / `downloadUserTemplateAction` / `updateUserRoleAction` / `deleteUserAction`
- Data-access：`getUserProfile` / `getCurrentStudentUser`（✅ P2-20 已修复：从 homework 模块迁移而来，6 个 student 页面通过此函数获取学生身份，不再依赖 homework 模块）/ `getAdminUsers`（管理员用户列表分页查询，支持搜索+角色聚合）/ `getAdminUserRoles`（角色名列表，用于筛选下拉框）
- Import-export：`generateUserImportTemplate` / `parseUserImportData` / `exportUsersToExcel`（+ re-export `batchImportUsers` / `UserImportResult` 保持向后兼容）
- User-service：`batchImportUsers`（用户创建 + 密码哈希 + 角色分配）
- Class-registration：`registerStudentByInvitationCode`（委托 classes/data-access 完成班级注册）
- Components：`UserImportDialog`（批量导入对话框）/ `AdminUsersView`（管理员用户列表客户端组件，搜索+筛选+分页+删除）

**依赖关系**：
- 依赖：`shared/*`（含 `shared/lib/role-utils`，✅ P2 已修复：删除本地 `normalizeRoleName`/`resolvePrimaryRole`/`rolePriority`，统一复用 `shared/lib/role-utils.resolvePrimaryRole`）、`@/auth`、`classes`（✅ P1-4 已修复：通过 `class-registration.ts` 调用 `classes/data-access.enrollStudentByInvitationCode`，不再直写 classEnrollments）
- 被依赖：`dashboard`（通过 data-access，P0-4 已修复）、`grades`（✅ P1-1 已修复：通过 users data-access）、`homework`（✅ P1-1 已修复：通过 users data-access）、`scheduling`（✅ P1-1 已修复：通过 users data-access）、`diagnostic`（✅ P1-1 已修复：通过 users data-access）、`elective`（✅ P1-1 已修复：通过 users data-access）、`proctoring`（✅ P1-1 已修复：通过 users data-access）、`parent`（✅ P1-1 已修复：通过 users data-access）

**已知问题**：
- ✅ P1 已解决：`import-export.ts` 四重职责已拆分为 `import-export.ts`（解析/生成）+ `user-service.ts`（用户创建）+ `class-registration.ts`（班级注册）
- ✅ P1 已解决：`batchImportUsers` 不再跨模块直写 `classEnrollments`，改为调用 `classes/data-access.enrollStudentByInvitationCode`
- ✅ P2 已解决：删除本地 `normalizeRoleName`/`resolvePrimaryRole`/`rolePriority`，统一复用 `shared/lib/role-utils.resolvePrimaryRole`，消除重复代码
- ✅ P1-1 已修复：~~`updateUserProfile` 绕过 data-access 直接 DB 写~~ 已下沉到 data-access
- ✅ P2-20 已修复：新增 `getCurrentStudentUser` 函数（从 homework 模块迁移），6 个 student 页面通过此函数获取学生身份，不再依赖 homework 模块
- ✅ P2 已解决：`data-access.ts` 已扩充写操作（updateUserProfile 已下沉）
- ⚠️ 已知限制：`AdminUsersView` 客户端组件的删除操作通过 `fetch("/api/admin/users/:id")` 调用，对应 API 路由尚未实现（`deleteUserAction` Server Action 已就绪，可作为后续 API 路由的实现基础）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `import-export.ts` | 157 | 文件解析/生成（模板生成 + 解析校验 + Excel 导出）+ re-export 向后兼容 |
| `user-service.ts` | 82 | 用户创建（批量导入 + 密码哈希 + 角色分配） |
| `class-registration.ts` | 21 | 班级注册（委托 classes/data-access） |
| `actions.ts` | 218 | 7 个 Server Action（profile 更新 + 模板下载/导入/导出 + 角色更新 + 删除） |
| `data-access.ts` | 394 | getUserProfile + 用户查询 + 管理员用户列表分页查询（getAdminUsers/getAdminUserRoles） |
| `components/admin-users-view.tsx` | 290 | 管理员用户列表客户端组件（搜索+筛选+表格+分页+删除对话框） |

---

## 2.12 dashboard（仪表盘模块）

**职责**：管理员/教师/学生/家长仪表盘数据聚合 + 权限校验 + i18n + 纯逻辑工具函数。

**导出函数**：
- Actions：`getAdminDashboardAction` / `getTeacherDashboardAction` / `getStudentDashboardAction` / `getParentDashboardAction`（均调用 `requirePermission()` 校验对应 `DASHBOARD_*_READ` 权限）
- Data-access：`getAdminDashboardData`（并行调用 6 个模块 stats 函数）
- Lib 纯函数：`toWeekday` / `countStudentAssignments` / `sortUpcomingAssignments` / `filterTodaySchedule` / `computeTeacherMetrics` / `getGreetingKey`
- Components：`AdminDashboardView` / `TeacherDashboardView` / `StudentDashboard` / `UserGrowthChart` / `DashboardGreetingHeader`（共享问候头部，V2 抽象）/ `DashboardSection`（分区 Error Boundary + Suspense + 骨架屏）（均接入 next-intl i18n）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`classes`（通过 data-access）、`homework`（通过 data-access）、`users`（通过 data-access）、`parent`（通过 data-access.getParentDashboardData）、`textbooks`/`questions`/`exams`（通过各模块 dashboard stats 函数）、`recharts`、`next-intl`
- 被依赖：无

**权限点**：
- `DASHBOARD_ADMIN_READ`（admin）
- `DASHBOARD_TEACHER_READ`（teacher）
- `DASHBOARD_STUDENT_READ`（student）
- `DASHBOARD_PARENT_READ`（parent）

**已知问题**：
- ✅ P0-4 已修复：`getAdminDashboardData` 改为并行调用各模块 dashboard stats 函数，不再直查跨模块表
- ✅ P0 已修复（2026-06-22）：所有仪表盘页面通过 `actions.ts` 调用 `requirePermission()` 进行权限校验，不再裸调 data-access
- ✅ P0 已修复（2026-06-22）：根重定向页 `/dashboard` 改用 `resolvePermissions()` + 权限点判断，不再 `role === "xxx"` 硬编码
- ✅ P0 已修复（2026-06-22）：所有仪表盘组件接入 next-intl（`useTranslations` / `getTranslations`），翻译文件 `messages/{zh-CN,en}/dashboard.json`
- ✅ P1 已修复（2026-06-22）：业务逻辑（weekday 转换、作业统计、教师指标计算、问候语时段）抽取至 `lib/dashboard-utils.ts` 纯函数，与 UI 分离
- ✅ P2 已修复（2026-06-22）：新增 `components/dashboard-section.tsx`，每个独立数据区块用 Error Boundary + Suspense + 骨架屏包裹，单区块崩溃/加载不波及整页（5 种骨架变体：stats/card/chart/table/list）
- ℹ️ V1 新增：`AdminDashboardData` 类型含 `userGrowth`/`homeworkTrend` 字段，`data-access.ts` 当前返回空数组占位，待后续接入真实统计
- ℹ️ parent 仪表盘组件仍位于 `modules/parent/components/parent-dashboard.tsx`，通过 `dashboard/actions.getParentDashboardAction` 调用（架构决策：保留在 parent 模块以避免移动文件破坏其他 import）
- ✅ P0 已修复（2026-06-22 V2）：10 个子组件 i18n 遗漏全部补齐（teacher-quick-actions / teacher-classes-card / teacher-homework-card / teacher-schedule / recent-submissions / teacher-grade-trends / student-grades-card / student-today-schedule-card / student-upcoming-assignments-card / admin-dashboard），新增 ~50 个翻译键
- ✅ P1 已修复（2026-06-22 V2）：抽象共享组件 `DashboardGreetingHeader`，消除 teacher/student 头部 90% 重复代码
- ✅ P2 已修复（2026-06-22 V2）：为 6 个纯函数添加 31 个单元测试（`tests/integration/dashboard/dashboard-utils.test.ts`）
- ✅ P2 已修复（2026-06-22 V2）：a11y 增强 — admin 表格 `<caption>`、teacher/student 视图语义化标签（`<header>` / `<section aria-label>` / `<aside aria-label>`）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 120 | 4 个 Server Action（编排层，含 `requirePermission()` 权限校验） |
| `data-access.ts` | 49 | admin 仪表盘数据聚合（并行调用各模块 stats 函数） |
| `lib/dashboard-utils.ts` | 170 | 纯逻辑工具函数（weekday / 统计 / 排序 / 指标计算 / 问候语） |
| `types.ts` | 74 | Admin / Teacher / Student 类型定义 |
| `components/dashboard-section.tsx` | 165 | 分区 Error Boundary + Suspense + 骨架屏（5 种变体：stats/card/chart/table/list） |
| `components/dashboard-greeting-header.tsx` | 36 | 共享问候头部组件（V2 抽象，消除 teacher/student 头部重复） |
| `components/admin-dashboard/admin-dashboard.tsx` | 267 | 管理员仪表盘视图（i18n + a11y 表格 caption） |
| `components/admin-dashboard/user-growth-chart.tsx` | 50 | recharts 折线图（i18n） |
| `components/teacher-dashboard/*.tsx` | 9 文件 | 教师仪表盘组件（i18n + a11y 语义化标签） |
| `components/student-dashboard/*.tsx` | 6 文件 | 学生仪表盘组件（i18n + a11y 语义化标签） |
| `tests/integration/dashboard/dashboard-utils.test.ts` | 408 | 6 个纯函数的 31 个单元测试（V2 新增） |

---

## 2.13 messaging（私信模块）

**职责**：站内私信（messages 表 CRUD）。

**导出函数**：
- Actions：`sendMessageAction` / `markMessageAsReadAction` / `deleteMessageAction` / `getMessagesAction` / `getMessageDetailAction` / `getRecipientsAction` / `getUnreadMessageCountAction` / `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`（✅ P1-4 已修复：通知 CRUD Action 已迁移至 notifications 模块，messaging 仅保留私信和通知偏好 Action）
- Data-access：`getMessages` / `getMessageById` / `getMessageThread` / `createMessage` / `markMessageAsRead` / `deleteMessage` / `getUnreadMessageCount` / `getRecipients`（按 DataScope 过滤可发送对象：class_taught 教师→学生、grade_managed 年级管理员→教师/学生、all 管理员、class_members 学生→自己班级的任课教师/班主任、children 家长→孩子的班主任/任课教师；通过 classes data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID）/ `getMessagesPageData`（✅ P1-5 新增：消息首页编排函数，一次性获取消息列表和通知列表）/ `getMessageDetailPageData`（✅ V2-P1-3 新增：消息详情页编排函数，获取详情并自动标记已读）
- Hooks：`useMessageSearch`（✅ P1-7 新增：消息搜索 hook，含防抖和请求竞态取消）
- Notification-preferences：~~re-export shim（实际逻辑在 `notifications/preferences.ts`）~~ ✅ P0-b 已修复：`notification-preferences.ts` 文件已删除（通知模块去重），消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`notifications`（✅ P0-4 / P1-5 已修复：通过 `sendNotification` dispatcher 发送通知，通知 CRUD 和偏好已迁移至 notifications 模块；✅ P1-4 已修复：通知 UI 组件已迁移至 notifications 模块）、`classes`（通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID，支持学生 class_members 和家长 children 数据范围）、`users`（通过 data-access.getUserNamesByIds 获取用户显示名称）
- 被依赖：`notifications`（✅ 已消除反向依赖）、`settings`（通知偏好表单）、`layout`（✅ P1-4 已修复：通知下拉组件改为从 notifications 模块导入）

**已知问题**：
- ✅ P0-4 已修复：~~`sendMessageAction` 绕过 notifications dispatcher 直接调用 `createNotification`~~ 改为调用 `notifications.sendNotification`，通知 CRUD 已迁移至 notifications 模块
- ✅ P0 已修复：~~与 notifications 双向依赖 + 职责重叠~~ 通知相关表（messageNotifications / notificationPreferences）所有权已移交 notifications 模块，messaging 仅保留 messages 表
- ✅ P1-5 已修复：~~同时管理 3 类数据（messages + messageNotifications + notificationPreferences）~~ 仅管理 messages 表，通知相关数据由 notifications 模块管理
- ✅ P1 已修复：~~通知相关 Action 使用 `requireAuth()` 而非 `requirePermission()`~~ 通知 Action 已迁移至 notifications 模块并改为 `requirePermission(Permissions.MESSAGE_READ)`
- ✅ P1 已修复：~~`markMessageAsReadAction` / `deleteMessageAction` / `getMessageDetailAction` 缺少 Zod 校验~~ 已添加 `MessageIdSchema` 校验 messageId 参数
- ✅ P1 已修复：~~`updateNotificationPreferencesAction` 缺少 Zod 校验~~ 已添加 `UpdateNotificationPreferencesSchema` 校验 8 个布尔字段
- ✅ P2 已修复：`data-access.ts` 中 3 处 `or(...)!` 非空断言清理为安全守卫（条件 push）
- ✅ P0-b 已修复：~~`notification-preferences.ts` re-export shim 文件~~ 已删除（通知模块去重），8 个消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`，消除 messaging 模块对通知偏好的冗余 re-export 层
- ✅ P1 已修复：~~全模块零 i18n，中英文案硬编码~~ 所有组件接入 next-intl（`useTranslations("messages")`），新增 `src/shared/i18n/messages/{zh-CN,en}/messages.json` 翻译字典（title/description/tabs/actions/form/status/meta/notificationType/search/empty/messages/error 共 13 个命名空间）；所有页面 `page.tsx` 使用 `generateMetadata` + `getTranslations` 替代硬编码 metadata
- ✅ P1 已修复：~~缺 Error Boundary~~ 新增 3 个 `error.tsx` 错误边界（`/messages`、`/messages/[id]`、`/messages/compose`），统一使用 `EmptyState` + i18n 错误文案 + 重试按钮
- ✅ P2 已修复：a11y 改进，`message-list.tsx` 添加 `aria-label` / `aria-hidden`
- ✅ P1-4 已修复：~~通知组件（notification-list.tsx / notification-dropdown.tsx）放在 messaging/components 下，直接 import notifications 模块类型和 messaging/actions~~ 两个组件已迁移至 `notifications/components/`，通知 CRUD Action 已迁移至 `notifications/actions.ts`，messaging 模块仅保留私信组件
- ✅ P1-5 已修复：~~页面层 `Promise.all` 编排 messaging 和 notifications 两个模块的 data-access~~ 新增 `getMessagesPageData` 编排函数，页面层仅调用单一函数
- ✅ P1-7 已修复：~~消息列表客户端 `useEffect` + `setTimeout` 防抖搜索未取消已发出的请求~~ 搜索逻辑抽离为 `useMessageSearch` hook（含防抖 + 请求竞态取消）；~~无分页 UI~~ 新增客户端分页 UI（PAGE_SIZE=20，ChevronLeft/ChevronRight 按钮）
- ✅ P1-9 已修复：~~`deleteMessage` 两个独立 UPDATE 无事务~~ 改为 `db.transaction` 包裹 senderDeletedAt 和 receiverDeletedAt 更新，保证原子性
- ✅ P2-11 已修复：~~发送/删除消息无埋点~~ `sendMessageAction` / `markMessageAsReadAction` / `deleteMessageAction` 新增 `trackEvent` 埋点（message.sent / message.marked_read / message.deleted）
- ✅ V2-P0-2 已修复：~~通知标题硬编码~~ `sendMessageAction` 通过 `getTranslations('messages')` 生成 i18n 通知标题（`notification.messageTitle` / `messageTitleNoSubject`）
- ✅ V2-P1-2 已修复：~~MessageList 客户端过滤冗余~~ 客户端过滤仅在初始数据（type=all）时执行，搜索结果已由服务端按 tab 过滤
- ✅ V2-P1-3 已修复：~~消息详情页分散编排~~ 新增 `getMessageDetailPageData` 编排函数，替代 page.tsx 中 `after()` + `getMessageById` + `markMessageAsRead` 的分散编排
- ✅ V2-P1-4 已修复：~~表单无服务端校验错误展示~~ `message-compose.tsx` 新增 `fieldErrors` 状态 + `aria-invalid` 字段级错误展示（receiverId/subject/content）
- ✅ V2-P2-1 已修复：~~轮询间隔魔法数字~~ `unread-message-badge.tsx` 轮询间隔提取为 `POLL_INTERVAL_MS` 常量（60_000ms）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | ~280 | 7 个私信 Server Action + 2 个通知偏好 Action（✅ P1-4：通知 CRUD Action 已迁移至 notifications 模块；✅ V2-P0-2：通知标题 i18n 化） |
| `data-access.ts` | ~290 | 私信 CRUD + `getMessagesPageData` 编排函数（✅ P1-5 新增）+ `getMessageDetailPageData` 编排函数（✅ V2-P1-3 新增） |
| `schema.ts` | 44 | 私信发送校验 + messageId 校验 + 通知偏好更新校验 |
| `types.ts` | 52 | 私信类型 + re-export 通知类型（向后兼容） |
| `hooks/use-message-search.ts` | ~60 | ✅ P1-7 新增：消息搜索 hook（防抖 + 请求竞态取消） |

**组件清单**：
| 组件 | 职责 |
|------|------|
| `components/message-list.tsx` | 消息列表（✅ P1-7：使用 `useMessageSearch` hook + 客户端分页 UI，PAGE_SIZE=20；✅ V2-P1-2：客户端过滤仅在初始数据时执行） |
| `components/message-detail.tsx` | 消息详情（含回复） |
| `components/message-compose.tsx` | 撰写新消息（✅ V2-P1-4：fieldErrors + aria-invalid 字段级错误展示） |
| `components/unread-message-badge.tsx` | 未读消息计数徽章（侧边栏，每 60 秒轮询 `getUnreadMessageCountAction`；✅ V2-P2-1：POLL_INTERVAL_MS 常量） |

**客户端行为**：
- `message-list.tsx`：客户端调用 `getMessagesAction` 搜索消息（useMessageSearch hook，400ms 防抖，请求竞态取消）；V2-P1-2 优化：客户端过滤仅在初始数据（type=all）时执行
- `unread-message-badge.tsx`：每 `POLL_INTERVAL_MS`（60_000ms）轮询 `getUnreadMessageCountAction` 刷新未读计数

---

## 2.14 notifications（通知分发模块）

**职责**：多渠道通知分发（SMS/Email/WeChat/InApp）+ 站内通知 CRUD + 通知偏好管理 + 通知 UI 组件。

**导出函数**：
- Actions：`sendNotificationAction` / `sendClassNotificationAction` / `getNotificationsAction` / `getUnreadNotificationCountAction` / `markNotificationAsReadAction` / `markAllNotificationsAsReadAction`（✅ P1-4 新增：后 4 个通知 CRUD Action 从 messaging 模块迁移）
- Dispatcher：`sendNotification(payload)` / `sendBatchNotifications(payloads)`
- Data-access：`createNotification` / `getNotifications` / `markNotificationAsRead` / `markAllNotificationsAsRead` / `getUnreadNotificationCount` / `getUserContactInfo` / `logNotificationSend` / `logNotificationSendBatch`（✅ P0-4 / P1-5 修复后从 messaging 迁移）
- Preferences：`getNotificationPreferences` / `upsertNotificationPreferences`（✅ P0-4 / P1-5 修复后从 messaging 迁移）
- Channels：`InAppChannelSender` / `SmsChannelSender` / `EmailChannelSender` / `WeChatChannelSender`
- Components：`NotificationList` / `NotificationDropdown`（✅ P1-4 新增：从 messaging/components 迁移）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`classes`（✅ P1-1 已修复：通过 classes data-access.getClassExists/getStudentIdsByClassId）
- 被依赖：`messaging`（✅ P0-4 / P1-5 已修复：messaging 通过 `sendNotification` dispatcher 发送通知；✅ P1-4 已修复：通知 UI 组件由 notifications 模块自持，messaging 不再反向依赖）、`layout`（✅ P1-4 已修复：通知下拉组件直接从 notifications 模块导入）、`app/(dashboard)/messages`（✅ P1-4 已修复：通知列表组件直接从 notifications 模块导入）

**已知问题**：
- ✅ P0-4 已修复：~~不拥有任何数据，全部依赖 messaging 模块~~ messageNotifications 和 notificationPreferences 表所有权已从 messaging 迁移至 notifications 模块
- ✅ P0 已修复：~~与 messaging 双向依赖~~ notifications 不再反向依赖 messaging，in-app-channel 改为静态导入本地 createNotification
- ✅ P1-1 已修复：~~`sendClassNotificationAction` 直查 `classes`/`classEnrollments`~~ 改为调用 `classes/data-access.getClassExists` / `getStudentIdsByClassId`
- ✅ P1-4 已修复：~~通知 UI 组件放在 messaging/components 下，直接 import notifications 类型和 messaging/actions~~ `notification-list.tsx` 和 `notification-dropdown.tsx` 已迁移至 `notifications/components/`，通知 CRUD Action 已从 messaging 迁移至 `notifications/actions.ts`，消除 UI 层跨模块耦合
- ✅ P2-11 已修复：~~通知标记已读无埋点~~ `markNotificationAsReadAction` / `markAllNotificationsAsReadAction` 新增 `trackEvent` 埋点（notification.marked_read / notification.marked_all_read）
- ✅ V2-P0-1 已修复：~~通知 i18n 键混在 messages.json 中~~ 新增独立的 `notifications.json` 命名空间（zh-CN/en），通知组件 `useTranslations` 从 `"messages"` 切换到 `"notifications"`；`src/i18n/request.ts` 新增 notifications 命名空间加载
- ✅ V2-P2-1 已修复：~~轮询间隔魔法数字~~ `notification-dropdown.tsx` 轮询间隔提取为 `POLL_INTERVAL_MS` 常量（30_000ms）
- ⚠️ P1：发送日志仅 console，无 `notification_logs` 表
- ✅ 渠道抽象优秀（接口 + 工厂 + Mock 实现）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `dispatcher.ts` | 152 | 渠道选择 + 并行分发 |
| `data-access.ts` | 177 | 站内通知 CRUD + 用户联系方式 + 日志（P0-4 / P1-5 修复后新增通知 CRUD） |
| `preferences.ts` | 166 | 通知偏好 CRUD（P0-4 / P1-5 修复后从 messaging 迁移） |
| `actions.ts` | ~260 | 6 个 Server Action（✅ P1-4：新增 4 个通知 CRUD Action） |
| `types.ts` | 120 | 通知负载 + 渠道配置 + 通知记录 + 偏好类型（P0-4 / P1-5 修复后扩充） |
| `index.ts` | ~75 | 对外导出入口（✅ P1-4：新增组件和 CRUD Action 导出） |
| `channels/*` | 5 文件 | 4 个渠道实现 |
| `components/notification-list.tsx` | ~140 | ✅ P1-4 新增（从 messaging 迁移）：通知列表组件 |
| `components/notification-dropdown.tsx` | ~180 | ✅ P1-4 新增（从 messaging 迁移）：通知下拉菜单组件 |

**组件清单**：
| 组件 | 职责 |
|------|------|
| `components/notification-list.tsx` | 通知列表（消息页底部，展示所有通知，支持标记已读；✅ V2-P0-1：useTranslations 命名空间从 "messages" 切换到 "notifications"） |
| `components/notification-dropdown.tsx` | 通知下拉菜单（站点头部，每 30 秒轮询 `getNotificationsAction` + `getUnreadNotificationCountAction`；✅ V2-P0-1：useTranslations 命名空间切换；✅ V2-P2-1：POLL_INTERVAL_MS 常量） |

**客户端行为**：
- `notification-dropdown.tsx`：每 `POLL_INTERVAL_MS`（30_000ms）轮询 `getNotificationsAction`（pageSize=10）和 `getUnreadNotificationCountAction` 刷新通知和未读计数

---

## 2.15 audit（审计模块）

**职责**：操作日志 + 登录日志 + 数据变更日志查询与导出。

**导出函数**：
- Actions：`getAuditLogsAction` / `getLoginLogsAction` / `getDataChangeLogsAction` / `exportAuditLogsAction` / `exportLoginLogsAction` / `exportDataChangeLogsAction`
- Data-access：`getAuditLogs` / `getLoginLogs` / `getDataChangeLogs` / `getAuditModuleOptions`

**依赖关系**：
- 依赖：`shared/*`、`@/auth`
- 被依赖：无

**已知问题**：
- ⚠️ P2：Excel 导出逻辑内联在 actions 层（应抽取到 `export.ts`）
- ⚠️ P2：三个导出 Action 结构高度重复
- ✅ P2 已修复：`data-access.ts` 中 6 处 catch 块添加 `console.error` 输出错误上下文；`toIso`/`clampPageSize`/`clampPage` 工具函数补齐显式返回类型
- ✅ data-access 职责清晰，无跨模块问题

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 212 | 6 个 Server Action（含内联导出） |
| `data-access.ts` | 260 | 日志查询 |
| `types.ts` | - | 类型定义 |

---

## 2.16 announcements（公告模块）

**职责**：公告 CRUD + 发布/归档 + 发布通知。

**导出函数**：
- Actions：`getAnnouncementsAction` / `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction`（✅ P1-2 已修复：actions 层不再直接访问 DB，全部下沉到 data-access；✅ 发布公告时触发通知模块 `sendBatchNotifications`）
- Data-access：`getAnnouncements`（支持 `audience` 受众过滤）/ `getAnnouncementById` / `insertAnnouncement` / `updateAnnouncementById` / `deleteAnnouncementById` / `publishAnnouncementById` / `archiveAnnouncementById`（后 5 个为 P1-2 新增）/ `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData`（✅ P1-5 新增：管理端列表页和编辑页编排函数，页面层仅调用单一函数）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`school`（获取年级列表）、`classes`（获取班级列表 + 解析受众）、`users`（获取目标用户 ID 列表）、`notifications`（发布公告时发送通知）
- 被依赖：无

**已知问题**：
- ✅ P1-2 已修复：~~所有写操作直接在 actions 层 `db.insert/update/delete`，未下沉到 data-access~~ 写操作已下沉到 data-access（5 个新函数）
- ✅ P2 已修复：~~`getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(ANNOUNCEMENT_READ)`~~ 改为 `requirePermission(Permissions.ANNOUNCEMENT_READ)`
- ✅ 已修复：用户端列表页传入 `audience` 受众过滤（school/grade/class），管理端返回所有公告
- ✅ 已修复：用户端新增公告详情页 `/announcements/[id]`（只读模式）
- ✅ 已修复：管理端列表页传递 `classes` 数据给 `AdminAnnouncementsView`
- ✅ 已修复：发布公告时（`publishAnnouncementAction` / `createAnnouncementAction` 直接发布 / `updateAnnouncementAction` 状态变为 published）触发通知模块 `sendBatchNotifications`
- ✅ 已修复：新增 `loading.tsx` 骨架屏（用户端 + 管理端）
- ✅ P1 已修复：~~全模块零 i18n，中英文案硬编码~~ 所有组件接入 next-intl（`useTranslations("announcements")`），新增 `src/shared/i18n/messages/{zh-CN,en}/announcements.json` 翻译字典（title/description/filter/status/type/form/actions/messages/meta/empty/error 共 11 个命名空间）；所有页面 `page.tsx` 使用 `generateMetadata` + `getTranslations` 替代硬编码 metadata
- ✅ P1 已修复：~~缺 Error Boundary~~ 新增 4 个 `error.tsx` 错误边界（`/announcements`、`/announcements/[id]`、`/admin/announcements`、`/admin/announcements/[id]`），统一使用 `EmptyState` + i18n 错误文案 + 重试按钮
- ✅ P2 已修复：a11y 改进，`announcement-card.tsx` / `announcement-detail.tsx` 添加 `aria-label`
- ✅ P1-5 已修复：~~页面层 `Promise.all` 编排 announcements/school/classes 三个模块的 data-access~~ 新增 `getAdminAnnouncementsPageData` 和 `getEditAnnouncementPageData` 编排函数，页面层仅调用单一函数
- ✅ P1-6 已修复：~~`targetGradeId` / `targetClassId` 为 optional，未根据 `type` 做条件必填校验~~ `CreateAnnouncementSchema` 和 `UpdateAnnouncementSchema` 添加 `superRefine(refineAudience)`，年级公告强制 `targetGradeId`，班级公告强制 `targetClassId`
- ✅ P2-11 已修复：~~发布/归档/删除公告无埋点~~ `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction` 新增 `trackEvent` 埋点（announcement.created / announcement.updated / announcement.published / announcement.deleted / announcement.archived）
- ✅ V2-P0-2 已修复：~~通知标题硬编码~~ `createAnnouncementAction` / `updateAnnouncementAction` / `publishAnnouncementAction` 通过 `getTranslations('announcements')` 生成 i18n 通知标题（`notification.publishedTitle` / `publishedContent`）
- ✅ V2-P1-1 已修复：~~AnnouncementList 客户端 useState/useMemo 过滤~~ 改为纯服务端过滤模式，Select 切换仅更新 URL `?status=` 触发 RSC 重新渲染
- ✅ V2-P1-4 已修复：~~表单无服务端校验错误展示~~ `announcement-form.tsx` 新增 `fieldErrors` 状态 + `aria-invalid` 字段级错误展示（title/content/targetGradeId/targetClassId）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | ~330 | 6 个 Server Action + 通知触发逻辑 + trackEvent 埋点（P1-2 已修复，无直接 DB 操作） |
| `data-access.ts` | ~230 | 公告 CRUD + 发布/归档 + 受众过滤 + `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData` 编排函数（✅ P1-5 新增） |
| `schema.ts` | ~70 | Zod 校验 + `refineAudience` 条件校验（✅ P1-6 新增 superRefine） |
| `types.ts` | ~65 | 类型定义（`GetAnnouncementsParams` 新增 `audience` 字段） |

**组件清单**：
| 组件 | 职责 |
|------|------|
| `components/announcement-list.tsx` | 公告列表（用户端，支持状态筛选；✅ V2-P1-1：纯服务端过滤，Select 切换更新 URL ?status= 触发 RSC 重新渲染） |
| `components/announcement-card.tsx` | 公告卡片（列表项） |
| `components/announcement-detail.tsx` | 公告详情（只读） |
| `components/announcement-form.tsx` | 公告表单（创建/编辑，✅ P1-6：条件校验由 schema superRefine 保证；✅ V2-P1-4：fieldErrors + aria-invalid 字段级错误展示） |
| `components/admin-announcements-view.tsx` | 管理端公告视图（列表 + 筛选） |

---

## 2.17 files（文件模块）

**职责**：文件附件 CRUD + 批量删除 + 统计。

**导出函数**：
- Data-access：`getAllFileAttachments` / `getFileAttachmentsByOwner` / `getFileAttachmentById` / `createFileAttachment` / `updateFileAttachment` / `deleteFileAttachment` / `batchDeleteFileAttachments` / `getFileStats`（✅ P2 已修复：7 个读函数使用 `React.cache()` 包装实现请求级 memoization：`getFileAttachment` / `getFileAttachmentsByTarget` / `getFileAttachmentsByUploader` / `getAllFileAttachments` / `getFileAttachmentsWithFilters` / `getFileStats` / `getFileAttachmentsByIds`）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`
- 被依赖：`app/api/upload` / `app/api/files/[id]` / `app/api/files/batch-delete`

**已知问题**：
- ✅ P2-13 已修复：~~所有函数 try-catch 吞错误返回空数组/null~~ 所有 catch 块已添加 `console.error` 输出错误上下文
- ✅ P2 已修复：`getFileAttachmentsWithFilters` 中 `or(...)!` 非空断言清理为安全守卫
- ✅ P2 已修复：~~`getFileAttachmentsWithFilters` 中 `conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注
- ⚠️ P2：无 `actions.ts`，data-access 被路由直接调用
- ✅ 职责单一，不跨模块查询

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 267 | 文件 CRUD + 批量删除 + 统计 |
| `types.ts` | - | 类型定义 |
| `components/*` | 6 文件 | 上传/列表/预览/管理 |

---

## 2.18 course-plans（课程计划模块）

**职责**：课程计划 CRUD + 周计划项 CRUD + 排序。

**导出函数**：
- Actions：`getCoursePlansAction` / `getCoursePlanByIdAction` / `createCoursePlanAction` / `updateCoursePlanAction` / `deleteCoursePlanAction` / `createCoursePlanItemAction` / `updateCoursePlanItemAction` / `deleteCoursePlanItemAction` / `toggleCoursePlanItemCompletedAction`
- Data-access：与 actions 对应

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`classes`（合理，getAdminClasses/getStaffOptions）、`school`（合理，getAcademicYears）
- 被依赖：无

**已知问题**：
- ⚠️ P2：`getSubjectOptions` 直查 `subjects` 表（subjects 无独立模块，可接受）
- ✅ P2 已修复：`data-access.ts` 中 3 处 catch 块添加 `console.error` 输出错误上下文（getCoursePlans/getCoursePlanById/getSubjectOptions）
- ✅ actions 层使用 `handleError`/`revalidatePlanPaths` 辅助函数（良好范例）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 320 | 课程计划 + 周计划项 CRUD |
| `actions.ts` | 265 | 9 个 Server Action |
| `schema.ts` | - | Zod 校验 |
| `types.ts` | - | 类型定义 |

---

## 2.19 parent（家长模块）

**职责**：家长视角的子女数据聚合与展示。

**导出函数**：
- Data-access：`getChildren` / `getChildBasicInfo` / `getChildDashboardData` / `getParentDashboardData` / `verifyParentChildRelation` / `getChildNameList`（✅ v4 新增：用于详情页头部多子女切换器，一次批量查询避免 N+1）
- Components：`ParentDashboard` / `ChildCard` / `ChildDetailHeader` / `ChildDetailPanel` / `SiblingSwitcher` / `ChildHomeworkSummary` / `ChildHomeworkDetail`（v4 新增）/ `ChildGradeSummary` / `ChildGradeDetail`（v4 新增）/ `ChildScheduleCard` / `ParentChildrenDataPage` / `ParentNoChildrenPage` / `ParentAttentionBanner`（v4 新增）/ `ParentAttendanceWarning`（v4 新增）/ `ParentAttendanceRateCard`（v4 新增）/ `ParentAttendanceCalendar`（v4 新增）/ `ParentExportButton`（v4 新增）

**v4 修复（产品/UX 维度）**：
- ✅ FEAT-G01：新增 `/parent/leave` 请假申请占位页（含 loading.tsx）
- ✅ FEAT-G02：详情页 Schedule Tab 支持完整周课表（新增 `weeklySchedule` 字段 + `ChildWeeklyScheduleItem` 类型 + `buildWeeklySchedule` 函数）
- ✅ FEAT-G03：详情页 Grades Tab 新增 `ChildGradeDetail` 按科目分组展示（平均分、趋势、最近成绩）
- ✅ FEAT-G04：详情页 Homework Tab 新增 `ChildHomeworkDetail` 展示完整作业信息（状态、截止、提交时间、尝试次数）
- ✅ FEAT-G05：考勤页新增 `ParentAttendanceWarning` 异常预警横幅（聚合缺勤/迟到/低出勤率）
- ✅ FEAT-G06：详情页底部新增"Contact Teacher"快捷入口
- ✅ FEAT-G07：详情页头部新增 `SiblingSwitcher` 多子女切换器
- ✅ LAYOUT-P01：仪表盘新增 `ParentAttentionBanner` 待办事项横幅
- ✅ LAYOUT-P02：`ChildCard` 突出 Overdue 异常（红色边框 + AlertTriangle 图标）
- ✅ LAYOUT-P03：仪表盘快捷入口改为 4 宫格大图标卡片
- ✅ LAYOUT-P04：详情页改为 Tab 布局（Overview/Homework/Grades/Schedule/Attendance/Diagnostic）
- ✅ LAYOUT-P05：详情页新增面包屑导航
- ✅ LAYOUT-P07：成绩趋势图 X 轴改用序号，避免日期重叠
- ✅ LAYOUT-P08：成绩页新增 `ParentExportButton` 导出按钮（占位）
- ✅ LAYOUT-P09：考勤页新增 `ParentAttendanceCalendar` 月历视图（按状态着色，支持按月切换）
- ✅ LAYOUT-P10：考勤异常高亮（与 FEAT-G05 同步实现）
- ✅ NAV-P02：Grades/Attendance 页面描述明确职责（多子女对比 vs 单子女详情）
- ✅ NAV-P03：详情页实现 `?tab=` 参数支持
- ✅ NAV-P04：所有 parent 路由新增 `loading.tsx` 骨架屏 + `error.tsx` 错误边界
- ✅ DATA-P02：成绩卡片新增 TrendIcon 进步/退步/持平标识
- ✅ DATA-P03：排名展示新增"Top X%"百分比
- ✅ DATA-P04：作业列表新增科目标识 Badge
- ✅ DATA-P05：作业分数显示新增"pts"单位
- ✅ DATA-P06：考勤页新增 `ParentAttendanceRateCard` 出勤率汇总卡片
- ✅ HABIT-P01：仪表盘"一眼定位异常"能力（AttentionBanner 聚合）
- ✅ HABIT-P02：待办横幅作业项直接跳转详情页 homework tab（1 次点击到达）
- ✅ HABIT-P03：多子女切换无需返回仪表盘
- ✅ HABIT-P06：仪表盘展示未读/待办数量
- ✅ A11Y-P02：Overdue 状态增加 AlertTriangle 图标辅助
- ✅ A11Y-P04：成绩图表容器新增 aria-label
- ✅ PERF-P01/P02：骨架屏 + 错误边界
- ✅ PERF-P03：空状态新增"Contact support"引导按钮
- ✅ PERF-P04：`ChildCard` Link 添加 `prefetch`
- ✅ MOBILE-P03：移动端子女卡片改为水平滑动 Carousel（snap-x）
- ✅ MOBILE-P04：作业/成绩列表项 `min-h-[44px]` 触摸区域

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`classes`（合理）、`homework`（合理）、`grades`（合理）、`users`（合理）、`school`（合理）、`attendance`（v4 新增：考勤页复用 `StudentAttendanceView`；⚠️ 跨模块 UI 类型依赖：3 个组件直接 import `@/modules/attendance/types`）
- 被依赖：无

**已知问题**：
- ⚠️ P1：3 个 parent 组件（`parent-attendance-warning.tsx`/`parent-attendance-rate-card.tsx`/`parent-attendance-calendar.tsx`）直接 import `@/modules/attendance/types`，违反模块解耦原则（应通过 data-access 接口或 shared 类型抽象）
- ⚠️ P1：parent-attendance-calendar.tsx 重新定义 `STATUS_DOT`/`STATUS_LABEL` 常量，与 attendance 模块重复
- ⚠️ P1：3 个 parent 组件的纯函数（`buildWarnings`/`aggregate`/`rateTone`/`formatDateKey`/`parseDateKey`/`buildCalendarDays`/`isSameDay`）未导出，无法单测
- ✅ P1 已修复：~~`app/(dashboard)/parent/children/[studentId]/page.tsx` 直接访问 DB（违反三层架构）~~ 改为调用 `verifyParentChildRelation` data-access 函数
- ✅ P1 已修复：~~权限校验未加 parentId 条件，存在信息泄露风险~~ `verifyParentChildRelation` 同时按 parentId + studentId 过滤
- ✅ P2 已修复：~~`getChildBasicInfo` 多次串行查询~~ 改为 `Promise.all` 并行化，并使用 `getStudentActiveClass` 一次 JOIN
- ✅ P2 已修复：~~`getGradeOptions` 全量查询效率低~~ 改为 `getGradeNameById` 按 ID 查询
- ✅ P2 已修复：~~`buildHomeworkSummary` 中 `[...assignments].sort()` 不必要拷贝~~ 改为 `toSorted()`
- ✅ P2 已修复：~~`in7Days` 死代码~~ 已删除
- ⚠️ v4 保留：`/parent/leave` 为占位页，待后端实现请假审批流后接入
- ⚠️ v4 保留：`ParentExportButton` 为占位，待后端实现成绩导出 Server Action 后接入
- ⚠️ v4 保留：详情页 Attendance/Diagnostic Tab 为占位提示，待对应功能实现后填充
- ✅ 职责单一，正确复用其他模块 data-access

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 243 | 子女关系 + 仪表盘数据聚合 + 关系校验 + 子女姓名列表（v4 新增 `getChildNameList` + `buildWeeklySchedule`） |
| `types.ts` | 79 | 类型定义（含 JSDoc，v4 新增 `ChildWeeklyScheduleItem`） |
| `components/parent-dashboard.tsx` | 110 | 仪表盘（v4 重构：待办横幅 + 宫格快捷入口 + 移动端水平滑动） |
| `components/parent-attention-banner.tsx` | 128 | v4 新增：待办事项/异常聚合横幅（作业项直接跳转详情页 homework tab） |
| `components/parent-attendance-warning.tsx` | 89 | v4 新增：考勤异常预警 |
| `components/parent-attendance-rate-card.tsx` | 105 | v4 新增：考勤出勤率汇总卡片 |
| `components/parent-attendance-calendar.tsx` | 175 | v4 新增：考勤月历视图（use client） |
| `components/parent-export-button.tsx` | 50 | v4 新增：成绩导出按钮（占位） |
| `components/child-card.tsx` | 148 | 子女卡片（v4 增强：异常突出 + 趋势图标） |
| `components/child-detail-header.tsx` | 78 | 详情页头部（v4 增强：面包屑） |
| `components/child-detail-panel.tsx` | 200 | 详情页 Tab 面板 + SiblingSwitcher（v4 重写，集成 Homework/Grade Detail） |
| `components/child-homework-summary.tsx` | 147 | 作业摘要（v4 增强：科目标识 + 触摸区域 + pts 单位） |
| `components/child-homework-detail.tsx` | 145 | v4 新增：作业详情视图（完整作业信息） |
| `components/child-grade-summary.tsx` | 159 | 成绩趋势（v4 增强：趋势图标 + aria-label） |
| `components/child-grade-detail.tsx` | 165 | v4 新增：成绩详情视图（按科目分组分析） |
| `components/child-schedule-card.tsx` | 119 | 课表卡片（v4 增强：周课表视图） |
| `components/parent-children-data-page.tsx` | 92 | 共享数据页（v4 增强：headerExtra） |

**路由清单**：
| 路由 | 文件 | 说明 |
|------|------|------|
| `/parent/dashboard` | `dashboard/page.tsx` + `loading.tsx` | 家长仪表盘 |
| `/parent/grades` | `grades/page.tsx` + `loading.tsx` | 多子女成绩聚合 |
| `/parent/diagnostic` | `diagnostic/page.tsx` + `loading.tsx` + `error.tsx` | P2-5 新增：多子女学情诊断聚合 |
| `/parent/attendance` | `attendance/page.tsx` + `loading.tsx` | 多子女考勤聚合（v4 新增预警横幅） |
| `/parent/leave` | `leave/page.tsx` + `loading.tsx` | v4 新增：请假申请（占位） |
| `/parent/children/[studentId]` | `children/[studentId]/page.tsx` + `loading.tsx` | 子女详情页（v4 重构：Tab 布局 + 多子女切换） |
| `error.tsx` | `error.tsx` | v4 新增：错误边界 |

---

## 2.20 elective（选课模块）

**职责**：选修课程管理 + 学生选课 + 抽签。

**导出函数**：
- Actions（11 个）：`getElectiveCoursesAction` / `createElectiveCourseAction` / `updateElectiveCourseAction` / `deleteElectiveCourseAction` / `getStudentSelectionsAction` / `selectCourseAction` / `dropCourseAction` / `runLotteryAction` / `getAvailableCoursesForStudentAction` / `openSelectionAction` / `closeSelectionAction`
- Data-access：`getElectiveCourses` / `getElectiveCourseById` / `createElectiveCourse` / `updateElectiveCourse` / `deleteElectiveCourse` / `openSelection` / `closeSelection` / `buildCourseSelect` / `mapCourseRow` / `resolveCourseDisplayNames` / `CourseCoreRow`（P3 新增导出，供 data-access-selections 复用）
- Data-access-operations：`selectCourse` / `dropCourse` / `runLottery` / `buildLotteryRankCase`（⚠️ 未导出，无法单测）
- Data-access-selections：`getCourseSelections` / `getStudentSelections` / `getStudentGradeId` / `getAvailableCoursesForStudent`
- Components：`ElectiveCourseList`（课程卡片网格 + 管理操作）/ `ElectiveCourseForm`（课程创建/编辑表单）/ `ElectiveFilters`（nuqs 筛选栏）/ `StudentSelectionView`（学生选课视图）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`school`（✅ P3 已修复：通过 school data-access.getSubjectOptions/getGradeOptions 获取科目/年级名称，不再直查 subjects/grades 表）、`users`（✅ P3 已修复：通过 users data-access.getUserNamesByIds 获取教师姓名，不再直查 users 表）、`classes`（通过 classes data-access.getStudentActiveGradeId 获取学生年级）
- 被依赖：无

**已知问题**（详见 `docs/architecture/audit/attendance-elective-audit-report.md`）：
- ❌ P0：3 个读 Action 无调用方（`getElectiveCoursesAction`/`getStudentSelectionsAction`/`getAvailableCoursesForStudentAction`），页面绕过 Action 直接调用 data-access
- ❌ P0：update/delete/select/drop/lottery Action 缺资源归属校验（教师 A 可操作教师 B 的课程，学生可退选他人课程）
- ❌ P0：i18n 完全缺失（4 组标签/颜色常量硬编码英文，组件中硬编码中文）
- ❌ P0：错误边界完全缺失（3 个角色目录均无 `error.tsx`）
- ⚠️ P1：`elective-course-form.tsx` 存在 `v as "fcfs" | "lottery"` 类型断言
- ⚠️ P1：`elective-course-list.tsx` 存在 `null as never` 类型逃逸
- ⚠️ P1：`buildLotteryRankCase` 未导出，无法单测
- ⚠️ P1：`SELECTION_MODE_LABELS` 已定义但表单未复用，硬编码 Select 选项
- ✅ P1 已修复：~~`buildCourseSelect` 跨模块 join users/subjects/grades 表~~ 改为只查 electiveCourses 表，通过 `resolveCourseDisplayNames` 调用 school/users data-access 获取显示名称
- ✅ P1 已修复：~~`getSubjectOptions` 本地直查 subjects 表且与 school 模块重复~~ 删除本地实现，改用 `school/data-access.getSubjectOptions`
- ✅ P1 已修复：~~`selectCourse`/`dropCourse` 缺事务包裹~~ 改为 `db.transaction` 包裹，FCFS 模式下使用 `FOR UPDATE` 行锁防止并发超卖
- ✅ P2 已修复：~~`mapCourseRow` 在 data-access.ts 与 data-access-selections.ts 重复定义~~ 抽取到 data-access.ts 统一导出，data-access-selections.ts 复用
- ✅ P2 已修复：~~`runLottery` 使用 `sort(() => Math.random() - 0.5)` 有偏 shuffle~~ 改为 Fisher-Yates 无偏洗牌算法
- ✅ P2 已修复：~~`selectCourse` FCFS 并发超卖风险~~ 使用 `db.transaction` + `.for("update")` 行锁
- ✅ 权限校验完整（ELECTIVE_MANAGE/SELECT/READ）

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 304 | 11 个 Server Action |
| `data-access.ts` | 250 | 课程 CRUD + scope 过滤 + 共享映射函数（P3 重构：移除跨模块 join，通过 school/users data-access 获取显示名称） |
| `data-access-operations.ts` | 245 | 选课操作（select/drop/lottery，P3 重构：事务包裹 + FOR UPDATE 锁 + Fisher-Yates 洗牌） |
| `data-access-selections.ts` | 149 | 选课记录查询 + 学生可选课程 |
| `schema.ts` | 132 | Zod 校验 |
| `types.ts` | 108 | 类型定义 + 4 组标签/颜色常量（硬编码英文） |
| `components/elective-course-list.tsx` | 233 | 课程卡片网格 + 管理操作 |
| `components/elective-course-form.tsx` | 293 | 课程创建/编辑表单 |
| `components/elective-filters.tsx` | 49 | nuqs 筛选栏（搜索 + 模式） |
| `components/student-selection-view.tsx` | 250 | 学生选课视图（已选 + 可选） |

---

## 2.21 proctoring（监考模块）

**职责**：考试监考事件记录 + 防作弊监控 + 监考面板。

**导出函数**：
- Actions：`recordProctoringEventAction` / `getProctoringDashboardAction`
- Data-access：`recordProctoringEvent` / `getExamSubmissionForProctoring` / `getExamForProctoring` / `getExamProctoringSummary` / `getStudentProctoringStatuses` / `getRecentProctoringEvents`（✅ P2 已修复：`getExamProctoringSummary` 使用 `Promise.all` 并行化考试信息与提交记录查询、事件类型统计与学生事件统计查询；合并两次 filter 为单次循环统计 started/submitted）

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`exams`（✅ P1-1 已修复：通过 exams data-access.getExamForProctoringCrossModule/getExamSubmissionForProctoringCrossModule/getExamSubmissionsForExam/getExamTitleById）、`users`（✅ P1-1 已修复：通过 users data-access.getUserNamesByIds）
- 被依赖：无

**已知问题**：
- ❌ P0：`exam-mode-config.tsx` 未集成到考试表单（死代码，监考功能无法启用）
- ✅ P0-6 已修复：~~事件上报存在 Server Action 与 REST API 双通道重复~~ 删除 `/api/proctoring/event` REST 路由（移至 deletes/），Server Action `recordProctoringEventAction` 为唯一规范路径
- ✅ P1-1 已修复：~~跨模块直查 `exams`/`examSubmissions`/`users`~~ 改为通过 exams/users data-access 函数获取数据
- ✅ P2 已修复：`actions.ts` 不再直接 import `db` 和 `examSubmissions`，submission 归属校验已下沉到 data-access；`recordProctoringEventAction` 改用 `requirePermission(EXAM_SUBMIT)` 并增加 `revalidatePath`
- ✅ P2 已修复：~~`getStudentProctoringStatuses` 串行查询（getUserNamesByIds 后再查事件）~~ 改为 `Promise.all` 并行拉取学生姓名与事件记录

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 409 | 事件记录 + 查询 + 摘要统计 + submission 归属校验 |
| `actions.ts` | 139 | 2 个 Server Action |
| `types.ts` | 136 | 类型定义 + 标签常量 + 阈值常量 |
| `components/anti-cheat-monitor.tsx` | - | 学生端防作弊监控 |
| `components/exam-mode-config.tsx` | - | 考试模式配置（**未集成**） |
| `components/proctoring-dashboard.tsx` | - | 教师监考面板 |

---

## 2.22 diagnostic（学情诊断模块）

**职责**：知识点掌握度查询 + 诊断报告生成。

**导出函数**：
- Actions：`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[]` 类型标注
- ✅ P0-2 已修复：~~`data-access-reports.ts` 直查 `users` 表获取姓名~~ 改为通过 `users/data-access.getUserNamesByIds` 跨模块接口
- ✅ P2-2 已修复：~~`class-diagnostic-view.tsx`/`student-diagnostic-view.tsx`/`mastery-radar-chart.tsx` 中存在 Tailwind 任意值~~ 改用标准 Tailwind 类（w-44/max-w-32/text-xs/h-96/max-w-lg）
- ✅ P2-1 已修复：~~图表/表格/列表缺少 a11y ARIA 属性~~ 为 5 个图表添加 `role="img"` + `aria-label`，2 个表格添加 `<caption>`，3 个列表添加 `role="list"`，3 个图标按钮添加 `aria-label`
- ✅ P2-3 已修复：~~班级报告将生成者 ID 存入 `studentId` 字段（schema 设计缺陷 workaround）~~ schema `learningDiagnosticReports.studentId` 改为可空，班级报告 `studentId` 置空，读取逻辑适配 null
- ✅ 与 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）
- Actions-avatar：`updateUserAvatarAction` / `removeUserAvatarAction`（✅ P2-8 新增：头像上传/删除，复用 `/api/upload` 路由）
- Actions-notifications：`sendTestNotificationAction`（✅ P2-10 新增：发送测试通知，占位实现待接入真实通知服务）
- Actions-system-settings：`getAdminSystemSettingsAction` / `saveAdminSystemSettingsAction`（✅ P0-3 新增：管理员系统设置 CRUD，4 分类 Zod 校验）
- Actions-security：`getSecurityCenterAction` / `toggleTwoFactorAction`（✅ P2-9 新增：2FA 状态查询/切换 + 最近登录历史）
- Data-access：`getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange`（P1 新增，从 actions 下沉）
- Data-access-system-settings：`getSystemSettingsByCategory` / `getAllSystemSettings` / `getSystemSetting` / `upsertSystemSetting` / `upsertSystemSettings`（✅ P0-3 新增：system_settings 表 CRUD，键值对存储模式）
- Components：`SettingsView`（统一设置页布局，5 标签页 General/Notifications/Appearance/Security/AI；角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入；Tab URL 持久化；每个 TabsContent 包裹 `SettingsSectionErrorBoundary` + `Suspense` 骨架屏；AI 标签页条件渲染需 `AI_CONFIGURE` 权限）、`SettingsServiceProvider` / `useSettingsService`（Context 注入 `SettingsService` 接口，解耦组件对 users/messaging actions 的直接依赖）、`SettingsSectionErrorBoundary`（分区 Error Boundary，局部失败不影响整页）、`QuickLinksCard`（快捷链接卡片，i18n 键驱动）、`ProfileStudentOverview` / `ProfileStudentOverviewSkeleton`（学生概览异步 Server Component + 骨架屏）、`ProfileTeacherOverview` / `ProfileTeacherOverviewSkeleton`（教师概览异步 Server Component + 骨架屏）、`AdminSettingsView`（✅ P0-3 已修复：从 mock 改为真实数据层，通过 Server Actions 加载/保存到 system_settings 表）、`AvatarUpload`（✅ P2-8 新增：头像上传/预览/删除客户端组件，文件验证 + i18n）、`SecurityCenterCard`（✅ P2-9 新增：2FA 开关 + 最近登录历史卡片）、`ThemePreferencesCard`（✅ P2-11 已增强：集成 `LocaleSwitcher` 语言切换）
- Config：`ROLE_SETTINGS_CONFIG` / `resolveRoleSettingsConfig`（配置驱动角色 → 设置视图映射，新增角色只需添加条目）
- Lib：`buildStudentOverviewData` / `computeStudentStats` / `sortUpcomingAssignments` / `filterTodaySchedule` / `toWeekday`（纯数据计算函数，与 UI 分离，便于单元测试）
- Types：`AiProviderSummary` / `AiProviderName` / `AiProviderExisting` / `SettingsService` / `ProfileService` / `NotificationPreferenceService`（服务接口定义，用于依赖注入解耦）

**依赖关系**：
- 依赖：`shared/*`（含 `shared/lib/bcrypt-utils`）、`@/auth`、`messaging`（页面层通过 `SettingsService` 接口注入，组件层不直接 import）、`users`（页面层通过 `SettingsService` 接口注入）、`classes` / `homework` / `dashboard`（ProfileStudentOverview 异步组件获取学生概览数据）、`notifications`（页面层获取通知偏好）
- 被依赖：无

**已知问题**：
- ⚠️ P2：混合 5 类职责（AI Provider + 密码 + 资料 + 主题 + 通知偏好）
- ✅ P1 已修复：~~无 `data-access.ts`，`actions.ts` 直接使用 `db`~~ 新建 `data-access.ts`，所有 DB 操作已下沉
- ✅ P1 已修复：~~`changePasswordAction` 使用 `requireAuth()` 无 Zod 校验~~ 改为 `requirePermission(USER_PROFILE_UPDATE)` + `ChangePasswordSchema` Zod 校验 + 并行查询优化
- ✅ P2 已修复：`actions-password.ts` 删除本地 `normalizeBcryptHash`，统一复用 `shared/lib/bcrypt-utils.normalizeBcryptHash`，消除重复代码
- ✅ P2-a 已修复：~~admin/teacher/student/parent 四个设置视图重复布局~~ 新增 `SettingsView` 统一设置页布局 + `resolveRoleSettingsConfig` 配置驱动角色路由，删除 `parent-settings-view.tsx` / `student-settings-view.tsx` / `teacher-settings-view.tsx`
- ✅ parent 角色路由已修复：通过 `ROLE_SETTINGS_CONFIG` 配置驱动，parent 用户正确渲染对应配置
- ✅ Tab URL 持久化已修复：`SettingsView` 改为受控模式，通过 `useSearchParams` 读取 `tab` 参数，`router.push` 更新 URL
- ✅ 登出二次确认已修复：`SettingsView` 的 Log out 按钮使用 `AlertDialog` 包裹，点击时弹出确认对话框
- ✅ AiProviderSettingsCard 已集成：`SettingsView` 新增 AI 标签页，条件渲染需 `AI_CONFIGURE` 权限
- ✅ password-change-form 任意值 Tailwind 类已修复：~~`[&>div]:bg-red-500` 等任意值类~~ Progress 组件新增 `indicatorClassName` prop，使用标准颜色类
- ✅ P0 已修复：~~`notification-preferences-form.tsx` 跨模块直接 import messaging/actions~~ 改为通过 `useSettingsService().notifications.updatePreferences` 调用，页面层注入实现
- ✅ P0 已修复：~~`profile-settings-form.tsx` 跨模块直接 import users/actions~~ 改为通过 `useSettingsService().profile.updateProfile` 调用，页面层注入实现
- ✅ P0 已修复：~~i18n 完全缺失~~ 新增 `settings.json` 翻译文件（zh-CN + en），所有组件改用 `useTranslations` / `getTranslations`
- ✅ P1 已修复：~~缺少 Error Boundary~~ 新增 `SettingsSectionErrorBoundary`，每个 TabsContent + profile 页面角色概览区块均包裹
- ✅ P1 已修复：~~缺少 Suspense 骨架屏~~ 每个 TabsContent 包裹 `Suspense` + `SettingsSectionSkeleton`；profile 页面包裹 `ProfileStudentOverviewSkeleton` / `ProfileTeacherOverviewSkeleton`
- ✅ P1 已修复：~~profile/page.tsx 业务逻辑与 UI 混合~~ 抽取 `buildStudentOverviewData` 等纯函数到 `lib/student-overview-data.ts`；拆分 `ProfileStudentOverview` / `ProfileTeacherOverview` 异步组件
- ✅ 密码修改有速率限制
- ✅ AI Provider 操作有 `AI_CONFIGURE` 权限校验
- ✅ P0-3 已修复：~~AdminSettingsView 为 mock 实现，无数据持久化~~ 新增 `system_settings` 表（键值对存储）+ `data-access-system-settings.ts` + `actions-system-settings.ts`，AdminSettingsView 改为真实数据层
- ✅ P2-8 已修复：~~无头像上传~~ 新增 `updateUserAvatar` data-access + `actions-avatar.ts` + `AvatarUpload` 组件，复用 `/api/upload` 路由
- ✅ P2-9 已修复：~~无 2FA / 会话管理~~ 新增 `actions-security.ts` + `SecurityCenterCard` 组件（2FA 开关占位 + 最近登录历史来自 login_logs 表）
- ✅ P2-10 已修复：~~通知偏好表单无测试通知按钮~~ 新增 `sendTestNotificationAction`，每个已启用渠道旁显示测试按钮
- ✅ P2-11 已修复：~~语言切换未集成到设置页~~ `ThemePreferencesCard` 集成 `LocaleSwitcher` 到 Appearance 标签页

**文件清单**：
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 160 | AI Provider CRUD + 测试（P1 已修复，无直接 DB 操作） |
| `actions-password.ts` | 87 | 修改密码（P1 已修复：requirePermission + Zod + data-access） |
| `actions-avatar.ts` | 56 | 头像上传/删除（P2-8 新增：requirePermission + revalidatePath） |
| `actions-notifications.ts` | 46 | 发送测试通知（P2-10 新增：占位实现待接入真实通知服务） |
| `actions-system-settings.ts` | 186 | 管理员系统设置 CRUD（P0-3 新增：4 分类 Zod 校验 + upsert） |
| `actions-security.ts` | 165 | 2FA 状态查询/切换 + 最近登录历史（P2-9 新增） |
| `data-access.ts` | 158 | AI Provider CRUD + 密码修改 DB 操作（P1 新增） |
| `data-access-system-settings.ts` | 119 | system_settings 表 CRUD（P0-3 新增：键值对存储模式） |
| `types.ts` | 60 | 类型定义（AiProviderSummary + SettingsService/ProfileService/NotificationPreferenceService 接口） |
| `config/role-settings-config.tsx` | 85 | 角色设置页配置驱动映射（ROLE_SETTINGS_CONFIG + resolveRoleSettingsConfig） |
| `lib/student-overview-data.ts` | 150 | 学生概览纯数据计算（buildStudentOverviewData + computeStudentStats 等，便于单测） |
| `components/settings-view.tsx` | 236 | SettingsView 统一设置页布局（5 标签页 + Error Boundary + Suspense + i18n + SecurityCenterCard 集成） |
| `components/settings-service-context.tsx` | 39 | SettingsServiceProvider + useSettingsService（Context 注入服务接口） |
| `components/settings-section-error-boundary.tsx` | 64 | 分区 Error Boundary（局部失败不影响整页） |
| `components/quick-links-card.tsx` | 42 | 快捷链接卡片（i18n 键驱动） |
| `components/profile-student-overview.tsx` | 91 | 学生概览异步 Server Component + 骨架屏 |
| `components/profile-teacher-overview.tsx` | 115 | 教师概览异步 Server Component + 骨架屏 |
| `components/admin-settings-view.tsx` | 425 | AdminSettingsView 系统设置视图（P0-3 已修复：真实数据层 + 4 个 Card + i18n） |
| `components/avatar-upload.tsx` | ~150 | 头像上传/预览/删除客户端组件（P2-8 新增：文件验证 + i18n） |
| `components/security-center-card.tsx` | ~240 | 安全中心卡片（P2-9 新增：2FA 开关 + 最近登录历史） |
| `components/profile-settings-form.tsx` | 158 | 个人资料表单（通过 SettingsService 注入，i18n） |
| `components/notification-preferences-form.tsx` | ~160 | 通知偏好表单（通过 SettingsService 注入，i18n + P2-10 测试按钮） |
| `components/password-change-form.tsx` | ~130 | 密码修改表单（i18n + a11y） |
| `components/theme-preferences-card.tsx` | ~80 | 主题偏好卡片（i18n + P2-11 LocaleSwitcher 集成） |
| `components/ai-provider-settings-card.tsx` | ~200 | AI 服务商配置卡片（i18n） |

---

## 2.24 auth（认证 UI 模块）

**职责**：认证页面 UI（登录/注册/布局）。

**导出函数**：纯 UI 组件（`LoginForm` / `RegisterForm` / `AuthLayout`）

**依赖关系**：
- 依赖：`shared/*`
- 被依赖：`app/(auth)/*`

**已知问题**：
- ✅ 纯 UI 模块，无 data-access/actions/types
- ✅ 认证逻辑由 NextAuth + `shared/lib/auth-guard` 统一处理

**文件清单**：
| 文件 | 职责 |
|------|------|
| `components/auth-layout.tsx` | 认证页面布局 |
| `components/login-form.tsx` | 登录表单 |
| `components/register-form.tsx` | 注册表单 |

---

## 2.25 layout（布局模块）

**职责**：应用骨架（侧边栏 + 顶部导航 + 导航配置 + 多角色切换）。

**导出函数**：`AppSidebar` / `SidebarProvider` / `SiteHeader` + `navigation` 配置

**依赖关系**：
- 依赖：`shared/hooks/use-permission`、`@/auth`（useSession）、`messaging`（通知下拉）、`shared/components/ui/select`（角色切换下拉）
- 被依赖：`app/(dashboard)/layout.tsx`

**已知问题**：
- ✅ P2 已修复：~~用权限反推角色~~ `app-sidebar.tsx` 改用 `hasRole()` 判断角色，并新增多角色切换机制（`SidebarContext.currentRole`/`setCurrentRole`，null 表示自动检测；当用户拥有多个角色时在侧边栏底部显示 `Select` 下拉切换）
- ✅ navigation.ts 无幽灵路由（13 个已修复）

**文件清单**：
| 文件 | 职责 |
|------|------|
| `components/app-sidebar.tsx` | 侧边栏（根据权限渲染 + 多角色切换下拉） |
| `components/sidebar-provider.tsx` | 侧边栏状态 Context（含 `currentRole`/`setCurrentRole`） |
| `components/site-header.tsx` | 顶部导航（含通知下拉） |
| `config/navigation.ts` | 导航配置（4 个角色） |

---

## 2.26 student（学生 UI 模块）

**职责**：学生端 UI 组件（课程视图 + 课表筛选/视图）。

**导出函数**：`StudentCoursesView` / `StudentScheduleFilters` / `StudentScheduleView`

**依赖关系**：
- 依赖：`shared/*`
- 被依赖：`app/(dashboard)/student/*`

**已知问题**：
- ⚠️ P2：与 classes 模块的 `schedule-view.tsx`/`schedule-filters.tsx` 可能功能重叠
- ✅ 纯 UI 模块，数据由页面通过 classes data-access 获取
- ✅ 认证模式已统一：所有 student 页面使用 `getCurrentStudentUser()`（users 模块）或 `getAuthContext()`（shared 模块），不再直接调用 `auth()` 或 `getDemoStudentUser()`

**文件清单**：
| 文件 | 职责 |
|------|------|
| `components/student-courses-view.tsx` | 学生课程视图（含 `ClassCard` memo 组件 + 加入班级表单，使用 `useTransition`） |
| `components/student-schedule-filters.tsx` | 课表筛选器 |
| `components/student-schedule-view.tsx` | 学生课表视图 |

**路由文件清单**（`app/(dashboard)/student/`）：
| 文件 | 职责 |
|------|------|
| `dashboard/page.tsx` + `loading.tsx` | 学生仪表盘 + 骨架屏 |
| `attendance/page.tsx` + `loading.tsx` | 学生考勤 + 骨架屏 |
| `diagnostic/page.tsx` + `loading.tsx` | 学情诊断 + 骨架屏 |
| `elective/page.tsx` + `loading.tsx` | 选课中心 + 骨架屏 |
| `grades/page.tsx` + `loading.tsx` | 我的成绩 + 骨架屏 |
| `learning/assignments/page.tsx` + `loading.tsx` | 作业列表（含 `AssignmentCard` 组件）+ 骨架屏 |
| `learning/assignments/[assignmentId]/page.tsx` + `loading.tsx` | 作业作答/复习 + 骨架屏 |
| `learning/courses/page.tsx` + `loading.tsx` | 课程列表 + 骨架屏 |
| `learning/textbooks/page.tsx` + `loading.tsx` | 教材列表 + 骨架屏 |
| `learning/textbooks/[id]/page.tsx` + `loading.tsx` | 教材阅读 + 骨架屏 |
| `schedule/page.tsx` + `loading.tsx` | 课表 + 骨架屏 |
| `error.tsx` | 路由组错误边界（提供"重试"按钮） |

---

## 2.27 lesson-preparation（备课模块）

**职责**：教师备课，基于教材章节创建课案（**节点图编辑器 React Flow**），支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。

> 架构变更（2026-06-21）：编辑器从列表式（BlockRenderer + @dnd-kit）升级为节点图式（NodeEditor + @xyflow/react）。数据结构从 v1（blocks 数组）升级到 v2（nodes + edges 节点图），旧数据通过 `migrateV1ToV2()` 自动迁移。

**数据结构**：
- v1（已废弃，仅向后兼容读取）：`{ version: 1, blocks: Block[] }`
- v2（当前）：`{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }`
  - `LessonPlanNode`：`Block` + `position: { x, y }`（画布坐标）
  - `LessonPlanEdge`：`{ id, source, target, sourceHandle?, targetHandle? }`（节点间连线）

**导出函数**：
- Data-access（`data-access.ts`）：`getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2`（v1→v2 迁移：blocks 数组转换为 nodes + 线性 edges）/ `normalizeDocument`（规范化：确保 content 为 v2 格式，兼容旧数据）
- Data-access-versions（`data-access-versions.ts`）：`getLessonPlanVersions` / `createLessonPlanVersion` / `getVersionContent` / `revertToVersion` / `pruneAutoVersions`
- Data-access-templates（`data-access-templates.ts`）：`getLessonPlanTemplates` / `saveAsTemplate` / `deletePersonalTemplate`
- Data-access-knowledge（`data-access-knowledge.ts`）：`getLessonPlansByKnowledgePoint` / `getLessonPlansByQuestion`
- Publish-service（`publish-service.ts`）：`publishLessonPlanHomework`
- AI-suggest（`ai-suggest.ts`）：`suggestKnowledgePoints`
- Actions：`getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction`

**依赖关系**：
- 依赖：`shared/*`、`@/auth`、`shared/lib/ai`、`@xyflow/react`（节点图编辑器）、`textbooks`（只读章节/知识点树）、`questions`（创建/查询题目）、`exams`（创建 exam 草稿）、`homework`（创建作业下发）、`classes`（查询教师班级）、`files`（附件）
- 被依赖：无

**已知问题**：
- ✅ 通过对方 data-access 调用跨模块数据，无直查跨模块表
- ✅ data-access 按职责拆分为 4 个文件（data-access/data-access-versions/data-access-templates/data-access-knowledge）
- ✅ actions 按职责拆分为 4 个文件（actions/actions-publish/actions-ai/actions-kp）
- ✅ 编辑器架构升级：NodeEditor（React Flow 画布）+ NodeEditPanel（侧边内容编辑面板）+ LessonNode（自定义节点组件），支持节点拖拽、连线、画布缩放
- ⚠️ `block-renderer.tsx` 标记为 @deprecated（已被 NodeEditor 替代，保留用于向后兼容）

> 架构变更（2026-06-22，本次审计修复）：
> - **P0-1 跨模块直查修复**：`publish-service.ts` 不再直接 `db.insert(examQuestions)` 和本地实现 `getStudentIdsByClassIds`，改为调用 `exams/data-access.addExamQuestions` 和 `classes/data-access.getStudentIdsByClassIds`，恢复三层架构约束
> - **P0-2 i18n 接入**：新增 `shared/i18n/messages/zh-CN/lesson-preparation.json` 和 `shared/i18n/messages/en/lesson-preparation.json`，注册 `lessonPreparation` 命名空间到 `src/i18n/request.ts`，17 个组件改造为 `useTranslations`/`getTranslations`
> - **P0-3 DataScope 过滤修复**：`data-access.ts` 的 `buildScopeCondition` 按 scope 类型精确过滤——`class_taught` 增加 `subjectId IN teacher.subjects AND gradeId IN teacher.grades`，`grade_managed` 限制 `gradeId IN managedGrades`，`class_members`/`children` 仅允许查看 `published` 课案
> - **P1-1 类型安全修复**：`as never` 断言全部替换为类型守卫函数（`isQuestionType`/`isV1Document`/`isV2Document`）+ `validTypes` 数组；`block-renderer.tsx` 使用 `as ExerciseBlockData`/`as TextStudyBlockData` 精确断言；`constants.ts` 的 `BLOCK_TYPE_LABELS`/`LESSON_PLAN_STATUS_LABELS` 改为 i18n 键（`BLOCK_TYPE_KEYS`/`LESSON_PLAN_STATUS_KEYS`）
> - **P1 纯函数抽取**：新增 `lib/document-migration.ts`（migrateV1ToV2/normalizeDocument/buildInitialContent，使用类型守卫替代 as 断言）、`lib/node-summary.ts`（getNodeSummary + NODE_COLORS + getNodeColor，接受翻译函数注入）、`lib/rf-mappers.ts`（toRfNodes/toRfEdges/fromRfEdges），data-access.ts 改为从 lib/ 导入并 re-export 保持向后兼容
> - **P1-2/P1-3 错误边界 + 骨架屏**：新增 `components/lesson-plan-error-boundary.tsx`（LessonPlanErrorBoundary 类组件）和 `components/lesson-plan-skeleton.tsx`（VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton）
> - **P1-4 阻塞式 UI 修复**：`alert()` 全部替换为 `sonner` toast；`confirm()` 全部替换为 `AlertDialog`（shadcn）；`window.location.reload` 替换为 `router.refresh()`；涉及 lesson-plan-card/version-history-drawer/inline-question-editor/text-study-block/exercise-block
> - **P1-5/P1-7 多实例 + 角色配置驱动**：新增 `providers/lesson-plan-provider.tsx`（LessonPlanProvider + Context + 4 个角色配置 TEACHER/ADMIN/STUDENT/PARENT + LessonPlanDataService 接口 + LessonPlanTracker 埋点接口 + useLessonPlanContextSafe/useRoleConfig 等 hooks）和 `services/default-data-service.ts`（包装 Server Actions 为 DataService 实现），lesson-plan-list/lesson-plan-card 通过 Context 注入数据服务，useRoleConfig 控制按钮可见性
> - **P1-8 Block 注册表**：新增 `config/block-registry.tsx`（BLOCK_REGISTRY 配置表 + getBlockComponent/isRichTextBlock），`node-edit-panel.tsx` 重构为配置驱动渲染，移除 if/else 链
> - **P1-4 window.location.reload 修复**：`exercise-block.tsx` 改用 `router.refresh()` 精确刷新缓存
> - **P2-1 a11y 修复**：5 个组件（question-bank-picker/publish-homework-dialog/knowledge-point-picker/exercise-block/text-study-block）添加 `role="dialog"`/`aria-modal`/`aria-label`；inline-question-editor 添加 `role="dialog"`/`aria-modal`/`aria-label`
> - **P2-4 监控埋点预留**：`providers/lesson-plan-provider.tsx` 定义 `LessonPlanTracker` 接口 + `noopTracker` 默认空实现，生产环境可替换为真实埋点

**文件清单**：
| 文件 | 职责 |
|------|------|
| `types.ts` | 类型定义（含 v1/v2 文档类型、LessonPlanNode、LessonPlanEdge） |
| `constants.ts` | 常量定义 |
| `schema.ts` | Zod 验证 |
| `lib/document-migration.ts` | **纯函数**：v1→v2 迁移（migrateV1ToV2）/ 规范化（normalizeDocument）/ 初始内容构建（buildInitialContent），使用类型守卫 isV1Document/isV2Document 替代 as 断言 |
| `lib/node-summary.ts` | **纯函数**：getNodeSummary（接受翻译函数注入，支持 i18n）+ NODE_COLORS + getNodeColor |
| `lib/rf-mappers.ts` | **纯函数**：toRfNodes/toRfEdges/fromRfEdges（LessonPlanNode/Edge ↔ React Flow Node/Edge 映射） |
| `config/block-registry.tsx` | **配置驱动**：BLOCK_REGISTRY 注册表 + getBlockComponent/isRichTextBlock，node-edit-panel 通过配置渲染 Block |
| `providers/lesson-plan-provider.tsx` | **Provider + Context（P1-5/P1-7/P2-4/V2-6）**：LessonPlanProvider 注入数据服务/角色配置/埋点；定义 LessonPlanDataService 接口、4 个角色配置（TEACHER/ADMIN/STUDENT/PARENT）、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker；hooks：useLessonPlanContextSafe（返回 null 不抛错）/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe（V2-6 新增，返回 noopTracker 不抛错） |
| `services/default-data-service.ts` | **默认数据服务实现**：createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现，测试可替换为 mock |
| `data-access.ts` | 课案 CRUD + 模板查询（migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容；buildScopeCondition 按 scope 类型精确过滤 P0-3；V2-1：抛出 `LessonPlanDataError` 错误码；V2-3：mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫） |
| `data-access-versions.ts` | 版本管理（创建/查询/回滚/清理；V2-3：mapRowToVersion 显式映射） |
| `data-access-templates.ts` | 个人模板 CRUD（V2-3：mapRowToTemplate 显式映射 + 类型守卫） |
| `data-access-knowledge.ts` | 按知识点/题目反查课案（V2-3：显式字段映射替代 `as unknown as`） |
| `actions.ts` | 课案 CRUD/版本/模板 Server Actions（V2-1：getTranslations i18n + 错误码捕获；V2-2：createLessonPlanAction 传入 translateTitle 翻译 SYSTEM_TEMPLATES） |
| `actions-publish.ts` | 发布作业 Server Action（V2-1：getTranslations i18n + PUBLISH_ERROR_KEY_MAP 错误码映射） |
| `actions-ai.ts` | AI 知识点建议 Server Action（V2-1：i18n + 错误码） |
| `actions-kp.ts` | 知识点选项 Server Action（V2-1：i18n + 错误码） |
| `publish-service.ts` | 发布作业服务（编排 homework/exams/classes，通过对方 data-access 调用，无直查跨模块表；V2-1：抛出 `PublishServiceError` 错误码；V2-3：显式字段映射替代 `as unknown as`） |
| `ai-suggest.ts` | AI 知识点建议服务 |
| `seed-templates.ts` | 模板种子数据 |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook（基于 zustand，支持 nodes/edges 操作：addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode） |
| `components/lesson-plan-list.tsx` | 课案列表（i18n 已接入） |
| `components/lesson-plan-card.tsx` | 课案卡片（i18n 已接入；V2-6：duplicate/archive 调用 tracker.track） |
| `components/lesson-plan-filters.tsx` | 课案筛选器（i18n 已接入；V2-5：3 个表单元素 label htmlFor 关联） |
| `components/lesson-plan-editor.tsx` | 课案编辑器（编排 NodeEditor + NodeEditPanel，i18n 已接入；V2-6：handleManualSave 调用 tracker.track） |
| `components/node-editor.tsx` | **节点图画布**（React Flow，使用 lib/rf-mappers + lib/node-summary 纯函数，i18n 已接入；V2-4：MiniMap 复用 getNodeColor；V2-5：role=application + 键盘导航配置） |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**（配置驱动渲染 Block，通过 getBlockComponent + LessonPlanErrorBoundary 包裹，i18n 已接入） |
| `components/nodes/lesson-node.tsx` | **自定义节点组件**（使用 lib/node-summary 的 getNodeSummary/getNodeColor，i18n 已接入） |
| `components/lesson-plan-error-boundary.tsx` | **错误边界**：LessonPlanErrorBoundary 类组件，支持 fallback 和 onError 回调 |
| `components/lesson-plan-skeleton.tsx` | **骨架屏**：VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton |
| `components/block-renderer.tsx` | ⚠️ @deprecated Block 渲染器（已被 NodeEditor 替代，保留向后兼容） |
| `components/template-picker.tsx` | 模板选择器（i18n 已接入；V2-6：create 调用 tracker.track） |
| `components/version-history-drawer.tsx` | 版本历史抽屉（i18n 已接入；V2-6：revert 调用 tracker.track） |
| `components/knowledge-point-picker.tsx` | 知识点选择器（i18n 已接入） |
| `components/question-bank-picker.tsx` | 题库选择器（i18n 已接入） |
| `components/inline-question-editor.tsx` | 内联题目编辑器（i18n 已接入；V2-5：type/difficulty select label htmlFor 关联） |
| `components/publish-homework-dialog.tsx` | 发布作业对话框（i18n 已接入；V2-6：publish 调用 tracker.track） |
| `components/blocks/rich-text-block.tsx` | 富文本 Block（被 NodeEditPanel 复用，i18n 已接入） |
| `components/blocks/text-study-block.tsx` | 课文研读 Block（被 NodeEditPanel 复用，i18n 已接入） |
| `components/blocks/exercise-block.tsx` | 练习 Block（被 NodeEditPanel 复用，使用 router.refresh 替代 window.location.reload，i18n 已接入；V2-5：purpose select label 关联 + 题目列表 ul/li 语义化） |
| `components/blocks/reflection-block.tsx` | 反思 Block（被 NodeEditPanel 复用，i18n 已接入） |

---

# 第三部分：已知架构问题和技术债

## 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`。
