Files
NextEdu/docs/architecture/004_architecture_impact_map.md
SpecialX dd7a49504f refactor(hooks): useActionQuery/useActionMutation 接入 QueryClient + 向后兼容
- useActionQuery 新增 queryKey 入参模式:传入 queryKey 走 useQuery 跨页共享缓存;不传回退旧 useEffect + useState 模式。Hook 内部始终声明 useQuery/useState/useEffect 以遵守 React Hooks 规则,通过 isCacheMode 切换启用状态

- useActionMutation 新增 mutationFn + actionId 模式:成功后按 CLIENT_INVALIDATION_MAP[actionId] 自动 invalidateQueries;mutate(action?) 参数可选

- toast 替换为 notify(行为等价,便于未来替换 toast 库)

- 同步更新 004/005 架构文档签名说明
2026-07-05 18:15:58 +08:00

570 KiB
Raw Blame History

Next_Edu 架构影响地图

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


目录


第一部分:全局架构概览

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          │
│  权限管理:  rbac                                                     │
│  其他:      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.tssrc/proxy.ts 位于根目录,属于应用层

1.1.1 M7 移动端 PWA 支持2026-07-01 新增)

  • PWA 清单:新增 src/app/manifest.tsNext.js App Router 元数据路由),生成 /manifest.webmanifest,声明 name/short_name=NextEdu/start_url=//display=standalone/theme_color=#1976d2/icons=/icon.svg
  • 根布局src/app/layout.tsx 新增 viewport 导出(width=device-widthinitialScale=1maximumScale=5userScalable=truethemeColor 按明暗主题切换)+ metadata 新增 manifest/appleWebApp 配置,支持添加到主屏幕与独立显示。
  • 触控友好lesson-preparation 模块 node-editor.tsxlesson-plan-readonly-view.tsx<ReactFlow> 组件新增 panOnDrag/zoomOnPinch/zoomOnDoubleClick={false}/selectionOnDrag={false} 属性,防止移动端双击误缩放、优化触控拖拽体验。

1.1.2 设计令牌体系2026-07-04 新增)

项目采用双层令牌架构Primitive + SemanticTailwind v4 通过 @theme inline 暴露为业务可用的类。

文件分布

src/app/
├─ globals.css                      # @import 入口 + base layer + 组件硬编码样式
└─ styles/tokens/
   ├─ primitive.css                 # Layer 1: 原始色板(zinc/stone/indigo)、字号/间距/阴影/字体家族阶梯
   ├─ semantic-light.css            # Layer 2: :root 语义令牌(shadcn + 扩展 + chart/sidebar + diff/graph)
   ├─ semantic-dark.css             # Layer 2: .dark 语义令牌(完整暗色)
   ├─ lesson-preparation.css        # --lp-* 令牌(明暗双份,HEX→HSL)
   ├─ tailwind-theme.css            # @theme inline 暴露所有令牌为 Tailwind 类
   └─ index.css                     # 汇总 @import 入口

令牌分类

分类 命名 暴露为 Tailwind 类
shadcn 标准 --background/--foreground/--primary/... bg-background/text-foreground/bg-primary/...
扩展层级 --background-elevated/--text-secondary/--border-strong/... bg-background-elevated/text-text-secondary/...
chart --chart-1~5 bg-chart-1/...
sidebar --sidebar-* bg-sidebar/text-sidebar-foreground/...
lp-* 命名空间 --lp-paper/--lp-inline-node-text/--lp-dot-* bg-lp-paper/text-lp-inline-node-text/...
字体家族 --font-family-sans/serif/mono font-sans/font-serif/font-mono
字号阶梯 --font-size-1~9(含 --font-size-0 微字号) text-size-1~text-size-9
间距阶梯 --space-0~18 w-[length:var(--space-N)]
阴影阶梯 --shadow-1~6 shadow-1~shadow-6
圆角阶梯 --radius-sm/md/lg/xl/2xl/full rounded-sm/rounded-md/...
动效 --duration-*/--ease-* duration-fast/ease-in/...
z-index --z-dropdown/sticky/modal/popover/toast z-dropdown/z-modal/...

强制约束

  • ESLint 规则 no-restricted-syntax 禁止 #hex 字面量
  • 自定义规则 design-tokens/no-hardcoded-fonts 禁止 'Inter'/'Fraunces'/'JetBrains Mono' 字面量(单词边界匹配,不影响 Interval/Interactive 等标识符)
  • 白名单:primitive.css(令牌定义)、email-channel.ts(邮件 HTMLmanifest.tsPWA
  • 任意值 w-[Npx] 等需通过 // eslint-disable-next-line no-restricted-syntax -- <reason> 注释豁免(如 Dialog 固定宽度、图表画布、loading.tsx 骨架等)

1.1.3 Phase 4.2 + Phase 4.5 状态管理优化2026-07-05 新增)

V5 状态管理统一的后续阶段,针对两类高频性能问题进行细粒度优化:Zustand 整体订阅导致的级联 re-renderuseState + 等待 Server Action 模式导致的 UI 不即时反馈

Phase 4.2lesson-plan-editor Zustand selector 拆分

useLessonPlanEditor 是组合了 5 个 slice 的 zustand store。原消费方使用 const editor = useLessonPlanEditor() 整体订阅,导致任一字段变化都触发整体 re-render。

文件 修改要点
modules/lesson-preparation/components/lesson-plan-editor.tsx 拆分为 9 个细粒度 selectorisDirty/isOnline/doc/title/isSaving/saveError/lastSavedAt/setTitle + canUndo/canRedo 派生 boolean移除 editor.canUndo() 重复调用,更新 useEffect 依赖与 printablePlan useMemo 使用拆分后的变量
modules/lesson-preparation/components/blocks/text-study-block.tsx const { updateNode } = useLessonPlanEditor()useLessonPlanEditor((s) => s.updateNode)
modules/lesson-preparation/components/blocks/exercise-block.tsx 拆为 useLessonPlanEditor((s) => s.updateNode) + useLessonPlanEditor((s) => s.planId)
modules/lesson-preparation/components/paper-editor/paper-context-menu.tsx 拆为 7 个细粒度 selectortoggleExpand/expandedNodeIds/updateNode/removeNode/addAnchor/duplicateNode/nodes),特别地将 doc 改为 s.doc.nodes 更精细订阅(addAnchor 等仅修改 anchors 不会触发本组件 re-render

关键决策

  • canUndo/canRedo 改为 useLessonPlanEditor((s) => s.canUndo()) 订阅派生 boolean避免每次渲染时调用函数
  • 不使用 useShallow 包装多字段 selector因为单字段 selector 已经够细,且避免了 useShallow 自身的浅比较开销
  • 函数引用(setTitle/updateNode 等)天然稳定,无需 useShallow

Phase 4.5useOptimistic + useTransition 乐观更新

将 4 个组件从 useState + 等待 Server Action 模式迁移到 React 19 useOptimistic + useTransition,自动管理乐观状态与回滚。

文件 修改要点
modules/messaging/components/message-detail.tsx 星标 useStateuseOptimistic<boolean> + useTransitionhandleToggleStarstartStarTransitionaddOptimisticStarred(nextStarred) + await toggleMessageStarAction + router.refresh()。JSX 中所有 isStarred 引用改为 optimisticIsStarredButton variant/aria-pressed/Star className/星标徽章)
modules/messaging/components/message-list.tsx 星标 starredOverride useState → useOptimistic<Map<string, boolean>>key=messageId, value=isStarredgetIsStarred 改为 optimisticStarredMap.get(m.id)。撤回 recalledOverride 仍保留 useState不需要乐观更新
modules/grades/components/grade-record-list.tsx 编辑保存 isSaving useState → useOptimistic<GradeRecordListItem[], GradeRecordListItem> + useTransitionhandleEditSave 构造 optimisticRecordstartSaveTransitionaddOptimisticRecord + await updateGradeRecordAction + router.refresh()。两处 records.map 改为 optimisticRecords.map(桌面表格 + 移动端卡片视图)即时显示乐观记录
modules/attendance/components/attendance-sheet.tsx isSubmitting useState → useOptimistic<boolean>。保留 <form action={handleSubmit}> 模式 + useFormStatus SubmitButtonprogressive enhancementsetOptimisticSubmitting(true) 在 form action 内调用action 完成后 optimisticSubmitting 自动恢复 false移除 finally

关键决策

  • attendance-sheet 保留 form action 模式而非改用 useTransition 包裹,因为 useFormStatus().pending 依赖 form action 的 promise 状态,若用 startTransition 包裹会导致 form action 立即返回、useFormStatus 不可靠
  • useOptimisticaddOptimistic 必须在 transition 或 action 内调用form action 也算 action
  • 成功后调用 router.refresh() 同步数据源,让 useOptimistic 回滚到最新服务端值
  • 不为撤回recall操作使用 useOptimistic因为撤回需要根据服务端返回判断是否成功2 分钟窗口校验),失败时不应乐观显示已撤回

验证结果

  • npx tsc --noEmit零新增错误pre-existing 错误:providers.tsx 缺少 @tanstack/react-query-devtoolsweb-vitals-reporter.tsx Metric 导出、homework/data-access-write.ts MySqlTransaction.run、paper-context-menu.tsx 第 58 行 duplicateNode 调用签名不匹配——此为 pre-existing bugduplicateNode(id: string) 只接受 1 参数但调用方传 2 个、print-view.tsx Object.entries 类型推断)
  • npm run lint零新增错误8 个 modified 文件均未出现在 lint 输出中pre-existing 3 errors + 13 warnings 均位于未修改文件:scripts/check-db-state.mjsscripts/seed-grade5-chinese.tspractice-result-view.tsxexam-rich-form.tsxuse-exam-preview-state.tsuse-exam-preview-tasks.tshomework/data-access-write.tsunread-message-badge.tsx

1.1.4 Phase 3.6 + Phase 3.7 + Phase 3.8 DB 性能优化2026-07-05 新增)

针对数据库查询性能的三阶段优化:FULLTEXT 全文检索公告 fan-out 批量化高 LIMIT 默认值调低

Phase 3.6searchQuestions 改 FULLTEXT 索引

将 questions 表的模糊查询从 LIKE '%xxx%' 升级为 MySQL FULLTEXT 索引 + MATCH AGAINST ... IN BOOLEAN MODE,提升大表检索性能。

文件 修改要点
src/shared/db/schema.ts 添加 import { sql } from "drizzle-orm";在 questions 表添加 STORED 生成列 contentTexttext("content_text").generatedAlwaysAs(sql\CAST(content AS CHAR)`, { mode: "stored" })`);补齐 5 个先前未同步的索引typeDifficultyIdx/statusCreatedIdx/creatorIdx/examStatusIdx/submittedAtIdx
drizzle/0001_questions_fulltext_search.sql drizzle-kit 生成的迁移CREATE TABLE lesson_plan_schedules + content_text 生成列 + 8 个索引;末尾手动追加 CREATE FULLTEXT INDEX questions_content_text_ft_idx ON questions (content_text)
drizzle/meta/0001_snapshot.json questions 表 indexes 节点添加 questions_content_text_ft_idx 声明type: "fulltext"
src/modules/search/data-access.ts searchQuestions(kw, limit?)searchQuestions(q, limit?);新增 toBooleanModeQuery 工具函数将原始查询转为 +word1* +word2* 格式;查询改用 MATCH(questions.contentText) AGAINST(? IN BOOLEAN MODE)
src/app/api/search/route.ts 本地 searchQuestions 函数同步改用 MATCH AGAINST + toBooleanModeQuery;调用点从 searchQuestions(kw, pageSize) 改为 searchQuestions(q, pageSize)

关键决策

  • 使用 STORED 生成列 content_text 而非 VIRTUAL因为 FULLTEXT 索引在 MySQL 5.7+ 仅支持 STORED 生成列
  • CAST(content AS CHAR) 将 JSON 字段序列化为纯文本,避免 MySQL FULLTEXT 无法直接索引 JSON 列的限制
  • drizzle-kit 无法生成 FULLTEXT 索引声明,迁移文件中需手动追加 CREATE FULLTEXT INDEX 语句,并同步更新 snapshot 的 indexes 节点
  • toBooleanModeQuery 转义 BOOLEAN MODE 特殊字符(+ - < > ( ) ~ * " ')避免用户输入破坏查询语法
  • 保留 textbooks/exams/announcements 的 LIKE 模糊匹配FULLTEXT 索引需 InnoDB + utf8mb4且这些表查询量较小

Phase 3.7getAllUserIds 加 LIMIT + 公告 fan-out 批量化

文件 修改要点
src/modules/users/data-access.ts getAllUserIds()getAllUserIds(limit=1000, offset=0),添加默认 LIMIT 防止 OOM + 分页参数支持
src/modules/announcements/data-access.ts resolveAnnouncementTargetUserIds school 分支改为分页循环遍历 getAllUserIds(PAGE_SIZE, offset)PAGE_SIZE=1000聚合所有用户 ID
src/modules/notifications/data-access.ts 新增 createNotifications(items) 批量 INSERT 函数(单次 db.insert().values([...])),返回生成的 ID 数组
src/modules/notifications/channels/in-app-channel.ts InAppChannelSender.sendBatchPromise.all(items.map(send)) 改为调用 createNotifications(inputs) 单次批量 INSERT预校验 recipient/userId 一致性;失败时整体回滚并返回失败结果
src/modules/notifications/dispatcher.ts sendBatchNotifications 重构:并行获取所有用户偏好+联系方式 → 收集 in_app 渠道 payload 单次 sendBatch → 其他渠道保持 per-payload 并行 → 按 payload 聚合日志
src/modules/notifications/index.ts 导出 createNotifications

关键决策

  • getAllUserIds 默认 LIMIT 1000 是平衡单次查询压力与 fan-out 完整性的折中值;调用方通过分页遍历获取全部用户
  • createNotifications 使用 drizzle 的 db.insert().values(array) 一次 INSERT 多行,相比 N 次单行 INSERT 显著降低网络往返
  • in_app 渠道批量化其他渠道sms/email/wechat保持原并行模式因为这些渠道的发送成本主要在网络 IO 而非 DB 写入
  • 批量 INSERT 失败时整体回滚drizzle 默认事务行为),每条记录返回失败结果

Phase 3.8:调低高 LIMIT 默认值

src/modules/*/data-access*.ts 中搜索 LIMIT > 100 的默认值并调低至 ≤100保留分页场景

文件 原 LIMIT 新 LIMIT 函数
src/modules/attendance/data-access-correlation.ts 5000 100 getAttendanceGradeCorrelation 调用 getGradeRecords
src/modules/adaptive-practice/data-access-strategy.ts 1000 100 getStudentAnsweredQuestionIds
src/modules/questions/data-access.ts 1000 100 exportQuestions

未修改(不在 data-access.ts 范围内)*

  • src/modules/homework/stats-service.ts:276limit: 200:345limit: 5000不在 data-access*.ts glob 范围内,本次未修改

验证结果

  • npx tsc --noEmit零新增错误pre-existing 错误:providers.tsxweb-vitals-reporter.tsxhomework/data-access-write.tspaper-context-menu.tsxprint-view.tsx 均为未修改文件)
  • npm run lint零新增错误16 个 pre-existing problems 均位于未修改文件:scripts/check-db-state.mjsscripts/seed-grade5-chinese.tspractice-result-view.tsxexam-rich-form.tsxuse-exam-preview-state.tsuse-exam-preview-tasks.tshomework/data-access-write.tsunread-message-badge.tsx

1.1.5 性能预算重构专项2026-07-05 新增)

完整审计报告见 docs/architecture/audit/performance-budget-audit-report.md。 本章节记录本次重构对架构产生的关键变更,以便后续维护时定位。

影响范围概览

Phase 改造内容 关键文件
2.1 tiptap 动态导入(3 处) shared/components/ui/rich-text-editor.tsx + modules/exams/editor/exam-rich-editor.tsx + modules/lesson-preparation/components/blocks/rich-text-block.tsx
2.2 ReactFlow 动态导入 modules/textbooks/components/knowledge-graph.tsx 拆为 knowledge-graph-inner.tsx + lazy wrapper,ReactFlowProvider 留外层
2.3 exams/editor barrel 拆分 新增 modules/exams/editor/types.ts 作为纯类型入口,供 server-only 消费方引用
2.4 chart.tsx barrel + status-badge 移除 use client chart.tsx 已是具名导出;status-badge.tsx 移除 "use client"(纯展示组件)
2.5 4 个角色根路由 loading.tsx 新增 teacher/loading.tsx + student/loading.tsx + parent/loading.tsx(admin 已有)
3.1 unstable_cache 评估 评估后决定不引入,与项目现有 React cache() 模式一致
3.2 auth 函数 cache 包装 shared/lib/session.ts:getSession + shared/lib/auth-guard.ts:resolveDataScope + getAuthContext + modules/classes/data-access.ts:getSessionTeacherId 全部用 React cache() 包装,getSessionTeacherId 改走 getAuthContext()
3.3 copyCoursePlanToClasses 批量化 modules/course-plans/data-access.ts:347 改为单事务 + 2 次批量 INSERT,O(N*M) → O(1)
3.4 gradeHomeworkAnswers 批量 UPDATE modules/homework/data-access-write.ts:356 改为单次 UPDATE ... CASE WHEN + sql.join,替代 N 次串行 UPDATE
3.5 exams 表索引补齐 shared/db/schema.ts 新增 5 个索引,迁移 drizzle/0001_questions_fulltext_search.sql
3.6 searchQuestions FULLTEXT shared/db/schema.ts 新增 contentText STORED 生成列 + questions_content_text_ft_idx FULLTEXT 索引;search/data-access.ts 改用 MATCH(...) AGAINST(... IN BOOLEAN MODE)
3.7 公告 fan-out 批量化 getAllUserIds 加分页参数;新增 notifications/data-access.ts:createNotifications 批量 INSERT;dispatcher.sendBatchNotifications 重构为 in_app 单次批量 + 其他渠道并行
3.8 高 LIMIT 调低 attendance 5000→100、adaptive-practice 1000→100、questions 1000→100
4.1 SidebarProvider memoize modules/layout/components/sidebar-provider.tsx resize debounce 200ms + useMemo context + useCallback toggleSidebar
4.2 lesson-plan-editor selector 拆分 lesson-plan-editor.tsx + 3 个子组件改为单字段 selector
4.3 题库表 + 考试表虚拟化 新增 @tanstack/react-virtual 依赖;question-data-table.tsx + exam-data-table.tsx 引入 useVirtualizer
4.4 recharts props 模块级常量 14 个 chart 文件提取 CHART_MARGIN/AXIS_TICK/TOOLTIP_CURSOR 等模块级常量
4.5 useOptimistic 改造 message-detail.tsx + message-list.tsx + grade-record-list.tsx + attendance-sheet.tsx 改用 useOptimistic + useTransition
4.6 高频子组件 React.memo AssignmentCard + StatusBadge + EmptyState 添加 React.memo(ClassCard/GraphKpNode 已有)
4.7 AiAssistantWidget 懒加载 新增 modules/ai/components/ai-assistant-widget-inner.tsx,外层改为 lazy wrapper
4.8 scan-image-viewer aspect-ratio modules/homework/components/scan-image-viewer.tsx <img>aspectRatio: "4 / 3" + width/height 属性
4.9 metadata 增强 app/layout.tsxmetadataBase/openGraph/robots/applicationName/authors/creator

关键架构决策

  1. 动态导入模式标准化:重 UI 库(tiptap / @xyflow/react / AI SDK)统一采用 xxx-inner.tsx + xxx.tsx lazy wrapper 模式。ReactFlowProvider 等 context provider 留在外层同步渲染,保证 lazy 子组件的 useReactFlow() 等 hook 可用。
  2. 请求级去重优先于跨请求缓存:auth 相关 4 个核心函数(getSession/resolveDataScope/getAuthContext/getSessionTeacherId)全部用 React cache() 包装。不引入 unstable_cache,因权限数据易变,跨请求缓存风险高于收益。
  3. 批量 SQL 优于循环 SQL:
    • INSERT 场景:用 db.insert().values([...]) 批量
    • UPDATE 场景:用 sql.join + CASE WHEN 单次执行
    • 复杂事务:用 db.transaction 包裹多个批量操作
  4. React 19 useOptimistic 替代手动 isPending:消息星标/出勤/成绩录入统一改为 useOptimistic + useTransition,移除手动 useState<boolean> 跟踪 pending。
  5. 细粒度 Zustand selector:单字段 selector 优先于 useShallow 多字段 selector,因为函数引用天然稳定。

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   │◀────┘ 仅外键引用(合理)
           │ (成绩)   │◀──── exams data-access✅ 2026-06-24 新增getExamsForGradeEntry/getExamForGradeEntry 按试卷录入成绩)
           └────┬─────┘
                │ ✅ 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/roles                  → requirePermission(ROLE_READ)
   ├─▶ /admin/roles/[id]             → requirePermission(ROLE_READ)
   ├─▶ /admin/permissions            → requirePermission(PERMISSION_READ)
   ├─▶ /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)
   └─▶ /admin/invitation-codes       → requirePermission(USER_MANAGE)  ✅ audit-P2-3 新增

   ✅ 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)  ✅ 已修复

   student/learning + student/schedule 路由权限校验2026-06-26 审计修复):
   ├─▶ /student/learning                  → requirePermission(CLASS_READ)  ✅ 已修复(原 homework:submit
   ├─▶ /student/learning/courses          → requirePermission(CLASS_READ)  ✅ 已修复(原 homework:submit
   ├─▶ /student/learning/courses/[classId]→ requirePermission(CLASS_READ)  ✅ 已修复(原 homework:submit
   └─▶ /student/schedule                  → requirePermission(CLASS_READ)  ✅ 已修复(原 homework:submit

第二部分:模块清单

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

2.1 shared基础设施层

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

导出函数(核心):

  • getAuthContext() / requirePermission(p) / requireAuth() — 认证与权限
  • resolvePermissions(roles) / resolveDataScope(userId, roles) — 权限解析
  • logAudit() / logLoginEvent() / logDataChange() — 日志记录
  • createAiChatCompletion() / parseAiChatPayload() — AI 调用
  • validatePassword() / isAccountLocked() / rateLimit() — 安全策略
  • exportToExcel() / parseExcel() / generateTemplate() — Excel 工具
  • cn() / formatDate() / formatFileSize() — 通用工具
  • getInitials(name) / formatDateForFile(d?) — 通用工具P1-c / P1-a 重构新增:从 parent/lib/utils.ts、grades/export-button.tsx 等多处重复实现抽取)
  • downloadBase64File(base64, filename, mimeType?) / downloadBlob(blob, filename) — 客户端文件下载P1-c 重构新增:从 grades/export-button、users/user-import-dialog、audit/audit-log-export-button 三处重复实现抽取,位于 lib/download.ts
  • handleActionError(e) / safeActionCall(action, options?) / safeJsonParse(json, msg) / safeParseDate(v, field) / safeParseNumber(v, field) / escapeLikePattern(input) — Server Action 错误处理与客户端调用包装2026-06-23 审计修复新增:位于 lib/action-utils.ts,统一所有 Server Action catch 块错误处理,避免内部错误消息暴露给客户端)
  • BusinessError / NotFoundError / ValidationError — 错误类2026-06-23 审计修复新增:位于 lib/action-utils.ts已知业务错误基类message 可安全返回客户端)
  • extractQuestionPreview(content, fallback?, maxLength?) — 题目内容纯文本预览提取( 2026-06-24 新增:位于 lib/question-content.ts,从 unknown 类型题目内容递归提取纯文本,支持字符串/数组/对象格式,可选截断;被 error-book、homework 等模块共享使用,避免各模块重复实现题目内容解析逻辑)
  • isRecord(v) — 通用类型守卫P1-6 代码去重新增:位于 lib/type-guards.ts,判断未知值是否为非空对象 v is Record<string, unknown>;消除 homework/stats-service、lesson-preparation/node-summary、lesson-preparation/ai-suggest 等 12+ 个文件中重复定义的 isRecord 局部实现,从 unknown 安全收窄类型,替代 as Record<string, unknown> 断言)
  • trackEvent(name, payload) / trackExamEvent(name, payload) / trackAuthEvent(name, payload) — 监控埋点接口(位于 lib/track-event.tsserver-only当前输出到 console.info预留接入外部监控服务EventName 联合类型覆盖 announcement/message/notification/attendance/elective/exam/homework/ai/audit/files/auth 模块事件; P3 新增事件名 homework.excellent_viewedhomework.remind_unsubmitted audit-P1-9 新增 auth 模块 8 个事件 auth.signin_success/auth.signin_failure/auth.signout/auth.signup/auth.2fa_enabled/auth.2fa_disabled/auth.account_locked/auth.rate_limited,并新增 trackAuthEvent 便捷函数自动设置 targetType="user"
  • ROLE_NAMES — 角色名常量对象( 2026-06-24 新增:位于 types/permissions.ts,包含 ADMIN/TEACHER/STUDENT/PARENT/GRADE_HEAD/TEACHING_HEAD 键,替代 DB 查询中硬编码的角色名字符串;被 error-book/data-access-analytics.getAllStudentIds 使用 ROLE_NAMES.STUDENT
  • apiSuccess(data, init?) / apiError(message, status, errorCode?, init?) / apiFromAction(result, options?) / handleApiError(error, init?) / withApiErrorHandler(handler) / parseJsonBody<T>(req) / errorToStatus(error) — API 路由层统一响应工具( 2026-07-04 API 规范化重构新增:位于 lib/api-response.ts,仅用于 app/api/.../route.ts;统一响应信封 { success, message?, errorCode?, data? }ActionState<T> 对齐集中错误→HTTP 状态码映射替代字符串匹配推导;withApiErrorHandler HOF 统一异常处理消除每个 route 重复 try/catchapiFromAction 通过 errorCode 驱动状态码替代 result.message?.includes("not found") 脆弱模式;errorToStatus 映射规则PermissionDeniedError→403 / NotFoundError→404 / ValidationError→400 / BusinessError→400 / 其他→500
  • formatSseEvent(data) / formatSseError(message) / formatSseDone() / createSseResponse(stream) / createSseError(message, status) — SSE 共享工具( 2026-07-04 API 规范化重构新增:位于 lib/sse.ts,统一 app/api/.../stream/route.ts 的事件格式与响应头;标准 SSE 头 Content-Type: text/event-stream + Cache-Control: no-cache, no-transform + Connection: keep-alive;客户端约定错误统一 { type: "error", message }、流结束信号 [DONE]

共享组件导出P0-b / P1-a / P1-b / P1-c / P2-a / P2-b / P3-a / P3-b / P3-c / P3-d / 第二轮 P0-1/P0-2/P0-3/P1-1/P1-2/P1-3/P1-4 重构新增,按类别组织):

类别 组件 文件 用途 消费方数量
UI 组件 StatCard components/ui/stat-card.tsx 统计卡片(标题+数值+图标+描述+跳转+骨架屏) 8 个P1-a
UI 组件 StatItem components/ui/stat-item.tsx 紧凑统计项label+icon+value+hint用于统计面板网格 8 个P1-a
UI 组件 ChipNav components/ui/chip-nav.tsx 芯片导航组(通过 URL search params 切换筛选维度Link 跳转) 3 个P1-b
UI 组件 PageHeader components/ui/page-header.tsx 页面头部(标题+描述+icon+actions响应式布局 2 个P2-b: profile/page.tsx, settings/security/page.tsx
UI 组件 FilterBar / FilterSearchInput / FilterResetButton components/ui/filter-bar.tsx 筛选栏容器+搜索框+重置按钮统一布局壳URL 状态由各模块处理) 5 个P3-b: exam/textbook/question/audit-log/login-log filters
UI 组件 ConfirmDeleteDialog components/ui/confirm-delete-dialog.tsx 通用删除确认对话框AlertDialog 包装,支持自定义 confirmText/cancelText 5 个P0-1: announcement-detail, message-detail, course-plan-detail, grade-classes-view, students-table
UI 组件 Pagination components/ui/pagination.tsx 通用分页 UIShowing X-Y of Z + Page X of Y + 上一页/下一页按钮) 3 个P0-2: audit-log-table, login-log-table, data-change-log-table
UI 组件 EmptyTableRow components/ui/empty-table-row.tsx 表格空状态行TableRow + TableCell 居中显示空状态文案) 3 个P0-3: audit-log-table, login-log-table, data-change-log-table
UI 组件 ResizablePanel components/ui/resizable-panel.tsx 自实现可拖拽分栏容器(左右两栏 + 中间分隔条 pointer 拖拽调整宽度,无新依赖;用于试卷富文本编辑器左编辑右预览、阅卷式批改左题目右扫描图等场景) 2 个exams/components/exam-rich-form, homework/components/homework-scan-grading-view
UI 组件 ErrorState components/error-state.tsx 通用错误边界展示组件("use client",统一 error.tsx 渲染逻辑AlertTriangle 图标 + i18n title/description + retry 按钮props{ error, reset, namespace };翻译键查找规则:在传入 namespace 下查找 error.title/error.description/error.retry 10+ 个2026-06-26 审计修复新增classes/student 等模块的 error.tsx
UI 组件 StatusBadge components/ui/status-badge.tsx 通用状态徽章Badge + 状态→variant/label/className 映射表,修复 in_progress 颜色不一致 bug 9+ 个P1-1: audit 3 文件, grades 2 文件, student/learning/assignments, parent/child-homework-summary, student-upcoming-assignments-card, question-columns
表单字段 TextField components/form-fields/text-field.tsx 通用文本字段FormField + Input 包装,支持 text/number/password/datetime-local 类型 + value 转换器) 3 个文件 16 处P1-2: profile-settings-form 6, exam-basic-info-form 4, ai-provider-settings-card 4
表单字段 SelectField components/form-fields/select-field.tsx 通用选择字段FormField + Select 包装,支持 toSelectValue/fromSelectValue 处理 number↔string 4 个文件 8 处P1-2: exam-basic-info-form 3, ai-provider-settings-card 1, create-question-dialog 2, profile-settings-form 1
表单字段 TextareaField components/form-fields/textarea-field.tsx 通用多行文本字段FormField + Textarea 包装) 1 个P1-2: create-question-dialog
图表组件 ChartCardShell components/charts/chart-card-shell.tsx 图表卡片外壳Card+Header+EmptyState+Content 统一结构) 8 个P3-c
图表组件 TrendLineChart components/charts/trend-line-chart.tsx 趋势折线图LineChart 统一配置,支持单/多系列) 8 个P3-c: grade-trend-chart 等)
图表组件 SimpleBarChart components/charts/simple-bar-chart.tsx 柱状图BarChart 统一配置,支持单/多 Bar + Cell 分桶着色 + defs 自定义 SVG 图案) 8 个P3-c: grade-distribution-chart 等)
图表组件 ComparisonRadarChart components/charts/comparison-radar-chart.tsx 对比雷达图RadarChart 统一配置,支持双 Radar 对比) 8 个P3-c: subject-comparison-chart, mastery-radar-chart 等)
课表组件 ScheduleList / ScheduleListItem components/schedule/schedule-list.tsx 课表列表+列表项(课程+时间+地点+班级徽章separator/card 两种变体) 3 个P3-a: student-today-schedule-card, child-schedule-card, student-schedule-view
题库组件 QuestionBankFilters components/question/question-bank-filters.tsx 题库筛选栏(搜索+题型+难度default/compact 两种布局) 2 个P3-d: exam-assembly, question-bank-picker
题库组件 QuestionCascadeFilter modules/questions/components/question-cascade-filter.tsx 审计新增教材→章节→知识点三级级联筛选nuqs URL 状态同步,清除上级自动清除下级) 1 个question-filters
题库组件 KnowledgePointSelector modules/questions/components/knowledge-point-selector.tsx 审计新增:知识点选择器(支持搜索过滤,从 create-question-dialog 拆分) 1 个create-question-dialog
题库组件 OptionsEditor modules/questions/components/options-editor.tsx 审计新增:选项编辑器(单选/多选模式,增删改选项,从 create-question-dialog 拆分) 1 个create-question-dialog
题库组件 QuestionBankResultsClient modules/questions/components/question-bank-results-client.tsx 审计新增RSC 与客户端 Hook 桥接(解决 useQuestionColumns Hook 在客户端组件中使用) 1 个teacher/questions/page
题库组件 QuestionContentRenderer modules/questions/components/question-content-renderer.tsx 审计新增:题目结构化渲染(题干/选项/答案/解析,单选圆点/多选方框,正确答案高亮) 1 个question-actions 查看详情)
题库组件 BatchOperations modules/questions/components/batch-operations.tsx 审计新增:批量操作工具栏(批量删除,权限感知,事务原子性) 1 个question-data-table
题库组件 ImportExportButtons modules/questions/components/import-export-buttons.tsx 审计新增导入导出组件JSON 格式,导出下载/上传导入,导入前预览确认) 2 个teacher/admin questions page
设置组件 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 }。Options 联合类型:旧版 { successMessage?, errorMessage?, onSuccess?, onError? } 或新版 { mutationFn?, actionId?, params?, invalidateQueryKeys?, ... }。新版接入 QueryClient成功后按 CLIENT_INVALIDATION_MAP[actionId] 自动 invalidateQueries。mutate(action?) 参数可选:传 action 走旧模式,不传则使用 options.mutationFn 通用 Server Action mutation Hook替代 50+ 文件中重复的 setIsWorking + try/catch/finally + toast 模式。V5 缓存策略重构新增 QueryClient + actionId 自动失效,向后兼容旧调用方 1 个示范P1-4: schools-view潜在影响 50+ 文件
useActionQuery hooks/use-action-query.ts useActionQuery<T>(action, options?): { data, loading, error, refetch }。Options 联合类型:旧版 { deps?, enabled?, errorMessage? } 或新版 UseActionQueryWithCacheOptions(含 queryKey + UseQueryOptions)。传入 queryKey 走 useQuery 跨页共享缓存;不传走旧 useEffect + useState。Hook 内部始终声明 useQuery + useState/useEffect 以遵守 React Hooks 规则 通用 Server Action 查询 Hook替代 11 个文件中重复的 useEffect + useState(loading) + Action().then().catch().finally() 模式内置竞态防护。V5 缓存策略重构新增 queryKey 入参走 QueryClient向后兼容旧调用方 1 个示范P1-4: create-question-dialog潜在影响 11 个文件

V5 状态管理统一专项基础设施(阶段 1 新增,零业务行为变更):

资产 文件 签名 用途 后续消费方
createQueryClient lib/query-client.ts createQueryClient(): QueryClient TanStack Query 工厂,默认 staleTime 30s / retry 1 / refetchOnWindowFocus false / mutations 全局 onError 兜底 app/providers.tsx
configureNotify + notify + rawToast lib/notify.ts configureNotify(resolver); notify.{success,error,info,warning,promise}; rawToast 统一 toast 接口,包装 sonner 并内置 i18n key 解析。configureNotifyProvidersNotifyConfigurator 注入 next-intl t 函数 query-client.ts 全局兜底 + 业务组件迁移后由各模块使用(替换 100+ 处 import { toast } from "sonner"
useDialogState hooks/use-dialog-state.ts useDialogState(): readonly [boolean, () => void, () => void, () => void] Dialog/Sheet 开关状态 Hook替换 30+ 处 const [open, setOpen] = useState(false) 模式 待迁移30+ 处 Dialog 组件
useUiStore + useModal stores/ui-store.ts useUiStore: { modals, openModal, closeModal, isOpen, closeAll }; useModal(name) 全局 UI storezustand集中管理 modal registry。命令式 API + Hook 双入口。与 sonner 的分工toast 走 notifymodal 走本 store 待迁移30+ 处 Dialog 组件切换为 useModal(name)
createServiceProvider lib/create-service-provider.tsx createServiceProvider<T>(displayName): { Provider, useService, Context } Service DI Provider 工厂,替换 14 处重复的 createContext(null) + Provider + useService 抛错模板 待迁移announcements / attendance / audit / dashboard / messaging / settings 等 14 处 Service DI Provider
Providers app/providers.tsx Providers({ children, locale, messages, session }): React.JSX.Element 客户端 Provider 聚合。嵌套顺序QueryClientProvider → NextIntlClientProvider → NotifyConfigurator → ThemeProvider → SessionProvider → NuqsAdapter → children + Toaster。DevTools 仅 dev 环境 app/layout.tsx

V5 状态分层模型5 层):

责任 选型
URL 状态 列表过滤、tab 持久化、深链接 nuqs useQueryState(单一来源,逐步替换 useSearchParams + window.location.search
服务端状态 列表/详情/分页数据mutation + 缓存失效 TanStack Query混合模式RSC 拿首屏 propsuseQuery 负责交互数据)
客户端业务状态 模块级状态机(如 exam preview、lesson editor zustand slice按模块拆参考 lesson-preparation 现有模式)
全局 UI 状态 modal registry zustand ui-store
表单状态 表单密集模块 react-hook-form + zodResolver

共享工具函数导出(第二轮 P1-3 重构新增):

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

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

依赖关系

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

已知问题

  • P0shared/lib/*@/auth 循环依赖 已修复(新增 shared/lib/session.ts 封装 session 获取3 个文件改为 import { getSession } from "@/shared/lib/session"
  • ⚠️ P1schema.ts 1111 行54 张表混合,超 1000 硬上限)
  • P1auth.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.tsai.ts 保留为向后兼容的重导出文件9 行)
  • P2-4 已修复:onboarding-gate.tsx 业务逻辑泄漏到 shared 已迁移至 modules/onboarding/actions/data-access/schema/types/components引导流程改为独立路由 /onboarding + middleware 重定向 + Server Action
  • P0-2/P0-3/P0-4/P0-5/P1-1/P1-2/P1-4/P1-5 已修复v3 对标 PowerSchool/Veracross/Auth0家长绑定三因子验证邮箱+生日+手机号后4位、教师多科目循环绑定、审计日志、服务端幂等、URL query 持久化步骤、局部错误收集、家长多子女动态行、跳过机制明确化
  • v3 i18n 体系引入:采用 next-intl 4.xwithout i18n routing 模式cookie 驱动 locale 切换,字典放在 shared/i18n/messages/{locale}/,支持 zh-CN/en 两种语言,不破坏现有路由组结构
  • v3 班级邀请码体系引入(对标 Google Classroom / 钉钉教育 / 智学网):新增 class_invitation_codes 表(独立表,支持有效期/次数限制/审计/多码并存6 位字母数字(剔除歧义字符 0/O/1/I/L空间 1.13 亿rate limit 防爆破10 次/5 分钟),审计日志全链路记录,懒清理过期码

文件清单

文件 行数 职责
db/schema.ts 1111 54 张表定义(超硬上限)
db/relations.ts - 表关系定义
db/index.ts - Drizzle 客户端
lib/auth-guard.ts - 认证上下文 + 权限校验 + DataScope
lib/permissions.ts - 角色-权限映射
lib/session.ts 38 session 获取单一入口getSessionserver-onlydynamic import 打破循环)
lib/ai.ts 9 向后兼容重导出P2-2 已拆分到 ai/ 目录)
lib/ai/payload-parser.ts 78 请求负载解析
lib/ai/api-key-crypto.ts 28 API Key 加密/解密
lib/ai/provider-config.ts 132 Provider 配置查询V3.1 增强:基于 session 用户的可见性/所有权校验public + own private 过滤)
lib/ai/client.ts 58 AI 客户端创建与调用
lib/ai/errors.ts 8 错误格式化
lib/ai/index.ts 5 聚合导出
lib/audit-logger.ts - 操作日志(通过 session.ts 获取 sessionhttp-utils 获取 IP/UA
lib/change-logger.ts - 数据变更日志(通过 session.ts 获取 sessionhttp-utils 获取 IP
lib/login-logger.ts - 登录日志(通过 http-utils 获取 IP/UA
lib/password-policy.ts - 密码策略纯函数
lib/rate-limit/ - 速率限制器抽象audit-P1-6 新增:抽象 RateLimiter 接口;默认内存实现 MemoryRateLimiter;可选 Redis 实现 RedisRateLimiter 基于 @upstash/ratelimit通过 RATE_LIMIT_DRIVER=memory|redis 环境变量切换;rateLimit() / resetRateLimit() 改为 Promise 返回以支持异步后端;rateLimitKey / RATE_LIMIT_RULES / rateLimitHeaders 保持同步纯函数)
lib/role-utils.ts 51 角色规范化纯函数normalizeRole / resolvePrimaryRole P2-8 修改:保留 grade_head/teaching_head 独立性,不再映射为 teacher优先级 admin>grade_head>teaching_head>teacher>student>parent
lib/bcrypt-utils.ts 18 bcrypt 哈希前缀规范化纯函数
lib/breached-password.ts 125 已泄露密码检测( audit-P2-4 新增HIBP k-anonymity API 范围查询;仅发送 SHA-1 前 5 字符到 api.pwnedpasswords.com本地比对完整哈希后缀不泄露用户密码3 秒超时 + AbortControllerfail-open 策略API 不可用时返回 checkSkipped=true 不阻断注册/改密server-only
lib/http-utils.ts 44 请求头解析resolveClientIp / getUserAgentserver-only
lib/password-security-service.ts 84 密码安全 DB 操作(账户锁定/失败登录追踪server-only
lib/route-resolver.ts 21 角色默认路径解析纯函数 resolveDefaultPath(roles: string[]): string audit-P0-6 新增:合并 proxy.tsonboarding/data-access.resolveDefaultPathByRoles 重复实现;纯函数无 server-only,可在 edge/proxy 运行时使用;优先级 admin>grade_head/teaching_head>teacher>student>parent>fallback /dashboard
lib/route-permissions.ts 82 路由权限配置常量( audit-P1-10 + audit-P1-12 新增:从 proxy.ts 抽取,配置驱动设计;导出 4 个 Record<string, Permission> 常量:SPECIFIC_ROUTE_PERMISSIONS15 个精确路由权限audit-P1-10 扩展自 3 个至 15 个覆盖 /admin/* 子路径)/ ROUTE_PREFIX_PERMISSIONS5 个角色路由前缀权限)/ DASHBOARD_ROUTE_PERMISSIONS4 个仪表盘细粒度权限防跨角色访问)/ API_ROUTE_PERMISSIONS1 个 API 路由权限);纯常量无 server-only,新增路由权限只需修改此文件无需改 proxy.ts 逻辑)
lib/permission-bitmap.ts 222 权限位图编解码( audit-P1-7 新增:将 67 个权限点字符串数组约 1.1KB 压缩为约 13 字符 base36 字符串,体积减少 99%PERMISSION_BITMAP_ORDER 顺序固定不可变,新增权限只能追加末尾;纯函数无 server-only,可在 edge runtime 使用;导出 encodePermissionsBitmap(permissions) / decodePermissionsBitmap(bitmap) / hasPermissionInBitmap(bitmap, permission)BigInt 精度安全:手工按位累加 base36 → BigInt避免 parseInt(bitmap, 36) 的 Number 53 bit 精度丢失)
lib/excel.ts - Excel 导入导出
lib/file-storage.ts - 文件存储抽象
hooks/use-permission.ts - 客户端权限 Hook
components/ui/* 35 文件 shadcn/ui 标准组件( P2-3 新增 alert.tsx
components/ui/stat-card.tsx 95 StatCard 统计卡片P1-a 新增)
components/ui/stat-item.tsx 38 StatItem 紧凑统计项P1-a 新增)
components/ui/chip-nav.tsx 78 ChipNav 芯片导航P1-b 新增)
components/ui/page-header.tsx 44 PageHeader 页面头部P2-b 新增,含 icon 属性)
components/ui/filter-bar.tsx 124 FilterBar + FilterSearchInput + FilterResetButtonP3-b 新增)
components/ui/resizable-panel.tsx - ResizablePanel 可拖拽分栏容器(自实现,无新依赖;用于富文本编辑器与阅卷式批改的左右分栏布局)
components/error-state.tsx 35 ErrorState 通用错误边界展示组件("use client"2026-06-26 审计修复新增:统一 30+ error.tsx 渲染逻辑props { error, reset, namespace },基于 EmptyState + AlertTriangle + next-intl useTranslations
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 新增)
modules/questions/components/question-cascade-filter.tsx 130+ QuestionCascadeFilter 三级级联筛选( 审计新增教材→章节→知识点nuqs URL 状态)
modules/questions/components/knowledge-point-selector.tsx 80+ KnowledgePointSelector 知识点选择器( 审计新增:从 create-question-dialog 拆分)
modules/questions/components/options-editor.tsx 120+ OptionsEditor 选项编辑器( 审计新增:从 create-question-dialog 拆分)
modules/questions/components/question-bank-results-client.tsx 30+ QuestionBankResultsClient RSC 桥接( 审计新增)
modules/questions/utils/parse-content.ts 60+ parseQuestionContent 类型守卫( 审计新增:替代 as 断言)
modules/questions/utils/track-event.ts 30+ trackQuestionEvent 埋点接口( 审计新增no-op 实现)
modules/questions/components/question-content-renderer.tsx 110+ QuestionContentRenderer 题目结构化渲染( 审计新增)
modules/questions/components/batch-operations.tsx 110+ BatchOperations 批量操作工具栏( 审计新增)
modules/questions/components/import-export-buttons.tsx 180+ ImportExportButtons 导入导出组件( 审计新增)
lib/download.ts 47 downloadBase64File + downloadBlob 客户端下载工具P1-c 新增)
lib/type-guards.ts - 通用类型守卫集合P1-6 新增 isRecord(v): v is Record<string, unknown>,消除 12+ 个文件重复定义;含 grades/lesson-preparation 等模块的类型守卫 isGradeDraftData/isExerciseBlockData/isPermission 等)
lib/track-event.ts - 监控埋点接口server-onlyno-op console.info 实现EventName 联合类型覆盖 11 个模块事件P3 新增 homework.excellent_viewedhomework.remind_unsubmittedaudit-P1-9 新增 auth 模块 8 个事件 + trackAuthEvent 便捷函数)
lib/utils.ts - 通用工具P1-a/P1-c 新增 getInitials + formatDateForFile
components/onboarding-gate.tsx 312 audit-P0-4 已删除(引导流程业务逻辑已迁移至 modules/onboarding/,原文件无任何 import 引用,删除以避免文件膨胀)
components/global-search.tsx 221 全局搜索(业务泄漏)
types/permissions.ts 157 67 个权限点常量 + Role/DataScope/AuthContext 类型( rbac 新增:Role 类型从固定联合类型改为 string 支持动态角色;新增 BUILTIN_ROLES 常量、BuiltinRole 类型、isBuiltinRole() 类型守卫;isRole() 标记为 deprecated
lib/api-response.ts 212 2026-07-04 API 规范化重构新增API 路由层统一响应工具apiSuccess/apiError/apiFromAction/handleApiError/withApiErrorHandler/parseJsonBody/errorToStatus统一响应信封 + 集中错误→HTTP 状态码映射,消除每个 route 重复 try/catch 与字符串匹配推导状态码
lib/sse.ts 81 2026-07-04 API 规范化重构新增SSE 共享工具formatSseEvent/formatSseError/formatSseDone/createSseResponse/createSseError统一 stream 路由的事件格式与响应头,消除 ai/chat/streamnotifications/stream 重复实现

2.2 exams考试模块

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

导出函数

  • ActionscreateExamAction / createAiExamAction / previewAiExamAction / regenerateAiQuestionAction / updateExamAction / deleteExamAction / duplicateExamAction / getExamPreviewAction / getSubjectsAction / getGradesAction / getExamsByGradeIdAction v4-P2-7 新增年级仪表盘维度3按 gradeId 查询年级下所有考试 + 提交统计EXAM_READ 权限)/ autoMarkExamAction 2026-06-24 新增AI 自动标记试卷文本,将粘贴的试卷文本交给 AI 解析为带题目块/分组/填空/加点字标记的 Tiptap JSONContent 文档EXAM_AI_GENERATE 权限,返回 { doc, title } 供富文本编辑器载入。 2026-06-24 拆分:迁移至 ai-pipeline/auto-mark.tsactions.ts re-export 保持向后兼容)/ createExamFromRichEditorAction 2026-06-24 新增:从富文本编辑器保存试卷草稿,将 Tiptap JSONContent 通过 editor/editor-to-structure.editorDocToStructure 转换为 questions + structure 后复用 persistAiGeneratedExamDraft 持久化EXAM_CREATE 权限; 2026-06-24 升级:递归 convertStructureNode 支持 section/group/question 三级层级section 数据不再丢失。 2026-06-24 拆分:迁移至 actions-rich-editor.tsactions.ts re-export 保持向后兼容)( P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access / updateExamFromRichEditorAction 2026-06-24 新增:从富文本编辑器更新已有试卷,校验所有权后递归转换 EditorDoc→structure为无 questionId 的题目创建 questions 记录,调用 data-access.updateExamWithRichEditorData 持久化EXAM_UPDATE 权限revalidatePath 列表页与组卷页。 P0-4 修复2026-06-24原直接 db.transaction 写 exams/examQuestions 表,现已下沉到 data-access.updateExamWithRichEditorData。 2026-06-24 拆分:迁移至 actions-rich-editor.tsactions.ts re-export 保持向后兼容)
  • Data-accessgetExams / getExamById / persistExamDraft / persistAiGeneratedExamDraft / buildExamDescription / resolveSubjectGradeNames / getExamCreatorId / updateExamWithQuestions / deleteExamById / duplicateExam / getExamPreview / getExamSubjects / getExamGrades / getExamsByGradeId v4-P2-7 新增年级仪表盘维度3exams 表有直接 gradeId 字段,配合 examSubmissions 聚合提交数/已评分数/平均分,支持 scope 行级过滤)(后 8 个为 P1-2 新增)/ getExamsForGradeEntry 2026-06-24 新增:按 scope 过滤试卷列表,只返回有题目的试卷,供成绩录入页试卷选择器使用,返回 id/title/subjectName/gradeName/questionCount/totalScore/ getExamForGradeEntry 2026-06-24 新增获取单个试卷详情含题目列表innerJoin questions 获取 type含 scope 校验,返回 id/title/subjectId/gradeId/totalScore/questions[{id,order,score,type}],供 grades 模块按试卷录入成绩使用)/ updateExamWithRichEditorData 2026-06-24 新增P0-4 修复:从富文本编辑器更新已有试卷的 DB 事务,单事务内更新 exams.title/structure + 删除旧 examQuestions + 插入新 examQuestions原 updateExamFromRichEditorAction 直接 db.transaction 的逻辑下沉至此)
  • AI PipelinegenerateAiCreateDraftFromSource / generateAiPreviewData / regenerateAiQuestionByInstruction / loadAiDraftQuestionsAndStructure 2026-06-24 新增:从 actions.ts 下沉,加载 AI 草稿题目与结构,优先从 rawAiQuestions JSON 解析,否则从源文本生成)
  • UtilsnormalizeStructurev3 新增:将持久化的 exam.structure unknown JSON 运行时校验并归一化为类型安全的 ExamNode[],类型守卫模式无 as 断言,从 teacher/exams/[id]/build/page.tsx 提取; 2026-06-24 升级:支持 section/group/question 三级层级递归校验section 节点含 title/level/childrengroup 节点含 title/instruction/children
  • Preview Utilscomponents/exam-preview-utils.ts 2026-06-24 补充记录):buildPreviewNodes / parseEditableContent / flattenPreviewQuestions / findPreviewQuestionNode / updatePreviewQuestionNodeInList / buildPreviewSignature / buildPreviewPayload / buildPreviewRequestData(以上 8 个为预览纯函数,供 Hook 与组件复用)+ 2026-06-24 新增 5 个(从 use-exam-preview-tasks Hook 提取以控制行数):buildTaskFormValues(提取表单字段子集供任务回填)/ isPreviewBackgroundTask(类型守卫,校验 localStorage 恢复数据)/ persistPreviewTasksToStorage(持久化任务到 localStorage/ parseStoredPreviewTasks(解析 localStorage 任务queued/running 标记为 failed 防僵尸)/ executeBackgroundPreviewTask(执行单个后台预览任务的纯函数)
  • Hooks 2026-06-24 拆分记录):useExamPreview主组合器68 行,组合三个子 Hook + handlePreview 编排,返回 API 与拆分前一致)/ usePreviewState(状态子 Hook67 行10 个 useState + 3 个更新函数)/ usePreviewBackgroundTasks(后台任务子 Hook80 行PQueue 队列 + localStorage 持久化 + 入队/打开任务)/ usePreviewRewriteAI 重写子 Hook79 行handleRewriteSelectedQuestion 调用 regenerateAiQuestionAction
  • Stats-serviceV3-8 新增):getExamAnalyticscache 包装,聚合考试所有作业的已批改提交,计算平均分/及格率/分数段分布/逐题错误率与难度等级,对标智学网考试分析)+ ExamAnalyticsSummary 类型
  • Types 2026-06-24 新增成绩录入相关类型):ExamQuestionItem(试卷中单个题目的精简结构 { id, order, score, type }/ ExamForGradeEntry(成绩录入用的试卷详情,含题目列表)/ ExamOptionForEntry(成绩录入页试卷选择器选项 { id, title, subjectName, gradeName, questionCount, totalScore }
  • Editor 子模块( 2026-06-24 新增 editor/ 目录,基于 Tiptap 的试卷富文本编辑器):
    • 组件:ExamRichEditor富文本编辑器主体forwardRef 暴露 ExamRichEditorHandle 以便父组件获取 JSONContent/ SelectionToolbar(浮动选择工具栏,对选中文本应用 DottedMark 等标记)
    • 自定义 Tiptap 扩展(editor/extensions/DottedMark(加点字标记,拼音注音题)/ BlankNode(填空空位节点,原子节点)/ ImageNode(图片节点,含 fileId/url/alt 属性)/ QuestionBlock(题目块节点,含 type/score 等 attrs/ GroupBlock(大题分组节点,含 title/ SectionBlock 2026-06-24 新增:分卷节点,含 title/level attrsisolating: true对应第卷/第Ⅱ卷层级)
    • 类型(editor/exam-rich-editor-types.tsRichQuestionTypesingle_choice | multiple_choice | judgment | text | composite/ RichQuestionContent(题干文本+选项+填空+图片+子题+正确答案)/ EditorQuestion / EditorStructureNode 2026-06-24 升级section|group|question 三级递归section 含 title/level/childrengroup 含 title/instruction/children/ EditorDoc{ title, questions, structure }/ EditorJSONContentJSONContent 别名)
    • 转换工具:editorDocToStructureeditor/editor-to-structure.tsTiptap JSONContent → EditorDoccreateExamFromRichEditorAction/updateExamFromRichEditorAction 持久化使用)/ structureToEditorDoceditor/structure-to-editor.tsEditorDoc → Tiptap JSONContent 2026-06-24 升级支持 section 节点回填,供编辑器回填已保存试卷使用)/ examNodesToEditorDoc 2026-06-24 新增:editor/exam-nodes-to-editor-doc.tsBuildExam 的 ExamNode[] + Question[] → EditorDoc 桥梁函数,递归转换 section/group/question供 edit-rich 页面加载已有试卷到富文本编辑器)
    • Barrel 导出:editor/index.ts 聚合导出上述所有组件、扩展、类型与转换函数
  • ComponentsV3-8 新增):ExamAnalyticsDashboard(考试分析仪表盘:汇总卡片+分数段分布+逐题分析表)
  • Components 2026-06-24 新增):ExamRichForm(富文本编辑器试卷创建/编辑表单,使用 ResizablePanel 左编辑右预览布局,集成 autoMarkExamAction 一键 AI 标记; 2026-06-24 升级支持 mode="create"|"edit" 双模式create 模式调用 createExamFromRichEditorAction 保存草稿edit 模式调用 updateExamFromRichEditorAction 更新已有试卷,编辑模式隐藏科目/年级/难度等字段并显示"Back to Build"按钮)
  • Components P3 新增记录):ExamPreview(试卷结构预览组件,从 exam-rich-form.tsx 提取,渲染 section/group/question 三级嵌套P3 修改:题目图片 <img>next/image <Image>,新增 next/image 导入;useTranslations('examHomework') 国际化)

依赖关系

  • 依赖:shared/*@/authquestions P0-1 已修复:通过 data-access.createQuestionWithRelationsclasses P0-2 已修复:通过 data-access.getClassGradeIdsByClassIdsschool P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptionshomeworkV3-8 新增stats-service 通过 homework/data-access.getHomeworkAssignmentsByExamId / getGradedSubmissionsByExamId 获取作业与提交数据,合理跨模块调用)
  • 被依赖:homework(通过 sourceExamId 外键,合理)、dashboard(通过 data-accessP0-4 已修复)、proctoring P1-1 已修复:通过 exams data-accessdiagnostic P1-1 已修复:通过 exams data-accessgrades 2026-06-24 新增:通过 data-access.getExamsForGradeEntry/getExamForGradeEntry 获取试卷列表和详情供按试卷录入成绩使用)

已知问题

  • P0-1 已修复:persistAiGeneratedExamDraft 直接 insert 到 questions 改为调用 questions/data-access.createQuestionWithRelations,通过 ID 映射保持 structure 引用一致
  • P0-2 已修复:getExams/getExamById/getExamsDashboardStats 直查 classes 改为调用 classes/data-access.getClassGradeIdsByClassIds
  • P1-1 已修复:getSubjectsAction/getGradesAction 直查 subjects/grades 改为调用 school/data-access.getSubjectOptions / getGradeOptions
  • P1-2 已修复:actions.ts 832 行(超 800 建议),多处直接 DB 操作 DB 操作已下沉到 data-accessactions.ts 现 691 行
  • P0-4 已修复2026-06-24updateExamFromRichEditorAction 直接 db.transaction 写 exams/examQuestions 表 DB 事务下沉到 data-access.updateExamWithRichEditorData
  • actions.ts 拆分2026-06-24actions.ts 1525 行(超 1000 硬上限) 拆分为 4 个文件actions.ts(633 行,核心 Action) + actions-helpers.ts(96 行,共享辅助) + actions-rich-editor.ts(250 行,富文本编辑器 Action) + ai-pipeline/auto-mark.ts(356 行,AI 自动标记 Action)actions.ts re-export 保持向后兼容
  • P0-8 已修复:data-access.ts 1036 行(超 1000 硬上限) 拆分为 data-access.ts(542 行,考试 CRUD) + data-access-cross-module.ts(462 行,13 个跨模块查询/写接口),文末 re-export 保持向后兼容
  • ⚠️ P1ai-pipeline.ts 857 行(超 800 建议),混合 4 类职责
  • P2 已修复:ai-pipeline.ts 中 3 处非空断言清理(draft.sections!.forEach → 安全守卫、aiParsed.sections!.flatMap?? []aiParsed.sections!.map?? []
  • V3-5/V3-8/V3-12exam-actions.tsx 增强V3-5 角色化菜单按角色显示不同操作项、V3-8 新增 analytics 菜单项BarChart3 图标,跳转 /teacher/exams/[id]/analytics、V3-12 移动端触摸目标尺寸优化

文件清单

文件 行数 职责
actions.ts 633 10 个核心 Server Action 2026-06-24 拆分:从 1525 行降至 633 行re-export autoMarkExamAction/createExamFromRichEditorAction/updateExamFromRichEditorAction 保持向后兼容P0-4 已修复:无直接 DB 操作)
actions-helpers.ts 96 2026-06-24 新增跨文件共享辅助函数getStringValue/getBoolValue/parseExamModeConfig/failState/successState/invalidFormState/prepareExamCreateContext
actions-rich-editor.ts 250 2026-06-24 新增:富文本编辑器 Server ActioncreateExamFromRichEditorAction/updateExamFromRichEditorAction+ 共享辅助 convertStructureNode + RichExamCreateSchema/RichExamUpdateSchema。P0-4 修复updateExamFromRichEditorAction 调用 data-access.updateExamWithRichEditorData 不再直访 DB
ai-pipeline/auto-mark.ts 356 2026-06-24 新增autoMarkExamAction Server Action + 纯转换辅助buildTiptapDocFromAiResponse/splitByDottedTexts/extractTitleFromAiResponse/AutoMarkSchema/AutoMarkResult。从 actions.ts 迁出
ai-pipeline.ts 857 AI 出题管线(超限)
data-access.ts 542 考试 CRUD 2026-06-24 拆分:跨模块接口迁移至 data-access-cross-module.ts原 1036 行降至 542 行;含 P1-2 写/查询函数P0-1/P0-2 已修复:通过 questions/classes data-access 跨模块通信; P0-4 修复:新增 updateExamWithRichEditorData 将富文本编辑器更新事务下沉;导出 parseExamMeta/getString/getNumber/toExamStatus 供 cross-module 复用;文末 re-export 跨模块接口保持向后兼容)
data-access-cross-module.ts 462 2026-06-24 新增:跨模块查询接口(供 homework/grades/proctoring/diagnostic/dashboard/lesson-preparation 等模块调用,避免直查 exams/examSubmissions 表)。包含 getExamsDashboardStats/getExamIdsByGradeIds/getExamWithQuestionsForHomework/getExamSubjectIdMap/getExamTitleById/getExamForProctoringCrossModule/getExamSubmissionForProctoringCrossModule/getExamSubmissionWithAnswers/getExamSubmissionsForExam/addExamQuestions/getExamsByGradeId/getExamsForGradeEntry/getExamForGradeEntry标记 server-only,从 data-access.ts 导入 parseExamMeta/getString/getNumber/toExamStatus 复用
stats-service.ts - V3-8 新增:考试分析数据聚合(getExamAnalytics + ExamAnalyticsSummary 类型)
types.ts 50+ 类型定义2026-06-24 新增ExamQuestionItem/ExamForGradeEntry/ExamOptionForEntry 供成绩录入使用。注:AutoMarkResult/AutoMarkSchema 已迁移至 ai-pipeline/auto-mark.tsRichExamCreateSchema/RichExamUpdateSchema 已迁移至 actions-rich-editor.ts
editor/index.ts 23 2026-06-24 新增:富文本编辑器 barrel 导出(聚合组件/扩展/类型/转换函数)
editor/exam-rich-editor.tsx - 2026-06-24 新增ExamRichEditor 富文本编辑器主体forwardRef + ExamRichEditorHandle
editor/selection-toolbar.tsx - 2026-06-24 新增SelectionToolbar 浮动选择工具栏
editor/exam-rich-editor-types.ts 42 2026-06-24 新增富文本编辑器类型定义RichQuestionType/RichQuestionContent/EditorQuestion/EditorStructureNode/EditorDoc/EditorJSONContent
editor/editor-to-structure.ts - 2026-06-24 新增editorDocToStructureTiptap JSONContent → EditorDoc
editor/structure-to-editor.ts - 2026-06-24 新增structureToEditorDocEditorDoc → Tiptap JSONContent
editor/extensions/index.ts 5 2026-06-24 新增Tiptap 扩展 barrel 导出
editor/extensions/dotted-mark.ts - 2026-06-24 新增DottedMark 加点字标记扩展
editor/extensions/blank-node.tsx - 2026-06-24 新增BlankNode 填空空位节点扩展
editor/extensions/image-node.ts - 2026-06-24 新增ImageNode 图片节点扩展
editor/extensions/group-block.tsx - 2026-06-24 新增GroupBlock 大题分组节点扩展
editor/extensions/question-block.tsx - 2026-06-24 新增QuestionBlock 题目块节点扩展
hooks/use-exam-preview.ts 68 预览 Hook 主组合器( 2026-06-24 拆分:从 318 行降至 68 行,组合 state/tasks/rewrite 三个子 Hook保留 handlePreview 编排逻辑,返回 API 与拆分前完全一致,调用方 exam-form.tsx 无需修改)
hooks/use-exam-preview-state.ts 67 2026-06-24 新增:预览状态子 Hook10 个 useState + updatePreviewQuestionNode/updateSelectedQuestionFromAi/applyPreviewResult 三个更新函数)
hooks/use-exam-preview-tasks.ts 80 2026-06-24 新增:预览后台任务子 HookPQueue 并发队列 + localStorage 持久化恢复 + handleBackgroundPreview/handleOpenPreviewTask
hooks/use-exam-preview-rewrite.ts 79 2026-06-24 新增:预览 AI 重写子 HookhandleRewriteSelectedQuestion 调用 regenerateAiQuestionAction
components/exam-preview-utils.ts 293 预览纯函数工具集buildPreviewNodes/parseEditableContent/flattenPreviewQuestions/findPreviewQuestionNode/updatePreviewQuestionNodeInList/buildPreviewSignature/buildPreviewPayload/buildPreviewRequestData + 2026-06-24 新增 5 个buildTaskFormValues/isPreviewBackgroundTask/persistPreviewTasksToStorage/parseStoredPreviewTasks/executeBackgroundPreviewTask从 use-exam-preview-tasks Hook 提取以控制行数)
components/exam-preview.tsx - 试卷结构预览组件use client从 exam-rich-form.tsx 提取,渲染试卷富文本编辑器的结构预览,支持分卷(section)/大题分组(group)/题目(question)三级嵌套递归渲染。 P3 修改:题目块中图片将原生 <img> 替换为 next/image<Image> 组件(自动优化、懒加载、防布局偏移),新增导入 next/image;使用 useTranslations('examHomework') 国际化 section/group 标题与统计文案
utils/normalize-structure.ts 57 v3 新增exam.structure 运行时校验与归一化(从 build/page.tsx 提取)
components/exam-analytics-dashboard.tsx - V3-8 新增:考试分析仪表盘组件
components/exam-actions.tsx - V3-5/V3-8/V3-12 增强:角色化菜单+analytics 链接+移动端触摸优化
components/exam-rich-form.tsx - 2026-06-24 新增:富文本编辑器试卷创建/编辑表单ResizablePanel 左编辑右预览 + AI 自动标记 + 保存草稿)。 2026-06-24 拆分后import 路径直连 ai-pipeline/auto-mark.tsautoMarkExamActionactions-rich-editor.tscreateExamFromRichEditorAction/updateExamFromRichEditorAction支持 create/edit 双模式
components/exam-boundaries.tsx 189 2026-06-25 新增P2-3模块统一边界组件。导出 ExamErrorBoundary(薄包装 SectionErrorBoundary固定 examHomework 命名空间)、ExamEmptyStatei18n 键驱动的空状态)、ExamSkeletonlist/card/detail/analytics/editor 五种变体)。复用 shared 层 EmptyState/Skeleton/SectionErrorBoundary避免每页重复实现。新增 i18n 键 exam.error.boundaryTitle/boundaryDescription/retry
components/* 19+ 文件 考试表单/组卷/预览/分析组件
services/exam-service-port.ts 95 2026-06-25 新增P2-4模块对内的服务端口契约。导出 ExamServicePort 接口listExams/getExam/createExam/updateExam/deleteExam/duplicateExam/autoMarkExam/getExamPreview/getAnalytics/getSubjects/getGrades 共 12 个方法)+ ExamDetail/ExamCreateInput/ExamUpdateInput 类型。区别于 shared 层的 ExamHomeworkServicePort跨模块调用方契约单例注册本接口面向模块内部组件通过 React Context 注入
services/exam-service-context.tsx 72 2026-06-25 新增P2-4ExamServicePort 的 React Context 注入点。导出 ExamServiceProviderProvider 组件)、useExamService(消费者 Hook未注入时抛错useTryExamService(测试用,未注入时返回 null。当前为骨架具体实现将在 P2-6+ 落地
services/index.ts 23 2026-06-25 新增services 子模块 barrel 导出
config/exam-widgets.ts 192 2026-06-25 新增P2-5配置驱动的 Widget 布局。导出 ExamWidgetConfig/ExamWidgetLayoutConfig 类型、四个角色的默认配置TEACHER_EXAM_WIDGET_CONFIG/ADMIN_EXAM_WIDGET_CONFIG/STUDENT_EXAM_WIDGET_CONFIG/PARENT_EXAM_WIDGET_CONFIGgetExamWidgetConfig(role)getWidgetsBySlot(config, slot) 工具函数。与 dashboard/config/widget-configs.ts 模式对齐

已知问题

  • P0-1 已修复:persistAiGeneratedExamDraft 直接 insert 到 questions 改为调用 questions/data-access.createQuestionWithRelations,通过 ID 映射保持 structure 引用一致
  • P0-2 已修复:getExams/getExamById/getExamsDashboardStats 直查 classes 改为调用 classes/data-access.getClassGradeIdsByClassIds
  • P1-1 已修复:getSubjectsAction/getGradesAction 直查 subjects/grades 改为调用 school/data-access.getSubjectOptions / getGradeOptions
  • P1-2 已修复:actions.ts 832 行(超 800 建议),多处直接 DB 操作 DB 操作已下沉到 data-accessactions.ts 现 691 行
  • P0-4 已修复2026-06-24updateExamFromRichEditorAction 直接 db.transaction 写 exams/examQuestions 表 DB 事务下沉到 data-access.updateExamWithRichEditorData
  • actions.ts 拆分2026-06-24actions.ts 1525 行(超 1000 硬上限) 拆分为 4 个文件actions.ts(633 行,核心 Action) + actions-helpers.ts(96 行,共享辅助) + actions-rich-editor.ts(250 行,富文本编辑器 Action) + ai-pipeline/auto-mark.ts(356 行,AI 自动标记 Action)actions.ts re-export 保持向后兼容
  • P0-8 已修复:data-access.ts 1036 行(超 1000 硬上限) 拆分为 data-access.ts(542 行,考试 CRUD) + data-access-cross-module.ts(462 行,13 个跨模块查询/写接口),文末 re-export 保持向后兼容
  • ⚠️ P1ai-pipeline.ts 857 行(超 800 建议),混合 4 类职责
  • P2 已修复:ai-pipeline.ts 中 3 处非空断言清理(draft.sections!.forEach → 安全守卫、aiParsed.sections!.flatMap?? []aiParsed.sections!.map?? []
  • V3-5/V3-8/V3-12exam-actions.tsx 增强V3-5 角色化菜单按角色显示不同操作项、V3-8 新增 analytics 菜单项BarChart3 图标,跳转 /teacher/exams/[id]/analytics、V3-12 移动端触摸目标尺寸优化
  • 2026-06-25 P2 落地exams 审计报告实施):
    • P2-1editor/exam-rich-editor-types.ts 新增 isRichQuestionType/isStandaloneQuestionType/toRichQuestionType/toStandaloneQuestionType 类型守卫;editor/editor-to-structure.tseditor/exam-nodes-to-editor-doc.ts 改用 toRichQuestionTypeactions-rich-editor.ts 两处 as "single_choice"|"multiple_choice"|"text"|"judgment" 断言改为 toStandaloneQuestionType 安全收窄(消除 4 处 as 违规)
    • P2-3新增 components/exam-boundaries.tsxExamErrorBoundary/ExamEmptyState/ExamSkeleton 三组合单元5 种骨架变体)
    • P2-4新增 services/exam-service-port.ts + services/exam-service-context.tsxExamServicePort 接口 + Context 注入骨架,与 shared 层 ExamHomeworkServicePort 区分定位)
    • P2-5新增 config/exam-widgets.ts(四角色 Widget 配置 + getExamWidgetConfig/getWidgetsBySlot

2.3 homework作业模块

职责:作业全生命周期(创建/发布/作答/批改/分析)。

导出函数

  • ActionscreateHomeworkAssignmentAction / startHomeworkSubmissionAction / saveHomeworkAnswerAction / submitHomeworkAction / gradeHomeworkSubmissionAction / batchAutoGradeSubmissionsActionV3-7 新增批量自动批改HOMEWORK_GRADE 权限+非管理员仅可批改自己创建的作业)/ getScansAction 2026-06-24 新增:获取某次提交的所有答题扫描图,扫描图存储在 fileAttachments 表中 targetType="homework"/targetId=submissionId支持两类访问者——教师 HOMEWORK_GRADE 仅可访问自己创建的作业的提交,学生 HOMEWORK_SUBMIT 仅可访问自己的提交)/ deleteScanAction 2026-06-24 新增:删除答题扫描图,仅允许提交者本人删除且仅在提交状态为 started 时允许,调用 files/data-access.deleteFileAttachment 删除 DB 记录)/ getExcellentSubmissionsAction P3-1 新增查询优秀作业列表HOMEWORK_GRADE 权限,按 ctx.dataScope 过滤,调用前埋点 homework.excellent_viewed/ remindUnsubmittedAction P3-2 新增催交提醒HOMEWORK_GRADE 权限,查询未提交学生后通过动态 import 调用 notifications/data-access.createNotification 创建 type=homework/priority=high 站内通知Promise.allSettled 容错统计,埋点 homework.remind_unsubmitted P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access/data-access-write
  • Data-accessgetHomeworkAssignments / getHomeworkAssignmentById / getHomeworkSubmissions / getStudentHomeworkAssignments / getStudentHomeworkTakeData / getHomeworkAssignmentReviewList / getHomeworkSubmissionDetails / getDemoStudentUser(已迁移至 users 模块 getCurrentStudentUser,此处为 re-export 向后兼容)/ toQuestionContent / getAssignmentMaxScoreById(后两者供 stats-service 使用;isRecord 已于 2026-06-24 迁移至 @/shared/lib/type-guards 统一复用)/ getHomeworkAssignmentsByExamIdV3-8 新增:按考试 ID 查作业+目标/提交/批改计数)/ getGradedSubmissionsByExamIdV3-8 新增:按考试 ID 查已批改提交,按学生去重)/ getStudentSubmissionResultV3-9 新增:查学生指定作业最新提交,用于结果页)/ getStudentExamResultsV3-11 新增:查学生考试结果列表,供家长端展示)/ getExcellentSubmissions P3-1 新增:查优秀作业列表,按得分率过滤 minPercentage 默认 80同一学生取最高分按得分率降序支持 scope 过滤)/ getUnsubmittedStudents P3-2 新增:查未提交作业的学生列表,对比 targets 与已提交学生集合返回差集,供催交提醒使用)
  • Data-access-classesgetAssignmentIdsForStudents / getHomeworkAssignmentsWithSubject / getHomeworkAssignmentsByIds / getAssignmentTargetCounts / getHomeworkSubmissionsForStudents / getPublishedHomeworkAssignmentsWithSubject / getHomeworkSubmissionsForAssignmentsP0-7 新增,供 classes 模块跨模块调用; P1-1 已修复:内部不再直查 exams/subjects 表,改为调用 exams/data-access.getExamSubjectIdMap + school/data-access.getSubjectNameMapByIds
  • Data-access-write11 个写操作函数P1-2 新增 10 个从 actions 下沉 + V3-7 新增 batchAutoGradeSubmissions
  • Stats-servicegetTeacherGradeTrends / getHomeworkAssignmentAnalytics / getStudentDashboardGrades(从 data-access.ts re-export 以保持向后兼容)
  • ComponentsV3-7/V3-9 新增):HomeworkBatchGradingView(批量批改视图:勾选+一键批改+toast 反馈)/ HomeworkSubmissionResult(提交后即时反馈:分数汇总+对错分布+错题预览)
  • Components 2026-06-24 新增扫描图相关):ScanUploader(学生扫描图上传组件,调用 /api/upload 上传图片targetType="homework",支持增删/排序,导出 ScanImage 类型)/ ScanImageViewer(扫描图查看器,支持翻页/缩放/旋转/全屏,用于阅卷式批改时查看学生答题图片)/ HomeworkScanGradingView(教师阅卷式批改视图,使用 ResizablePanel 左侧题目+批改表单、右侧扫描图查看器,集成 getScansAction 拉取扫描图、gradeHomeworkSubmissionAction 批改,支持上一份/下一份提交快速切换)
  • Components P3-1 新增):ExcellentSubmissions(优秀作业展示 async 服务端组件,按得分率降序,前三名金/银/铜徽章,迟交 Clock 标注,仅展示姓名不展示学号保护隐私,直接调用 getExcellentSubmissions 避免多余 action 往返)/ ExcellentSubmissionsWithBoundary(带 SectionErrorBoundary + Suspense 包裹版本,提供 Skeleton 骨架屏与局部错误降级,避免本区块故障影响整个作业详情页)
  • Types 2026-06-24 新增):ScanAttachment(扫描图附件结构 { fileId, url, filename, originalName, page },由 getScansAction 返回)/ ScanImage(扫描图 UI 结构,由 ScanUploader 导出供 ScanImageViewer 复用)
  • Types P3-1 新增):ExcellentSubmissionItem(优秀作业展示项 { submissionId, assignmentId, assignmentTitle, studentName, totalScore, maxScore, percentage, submittedAt, isLate }percentage 为得分率 0-100isLate 标注迟交但质量优秀样例)/ ExcellentSubmissionQuery(优秀作业查询参数 { assignmentId, minPercentage?, limit? }minPercentage 默认 80limit 默认 10

依赖关系

  • 依赖:shared/*@/authexams P1-1 已修复:通过 exams data-access.getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework/getExamForProctoringCrossModuleclasses P1-1 已修复:通过 classes data-access.getStudentIdsByClassId 等 7 个函数)、school P1-1 已修复:通过 school data-access.getSubjectOptions/getSubjectNameMapByIdsusers P1-1 已修复:通过 users data-access.getUserWithRole/getUserNamesByIdsfiles 2026-06-24 新增:deleteScanAction 调用 files/data-access.deleteFileAttachment 删除扫描图 DB 记录,合理跨模块调用)、notifications P3-2 新增:remindUnsubmittedAction 通过动态 import 调用 notifications/data-access.createNotification 为未提交学生创建催交通知,合理跨模块调用)
  • 被依赖:dashboard(通过 data-access合理parent(通过 data-access合理V3-11 新增 getStudentExamResults 供 parent 调用)、classes P0-7 已修复classes 通过 homework/data-access-classes 获取作业数据,不再反向直查 homework/exams 表)、examsV3-8 新增:exams/stats-service.getExamAnalytics 调用 getHomeworkAssignmentsByExamId / getGradedSubmissionsByExamId,合理跨模块调用)

已知问题

  • P0 已解决:data-access.ts 已拆分至 598 行(原 1038 行超 1000 硬上限),统计函数迁移至 stats-service.ts
  • P0 已解决:getStudentDashboardGrades 排名计算逻辑迁移至 stats-service.ts
  • P0 已解决:getHomeworkAssignmentAnalytics 错误率统计逻辑迁移至 stats-service.ts
  • P0-7 已修复:新增 data-access-classes.ts,将 classes 模块对 homework/exams 表的直查封装为 homework 模块的导出函数,恢复三层架构
  • P1-1 已修复:5 处直查 exams 改为调用 exams/data-access.getExamIdsByGradeIds / getExamSubjectIdMap / getExamWithQuestionsForHomeworkdata-access-classes.ts 内部 JOIN exams+subjects 也改为调用 exams/data-access.getExamSubjectIdMap + school/data-access.getSubjectNameMapByIds,彻底消除跨模块直查
  • P0-竞品已修复:新增 hooks/use-exam-countdown.ts 倒计时 Hook对标智学网/猿题库),homework-take-view.tsx 集成限时/监考模式倒计时显示与到时自动提交;data-access.tsgetStudentHomeworkTakeData 新增 examModeConfig + startedAt 字段供客户端计算截止时间
  • P1-2 已修复:actions.ts 多处直接 DB 操作(createHomeworkAssignmentAction 157 行) DB 操作已下沉到 data-access-write.tsactions.ts 现 239 行
  • V3-7新增 batchAutoGradeSubmissionsAction + batchAutoGradeSubmissions + HomeworkBatchGradingView,提交列表页接入批量批改
  • V3-8新增 getHomeworkAssignmentsByExamId + getGradedSubmissionsByExamId,供 exams 模块跨模块调用
  • V3-9新增 getStudentSubmissionResult + HomeworkSubmissionResult + 路由 /student/learning/assignments/[assignmentId]/resulthomework-take-view.tsx 提交后跳转结果页
  • V3-12homework-take-view.tsx 移动端触摸目标尺寸优化
  • 2026-06-23 审计修复data-access 层 4 个查询函数新增 scope?: DataScope 参数(getHomeworkAssignments/getHomeworkAssignmentById/getHomeworkSubmissions/getHomeworkAssignmentReviewList),教师仅查看自己创建的作业,学生/家长仅查看相关作业Server Action catch 块改用 handleActionError 统一错误处理
  • 2026-06-24 i18n 完成11 个文件6 个客户端组件 + 3 个服务端页面 + 2 个已有 t 的页面)全部接入 next-intl,硬编码中文/英文文案替换为 examHomework.homework.* 翻译键zh-CN + en 双语);客户端组件使用 useTranslations("examHomework"),服务端页面使用 getTranslations("examHomework")
  • 2026-06-25 审计修复 P013 个缺失的 loading.tsx/error.tsx 路由文件全部补全teacher/homework 根 + assignments + submissions + submissions/[submissionId] + scan-gradingstudent/learning/assignments + resulthomework-take-view.tsx 拆分为 5 个组件(HomeworkTakeView 主容器 396 行 + HomeworkTakeSidebar 侧边栏 + HomeworkTakeConfirmDialog 提交确认对话框);schema.ts 错误消息改为 i18n 键 homework.form.error.*Server Action 通过 translateZodErrors 翻译后返回 fieldErrorsScanAttachment 类型从 actions.ts 迁移至 types.ts
  • 2026-06-25 审计修复 P1homework-grading-view.tsx 删除本地 wrapper 函数(isAutoGradable/applyAutoGrades/getCorrectnessState/extractQuestionText),直接调用 lib/question-content-utilsdata-access.ts 末尾 re-exportgetHomeworkAssignmentsByExamId/getGradedSubmissionsByExamId/getStudentSubmissionResult/getStudentExamResults/getStudentHomeworkAssignments/getStudentHomeworkTakeData)已删除,调用方改为直接 import 子模块;teacher/homework/assignments/[id]/page.tsx Analytics 区块接入 SectionErrorBoundary + Suspense + SkeletongetScansAction 中的 DB 查询下沉到新建的 data-access-scans.ts::getScansBySubmissionId
  • 2026-06-25 审计修复 P2startHomeworkSubmissionAction / saveHomeworkAnswerAction / deleteScanAction 补全 trackExamEvent 埋点(homework.started / homework.answer_saved / homework.scan_deletedextractQuestionTexthomework-grading-view.tsx 迁移到 lib/question-content-utils.ts

文件清单

文件 行数 职责
data-access.ts 716 作业 CRUD + 批改( 2026-06-25删除末尾 re-export调用方改为直接 import 子模块; P3-1 新增 getExcellentSubmissions:按得分率过滤优秀作业,同一学生取最高分; P3-2 新增 getUnsubmittedStudents:查询未提交作业学生供催交提醒使用)
data-access-write.ts 455 作业写操作P1-2 新增 10 个写函数从 actions 下沉V3-7 新增 batchAutoGradeSubmissions
data-access-classes.ts 285 跨模块查询封装P0-7 新增; P1-1 已修复:内部通过 exams/school data-access 获取考试科目信息,不再直查 exams/subjects 表)
data-access-student.ts 293 学生端只读查询( 2026-06-25从 data-access.ts 拆出 getStudentSubmissionResult/getStudentExamResults/getStudentHomeworkAssignments/getStudentHomeworkTakeData
data-access-exam-cross.ts 119 考试跨模块查询(getHomeworkAssignmentsByExamId/getGradedSubmissionsByExamId
data-access-scans.ts 44 2026-06-25 审计新增:扫描图查询(getScansBySubmissionId,从 actions.ts 下沉的 DB 查询)
data-access-error-collection.ts 121 错题采集查询
data-access-utils.ts 8 内部工具函数
stats-service.ts 414 统计分析(教师趋势/作业分析/学生仪表盘成绩)
actions.ts 688 10 个 Server ActionP1-2 已修复,无直接 DB 操作; 2026-06-25ScanAttachment 类型迁移至 types.tsschema.ts 错误消息改为 i18n 键,新增 translateZodErrors 翻译辅助函数;补全 trackExamEvent 埋点; P3-1 新增 getExcellentSubmissionsAction查询优秀作业列表HOMEWORK_GRADE 权限 + scope 过滤 + 埋点 homework.excellent_viewed P3-2 新增 remindUnsubmittedAction:催交提醒,调用 notifications/data-access.createNotification 创建站内通知,埋点 homework.remind_unsubmitted
types.ts 282 类型定义( 2026-06-25新增 ScanAttachment 接口,从 actions.ts 迁入; P3-1 新增 ExcellentSubmissionItem/ExcellentSubmissionQuery 类型)
schema.ts 63 Zod 校验( 2026-06-25错误消息改为 i18n 键 homework.form.error.*
lib/question-content-utils.ts 321 题目内容工具函数( 2026-06-25extractQuestionText 从 grading-view 迁入)
lib/question-content-utils.test.ts 415 工具函数单测
hooks/use-exam-countdown.ts 106 P0-竞品新增:考试倒计时 Hook每秒更新、紧急状态高亮、到时回调自动提交
hooks/use-debounced-auto-save.ts 182 答案防抖自动保存 HooklocalStorage 离线缓存)
components/homework-take-view.tsx 396 2026-06-25 重构:从 520 行拆分为 3 个组件(主容器 + Sidebar + ConfirmDialogV3-9/V3-12提交后跳转结果页+移动端触摸优化P0-竞品:集成限时/监考模式倒计时useExamCountdown+ 到时自动提交
components/homework-take-sidebar.tsx 169 2026-06-25 新增:从 homework-take-view 拆出(自动保存状态/作业信息/截止时间/尝试次数/描述/进度网格/提交按钮)
components/homework-take-confirm-dialog.tsx 62 2026-06-25 新增:从 homework-take-view 拆出(提交确认对话框,含未作答数量警告)
components/homework-grading-view.tsx 472 2026-06-25删除 4 个本地 wrapper 函数,直接调用 lib/question-content-utils
components/homework-batch-grading-view.tsx 165 V3-7 新增批量批改视图use client
components/homework-submission-result.tsx 224 V3-9 新增:提交后即时反馈页
components/homework-scan-grading-view.tsx 262 2026-06-24 新增教师阅卷式批改视图ResizablePanel 左题目右扫描图 + 上一份/下一份切换)
components/scan-uploader.tsx 254 2026-06-24 新增:学生扫描图上传组件(调用 /api/uploadtargetType="homework",导出 ScanImage 类型)
components/scan-image-viewer.tsx 194 2026-06-24 新增:扫描图查看器(翻页/缩放/旋转/全屏)
components/question-renderer.tsx 339 题目渲染器(支持单选/多选/判断/文本题型)
components/excellent-submissions.tsx 206 P3-1优秀作业展示服务端组件 + SectionErrorBoundary + Suspense + 骨架屏; P3-5 修复:getTranslationsnext-intl/server 导入)
components/student-homework-review-view.tsx 206 学生作业回顾视图
components/homework-assignment-form.tsx 219 作业创建/编辑表单
components/assignment-filters.tsx 46 作业列表筛选器use client基于 nuqs 的 URL 状态管理( 2026-06-24 i18n接入 useTranslations
components/homework-assignment-exam-content-card.tsx 29 作业考试内容卡片( 2026-06-24 i18n接入 useTranslations
components/homework-assignment-exam-preview-pane.tsx 39 作业考试预览面板( 2026-06-24 i18n接入 useTranslations
components/homework-assignment-exam-error-explorer.tsx 39 作业错误分析主组件
components/homework-assignment-exam-error-explorer-lazy.tsx 76 作业错误分析懒加载组件( 2026-06-24 i18nFallback 接入 useTranslations
components/homework-assignment-question-error-detail-panel.tsx 132 题目错误详情面板( 2026-06-24 i18n接入 useTranslationsformatAnswer 移入组件内闭包以访问 t
components/homework-assignment-question-error-overview-card.tsx 89 题目错误概览卡片( 2026-06-24 i18n接入 useTranslations

2.4 questions题库模块

职责:题库管理(题目 CRUD、知识点关联、题型支持、教材→章节→知识点级联筛选

导出函数

  • Actions11 个,全部调用 requirePermission()getQuestionsAction / createQuestionAction / updateQuestionAction / deleteQuestionAction / getKnowledgePointOptionsAction / getTextbookOptionsAction 审计新增:级联第一级)/ getChapterOptionsAction(textbookId) 审计新增:级联第二级)/ getKnowledgePointOptionsByChapterAction(chapterId) 审计新增:级联第三级)/ deleteQuestionsBatchAction 审计新增:批量删除)/ exportQuestionsAction 审计新增:导出)/ importQuestionsAction 审计新增:导入)
  • Data-accessgetQuestions 审计升级:支持 textbookId/chapterId 级联筛选参数,通过 textbooks data-access 获取知识点 ID 集合)/ createQuestionWithRelations / updateQuestionById / deleteQuestionByIdRecursive / getKnowledgePointOptions / getTextbookOptions 审计新增)/ getChapterOptions(textbookId) 审计新增)/ getKnowledgePointOptionsByChapter(chapterId) 审计新增)/ deleteQuestionsBatch 审计新增:批量删除,事务原子性)/ exportQuestions 审计新增:导出,权限范围过滤)/ importQuestions 审计新增:批量导入,事务原子性)
  • TypesQuestion / QuestionWithRelations / GetQuestionsParams 审计升级:新增 textbookId/chapterId 字段)/ CreateQuestionInput / KnowledgePointOption / TextbookOption 审计新增)/ ChapterOption 审计新增)/ QuestionContent 审计新增:类型守卫接口)/ QuestionOption 审计新增)/ QUESTION_TYPE_I18N_KEY 审计新增:题型 i18n 键映射)/ DIFFICULTY_I18N_KEY 审计新增:难度 i18n 键映射)
  • UtilsparseQuestionContent(raw) 审计新增:类型守卫,替代 as 断言)/ getQuestionPreview / getQuestionOptions / getQuestionText / trackQuestionEvent 审计新增:埋点接口 no-op/ trackQuestionCreated / trackQuestionUpdated / trackQuestionDeleted / trackQuestionSearched
  • ComponentsQuestionFilters 审计重写:组合 QuestionCascadeFilter/ QuestionCascadeFilter 审计新增三级级联筛选nuqs 管理 URL 状态)/ QuestionDataTable 审计重写i18n + 集成 BatchOperations Phase 4.3:引入 @tanstack/react-virtual 虚拟滚动替换分页)/ useQuestionColumns 审计改为 Hook因 useTranslations 是 Hook/ CreateQuestionButton 审计重写:权限感知)/ QuestionActions 审计重写:权限感知 + 类型守卫 + QuestionContentRenderer 结构化预览)/ CreateQuestionDialog 审计重写:拆分为 281 行)/ KnowledgePointSelector 审计新增:从 dialog 拆分)/ OptionsEditor 审计新增:从 dialog 拆分)/ QuestionBankResultsClient 审计新增RSC 与客户端 Hook 桥接)/ QuestionContentRenderer 审计新增:题目结构化渲染)/ BatchOperations 审计新增:批量删除工具栏)/ ImportExportButtons 审计新增JSON 导入导出)

依赖关系

  • 依赖:shared/*@/authtextbooks 审计升级:通过 textbooks data-access 的 getTextbooks / getChaptersByTextbookId / getKnowledgePointsByChapterId / getKnowledgePointsByTextbookId 实现级联筛选)
  • 被依赖:exams(通过类型导入,合理)、textbooksUI 组合,合理)、error-book(通过 data-access合理adaptive-practice(通过 data-access合理

已知问题

  • P1-2 已修复:写操作函数错放在 actions.ts 已下沉到 data-access
  • P1-1 已修复:getKnowledgePointOptionsAction 直查 textbooks 模块表 改为调用 textbooks data-access
  • P2 已解决:data-access.ts 仅 129 行 扩充至 260+ 行
  • 审计 P0 已修复:按名字搜索等于无用 新增教材→章节→知识点三级级联筛选QuestionCascadeFilter + URL 状态同步)
  • 审计 P0 已修复:前端权限硬编码 全部改用 usePermission().hasPermission()
  • 审计 P0 已修复:as 断言违反 TypeScript 严格模式 提取 parseQuestionContent 类型守卫函数
  • 审计 P0 已修复:i18n 完全缺失 新增 questions.json 翻译文件zh-CN + en所有展示文本使用 i18n 键
  • 审计 P0 已修复:缺 Error Boundary 新增 app/(dashboard)/teacher/questions/error.tsx
  • 审计 P1 已修复:create-question-dialog.tsx 453 行超规范 拆分为 KnowledgePointSelector + OptionsEditor + 主对话框281 行)
  • 审计 P1 已修复:列定义为纯函数无法使用 useTranslations 改为 useQuestionColumns Hook通过 QuestionBankResultsClient 客户端桥接
  • 审计 P1 已修复:缺监控埋点 预留 trackQuestionEvent no-op 接口
  • 审计 P2 已修复:Server Action 缺权限校验 全部 8 个 Action 均调用 requirePermission()
  • 审计 P2 已实施:仅支持教师角色 新增 admin/questions 页面,复用全部组件,权限感知自动适配
  • 审计 P2 已实施:无题目结构化预览 新增 QuestionContentRenderer 组件(题干/选项/答案/解析,单选圆点/多选方框,正确答案高亮)
  • 审计 P2 已实施:无批量操作 新增 deleteQuestionsBatch data-access + deleteQuestionsBatchAction + BatchOperations 组件(事务原子性,权限感知)
  • 审计 P2 已实施:无导入导出 新增 exportQuestions/importQuestions data-access + exportQuestionsAction/importQuestionsAction + ImportExportButtons 组件JSON 格式,权限范围过滤,导入预览确认)

文件清单

文件 行数 职责
actions.ts 380+ 11 个 Server Action全部 requirePermission含 3 个级联筛选 + 批量删除 + 导入导出)
data-access.ts 600+ 题目 CRUD + 知识点选项 + 级联筛选 + 批量删除 + 导入导出
schema.ts 18 Zod 校验
types.ts 60+ 类型定义(含 TextbookOption/ChapterOption/QuestionContent/i18n 键映射)
utils/parse-content.ts 60+ 类型守卫parseQuestionContent + 便捷方法)
utils/track-event.ts 30+ 埋点接口no-op 实现)
components/question-cascade-filter.tsx 130+ 三级级联筛选nuqs URL 状态)
components/question-filters.tsx 40+ 筛选栏(组合 QuestionCascadeFilter
components/question-data-table.tsx 180+ 数据表格i18n + 集成 BatchOperations + Phase 4.3@tanstack/react-virtual 虚拟滚动,替换分页避免大数据渲染卡顿)
components/question-columns.tsx 100+ 列定义 HookuseQuestionColumns
components/create-question-button.tsx 30+ 创建按钮(权限感知)
components/question-actions.tsx 200+ 行操作(权限感知 + 类型守卫 + QuestionContentRenderer
components/create-question-dialog.tsx 281 创建对话框(组合 KnowledgePointSelector + OptionsEditor
components/knowledge-point-selector.tsx 80+ 知识点选择器(从 dialog 拆分)
components/options-editor.tsx 120+ 选项编辑器(从 dialog 拆分)
components/question-bank-results-client.tsx 30+ RSC 与客户端 Hook 桥接
components/question-content-renderer.tsx 110+ 题目结构化渲染( 审计新增)
components/batch-operations.tsx 110+ 批量操作工具栏( 审计新增)
components/import-export-buttons.tsx 180+ 导入导出组件( 审计新增)

2.5 textbooks教材模块— 标杆模块data-access 层)

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

导出函数

  • Actions14 个,均为写操作;读操作由 RSC 页面直接调用 data-accesscreateTextbookAction / updateTextbookAction / deleteTextbookAction / createChapterAction / updateChapterContentAction / deleteChapterAction / reorderChaptersAction / createKnowledgePointAction / updateKnowledgePointAction / deleteKnowledgePointAction / getKnowledgePointsByChapterAction / getKnowledgeGraphDataAction Task 7 新增:知识图谱数据查询,支持 structure/student-mastery/class-mastery 三种视图)/ createPrerequisiteAction Task 7 新增:声明前置依赖,含循环检测)/ deletePrerequisiteAction Task 7 新增:删除前置依赖)
  • Data-accessgetTextbooks / getTextbookById / getChaptersByTextbookId / getKnowledgePointsByChapterId / getKnowledgePointsByTextbookId / createTextbook / updateTextbook / deleteTextbook / createChapter / updateChapterContent / deleteChapter / createKnowledgePoint / updateKnowledgePoint / deleteKnowledgePoint / reorderChapters / getTextbooksDashboardStats / getKnowledgePointOptions(跨模块接口,供 questions 调用)/ getTextbooksWithScopeP1-1 新增:按数据范围获取教材列表,学生端强制按年级过滤)/ verifyChapterBelongsToTextbookP0-4 新增:资源归属校验)/ verifyKnowledgePointBelongsToTextbookP0-4 新增:资源归属校验)/ verifyKnowledgePointBelongsToChapterP0-4 新增:校验知识点是否属于指定章节)/ createPrerequisite Task 7 新增:创建前置依赖)/ deletePrerequisite Task 7 新增:删除前置依赖)/ getPrerequisiteEdgesForTextbook Task 7 新增:获取教材下所有前置依赖边,用于循环检测)/ getSubjectLabelKey / getGradeLabelKeyi18n 标签键)
  • Data-access-graph Task 5 新增,图谱只读查询):getKnowledgePointsWithRelations(知识点+依赖+题目数聚合查询)/ getStudentKpMastery(学生掌握度)/ getClassKpMastery(班级平均掌握度)/ getPrerequisitesForKp / getSuccessorsForKp
  • Constants 新增):SUBJECTS / GRADES / SUBJECT_COLORS / getSubjectColor / getSubjectLabelKey / getGradeLabelKey
    • v1 测试修复:SUBJECTS 新增 Chinese 学科、GRADES 新增 Grade 1/Grade 2,与 seed 数据对齐
    • v1 测试修复:SUBJECT_COLORS 新增 Chinese 主题色rose
  • Utils 新增,纯函数 + 单测):sortChapters / buildChapterTree / buildChapterIndex / findChapterParent / filterKnowledgePointsByChapter / normalizeOptional / highlightKnowledgePoints / hasCycleAfterAddingEdgeTask 4 新增循环依赖检测DFS 算法)/ findChapterByIdV3 续审计新增:递归查找章节树中指定 ID 的章节,替代各模块本地重复实现)
  • Graph-layout 新增,纯函数 + 单测):computeGraphLayout
  • Components Task 13 新增):GraphNodeDetailPanel — 知识图谱节点详情侧边面板,展示知识点名称/描述/掌握度/关联题目/前置后置知识点,支持跳转与前置关系增删
  • Analytics 新增):TextbookAnalytics / TextbookAnalyticsProvider / useTextbookAnalytics

依赖关系

  • 依赖:shared/*@/auth@xyflow/react(知识图谱可视化)、@dagrejs/dagre(图谱分层布局算法)
  • 被依赖:questions P1-1 已修复:通过 textbooks data-accessexams(通过类型)、dashboard(通过 data-accessP0-4 已修复)
  • UI 层跨模块依赖已解耦:textbooks/components/knowledge-point-dialogs.tsx 不再直接 import questions 模块,改为通过 render prop 注入创建题目入口

已知问题

  • 无跨模块 DB 访问data-access 层)
  • actions 层编排模式标杆(权限校验 → 调用 data-access → revalidatePath
  • data-access 层职责单一
  • P2 已修复:data-access.tsbyId.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.tsSUBJECTS / GRADES / SUBJECT_COLORS
  • v1 测试修复:textbook-reader.tsx 移除 SheetTrigger(错误置于 Sheet 外部导致 DialogTrigger must be used within Dialog),改用受控 Button + onClick 打开移动端抽屉
  • v1 测试修复:新增 teacher-textbook-reader.tsx 客户端包装组件,解决 Server Component 向 Client Component 传递函数 proprenderQuestionCreator)违反 Next.js 序列化约束的问题
  • v1 测试修复:scripts/seed.ts 教材 subject/grade 改为英文 valueconstants.ts 对齐),消除 i18n MISSING_MESSAGE 错误
  • P1 纯逻辑已导出并补单测:新增 utils.ts / graph-layout.ts 及对应 .test.ts
  • v3 审计修复2026-06-24学生端两个页面添加 requirePermission(Permissions.TEXTBOOK_READ) 显式权限校验
  • v3 审计修复2026-06-24getKnowledgeGraphDataAction 添加学生年级 scope 过滤,防止跨年级越权查看图谱数据
  • v3 审计修复2026-06-24getKnowledgeGraphDataAction 的 class-mastery 模式限制为教师/管理员使用
  • v3 审计修复2026-06-24deleteChapter 级联删除 knowledgePointPrerequisites 孤儿记录,保证数据完整性
  • v3 审计修复2026-06-24knowledge-graph.tsx 前置依赖操作添加 try/catch/finally防止 loading 状态卡死
  • v3 审计修复2026-06-24textbook-reader.tsx onCreateKnowledgePoint 添加 try/finally
  • v3 审计修复2026-06-24section-error-boundary.tsx 添加 componentDidCatch 错误日志记录 + role="alert" a11y
  • v3 审计修复2026-06-244 个 error.tsx 抽取为共享 RouteError 组件,消除重复代码
  • v3 审计修复2026-06-24所有 loading.tsx 添加 role="status" / aria-busy a11y 标记
  • v3 审计修复2026-06-24chapter-sidebar-list.tsx 章节项添加 role="button" / tabIndex / onKeyDown 键盘支持
  • v3 审计修复2026-06-24graph-node-detail-panel.tsx 图标按钮添加 aria-label
  • v3 审计修复2026-06-24data-access.ts as [string, string] 断言改为返回类型标注
  • v3 审计修复2026-06-24knowledge-point-dialogs.tsx as 断言改为 void 包装
  • v3 审计修复2026-06-24graph-kp-node.tsx 双重 as unknown as 断言改为类型守卫函数 extractNodeData
  • v3 审计修复2026-06-24graph-layout.ts GraphLayoutNodeData 新增 graphData 字段类型定义
  • v3 审计修复2026-06-24删除死代码 verifyKnowledgePointBelongsToChapterReorderChaptersSchema、deprecated canEdit prop
  • v3 审计修复2026-06-24textbook-settings-dialog.tsx 新增 redirectAfterDelete prop 消除硬编码 URL
  • v3 审计修复2026-06-24teacher/textbooks/[id]/loading.tsx 骨架屏布局改为匹配阅读器布局
  • v3 P2 修复2026-06-24data-access.ts 3 处硬编码英文异常文案改为 NotFoundError(教材/章节),消息可安全返回客户端
  • v3 P2 修复2026-06-24graph-layout.ts O(n²) some() 查找改为 Set 查找,性能降为 O(n)
  • v3 P2 修复2026-06-24抽取 textbook-form-fields.tsx 共享组件,消除 textbook-form-dialogtextbook-settings-dialog 表单字段重复
  • v3 P2 修复2026-06-24textbook-content-panel.tsx 17 个 props 按 chapter/editing/selection/highlight 四组重构,提升可读性
  • v3 P2 修复2026-06-24textbook-reader.tsx 移除命令式 classList 操作,视觉高亮改为 isHighlighted 状态驱动,仅保留 scrollIntoView
  • v3 P2 修复2026-06-24use-kp-crud.ts122 行)拆分为 use-kp-create/use-kp-delete/use-kp-update 三个子 Hook + 门面,均在 80 行限制内
  • ⚠️ i18n 覆盖率约 99%v3 新增 notFound/noClassMasteryPermission/close/prerequisiteAddFailed/prerequisiteRemoveFailed 等键data-access 异常文案已错误码化)
  • ⚠️ P2 图谱方向键导航未实现
  • ⚠️ P2 data-access 函数未导出非缓存版本,单测难以 mock

文件清单

文件 行数 职责
actions.ts 502 14 个 Server Action写操作含 Zod 校验 + 资源归属校验 + 知识图谱查询/前置依赖管理 + class-mastery 视图)
data-access.ts 593 教材/章节/知识点 CRUD + 跨模块查询接口 + 资源归属校验 + 数据范围过滤 + 前置依赖 CRUDv3 P2硬编码异常改为 NotFoundError
data-access-graph.ts 188 知识图谱只读查询( Task 5 新增:知识点关联聚合、学生/班级掌握度、前置后置知识点,标记 server-onlyv2 修复:前置依赖双向 IN 过滤防跨教材污染)
types.ts 94 类型定义含知识图谱类型GraphViewMode/GraphLayoutMode/MasteryInfo/KpWithRelations/GraphNodeData/GraphEdgeData/KnowledgeGraphData/MasteryLevel
schema.ts 56 Zod 校验(含 CreatePrerequisiteSchema/DeletePrerequisiteSchemav2 修复refine 消息改为英文v3 修复:移除死代码 ReorderChaptersSchema
constants.ts 91 学科/年级常量与颜色映射( 新增v1 测试修复:新增 Chinese/Grade 1/Grade 2
utils.ts 226 章节树构建/排序/查找等纯函数 + 循环检测( 新增,含单测)
graph-layout.ts 109 知识图谱布局计算纯函数( Task 8 重写为 dagre 集成,输出 React Flow 格式含单测v3 P2O(n²) 改为 Set 查找)
analytics.tsx 37 教材分析 Context/Provider/Hook 新增)
hooks/use-knowledge-point-actions.ts 45 知识点操作门面 Hookv2 拆分:组合 useKpDialogState + useKpCrud对外 API 不变)
hooks/use-kp-dialog-state.ts 33 知识点对话框状态管理 Hookv2 新增:从 use-knowledge-point-actions 拆分)
hooks/use-kp-crud.ts 52 知识点 CRUD 门面 Hookv3 P2 拆分:组合 useKpCreate/useKpDelete/useKpUpdate对外 API 不变)
hooks/use-kp-create.ts 43 知识点创建 Hookv3 P2 新增:从 use-kp-crud 拆分)
hooks/use-kp-delete.ts 49 知识点删除 Hookv3 P2 新增:从 use-kp-crud 拆分)
hooks/use-kp-update.ts 36 知识点更新 Hookv3 P2 新增:从 use-kp-crud 拆分)
hooks/use-text-selection.ts 49 文本选区捕获 Hook
hooks/use-graph-data.ts 65 知识图谱数据加载 Hook Task 11 新增:派生值模式避免 effect 中 setStatev2 修复:区分 isLoading/isRefreshing 避免 UI 闪烁)
components/knowledge-graph.tsx 408 知识图谱主组件( Task 10/15 重写:全书视图 + 搜索高亮 + 关联节点高亮 + 章节着色v2 新增:添加/删除前置依赖 Dialogv4 新增hierarchical/force 布局模式切换,根据布局模式渲染分层 ReactFlow 或 Obsidian 风格力导向图,状态/详情面板/依赖增删对话框共享)
components/graph-kp-node.tsx 92 React Flow 自定义节点( Task 9 新增:知识点名称+题目数徽章+掌握度进度条v2 修复:节点宽度常量化 NODE_WIDTHv3 修复:类型守卫替代 as 断言)
components/graph-prerequisite-edge.tsx 43 React Flow 自定义边( Task 9 新增:虚线+箭头表示前置依赖v2 修复GraphEdgeData 类型安全转换)
components/graph-toolbar.tsx 121 图谱工具栏( Task 9 新增:视图模式切换 + 搜索 + 重置视图v2 新增isRefreshing 轻量指示器v4 新增hierarchical/force 布局模式切换 Select
components/force-graph.tsx 451 Obsidian 风格力导向图谱组件v4 新增:基于 react-force-graph-2d + d3-forcev5 视觉对齐 Obsidian Graph View小圆点节点+极细半透明边线+标签右侧带背景+hover/选中邻居高亮+紫色外圈焦点环;通过 next/dynamic ssr:false + import type 加载避免 SSR canvas/window 报错)
components/graph-node-detail-panel.tsx 171 节点详情侧边面板( Task 13 新增:描述/掌握度/关联题目/前置后置列表v2 修复:移除未使用 textbookId propv3 修复aria-label
components/chapter-sidebar-list.tsx 325 章节侧边栏列表v2 修复canEdit 默认 false + orderUpdateFailed i18n keyv3 修复:键盘导航)
components/section-error-boundary.tsx 53 章节内容错误边界v2 修复:默认值改为空字符串 + 条件渲染v3 修复componentDidCatch + a11y
components/textbook-form-fields.tsx 103 教材表单字段共享组件v3 P2 新增:消除 form-dialog 与 settings-dialog 表单重复)
components/textbook-content-panel.tsx 199 章节内容面板v3 P217 props 按 chapter/editing/selection/highlight 四组重构)
components/textbook-form-dialog.tsx 68 教材创建对话框v3 P2复用 TextbookFormFields
components/textbook-settings-dialog.tsx 136 教材设置对话框v3 P2复用 TextbookFormFields
components/textbook-reader.tsx 458 教材阅读器主组件v3 P2移除命令式 classList高亮改为状态驱动v5 桌面端改用 ResizablePanel 可拖拽分栏,图谱 tab 时图谱初始占 60%,切换 tab 用 key 重置比例)
components/* 17 文件 教材编辑/知识图谱组件(新增 textbook-form-fields.tsx、5 个 graph-* 组件、3 个 use-kp-* 子 Hookteacher-textbook-reader.tsx 已移至 app 层)

2.6 grades成绩模块— 标杆模块(拆分范例)

职责:成绩分析(录入/查询/统计/导出/趋势对比分析)。

导出函数

  • ActionsgetGradeRecordsAction / createGradeRecordActionv4-P1-6 增强:成绩录入后通知学生和家长,调用 notifyGradeEntered/ updateGradeRecordAction / deleteGradeRecordAction / exportGradesActionv4-P1-12 增强:新增可选 studentId 参数,支持按学生导出,家长视角调用 exportStudentGradeRecordsToExcel,校验 studentId 属于家长子女)/ getGradeTrendAction / getClassComparisonAction / getSubjectComparisonAction / getGradeDistributionAction / getGradeDistributionByGradeIdAction v4-P2-7 新增年级仪表盘维度1按 gradeId 查询年级整体 + 按班级拆分的成绩分布GRADE_RECORD_READ 权限)/ getClassRankingAction / getRankingTrendAction / getGradeRecordByIdAction / getClassGradeStatsAction / getStudentGradeSummaryAction / batchCreateGradeRecordsActionv4-P1-6 增强:批量成绩录入后通知学生和家长)/ batchCreateGradeRecordsByExamAction 2026-06-24 新增, 2026-06-25 增强错题采集:按试卷批量录入每题得分,流程 requirePermission → getExamForGradeEntry scope 校验 → assertClassInScope → safeJsonParse → BatchGradeEntryByExamSchema 校验 → batchCreateGradeRecordsByExam 单事务写入(返回 {gradeRecordId, studentId, submissionId}[])→ Promise.allSettled 并行执行 updateMasteryFromExamScore + collectFromExamSubmission考试错题采集失败不阻断→ notifyGradeEntered → revalidatePath返回 gradeRecordId 列表供撤销)/ saveGradeDraftAction / getGradeDraftAction / deleteGradeDraftAction v3-P2 新增:成绩录入草稿 Server Actions分别使用 GRADE_RECORD_MANAGE/GRADE_RECORD_READ/GRADE_RECORD_MANAGE 权限)。注:assertClassInScope 原位于 actions.ts P3 新增导出:班级 scope 校验工具,供 actions-analytics 复用), v4-P2-6 修复:因 "use server" 文件要求所有 export 为 asyncassertClassInScope 是同步函数,已迁移至独立文件 lib/scope-check.tsactions.ts 与 actions-analytics.ts 均从 ./lib/scope-check 导入
  • Data-accessgetGradeRecords / getStudentGradeSummary / getClassRanking / getClassStudentsForEntry / getClassGradeStats / getClassGradeStatsWithMeta / getGradeTrend / getClassComparison / getSubjectComparison / getGradeDistribution / getGradeDistributionByGradeId v4-P2-7 新增年级仪表盘维度1通过 getClassesByGradeId 获取年级下所有班级inArray 查询成绩记录,复用 computeGradeDistribution/computeGradeStats 纯函数,返回整体分布 + 按班级拆分)/ getRankingTrend / PaginatedGradeRecords P3 新增:分页结果接口 { records, total }/ saveGradeDraft / getGradeDraft / deleteGradeDraft v3-P2 新增:成绩录入草稿 CRUDupsert + 24 小时过期)/ getExamOptionsForGrades / getSchoolWideGradeSummary v3-P2 新增:考试选项查询 + 全校各年级成绩汇总,管理员视图按年级聚合平均分/及格率/优秀率/学生数/班级数,加权平均计算全校汇总)/ batchCreateGradeRecordsByExam 2026-06-24 新增, 2026-06-25 更新返回类型:按试卷批量录入成绩,单事务写入 grade_records + grade_record_answers + 投影到 exam_submissions(status=graded) + submission_answers(answerContent=null),使错题集/成绩分析等下游模块无需改造即可读取教师录入的成绩,返回 BatchGradeRecordResult[]{ gradeRecordId, studentId, submissionId })供调用方触发错题采集等后置钩子)
  • Types v3-P2 新增, v4-P2-7 新增年级分布类型, 2026-06-24 新增按试卷录入类型):SchoolWideGradeSummaryItem / SchoolWideGradeSummary / GradeDraftData(草稿数据接口)/ GradeDistributionByGradeResult v4-P2-7 新增:年级维度成绩分布结果,含 overall 整体分布 + stats 统计 + byClass 按班级拆分数组)/ GradeDistributionByGradeClassItem v4-P2-7 新增按班级拆分的分布项classId/className/distribution/stats/ GradeRecordAnswer 2026-06-24 新增:成绩记录-题目得分明细,对应 grade_record_answers 表)/ BatchGradeEntryByExamQuestion 2026-06-24 新增:按试卷录入时单个题目得分 { questionId, score }/ BatchGradeEntryByExamItem 2026-06-24 新增:按试卷录入时单个学生所有题目得分 { studentId, answers }
  • Lib P1-2 新增, P3 更新签名, P3-26 拆分, v4-P2-6 新增 scope-checktoNumber / normalize(位于 lib/grade-utils.tsbuildScopeClassFilter(scope, currentUserId?)P3-26 从 grade-utils.ts 迁移至 lib/scope-filter.tsP3 修复:class_members scope 内置 studentId 过滤,需传入 currentUserId 参数);assertClassInScope(scope: DataScope, classId: string): string | null v4-P2-6 从 actions.ts 迁移至 lib/scope-check.ts:校验 classId 是否在 scope 允许范围内,供 actions.ts 与 actions-analytics.ts 复用。迁移原因actions.ts 是 "use server" 文件要求所有 export 为 async而 assertClassInScope 是同步函数)
  • Stats-service P1-1 新增):computeGradeStats / computeAverageScore / buildGradeTrendPoints / computeTrendAverage / computeClassComparisonStats / computeSubjectComparisonStats / computeGradeDistribution / buildRankingTrendPoints(从 3 个 data-access 文件抽取的纯函数,使数据层专注 DB I/O统计逻辑可独立测试
  • Export v4-P1-12 新增):exportGradeRecordsToExcel / exportClassGradeReportToExcel / exportStudentGradeRecordsToExcelv4-P1-12 新增:导出单个学生成绩单家长视角,仅含成绩明细 + 个人统计不含班级数据scope 为 children 自动按 studentId 过滤)/ formatDateForFile(已迁移至 shared/lib/utils
  • Components P1-5 新增):WidgetBoundaryError Boundary + Suspense + Skeleton 组合,含 a11y 属性)/ SchoolWideSummaryCard v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格)/ ScoreCell v4-P1-7 新增:成绩单元格组件,根据得分率着色——红<60%/黄60-84%/绿≥85%,使用语义化 Tailwind 类名避免动态拼接fullScore<=0 时不着色)

依赖关系

  • 依赖:shared/*@/authclasses P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId/getClassGradeIdsByClassIdsschool P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptionsusers P1-1 已修复:通过 users data-access.getUserNamesByIdsexams 2026-06-24 新增:通过 exams data-access.getExamsForGradeEntry/getExamForGradeEntry 获取试卷列表和详情供按试卷录入成绩使用)、error-book 2026-06-25 新增:通过 error-book data-access.collectFromExamSubmission 在成绩录入后自动采集考试错题)、diagnostic(通过 diagnostic data-access.updateMasteryFromExamScore 更新掌握度)
  • 被依赖:parent(通过 data-access合理dashboard

已知问题

  • P1-1 已修复:多处直查 classes/classEnrollments/subjects/users 改为调用对应模块 data-access 函数classes/school/users
  • P1-1 已修复:统计计算业务逻辑混入 data-accessgetClassGradeStats / getGradeDistribution 抽取为纯函数到 stats-service.ts,数据层专注 DB I/O
  • P1-2 已修复:toNumber/normalize/buildScopeClassFilter 在 3 个 data-access 文件中重复定义 抽取到 lib/grade-utils.ts 统一维护
  • P1-3 已修复:12 个查询/分析 Action 缺少 Zod 校验 新增 12 个查询 schemaDeleteGradeRecordSchema/GetGradeRecordByIdSchema/GradeQuerySchema/ClassGradeStatsQuerySchema/StudentGradeSummaryQuerySchema/ClassRankingQuerySchema/ExportGradesSchema/GradeTrendQuerySchema/ClassComparisonQuerySchema/SubjectComparisonQuerySchema/GradeDistributionQuerySchema/RankingTrendQuerySchema所有 Action 使用 safeParse 校验
  • P1-4 已修复:batch-grade-entry.tsx/grade-record-form.tsx/grade-distribution-chart.tsx 中存在 as 断言 改用类型守卫函数isGradeType/isSemester/isDistributionTooltipPayload
  • P1-5 已修复:teacher/grades 与 teacher/diagnostic 路由缺少 loading.tsx/error.tsx 已为 7 个路由补齐 loading.tsx + error.tsx并新增 WidgetBoundary 通用组件
  • P2-1 已修复:图表/表格/列表缺少 a11y ARIA 属性 为 4 个成绩图表添加 role="img" + aria-label2 个表格添加 <caption>3 个图标按钮添加 aria-label
  • P2-2 已修复:diagnostic 组件中存在 Tailwind 任意值 改用标准 Tailwind 类
  • P2-4 已修复:~~buildScopeClassFiltergrade_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.tsscoreMap.get(r.studentId)! 非空断言清理为安全守卫(if (!subjMap) continue
  • P3 修复2026-06-22schema.ts 缺少输入边界约束 score 字段添加 .max(1000)records 数组添加 .max(500),查询 schema 补全 studentId/semester/examId 字段
  • P3 修复2026-06-22buildScopeClassFilterclass_members scope 未应用 studentId 过滤 新增 currentUserId 参数,class_members scope 内置 eq(gradeRecords.studentId, currentUserId) 过滤
  • P3 修复2026-06-22getGradeRecords 内存分页(全量查询后切片) 改为 DB 层分页limit/offset并行查询总数与当前页数据返回 PaginatedGradeRecords { records, total }
  • P3 修复2026-06-22batchCreateGradeRecords 非原子操作 包裹 db.transaction 确保原子性
  • P3 修复2026-06-22updateGradeRecord/deleteGradeRecord 未检查记录是否存在 新增存在性检查,不存在时抛 NotFoundError("成绩记录")
  • P3 修复2026-06-22getClassGradeStats/getStudentGradeSummary/getClassRanking/getClassStudentsForEntry/getClassGradeStatsWithMeta 缺少 scope 过滤 所有函数新增 scope?/currentUserId? 参数,应用 buildScopeClassFiltergetStudentGradeSummary/getClassStudentsForEntryclass_taught scope 校验学生/班级归属
  • P3 修复2026-06-22getClassRanking 未处理并列排名 相同平均分获得相同名次,下一名次跳过占位
  • P3 修复2026-06-22getClassComparison/getRankingTrend 缺少 scope 过滤 应用 buildScopeClassFiltergetRankingTrendclass_taught scope 校验
  • P3 修复2026-06-22actions.ts/actions-analytics.ts catch 块直接返回 e.message 改用 handleActionError(e) 统一错误处理;batchCreateGradeRecordsAction 使用 safeJsonParse 解析 JSON
  • P3 修复2026-06-22actions-analytics.ts 缺少 class scope 校验 getGradeTrendAction/getSubjectComparisonAction/getGradeDistributionAction 新增 assertClassInScope 校验
  • P3 修复2026-06-22组件直接 try/catch 调用 Server Action 改用 safeActionCall 包装器onError/onFinally 回调);batch-grade-entry.tsx localStorage 访问包裹 typeof window !== "undefined" 检查;区分"未录入"与"录入 0"
  • P3 修复2026-06-22grade-trend-card.tsx 排序未处理 NaN 日期、除法未检查 fullScore > 0 新增日期有效性检查与 fullScore > 0 守卫
  • P3 修复2026-06-22页面文件缺少 scope 传递 teacher/grades/page.tsx 使用 DB 层分页analytics/page.tsx 添加 EmptyStateentry/page.tsx 与 stats/page.tsx 过滤 class_taught scope 班级student/grades/page.tsx 与 parent/grades/page.tsx 传递 ctx.dataScope 到 data-access
  • v3-P2 改进2026-06-23新增 grade_drafts成绩录入草稿userId+classId+subjectId+type 唯一键content JSON 存储 scores24 小时过期schema.ts 第 1444-1469 行);新增 saveGradeDraft/getGradeDraft/deleteGradeDraft data-access 函数 + 对应 3 个 Server Actions支持成绩批量录入草稿服务端持久化
  • v3-P2 改进2026-06-23新增 getExamOptionsForGrades data-access 函数,获取指定班级/科目下有成绩记录的考试列表;AnalyticsFilters 组件新增 exams/currentExamId/currentSemester props添加学期和考试筛选 ChipNavteacher/grades/analytics/page.tsx 新增 semester/examId 搜索参数解析
  • v3-P2 改进2026-06-23getGradeTrend/getGradeDistribution/getSubjectComparison/getClassComparison 均新增 semesterexamId 可选参数,支持按学期和考试筛选分析
  • v3-P2 改进2026-06-23新增 getSchoolWideGradeSummary data-access 函数 + SchoolWideSummaryCard 组件 + SchoolWideGradeSummary/SchoolWideGradeSummaryItem 类型,管理员全校成绩汇总视图(按年级聚合平均分/及格率/优秀率/学生数/班级数加权平均计算全校汇总admin/school/grades/insights/page.tsx 顶部新增 SchoolWideSummaryCard
  • v3-P2 改进2026-06-23parent/grades/page.tsx 为每个子女并行查询 getClassAverageTrend,渲染 GradeTrendCard
  • v3-P3-4 改进2026-06-23GradeTrendCard 新增日期范围选择器(全部/近7天/近30天/近90天通过 nuqs trendRange URL 参数持久化useEffect 中计算截止时间戳避免渲染阶段调用 Date.now()
  • v4-P3-4 改进2026-06-23GradeDistributionChart 色盲友好双重编码——每个分数段使用不同 SVG pattern条纹/点状/交叉线/反向条纹/网格)+ 颜色,SimpleBarChart 新增 defs prop 支持自定义 SVG 图案
  • P3 修复2026-06-23lib/grade-utils.ts 72 行超 40 行工具函数上限 P3-26 将 buildScopeClassFilter 迁移至 lib/scope-filter.tsgrade-utils.ts 仅保留 toNumber/normalize20 行)
  • P3 修复2026-06-23stats-service.ts createDefaultBuckets 不必要导出 P3-10 移除 export 关键字,改为内部函数
  • P3 修复2026-06-23stats-service.ts buildGradeTrendPoints 使用 as 断言 P3-24 新增 isGradeTrendType 类型守卫函数替代 as 断言
  • P3 修复2026-06-23export.ts 局部 avg 函数与 stats-service.computeAverageScore 重复 P3-6 删除局部 avg复用 computeAverageScore
  • P3 修复2026-06-23export.ts TYPE_LABELS 硬编码中文 P3-7 改用 next-intl getTranslations新增 export.sheets/columns/metrics i18n 键zh-CN/en 同步)
  • v4-P1-6 改进2026-06-23createGradeRecordAction / batchCreateGradeRecordsAction 成绩录入后通知学生和家长(调用 notifyGradeEntered,内部使用 parent/data-access.getParentIdsByStudentIds 批量查询家长 ID通知失败不阻断成绩录入
  • v4-P1-7 改进2026-06-23新增 ScoreCell 组件components/score-cell.tsx根据得分率着色红<60%/黄60-84%/绿≥85%),使用语义化 Tailwind 类名避免动态拼接
  • v4-P1-10 改进2026-06-23grade-record-list.tsx 使用 ScoreCell 替代纯文本分数展示 + 表格 overflow-x-auto 水平滚动
  • v4-P1-12 改进2026-06-23exportGradesAction 新增可选 studentId 参数,支持按学生导出(家长视角);新增 exportStudentGradeRecordsToExcel 导出函数(仅含成绩明细 + 个人统计不含班级数据parent/grades/page.tsx 传 studentId 到 ParentExportButtonParentExportButton 接入 exportGradesAction
  • v3-P3-1 改进2026-06-23batch-grade-entry.tsx 新增"下载模板"按钮,客户端生成 CSV 模板(含学生姓名/分数/备注列头 + BOM 支持 Excel UTF-8教师可下载填好后粘贴到录入表格
  • v3-P3-2 改进2026-06-23grade-record-list.tsx 新增多选复选框(全选/单选)+ 批量删除工具栏 + 批量删除确认对话框;新增 bulkDeleteGradeRecords data-access 函数(使用 inArray 一次性删除避免 N+1+ bulkDeleteGradeRecordsAction Server Action限制单次最多 500 条)
  • v4-P3-2 改进2026-06-23batch-grade-entry.tsx 顶部新增可折叠新手引导提示框4 步使用说明),使用 localStorage 记住用户关闭状态避免重复显示
  • v4-P2-6 修复2026-06-23assertClassInScope 是同步函数但位于 "use server" 文件 actions.ts 中 Next.js 要求 "use server" 文件中所有 export 必须为 async同步 export 会导致构建错误。修复:将 assertClassInScope 迁移至独立文件 lib/scope-check.ts(含 import "server-only"actions.ts 与 actions-analytics.ts 均从 ./lib/scope-check 导入
  • 2026-06-24 重新设计:批量录入从"只录总分"改为"按试卷录入每题得分"。新增 grade_record_answers 表存储每题得分明细(迁移 0010_grade_record_answers.sqlbatchCreateGradeRecordsByExam data-access 单事务写入 grade_records + grade_record_answers + 投影到 exam_submissions(status=graded) + submission_answers(answerContent=null),使错题集/成绩分析等下游模块无需改造即可读取教师录入的成绩(学生只看到对错,不知道学生答案)。BatchGradeEntryByExam 组件完全重写为 Excel 式表格(行=学生,列=题目,末列=总分自动计算支持多行多列粘贴、Enter 跳下一行、Tab 跳下一格、分数校验、撤销机制sessionStorage 5 分钟有效)。新增对 exams 模块的依赖getExamsForGradeEntry/getExamForGradeEntry。i18n 新增 batchByExam 章节zh-CN/en ~35 键)
  • 审计修复2026-06-22school-wide-summary-card.tsx 整个组件无 i18n13 处硬编码中文) 改为 async 组件使用 getTranslations("grades"),新增 schoolWide 翻译键节zh-CN/en 同步 14 键)
  • 审计修复2026-06-225 个 teacher/grades 页面文件大量硬编码中文/英文 page.tsx/analytics/page.tsx/entry/page.tsx/stats/page.tsx/error.tsx 全部使用 getTranslations("grades")useTranslations("grades"),新增 page 翻译键节zh-CN/en 同步 ~30 键)
  • 审计修复2026-06-22actions.ts 15+ 处硬编码中文错误消息/通知内容 notifyGradeEntered 使用 getTranslations("grades") 获取通知标题和内容;batchCreateGradeRecordsByExamAction 错误消息改用 t("action.*") 翻译键;exportGradesAction 文件名和错误消息改用翻译键,新增 action 翻译键节zh-CN/en 同步 ~20 键)
  • 审计修复2026-06-22data-access.ts 2 处硬编码中文 throw Error 改为英文错误消息data-access 层为防御性检查action 层已先行校验)
  • 审计修复2026-06-22data-access.ts:782 as 断言无类型守卫 新增 isGradeDraftData 类型守卫函数lib/type-guards.ts从 unknown 安全验证 DB JSON 结构
  • 审计修复2026-06-22data-access-analytics.ts:360-362 非空断言 ! 改用类型谓词 filter((r): r is typeof r & { stats: NonNullable<typeof r.stats> } => r.stats !== null) 消除非空断言
  • 审计修复2026-06-22export.ts 内存过滤而非 DB 层过滤 exportStudentGradeRecordsToExcel 传入 studentIdgetGradeRecords DB 层过滤
  • 审计修复2026-06-22teacher/grades/page.tsx 内联类型守卫 + as 断言 改用 lib/type-guards.tsisGradeType/isSemester 函数
  • 审计修复2026-06-2211 个组件函数缺少返回值类型标注 全部添加 : JSX.Element: Promise<JSX.Element> 返回值类型
  • 审计修复2026-06-2417 处 Tailwind 任意值(w-[140px]/h-[280px]/min-w-[80px] 等) 图表组件移除匹配默认值的 props 或改用标准类(h-60/w-52/w-60),过滤器改用 w-36/w-32,表格改用 min-w-32/min-w-20
  • 审计修复2026-06-24batch-grade-entry.tsx 2 处 window.confirm() 改用声明式 AlertDialog + pendingSwitch 状态管理,新增 3 个 i18n 键confirmTitle/confirmAction/cancelAction
  • 审计修复2026-06-24class-grade-report.tsx 缺少返回值类型标注 补充 : Promise<JSX.Element> 返回值类型
  • 审计修复2026-06-25 P03 个 error.tsx 硬编码中文parent/grades、admin/school/grades、admin/school/grades/insights 改为 useTranslations("grades") + 新增 error.* 翻译键zh-CN/en 同步);7 个 loading/error.tsx 函数缺返回值类型 补全 : JSX.Element 返回值类型标注
  • 审计修复2026-06-25 P1-1actions.ts 超 800 行856 行) 拆分草稿相关 3 个 Action 到 actions-draft.ts106 行);通知逻辑 notifyGradeEntered 抽取至 lib/notify.ts60 行actions.ts 降至 646 行
  • 审计修复2026-06-25 P1-2data-access.ts 超 800 行816 行) 拆分草稿 CRUD 到 data-access-drafts.ts107 行);拆分按试卷录入到 data-access-exam-entry.ts167 行data-access.ts 降至 536 行
  • 审计修复2026-06-25 P1-3actions.ts 多处英文错误消息未 i18n 新增 action.error.* 翻译键,所有错误消息改用 t("action.error.*")scope 校验错误改用 t("action.scopeError.{code}")
  • 审计修复2026-06-25 P1-4lib/scope-check.ts 返回硬编码英文消息 改为返回 ScopeCheckErrorCode 枚举("class_taught" | "class_members" | "denied"),由 Action 层通过 i18n 键 grades.action.scopeError.{code} 翻译
  • 审计修复2026-06-25 P1-5actions-analytics.ts scope 校验消息未 i18n 所有 6 个 Action 使用 getTranslations("grades") 获取翻译;新增 ownRankingTrendOnly/childrenRankingTrendOnly 翻译键
  • 审计修复2026-06-25 P1-6/P1-7batch-grade-entry.tsx 超 500 行604 行)+ as 断言 拆分为 BatchGradeEntryTable + BatchGradeEntryStats + BatchGradeEntryDialog 子组件 + useBatchGradeEntryUndo Hook主组件降至 422 行;撤销数据使用 isUndoData 类型守卫in 运算符)替代 as { ids: string[]; timestamp: number } 断言
  • 审计修复2026-06-25 P1-8parent/grades/page.tsx 第 63 行 as 断言 改用 for 循环 + 类型守卫 if (summary === null) continue 替代 .filter().map() + as NonNullable<...> 断言
  • 审计修复2026-06-25 P1-9teacher/grades/page.tsx 列表与统计未 WidgetBoundary 包裹 新增 <WidgetBoundary title={t("title.list")}> 包裹 <GradeRecordList>,避免单数据区块失败导致整页崩溃
  • 审计修复2026-06-25 P2-2grade-filters.tsx/grade-query-filters.tsx/export-button.tsx 函数缺返回值类型 补全 : JSX.Element / : Promise<void> 返回值类型标注
  • 审计修复2026-06-25 P2-3updateGradeRecord 使用 Record<string, unknown> 累积更新字段 改用 Partial<typeof gradeRecords.$inferInsert> 类型安全更新
  • 审计修复2026-06-25 P2-4getStudentGradeSummary 内部调用 getClassRanking 形成 data-access 层内部耦合 新增 getStudentRankInClass 独立函数(两步法:查学生平均分 → 查所有学生平均分 → 计算严格高于的不同平均分数),避免获取整个班级排名(含姓名查询)只为查单个学生排名
  • 审计修复2026-06-25 P2-9lib/type-guards.ts 第 8/12 行 as 断言 isGradeType/isSemester 改用 GRADE_TYPES.some((t) => t === v) 替代 (GRADE_TYPES as readonly string[]).includes(v)isGradeDraftData 改用 in 运算符替代 as Record<string, unknown> 断言

文件清单

文件 行数 职责
actions.ts 646 17 个 Server Action含 Zod 校验,含 v2-P1-5 安全修复assertClassInScope + 行级 scope 校验P3 修复handleActionError + safeJsonParse + scope 传递 + DB 层分页v4-P1-12exportGradesAction 新增 studentId 参数v3-P3-2 新增bulkDeleteGradeRecordsAction 批量删除v4-P2-6assertClassInScope 迁移至 lib/scope-check.ts2026-06-24 新增batchCreateGradeRecordsByExamAction 按试卷录入每题得分;P1-1 重构2026-06-25:草稿相关 3 个 ActionsaveGradeDraftAction/getGradeDraftAction/deleteGradeDraftAction迁移至 actions-draft.ts通知逻辑 notifyGradeEntered 抽取至 lib/notify.ts错误消息全部 i18nt("action.*")scope 校验错误改用 ScopeCheckErrorCode + t("action.scopeError.{code}") 翻译)
actions-draft.ts 106 P1-1 新增2026-06-25:成绩录入草稿 3 个 Server ActionsaveGradeDraftAction/getGradeDraftAction/deleteGradeDraftAction从 actions.ts 拆分以使 actions.ts 降至 800 行以内。权限分别为 GRADE_RECORD_MANAGE/GRADE_RECORD_READ/GRADE_RECORD_MANAGE调用 data-access-drafts错误消息使用 i18n 翻译键
actions-lock.ts 197 P3-6 新增2026-06-26:协同录入锁 4 个 Server ActiongetDraftLockStatusAction/acquireDraftLockAction/renewDraftLockAction/releaseDraftLockAction。权限 GRADE_RECORD_READ查询/GRADE_RECORD_MANAGE获取/续期/释放),调用 data-access-drafts 锁函数 + assertClassInScope 校验 + getUserNamesByIds 解析锁持有者姓名(避免跨模块 DB join。releaseDraftLockAction 失败不阻断主流程
actions-import.ts 297 P3-10 新增2026-06-26Excel 批量导入成绩 3 个 Server ActiondownloadGradeImportTemplateAction/importGradesFromExcelAction/previewGradeImportAction。权限 GRADE_RECORD_MANAGE + assertClassInScope调用 import-export.ts 解析 Excel + 匹配学生姓名 + 批量入库。支持模板下载(含班级学生姓名)、预校验、正式导入
actions-appeal.ts 197 P3-7 新增2026-06-26:成绩申诉 5 个 Server ActioncreateGradeAppealAction/withdrawGradeAppealAction/getStudentAppealsAction/reviewGradeAppealAction/getPendingAppealsForReviewAction。学生权限 GRADE_RECORD_READ提交/撤回/查看自己申诉),教师权限 GRADE_RECORD_MANAGE复核。审核通过且提供 adjustedScore 时同步更新 grade_records.score
data-access-appeals.ts 261 P3-7 新增2026-06-26成绩申诉数据访问层。7 个函数hasPendingAppeal/createGradeAppeal/getStudentAppeals/getPendingAppealsForReview/getAppealById/reviewGradeAppeal/withdrawGradeAppeal+ GradeAppeal/GradeAppealWithRecord 接口。申诉状态机 pending→approved/rejected/withdrawnoriginalScore 快照留痕reviewGradeAppeal 同步更新成绩记录
import-export.ts 278 P3-10 新增2026-06-26Excel 导入成绩逻辑。generateGradeImportTemplate 生成含班级学生姓名的模板parseGradeExcelBuffer 解析 Excel支持中英双语表头parseGradeImportData 匹配学生姓名到 ID处理重名buildBatchInputFromImport 转换为批量录入输入。依赖 @/shared/lib/excel
actions-analytics.ts 219 6 个分析 Action含 Zod 校验P3 修复handleActionError + assertClassInScope 校验v4-P2-6assertClassInScope 改从 ./lib/scope-check 导入;P1-5 重构2026-06-25:所有 Action 使用 getTranslations("grades") 获取翻译,错误消息改用 t("action.invalidQuery")/t("action.scopeError.{code}");学生/家长排名趋势访问校验使用 t("action.ownRankingTrendOnly")/t("action.childrenRankingTrendOnly")
data-access.ts 536 成绩 CRUD + 统计(含 v2-P2-9 修复recorderName 批量查询P3 修复PaginatedGradeRecords 接口 + DB 层分页 + 事务 + 存在性检查 + scope 过滤 + 并列排名v3-P3-2 新增bulkDeleteGradeRecords 使用 inArray 批量删除;P1-2 重构2026-06-25:草稿 CRUDsaveGradeDraft/getGradeDraft/deleteGradeDraft迁移至 data-access-drafts.ts按试卷录入batchCreateGradeRecordsByExam迁移至 data-access-exam-entry.tsP2-3updateGradeRecord 类型强化为 Partial<typeof gradeRecords.$inferInsert>P2-4:新增 getStudentRankInClass 解耦 getStudentGradeSummary 与 getClassRanking
data-access-drafts.ts 405 P1-2 新增2026-06-25:成绩录入草稿 CRUDsaveGradeDraft/getGradeDraft/deleteGradeDraft+ GradeDraftData 接口,从 data-access.ts 拆分。使用 cache() memoize getGradeDraftisGradeDraftData 类型守卫安全解析 DB JSON 列24 小时过期。P3-6 扩展2026-06-26:新增协同录入锁机制 5 个函数getDraftLockStatus/acquireDraftLock/renewDraftLock/releaseDraftLock/cleanupExpiredDraftLocks+ DraftLockStatus/AcquireLockResult 接口LOCK_TTL_MS=5 分钟,悲观锁语义+乐观 TTL+令牌续期,锁以 grade_drafts 行为载体lockedBy/lockedAt/lockToken 列)
data-access-exam-entry.ts 167 P1-2 新增2026-06-25按试卷批量录入成绩batchCreateGradeRecordsByExam+ BatchGradeRecordResult 接口,从 data-access.ts 拆分。单事务写入 grade_records + grade_record_answers + 投影到 exam_submissions(status=graded) + submission_answers(answerContent=null),返回 BatchGradeRecordResult[]{gradeRecordId, studentId, submissionId})供调用方触发错题采集等后置钩子
data-access-analytics.ts 417 趋势/对比分析P3 修复getClassComparison 应用 buildScopeClassFilterv3-P2 新增getExamOptionsForGrades/getSchoolWideGradeSummarygetGradeTrend/getClassComparison/getSubjectComparison/getGradeDistribution 新增 semester/examId 可选参数)
data-access-ranking.ts 166 排名查询P3 修复getRankingTrend 接受 scope 参数 + class_taught 校验)
stats-service.ts 285 统计计算纯函数P1-1 新增8 个纯函数 + 2 个常量 + 2 个接口P3-10createDefaultBuckets 改为内部函数P3-24buildGradeTrendPoints 使用 isGradeTrendType 类型守卫替代 as 断言)
export.ts 290+ Excel 导出v2-P1-5 修复:传递 currentUserId 到 data-accessP3 修复:适配 PaginatedGradeRecords 结构 + 传递 scopeP3-6复用 stats-service.computeAverageScore 替代局部 avgP3-7硬编码中文改用 next-intl getTranslationsv4-P1-12 新增exportStudentGradeRecordsToExcel 家长视角单学生导出)
schema.ts 237 Zod 校验(含 12 个查询 schemaP3 修复score .max(1000) + records .max(500) + 补全查询字段v3-P2 新增grade_drafts 表定义2026-06-24 新增BatchGradeEntryByExamSchemaP3-10 新增2026-06-26ExcelImportRowSchema/ExcelImportGradesSchema + 导出 ExcelImportRowInput/ExcelImportGradesInput 类型;P3-7 新增2026-06-26GradeAppealStatusEnum/CreateGradeAppealSchema/ReviewGradeAppealSchema + 导出 CreateGradeAppealInput/ReviewGradeAppealInput 类型)
lib/grade-utils.ts 20 公共工具函数toNumber/normalizeP3-26buildScopeClassFilter 迁移至 scope-filter.ts
lib/scope-filter.ts 56 DB 行级权限过滤buildScopeClassFilterP3-26 从 grade-utils.ts 迁移v2-P2-2 修复:改用 classes data-access 子查询P3 修复:新增 currentUserId 参数)
lib/scope-check.ts 34 v4-P2-6 新增:班级 scope 校验工具assertClassInScope 同步函数,从 actions.ts 迁移至此独立文件以避开 "use server" 文件要求 export 必须为 async 的限制;含 import "server-only"P1-4 重构2026-06-25:返回类型从 string | null 改为 ScopeCheckErrorCode | null"class_taught" | "class_members" | "denied"),消除硬编码英文字符串,调用方通过 i18n 键 grades.action.scopeError.{code} 翻译)
lib/notify.ts 60 P1-1 新增2026-06-25成绩录入通知工具notifyGradeEntered从 actions.ts 抽取。使用 next-intl getTranslations 获取通知标题和内容i18nP2-7 改进:使用 Promise.allSettled 并行化通知写入,避免批量录入 30+ 学生时串行等待;依赖 notifications/data-access.createNotification + parent/data-access.getParentIdsByStudentIds + school/data-access.getSubjectNameById
lib/report-card.ts 380 P3-1 新增2026-06-26成绩报告卡数据访问层。getReportCardDatacached聚合学生成绩+班级信息+班主任+学年+科目排名+总体统计computeSubjectRank 计算单科排名。三层 scope 校验owned学生自己/children家长子女/class_taught教师班级学生。静态导入 data-accessgetStudentRankInClass/getClassNamesByIds+ stats-servicecomputeAverageScore
lib/type-guards.ts 25 类型守卫纯函数isGradeType/isSemester/isGradeDraftDataP2-9 修复2026-06-25isGradeType/isSemester 使用 GRADE_TYPES.some((t) => t === v) 替代 (GRADE_TYPES as readonly string[]).includes(v),消除 as 断言isGradeDraftData 使用 in 运算符替代 as Record<string, unknown> 断言)
types.ts 200+ 类型定义v3-P2 新增SchoolWideGradeSummaryItem/SchoolWideGradeSummary2026-06-24 新增GradeRecordAnswer/BatchGradeEntryByExamQuestion/BatchGradeEntryByExamItem
components/widget-boundary.tsx 136 Widget 边界组件P1-5 新增v2-P1-1 已在 3 个页面应用;P1-92026-06-25teacher/grades/page.tsx 列表与统计卡片新增 WidgetBoundary 包裹,避免单数据区块失败导致整页崩溃)
components/school-wide-summary-card.tsx - v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格)
components/score-cell.tsx 41 v4-P1-7 新增:成绩单元格组件,根据得分率着色(红<60%/黄60-84%/绿≥85%),使用语义化 Tailwind 类名
components/grade-trend-card.tsx 69 趋势卡片v2-P2-9 修复a11yv2-P1-4i18nP3 修复NaN 日期检查 + fullScore > 0 守卫)
components/grade-record-list.tsx 200+ 成绩记录列表v2-P1-4i18nP3 修复safeActionCall 包装删除操作v4-P1-7使用 ScoreCellv4-P1-10overflow-x-autov3-P3-2 新增:多选复选框 + 全选 + 批量删除工具栏 + 批量删除确认对话框,接入 bulkDeleteGradeRecordsAction
components/grade-distribution-chart.tsx 100 分数分布图v2-P1-4i18n
components/subject-comparison-chart.tsx 62 科目对比图v2-P1-4i18n
components/class-comparison-chart.tsx 194 班级对比图v2-P1-4i18nv3-P3-5 新增:显著性分析区域,基于极差和样本量的经验规则判断班级间差异,含可折叠详细分析)
components/grade-trend-chart.tsx 59 趋势图v2-P1-4i18n
components/grade-record-form.tsx 177 录入表单v2-P2-7 修复Label htmlForv2-P1-4i18nP3 修复safeActionCall 包装提交)
components/batch-grade-entry.tsx 422 2026-06-24 完全重写:导出名改为 BatchGradeEntryByExam按试卷录入每题得分。Excel 式表格(行=学生,列=题目,末列=总分自动计算)。交互:试卷选择器(按 scope 过滤)→ 班级选择器(按试卷 gradeId 过滤);多行多列 Excel 粘贴Tab 分隔Enter 跳下一行同一列Tab 跳下一格;实时统计(已录入/总数/均分/最高/最低分数校验超过题目满分标红撤销机制sessionStorage 5 分钟有效)。P1-6/P1-7 重构2026-06-25:拆分为 3 个子组件 + 1 个 HookBatchGradeEntryTable + BatchGradeEntryStats + BatchGradeEntryDialog + useBatchGradeEntryUndo主组件从 604 行降至 422 行;撤销数据使用 isUndoData 类型守卫替代 as { ids: string[]; timestamp: number } 断言window.confirm() 已替换为声明式 AlertDialog。Props: exams/classes/classGradeMap/exam/students/defaultExamId?/defaultClassId?(原 v2-P2-7 Label htmlForv2-P1-4 i18nP3 safeActionCall + localStorage 安全检查v3-P2 服务端草稿v3-P3-1 下载 CSV 模板v4-P3-2 新手引导均已替换)
components/batch-grade-entry-table.tsx 115 P1-6 新增2026-06-25:批量录入 Excel 式表格子组件(行=学生,列=题目,末列=总分自动计算)。支持多行多列 Excel 粘贴、Enter 跳下一行、Tab 跳下一格、分数校验(超过题目满分标红)。接受 inputRefs 供父组件管理输入焦点
components/batch-grade-entry-stats.tsx 68 P1-6 新增2026-06-25:批量录入统计栏子组件,显示已录入/总数/均分/最高/最低/无效分数徽章,含学生搜索框过滤。导出 BatchGradeEntryStatsData 接口
components/batch-grade-entry-dialog.tsx 58 P1-6 新增2026-06-25:批量录入切换确认对话框子组件(替代 window.confirm())。导出 PendingSwitch 联合类型 { kind: "exam"; examId; message } | { kind: "class"; classId; message } | null
hooks/use-batch-grade-entry-undo.ts 86 P1-6/P1-7 新增2026-06-25:批量录入撤销 Hook。封装撤销令牌的序列化、sessionStorage 读写、5 分钟 TTL 过期检查、撤销 Action 调用。包含 isUndoData 类型守卫函数(替代 as 断言),使用 in 运算符做安全属性访问
components/grade-filters.tsx 76 过滤器v2-P1-4i18nP2-22026-06-25:补全 : JSX.Element 返回值类型)
components/student-grade-summary.tsx 107 学生成绩摘要v2-P1-4i18n
components/export-button.tsx 79 导出按钮v2-P1-4i18nP3 修复safeActionCall 包装导出操作;P2-22026-06-25handleExport 补全 : Promise 返回值类型)
components/analytics-filters.tsx 86+ 分析过滤器v2-P1-4i18nv3-P2 新增exams/currentExamId/currentSemester props添加学期和考试筛选 ChipNav
components/stats-class-selector.tsx 40 统计班级选择器v2-P1-4i18n
components/grade-stats-card.tsx 74 统计卡片v2-P1-4i18n
components/class-grade-report.tsx 90 班级成绩报告v2-P1-4i18n
components/grade-query-filters.tsx 96 查询过滤器v2-P2-7 修复Label htmlForv2-P1-4i18nP2-22026-06-25:补全 : JSX.Element 返回值类型)
components/report-card-view.tsx 323 P3-1 新增2026-06-26A4 打印格式成绩报告卡服务端组件。含表头(学校名+标题+学期、学生信息网格、科目成绩表格rowSpan 合并科目名/排名列)、汇总统计(均分/班排名/及格率/优秀率)、教师评语+签字区。使用 globals.css @media print 规则控制打印布局
components/report-card-print-button.tsx 106 P3-1 新增2026-06-26:跳转至报告卡页面的客户端按钮,支持学年/学期筛选
components/report-card-print-action.tsx 48 P3-1 新增2026-06-26:调用 window.print() 的客户端按钮组件,含 loading 状态和错误 toast
components/excel-import-dialog.tsx 518 P3-10 新增2026-06-26Excel 批量导入成绩对话框3 步流程:下载模板→填写参数→上传文件)。展示导入结果(成功/失败计数、未匹配学生、无效行表格前 20 条)。使用 safeActionCall 包装 Action 调用
components/draft-lock-banner.tsx 75 P3-6 新增2026-06-26协同录入锁状态提示条。三种状态acquiringloading/conflict锁被他人持有警告+重试按钮)/acquired不渲染。使用 Alert 组件
components/knowledge-point-mastery-chart.tsx 232 P3-3 新增2026-06-26:知识点掌握度柱状图组件,集成 diagnostic 模块的 getClassMasterySummary 数据。使用 SimpleBarChart + masteryBarColor 函数着色 + isMasteryTooltipPayload 类型守卫
hooks/use-draft-lock.ts 184 P3-6 新增2026-06-26:协同录入锁管理 Hook。职责进入页面自动获取锁、3 分钟心跳续期TTL 5 分钟)、组件卸载/提交成功释放锁、锁冲突状态暴露。使用 ref 保存最新 token 避免闭包捕获旧值,防重复 acquire/release

架构变更2026-07-05Phase 4.5 — grade-record-list.tsx 编辑保存改用 useOptimistic + useTransition

  • grade-record-list.tsx:编辑保存 isSaving useState → useOptimistic<GradeRecordListItem[], GradeRecordListItem> + useTransitionhandleEditSave 构造 optimisticRecordstartSaveTransitionaddOptimisticRecord + await updateGradeRecordAction + router.refresh();两处 records.map 改为 optimisticRecords.map(桌面表格 + 移动端卡片视图)即时显示乐观记录
  • 关键决策:成功后调用 router.refresh() 同步数据源,让 useOptimistic 回滚到最新服务端值;不为删除操作使用 useOptimistic因为删除需要服务端确认外键约束、关联记录检查等

2.7 classes班级模块— 耦合最严重

职责:班级 CRUD + 学生/教师管理 + 邀请码注册。

导出函数

  • ActionscreateTeacherClassAction / updateTeacherClassAction / deleteTeacherClassAction / createAdminClassAction / updateAdminClassAction / deleteAdminClassAction / createGradeClassAction / updateGradeClassAction / deleteGradeClassAction
  • Data-accessgetAdminClasses / getTeacherClasses / getGradeManagedClasses / getStudentClasses / getClassDetails / getClassStudents / getClassSchedule / getClassHomeworkInsights / getGradeHomeworkInsights / getStudentsSubjectScores / verifyTeacherOwnsClass / getTeacherIdsByClassIds(获取多个班级的所有教师 ID班主任 + 任课教师,跨模块接口,供 messaging 模块调用)/ getClassIdByScheduleId(scheduleId: string): Promise<string | null> 2026-06-26 审计修复新增:根据课表项 ID 获取所属班级 ID供 actions-schedule 的 create/update/delete 三个 Action 做越权校验,防止教师通过 scheduleId 修改他人班级课表)( P0-5 已修复classSchedule 写函数 createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem 已迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留 classSchedule 读函数; P2 已修复:getAccessibleClassIdsForTeacher 使用 Promise.all 并行化 ownedIds 与 assignedIds 查询)
  • SchemaCreateTeacherClassSchema / UpdateTeacherClassSchema / DeleteTeacherClassSchema / CreateAdminClassSchema / UpdateAdminClassSchema / DeleteAdminClassSchema / CreateGradeClassSchema / UpdateGradeClassSchema / DeleteGradeClassSchema / CreateClassScheduleItemSchema / UpdateClassScheduleItemSchema / DeleteClassScheduleItemSchema / EnrollStudentByEmailSchema
  • ComponentsAdminClassesClient / GradeClassesClientP1-D 重构:降至 ≤160 行,组合使用共享组件)/ ClassListTableadmin/grade 共享班级列表表格P1-D 抽取)/ ClassFormDialogadmin/grade 共享创建/编辑表单弹窗,schools 可选控制学校选择显隐P1-D 抽取)/ ClassDeleteDialogadmin/grade 共享删除确认弹窗,基于 ConfirmDeleteDialogP1-D 抽取)/ ClassListToolbaradmin/grade 共享工具栏:新建按钮+计数徽章P1-D 抽取)/ class-form-utilsformatSubjectTeachers 工具函数 + ClassFormGrade/SubjectTeacherState 类型P1-D 抽取)/ ClassErrorBoundaryP1-C 新增class component Error Boundary + i18n + router.refresh 重试)/ ClassListSkeleton / ClassCardSkeleton / ClassGridSkeleton / StudentListSkeleton / ScheduleGridSkeletonP1-C 新增5 个骨架屏组件)/ MyClassesGridP0-A 修复:已接入 i18n/ ScheduleView P2 修复:已拆分出 ScheduleCreateDialog/ScheduleEditDialog/ScheduleDeleteDialog 3 个对话框子组件,主组件从 464 行降至 175 行;不再持有 isWorking/handleCreate/handleUpdate/handleDelete 状态下沉到子对话框WEEKDAYS 常量改用从 ./schedule-utils 导入的 SCHEDULE_WEEKDAYS/ ScheduleCreateDialogP2 修复新增:课表新增对话框,从 schedule-view 拆分)/ ScheduleEditDialogP2 修复新增:课表编辑对话框,从 schedule-view 拆分)/ ScheduleDeleteDialogP2 修复新增:课表删除确认对话框,从 schedule-view 拆分)/ ScheduleFilters / StudentsTable / StudentsFiltersP0-A 修复:已接入 i18n/ ClassHeader / ClassOverviewStats / ClassQuickActions / ClassScheduleWidget / ClassAssignmentsWidget / ClassStudentsWidget / ClassTrendsWidget / EditClassDialogP0-A 修复class-detail 下 7 个子组件 + 编辑对话框已接入 i18n
  • 工具函数components/schedule-utils.ts2026-06-26 审计修复新增):getSubjectColor(subject: string): string(根据科目名返回对应颜色类名,中英文双语关键词匹配;修复原实现仅匹配英文关键词导致中文 DEFAULT_CLASS_SUBJECTS 全部落到 default 分支的颜色分组失效 bug/ getPositionStyle(startTime: string, endTime: string): { top: string; height: string }(根据课程开始/结束时间计算课表块在 8:00-18:00 时间轴上的定位百分比)/ timeToMinutes(time: string): number(将 "HH:MM" 时间字符串转换为当天从 0 点起的分钟数)/ SCHEDULE_WEEKDAYS P2 修复新增:课表周常量数组,供 schedule-view 及拆分出的 3 个对话框子组件复用,替换原 schedule-view.tsx 内联的 WEEKDAYS 常量)
  • HooksuseClassDataadmin/grade 共享对话框+表单状态管理+重置逻辑P1-E 抽取)/ useClassFilters按学校筛选年级admin 模式使用P1-E 抽取)

依赖关系

  • 依赖:shared/*@/authschool P1-1 已修复:通过 school data-access.isGradeHead/isGradeManager/findGradeIdByHeadAndNamehomework P0-7 已修复:通过 homework/data-access-classes 暴露的函数获取作业数据,不再直查 homework/exams 表)
  • 被依赖:exams/homework/grades/attendance/scheduling/dashboard(通过 data-accessP0-4 已修复)/parent/course-plans/users P1-1 已修复8+ 处直查 classes 表改为通过 classes data-access/messaging(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId支持学生/家长给班级教师发消息)

已知问题

  • P0-1 已修复:data-access.ts 已拆分为 5 个文件data-access/data-access-stats/data-access-schedule/data-access-students/data-access-admin所有文件均 ≤800 行
  • P0-5 已修复classSchedule 写函数createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem已迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留 classSchedule 读函数getStudentSchedule/getClassSchedule新增 verifyTeacherOwnsClass 供 scheduling 模块跨模块校验教师班级归属
  • P0-7 已修复:data-access-stats.tsdata-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.tsidByName.get(name)! 非空断言清理为 flatMap 安全过滤;data-access-admin.ts 中同类非空断言清理
  • P0-3 修复2026-06-22actions.ts 974 行接近 1000 行硬上限 拆分为 6 个文件actions-teacher/actions-admin/actions-grade/actions-invitations/actions-schedule/actions-sharedactions.ts 改为 50 行 barrel re-export
  • P1-1 修复2026-06-22ctx.roles.includes("admin"/"teacher"/"student") 角色硬编码 改为 hasAdminScope(ctx)/hasTeacherScope(ctx)/hasStudentScope(ctx) 基于 dataScope.type 判断
  • P1-4 修复2026-06-22types.ts 中 ClassHomeworkInsights 等跨领域类型保留在 classes 模块(因为是 classes 对 homework 数据的视图),添加注释说明归属决策
  • P1-D 修复2026-06-24~admin-classes-view.tsx 533 行与 grade-classes-view.tsx 439 行 80% 代码重复 拆分为组合模式:抽取 ClassListTable / ClassFormDialog / ClassDeleteDialog / ClassListToolbar 4 个共享组件 + class-form-utilsformatSubjectTeachers 工具函数 + ClassFormGrade/SubjectTeacherState 类型admin-classes-view 降至 159 行grade-classes-view 降至 153 行admin 删除弹窗统一改用 ConfirmDeleteDialog(与 grade 一致)
  • P1-E 修复2026-06-24抽取 useClassData hook对话框开关+表单字段+提交中标记+重置逻辑admin/grade 共享)+ useClassFilters hook按学校筛选年级admin 模式使用)
  • P0-A 修复2026-06-24classes 模块 13/14 个组件未接入 i18n 全部 13 个组件admin-classes-view/grade-classes-view/my-classes-grid/schedule-view/schedule-filters/students-filters/students-table/class-detail 下 7 个子组件/edit-class-dialog已接入 useTranslations("classes")my-classes-grid 中 5 处硬编码英文("Failed to regenerate"/"Failed to copy"/"No Room"/"vs last week")已替换为翻译键
  • P0-B 修复2026-06-24classes.json 仅 55 行 扩充至 ~370 行,覆盖 invitation/class/list/form/delete/filters/myClasses/students/schedule/detailheader/overview/trends/assignments/widgets/edit/empty/grade/bulk/errors 全部场景en 镜像同步
  • P1-C 修复2026-06-24新增 class-error-boundary.tsxclass component Error Boundary + i18n + router.refresh 重试)和 class-skeleton.tsxClassListSkeleton/ClassCardSkeleton/ClassGridSkeleton/StudentListSkeleton/ScheduleGridSkeleton6 个页面均已包裹 Error Boundaryadmin/school/classes + teacher/classes/my + teacher/classes/my/[id] + teacher/classes/schedule + teacher/classes/studentsClassErrorBoundary、management/grade/dashboardSchoolErrorBoundaryschedule/students 页面 EmptyState 硬编码英文替换为 i18n
  • 2026-06-26 审计修复i18n 全覆盖 + 越权校验 + 角色硬编码消除 + 错误处理统一 + 路由错误边界完善):
    • i18n 翻译键扩充classes.json 新增 actions 命名空间42 个键,覆盖全部 Server Action 错误/成功消息)、error 命名空间3 个键title/description/retry供 ErrorState 组件使用)、filters 命名空间新增 10 个键、detail 命名空间新增 12 个键
    • 越权校验加固actions-schedule.ts 新增 assertClassOwnership 辅助函数(调用 getClassIdByScheduleId + verifyTeacherOwnsClasscreate/update/delete 三个 Action 增加班级归属校验,防止教师通过 scheduleId 修改他人班级课表;actions-teacher.tsupdateTeacherClassAction / deleteTeacherClassAction 增加越权校验
    • 角色硬编码消除data-access.ts / data-access-teacher.ts / data-access-admin.ts 中所有 roles.name, "teacher" / roles.name, "student" 改为 ROLE_NAMES.TEACHER / ROLE_NAMES.STUDENT(共 7 处),统一引用 @/shared/types/permissions 常量
    • 错误处理统一actions-admin.ts / actions-grade.ts / actions-invitations.ts / actions-schedule.ts / actions-teacher.ts 所有 catch 块改为使用 handleActionError(来自 @/shared/lib/action-utils),所有 message 改为使用 await getTranslations("classes") + t(),移除 PermissionDeniedError 直接 importactions-invitations.tsassertClassOwnership 签名变更:新增 t 参数;actions-shared.tstoWeekday / parseSubjectTeachers 改为 async使用 ValidationError 代替 throw new Error
    • DEFAULT_CLASS_SUBJECTS 回退逻辑data-access.tsgetClassSubjects() 新增回退逻辑——subjects 表为空时返回 DEFAULT_CLASS_SUBJECTSdata-access-teacher.ts / data-access-admin.tsinArray(subjects.name, DEFAULT_CLASS_SUBJECTS) 改为 inArray(subjects.name, await getClassSubjects()),确保筛选条件与回退数据源一致
    • DB 错误处理修正data-access-teacher.tsgetTeacherClasses() DB 错误时 throw 而非返回空数组(避免静默吞错)
    • 路由错误边界完善10 个 error.tsx + 1 个 loading.tsx 新增teacher/classes/my/error.tsx、teacher/classes/my/[id]/error.tsx + loading.tsx、teacher/classes/students/error.tsx、teacher/classes/schedule/error.tsx、student/learning/error.tsx + loading.tsx、student/learning/courses/error.tsx、student/learning/courses/[classId]/error.tsx、student/schedule/error.tsx统一使用 shared 层 ErrorState 组件
    • 新增工具函数文件components/schedule-utils.ts 抽取 getSubjectColor / getPositionStyle / timeToMinutes 3 个纯函数(修复中文科目颜色分组失效 bug + 提升可测试性)
    • 新增权限点使用student/learning/page.tsx / student/learning/courses/page.tsx / student/learning/courses/[classId]/page.tsx / student/schedule/page.tsx 接入 requirePermission(CLASS_READ)
    • generateMetadata 接入 P2 修复新增8 个 page.tsx 新增 generateMetadata 导出(通过 next-intl getTranslations 生成页面标题):teacher/classes/my/page.tsxteacher/classes/my/[id]/page.tsxteacher/classes/schedule/page.tsxteacher/classes/students/page.tsxstudent/learning/page.tsxstudent/learning/courses/page.tsxstudent/learning/courses/[classId]/page.tsxstudent/schedule/page.tsx

文件清单

文件 行数 职责
data-access.ts 548 核心班级 CRUD + 邀请码 + 教师班级管理(含 re-export 向后兼容)
data-access-stats.ts 513 作业统计查询(班级/年级作业洞察,通过 homework/data-access-classes 获取数据)
data-access-schedule.ts 93 课表查询(学生/班级课表只读P0-5 已修复:写函数已迁移至 scheduling 模块)
data-access-students.ts 253 学生相关查询(科目成绩、学生名单、学生班级,通过 homework/data-access-classes 获取数据)
data-access-admin.ts 406 管理员班级管理(管理员班级 CRUD、年级管理班级查询
actions.ts 50 Barrel re-exportP0-3 修复:从 974 行拆分为 6 个文件)
actions-teacher.ts 100 教师班级 CRUD3 个 Action
actions-admin.ts 120 管理员班级 CRUD3 个 Action
actions-grade.ts 110 年级组长班级 CRUD3 个 Action
actions-invitations.ts 502 邀请码与注册 + 批量操作10 个 ActionP2-4 新增批量导入学生/批量分配教师)
actions-schedule.ts 90 班级课表 CRUD3 个 Action
actions-shared.ts 60 共享工具hasAdminScope/hasTeacherScope/hasStudentScope/parseSubjectTeachers/toWeekday
schema.ts 152 Zod 校验13 个 schema教师/管理员/年级班级 CRUD + 课表 CRUD + 邮箱注册)
types.ts 201 类型定义含跨领域类型说明注释P1-4 修复)
components/admin-classes-view.tsx 159 管理员班级客户端P1-D 重构:组合共享组件)
components/grade-classes-view.tsx 153 年级班级客户端P1-D 重构:组合共享组件)
components/class-list-table.tsx 115 班级列表表格admin/grade 共享P1-D 抽取)
components/class-form-dialog.tsx 200 班级创建/编辑表单弹窗admin/grade 共享P1-D 抽取)
components/class-delete-dialog.tsx 35 班级删除确认弹窗admin/grade 共享P1-D 抽取)
components/class-list-toolbar.tsx 30 班级列表工具栏admin/grade 共享P1-D 抽取)
components/class-form-utils.ts 35 formatSubjectTeachers + 表单类型P1-D 抽取)
components/schedule-utils.ts 89 课表纯函数工具集2026-06-26 审计修复新增getSubjectColor/getPositionStyle/timeToMinutes P2 修复新增 SCHEDULE_WEEKDAYS 周常量;修复中文科目颜色分组失效 bug + 提升可测试性)
components/class-error-boundary.tsx 45 班级模块 Error BoundaryP1-C 新增)
components/class-skeleton.tsx 80 班级模块骨架屏组件5 个List/Card/Grid/StudentList/ScheduleGridP1-C 新增)
components/my-classes-grid.tsx 418 教师班级卡片网格P0-A 修复:已接入 i18n
components/schedule-view.tsx 175 课表视图P0-A 修复:已接入 i18n P2 修复:拆分出 ScheduleCreateDialog/ScheduleEditDialog/ScheduleDeleteDialog 3 个对话框子组件,主组件从 464 行降至 175 行;不再持有 isWorking/handleCreate/handleUpdate/handleDelete 状态下沉到子对话框WEEKDAYS 常量改用从 ./schedule-utils 导入的 SCHEDULE_WEEKDAYS
components/schedule-create-dialog.tsx - 课表新增对话框( P2 修复新增:从 schedule-view.tsx 拆分,承接原 isWorking/handleCreate 状态)
components/schedule-edit-dialog.tsx - 课表编辑对话框( P2 修复新增:从 schedule-view.tsx 拆分,承接原 isWorking/handleUpdate 状态)
components/schedule-delete-dialog.tsx - 课表删除确认对话框( P2 修复新增:从 schedule-view.tsx 拆分,承接原 isWorking/handleDelete 状态)
components/schedule-filters.tsx 60 课表筛选器P0-A 修复:已接入 i18n
components/students-filters.tsx 60 学生筛选器P0-A 修复:已接入 i18n
components/students-table.tsx 120 学生表格P0-A 修复:已接入 i18n
components/class-detail/class-header.tsx 100 班级详情头部P0-A 修复:已接入 i18n
components/class-detail/class-overview-stats.tsx 80 班级概览统计P0-A 修复:已接入 i18n
components/class-detail/class-quick-actions.tsx 60 快捷操作P0-A 修复:已接入 i18n
components/class-detail/class-schedule-widget.tsx 120 课表 WidgetP0-A 修复:已接入 i18n
components/class-detail/class-assignments-widget.tsx 100 作业 WidgetP0-A 修复:已接入 i18n
components/class-detail/class-students-widget.tsx 120 学生 WidgetP0-A 修复:已接入 i18n
components/class-detail/class-trends-widget.tsx 150 趋势 WidgetP0-A 修复:已接入 i18n
components/class-detail/edit-class-dialog.tsx 145 编辑班级对话框P0-A 修复:已接入 i18n
hooks/use-class-data.ts 95 对话框+表单状态管理 hookP1-E 抽取)
hooks/use-class-filters.ts 30 按学校筛选年级 hookP1-E 抽取)

路由文件清单app/(dashboard)/2026-06-26 审计修复新增错误边界 + 加载骨架):

文件 职责
teacher/classes/my/error.tsx 教师我的班级错误边界(使用 shared ErrorState + namespace="classes"
teacher/classes/my/[id]/error.tsx 班级详情错误边界(使用 shared ErrorState
teacher/classes/my/[id]/loading.tsx 班级详情加载骨架ClassGridSkeleton
teacher/classes/students/error.tsx 学生管理错误边界(使用 shared ErrorState
teacher/classes/schedule/error.tsx 课表错误边界(使用 shared ErrorState
student/learning/error.tsx 学生学习中心错误边界(使用 shared ErrorState
student/learning/loading.tsx 学生学习中心加载骨架
student/learning/courses/error.tsx 学生课程列表错误边界(使用 shared ErrorState
student/learning/courses/[classId]/error.tsx 学生课程详情错误边界(使用 shared ErrorState
student/schedule/error.tsx 学生课表错误边界(使用 shared ErrorState

2.8 school学校模块

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

导出函数

  • ActionscreateSchoolAction / updateSchoolAction / deleteSchoolAction / createAcademicYearAction / updateAcademicYearAction / deleteAcademicYearAction / createDepartmentAction / updateDepartmentAction / deleteDepartmentAction / createGradeAction / updateGradeAction / deleteGradeAction / promoteGradesAction(编排层:权限校验 + Zod 校验 + 调用 data-access + revalidatePath + after(logAudit)promoteGradesAction 年级升级,审计日志 grade.promote
  • Data-access只读查询getSchools / getGrades / getDepartments / getAcademicYears / getStaffOptions / getGradesForStaff / getOrgTree / getSubjectOptions / getGradeOptions / getSubjectNameMapByIdsP1-1 新增:批量科目名称映射,供 homework/data-access-classes 调用)/ getGradeOverviewStats v4-P2-6 新增:年级概览统计,返回 GradeOverviewStats[],每个年级的 classCount/studentCount/teacherCount供年级管理卡片视图使用动态导入 classes/data-access 避免循环依赖))+ 写操作(create/update/delete × Department/School/Grade/AcademicYear+ promoteGrades(schoolId) 年级升级order +1 + 名称升级,辅助函数 promoteGradeName
  • Types v4-P2-6 新增):GradeOverviewStats(年级概览统计接口:{ gradeId, classCount, studentCount, teacherCount },位于 data-access.ts
  • ComponentsSchoolsClient / SchoolFormDialog / SchoolDeleteDialog / SchoolListToolbar / SchoolErrorBoundary / SchoolListSkeleton / SchoolCardSkeleton / OrgTreeNav / GradesClient v4-P2-6 更新:新增 gradeStats: GradeOverviewStats[] prop渲染年级概览卡片视图——每个年级卡片展示班级/学生/教师数 + 年级主任/教学主任 + 快捷操作入口)/ GradeInsightsFilters v4-P2-6 新增:年级洞察筛选器,使用 ChipNav 替代原生 form get点击 chip 即时通过 URL 参数切换,无整页刷新)/ GradeDistributionPanel / GradeHomeworkPanel / GradeExamsPanel / GradeProgressPanel v4-P2-7 新增:年级仪表盘 4 个维度面板组件,位于 components/grade-dashboard/ 子目录,服务端组件直接渲染数据,无客户端交互)

依赖关系

  • 依赖:shared/*@/authusers⚠️ getStaffOptions 直查 users/roles可接受
  • 被依赖:exams P1-1 已修复:通过 school data-accesshomework P1-1 已修复:通过 school data-accessgrades P1-1 已修复:通过 school data-accessquestions P1-1 已修复:通过 textbooks data-accessclasses P1-1 已修复:通过 school data-accesscourse-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 未记录)
  • ⚠️ P2getStaffOptions/getGrades 直查 users/roles展示用可接受
  • P0-2 修复2026-06-22年级 CRUD 逻辑与 grade-management 模块重复定义 grade-management 死模块已删除,年级 CRUD 统一由 school 模块负责
  • P0-5 修复2026-06-22school/components/* 4 个组件缺少 i18n 全部 4 个组件schools-view/grades-view/departments-view/academic-year-view已接入 useTranslations("school")school.json i18n 文件已创建并扩充
  • P1-3 修复2026-06-22新增 school-error-boundary.tsxclass component Error Boundary + i18n + router.refresh 重试)和 school-skeleton.tsxSchoolListSkeleton 表格骨架 + SchoolCardSkeleton 卡片骨架4 个页面schools/grades/departments/academic-year均已包裹 SchoolErrorBoundaryschool.json 补充 errors.boundary.* 翻译键
  • P1-5 修复2026-06-22schools-view.tsx 硬编码 Table+Dialog+AlertDialog 拆分为组合模式SchoolListToolbar + SchoolFormDialog + SchoolDeleteDialog + useSchoolData hook
  • P1-6 修复2026-06-22新增 getSchoolsForUser(userId) / getGradesForUser(userId) 权限感知查询函数,根据用户角色返回可见数据范围
  • P2-1 修复2026-06-22抽取 use-school-data hook将对话框状态管理逻辑与 UI 分离
  • P2-2 修复2026-06-23新增 getOrgTree() data-access 函数(学校→年级→班级三级树,通过 import("@/modules/classes/data-access") 动态导入获取班级数据避免循环依赖)+ OrgTreeNode 类型 + OrgTreeNav 组件(三级树形导航,支持搜索过滤/选中高亮/展开折叠/不同节点类型图标school.json 补充 orgTree.* 翻译键
  • P2-3 修复2026-06-23新增 promoteGradesAction + promoteGrades(schoolId) + promoteGradeName(name) 辅助函数(中文数字 一→二…十二、阿拉伯数字 1→2…12 识别),按 order 降序逐条 +1 避免唯一约束冲突;含 after(() => logAudit({ action: "grade.promote" })) 审计日志
  • P2-4 修复2026-06-23新增 bulkEnrollStudentsActionCSV 批量导入学生,复用 enrollStudentByEmail+ bulkAssignSubjectTeachersActionCSV 批量分配教师,简化实现含 TODO 待完善查找逻辑classes/actions.ts barrel 导出
  • P2-5 修复2026-06-23为 department/academicYear/grade 的 9 个 CRUD Action 补充 after(() => logAudit(...)) 审计日志action: department.create/update/delete、academicYear.create/update/delete、grade.create/update/delete与 school 实体审计日志策略一致
  • v4-P2-6 改进2026-06-23年级管理体验增强——新增 getGradeOverviewStats() data-access 函数(返回 GradeOverviewStats[]:每个年级的 classCount/studentCount/teacherCount动态导入 classes/data-access 避免循环依赖);GradesClient 新增 gradeStats prop渲染年级概览卡片视图班级/学生/教师数 + 年级主任/教学主任 + 快捷操作入口admin/school/grades/page.tsx 新增 getGradeOverviewStats() 查询并传入 GradesClient
  • v4-P2-6 改进2026-06-23新增 GradeInsightsFilters 组件(使用 ChipNav 替代原生 form get点击 chip 即时通过 URL 参数切换无整页刷新admin/school/grades/insights/page.tsx 与 management/grade/insights/page.tsx 重写,使用 GradeInsightsFilters 替代原生 form get添加 i18n表格添加 overflow-x-auto 水平滚动
  • P1-A 修复2026-06-24grades-view.tsx 860 行超出组件行数限制 拆分为组合模式:GradeFormDialog(创建/编辑表单对话框,含客户端校验+去重+isDirty 检测)+ GradeDeleteDialog(删除确认)+ GradeListToolbar(搜索/筛选/排序工具栏)+ GradeOverviewCards(年级概览卡片视图)+ useGradeData hook对话框状态管理主容器 grades-view.tsx 降至 290 行,参照 schools-view.tsx 组合模式

文件清单

文件 行数 职责
actions.ts 457 13 个 Server Action编排层无 DB 直访;含 promoteGradesAction 年级升级)
data-access.ts 782 只读查询 + 12 个写操作 + promoteGrades 年级升级 + getOrgTree 组织架构树 + 跨模块查询接口 + 权限感知函数getSchoolsForUser/getGradesForUser
schema.ts 56 Zod 校验(含 PromoteGradesSchema
types.ts 103 类型定义(含 Insert/Update 入参类型 + OrgTreeNode
components/schools-view.tsx 132 学校列表容器组合模式P1-5 修复)
components/school-form-dialog.tsx 80 学校创建/编辑对话框P1-5 修复)
components/school-delete-dialog.tsx 50 学校删除确认对话框P1-5 修复)
components/school-list-toolbar.tsx 30 学校列表工具栏P1-5 修复)
components/school-error-boundary.tsx 72 共享 Error BoundaryP1-3 修复)
components/school-skeleton.tsx 69 共享骨架屏P1-3 修复)
components/org-tree-nav.tsx 134 学校→年级→班级三级树形导航P2-2 修复:搜索过滤 + 选中高亮 + 展开折叠 + 节点类型图标)
components/grades-view.tsx 290 年级管理客户端P1-A 修复:拆分为组合模式,仅负责列表渲染+筛选+组合子组件)
components/grade-form-dialog.tsx 280 年级创建/编辑表单对话框P1-A 新增:含客户端校验+去重+isDirty 检测useActionMutation 管理 mutation
components/grade-delete-dialog.tsx 65 年级删除确认对话框P1-A 新增)
components/grade-list-toolbar.tsx 115 年级列表工具栏P1-A 新增:搜索+学校筛选+主任筛选+排序+新建按钮)
components/grade-overview-cards.tsx 165 年级概览卡片视图P1-A 新增:班级/学生/教师数+年级主任/教学主任+快捷操作)
components/grade-insights-filters.tsx 48 v4-P2-6 新增年级洞察筛选器ChipNav 替代原生 form get点击 chip 即时通过 URL 参数切换,无整页刷新)
hooks/use-school-data.ts 40 学校数据管理 hookP2-1 修复)
hooks/use-grade-data.ts 42 年级数据管理 hookP1-A 新增:对话框状态管理,参照 use-school-data 模式)

2.8b grade-management年级管理模块 已删除

2026-06-22 审计发现该模块拥有完整的理想架构Service 接口 + Context DI + 角色配置 + Error Boundary + Skeleton + i18n + hooks 分离),但 13 个相关页面中无任何一个导入此模块management/grade/* 页面实际依赖 classesschool 模块的 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排课模块

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

导出函数

  • ActionsautoScheduleAction / applyAutoScheduleAction / getSchedulingRulesAction / updateSchedulingRulesAction / getScheduleChangesAction / createScheduleChangeAction / updateScheduleChangeAction / deleteScheduleChangeAction
  • Data-accessgetSchedulingRules / 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/*@/authclasses 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
  • ⚠️ P2actions.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考勤模块— 结构典范

职责:考勤记录管理 + 统计分析 + 规则配置。 L-6 节次考勤:支持按节次(早读/上午/下午/晚自习/全日)记录与过滤。 L-9 关联分析: 考勤与成绩跨模块关联分析(Pearson 相关系数 + 风险分级)。

导出函数

  • Actions5 个写 ActionrecordAttendanceAction L-6formData 读取 period/ batchRecordAttendanceAction L-2保存成功后调用 notifyParentsOfAbsence 异步通知家长,失败不阻断主流程; L-6每条记录携带 period/ updateAttendanceAction L-6formData 读取 period/ deleteAttendanceAction / saveAttendanceRulesAction 审计修复:均调用 requirePermission(ATTENDANCE_MANAGE) + 资源归属校验 + trackEvent 埋点)
  • Data-accessgetAttendanceRecords 审计修复:移除跨模块 JOIN委托 getUserNamesByIds/getClassNamesByIds L-6支持按 period 过滤)/ getClassAttendanceForDate L-6新增可选 period 参数)/ buildPeriodFilter L-6 新增纯函数full_day 匹配 NULL OR 'full_day' 向后兼容)/ createAttendanceRecord L-6写入 period 默认 full_day/ batchCreateAttendanceRecords L-6批量写入 period/ updateAttendanceRecord L-6支持更新 period/ deleteAttendanceRecord / getClassStudentsForAttendance 审计修复:委托 classes/data-access.getClassActiveStudentsWithInfo/ getAttendanceStats 审计修复SQL 聚合 + 返回统一 AttendanceStats 类型)/ upsertAttendanceRules / getAttendanceRules / getAttendanceRecordClassId / syncExcusedFromLeaveRequest L-5 新增:请假审批通过后遍历日期区间插入 excused 记录,幂等不覆盖已有记录; L-6插入时 period: full_dayleave-requests/actions.reviewLeaveRequestAction 调用)
  • Data-access-statsgetStudentAttendanceSummary 审计修复SQL 聚合 + 委托 data-access L-6recentRecords 映射 period/ getClassAttendanceStats 审计修复SQL 聚合 + 委托 data-access L-6studentRecords 映射 period/ computeStats 纯函数导出,可单测)
  • Notifications L-2 新增):notifyParentsOfAbsence批量考勤录入后多渠道通知家长in_app/sms/email/wechat一次性批量查询 parent-student 映射避免 N+1
  • Import-exportexportAttendanceRecordsToExcel 审计修复:类型守卫 + i18n 列头)
  • Services 审计新增 P1-12/P2-6AttendanceDataService 接口 / AttendanceReadService / AttendanceWriteService / AttendanceRepository / AttendanceProvider / useAttendanceService / createAttendanceDataService / createAttendanceReadService
  • ComponentsAttendanceSheet 审计修复AlertDialog + 常量统一 + 容器内 keydown + useMemo L-6新增节次选择器3 列布局,提交时携带 period/ AttendanceRecordList 审计修复:usePermission 控制删除按钮 + resolveActionError L-6新增节次列null 视为 full_day 展示)/ AttendanceFilters / AttendanceStatsCard / AttendanceStatsCards 审计修复:统一 AttendanceStats 类型)/ AttendanceStatsClassSelector / AttendanceRulesForm 审计修复:resolveActionError/ StudentAttendanceView / AttendancePageLayout
  • Data-access-correlation( L-9 新增): getAttendanceGradeCorrelation(跨模块委托 classes/users/grades data-access 聚合数据) / canAccessCorrelationAnalysis(权限校验)
  • Correlation-compute( L-9 新增纯函数, 与 IO 解耦便于单测): computePearsonCorrelation / classifyRiskLevel / interpretCorrelation / computeCorrelationSummary
  • Components L-9( L-9 新增): AttendanceGradeCorrelationCard(散点图 + 风险表格, recharts ScatterChart 风险等级着色)

依赖关系

  • 依赖:shared/*(含 shared/constants/attendance-status 审计新增:状态类型与视觉常量下沉至 shared@/authclasses 审计修复:通过 classes/data-access 获取班级学生/名称)、users 审计修复:通过 users/data-access.getUserNamesByIds 获取姓名)、parent L-2 新增:通过 parent/data-access.getParentStudentMapByStudentIds 获取家长-学生映射)、notifications L-2 新增:通过 sendBatchNotifications 多渠道分发)、grades( L-9 新增:通过 grades/data-access.getGradeRecords 获取成绩数据用于关联分析)、next-intl
  • 被依赖:parent 审计修复:通过 createAttendanceReadService 接口消费,不再直接 import attendance 组件;常量改从 shared/constants/attendance-status 导入)、app/api/export(通过 exportAttendanceRecordsToExcel)、leave-requests L-5 新增:审批通过后通过 syncExcusedFromLeaveRequest 同步 excused 考勤记录)

L-6 节次考勤数据模型

  • DBattendanceRecords.period enum('morning_reading','morning','afternoon','evening','full_day') nullable向后兼容 NULL 视为 full_day
  • 索引:classDatePeriodIdxclass_id + date + period 复合索引)
  • 迁移:drizzle/0021_attendance_period.sql
  • 类型:AttendancePeriodtypes.tsAttendanceRecord/AttendanceListItem/AttendanceQueryParams 新增 period 字段
  • SchemaAttendancePeriodEnumZodRecordAttendanceSchema 默认 full_dayUpdateAttendanceSchema 可选 period
  • i18nattendance.period.{label,morning_reading,morning,afternoon,evening,full_day,allPeriods} 中英文翻译
  • 过滤策略:buildPeriodFilter 对 full_day 使用 or(isNull(period), eq(period, "full_day")) 匹配旧数据
  • 对外统一mapListItem 中 r.period ?? "full_day",前端展示 null 统一为 full_day

L-9 考勤与成绩关联分析

  • 类型: AttendanceGradeRiskLevel(high/medium/low) / AttendanceGradeCorrelationItem / AttendanceGradeCorrelationSummary(types.ts)
  • 纯函数(correlation-compute.ts) computePearsonCorrelation(Pearson r) / classifyRiskLevel(high=低出勤+低分, medium=中低出勤+中低分, low=其余) / interpretCorrelation / computeCorrelationSummary
  • 数据访问(data-access-correlation.ts) getAttendanceGradeCorrelation 跨模块委托 classes/users/grades data-access; canAccessCorrelationAnalysis scope 校验
  • UI(attendance-grade-correlation-card.tsx) recharts ScatterChart 散点图, 风险等级着色(red/amber/emerald)
  • 集成: admin/attendance/page.tsx 指定班级时显示
  • i18n attendance.correlation.* 中英文翻译

已知问题(详见 docs/architecture/audit/attendance-audit-report.md

  • P0-1 已修复teacher/sheet、teacher/stats 页面增加 requirePermission(ATTENDANCE_READ)
  • P0-2 已修复teacher 分页使用后端返回的 totalPages
  • P0-3 已修复:safeParseDate fieldName 改为中性 key前端通过 resolveActionError 按 errorCode 本地化
  • P0-4 已修复error.tsx 重试按钮改用 t("actions.retry")
  • P0-5 已修复data-access 移除跨模块 JOIN改为委托 data-access
  • P1-1 已修复teacher/student 主页面增加 requirePermission(ATTENDANCE_READ)
  • P1-2 已修复parent 通过 AttendanceReadService 接口消费
  • P1-3 已修复parent-attendance-calendar 改从 shared/constants/attendance-status 导入
  • P1-6 已修复export.ts 类型守卫替代 as 断言
  • P1-7 已修复calendar 使用 useLocale() 替代硬编码 locale
  • P1-8 已修复teacher 子路由新增 error.tsx
  • P1-9 已修复:常量统一从 constants.tsre-export shared导入
  • P1-10 已修复4 个 error.tsx 抽取为 shared RouteErrorBoundary
  • P1-11 已修复:getClassAttendanceStats 改用 SQL 聚合
  • P1-12 已修复:新增 AttendanceDataService 接口 + Context 注入
  • P2-1 已修复:删除按钮 usePermission 控制显隐
  • P2-5 已修复stats 数据结构统一为 AttendanceStats
  • P2-6 已修复:导出 AttendanceRepository 接口类型
  • P2-7 已修复keydown 限容器内 + 排除 Select/contenteditable
  • P2-8 已修复parent-attendance-warning/rate-card 改为 RSC
  • P2-9 已修复statusCounts 使用 useMemo
  • stats 独立拆分为 data-access-stats.ts(拆分范例)
  • DataScope 完整接入 6 种 scope 类型
  • actions 层无直接 DB 访问
  • Server Action 均有 trackEvent 埋点

文件清单

文件 行数 职责
actions.ts 258 5 个写 Server Action权限校验 + 归属校验 + Zod + 埋点; L-6 读取 period
data-access.ts ~410 考勤 CRUD + 规则 upsert + 总览统计SQL 聚合 + 委托 data-access L-6 period 过滤/写入/更新)
data-access-stats.ts ~230 学生/班级考勤汇总SQL 聚合 + 委托 data-access L-6 period 映射)
export.ts 93 Excel 导出(类型守卫 + i18n 列头)
schema.ts ~60 Zod 校验6 个 schema + L-6 AttendancePeriodEnum
types.ts ~95 类型定义(状态常量已迁移至 shared L-6 AttendancePeriod + period 字段)
constants.ts 19 重导出 shared/constants/attendance-status
services/types.ts 166 服务接口抽象AttendanceDataService/Repository
services/attendance-context.tsx 46 React Context 注入AttendanceProvider + hook
services/attendance-data-service.ts 162 服务实现 + 工厂函数
components/attendance-sheet.tsx ~480 批量点名表单AlertDialog + 容器内 keydown + useMemo L-6 节次选择器)
components/attendance-record-list.tsx ~165 记录列表 + 删除对话框(权限控制 + resolveActionError L-6 节次列)
components/attendance-filters.tsx 94 URL 同步筛选器
components/attendance-stats-card.tsx 82 单卡片统计8 指标)
components/attendance-stats-cards.tsx 83 管理员总览 6 卡片网格(统一 AttendanceStats
components/attendance-stats-class-selector.tsx 27 班级筛选 ChipNav
components/attendance-rules-form.tsx 155 规则配置表单resolveActionError
components/student-attendance-view.tsx 111 学生只读视图
components/attendance-page-layout.tsx 38 页面布局骨架(插槽模式)
correlation-compute.ts ~90 L-9 纯函数: Pearson 相关系数 + 风险分级 + 汇总 (与 IO 解耦)
data-access-correlation.ts ~130 L-9 跨模块关联分析: 委托 classes/users/grades data-access
components/attendance-grade-correlation-card.tsx ~220 L-9 散点图 + 风险表格 (recharts ScatterChart)

架构变更2026-07-05Phase 4.5 — attendance-sheet.tsx 提交状态改用 useOptimistic

  • attendance-sheet.tsxisSubmitting useState → useOptimistic<boolean>。保留 <form action={handleSubmit}> 模式 + useFormStatus SubmitButtonprogressive enhancementsetOptimisticSubmitting(true) 在 form action 内调用action 完成后 optimisticSubmitting 自动恢复 false移除 finally
  • 关键决策:保留 form action 模式而非改用 useTransition 包裹,因为 useFormStatus().pending 依赖 form action 的 promise 状态,若用 startTransition 包裹会导致 form action 立即返回、useFormStatus 不可靠;useOptimisticaddOptimistic 必须在 transition 或 action 内调用form action 也算 action

2.11 users用户模块

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

导出函数

  • ActionsgetUserProfileAction / updateUserProfileAction / importUsersAction / exportUsersAction / downloadUserTemplateAction / updateUserRoleAction / deleteUserAction
  • Data-accessgetUserProfile / getCurrentStudentUser P2-20 已修复:从 homework 模块迁移而来6 个 student 页面通过此函数获取学生身份,不再依赖 homework 模块)/ getAdminUsers(管理员用户列表分页查询,支持搜索+角色聚合)/ getAdminUserRoles(角色名列表,用于筛选下拉框)/ getAllUserIds(limit=1000, offset=0) P3-7全校用户 ID 查询,默认 LIMIT 1000 防止 OOM + 分页参数支持)/ getUserIdsByGradeId / getUserNamesByIds
  • Import-exportgenerateUserImportTemplate / parseUserImportData / exportUsersToExcel+ re-export batchImportUsers / UserImportResult 保持向后兼容)
  • User-servicebatchImportUsers(用户创建 + 密码哈希 + 角色分配)
  • Class-registrationregisterStudentByInvitationCode(委托 classes/data-access 完成班级注册)
  • ComponentsUserImportDialog(批量导入对话框)/ AdminUsersView(管理员用户列表客户端组件,搜索+筛选+分页+删除)

依赖关系

  • 依赖:shared/*(含 shared/lib/role-utils P2 已修复:删除本地 normalizeRoleName/resolvePrimaryRole/rolePriority,统一复用 shared/lib/role-utils.resolvePrimaryRole)、@/authclasses P1-4 已修复:通过 class-registration.ts 调用 classes/data-access.enrollStudentByInvitationCode,不再直写 classEnrollments
  • 被依赖:dashboard(通过 data-accessP0-4 已修复)、grades P1-1 已修复:通过 users data-accesshomework P1-1 已修复:通过 users data-accessscheduling P1-1 已修复:通过 users data-accessdiagnostic P1-1 已修复:通过 users data-accesselective P1-1 已修复:通过 users data-accessproctoring P1-1 已修复:通过 users data-accessparent P1-1 已修复:通过 users data-access

已知问题

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

文件清单

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

2.12 dashboard仪表盘模块

职责:管理员/教师/学生/家长仪表盘数据聚合 + 权限校验 + i18n + 纯逻辑工具函数 + 数据服务接口抽象 + 配置驱动 Widget 渲染 + 监控埋点接口 + 时间范围筛选 + 数据对比 + 自定义仪表盘 + 通知集成 + 实时更新 + 数据导出 + 移动端优化。

导出函数

  • ActionsgetAdminDashboardAction / getTeacherDashboardAction / getStudentDashboardAction / getParentDashboardAction(均调用 requirePermission() 校验对应 DASHBOARD_*_READ 权限)
  • Data-accessgetAdminDashboardData(并行调用 6 个模块 stats 函数V4 添加趋势数据 TODO 注释)
  • StreamsgetAdminDashboardStreamsV4 新增:返回各分区独立 Promise 供 React use() 流式消费)
  • Lib 纯函数:toWeekday / countStudentAssignments / sortUpcomingAssignments / filterTodayScheduleV4 修复:移除 as T[] 断言,改为非泛型函数)/ computeTeacherMetrics / getGreetingKey / timeToMinutesV4 新增)/ getScheduleStatusV4 新增)/ getDueUrgencyV4 新增)/ getActionLabelKeyV4 新增)/ getActionVariantV4 新增)/ computeComparisonL3 新增:数据对比计算)/ toCSVL7 新增CSV 导出)/ downloadFileL7 新增:浏览器下载)/ exportDashboardCSVL7 新增:仪表盘 CSV 导出便捷方法)
  • ServicesV4 新增):DashboardService 接口(抽象数据依赖,含 4 个角色方法签名)/ DashboardAnalytics 接口监控埋点Widget 点击 / 空状态 / 错误重试 / 页面停留)/ DashboardServiceProviderReact Context 依赖注入)/ useDashboardService / useDashboardAnalytics
  • ConfigV4 新增):ADMIN_WIDGET_CONFIG / TEACHER_WIDGET_CONFIG / STUDENT_WIDGET_CONFIG / PARENT_WIDGET_CONFIG / getWidgetConfig(配置驱动 Widget 布局,新增角色只需修改配置)
  • HooksL4/L6 新增):useDashboardPreferencesL4 自定义仪表盘偏好localStorage 持久化)/ useDashboardRealtimeL6 SSE 实时更新,指数退避重连)
  • ComponentsAdminDashboardView / TeacherDashboardView / StudentDashboard / ParentDashboardV4 P1-4 从 parent 模块迁移,使用 slots 组合注入)/ UserGrowthChart / DashboardGreetingHeader共享问候头部V2 抽象V4 支持空用户名 fallback/ DashboardSection(分区 Error Boundary + Suspense + 骨架屏V4 P3-1 新增 ariaLabel prop 支持键盘导航)/ DashboardTimeRangeFilterL2 时间范围筛选)/ ComparisonBadgeL3 数据对比徽章)/ DashboardNotificationWidgetL5 通知中心 Widget/ DashboardResponsiveLayoutL8 移动端响应式布局)/ MobileSwipeContainerL8 移动端水平滑动)/ DesktopGridL8 桌面端网格)
  • TypesV4 新增):DashboardRole / WidgetSkeletonVariant / DashboardWidgetConfig / DashboardLayoutConfig / ComparisonTrend / ComparisonResultL3/ TimeRangeL2/ ConnectionStatusL6/ DashboardNotificationItemL5/ ExportRow / ExportColumnL7

依赖关系

  • 依赖:shared/*@/authclasses(通过 data-accesshomework(通过 data-accessusers(通过 data-accessparent(通过 data-access.getParentDashboardData + 组件 slots 注入 ChildCard/ParentAttentionBannertextbooks/questions/exams(通过各模块 dashboard stats 函数)、rechartsnext-intl
  • 被依赖:无

权限点

  • DASHBOARD_ADMIN_READadmin
  • DASHBOARD_TEACHER_READteacher
  • DASHBOARD_STUDENT_READstudent
  • DASHBOARD_PARENT_READparent

已知问题

  • 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-intluseTranslations / 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 当前返回空数组占位V4 P2-4 添加 TODO 注释),待后续接入真实统计
  • V4 P1-4 已修复:ParentDashboard 组件从 modules/parent/components/ 迁移至 modules/dashboard/components/parent-dashboard/,使用 slots 组合注入 ChildCard 和 ParentAttentionBannerparent 模块仅保留数据访问
  • P0 已修复2026-06-22 V210 个子组件 i18n 遗漏全部补齐,新增 ~50 个翻译键
  • P1 已修复2026-06-22 V2抽象共享组件 DashboardGreetingHeader,消除 teacher/student 头部 90% 重复代码
  • P2 已修复2026-06-22 V2为 6 个纯函数添加 31 个单元测试
  • P2 已修复2026-06-22 V2a11y 增强 — admin 表格 <caption>、teacher/student 视图语义化标签
  • P0 已修复2026-06-22 V4filterTodaySchedule 移除 as T[] 类型断言,改为非泛型函数
  • P1 已修复2026-06-22 V45 个组件内嵌纯函数抽取至 lib/dashboard-utils.ts
  • P1 已修复2026-06-22 V4teacherName 硬编码 "Teacher" fallback 改为空字符串
  • P1 已修复2026-06-22 V4teacher-schedule.tsx 移除本地类型定义,从 types.ts 导入
  • P2 已修复2026-06-22 V4新增 services/dashboard-service.tsx,定义 DashboardService 接口 + DashboardAnalytics 监控埋点接口 + React Context 依赖注入
  • P2 已修复2026-06-22 V4新增 config/widget-configs.ts4 个角色的 Widget 布局配置,支持配置驱动渲染
  • V4 P2-4 已修复admin 趋势数据占位添加 TODO 注释,标记后续实现
  • V4 P2-5 已修复4 个角色 StatCard 使用模式统一(均传 color + valueClassName="tabular-nums"
  • V4 P3-1 已修复:DashboardSection 新增 ariaLabel prop渲染 <section role="region" tabIndex={0}>admin/teacher/student 视图全部接入
  • V4 P3-2 已修复:AdminTrendCharts 添加 TODO 注释标记趋势数据待接入
  • V4 P3-3 已修复:TeacherTodoCard 排序逻辑改用 VARIANT_PRIORITY 数值映射,提升可读性
  • L1 已实施Widget 下钻导航 — admin StatCard 添加 href 跳转ContentRow 支持可选 href 下钻
  • L2 已实施:时间范围筛选 — 新增 DashboardTimeRangeFilter 客户端组件URL search param 持久化,集成到 admin dashboard
  • L3 已实施:数据对比 — 新增 ComparisonBadge 组件 + computeComparison 纯函数
  • L4 已实施:自定义仪表盘 — 新增 useDashboardPreferences HooklocalStorage 持久化 Widget 显示/隐藏偏好
  • L5 已实施:通知中心集成 — 新增 DashboardNotificationWidget 组件
  • L6 已实施:实时更新 — 新增 useDashboardRealtime HookSSE + 指数退避重连)
  • L7 已实施:数据导出 — 新增 lib/export-utils.tsCSV 导出 + 浏览器下载)
  • L8 已实施:移动端优化 — 新增 DashboardResponsiveLayout / MobileSwipeContainer / DesktopGrid 组件

文件清单

文件 行数 职责
actions.ts 167 4 个 Server Action编排层requirePermission() 权限校验)
data-access.ts 51 admin 仪表盘数据聚合(并行调用各模块 stats 函数V4 添加趋势数据 TODO
streams.ts 34 admin 流式数据源(返回未解析 Promise 供 React use() 消费)
lib/dashboard-utils.ts 279 纯逻辑工具函数weekday / 统计 / 排序 / 指标计算 / 问候语 / 课表状态 / 作业紧急度 / 操作按钮 V4 新增 5 个函数)
lib/export-utils.ts 73 L7 新增CSV 导出工具toCSV / downloadFile / exportDashboardCSV
types.ts 107 Admin / Teacher / Student 类型定义 + V4 新增 Widget 配置类型
services/dashboard-service.tsx 110 V4 新增DashboardService 接口 + DashboardAnalytics 接口 + React Context 依赖注入
config/widget-configs.ts 62 V4 新增4 个角色的 Widget 布局配置(配置驱动渲染)
hooks/use-dashboard-preferences.ts 82 L4 新增:自定义仪表盘偏好 HooklocalStorage 持久化)
hooks/use-dashboard-realtime.ts 73 L6 新增SSE 实时更新 Hook指数退避重连
components/dashboard-section.tsx 186 分区 Error Boundary + Suspense + 骨架屏V4 P3-1 新增 ariaLabel prop
components/dashboard-greeting-header.tsx 36 共享问候头部组件V2 抽象V4 支持空用户名 fallback
components/dashboard-time-range-filter.tsx 57 L2 新增时间范围筛选器URL search param 持久化)
components/comparison-badge.tsx 75 L3 新增:数据对比徽章 + computeComparison 纯函数
components/dashboard-notification-widget.tsx 85 L5 新增:通知中心 Widget
components/dashboard-responsive-layout.tsx 72 L8 新增:移动端响应式布局组件
components/parent-dashboard/parent-dashboard.tsx 115 V4 P1-4 迁移家长仪表盘视图slots 组合注入)
components/admin-dashboard/admin-dashboard.tsx 180 管理员仪表盘视图V4 接入时间范围筛选 + ariaLabel
components/admin-dashboard/admin-sections.tsx 245 管理员 6 个分区组件V4 StatCard 下钻 + ContentRow href
components/admin-dashboard/user-growth-chart.tsx 65 recharts 折线图i18n
components/teacher-dashboard/*.tsx 9 文件 教师仪表盘组件V4 修复 teacher-schedule 类型导入 + TeacherTodoCard 排序优化 + ariaLabel
components/student-dashboard/*.tsx 6 文件 学生仪表盘组件V4 抽取纯函数 + ariaLabel
tests/integration/dashboard/dashboard-utils.test.ts 408 6 个纯函数的 31 个单元测试V2 新增)

2.13 messaging私信模块

职责站内私信messages 表 CRUD+ 消息草稿message_drafts 表 CRUD

导出函数

  • ActionssendMessageAction 审计 V1-P0-1新增 isReceiverAllowed 二次校验收件人是否在 sender DataScope 内 + 审计 V1-P0-6校验 parentMessageId 属于当前用户会话 + 审计 V1-P1-9失败埋点 message.send_failed/ markMessageAsReadAction / deleteMessageAction / getMessagesAction 审计 V1参数新增 starredOnly?: boolean/ getRecipientsAction / getUnreadMessageCountAction P1-4 已修复:通知 CRUD Action 已迁移至 notifications 模块; V3-P1-3 已修复:通知偏好 Action 已迁移至 notifications 模块messaging 仅保留私信 Action V2-P2-13c 新增:toggleMessageStarAction 星标切换 / getMessageDraftsAction 草稿列表 / saveMessageDraftAction 草稿保存( V3-P0-1使用 MessageDraftSchema Zod 校验替代 as 断言)/ deleteMessageDraftAction 草稿删除; 审计 V1-P0-4 已删除:getMessageDetailActionV2-P1-3 重构遗留死代码,详情数据由 RSC 通过 getMessageDetailPageData 直接获取); 审计 V1-P1-3 新增:getMessageThreadAction 获取消息线程(替代组件直接调用 data-access统一权限边界
  • Data-accessgetMessages V2-P2-13c 新增 starredOnly 星标筛选)/ getMessageById / getMessageThread 审计 V1-P0-2签名变更为 (messageId, userId),内部校验当前用户对根消息的访问权)/ createMessage / markMessageAsRead / deleteMessage / toggleMessageStar V2-P2-13c 新增:接收方星标切换)/ getUnreadMessageCount / getRecipients 审计 V1-P1-6返回 role 收紧为 RecipientRole 类型admin 分支补 role/ isReceiverAllowed 审计 V1-P0-1 新增:校验收件人是否在 sender DataScope 内)/ getMessageDetailPageData V2-P1-3 新增:消息详情页编排函数,获取详情并自动标记已读; 审计 V1-P0-3getMessagesPageData 已删除,编排迁回 messages/page.tsx 页面层 Promise.all 调用 messaging + notifications data-access保持模块独立性/ getMessageDrafts / createMessageDraft / updateMessageDraft / deleteMessageDraft / getMessageDraftById V2-P2-13c 新增:消息草稿 CRUD操作 message_drafts 表)
  • HooksuseMessageSearch P1-7 新增:消息搜索 hook含防抖和请求竞态取消
  • LibbuildReplyHref 审计 V1-P1-7 新增:构建回复消息 URL 纯函数,使用 URLSearchParams 安全编码,替代组件内字符串拼接)
  • Notification-preferencesre-export shim实际逻辑在 notifications/preferences.ts P0-b 已修复:notification-preferences.ts 文件已删除(通知模块去重),消费方改为直接从 @/modules/notifications/preferences 导入 getNotificationPreferences / upsertNotificationPreferences

依赖关系

  • 依赖:shared/*@/authnotifications P0-4 / P1-5 已修复:通过 sendNotification dispatcher 发送通知,通知 CRUD 和偏好已迁移至 notifications 模块; P1-4 已修复:通知 UI 组件已迁移至 notifications 模块; 审计 V1-P0-3 已修复data-access 不再动态 import 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-intluseTranslations("messages")),新增 src/shared/i18n/messages/{zh-CN,en}/messages.json 翻译字典title/description/tabs/actions/form/status/meta/notificationType/search/empty/messages/error 共 13 个命名空间; 审计 V1-P1-4 新增 pagination / drafts / thread 三个命名空间);所有页面 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 审计 V1-P1-4分页 aria-label i18n 化(替代硬编码 "Previous page" / "Next page"),星标按钮加 aria-pressed
  • P1-4 已修复:通知组件notification-list.tsx / notification-dropdown.tsx放在 messaging/components 下,直接 import notifications 模块类型和 messaging/actions 两个组件已迁移至 notifications/components/,通知 CRUD Action 已迁移至 notifications/actions.tsmessaging 模块仅保留私信组件
  • 审计 V1-P0-3 已修复:getMessagesPageData 在 data-access 层动态 await import("@/modules/notifications/data-access") 跨模块编排 编排迁回 messages/page.tsx 页面层 Promise.all([getMessages(...), getNotifications(...)]),保持 messaging data-access 模块独立性
  • 审计 V1-P0-4 已修复:getMessageDetailAction 死代码V2-P1-3 重构遗留,全代码库无引用) 已彻底删除
  • 审计 V1-P0-1 已修复:sendMessageAction 未校验 receiverId 是否在 sender DataScope 内 新增 isReceiverAllowed(userId, receiverId, scope) data-access 函数Server Action 二次校验,防止越权发送
  • 审计 V1-P0-2 已修复:getMessageThread(messageId) 缺少 userId 参数和权限校验 签名变更为 (messageId, userId)where 子句加 (senderId=userId OR receiverId=userId),纵深防御
  • 审计 V1-P0-5 已修复:MessageCompose 自动保存 effect 在用户快速提交后仍会触发新草稿创建 新增 submittedRef 标志位,提交后跳过自动保存,失败时重置
  • 审计 V1-P0-6 已修复:sendMessageAction 未校验 parentMessageId 属于当前用户会话 新增 getMessageById(parentMessageId, ctx.userId) 校验,防止将回复注入他人会话线程
  • 审计 V1-P1-1 已修复:compose 页面无草稿列表 UI 新增 MessageDraftList 组件compose page 通过 getMessageDraftsAction 获取草稿并渲染,支持"继续编辑"和"删除草稿"
  • 审计 V1-P1-2 已修复:MessageList 缺少"星标" Tabdata-access starredOnly 筛选不可达 Tab 类型新增 starred,切换时调用 getMessagesAction({type:"all", starredOnly:true})
  • 审计 V1-P1-3 已修复:MessageDetail 不渲染消息线程,getMessageThread 已实现但 UI 缺失 新增 getMessageThreadAction + 线程渲染区块,时间正序展示回复
  • 审计 V1-P1-6 已修复:RecipientOption.role 为宽松的 string? 新增 RecipientRole = "student" | "teacher" | "admin" | "parent" 类型admin 分支补 role
  • 审计 V1-P1-7 已修复:replyHref 构建逻辑散落在 MessageDetail 组件内URL 参数未安全编码 抽取为 lib/build-reply-href.ts 纯函数,使用 URLSearchParams 编码
  • 审计 V1-P1-9 已修复:sendMessageAction 失败路径无埋点 catch 块调用 trackEvent({event:"message.send_failed"}),权限拒绝除外避免噪音
  • P1-7 已修复:消息列表客户端 useEffect + setTimeout 防抖搜索未取消已发出的请求 搜索逻辑抽离为 useMessageSearch hook含防抖 + 请求竞态取消);无分页 UI 新增客户端分页 UIPAGE_SIZE=20ChevronLeft/ChevronRight 按钮)
  • P1-9 已修复:deleteMessage 两个独立 UPDATE 无事务 改为 db.transaction 包裹 senderDeletedAt 和 receiverDeletedAt 更新,保证原子性
  • P2-11 已修复:发送/删除消息无埋点 sendMessageAction / markMessageAsReadAction / deleteMessageAction 新增 trackEvent 埋点message.sent / message.marked_read / message.deleted
  • V2-P0-2 已修复:通知标题硬编码 sendMessageAction 通过 getTranslations('messages') 生成 i18n 通知标题(notification.messageTitle / messageTitleNoSubject
  • V2-P1-2 已修复:MessageList 客户端过滤冗余 客户端过滤仅在初始数据type=all时执行搜索结果已由服务端按 tab 过滤
  • V2-P1-3 已修复:消息详情页分散编排 新增 getMessageDetailPageData 编排函数,替代 page.tsx 中 after() + getMessageById + markMessageAsRead 的分散编排
  • V2-P1-4 已修复:表单无服务端校验错误展示 message-compose.tsx 新增 fieldErrors 状态 + aria-invalid 字段级错误展示receiverId/subject/content
  • V2-P2-1 已修复:轮询间隔魔法数字 unread-message-badge.tsx 轮询间隔提取为 POLL_INTERVAL_MS 常量(60_000ms → 30_000ms V2-P3 与通知组件保持一致)
  • V2-P2-13c 已实现:消息星标和草稿功能。messages 表新增 isStarred 字段(接收方可标记重要消息)+ messages_receiver_starred_idx 复合索引;新增 message_draftsuserId/receiverId/subject/content/parentMessageId + updatedAt/createdAtmessage_drafts_user_idxmessage_drafts_user_updated_idx 索引data-access 新增 toggleMessageStar / getMessageDrafts / createMessageDraft / updateMessageDraft / deleteMessageDraft / getMessageDraftByIdactions 新增 toggleMessageStarAction / getMessageDraftsAction / saveMessageDraftAction / deleteMessageDraftActiongetMessages 支持 starredOnly 筛选i18n 新增 actions.star/unstar/saveDraft/deleteDraft + messages.draftSaved/draftDeleted/starToggled + empty.noDrafts/noStarred 翻译键;迁移文件 drizzle/0008_message_star_draft.sql
  • V3-P0-1 已修复:saveMessageDraftAction 中 5 处 formData.get("xxx") as string \| null 断言 改为使用 MessageDraftSchema Zod 校验安全转换 FormData 值
  • V3-P1-3 已修复:getNotificationPreferencesAction / updateNotificationPreferencesAction 放在 messaging 模块 已迁移至 notifications/actions.tsmessaging 仅保留私信 Action
  • V3-P1-6 已修复:getMessageDraftsActiongetMessagesPageData 使用内联 import("./types").XXX 类型导入 改为顶部 import type 语句
  • V3-P2-7 已修复:message-list.tsxsetTab(v as Tab) 断言 改为 isTab 类型守卫

未实施项P1-5 / P1-8 / P2- 中长期)*

  • P1-5UnreadMessageBadge 接入 SSE 实时推送(需扩展 /api/notifications/stream 端点 payload
  • P1-8 已实施:MessageList 依赖注入化(定义 MessageListService 接口通过 MessageListServiceProvider Context 注入,default-message-list-service.ts 绑定现有 Server Actions
  • P2-1 已实施:批量操作(bulkMarkMessagesAsRead / bulkDeleteMessages / bulkToggleMessagesStar,使用 inArray + 事务)
  • P2-2 已实施群组消息fan-out on write 策略,messages.groupMessageId 列 + sendGroupMessage data-access + sendGroupMessageAction教师→全班家长DataScope 校验)
  • P2-3 已实施:附件支持(复用 fileAttachments 表,targetType="message" + getMessageAttachments data-access + getMessageAttachmentsActionUI 含 MessageAttachmentUploader 上传 + MessageAttachmentList 展示下载)
  • P2-4 已实施:消息撤回(messages.recalledAt 列 + recallMessage data-access2 分钟窗口校验UI 显示"已撤回"占位)
  • P2-5 已实施:举报+屏蔽(新增 message_reports 表 + user_blocks 表,reportMessage / blockUser / unblockUser / isEitherUserBlocked data-accessreportMessageAction / blockUserAction / unblockUserAction / getUserBlocksActionsendMessageAction 集成 isEitherUserBlocked 屏蔽校验)
  • P2-6 已实施:快捷回复模板(message_templates 表 + CRUD data-access + MessageTemplatePicker UI 组件集成到 compose
  • P2-7 已实施:配置驱动的角色组合(RecipientResolver 注册表模式,RECIPIENT_RESOLVERS 按 DataScope.type 索引,新增角色仅新增配置项)
  • P2-8 已实施Error Boundary 细化(列表区/详情区/草稿区分别 SectionErrorBoundary 包裹)
  • P2-9 已实施React Suspense 流式渲染RSC 使用 Suspense 包裹 MessageList + NotificationList骨架屏 fallback
  • P2-10 已实施:跨设备草稿同步(message_drafts.deviceId + version 列 + userParentIdx 索引,updateMessageDraft 带乐观锁返回 "ok" | "not_found" | "conflict"saveMessageDraftAction 处理冲突与 not_found 重建)
  • P2-11 已实施:组合搜索(getMessages 扩展 senderId / dateFrom / dateTo / isRead 参数)
  • P1-9 已实施:失败埋点(message.send_failed / message.group_send_failed / message.report_failed 等)

文件清单

文件 行数 职责
actions.ts ~360 11 个私信 Server Action 审计 V1-P0-4删除 getMessageDetailAction 死代码; 审计 V1-P1-3新增 getMessageThreadAction 审计 V1-P0-1/P0-6/P1-9sendMessageAction 增加 receiverId 二次校验 + parentMessageId 校验 + 失败埋点; V3-P1-3通知偏好 Action 已迁移至 notifications V3-P0-1saveMessageDraftAction 使用 Zod 校验; V2-P0-2通知标题 i18n 化)
data-access.ts ~430 私信 CRUD + 审计 V1-P0-1 新增 isReceiverAllowed + 审计 V1-P0-2 getMessageThread 增加 userId 校验 + 审计 V1-P0-3 删除 getMessagesPageData 编排迁出至页面层 + getMessageDetailPageData 编排函数( P1-5 新增; V3-P1-6内联类型导入改为顶部 import type
schema.ts 40 私信发送校验 + messageId 校验 + 消息草稿校验( V3-P0-1 新增 MessageDraftSchema V3-P1-3UpdateNotificationPreferencesSchema 已迁移至 notifications/schema.ts
types.ts 85 私信类型 + 审计 V1-P1-6 新增 RecipientRole 类型 + re-export 通知类型(向后兼容)
hooks/use-message-search.ts ~60 P1-7 新增:消息搜索 hook防抖 + 请求竞态取消)
lib/build-reply-href.ts ~30 审计 V1-P1-7 新增:构建回复 URL 纯函数URLSearchParams 编码)
lib/type-guards.ts ~40 类型守卫专项重构2026-07-04新增isRecipientRole / isMessageReportReason / isMessageReportStatus 守卫 + toMessageReportReason / toMessageReportStatus 安全窄化函数fallback 默认值),替代 data-access.tsmessage-report-block.tsx 中的 as 断言

组件清单

组件 职责
components/message-list.tsx 消息列表( P1-7使用 useMessageSearch hook + 客户端分页 UIPAGE_SIZE=20 V2-P1-2客户端过滤仅在初始数据时执行 审计 V1-P1-2新增 starred Tab + 调用 getMessagesAction({starredOnly:true}) 审计 V1-P1-4分页 aria-label i18n 化)
components/message-detail.tsx 消息详情(含回复; 审计 V1-P1-3渲染消息线程 审计 V1-P1-7使用 buildReplyHref 纯函数; 审计 V1-P1-4星标按钮加 aria-pressed
components/message-compose.tsx 撰写新消息( V2-P1-4fieldErrors + aria-invalid 字段级错误展示; 审计 V1-P0-5submittedRef 防止提交后自动保存竞态)
components/message-draft-list.tsx 审计 V1-P1-1 新增:草稿列表组件(继续编辑 + 删除草稿 + 乐观更新)
components/unread-message-badge.tsx 未读消息计数徽章(侧边栏,每 30 秒轮询 getUnreadMessageCountAction V2-P2-1POLL_INTERVAL_MS 常量; V2-P3间隔从 60s 缩短为 30s 与通知一致)

客户端行为

  • message-list.tsx:客户端调用 getMessagesAction 搜索消息useMessageSearch hook400ms 防抖请求竞态取消V2-P1-2 优化客户端过滤仅在初始数据type=all时执行 审计 V1-P1-2starred Tab 单独加载 getMessagesAction({type:"all", starredOnly:true})
  • unread-message-badge.tsx:每 POLL_INTERVAL_MS30_000ms轮询 getUnreadMessageCountAction 刷新未读计数
  • message-detail.tsx 审计 V1-P1-3 客户端调用 getMessageThreadAction(messageId) 加载回复链
  • message-compose.tsx 审计 V1-P0-5 submittedRef 标志位阻止发送后自动保存 effect 触发新草稿

架构变更2026-07-05Phase 4.5 — 消息星标改用 useOptimistic + useTransition

  • message-detail.tsx:星标 useStateuseOptimistic<boolean> + useTransitionhandleToggleStarstartStarTransitionaddOptimisticStarred(nextStarred) + await toggleMessageStarAction + router.refresh()。JSX 中所有 isStarred 引用改为 optimisticIsStarredButton variant/aria-pressed/Star className/星标徽章)
  • message-list.tsx:星标 starredOverride useState → useOptimistic<Map<string, boolean>>key=messageId, value=isStarredgetIsStarred 改为 optimisticStarredMap.get(m.id)handleToggleStar 重写为 startStarTransition + addOptimisticStar + await router.refresh()。撤回 recalledOverride 仍保留 useState不需要乐观更新
  • 关键决策:成功后调用 router.refresh() 同步数据源,让 useOptimistic 回滚到最新服务端值不为撤回recall操作使用 useOptimistic因为撤回需要根据服务端返回判断是否成功2 分钟窗口校验),失败时不应乐观显示已撤回

2.14 notifications通知分发模块

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

导出函数

  • ActionssendNotificationAction / sendClassNotificationAction / getNotificationsAction / getUnreadNotificationCountAction / markNotificationAsReadAction / markAllNotificationsAsReadAction / archiveNotificationAction P1-4 新增:后 4 个通知 CRUD Action 从 messaging 模块迁移; V2-P2-13b 新增archiveNotificationAction 归档 Action V3-P1-3 新增:getNotificationPreferencesAction / updateNotificationPreferencesAction 从 messaging 模块迁移)
  • DispatchersendNotification(payload) / sendBatchNotifications(payloads) P3-7sendBatchNotifications 重构为 in_app 单次批量 INSERT其他渠道保持 per-payload 并行)
  • Data-accesscreateNotification / createNotifications(items) P3-7 新增:批量 INSERT 多行)/ getNotifications / markNotificationAsRead / markAllNotificationsAsRead / getUnreadNotificationCount / archiveNotification / unarchiveNotification / getUserContactInfo / logNotificationSend / logNotificationSendBatch P0-4 / P1-5 修复后从 messaging 迁移; V2-P2-13b 新增archiveNotification / unarchiveNotification 归档函数)
  • PreferencesgetNotificationPreferences / upsertNotificationPreferences P0-4 / P1-5 修复后从 messaging 迁移)
  • ChannelsInAppChannelSender / SmsChannelSender / EmailChannelSender / WeChatChannelSender
  • ComponentsNotificationList / NotificationDropdown P1-4 新增:从 messaging/components 迁移; V2-P2-13bNotificationList 支持优先级 Badge 显示和归档操作; V2-P2-13cNotificationList 支持按类型筛选)
  • HooksuseNotificationStream V2-P3 新增SSE 实时推送 + 轮询降级 HookuseDesktopNotifications V2-P2-13c 新增:浏览器桌面推送 Hook支持权限管理和新通知桌面提醒
  • TypesNotificationPriority V2-P2-13b 新增:通知优先级类型 low/normal/high/urgent

依赖关系

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

已知问题

  • P0-4 已修复:不拥有任何数据,全部依赖 messaging 模块 messageNotifications 和 notificationPreferences 表所有权已从 messaging 迁移至 notifications 模块
  • P0 已修复:与 messaging 双向依赖 notifications 不再反向依赖 messagingin-app-channel 改为静态导入本地 createNotification
  • P1-1 已修复:sendClassNotificationAction 直查 classes/classEnrollments 改为调用 classes/data-access.getClassExists / getStudentIdsByClassId
  • P1-4 已修复:通知 UI 组件放在 messaging/components 下,直接 import notifications 类型和 messaging/actions notification-list.tsxnotification-dropdown.tsx 已迁移至 notifications/components/,通知 CRUD Action 已从 messaging 迁移至 notifications/actions.ts,消除 UI 层跨模块耦合
  • P2-11 已修复:通知标记已读无埋点 markNotificationAsReadAction / markAllNotificationsAsReadAction 新增 trackEvent 埋点notification.marked_read / notification.marked_all_read
  • V2-P0-1 已修复:通知 i18n 键混在 messages.json 中 新增独立的 notifications.json 命名空间zh-CN/en通知组件 useTranslations"messages" 切换到 "notifications"src/i18n/request.ts 新增 notifications 命名空间加载
  • V2-P2-1 已修复:轮询间隔魔法数字 notification-dropdown.tsx 轮询间隔提取为 POLL_INTERVAL_MS 常量30_000ms
  • V2-P3 已优化:30 秒轮询 notification-dropdown.tsx 改为 SSE 实时推送(/api/notifications/streamSSE 不可用时自动降级为轮询(调用 Server Actions间隔 30 秒)
  • V2-P2-13b 新增通知优先级和归档功能。schema.ts messageNotifications 表新增 prioritylow/normal/high/urgent默认 normalisArchived(默认 false字段 + 2 个索引types.ts 新增 NotificationPriority 类型data-access.ts 新增 archiveNotification / unarchiveNotification 函数,getNotifications 支持归档和优先级筛选(默认仅返回未归档),createNotification 支持 priorityactions.ts 新增 archiveNotificationAction(含 trackEvent 埋点 notification.archivedNotificationList 组件支持优先级 Badge 显示和归档按钮i18n 新增 priority/actions.archive/messages.archiveFailed 翻译键
  • V2-P2-13c 新增:通知分类筛选 + 浏览器桌面推送。NotificationList 组件新增按类型筛选按钮组all/message/announcement/homework/grade空状态区分"无通知"和"无筛选结果";新增 useDesktopNotifications Hook浏览器 Notification API支持权限管理、自动请求权限、新通知桌面推送、点击跳转NotificationDropdown 集成桌面推送监听新通知并触发桌面提醒首次加载不批量推送i18n 新增 filter.all / empty.noFilterResults / empty.noFilterResultsDesc 翻译键
  • ⚠️ P1发送日志仅 consolenotification_logs
  • 渠道抽象优秀(接口 + 工厂 + Mock 实现)
  • V3-P1-3 已修复:通知偏好 Action 放在 messaging 模块 getNotificationPreferencesAction / updateNotificationPreferencesAction 已迁移至 notifications/actions.ts,新增 notifications/schema.tsUpdateNotificationPreferencesSchema 从 messaging 迁移)
  • V3-P1-4 已修复:sendBatchNotifications 重复日志 移除 sendBatchNotifications 中的重复 logNotificationSendBatch 调用(日志已在 sendNotification 内部记录)
  • V3-P1-5 已修复:腾讯云短信 SmsSdkAppId 使用 templateCode 新增 SMS_SDK_APP_ID 环境变量和 smsSdkAppId 配置字段,SmsSdkAppId 使用独立配置值
  • V3-P2-7 已修复:notification-list.tsxObject.keys(TYPE_ICON) as NotificationType[] 断言 改为 TYPE_KEYS 常量数组
  • V3-P2-8 已标记:logNotificationSendconsole.info / console.error 添加 TODO 注释,预留统一日志服务接入点

文件清单

文件 行数 职责
dispatcher.ts ~225 渠道选择 + 并行分发( V3-P1-4移除 sendBatchNotifications 重复日志; P3-7sendBatchNotifications 重构为 in_app 批量 INSERT + 其他渠道并行)
data-access.ts ~290 站内通知 CRUD + 用户联系方式 + 发送日志持久化notification_logs 表P0-4 / P1-5 修复后新增通知 CRUD V2-P2-13b新增归档函数 + 优先级/归档筛选; V3-P2-8console 日志添加 TODO 统一日志服务标记; P3-7新增 createNotifications 批量 INSERT 函数)
preferences.ts 166 通知偏好 CRUDP0-4 / P1-5 修复后从 messaging 迁移)
actions.ts ~380 9 个 Server Action P1-4新增 4 个通知 CRUD Action V2-P2-13b新增 archiveNotificationAction V3-P1-3新增 getNotificationPreferencesAction / updateNotificationPreferencesAction 从 messaging 迁移)
schema.ts 22 V3-P1-3 新增:通知偏好校验 Schema从 messaging/schema.ts 迁移)
types.ts ~130 通知负载 + 渠道配置 + 通知记录 + 偏好类型P0-4 / P1-5 修复后扩充; V2-P2-13b新增 NotificationPriority 类型 + priority/isArchived 字段)
index.ts ~85 对外导出入口( P1-4新增组件和 CRUD Action 导出; V2-P2-13b新增归档函数/Action/类型导出; V2-P2-13c新增 useNotificationStream / useDesktopNotifications Hook 导出; V3-P1-3新增通知偏好 Action 和 Schema 导出)
channels/* 5 文件 4 个渠道实现
components/notification-list.tsx ~190 P1-4 新增(从 messaging 迁移):通知列表组件; V2-P2-13b支持优先级 Badge 显示和归档操作; V2-P2-13c支持按类型筛选 + 空状态区分
components/notification-dropdown.tsx ~180 P1-4 新增(从 messaging 迁移):通知下拉菜单组件; V2-P3改用 SSE 实时推送 + 轮询降级; V2-P2-13c集成 useDesktopNotifications 桌面推送
hooks/use-notification-stream.ts ~195 V2-P3 新增SSE 实时推送 HookEventSource + 轮询降级)
hooks/use-desktop-notifications.ts ~100 V2-P2-13c 新增:浏览器桌面推送 HookNotification API + 权限管理 + 点击跳转)

组件清单

组件 职责
components/notification-list.tsx 通知列表(消息页底部,展示所有通知,支持标记已读; V2-P0-1useTranslations 命名空间从 "messages" 切换到 "notifications" V2-P2-13b支持优先级 Badge 显示和归档操作; V2-P2-13c支持按类型筛选 + 空状态区分"无通知"/"无筛选结果"
components/notification-dropdown.tsx 通知下拉菜单(站点头部, V2-P3改用 SSE 实时推送 /api/notifications/stream + 轮询降级; V2-P0-1useTranslations 命名空间切换; V2-P2-1POLL_INTERVAL_MS 常量; V2-P2-13c集成 useDesktopNotifications 桌面推送,监听新通知并触发桌面提醒)

客户端行为

  • notification-dropdown.tsx V2-P3 改用 useNotificationStream Hook 消费 SSE 实时推送(/api/notifications/streamSSE 不可用时自动降级为轮询(调用 getNotificationsAction + getUnreadNotificationCountAction,间隔 30 秒); V2-P2-13c 集成 useDesktopNotifications Hook通过 prevNotificationIds 对比检测新通知并触发桌面推送(首次加载不批量推送)
  • hooks/use-notification-stream.ts V2-P3 新增,管理 SSE 连接生命周期EventSource onopen/onmessage/onerror降级时通过 pollFnRef 调用 Server Actions 轮询
  • hooks/use-desktop-notifications.ts V2-P2-13c 新增,封装浏览器 Notification API权限管理、自动请求权限、桌面推送、点击跳转回调SSR 安全(typeof window !== "undefined" 检查)

2.15 audit审计模块

职责:操作日志 + 登录日志 + 数据变更日志查询与导出 + 审计概览仪表盘 + 数据保留策略(含 i18n + Error Boundary + 骨架屏 + 埋点 + Service 接口抽象)。

导出函数

  • ActionsgetDataChangeLogsAction / exportAuditLogsAction / exportLoginLogsAction / exportDataChangeLogsAction / getAuditRetentionConfigAction / saveAuditRetentionConfigAction / purgeAuditLogsAction 审计重构:导出逻辑下沉到 export.tsactions 仅编排;新增 trackEvent 埋点 audit.exported P2-5 新增保留策略 3 个 Action v2 修正:保留策略操作 event 改为 audit.retention v2 新增 revalidatePath 精确刷新缓存)
  • Data-accessgetAuditLogs / getLoginLogs / getDataChangeLogs / getAuditModuleOptions / getDataChangeStats / getDataChangeTableOptions / getAuditLogsForExport / getLoginLogsForExport / getDataChangeLogsForExport / getAuditOverviewStats / getAuditTrend / getDataChangeActionStats P2-4 新增概览统计 3 个函数)
  • Export 审计重构新增):buildAuditLogExport / buildLoginLogExport / buildDataChangeLogExportExcel 列定义 + 行映射纯函数,从 actions 抽取)
  • Retention P2-5 新增):getAuditRetentionConfig / saveAuditRetentionConfig / purgeExpiredAuditLogs / calculateRetentionThreshold / isValidRetentionDays(保留策略配置读写 + 清理 + 纯函数)
  • Hooks 审计重构新增):useLogPagination(分页 URL 状态管理,三处重复逻辑抽取)
  • Services P2-8 新增, v2 接线完成):AuditService 接口 + AuditServiceProvider Context + useAuditService / useAuditAnalytics hook + adminAuditService 默认实现v2 改造为 Client-safe查询方法抛错强制走 RSC props写操作委托 Server Action+ mockAuditService 测试用实现v2 修正:消除 9 处 as 断言,为所有方法添加显式返回类型标注)

依赖关系

  • 依赖:shared/*@/authnext-intli18nshared/lib/track-event(埋点)、settings(保留策略配置存储于 system_settingsaudit_retention 分类)
  • 被依赖:无

已知问题

  • 审计重构 P0 已修复:Excel 导出逻辑内联在 actions 层 已抽取到 export.ts144 行actions.ts 从 212 行精简到 139 行
  • 审计重构 P0 已修复:三个导出 Action 结构高度重复 列定义 + 行映射统一到 export.ts 的 buildXxxExport 函数
  • 审计重构 P0 已修复:全模块零 i18n 所有组件接入 useTranslations("audit") + useLocale()formatDate 从硬编码 zh-CN 改为 locale 变量;新增 src/shared/i18n/messages/{zh-CN,en}/audit.json(含 table/filter/empty/export/error/detail/overview/retention 命名空间)
  • 审计重构 P0 已修复:缺 Error Boundary + 骨架屏 新增 AuditErrorBoundaryclass 组件,捕获错误展示 EmptyState + 重试按钮4 个页面新增 loading.tsx(骨架屏)+ error.tsx(错误边界)
  • 审计重构 P1 已修复:DataChangeLogTable 281 行过大 拆分为 data-change-log-table.tsx146 行,仅表格)+ data-change-log-filters.tsx138 行,独立筛选器)+ data-change-log-view.tsx53 行,视图组合)
  • 审计重构 P1 已修复:三处分页逻辑重复 抽取 useLogPagination hook27 行)
  • 审计重构 P1 已修复:导出无埋点 exportAuditLogsAction / exportLoginLogsAction / exportDataChangeLogsAction 新增 trackEvent 埋点(audit.exported
  • 审计重构 P1 已修复a11y 改进,筛选器添加 aria-labelaria-expanded 属性
  • 审计重构 P1 已修复:api/export/route.ts 中 as Record<string, string> 类型断言 改为 for 循环 + 类型守卫
  • P2 已修复:data-access.ts 中 6 处 catch 块添加 console.error 输出错误上下文;toIso/clampPageSize/clampPage 工具函数补齐显式返回类型
  • P2-1 已修复:data-access 错误吞没 所有 catch 块改为 throw error不再返回空数组UI 通过 ErrorBoundary 捕获
  • P2-2 已修复:无日志详情视图 新增 AuditLogDetailDialogdiscriminated union 模式,支持 audit/login/dataChange 三种类型)
  • P2-3 已修复:无用户维度筛选 三个筛选器均新增 userId Input + URL 状态管理
  • P2-4 已修复:无审计概览仪表盘 新增 /admin/audit-logs/overview 页面(统计卡片 + 趋势图 + 分布图 + 快捷入口 + 保留策略配置)
  • P2-5 已修复:无数据保留策略 新增 retention.ts(配置读写 + 清理纯函数)+ 3 个 Server Action + cron API 路由 + AuditRetentionSettings 组件
  • audit-P2-5auth 审计)已修复:login_logsaudit_logs/data_change_logs 共用同一保留期180 天),无法为登录日志配置更长的安全取证保留期 新增独立 loginLogRetentionDays 配置项(DEFAULT_LOGIN_LOG_RETENTION_DAYS = 365AuditRetentionConfig 类型新增 loginLogRetentionDays 字段;getAuditRetentionConfig / saveAuditRetentionConfig 读写新配置项;purgeExpiredAuditLogs(retentionDays, loginLogRetentionDays?) 支持为 login_logs 传入独立保留期(向后兼容,第二参数可选);purgeAuditLogsAction / AuditService.purgeExpiredAuditLogs 接口同步更新;AuditRetentionSettings 组件新增登录日志保留天数 Input新增 vercel.json 配置 cron schedule 0 3 * * *(每日凌晨 3 点触发 /api/cron/audit-cleanupenv.mjs 新增 CRON_SECRET 可选环境变量校验
  • P2-6 已修复:无单测 新增 retention.test.ts9 个测试)+ export.test.ts13 个测试),全部通过
  • P2-8 已修复:无 Service 接口抽象 新增 services/audit-service.tsx(接口 + Context + hook+ admin-audit-service.ts(默认实现)+ mock-audit-service.ts(测试用)
  • v2 P0-1 已修复:Service 抽象层定义但从未接线(死代码) admin-audit-service.ts 改造为 Client-safe移除 server-only + data-access 依赖;查询方法抛错强制走 RSC props写操作委托 Server Actionoverview/page.tsx 注入 AuditServiceProviderAuditRetentionSettingsAuditLogExportButton 改用 useAuditService() + useAuditAnalytics() hook
  • v2 P0-2 已修复:mock-audit-service.ts 使用 9 处 as 类型断言违规 为所有方法对象字面量添加显式返回类型标注 Promise<T>,消除全部 as 断言
  • v2 P0-3 已修复:保留策略 Server Action 无 revalidatePath saveAuditRetentionConfigActionpurgeAuditLogsAction 末尾新增 revalidatePath("/admin/audit-logs/overview") 精确刷新缓存
  • v2 P1-1 已修复:trackEvent event 名称与业务不匹配(保留策略操作用了 audit.exported 改为 audit.retention(同步更新 actions.ts + api/cron/audit-cleanup/route.ts + shared/lib/track-event.ts EventName 联合类型新增 audit.retention
  • v2 P1-2 已修复:AuditErrorBoundary 使用 namespace="common" 不符合模块约定 改为 namespace="audit" + 新增 error.boundaryTitle / error.boundaryDescription 翻译键
  • v2 P1-3 已修复:AuditLogExportButtonAuditRetentionSettings 未接入埋点 hook AuditLogExportButton 调用 analytics.trackExport(exportType, 0)AuditRetentionSettings 调用 analytics.trackRetentionConfigChange(config) + analytics.trackPurge(result)
  • v2 P1-4 已修复:getAuditModuleOptions / getDataChangeTableOptions catch 块吞错误返回空数组 改为 throw errorUI 通过 ErrorBoundary 捕获
  • v2 P1-5 已修复:日期筛选器两个 Input 共用 aria-label={t("table.time")} 无法区分起止 改为 aria-label={t("filter.startDate")}aria-label={t("filter.endDate")}(同步更新三个 filters 组件)
  • v2 P1-6 已修复:AuditLogDetailDialogDialogDescription 与 DialogTitle 重复 改为 t("detail.description") 并新增对应翻译键
  • v2 P1-7 已修复:骨架屏缺少 aria-hidden 屏幕阅读器会朗读骨架 audit-log-table-skeleton.tsx 根 div 与 4 个 loading.tsx 添加 aria-hidden="true" role="presentation"
  • data-access 职责清晰,无跨模块问题

文件清单

文件 行数 职责
actions.ts 210 7 个 Server Action编排层3 导出 + 3 保留策略 + 1 查询; v2 新增 revalidatePath + audit.retention 埋点; audit-P2-5 purgeAuditLogsAction 新增 loginLogRetentionDays 参数 + trackEvent 埋点)
data-access.ts 387 日志查询12 个函数3 查询 + 3 导出 + 3 选项/统计 + 3 概览统计; v2 P1-4 错误处理统一为 throw
export.ts 144 Excel 导出纯函数(列定义 + 行映射 + buildXxxExport
retention.ts 181 保留策略(配置读写 + 清理 + 纯函数, P2-5 新增; audit-P2-5 新增 DEFAULT_LOGIN_LOG_RETENTION_DAYS=365 + loginLogRetentionDays 配置 + purgeExpiredAuditLogs 第二可选参数)
types.ts 167 类型定义(含概览统计 + 保留策略类型, P2-4/P2-5 扩展; audit-P2-5 AuditRetentionConfig 新增 loginLogRetentionDays 字段)
hooks/use-log-pagination.ts 27 分页 URL 状态管理 hook
services/audit-service.tsx 124 Service 接口 + Context Provider + hook P2-8 新增)
services/admin-audit-service.ts 82 管理员默认实现( v2 P0-1 改造为 Client-safe查询抛错 + 写操作委托 Server Action
services/mock-audit-service.ts 70 测试用 Mock 实现( v2 P0-2 消除 9 处 as 断言)
retention.test.ts 85 保留策略纯函数单测9 个测试, P2-6 新增)
export.test.ts 175 导出映射纯函数单测13 个测试, P2-6 新增)

组件清单

组件 行数 职责
components/audit-error-boundary.tsx 19 审计模块错误边界class 组件, v2 P1-2 namespace 改为 audit
components/audit-log-table-skeleton.tsx 42 骨架屏( v2 P1-7 添加 aria-hidden
components/audit-log-table.tsx 115 操作日志表格i18n + a11y + 详情对话框)
components/audit-log-filters.tsx 97 操作日志筛选器i18n + a11y + userId 筛选; v2 P1-5 日期 aria-label 区分起止)
components/audit-log-view.tsx 51 操作日志视图(筛选+表格+分页)
components/audit-log-export-button.tsx 76 导出按钮i18n v2 P1-3 接入 useAuditAnalytics().trackExport
components/audit-log-detail-dialog.tsx 132 日志详情对话框discriminated union P2-2 新增; v2 P1-6 修复 DialogDescription 重复标题)
components/login-log-table.tsx 109 登录日志表格i18n + a11y + 详情对话框)
components/login-log-filters.tsx 83 登录日志筛选器i18n + a11y + userId 筛选; v2 P1-5 日期 aria-label 区分起止)
components/login-log-view.tsx 49 登录日志视图
components/data-change-log-table.tsx 150 数据变更日志表格i18n + a11y + 详情对话框)
components/data-change-log-filters.tsx 139 数据变更日志筛选器i18n + a11y + userId 筛选; v2 P1-5 日期 aria-label 区分起止)
components/data-change-log-view.tsx 53 数据变更日志视图
components/audit-overview-stats-bar.tsx 52 概览统计卡片栏( P2-4 新增)
components/audit-activity-trend-chart.tsx 67 活动趋势折线图( P2-4 新增)
components/data-change-distribution-chart.tsx 72 数据变更分布柱状图( P2-4 新增)
components/audit-overview-view.tsx 113 概览页视图(统计+图表+快捷入口+保留配置, P2-4 新增)
components/audit-retention-settings.tsx 179 保留策略配置组件( P2-5 新增; v2 P0-1 + P1-3 改用 useAuditService + useAuditAnalytics

页面路由

  • /admin/audit-logsrequirePermission(AUDIT_LOG_READ) + AuditErrorBoundary 包裹
  • /admin/audit-logs/login-logsrequirePermission(AUDIT_LOG_READ) + AuditErrorBoundary 包裹
  • /admin/audit-logs/data-changesrequirePermission(AUDIT_LOG_READ) + AuditErrorBoundary 包裹
  • /admin/audit-logs/overviewrequirePermission(AUDIT_LOG_READ) + AuditErrorBoundary 包裹( P2-4 新增)
  • /api/cron/audit-cleanup → CRON_SECRET 鉴权( P2-5 新增)
  • 4 个页面均新增 loading.tsx(骨架屏)+ error.tsx(错误边界)

2.16 announcements公告模块

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

导出函数

  • ActionsgetAnnouncementsAction / createAnnouncementAction / updateAnnouncementAction / deleteAnnouncementAction / publishAnnouncementAction / archiveAnnouncementAction P1 已修复actions 层不再直接访问 DB全部下沉到 data-access 发布公告时触发通知模块 sendBatchNotifications/ toggleAnnouncementPinAction / markAnnouncementAsReadAction / getAnnouncementReadStatusAction V2-P2-13d 新增:置顶切换、已读标记、批量已读状态查询; Audit-P0-3toggleAnnouncementPinAction / markAnnouncementAsReadAction 新增资源所有权/可见性二次校验; Audit-P1-1所有 Action 返回 i18n 化 message Audit-P1-7handleActionError 集成 console.error + trackEvent("announcement.action_error") + 区分 PermissionDeniedError / FORBIDDEN_ANNOUNCEMENT
  • Data-accessgetAnnouncements Audit-P0-2audience 字段从单值升级为数组 { gradeIds, classIds },过滤逻辑改用 inArray/ getAnnouncementById / getAnnouncementByIdForUser Audit-P0-1 新增:用户端详情页专用,结合 status=published + 受众过滤 + 已读状态 + readCount 一次返回,防止越权)/ isAnnouncementVisibleToAudience Audit-P0-1 新增:纯函数,判断公告对受众是否可见,可独立单测)/ resolveUserAudience Audit-P0-2从原 resolveAudience 重命名,遍历所有 childrenIds/classIds/gradeIds 而非仅首个)/ resolveAnnouncementTargetUserIds Audit-P1-5从 actions.ts 下沉到 data-access P3-7school 分支改为分页循环遍历 getAllUserIds(PAGE_SIZE, offset)PAGE_SIZE=1000避免单次查询超大学校全量用户导致 OOM/ insertAnnouncement / updateAnnouncementById / deleteAnnouncementById / publishAnnouncementById / archiveAnnouncementById(后 5 个为 P1-2 新增)/ toggleAnnouncementPin Audit-P1-6原子化 UPDATE ... SET isPinned = NOT isPinned/ markAnnouncementAsRead Audit-P1-6onDuplicateKeyUpdate 实现幂等 upsert/ isAnnouncementReadByUser / getAnnouncementReadCount / getAnnouncementReadStatusForUser V2-P2-13d 新增:置顶切换 + 已读回执 CRUD/ getAdminAnnouncementsPageData / getEditAnnouncementPageData P1-5 新增:管理端列表页和编辑页编排函数)/ getUserAnnouncementsPageData V3-P0-2 新增:用户端列表页编排函数)/ getAdminAnnouncementDetailPageData Audit-P1-2 新增:管理端详情页编排函数,并行 getAnnouncementById + getAnnouncementReadCount + classes.getClassOptions

依赖关系

  • 依赖:shared/*@/authschool(获取年级列表)、classes(获取班级列表 + 解析受众)、users(获取目标用户 ID 列表)、notifications(发布公告时发送通知)、shared/i18n Audit-P1-1Actions 通过 getTranslations('announcements') 返回 i18n 化 message
  • 被依赖:无

已知问题

  • P1-2 已修复:所有写操作直接在 actions 层 db.insert/update/delete,未下沉到 data-access 写操作已下沉到 data-access5 个新函数)
  • P2 已修复:getAnnouncementsAction 使用 requireAuth() 而非 requirePermission(ANNOUNCEMENT_READ) 改为 requirePermission(Permissions.ANNOUNCEMENT_READ)
  • 已修复:用户端列表页传入 audience 受众过滤school/grade/class管理端返回所有公告
  • 已修复:用户端新增公告详情页 /announcements/[id](只读模式)
  • 已修复:管理端列表页传递 classes 数据给 AdminAnnouncementsView
  • 已修复:发布公告时(publishAnnouncementAction / createAnnouncementAction 直接发布 / updateAnnouncementAction 状态变为 published触发通知模块 sendBatchNotifications
  • 已修复:新增 loading.tsx 骨架屏(用户端 + 管理端)
  • P1 已修复:全模块零 i18n中英文案硬编码 所有组件接入 next-intluseTranslations("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 新增 getAdminAnnouncementsPageDatagetEditAnnouncementPageData 编排函数,页面层仅调用单一函数
  • P1-6 已修复:targetGradeId / targetClassId 为 optional未根据 type 做条件必填校验 CreateAnnouncementSchemaUpdateAnnouncementSchema 添加 superRefine(refineAudience),年级公告强制 targetGradeId,班级公告强制 targetClassId
  • P2-11 已修复:发布/归档/删除公告无埋点 createAnnouncementAction / updateAnnouncementAction / deleteAnnouncementAction / publishAnnouncementAction / archiveAnnouncementAction 新增 trackEvent 埋点announcement.created / announcement.updated / announcement.published / announcement.deleted / announcement.archived
  • V2-P0-2 已修复:通知标题硬编码 createAnnouncementAction / updateAnnouncementAction / publishAnnouncementAction 通过 getTranslations('announcements') 生成 i18n 通知标题(notification.publishedTitle / publishedContent
  • V2-P1-1 已修复:AnnouncementList 客户端 useState/useMemo 过滤 改为纯服务端过滤模式Select 切换仅更新 URL ?status= 触发 RSC 重新渲染
  • V2-P1-4 已修复:表单无服务端校验错误展示 announcement-form.tsx 新增 fieldErrors 状态 + aria-invalid 字段级错误展示title/content/targetGradeId/targetClassId
  • V2-P2-13d 已实现:公告置顶 + 已读回执。announcements 表新增 isPinned 字段(置顶优先排序,getAnnouncements orderBy 改为 desc(isPinned), desc(createdAt));新增 announcement_reads 表(唯一索引 announcementId+userId 保证幂等);新增 3 个 Server ActiontoggleAnnouncementPinAction / markAnnouncementAsReadAction / getAnnouncementReadStatusAction+ 5 个 data-access 函数(toggleAnnouncementPin / markAnnouncementAsRead / isAnnouncementReadByUser / getAnnouncementReadCount / getAnnouncementReadStatusForUserAnnouncement 类型新增 isPinned / isReadByCurrentUser? / readCount? 字段;新增 AnnouncementRead 类型;埋点新增 announcement.pin_toggled / announcement.marked_read
  • V3-P0-2 已修复:用户端公告列表页 resolveAudience 50 行业务逻辑在路由层 新增 getUserAnnouncementsPageData 编排函数(含 resolveAudience 内部函数),将受众解析逻辑从 page.tsx 下沉到 data-access.ts,页面层仅调用单一函数
  • Audit-P0-1 已修复:用户端详情页越权访问草稿/他人班级公告 新增 getAnnouncementByIdForUser,结合 status="published" + 受众过滤(isAnnouncementVisibleToAudience 纯函数)+ 已读状态 + readCount 一次返回,markAnnouncementAsReadAction 调用此函数做资源可见性二次校验,不可见时抛 FORBIDDEN_ANNOUNCEMENT
  • Audit-P0-2 已修复:resolveAudience 仅取首个 child/class/grade 导致家长多孩子/教师多班级时只看到首个受众公告 audience 类型升级为 { gradeIds: string[]; classIds: string[] }resolveAudience 重命名为 resolveUserAudience 遍历全部;getAnnouncements 过滤逻辑改用 inArray 支持数组
  • Audit-P0-3 已修复:toggleAnnouncementPinAction / markAnnouncementAsReadAction 缺资源所有权二次校验 前者新增资源存在性校验(不存在返回 404后者调用 getAnnouncementByIdForUser 做可见性二次校验
  • Audit-P1-1 已修复:Actions 返回英文硬编码 message 全部改用 t("messages.created") / t("messages.updated") / t("messages.deleted") / t("messages.published") / t("messages.archived") / t("messages.pinned") / t("messages.unpinned") / t("messages.markedRead") / t("messages.invalidFormData") / t("messages.unexpectedError") / t("messages.permissionDenied") / t("messages.forbidden")
  • Audit-P1-2 已修复:管理端 /admin/announcements/[id] 原为编辑表单页,缺独立详情页 拆分为详情页(AnnouncementDetail canManage=true,调用 getAdminAnnouncementDetailPageData 含 readCount 统计)+ /edit 子路由(编辑表单);用户端 AnnouncementList 通过 service.getReadStatus 批量获取已读状态传入卡片做已读/未读视觉区分
  • Audit-P1-3 已修复:组件直接 import actions 耦合 引入 AnnouncementsServiceContext 依赖注入(AnnouncementsServiceProvider + useAnnouncementsService() + DefaultAnnouncementsService 默认实现代理到真实 Server Actions所有交互型组件Card/Detail/Form/List通过 Hook 消费,便于测试 mock 和未来角色差异化实现
  • Audit-P1-4 已修复:AnnouncementForm 硬编码 router.push("/admin/announcements") 路由 新增 onSuccess / onCancel 回调优先于 successHref / cancelHref 兜底,便于在 Dialog 中复用
  • Audit-P1-5 已修复:resolveTargetUserIds 业务逻辑位于 actions 层 下沉到 data-access resolveAnnouncementTargetUserIds
  • Audit-P1-6 已修复:toggleAnnouncementPin read-then-write 竞态 原子化 UPDATE ... SET isPinned = NOT isPinnedmarkAnnouncementAsRead select-then-insert 双查询 改用 onDuplicateKeyUpdate 幂等 upsert
  • Audit-P1-7 已修复:handleActionError 缺日志/埋点/i18n 重写为接收 t 函数,集成 console.error + trackEvent("announcement.action_error") + 区分 PermissionDeniedError / FORBIDDEN_ANNOUNCEMENT / 其他错误
  • Audit-P1-8 已修复:表单与 Dialog 行为耦合(保存后路由跳转关闭不了 Dialog AdminAnnouncementsView Dialog 内表单通过 onSuccess={handleFormSuccess} 关闭弹窗
  • Audit-P2-1 已修复:a11y置顶按钮嵌套在 Link 内导致键盘导航异常 改为绝对定位 + onKeyDown 处理 Enter/Space脱离外层 Link 语义
  • Audit-P2-2 已修复:mapRow 类型不安全 改用 typeof announcements.$inferSelect 推导
  • Audit-P2-3 已修复:i18n 死键与缺键 清理 description.detail / description.create 等死键,补 messages.invalidFormData / messages.unexpectedError / messages.permissionDenied / messages.forbidden
  • Audit-P2-4 已修复:AnnouncementList.detailHrefBuilder 死 prop 删除(已由 V3 的 detailHrefPrefix 替代,本次彻底清理)
  • Audit-P2-5 已修复:两处 loading.tsx 重复骨架屏 JSX 抽取 AnnouncementListSkeleton 共享组件,支持 showCreateButton prop
  • Audit-P2-6 已修复:用户端公告列表无分页 UI 新增 AnnouncementPagination 组件URL ?page=N 持久化、a11y、窗口策略、边界处理新增 countAnnouncements data-access 函数;getUserAnnouncementsPageData 升级为接收 page/pageSize 并返回 PaginatedAnnouncements 类型 { items, total, page, pageSize },内部并行执行列表查询与 count 查询;/announcements/page.tsx 读取 ?page= 查询参数并传入分页数据;新增 i18n 键 pagination.label / prev / next / page
  • Audit-P2-7 已修复:模块零测试覆盖 新增 3 个测试文件:schema.test.ts18 个测试,覆盖 refineAudience 条件校验矩阵school/grade/class 类型与 target 必填组合 + transform 归一化);is-announcement-visible.test.ts15 个测试,覆盖 isAnnouncementVisibleToAudience 纯函数,含 P0-2 多孩子/多班级场景);components/announcement-card.test.tsx16 个测试覆盖基础渲染、isRead 视觉区分、isPinned 交互、a11y 键盘导航 Enter/Space、toggling 禁用、错误回滚)。同时修复项目级 @testing-library/dom peer dependency 缺失(已影响 dashboard-section.test.tsx 等现有测试)

文件清单

文件 行数 职责
actions.ts ~400 9 个 Server Action + 通知触发逻辑 + trackEvent 埋点P1-2 已修复,无直接 DB 操作V2-P2-13d 新增置顶/已读/批量已读状态 3 个 ActionAudit-P0-3 新增资源所有权二次校验Audit-P1-1 i18n 化 messageAudit-P1-7 handleActionError 集成日志/埋点/i18n
data-access.ts ~580 公告 CRUD + 发布/归档 + 受众过滤 + getAdminAnnouncementsPageData / getEditAnnouncementPageData / getUserAnnouncementsPageData 编排函数 + getAdminAnnouncementDetailPageDataAudit-P1-2 新增)+ countAnnouncementsAudit-P2-6 新增V2-P2-13d 置顶切换 + 已读回执 5 个函数Audit-P0-1 新增 getAnnouncementByIdForUser / isAnnouncementVisibleToAudienceAudit-P0-2 resolveAudienceresolveUserAudience 遍历全部受众Audit-P1-5 resolveAnnouncementTargetUserIds 下沉Audit-P1-6 toggleAnnouncementPin / markAnnouncementAsRead 原子化Audit-P2-2 mapRow 改用 $inferSelect 推导
schema.ts ~95 Zod 校验 + refineAudience 条件校验( P1-6 新增 superRefine
types.ts ~130 类型定义(GetAnnouncementsParams.audience 升级为数组 Audit-P0-2新增 UserAudience Audit-P0-2新增 AnnouncementsService 接口 Audit-P1-3新增 PaginatedAnnouncements Audit-P2-6
schema.test.ts ~200 Audit-P2-7 新增18 个测试,覆盖 refineAudience 条件校验矩阵 + transform 归一化
is-announcement-visible.test.ts ~180 Audit-P2-7 新增15 个测试,覆盖 isAnnouncementVisibleToAudience 纯函数(含 P0-2 多孩子/多班级场景)
components/announcements-service-context.tsx ~40 Audit-P1-3 新增:AnnouncementsServiceProvider + useAnnouncementsService()
components/default-announcements-service.ts ~60 Audit-P1-3 新增:AnnouncementsService 默认实现,代理到真实 Server Actions
components/announcement-list-skeleton.tsx ~40 Audit-P2-5 新增:共享骨架屏,支持 showCreateButton prop
components/announcement-pagination.tsx ~100 Audit-P2-6 新增分页组件URL ?page=N 持久化a11y + 窗口策略 + 边界处理
components/announcement-card.test.tsx ~300 Audit-P2-7 新增16 个测试覆盖基础渲染、isRead 视觉、isPinned 交互、a11y 键盘导航、toggling 禁用、错误回滚

组件清单

组件 职责
components/announcement-list.tsx 公告列表(用户端,支持状态筛选; V2-P1-1纯服务端过滤Select 切换更新 URL ?status= 触发 RSC 重新渲染; V3新增 detailHrefPrefix prop 替代 detailHrefBuilder 函数 prop Audit-P1-2用户端通过 service.getReadStatus 批量获取已读状态传入卡片; Audit-P2-4删除死 prop detailHrefBuilder Audit-P2-6新增 pagination prop传入时在列表底部渲染 AnnouncementPagination
components/announcement-card.tsx 公告卡片(列表项; Audit-P1-3通过 useAnnouncementsService() 消费 togglePin Audit-P2-1置顶按钮绝对定位 + onKeyDown 处理; Audit-P1-2新增 isRead prop 已读/未读视觉区分)
components/announcement-detail.tsx 公告详情(只读; Audit-P1-3所有 Action 调用改用 useAnnouncementsService()
components/announcement-form.tsx 公告表单(创建/编辑, P1-6条件校验由 schema superRefine 保证; V2-P1-4fieldErrors + aria-invalid Audit-P1-3通过 useAnnouncementsService() 消费 create/update Audit-P1-4/P1-8新增 onSuccess / onCancel 回调优先于 successHref / cancelHref 兜底)
components/admin-announcements-view.tsx 管理端公告视图(列表 + 筛选; Audit-P1-3包裹 AnnouncementsServiceProvider Audit-P1-8Dialog 内表单通过 onSuccess 关闭弹窗)
components/announcement-pagination.tsx Audit-P2-6 新增分页导航URL 持久化、a11y、窗口策略、边界处理

2.17 files文件模块

职责:文件附件 CRUD + 批量删除 + 统计 + 上传/预览 UI。

导出函数

  • Actions 2026-06-25 新增 actions.ts 编排层P0 修复):uploadFileActionFILE_UPLOAD 权限 + Zod 校验 + storageProvider.save + trackEvent + logAudit/ getFileActionFILE_READ 权限 + ownership 检查:非 FILE_DELETE 持有者只能读自己上传的)/ deleteFileActionFILE_DELETE 权限 + storageProvider.delete/ batchDeleteFilesActionFILE_DELETE 权限 + Zod 校验 max 100 ids/ getFileListActionFILE_READ 权限)/ getFileStatsActionFILE_READ 权限)
  • Data-accesscreateFileAttachment / getFileAttachmentcache/ getFileAttachmentsByTargetcache/ getFileAttachmentsByUploadercache/ getAllFileAttachmentscache/ deleteFileAttachment / deleteFileAttachments(批量)/ getFileAttachmentsWithFilterscache/ getFileStatscache/ getFileByUrlcache 2026-06-25 新增:按 url 字段查询)/ getFileAttachmentsByIdscache P2 已修复7 个读函数使用 React.cache() 包装实现请求级 memoization
  • Types 2026-06-25 更新):FileTargetType 新增 "user_avatar" 枚举值,现为 "exam" | "textbook" | "question" | "announcement" | "homework" | "user_avatar"用于用户头像上传场景targetId = users.idFileUploadResult / FileAttachment / CreateFileAttachmentInput / FileAttachmentQueryParams / FileStats / BatchDeleteResult
  • Schema 2026-06-25 新增 schema.tsUploadMetadataSchema / BatchDeleteSchemamax 100 ids/ FileListQuerySchema / FileTargetTypeSchema / validateFileSize
  • ComponentsFileUpload / FilePreview / FilePreviewDialog 2026-06-25 补录)/ FileIcon / AdminFilesView
  • Hooks 2026-06-25 新增 hooks/ 目录):useFileUploadXHR 上传、进度、状态机、校验)/ useFileBatchOperations(客户端筛选、选择、批量删除)/ useFilePreview + useImageZoom(图片缩放)

依赖关系

  • 依赖:shared/*@/auth
  • 被依赖:app/api/upload / app/api/files/[id] / app/api/files/batch-delete 2026-06-25 重构3 个 API 路由改为委托 actions.ts,仅保留速率限制和请求解析)/ homework 2026-06-24homework/actions.deleteScanAction 调用 deleteFileAttachmenthomework/components/scan-uploader 调用 /api/uploadtargetType="homework"/ settings 2026-06-25 新增:settings/actions-avatar.ts 通过 storageProvider 抽象删除旧头像文件targetType="user_avatar"settings/components/avatar-upload.tsx 上传头像走 /api/upload

已知问题

  • P0 已修复:actions.tsdata-access 被路由直接调用 新建 actions.ts 编排层,所有 API 路由upload/files/[id]/files/batch-delete改为委托 actions路由层仅保留速率限制
  • P0 已修复:app/api/upload/route.tsrequireAuth() 无权限校验 改为 requirePermission(FILE_UPLOAD)
  • P0 已修复:app/api/files/[id]/route.ts 横向越权(任意用户可读/删他人文件) getFileAction 加入 ownership 检查(非 FILE_DELETE 持有者只能读自己上传的)
  • P1 已修复:i18n 缺失,组件硬编码英文 补全 en/files.json + zh-CN/files.json88 行,含 title/upload/list/preview/admin 等命名空间),所有组件接入 useTranslations
  • P1 已修复:缺少 loading.tsx 骨架屏和 error.tsx 错误边界 新增 app/(dashboard)/admin/files/loading.tsx + error.tsx
  • P1 已修复:组件逻辑与 UI 耦合,不可单测 抽取 3 个自定义 hooksuseFileUpload / useFileBatchOperations / useFilePreview
  • P1 已修复:settings/actions-avatar.ts 直接 import fs/promises 绕过 storageProvider 抽象 改用 storageProvider.delete
  • P1 已修复:FileTargetType"user_avatar" 枚举值导致 settings 头像上传 targetType 未注册 已加入枚举
  • P2 已修复:file-list.tsx 125 行死代码 已删除
  • P2 已修复:所有函数 try-catch 吞错误返回空数组/null 所有 catch 块已添加 console.error 输出错误上下文
  • P2 已修复:getFileAttachmentsWithFiltersor(...)! 非空断言清理为安全守卫
  • P2 已修复:getFileAttachmentsWithFiltersconditions 隐式 any[] 改为显式 SQL[] 类型标注
  • P2 已修复:admin-files-view.tsxt(filter.${key} as never) 类型断言 改为 renderTypeLabel 辅助函数 + 字面量联合类型 + switch
  • P2 已修复:file-preview-dialog.tsxlabel 变量赋值后未使用 已移除
  • P2 新增:监控埋点 — actions.ts 接入 trackEventfile.uploaded/upload_failed/deleted/batch_deleted/viewed/action_error+ logAudit
  • P2 新增a11y — 所有图标加 aria-hidden,按钮加 aria-labelAdminFilesView 用三层 WidgetBoundary 包裹StatsSection / UploadSection / ListSection
  • 职责单一,不跨模块查询

文件清单

文件 行数 职责
data-access.ts 305 文件 CRUD + 批量删除 + 统计11 个函数 + mapRow
actions.ts 330 6 个 Server Action编排层权限+Zod+storageProvider+trackEvent+logAudit
schema.ts - Zod 校验UploadMetadata/BatchDelete/FileListQuery/FileTargetType
types.ts 70 类型定义FileTargetType 含 user_avatar
components/* 5 文件 上传/预览/预览弹窗/图标/管理视图
hooks/* 3 文件 上传/批量操作/预览 hooks

2.18 course-plans课程计划模块

职责:课程计划 CRUD + 周计划项 CRUD + 排序 + 批量操作 + 复制 + 年级进度统计 + 日历视图 + CSV 导出 + 模板库 + 拖拽排序 + 数据联动 + 组件级 Error Boundary。

导出函数

  • ActionsgetCoursePlansAction / getCoursePlanAction / createCoursePlanAction / updateCoursePlanAction / deleteCoursePlanAction / createCoursePlanItemAction / updateCoursePlanItemAction / deleteCoursePlanItemAction / toggleCoursePlanItemCompletedAction / getGradeCoursePlanProgressAction年级仪表盘维度4/ reorderCoursePlanItemsAction P1-7 新增:拖拽排序周计划条目)/ bulkToggleItemsAction P2-4 新增:批量标记完成状态)/ copyCoursePlanAction P2-4 新增复制计划到其他班级P2-5 复用为"从模板创建"/ getTemplateCandidatesAction P2-5 新增:获取模板候选列表,复用现有计划作为模板)/ trackCoursePlanEvent P2-8 新增:监控埋点接口预留,已在 plan_deleted/items_reordered/plan_exported/plan_created_from_template 四处调用)
  • Data-access与 actions 对应 + getGradeCoursePlanProgress + reorderCoursePlanItems P1-7 新增)+ bulkUpdateItemCompleted P2-4 新增)+ copyCoursePlanToClasses P2-4 新增)+ enrichPlanRows P1-1 新增:批量解析班级/科目/教师名称,通过动态 import 对方 data-access不再直接 JOIN 其他模块表+ buildScopeCondition P0-3 新增DataScope 权限过滤)
  • Lib P2 新增):lib/export-utils.ts(纯函数 planToExportRows/buildExportColumns/exportCoursePlanReportCSV 导出不依赖 i18n标签由调用方传入/ lib/calendar-utils.ts(纯函数 planToCalendarEvents/buildMonthGrid/eventsOnDay/filterEventsInRange + 日期工具 addDays/addMonths/startOfWeek/startOfMonth/endOfMonth/isSameDay/isWithinRange/formatDateISO/parseISODate
  • Types v4-P2-7 新增):GradeCoursePlanProgressItem / GradeCoursePlanProgressResult / CoursePlanQueryScope P0-3 新增:数据范围上下文,含 userId/isAdmin/classIds/teacherId/studentId/ ReorderCoursePlanItemInput P1-7 新增)/ 类型守卫 isCoursePlanStatus / isCoursePlanSemester P1-4 新增:替代 as 断言)/ 配置驱动 ROLE_WIDGET_CONFIG P2 新增admin/teacher/parent/student 四角色 Widget 配置)/ CalendarEvent P2-7 新增:日历事件类型)/ CoursePlanExportColumnKey + CoursePlanColumnLabels P2-6 新增:导出列类型与标签接口)
  • ComponentsCoursePlanList P1 修复:使用 <Link> + 类型守卫 + i18n/ CoursePlanDetail P1 修复:批量选择 + successHref prop + 埋点; P2-1 增强:SectionErrorBoundary 包裹进度条 + 周计划表 + 日历视图; P2-3 增强:textbooksHref/homeworkHref props 支持数据联动跳转; P2-6 增强CSV 导出按钮; P2-7 增强:嵌入 CoursePlanCalendar/ CoursePlanForm P1 修复:类型守卫 + successHref prop P2-5 增强:嵌入 TemplatePickerDialog/ CoursePlanItemEditor P1 修复i18n/ CoursePlanProgress P1 修复a11y aria 属性 + i18n/ SortableWeekRow P1-7/P2-3 新增:@dnd-kit/sortable 拖拽行 + 数据联动跳转链接 + 复选框)/ CoursePlanCalendar P2-7 新增:月历视图 6×7 网格 + 上一月/下一月/今天导航 + 事件列表)/ TemplatePickerDialog P2-5 新增:模板选择器 + 搜索过滤 + 克隆到目标班级)

依赖关系

  • 依赖:shared/*(含 P2-6 新增 shared/lib/export-utils.ts 通用 CSV 导出工具)、@/authclasses P1-1 已修复:通过动态 import getClassNamesByIds 批量解析,不再直接 JOIN classes 表)、school P1-1 已修复:通过动态 import getSubjectNameMapByIds 批量解析;getSubjectOptions 已移至 school 模块)、users P1-1 已修复:通过动态 import getUserNamesByIds 批量解析)、@dnd-kit/core + @dnd-kit/sortable + @dnd-kit/utilities P1-7 新增:拖拽排序依赖)
  • 被依赖parent/student 路由通过 page.tsx 调用 data-access P2-2 已新增)

已知问题

  • P0-1 已修复:教师详情页信息泄露 — getCoursePlanById 增加 scope 参数,非 admin 仅能查看自己负责的计划
  • P0-2 已修复admin 页面无 requirePermission — 所有 admin 页面补充 requirePermission(COURSE_PLAN_READ/MANAGE)
  • P0-3 已修复data-access 无 DataScope 过滤 — getCoursePlans/getCoursePlanById/getGradeCoursePlanProgress 增加可选 scope 参数
  • P0-4 已修复i18n 严重缺失 — 翻译键从 5 个扩展至 ~230 个,覆盖状态/筛选/列表/详情/表单/条目/进度/Toast/教师/家长/学生/错误/日历/导出/批量/模板/加载 + Error Boundary 文案
  • P1-1 已修复:跨模块直接 JOIN — 移除 buildPlanSelectleftJoin(classes/subjects/users),改用 enrichPlanRows 动态 import 对方 data-access 批量查询
  • P1-2 已修复:缺 loading.tsx/error.tsx — 12 个路由(含 P2-2 新增 parent/student均补充骨架屏 + 错误边界(使用 EmptyState + i18n
  • P1-3 已修复:原生 <a> 标签 — 替换为 Next.js <Link> 组件
  • P1-4 已修复:as 断言 — 替换为类型守卫函数 isCoursePlanStatus/isCoursePlanSemester
  • P1-5 已修复URL 路径推断角色 — 改为 successHref prop 显式传入
  • P1-6 已修复Action 入参未验证 — getCoursePlansAction/getGradeCoursePlanProgressAction/reorderCoursePlanItemsAction/bulkToggleItemsAction/copyCoursePlanAction 增加 Zod schema 验证
  • P1-7 已修复:死代码 reorderCoursePlanItems — 已补充对应 Action + UI 拖拽排序(SortableWeekRow + DndContext + SortableContextPointerSensor + KeyboardSensor 支持 a11y
  • P2-1 已修复:无 Error Boundary — SectionErrorBoundary 包裹进度条 + 周计划表 + 日历视图三个数据区块
  • P2-2 已修复parent/student 无路由 — 新增 parent/student 课程计划只读路由(按子女班级/学生班级 scope 过滤)
  • P2-3 已修复:无数据联动 — 周计划项章节文本渲染为教材跳转链接,行尾新增作业跳转按钮(通过 textbooksHref/homeworkHref props 按角色注入)
  • P2-4 已修复:无批量操作 — 批量标记完成 + 批量复制计划到其他班级
  • P2-5 已修复:无模板复用 — TemplatePickerDialog 复用现有计划作为模板,搜索 + 选择 + 克隆到目标班级
  • P2-6 已修复:无导出 — CSV 导出UTF-8 BOM 兼容 Excel通用工具迁移至 shared/lib/export-utils.ts
  • P2-7 已修复:无日历视图 — CoursePlanCalendar 月历视图,周计划项按 plan.startDate 映射到日期范围
  • P2-8 已修复:无监控埋点 — trackCoursePlanEvent 预留接口,已在 plan_deleted/items_reordered/plan_exported/plan_created_from_template 四处调用
  • actions 层使用 handleActionError/revalidatePlanPaths 辅助函数(良好范例)
  • resolveScope 函数正确处理 DataScope union 类型admin/teacher/parent/student 四角色范围解析)

文件清单

文件 行数 职责
data-access.ts 507 课程计划 + 周计划项 CRUD + 批量 + 复制 + 名称解析解耦
actions.ts 520 14 个 Server Action + 埋点接口 + scope 解析 + 模板候选
schema.ts 213 Zod 校验(含 Action 入参验证 schema
types.ts 161 类型定义 + 类型守卫 + 角色配置驱动 + CalendarEvent + 导出列类型
lib/export-utils.ts 91 CSV 导出纯函数planToExportRows + buildExportColumns + exportCoursePlanReport
lib/calendar-utils.ts 175 日历视图纯函数(事件映射 + 月历网格 + 日期工具)
components/course-plan-list.tsx 162 列表组件i18n + Link + 类型守卫)
components/course-plan-detail.tsx 384 详情组件(批量选择 + 埋点 + successHref + Error Boundary + 拖拽 + 导出 + 日历 + 数据联动)
components/course-plan-form.tsx 326 表单组件(类型守卫 + successHref + 模板选择器)
components/course-plan-item-editor.tsx 250 周计划编辑器i18n
components/course-plan-progress.tsx 42 进度条a11y + i18n
components/sortable-week-row.tsx 176 拖拽行(@dnd-kit + 数据联动跳转 + 复选框 + a11y
components/course-plan-calendar.tsx 200 月历视图6×7 网格 + 导航 + 事件列表 + a11y
components/template-picker-dialog.tsx 195 模板选择器(搜索 + 列表 + 克隆 + a11y
i18n/zh-CN/course-plans.json 233 中文翻译(~230 键)
i18n/en/course-plans.json 233 英文翻译(~230 键)

页面路由 P1-2 全部补充 loading.tsx + error.tsx P2-2 新增 parent/student 路由):

路由 角色 权限 说明
admin/course-plans admin COURSE_PLAN_READ 列表(全局视图)
admin/course-plans/[id] admin COURSE_PLAN_READ 详情(含日历/导出/拖拽)
admin/course-plans/[id]/edit admin COURSE_PLAN_MANAGE 编辑
admin/course-plans/create admin COURSE_PLAN_MANAGE 创建(含从模板创建)
teacher/course-plans teacher COURSE_PLAN_READ 列表(按 teacherId 过滤)
teacher/course-plans/[id] teacher COURSE_PLAN_READ 详情scope 过滤防越权 + 数据联动)
parent/course-plans parent COURSE_PLAN_READ 列表(按子女 classIds 过滤)
parent/course-plans/[id] parent COURSE_PLAN_READ 详情(按子女 classIds scope 过滤)
student/course-plans student COURSE_PLAN_READ 列表(按 class_members scope 过滤)
student/course-plans/[id] student COURSE_PLAN_READ 详情(按 class_members scope 过滤 + 教材联动)

2.19 parent家长模块

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

导出函数

  • Data-accessgetChildren / getChildBasicInfo / getChildDashboardDataV3-11 增强:并行调用 homework/data-access.getStudentExamResults 获取考试结果)/ getParentDashboardData / verifyParentChildRelation / getChildNameList v4 新增:用于详情页头部多子女切换器,一次批量查询避免 N+1
  • TypesV3-11 新增):ChildExamResultItem单条考试结果submissionId/examId/examTitle/assignmentId/assignmentTitle/score/maxScore/submittedAt/statusChildDashboardData 扩展 examResults: ChildExamResultItem[] 字段
  • ComponentsParentDashboard / ChildCard / ChildDetailHeader / ChildDetailPanelV3-11 增强:新增 exams Tab/ SiblingSwitcher / ChildHomeworkSummary / ChildHomeworkDetailv4 新增)/ ChildGradeSummary / ChildGradeDetailv4 新增)/ ChildScheduleCard / ChildExamDetailV3-11 新增:子女考试详情视图,汇总卡片+考试列表)/ ParentChildrenDataPage / ParentNoChildrenPage / ParentAttentionBannerv4 新增)/ ParentAttendanceWarningv4 新增)/ ParentAttendanceRateCardv4 新增)/ ParentAttendanceCalendarv4 新增)/ ParentExportButtonv4 新增)

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-P02ChildCard 突出 Overdue 异常(红色边框 + AlertTriangle 图标)
  • LAYOUT-P03仪表盘快捷入口改为 4 宫格大图标卡片
  • LAYOUT-P04详情页改为 Tab 布局Overview/Homework/Grades/Schedule/Attendance/Diagnostic
  • LAYOUT-P05详情页新增面包屑导航
  • LAYOUT-P07成绩趋势图 X 轴改用序号,避免日期重叠
  • LAYOUT-P08成绩页新增 ParentExportButton 导出按钮(占位)
  • LAYOUT-P09考勤页新增 ParentAttendanceCalendar 月历视图(按状态着色,支持按月切换)
  • LAYOUT-P10考勤异常高亮与 FEAT-G05 同步实现)
  • NAV-P02Grades/Attendance 页面描述明确职责(多子女对比 vs 单子女详情)
  • NAV-P03详情页实现 ?tab= 参数支持
  • NAV-P04所有 parent 路由新增 loading.tsx 骨架屏 + error.tsx 错误边界
  • DATA-P02成绩卡片新增 TrendIcon 进步/退步/持平标识
  • DATA-P03排名展示新增"Top X%"百分比
  • DATA-P04作业列表新增科目标识 Badge
  • DATA-P05作业分数显示新增"pts"单位
  • DATA-P06考勤页新增 ParentAttendanceRateCard 出勤率汇总卡片
  • HABIT-P01仪表盘"一眼定位异常"能力AttentionBanner 聚合)
  • HABIT-P02待办横幅作业项直接跳转详情页 homework tab1 次点击到达)
  • HABIT-P03多子女切换无需返回仪表盘
  • HABIT-P06仪表盘展示未读/待办数量
  • A11Y-P02Overdue 状态增加 AlertTriangle 图标辅助
  • A11Y-P04成绩图表容器新增 aria-label
  • PERF-P01/P02骨架屏 + 错误边界
  • PERF-P03空状态新增"Contact support"引导按钮
  • PERF-P04ChildCard Link 添加 prefetch
  • MOBILE-P03移动端子女卡片改为水平滑动 Carouselsnap-x
  • MOBILE-P04作业/成绩列表项 min-h-[44px] 触摸区域

V3-11 修复(家长端考试详情)

  • V3-11parent/types.ts 新增 ChildExamResultItem 类型;ChildDashboardData 扩展 examResults 字段
  • V3-11parent/data-access.ts getChildDashboardData 并行调用 homework/data-access.getStudentExamResults 获取子女考试结果(加入现有 Promise.all
  • V3-11新增组件 ChildExamDetail(考试成绩汇总卡片:已参加考试数/平均分/最高分 + 考试成绩列表:考试标题/分数/得分率 Progress/提交时间)
  • V3-11child-detail-panel.tsx 新增 exams Tab对标智学网家长端考试详情

依赖关系

  • 依赖:shared/*@/authclasses(合理)、homework合理V3-11 新增 getStudentExamResults 调用)、grades(合理)、users(合理)、school(合理)、attendancev4 新增:考勤页复用 StudentAttendanceView⚠️ 跨模块 UI 类型依赖3 个组件直接 import @/modules/attendance/types
  • 被依赖:无

已知问题

  • ⚠️ P13 个 parent 组件(parent-attendance-warning.tsx/parent-attendance-rate-card.tsx/parent-attendance-calendar.tsx)直接 import @/modules/attendance/types,违反模块解耦原则(应通过 data-access 接口或 shared 类型抽象)
  • ⚠️ P1parent-attendance-calendar.tsx 重新定义 STATUS_DOT/STATUS_LABEL 常量,与 attendance 模块重复
  • ⚠️ P13 个 parent 组件的纯函数(buildWarnings/aggregate/rateTone/formatDateKey/parseDateKey/buildCalendarDays/isSameDay)未导出,无法单测
  • P1 已修复:app/(dashboard)/parent/children/[studentId]/page.tsx 直接访问 DB违反三层架构 改为调用 verifyParentChildRelation data-access 函数
  • P1 已修复:权限校验未加 parentId 条件,存在信息泄露风险 verifyParentChildRelation 同时按 parentId + studentId 过滤
  • P2 已修复:getChildBasicInfo 多次串行查询 改为 Promise.all 并行化,并使用 getStudentActiveClass 一次 JOIN
  • P2 已修复:getGradeOptions 全量查询效率低 改为 getGradeNameById 按 ID 查询
  • P2 已修复:buildHomeworkSummary[...assignments].sort() 不必要拷贝 改为 toSorted()
  • P2 已修复:in7Days 死代码 已删除
  • ⚠️ v4 保留:/parent/leave 为占位页,待后端实现请假审批流后接入
  • v4-P1-5 改进2026-06-23新增 getParentIdsByStudentIds data-access 函数,批量查询多个学生的家长 userId去重供 diagnostic/grades 模块通知场景调用
  • v4-P1-9 改进2026-06-23parent/diagnostic/page.tsx 新增错误卡片展示子女诊断查询失败原因
  • v4-P1-12 改进2026-06-23ParentExportButton 接入 exportGradesAction,支持按 studentId 导出单个子女成绩parent/grades/page.tsx 传 studentId 到 ParentExportButton
  • ⚠️ v4 保留:详情页 Attendance/Diagnostic Tab 为占位提示,待对应功能实现后填充
  • v3-P2 改进2026-06-23parent/grades/page.tsx 为每个子女并行查询 getClassAverageTrend,渲染 GradeTrendCardparent/diagnostic/page.tsx 传入 practiceHrefBase={null} 隐藏练习按钮
  • 职责单一,正确复用其他模块 data-access

文件清单

文件 行数 职责
data-access.ts 243+ 子女关系 + 仪表盘数据聚合 + 关系校验 + 子女姓名列表v4 新增 getChildNameList + buildWeeklyScheduleV3-11 getChildDashboardData 并行调用 getStudentExamResults
types.ts 79+ 类型定义(含 JSDocv4 新增 ChildWeeklyScheduleItemV3-11 新增 ChildExamResultItem + ChildDashboardData.examResults
components/parent-dashboard.tsx 110 仪表盘v4 重构:待办横幅 + 宫格快捷入口 + 移动端水平滑动)
components/parent-attention-banner.tsx 128 v4 新增:待办事项/异常聚合横幅(作业项直接跳转详情页 homework tab
components/parent-attendance-warning.tsx 89 v4 新增:考勤异常预警
components/parent-attendance-rate-card.tsx 105 v4 新增:考勤出勤率汇总卡片
components/parent-attendance-calendar.tsx 175 v4 新增考勤月历视图use client
components/parent-export-button.tsx 50 v4 新增成绩导出按钮v4-P1-12 已接入 exportGradesAction支持按 studentId 导出单个子女成绩)
components/child-card.tsx 148 子女卡片v4 增强:异常突出 + 趋势图标)
components/child-detail-header.tsx 78 详情页头部v4 增强:面包屑)
components/child-detail-panel.tsx 200+ 详情页 Tab 面板 + SiblingSwitcherv4 重写,集成 Homework/Grade DetailV3-11 新增 exams Tab i18n硬编码英文提取为 parent.childDetail.* 翻译键,新增 parent.json修复 P2-4as 断言改用 isTab 类型守卫)
components/child-homework-summary.tsx 147 作业摘要v4 增强:科目标识 + 触摸区域 + pts 单位)
components/child-homework-detail.tsx 145 v4 新增:作业详情视图(完整作业信息)
components/child-grade-summary.tsx 159 成绩趋势v4 增强:趋势图标 + aria-label
components/child-grade-detail.tsx 165 v4 新增:成绩详情视图(按科目分组分析)
components/child-exam-detail.tsx 165 V3-11 新增 / P3-4 改进:子女考试详情视图(汇总卡片+考试列表对标智学网家长端P3-4所有硬编码英文文案改为 i18n 翻译键 examHomework.homework.parentExam.*,新增 ChevronRight 导航图标ul/li 语义化标签 + ARIA 属性,触控目标 ≥ 44px
components/child-schedule-card.tsx 119 课表卡片v4 增强:周课表视图)
components/parent-children-data-page.tsx 92 共享数据页v4 增强headerExtra

路由清单

路由 文件 说明
/parent/dashboard dashboard/page.tsx + loading.tsx 家长仪表盘
/parent/grades grades/page.tsx + loading.tsx 多子女成绩聚合v3-P2并行查询 getClassAverageTrend + GradeTrendCard
/parent/diagnostic diagnostic/page.tsx + loading.tsx + error.tsx P2-5 新增多子女学情诊断聚合v3-P2practiceHrefBase={null} 隐藏练习按钮)
/parent/attendance attendance/page.tsx + loading.tsx 多子女考勤聚合v4 新增预警横幅)
/parent/leave leave/page.tsx + loading.tsx v4 新增:请假申请(占位)
/parent/children/[studentId] children/[studentId]/page.tsx + loading.tsx 子女详情页v4 重构Tab 布局 + 多子女切换)
error.tsx error.tsx v4 新增:错误边界

2.20 elective选课模块

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

导出函数

  • Actions11 个):getElectiveCoursesAction / createElectiveCourseAction / updateElectiveCourseAction / deleteElectiveCourseAction / getStudentSelectionsAction / selectCourseAction / dropCourseAction / runLotteryAction / getAvailableCoursesForStudentAction / openSelectionAction / closeSelectionAction P2-28 个写 Action 错误消息改用 getTranslations("elective") 国际化)
  • Data-accessgetElectiveCourses / getElectiveCourseById / createElectiveCourse / updateElectiveCourse / deleteElectiveCourse / openSelection / closeSelection / buildCourseSelect / mapCourseRow / resolveCourseDisplayNames P2-7使用 React cache() 包装 subject/grade options 请求级去重; P2-8通过 resolvers.ts 接口抽象跨模块依赖)/ CourseCoreRow
  • Data-access-operationsselectCourse P2-9新增 checkScheduleConflict 时间冲突检测; P2-10新增 checkCreditLimit 学分上限校验; P2-4学分上限改用 getElectiveCreditLimit 配置化,按年级覆盖; P2-4FCFS 录取后触发 notifyCapacityThresholdIfNeeded 容量阈值通知)/ dropCourse P2-4新增 dropReason 参数 + dropDeadline 校验)/ runLottery / buildLotteryRankCase⚠️ 未导出,无法单测)/ checkScheduleConflictP2-9 新增)/ checkCreditLimitP2-10 新增P2-4 重构为配置化)
  • Data-access-selectionsgetCourseSelections / getStudentSelections / getStudentGradeId P2-8通过 resolvers.ts StudentGradeResolver 接口抽象; P2-4selectCourse 调用以获取年级用于学分上限)/ getAvailableCoursesForStudent / resolveStudentDisplayNames P2-8通过 resolvers.ts CourseDisplayResolver 接口抽象)
  • Data-access-settings P2-4 新增):getElectiveCreditLimit(gradeId?)按年级覆盖的学期学分上限fallback 到全局,默认 10/ getCapacityNotifyThreshold()(容量阈值通知比例,默认 0.9/ ELECTIVE_DEFAULTS
  • Resolvers P2-8 新增):CourseDisplayResolver / StudentGradeResolver(接口)/ getCourseDisplayResolver / getStudentGradeResolver / setCourseDisplayResolver / setStudentGradeResolver / resetResolvers(可注入测试 mock
  • Lib 纯函数( P2-9 新增):parseSchedule(解析时间段字符串)/ isScheduleConflict(判断两个时间段是否冲突)
  • Import-export P2-11 新增):exportElectiveCoursesToExcel(课程列表导出)/ exportCourseSelectionsToExcel(选课名单导出)
  • ComponentsElectiveCourseList(课程卡片网格 + 管理操作)/ ElectiveCourseForm(课程创建/编辑表单)/ ElectiveFiltersnuqs 筛选栏)/ StudentMySelectionsSection + StudentAvailableCoursesSection P2-3由原 StudentSelectionView 拆分为两个独立客户端组件,支持 Suspense 流式渲染)/ ElectivePageLayout P2-4 新增页面布局骨架admin/teacher 选课页复用)/ ElectiveStatsCards P1-13 新增)/ ElectiveCourseDetail P2-2 新增:课程详情复用组件)/ ParentSelectionView P0-2 新增:家长只读视图)

依赖关系

  • 依赖:shared/*@/authschool P3 已修复:通过 school data-access.getSubjectOptions/getGradeOptions 获取科目/年级名称; P2-8通过 resolvers.ts 接口抽象)、users P3 已修复:通过 users data-access.getUserNamesByIds P2-8通过 resolvers.ts 接口抽象)、classes(通过 classes data-access.getStudentActiveGradeId P2-8通过 resolvers.ts 接口抽象)、next-intl P2-2Server Action 错误消息 i18n P2-4容量阈值通知文案 i18nnotifications P2-4 新增:通过 sendNotification 发送容量阈值通知给教师/管理员)、systemSettings 表( P2-4读取 elective 学分上限与容量阈值配置)
  • 被依赖:app/api/export P2-11通过 exportElectiveCoursesToExcel / exportCourseSelectionsToExcel

已知问题(详见 docs/architecture/audit/elective-audit-report.mdattendance-elective-audit-report.md

  • P2-2 已修复8 个写 Action 错误消息改用 getTranslations("elective") 国际化
  • P2-4 已修复:新增 ElectivePageLayout 组件admin/teacher 选课页复用布局
  • P2-7 已修复:resolveCourseDisplayNames 使用 React cache() 包装 subject/grade options 请求级去重
  • P2-8 已修复:新增 resolvers.ts跨模块依赖users/school/classes通过接口抽象支持测试注入 mock
  • P2-9 已修复:selectCourse 新增时间冲突检测(parseSchedule + isScheduleConflict 纯函数 + checkScheduleConflict 异步查询)
  • P2-10 已修复:selectCourse 新增学分上限校验(checkCreditLimitMAX_CREDIT_PER_TERM=10
  • P2-11 已修复:新增 export.ts,课程列表/选课名单可导出 Excel
  • P0-1 已修复2026-06-25教师页面跳转 admin 路由修复,新增 teacher/elective/createteacher/elective/[id]/edit 路由(含权限校验、教师越权防护)
  • P0-2 已修复2026-06-25新增 parent/elective/page.tsx(含 loading.tsx + error.tsx使用 ParentChildrenDataPage 模式 + parentId+studentId 双重校验
  • P0-3 已修复2026-06-25admin/elective/page.tsxstudent/elective/page.tsx 入口添加 requirePermission(Permissions.ELECTIVE_READ) 纵深防御
  • P0-4 已修复2026-06-25data-access-operations.ts 引入 ElectiveBusinessError(带 i18n code + paramsactions.ts 通过 translateBusinessError 翻译后再返回
  • P0-5 已修复2026-06-25export.ts 移除 as 类型断言,改用 ElectiveCourseStatusEnum.safeParse() 类型守卫
  • P1-6 已修复2026-06-25elective-course-form.tsx 全量 i18n 化,所有硬编码英文替换为 t("form.*")
  • P1-7 已修复2026-06-25elective-course-list.tsx<a> 替换为 Next.js <Link>
  • P1-8 已修复2026-06-253 个 error.tsx 文案修正为 t("errors.title")/t("errors.description")/t("actions.retry")
  • P1-9 已修复2026-06-25export.ts 表头全量 i18n 化statusHeader/priorityHeader/selectedAtHeader/enrolledAtHeader/indexHeader
  • P1-10 已修复2026-06-25getElectiveCourses / getElectiveCourseById 移除 try-catch 静默吞错,让异常冒泡到 RSC 触发 error.tsx
  • P1-11 已修复2026-06-25parseSchedule 重写支持完整英文星期Monday/Tuesday 等)+ 中文(周一/星期一)+ 多时段(逗号/分号分隔)
  • P1-12 已修复2026-06-25runLottery 不再自动将课程置为 closed,保留 status="open" 支持重抽
  • P1-13 已修复2026-06-25新增 data-access-stats.tsElectiveStatsCards 组件,集成到 admin 列表页顶部展示 4 项关键指标
  • P2-1 已修复2026-06-25新增 tests/integration/elective/elective-pure-functions.test.ts35 个单测,覆盖 parseSchedule / isScheduleConflict / normalizeDay / buildLotteryRankCase / mapCourseRow
  • P2-2 已修复2026-06-25新增 ElectiveCourseDetail 复用组件 + admin/student 课程详情页(含 loading.tsx
  • P2-3 已修复2026-06-25student 页面拆分"我的选课"与"可选课程"为两个独立 Suspense 边界admin 列表与统计卡片分离 SuspenseStudentSelectionView 拆分为 StudentMySelectionsSection + StudentAvailableCoursesSection;新增 4 个异步 RSC loaderMySelectionsLoader / AvailableCoursesLoader / StatsCardsLoader / CourseListLoader+ 分段骨架屏
  • P2-4 已修复2026-06-25配置化学分上限data-access-settings.ts 读取 systemSettings按年级覆盖默认 10退课截止时间 dropDeadline 字段 + 校验;退课理由 dropReason 字段(可选填写,最长 255 字符容量阈值通知FCFS 录取后跨过 capacityNotifyThreshold 阈值时通知教师fire-and-forget新增 ElectiveErrorCode.dropDeadlinePassed;通知文案 i18n 化
  • 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所有页面入口均调用 requirePermission
  • ⚠️ 无 P2 待办:所有审计报告 P0/P1/P2 项均已实施完毕

文件清单

文件 行数 职责
actions.ts 348 11 个 Server ActionP0-4translateBusinessError 翻译 ElectiveBusinessError
data-access.ts 258 课程 CRUD + scope 过滤 + 共享映射函数P1-10移除静默吞错
data-access-operations.ts 374 选课操作P0-4ElectiveBusinessError + i18n codeP1-11parseSchedule 支持完整星期+多时段P1-12抽签可重跑
data-access-selections.ts 147 选课记录查询 + 学生可选课程
data-access-stats.ts 88 管理员概览统计P1-13 新增getElectiveOverviewStats
resolvers.ts 83 跨模块依赖接口抽象
export.ts 102 Excel 导出P0-5移除 as 断言P1-9表头全量 i18n
schema.ts 179 Zod 校验
types.ts 73 类型定义
constants.ts 55 i18n key 映射 + Badge variant + 类型守卫
components/elective-course-list.tsx 236 课程卡片网格 + 管理操作P1-7a 转 Link
components/elective-course-form.tsx 301 课程创建/编辑表单P1-6全量 i18n 化)
components/elective-filters.tsx 51 nuqs 筛选栏(搜索 + 模式)
components/student-selection-view.tsx 278 学生选课视图( P2-3拆分为 StudentMySelectionsSection + StudentAvailableCoursesSection 两个独立客户端组件,支持 Suspense 流式渲染)
components/elective-page-layout.tsx 30 页面布局骨架
components/elective-stats-cards.tsx 73 管理员统计卡片网格P1-13 新增)
components/parent-selection-view.tsx 80 家长查看子女选课只读视图P0-2 新增)
components/elective-course-detail.tsx 130 课程详情复用组件P2-2 新增admin/student 共用)
app/(dashboard)/student/elective/_components/my-selections-loader.tsx 17 异步 RSC loader我的选课P2-3 新增)
app/(dashboard)/student/elective/_components/available-courses-loader.tsx 45 异步 RSC loader可选课程 + 过滤逻辑P2-3 新增)
app/(dashboard)/admin/elective/_components/stats-cards-loader.tsx 12 异步 RSC loader统计卡片P2-3 新增)
app/(dashboard)/admin/elective/_components/course-list-loader.tsx 30 异步 RSC loader课程列表 + 状态过滤P2-3 新增)

2.21 proctoring监考模块

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

导出函数

  • ActionsrecordProctoringEventAction / getProctoringDashboardAction
  • Data-accessrecordProctoringEvent / getExamSubmissionForProctoring / getExamForProctoring / getExamProctoringSummary / getStudentProctoringStatuses / getRecentProctoringEvents P2 已修复:getExamProctoringSummary 使用 Promise.all 并行化考试信息与提交记录查询、事件类型统计与学生事件统计查询;合并两次 filter 为单次循环统计 started/submitted

依赖关系

  • 依赖:shared/*@/authexams P1-1 已修复:通过 exams data-access.getExamForProctoringCrossModule/getExamSubmissionForProctoringCrossModule/getExamSubmissionsForExam/getExamTitleByIdusers P1-1 已修复:通过 users data-access.getUserNamesByIds
  • 被依赖:无

已知问题

  • P1-2 已修复:exam-mode-config.tsx 已集成到考试表单(exam-form.tsx 调用 <ExamModeConfig />),移除全部 10 处 as 类型断言,改用 useFormContext 替代 Control prop 避免 Control<T> 不变型问题
  • P1-3 已修复:proctoring-dashboard.tsx 用类型守卫函数 isProctoringEventType + toProctoringEventTypes 替代 Object.keys(...) as ProctoringEventType[] 断言
  • P0-6 已修复:事件上报存在 Server Action 与 REST API 双通道重复 删除 /api/proctoring/event REST 路由(移至 deletes/Server Action recordProctoringEventAction 为唯一规范路径
  • P1-1 已修复:跨模块直查 exams/examSubmissions/users 改为通过 exams/users data-access 函数获取数据
  • P2 已修复:actions.ts 不再直接 import dbexamSubmissionssubmission 归属校验已下沉到 data-accessrecordProctoringEventAction 改用 requirePermission(EXAM_SUBMIT) 并增加 revalidatePath
  • P2 已修复:getStudentProctoringStatuses 串行查询getUserNamesByIds 后再查事件) 改为 Promise.all 并行拉取学生姓名与事件记录

文件清单

文件 行数 职责
data-access.ts 409 事件记录 + 查询 + 摘要统计 + submission 归属校验
actions.ts 139 2 个 Server Action
types.ts 136 类型定义 + 标签常量 + 阈值常量
components/anti-cheat-monitor.tsx - 学生端防作弊监控
components/exam-mode-config.tsx - 考试模式配置( P1-2 已修复:已集成到 exam-formuseFormContext 替代 Control prop无 as 断言)
components/proctoring-dashboard.tsx - 教师监考面板( P1-3 已修复:类型守卫函数替代 as 断言)

2.22 diagnostic学情诊断模块

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

导出函数

  • ActionsgenerateStudentReportAction / generateClassReportAction / generateGradeReportActionv4-P2-3 新增:生成年级诊断报告)/ publishReportActionv4-P1-4 + v4-P1-5 增强:班级报告发布时通知全班学生 + 全班学生家长,个人报告通知学生本人 + 其家长,调用 parent/data-access.getParentIdsByStudentIds 批量查询家长 ID/ deleteReportActionv2-P2-3 修复:删除死代码 getDiagnosticReportsAction / getDiagnosticReportByIdAction,页面直接调用 data-access 并自行权限校验)/ exportDiagnosticReportActionv3-P2-4 新增:导出 Excel/ getClassStudentsByKnowledgePointActionv3-P2-5 新增:按知识点筛选学生)
  • Data-accessupdateMasteryFromSubmissionv2-P1-8 修复累积模式v2-P2-5 修复db.transaction 包裹)/ getStudentMasteryP3-19 修复:移除 export改为模块内部函数/ getStudentMasterySummaryP3-18 修复getUserNamesByIds 与 getStudentMastery 并行查询)/ getClassMasterySummaryv2-P2-4 修复totalStudents 语义 + 班级平均掌握度按学生平均)/ getGradeMasterySummaryv4-P2-3 新增:年级掌握度摘要)/ getKnowledgePointStatsv2-P1-7 修复:页面先查班级再传参)/ getClassStudentsByKnowledgePointv3-P2-5 新增)
  • Data-access-reportsgenerateDiagnosticReport / generateClassDiagnosticReportv2-P2-6 修复校验掌握度数据P3-27 修复:使用 DiagnosticReportError 结构化错误码)/ generateGradeDiagnosticReportv4-P2-3 新增:生成年级诊断报告)/ getDiagnosticReportsP3-15 修复:支持分页 limit/offset返回 { reports, total } 结构v4-P1-1 增强:新增可选 scope?: DataScope 参数,支持 children/class_taught 行级权限过滤class_taught 通过 getStudentIdsByClassIds 解析所教班级学生 ID/ getDiagnosticReportById / publishDiagnosticReport / deleteDiagnosticReport P2 已修复:使用 React.cache() 包装实现请求级 memoizationP3-1 修复toNumber 从 grades 模块导入)/ DiagnosticReportErrorP3-27 新增结构化错误码类v4-P2-3 新增 GRADE_NOT_FOUND/GRADE_NO_MASTERY_DATA 错误码)
  • Stats-service v2-P1-6 新增):serializeMasteryWithKp / computeAverageMastery / classifyStrengthsWeaknessesP3-16 修复:弱项阈值从 <60 改为 <80消除 60-79 盲区)/ buildStudentMasterySummary / aggregateClassMastery / computeKpStats / computeClassAverageMastery / buildStudentsNeedingAttention / buildClassMasterySummary / buildGradeMasterySummaryv4-P2-3 新增)/ buildStudentReportContent / buildClassReportContent / buildGradeReportContentv4-P2-3 新增)/ computeMasteryLevel / serializeMastery(从 data-access / data-access-reports 抽取的纯函数)
  • Exportv3-P2-4 新增):exportDiagnosticReportToExcelv4-P2-1 增强:班级报告导出新增知识点统计+需关注学生 Sheeti18n 全覆盖)/ buildDiagnosticReportFilenamev4-P2 改为 async使用 i18n 翻译文件名)
  • Role-configv4-P2-2 新增):DIAGNOSTIC_ROLE_CONFIG / getDiagnosticRoleConfig / DiagnosticRole 类型(配置驱动角色差异,新增角色只需添加配置项)
  • SchemaGenerateStudentReportSchema / GenerateClassReportSchema / GenerateGradeReportSchemav4-P2-3 新增)/ PublishReportSchema / DeleteReportSchemav2-P2-3 修复:删除死代码 GetDiagnosticReportsSchema / GetDiagnosticReportByIdSchema

依赖关系

  • 依赖:shared/*@/authexams P1-1 已修复:通过 exams data-access.getExamSubmissionWithAnswersquestions P1-1 已修复:通过 questions data-access.getKnowledgePointsForQuestionsclasses P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getActiveStudentIdsByClassIdv2-P1-7 新增 getStudentActiveClassIdusers P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeIdgradesP3-1 修复:通过 grades/lib/grade-utils.toNumber 复用工具函数)、schoolv4-P2-3 新增:通过 school data-access.getGradeNameById 获取年级名称)
  • 被依赖:无

已知问题

  • P1-1 已修复:updateMasteryFromSubmission 跨模块直查 4 张表 改为调用 exams/data-access.getExamSubmissionWithAnswersquestions/data-access.getKnowledgePointsForQuestions
  • P2 已修复:data-access-reports.ts 有未使用代码(round2 + void round2 已删除死代码
  • P2 已修复:updateMasteryFromSubmission 循环内串行 await upsert 改为 Promise.all 并行执行所有 upsert
  • P2 已修复:getClassMasterySummary 串行查询 改为两组 Promise.all 并行
  • P2 已修复:getDiagnosticReportsconditions 隐式 any[] 改为显式 SQL[] 类型标注
  • P0-2 已修复:data-access-reports.ts 直查 users 表获取姓名 改为通过 users/data-access.getUserNamesByIds
  • P2-2 已修复:Tailwind 任意值 改用标准 Tailwind 类
  • P2-1 已修复:图表/表格/列表缺少 a11y ARIA 属性 为 5 个图表添加 role="img" + aria-label2 个表格添加 <caption>3 个列表添加 role="list"3 个图标按钮添加 aria-label
  • P2-3 已修复:班级报告将生成者 ID 存入 studentId 字段 schema 改为可空,班级报告 studentId 置空
  • v2-P1-6 已修复:统计逻辑混在 data-access 层 抽取 stats-service.ts352 行12 个纯函数 + 2 个常量 + 4 个接口)
  • v2-P1-7 已修复:getKnowledgePointStats 无参调用 页面先查 getStudentActiveClassId 再传参
  • v2-P1-8 已修复:updateMasteryFromSubmission 覆盖模式 改为累积计算(读取已有记录后累加)
  • v2-P2-3 已修复:死代码 getDiagnosticReportsAction / getDiagnosticReportByIdAction 全局零调用 已删除,页面直接调用 data-access
  • v2-P2-4 已修复:totalStudents 语义错误 + 班级平均掌握度计算偏差 改为实际有掌握度记录的学生数;先算学生个人平均再取平均
  • v2-P2-5 已修复:多 upsert 无事务包裹 使用 db.transaction() 保证原子性
  • v2-P2-6 已修复:生成报告未校验掌握度数据 添加 totalKnowledgePoints === 0studentCount === 0 校验
  • v2-P1-4 已修复:4 个组件 i18n 完全未接入 全部接入 useTranslations("diagnostic")
  • v2-P2-7 已修复:report-list.tsx 过滤器 Label 缺少 htmlFor 添加 htmlForid
  • 与 grades 模块无职责重叠
  • v3-P2 改进2026-06-23StudentDiagnosticView 新增 practiceHrefBase propstring | nullnull 时隐藏练习按钮teacher/diagnostic/student/[studentId] 传入 practiceHrefBase="/teacher/questions"parent/diagnostic 传入 practiceHrefBase={null} 隐藏练习按钮
  • P3-1 已修复2026-06-23data-access-reports.ts 本地定义 toNumbergrades/lib/grade-utils.ts 重复 改为从 grades 模块导入
  • P3-15 已修复2026-06-23getDiagnosticReports 无分页,可能返回大量数据 添加 limit/offset 分页支持,返回 { reports, total } 结构Promise.all 并行查询总数和数据
  • P3-16 已修复2026-06-23强弱项分类存在 60-79 盲区 弱项阈值从 <60 改为 <80确保所有知识点都被分类
  • P3-17 已修复:班级报告 strengths 无数量上限 buildClassReportContent 中 strengths 已限制为前 5 个(按掌握度降序)
  • P3-18 已修复2026-06-23getStudentMasterySummary 串行查询用户名和掌握度 改为 Promise.all 并行查询
  • P3-19 已修复2026-06-23getStudentMastery 使用 export 但仅内部使用 移除 export改为模块内部函数
  • P3-27 已修复2026-06-23generateDiagnosticReport / generateClassDiagnosticReportthrow new Error("...") 直接暴露给用户 改为使用 DiagnosticReportError 结构化错误码(继承 BusinessError
  • P3-21 已修复2026-06-23ClassDiagnosticView View 按钮已有描述性 aria-labelclassDiagnostic.viewAriaLabel,含学生名)
  • P3-22 已修复2026-06-23StudentDiagnosticView Practice 按钮添加 aria-labelstudentDiagnostic.practiceAriaLabel,含知识点名)
  • v4-P3-6 已修复2026-06-23MasteryRadarChart 雷达图添加 tabIndex={0} 键盘可达性 + focus-visible 焦点样式(原有 role="img" + aria-label 保留)
  • v4-P1-1 改进2026-06-23getDiagnosticReports 新增可选 scope?: DataScope 参数,支持 children/class_taught 行级权限过滤teacher/diagnostic/page.tsx 传入 dataScopestudent/diagnostic/page.tsx 传 status:"published" + dataScopeparent/diagnostic/page.tsx 传 status:"published" + dataScope + 错误卡片
  • v4-P1-3 改进2026-06-23student-diagnostic-view.tsx 移除草稿回退逻辑(不再展示 draft 状态报告)
  • v4-P1-4 + v4-P1-5 改进2026-06-23publishReportAction 班级报告发布时通知全班学生 + 全班学生家长(通过 getStudentIdsByClassId 获取全班学生,通过 parent/data-access.getParentIdsByStudentIds 获取家长 ID个人报告通知学生本人 + 其家长
  • v4-P1-8 + v4-P1-11 改进2026-06-23class-diagnostic-view.tsx 热力图添加图例 + 表格 overflow-x-auto 水平滚动
  • v4-P1-9 改进2026-06-23parent/diagnostic/page.tsx 新增错误卡片Error Card展示子女诊断查询失败原因
  • v4-P1 数据库变更2026-06-23learningDiagnosticReports 表新增 classId 字段varchar(128), references classes.id, onDelete: set null+ diagnostic_class_idx 索引;DiagnosticReport 接口新增 classId: string | null 字段(班级报告关联班级 ID个人报告为 null
  • v4-P1 师生关系校验2026-06-23teacher/diagnostic/student/[studentId]/page.tsx 新增 class_taught 师生关系校验
  • v4-P2 审计修复2026-06-24P0-1 跨模块 import WidgetBoundary → 提升到 shared/components/widget-boundary.tsxdiagnostic 和 grades 模块统一从 shared 引用
  • v4-P2 审计修复2026-06-24P0-2/P0-3 教师页面标题 + error.tsx 硬编码 → 接入 getTranslations("diagnostic") / useTranslations("diagnostic")
  • v4-P2 审计修复2026-06-24P1-1 parent 错误卡片中英文混用 → 提取完整 i18n 键 error.childLoadFailed / error.childLoadFailedDesc
  • v4-P2 审计修复2026-06-24P1-2 as 类型断言 → 改用类型守卫函数 isReportType / isReportStatus 返回值收窄
  • v4-P2 审计修复2026-06-24P1-3 分享链接指向不存在路由 → 移除分享功能(无报告详情页需求)
  • v4-P2 审计修复2026-06-24P1-4 报告内容硬编码中文 → stats-service 接收 ReportContentTranslations 接口参数actions 层从 next-intl 构建翻译注入
  • v4-P2 审计修复2026-06-24P1-5 Excel 导出表头硬编码 → export.ts 使用 getTranslations("diagnostic.exportContent")buildDiagnosticReportFilename 改为 async
  • v4-P2 审计修复2026-06-24P2-1 班级报告导出缺少明细 → 利用 classId 字段查询 getClassMasterySummary,导出知识点统计 Sheet + 需关注学生 Sheet
  • v4-P2 审计修复2026-06-24P2-2 角色差异通过 props 硬编码 → 新增 role-config.tsDIAGNOSTIC_ROLE_CONFIG 配置对象驱动 practiceHrefBase,组件接受 role prop
  • v4-P2 审计修复2026-06-24P2-3 无年级诊断报告入口 → 新增 gradeId 字段schema + 迁移 0012GradeMasterySummary 类型、getGradeMasterySummary / buildGradeMasterySummary / buildGradeReportContent / generateGradeDiagnosticReport / generateGradeReportAction / GenerateGradeReportSchemai18n 新增 gradeSummary / gradeRecommendation / gradeNoWeakness
  • v4-P2 审计修复2026-06-24P2-4 热力图色块缺少键盘导航 → 添加 tabIndex={0} + focus-visible:ring-2 样式,外层容器 roleimg 改为 group
  • v2-P0-1 审计修复2026-06-253 个子路由 error.tsx 硬编码中文 → teacher/diagnostic/class/[classId]/error.tsx、teacher/diagnostic/student/[studentId]/error.tsx、parent/diagnostic/error.tsx 全部接入 useTranslations("diagnostic"),新增 error.classLoadFailed/Descerror.studentLoadFailed/Descerror.parentLoadFailed/Desc i18n 键
  • v2-P1-1 审计修复2026-06-25getDiagnosticReports 未过滤 grade_managed DataScope 在 data-access-reports.ts 中新增 grade_managed 分支,调用 users/data-access.getUserIdsByGradeId 解析年级学生 ID 后用 inArray 过滤,修复年级主任越权查看全校报告的安全漏洞
  • v2-P1-2 审计修复2026-06-25i18n 弱项标签"弱项(<60%"与代码逻辑(<80%)不一致 → 修正为"弱项(<80%"
  • v2-P1-3 审计修复2026-06-25热力图 aria-label 硬编码中文标点 → 使用 i18n 模板键 classDiagnostic.heatmapCellAriaLabel: "{name}{level}%{label}{mastered}/{total}"
  • v2-P1-4 审计修复2026-06-25组件直接 import actions 无接口抽象 → 新增 services/diagnostic-service.tsDiagnosticService 接口)、services/diagnostic-service-context.tsxDiagnosticServiceProvider + useDiagnosticServiceservices/default-diagnostic-service.ts(默认实现绑定 Server Actionsreport-list.tsx 和 class-diagnostic-view.tsx 改用 Context 注入
  • v2-P1-5 审计修复2026-06-25置信度计算过于简化仅 null/非 null 两级)→ 改为基于知识点数量的多级判断0 insufficient / 1-3 low / 4-8 medium / >8 high
  • v2-P1-6 审计修复2026-06-25无 Error Boundary 包裹独立数据区块 → student-diagnostic-view雷达图/强弱项/最新报告/历史报告)和 class-diagnostic-view概览/热力图/筛选/排名/关注列表/生成报告)各数据区块均用 WidgetBoundary 包裹
  • v2-P2-1 审计修复2026-06-25死 i18n 键未清理 → 移除 9 个 share 相关 i18n 键share/shareAriaLabel/shareTitle 等)
  • v2-P2-2 审计修复2026-06-25教师报告列表页客户端过滤 class_members → 移除客户端 filterDataScope 过滤已在 data-access 层完成
  • v2-P2-3 审计修复2026-06-25export.ts 错误消息硬编码英文 → 新增 DiagnosticExportError 结构化错误码(继承 BusinessErrorthrow new Error("Report not found") 改为 throw new DiagnosticExportError("REPORT_NOT_FOUND", ...)
  • v2-P2-5 审计修复2026-06-25雷达图名称截断无 tooltip → 改用完整 knowledgePoint 作为 angleKey 使 Tooltip 显示完整名称,通过 angleTickFormatter 截断轴上显示文本8 字符+省略号)
  • v2-P2-6 审计修复2026-06-25通知类型复用 "grade" → NotificationType 新增 "diagnostic" 类型,更新 notification-list/notification-dropdown TYPE_ICON 和 TYPE_KEYSdiagnostic/actions.ts 两处 createNotification 改用 type: "diagnostic"
  • v2-P2-7 审计修复2026-06-25无监控埋点接口 → 新增 services/diagnostic-monitor.tsDiagnosticMonitor 接口 + noopDiagnosticMonitor 实现)、services/diagnostic-monitor-context.tsxDiagnosticMonitorProvider + useDiagnosticMonitorservices/monitored-diagnostic-service.tscreateMonitoredDiagnosticService 工厂函数,组合模式包装服务添加埋点),两个 teacher 页面接入 DiagnosticMonitorProvider
  • v2-P2-9 审计修复2026-06-25parent/diagnostic noRecordsTitle 与 noRecordsDescription 重复 → 新增 parent.noReportsTitle / parent.noReportsDescription 独立 i18n 键

文件清单

文件 行数 职责
data-access.ts 477 知识点掌握度查询 + 更新v2-P1-8 累积模式v2-P2-5 事务v2-P2-4 语义修正v2-P1-6 改用 stats-service 纯函数v4-P2-3 新增 getGradeMasterySummary
data-access-reports.ts 256+ 诊断报告 CRUDv2-P2-6 校验v2-P1-6 改用 stats-service 纯函数v4-P1-1getDiagnosticReports 新增 scope 参数支持行级权限过滤v4-P2-3 新增 generateGradeDiagnosticReport
stats-service.ts 388+ 统计计算纯函数v2-P1-6 新增12 个纯函数 + 2 个常量 + 4 个接口v4-P2-3 新增 buildGradeMasterySummary/buildGradeReportContent
actions.ts 274+ 5 个 Server Actionv2-P2-3 删除 2 个死代码读 Actionv4-P1-4/v4-P1-5publishReportAction 新增全班学生 + 家长通知v4-P2-3 新增 generateGradeReportAction
schema.ts 31+ Zod 校验4 个 schemav2-P2-3 删除 2 个死代码 schemav4-P2-3 新增 GenerateGradeReportSchema
types.ts 109+ 类型定义v4-P2-3 新增 GradeMasterySummary 接口)
export.ts 165+ Excel 导出v3-P2-4 新增v4-P2-1 班级报告导出新增知识点统计+需关注学生 Sheeti18n 全覆盖)
role-config.ts 40+ 角色配置驱动v4-P2-2 新增DIAGNOSTIC_ROLE_CONFIG 配置对象,通过 role prop 驱动 practiceHrefBase 等角色差异)
components/class-diagnostic-view.tsx 448+ 班级诊断视图v2-P1-6 热力图 a11yv2-P1-4 i18nv4-P1-8 热力图图例v4-P1-11 表格 overflow-x-autov4-P2-4 热力图色块 tabIndex+focus-visible 键盘导航)
components/student-diagnostic-view.tsx 293+ 学生诊断视图v2-P1-4 i18nv3-P2 新增practiceHrefBase propv4-P1-3 移除草稿回退逻辑v4-P2-2 改为 role prop 配置驱动)
components/mastery-radar-chart.tsx 85 雷达图v2-P1-4 i18n
components/report-list.tsx 445 报告列表v2-P2-7 Label htmlForv2-P1-4 i18nv4-P1-3 移除分享功能避免 404
components/confidence-utils.ts 31 置信度计算v4-P3-7 新增v2-P1-5 改为基于知识点数量的多级判断)
services/diagnostic-service.ts 58 DiagnosticService 接口v2-P1-4 新增:抽象客户端可调用的诊断操作)
services/diagnostic-service-context.tsx 46 DiagnosticServiceProvider + useDiagnosticServicev2-P1-4 新增Context 注入)
services/default-diagnostic-service.ts 86 默认服务实现绑定 Server Actionsv2-P1-4 新增)
services/diagnostic-monitor.ts 95 DiagnosticMonitor 接口 + noopDiagnosticMonitorv2-P2-7 新增:监控埋点扩展点)
services/diagnostic-monitor-context.tsx 56 DiagnosticMonitorProvider + useDiagnosticMonitorv2-P2-7 新增)
services/monitored-diagnostic-service.ts 159 createMonitoredDiagnosticService 工厂v2-P2-7 新增:组合模式包装服务添加埋点)

2.23 settings设置模块— V3 AI 配置统一入口

职责:系统设置(学校信息/安全策略/文件上传/通知配置)+ AI Provider 管理 + 密码修改 + 个人资料 + 主题偏好 + 通知偏好 + 个人信息页(学生/教师概览)+ 学校品牌配置audit-P2-6 新增:登录/注册页 logo/标语/作者。V3 将 AI Provider 配置统一到 /admin/ai-settings 独立页面,移除 /settings?tab=ai 标签页和考试页面内嵌弹窗。V3.1 新增 public/private 可见性模型:管理员发布 public Provider 全员可用,普通用户可创建 private Provider 仅本人可见。audit-P2-6 新增品牌配置:brand-config.ts 纯类型 + 默认值(无 server-only,可被 Client Component 导入)+ data-access-brand.ts 数据访问(server-only,从 system_settingsbrand 分类读取 4 个键)+ actions-brand.ts Server Action + BrandConfigCard 管理员配置组件。

导出函数

  • ActionsgetAiProviderSummariesV3.1 增强:按用户身份过滤 public + own private/ upsertAiProviderActionV3.1 增强visibility 字段,非管理员强制 private/ testAiProviderAction / deleteAiProviderActionV3 新增删除能力)/ canConfigurePublicAiProviderV3.1 新增:检查当前用户是否拥有 AI_CONFIGURE 权限)
  • Actions-passwordchangePasswordAction P1 已修复:使用 requirePermission(USER_PROFILE_UPDATE) + Zod 校验 + DB 操作下沉到 data-access
  • Actions-avatarupdateUserAvatarAction / removeUserAvatarAction P2-8 新增:头像上传/删除,复用 /api/upload 路由)
  • Actions-notificationssendTestNotificationAction P2-10 新增:发送测试通知,占位实现待接入真实通知服务)
  • Actions-system-settingsgetAdminSystemSettingsAction / saveAdminSystemSettingsAction P0-3 新增:管理员系统设置 CRUD4 分类 Zod 校验)
  • Actions-brandgetBrandConfigAction / saveBrandConfigAction audit-P2-6 新增:品牌配置 CRUDrequirePermission(SCHOOL_MANAGE) + Zod 校验 + revalidatePath 刷新 /(auth)/login / /(auth)/register / /admin/settings
  • Actions-securitygetSecurityCenterAction / toggleTwoFactorAction P2-9 新增2FA 状态查询/切换 + 最近登录历史)
  • Data-accessgetAiProviderSummaries(管理员视图,返回全部)/ getAiProviderSummariesForUserV3.1 新增:用户视图,返回 public + own private/ countDefaultAiProviders / getAiProviderForUpdateV3.1 增强:可选 userId 做所有权校验)/ updateAiProviderV3.1 增强visibility 字段)/ createAiProviderV3.1 增强visibility 字段)/ deleteAiProviderV3 新增:事务删除 + 自动转移默认V3.1 增强:可选 userId 做所有权校验)/ getUserPasswordHash / getPasswordSecurityByUserId / updateUserPassword / upsertPasswordSecurityOnPasswordChangeP1 新增,从 actions 下沉)
  • Data-access-system-settingsgetSystemSettingsByCategory / getAllSystemSettings / getSystemSetting / upsertSystemSetting / upsertSystemSettings P0-3 新增system_settings 表 CRUD键值对存储模式 audit-P2-6 SystemSettingCategory 联合类型新增 "brand"
  • Data-access-brandgetBrandConfig / saveBrandConfig audit-P2-6 新增:server-only 数据访问,从 system_settingsbrand 分类并行读取 4 个键 schoolName/logoUrl/testimonialQuote/testimonialAuthor未配置项使用 DEFAULT_BRAND_CONFIG 默认值)
  • Brand-config纯类型server-onlyBrandConfig接口schoolName / logoUrl: string|null / testimonialQuote / testimonialAuthor/ DEFAULT_BRAND_CONFIG默认值schoolName="Next_Edu" / logoUrl=null / 默认英文标语)
  • ComponentsSettingsView统一设置页布局V3 移除 AI 标签页后为 4 标签页 General/Notifications/Appearance/Security角色差异通过 resolveRoleSettingsConfig 配置驱动 + generalExtra props 注入Tab URL 持久化;每个 TabsContent 包裹 SettingsSectionErrorBoundary + Suspense 骨架屏)、SettingsServiceProvider / useSettingsServiceContext 注入 SettingsService 接口,解耦组件对 users/messaging actions 的直接依赖)、SettingsSectionErrorBoundary(分区 Error Boundary局部失败不影响整页QuickLinksCard快捷链接卡片i18n 键驱动)、ProfileStudentOverview / ProfileStudentOverviewSkeleton(学生概览异步 Server Component + 骨架屏)、ProfileTeacherOverview / ProfileTeacherOverviewSkeleton(教师概览异步 Server Component + 骨架屏)、AdminSettingsView P0-3 已修复:从 mock 改为真实数据层; v3 已拆分:编排 4 个子 CardSchoolInfoCard / SecurityPolicyCard / FileUploadCard / NotificationConfigCard v3 新增:从 AdminSettingsView 拆分的 4 个独立 Card 组件,接收 values + onChange propsAvatarUpload P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18nSecurityCenterCard P2-9 新增; v3 已拆分:改为编排器,加载数据 + 渲染两个子区块)、SecurityTwoFactorSection v3 新增2FA 启用/关闭/重新生成备份码,从 SecurityCenterCard 拆分)、SecurityRecentLoginsSection v3 新增:最近登录历史 + 远程登出,从 SecurityCenterCard 拆分)、ThemePreferencesCard P2-11 已增强:集成 LocaleSwitcher 语言切换)、AiProviderSettingsCardV3 增强:新增删除按钮 + AlertDialog 确认V3.1 增强visibility 选择器 + 可见性/归属 Badge v3 已拆分:提取 Selector + DeleteDialog 子组件)、AiProviderSelector v3 新增:服务商选择器 + 密钥状态展示,从 AiProviderSettingsCard 拆分)、AiProviderDeleteDialog v3 新增:删除确认对话框,从 AiProviderSettingsCard 拆分)、BrandConfigCard audit-P2-6 新增:管理员品牌配置卡片,独立表单 + 独立保存,通过 getBrandConfigAction/saveBrandConfigAction 调用,挂载在 AdminSettingsView 主表单之后以避免嵌套 <form>
  • ConfigROLE_SETTINGS_CONFIG / resolveRoleSettingsConfig(配置驱动角色 → 设置视图映射,新增角色只需添加条目)、ProfileOverviewType / resolveProfileOverviewType v3 新增:配置驱动角色 → 概览类型映射,消除 roles.includes 硬编码,优先级 student > teacher > none
  • LibbuildStudentOverviewData / computeStudentStats / sortUpcomingAssignments / filterTodaySchedule / toWeekday(纯数据计算函数,与 UI 分离,便于单元测试)、toSettingItem / SettingItem v3 新增:从 actions-system-settings.ts 提取的纯函数,将表单值序列化为数据库存储格式,便于单元测试)
  • TypesAiProviderSummaryV3.1 增强:新增 visibility/createdBy 字段)/ AiProviderNameV3.2 增强:新增 'ollama' 本地部署支持)/ AiProviderVisibilityV3.1 新增:'public' | 'private'/ AiProviderExistingV3.1 增强:新增 visibility/createdBy 字段)/ SettingsService / ProfileService / NotificationPreferenceService(服务接口定义,用于依赖注入解耦)

依赖关系

  • 依赖:shared/*(含 shared/lib/bcrypt-utils)、@/authmessaging(页面层通过 SettingsService 接口注入,组件层不直接 importusers(页面层通过 SettingsService 接口注入)、classes / homework / dashboardProfileStudentOverview 异步组件获取学生概览数据)、notifications(页面层获取通知偏好)
  • 被依赖:无

已知问题

  • ⚠️ P2混合 5 类职责AI Provider + 密码 + 资料 + 主题 + 通知偏好)
  • P1 已修复:data-access.tsactions.ts 直接使用 db 新建 data-access.ts,所有 DB 操作已下沉
  • P1 已修复:changePasswordAction 使用 requireAuth() 无 Zod 校验 改为 requirePermission(USER_PROFILE_UPDATE) + ChangePasswordSchema Zod 校验 + 并行查询优化
  • audit-P2-4 已修复:changePasswordAction 未检测新密码是否在已知数据泄露中,用户可将弱密码(如 "Pass1234")改回使用validatePassword 校验通过后、DB 查询前调用 checkBreachedPassword(newPassword)命中则拒绝改密并提示用户更换密码fail-open 策略HIBP API 不可用时跳过检查不阻断改密流程
  • audit-P2-6 已修复:AuthLayout 学校品牌信息(学校名/logo/标语/作者)硬编码在组件内,无法由管理员动态配置 新增品牌配置完整链路:brand-config.ts(纯类型 + DEFAULT_BRAND_CONFIG 默认值,无 server-only 可被 Client Component 导入)+ data-access-brand.tsserver-only 数据访问,从 system_settingsbrand 分类读取 4 个键)+ actions-brand.tsrequirePermission(SCHOOL_MANAGE) + Zod 校验 + revalidatePath+ BrandConfigCard(管理员配置卡片,独立表单 + 独立保存,挂载在 AdminSettingsView 主表单之后以避免嵌套 <form>)。SystemSettingCategory 联合类型新增 "brand"。i18n 键 settings.brand.*11 个键)已添加到 zh-CN + en
  • 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 + SettingsSectionSkeletonprofile 页面包裹 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.tsAdminSettingsView 改为真实数据层
  • P2-8 已修复:无头像上传 新增 updateUserAvatar data-access + actions-avatar.ts + AvatarUpload 组件,复用 /api/upload 路由
  • P2-9 已修复:无 2FA / 会话管理 新增 actions-security.ts + SecurityCenterCard 组件2FA 开关占位 + 最近登录历史来自 login_logs 表)
  • P2-10 已修复:通知偏好表单无测试通知按钮 新增 sendTestNotificationAction,每个已启用渠道旁显示测试按钮
  • P2-11 已修复:语言切换未集成到设置页 ThemePreferencesCard 集成 LocaleSwitcher 到 Appearance 标签页
  • v1.1 已修复:/settings 页面 ErrorBoundary 触发Functions cannot be passed directly to Client Components 新增 actions-service.ts"use server" 文件),导出 updateProfileAction + updateNotificationPreferencesAction 两个 Server Action wrapperpage.tsx 直接传递 Server Action 引用;SettingsService 接口的 getProfile/getPreferences 改为可选
  • v1.1 已修复:i18n 键双重 settings. 前缀MISSING_MESSAGE role-settings-config.tsxdescriptionKey 去掉 settings. 前缀
  • v1.1 已修复:AI 标签页 FormLabel 在 Form 上下文外使用getFieldState null ai-provider-settings-card.tsx 中两处 <FormLabel> 改为 <Label>
  • v1.1 已修复:非管理员访问 /settings?tab=ai 显示空白 settings-view.tsx 新增 resolveTab() 函数,对无权限的 tab 回退到 general
  • v1.1 已修复:数据库 notification_preferences 表缺少 quiet_hours_* 字段 通过 ALTER TABLE 添加缺失字段
  • v1.1 已修复:数据库 system_settings 表不存在 通过 CREATE TABLE 创建
  • v3 已修复:ProfileStudentOverview / ProfileTeacherOverview 直接 import classes/homework data-access 新增 data-access-profile-overview.ts 包装层,组件通过本模块 data-access 调用,消除跨模块直接依赖
  • v3 已修复:profile/page.tsx 使用 roles.includes("student") 角色硬编码 新增 config/profile-overview-config.ts 配置驱动映射,消除硬编码
  • v3 已修复:i18n 命名空间不一致profilePage vs profile.page 统一合并到 profile.page,删除顶层 profilePage 节点zh-CN + en
  • v3 已修复:SecurityCenterCard 645 行超过 500 行限制 拆分为 SecurityTwoFactorSection2FA 流程)+ SecurityRecentLoginsSection(登录历史),主文件改为 ~115 行编排器
  • v3 已修复:AiProviderSettingsCard 529 行超过 500 行限制 拆分出 AiProviderSelector(选择器)+ AiProviderDeleteDialog(删除对话框),主文件降至 ~443 行
  • v3 已修复:AdminSettingsView 444 行内联 4 个 Card 拆分为 SchoolInfoCard / SecurityPolicyCard / FileUploadCard / NotificationConfigCard 4 个独立组件,主文件降至 ~191 行
  • v3 已修复:settings/page.tsx metadata 为静态 "Settings" 字符串 改为 generateMetadata + getTranslations("settings"),支持 i18n
  • v3 已修复:toSettingItem 为 actions 文件内私有函数,无法单元测试 提取到 lib/system-settings-utils.ts 并导出,新增 system-settings-utils.test.ts 单元测试覆盖 string/number/boolean/json 4 种类型 + 边界情况

文件清单

文件 行数 职责
actions.ts 160 AI Provider CRUD + 测试P1 已修复,无直接 DB 操作)
actions-password.ts 101 修改密码P1 已修复requirePermission + Zod + data-access audit-P2-4 新增 checkBreachedPassword() HIBP 检测)
actions-avatar.ts 56 头像上传/删除P2-8 新增requirePermission + revalidatePath
actions-notifications.ts 46 发送测试通知P2-10 新增:占位实现待接入真实通知服务)
actions-system-settings.ts 186 管理员系统设置 CRUDP0-3 新增4 分类 Zod 校验 + upsert
actions-security.ts 165 2FA 状态查询/切换 + 最近登录历史P2-9 新增)
actions-service.ts 60 设置页 Server Action wrapperv1.1 新增updateProfileAction + updateNotificationPreferencesAction作为 Server Action 引用传递给 Client Component避免 Server→Client 函数传递违规)
data-access.ts 158 AI Provider CRUD + 密码修改 DB 操作P1 新增)
data-access-system-settings.ts 112 system_settings 表 CRUDP0-3 新增:键值对存储模式; audit-P2-6 SystemSettingCategory 联合类型新增 "brand"
data-access-brand.ts 72 品牌配置数据访问audit-P2-6 新增:server-only,从 system_settingsbrand 分类并行读取 4 个键,未配置项使用 DEFAULT_BRAND_CONFIG 默认值re-exports BrandConfig + DEFAULT_BRAND_CONFIG 类型)
brand-config.ts 25 品牌配置类型 + 默认值audit-P2-6 新增:纯类型无 server-only,可被 Server / Client Component 安全导入;BrandConfig 接口 + DEFAULT_BRAND_CONFIG 默认值)
actions-brand.ts 70 品牌配置 Server Actionaudit-P2-6 新增:getBrandConfigAction / saveBrandConfigActionrequirePermission(SCHOOL_MANAGE) + Zod 校验 + revalidatePath 刷新 /(auth)/login / /(auth)/register / /admin/settings
types.ts 60 类型定义AiProviderSummary + SettingsService/ProfileService/NotificationPreferenceService 接口)
config/role-settings-config.tsx 85 角色设置页配置驱动映射ROLE_SETTINGS_CONFIG + resolveRoleSettingsConfig
lib/student-overview-data.ts 150 学生概览纯数据计算buildStudentOverviewData + computeStudentStats 等,便于单测)
components/settings-view.tsx 236 SettingsView 统一设置页布局5 标签页 + Error Boundary + Suspense + i18n + SecurityCenterCard 集成)
components/settings-service-context.tsx 39 SettingsServiceProvider + useSettingsServiceContext 注入服务接口)
components/settings-section-error-boundary.tsx 64 分区 Error Boundary局部失败不影响整页
components/quick-links-card.tsx 42 快捷链接卡片i18n 键驱动)
components/profile-student-overview.tsx 91 学生概览异步 Server Component + 骨架屏
components/profile-teacher-overview.tsx 115 教师概览异步 Server Component + 骨架屏
components/admin-settings-view.tsx 176 AdminSettingsView 系统设置视图P0-3 已修复:真实数据层 + 4 个 Card + i18n audit-P2-6 在主表单后挂载 BrandConfigCard,独立表单避免嵌套 <form>
components/brand-config-card.tsx 148 品牌配置卡片audit-P2-6 新增:管理员配置学校品牌信息 schoolName/logoUrl/testimonialQuote/testimonialAuthor独立表单 + 独立保存;通过 getBrandConfigAction/saveBrandConfigAction 调用i18n settings.brand.*
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登录/注册/布局)+ 注册 Server Action + 注册 data-access + 登录 2FA 服务编排。

架构变更2026-06-25audit-P0 重构):模块从"纯 UI"升级为完整三层结构。原 app/(auth)/register/page.tsx 内联的 167 行 registerAction 下沉至 modules/auth/actions.ts;原 auth.ts 跨模块依赖 modules/settings/actions-security 的 2FA 校验函数,改为同模块 modules/auth/services/two-factor-service.ts 编排(仍通过 modules/settings/data-access-two-factormodules/settings/lib/totp 获取数据,属合理跨模块 data-access 调用)。

导出函数

  • UI 组件:LoginForm / RegisterForm / AuthLayout audit-P2-6 增强:接受 brand?: BrandConfig propapp/(auth)/layout.tsx Server Component 调用 getBrandConfig() 注入;未配置时使用 DEFAULT_BRAND_CONFIG 默认值;渲染 schoolName / logoUrlimg 或 GraduationCap 图标)/ testimonialQuote / testimonialAuthor
  • Server Actionsactions.tsregisterAction / checkEmailAvailabilityAction
  • Data-accessdata-access.tscreateUser / isEmailAvailable / buildRegisterInput
  • Servicesservices/two-factor-service.tsverifyTwoFactorForLogin / preflightTwoFactorByEmail
  • Schemaschema.tsRegisterSchemaZodsuperRefine 未成年人 guardian 字段条件校验、calcAgeisMinorByBirthDate 纯函数)
  • Typestypes.tsRegisterResult / RegisterInput / EmailAvailabilityResult / RegisterErrorCode

依赖关系

  • 依赖:shared/*(含 shared/lib/rate-limitshared/lib/login-loggershared/lib/bcrypt-utilsshared/lib/http-utils)、modules/settings/data-access-two-factormodules/settings/lib/totp 合理跨模块 data-access 调用)
  • 被依赖:app/(auth)/*src/auth.ts(动态 import services/two-factor-service

已知问题

  • audit-P0-1 已修复:app/(auth)/register/page.tsx 内联 167 行 registerAction 下沉至 modules/auth/actions.ts,页面降至 13 行
  • audit-P0-2 已修复:auth.ts 跨模块依赖 modules/settings/actions-security 改为同模块 services/two-factor-service.ts 编排
  • audit-P0-5 已修复:auth.ts 3 处 as 断言 通过 User 接口扩展(next-auth.d.ts)与 "token" in message 类型收窄消除
  • audit-P1-1 已修复:actions.tsERROR_MESSAGES 硬编码中文字符串 替换为 next-intl getTranslations("auth.register") 翻译键(messageForError()RegisterErrorCode 查表 errors.${code}register-form.tsxlogin-form.tsx 同步迁移至 useTranslations,含 t.rich() 嵌入隐私政策/用户协议链接;新增 preflightTwoFactorAction Server Action 消除 login-form 对 modules/settings/actions-security 的跨模块依赖
  • audit-P1-3 已修复:auth.ts authorize 回调混合 7 类职责107 行) 抽取至 modules/auth/services/login-service.tsauthenticateUser() 函数(输入解析/速率限制/用户查询/账户锁定/密码校验/2FA/角色加载 7 步auth.ts authorize 降至 3 行薄包装(动态 import + 委托调用),可独立测试
  • audit-P1-6 已修复:shared/lib/rate-limit.ts 单实例内存实现 重构为 shared/lib/rate-limit/ 目录:抽象 RateLimiter 接口(limit/reset 均 Promise 返回);默认 MemoryRateLimiter 进程级实现;可选 RedisRateLimiter 基于 @upstash/ratelimit + @upstash/redis(懒加载,按 ${limit}:${windowMs} 缓存 Ratelimit 实例);通过 env.RATE_LIMIT_DRIVER=memory|redis 切换;rateLimit()/resetRateLimit() 改为 Promise 返回以支持异步后端,调用方全部更新为 awaitrateLimitKey/RATE_LIMIT_RULES/rateLimitHeaders 保持同步纯函数
  • audit-P1-7 已修复:JWT 中存储完整 permissions[] 数组67 项约 1.1KB)导致 cookie 体积过大 新增 shared/lib/permission-bitmap.ts,将 67 个权限点编码为 BigInt → base36 字符串(约 13 字符,体积减少 99%)。auth.ts JWT callback 写入 token.permissionsBitmap(不再写 token.permissions 数组);session callback 通过 decodePermissionsBitmap() 还原为 Permission[] 填充 session.user.permissions,业务代码(getAuthContext() / usePermission())无需感知位图存在;proxy.ts 直接调用 hasPermissionInBitmap() 检查单个权限,无需完整解码(性能优化:边缘运行时每请求都经过)。PERMISSION_BITMAP_ORDER 数组顺序固定不可变,新增权限只能追加末尾。next-auth.d.ts JWT 接口新增 permissionsBitmap: stringpermissions 标记为 @deprecated
  • audit-P1-9 已修复:auth 模块未定义任何 trackEvent 事件,登录成功/失败/登出/注册/2FA 启用/禁用/账户锁定/限流触发均无业务埋点 shared/lib/track-event.ts EventName 新增 8 个 auth 事件(auth.signin_success/auth.signin_failure/auth.signout/auth.signup/auth.2fa_enabled/auth.2fa_disabled/auth.account_locked/auth.rate_limited+ trackAuthEvent 便捷函数;auth.ts events.signIn/signOut 调用 trackAuthEventlogin-service.ts 在限流/账户锁定/密码错误/2FA 失败 4 个失败分支调用 trackAuthEventmodules/auth/actions.ts registerAction 成功后调用 trackAuthEvent("auth.signup")modules/settings/actions-security.ts verifyTwoFactorAction/disableTwoFactorAction 调用 trackAuthEvent("auth.2fa_enabled/disabled")
  • audit-P1-10 已修复:proxy.ts SPECIFIC_ROUTE_PERMISSIONS 仅覆盖 3 个 /admin/* 子路径ai-settings/roles/permissions其他 /admin/announcements//admin/audit-logs//admin/users 等子路径全部走 /admin 前缀匹配 SCHOOL_MANAGE 权限,导致拥有 ANNOUNCEMENT_MANAGE/AUDIT_LOG_READ/USER_MANAGE 等细粒度权限的非管理员角色(如教师、年级主任)无法访问这些页面 扩展 SPECIFIC_ROUTE_PERMISSIONS 至 15 个条目,新增 /admin/announcements(ANNOUNCEMENT_MANAGE)、/admin/audit-logs + 3 子路径(AUDIT_LOG_READ)、/admin/elective(ELECTIVE_MANAGE)、/admin/questions(QUESTION_READ)、/admin/users(USER_MANAGE)、/admin/error-book(ERROR_BOOK_ANALYTICS_READ)、/admin/lesson-plans(LESSON_PLAN_READ)、/admin/course-plans(COURSE_PLAN_READ)。精确路由优先级高于前缀匹配,使非管理员角色可直接访问对应权限的 /admin/* 页面
  • audit-P1-12 已修复:proxy.ts 内联 4 个权限映射常量SPECIFIC_ROUTE_PERMISSIONS / ROUTE_PERMISSIONS / DASHBOARD_ROUTE_PERMISSIONS / API_PERMISSIONS新增路由或权限点时必须修改 proxy.ts 逻辑代码,违反配置驱动设计 抽取 shared/lib/route-permissions.ts 配置文件,导出 4 个 Record<string, Permission> 类型常量(重命名 ROUTE_PERMISSIONSROUTE_PREFIX_PERMISSIONSAPI_PERMISSIONSAPI_ROUTE_PERMISSIONS 以更准确表达语义。proxy.ts 从此文件 import新增路由权限只需修改配置文件无需改 proxy.ts 逻辑;所有映射 value 类型从 string 收紧为 Permission,消除 as Permission 断言
  • audit-P2-3 已实施:系统开放注册缺少预分配角色/班级的机制,管理员无法批量引导特定用户(如新教师/家长)注册 新增 invitation_codes 表 + modules/invitation-codes 模块types/schema/lib/code-generator/data-access/actions8 字符安全字母表(剔除 I/O/0/1+ Set 去重 + 批量插入admin 可批量生成邀请码1-100 张/批)并指定角色/限定邮箱/绑定班级/设置过期/备注,生成结果仅此一次返回明文供复制;注册流程集成 invitationCode 字段(可选),registerAction 调用 validateInvitationCode()consumeInvitationCode()(乐观锁 UPDATE ... WHERE usedBy IS NULL),邀请码角色覆盖默认 student 角色admin 管理页 /admin/invitation-codesUSER_MANAGE 权限)含统计卡片/表格/生成对话框/删除确认/复制i18n invitationCodes 命名空间已注册zh-CN + en
  • audit-P2-4 已修复:注册流程未检测密码是否在已知数据泄露中出现,弱密码(如 "Pass1234" 等热门泄露密码)可通过 Zod 最小长度校验 新增 shared/lib/breached-password.ts 接入 Have I Been Pwned APIk-anonymity 范围查询:仅发送 SHA-1 前 5 字符,本地比对完整哈希后缀,不泄露用户密码);registerAction 在邮箱可用性检查后、createUser 前调用 checkBreachedPassword(data.password),命中则返回新错误码 BREACHED_PASSWORD + 写审计日志i18n 键 auth.register.errors.BREACHED_PASSWORD 已添加zh-CN + en。fail-open 策略HIBP API 不可用时(超时/网络错误)返回 checkSkipped=true 跳过检查,不阻断注册流程
  • audit-P2-6 已修复:AuthLayout 学校品牌信息(学校名/logo/标语/作者)硬编码在组件内,无法由管理员动态配置 AuthLayout 接受 brand?: BrandConfig propapp/(auth)/layout.tsx 改为 async Server Component调用 modules/settings/data-access-brand.getBrandConfig() 读取品牌配置并注入;数据库不可用时 try/catch fail-open 使用 DEFAULT_BRAND_CONFIG 默认值,不阻断认证页面渲染。品牌配置由 modules/settings 提供完整 CRUD 链路(详见 settings 模块)

文件清单

文件 行数 职责
actions.ts 161 注册 Server ActionregisterAction + checkEmailAvailabilityAction + preflightTwoFactorActionZod 校验 + 独立 rate limit key register:{ip} + 审计日志 + 结构化错误码 REGISTER_ERROR_CODES + messageForError() i18n 翻译; audit-P1-1 已迁移 next-intl audit-P1-9 注册成功埋点 trackAuthEvent("auth.signup") audit-P2-4 邮箱可用性检查后调用 checkBreachedPassword() HIBP k-anonymity 检测)
data-access.ts 90 注册数据访问(createUser bcrypt cost=12 不自动创建角色;isEmailAvailablebuildRegisterInput 从 FormData 构造 RegisterInput
schema.ts 95 Zod RegisterSchema(含 superRefine 未成年人 guardian 字段条件校验、calcAgeisMinorByBirthDate 纯函数)
types.ts 35 类型定义(RegisterResult / RegisterInput / EmailAvailabilityResult / RegisterErrorCode,纯类型无 server-only
services/two-factor-service.ts 75 登录 2FA 编排(verifyTwoFactorForLogin + preflightTwoFactorByEmail,依赖 modules/settings/data-access-two-factor + modules/settings/lib/totp audit-P0-2 新增)
services/login-service.ts 177 audit-P1-3 新增:登录认证服务(authenticateUser 函数,从 auth.ts authorize 回调提取 7 类职责:输入解析/速率限制/用户查询/账户锁定/密码校验/2FA/角色加载;导出 AuthenticateUserInput / AuthenticatedUser 类型audit-P1-6 后 rateLimit()/resetRateLimit() 调用改为 await
components/auth-layout.tsx 46 认证页面布局audit-P2-6 增强:接受 brand?: BrandConfig prop渲染 schoolName / logoUrlimg 或 GraduationCap 图标 fallback/ testimonialQuote / testimonialAuthor未配置时使用 DEFAULT_BRAND_CONFIG
components/login-form.tsx - 登录表单
components/register-form.tsx - 注册表单(registerAction 通过 prop 注入,含 agreedTerms/agreedGuardian hidden input 确保 Zod 收到 boolean

2.24b onboarding首次登录引导模块

职责:首次登录引导流程(独立路由 /onboarding + Server Action + proxy 重定向v3 对标 PowerSchool/Veracross/Auth0

架构变更2026-06-25audit-P1-2 拆分):原 onboarding-stepper.tsx 单文件 451 行(接近 500 上限)拆分为三层结构:

  • 状态与提交逻辑 → hooks/use-onboarding-form.ts208 行,含所有 React state / 导航 / completeOnboardingAction 调用)
  • 家长子女子表单 → components/parent-children-form.tsx122 行纯受控组件props: childRows/isSubmitting/onUpdate/onAdd/onRemove
  • 主文件 onboarding-stepper.tsx 降至 259 行,成为纯展示编排器
  • ChildRow / EMPTY_CHILD_ROW 类型与常量从组件内提取至 types.ts,便于跨文件复用

导出函数

  • UI 组件:OnboardingStepper / ParentChildrenForm
  • HookuseOnboardingForm / UseOnboardingFormReturn(类型)
  • Server Actionsactions.tsgetOnboardingStatusAction / completeOnboardingAction
  • Data-accessdata-access.tsgetOnboardingStatus / updateUserProfile / bindParentToChild
  • Schemaschema.tsOnboardingSchemaZodv3 重构为 children 数组)
  • Typestypes.tsOnboardingRoleInfo / OnboardingStatus / OnboardingCompleteData / OnboardingFailureItem / BindParentToChildParams / ChildRow / EMPTY_CHILD_ROW

依赖关系

  • 依赖:shared/*(含 shared/lib/auth-guard)、modules/classes(班级码绑定 data-accessmodules/users(用户表读写)
  • 被依赖:app/onboarding/page.tsxproxy.ts(读 token.onboarded

已知问题

  • audit-P1-2 已修复:onboarding-stepper.tsx 451 行接近 500 上限 拆分为 Hook + 子组件 + 编排器三层,主文件降至 259 行
  • audit-P1-8 已修复:bindParentToChild 未做独立速率限制,单次 Action 内可循环调用 10 次,三因子(生日 × 手机后4枚举攻击窗口completeOnboardingAction 中针对家长角色且 children.length > 0 时添加独立速率限制 key onboarding:bind:{parentId},限制每小时 5 次5 × 10 = 50 次尝试/小时,远低于 3.65M 组合枚举所需量级);触发限流时写审计日志 onboarding.bind_rate_limited 并返回中文提示i18n 待后续迁移)
  • audit-P1-13 已修复:步骤列表与角色过滤逻辑硬编码在 use-onboarding-form.tsSTEPS_KEYS 常量 + isAdmin ? filter : identity 三元表达式),新增/调整步骤必须修改 Hook 逻辑 抽取配置驱动设计:在 types.ts 新增 OnboardingStepId / OnboardingStepConfig 类型 + ONBOARDING_STEPS 配置数组 + getOnboardingStepsForRole(role) 纯函数过滤步骤;use-onboarding-form.ts 消费配置派生 stepConfigs/stepKeys/currentStepId/canSkip,不再内联步骤定义;onboarding-stepper.tsxcurrentStepId 渲染内容(替代数字下标 step === N),跳过按钮可见性由配置 canSkip 驱动。附带修复:原 grade_head/teaching_head 在 roleInfo 步骤无内容却无法跳过(canSkipStep2 = isAdmin || isStudent || isTeacher 未包含他们),现配置 applicableRoles: ["teacher","student","parent"] 使他们直接跳过该步骤
  • P2-4 已修复:onboarding-gate.tsx 业务逻辑泄漏到 shared 已删除,迁移至 modules/onboarding/

文件清单

文件 行数 职责
actions.ts - Server ActionsgetOnboardingStatusAction + completeOnboardingAction,含幂等检查/局部错误收集/教师多科目循环绑定/审计日志/audit-P1-8 家长绑定子女独立速率限制 onboarding:bind:{userId} 5次/小时)
data-access.ts - 数据访问(getOnboardingStatus + updateUserProfile + bindParentToChild v3 三因子验证)
schema.ts - Zod OnboardingSchema(含 children 数组,支持多子女)
types.ts - 类型定义(含 ChildRow / EMPTY_CHILD_ROWaudit-P1-2 新增)
hooks/use-onboarding-form.ts 208 audit-P1-2 新增:表单状态与处理逻辑 Hookstep/name/phone/children 状态 + 导航 + URL query 持久化 + completeOnboardingAction 调用)
components/onboarding-stepper.tsx 259 客户端 stepper 编排器4 步audit-P1-2 重构后纯展示)
components/parent-children-form.tsx 122 audit-P1-2 新增:家长子女绑定子表单(纯受控组件)

2.25 layout布局模块

职责:应用骨架(侧边栏 + 顶部导航 + 导航配置 + 多角色切换)。

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

依赖关系

  • 依赖:shared/hooks/use-permission@/authuseSessionmessaging(通知下拉)、shared/components/ui/select(角色切换下拉)
  • 被依赖:app/(dashboard)/layout.tsx

已知问题

  • P2 已修复:用权限反推角色 app-sidebar.tsx 改用 hasRole() 判断角色,并新增多角色切换机制(SidebarContext.currentRole/setCurrentRolenull 表示自动检测;当用户拥有多个角色时在侧边栏底部显示 Select 下拉切换)
  • navigation.ts 无幽灵路由13 个已修复)

文件清单

文件 职责
components/app-sidebar.tsx 侧边栏(根据权限渲染 + 多角色切换下拉)
components/sidebar-provider.tsx 侧边栏状态 ContextcurrentRole/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/page.tsx 学习中心入口(课程/作业/教材 3 个卡片; 2026-06-26 审计修复:权限点改为 CLASS_READ
learning/assignments/page.tsx + loading.tsx 作业列表(含 AssignmentCard 组件)+ 骨架屏
learning/assignments/[assignmentId]/page.tsx + loading.tsx 作业作答/复习 + 骨架屏
learning/courses/page.tsx + loading.tsx 课程列表 + 骨架屏( 2026-06-26 审计修复:权限点改为 CLASS_READ
learning/courses/[classId]/page.tsx 课程详情( 2026-06-26 审计修复新增路由:权限点 CLASS_READ
learning/textbooks/page.tsx + loading.tsx 教材列表 + 骨架屏
learning/textbooks/[id]/page.tsx + loading.tsx 教材阅读 + 骨架屏
schedule/page.tsx + loading.tsx 课表 + 骨架屏( 2026-06-26 审计修复:权限点改为 CLASS_READ
error.tsx 路由组错误边界(提供"重试"按钮)
learning/error.tsx + loading.tsx 2026-06-26 审计修复新增学习中心错误边界shared ErrorState+ 加载骨架
learning/courses/error.tsx 2026-06-26 审计修复新增课程列表错误边界shared ErrorState
learning/courses/[classId]/error.tsx 2026-06-26 审计修复新增课程详情错误边界shared ErrorState
schedule/error.tsx 2026-06-26 审计修复新增学生课表错误边界shared ErrorState

2.27 lesson-preparation备课模块

职责:教师备课,基于教材章节创建课案(V4 纸感重构:三栏布局 StructureTree + PaperEditor + DetailPanel),正文采用 Tiptap 富文本编辑器AnchorMark/AnchorPoint 内联锚点),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布、撤销/重做、AI 反馈/差异化、一致性校验、课时安排。

架构变更2026-06-21编辑器从列表式BlockRenderer + @dnd-kit升级为节点图式NodeEditor + @xyflow/react。数据结构从 v1blocks 数组)升级到 v2nodes + edges 节点图),旧数据通过 migrateV1ToV2() 自动迁移。

架构变更2026-06-23数据结构从 v2 升级到 v3课文锚点画布格式。正文节点textbook_content作为画布中央核心教学节点围绕正文组织。新增锚点系统range/point 两种锚定方式、占位符注入算法、11 种定制节点类型。旧数据通过 migrateV2ToV3() 自动迁移。

架构变更2026-07-04V4 纸感重构):抛弃 React Flow 画布改用三栏布局260px 结构树 + 1fr 纸感编辑器 + 380px 详情面板)。正文采用 Tiptap 富文本编辑器,锚点从字符串偏移升级为 Tiptap MarkAnchorMark 区间锚/AnchorPoint 点锚v3 旧锚点通过 AnchorMigrationBanner 提示失效。doc.version=4editor-slice 移除 updateNodePosition/connect/setEdges/autoLayout。新增 interaction block师生交互对话回合。结构树支持节点展开到正文流expanded-slice。PrintView 改为按 nodes.order 排序渲染。删除 8 个 React Flow 画布和字符串锚点相关文件node-editor/nodes/*/anchor-injector/rf-mappers/auto-layout

数据结构

  • v1已废弃仅向后兼容读取{ version: 1, blocks: Block[] }
  • v2已废弃仅向后兼容读取{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }
  • v3已废弃仅向后兼容读取{ version: 3, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }
    • NodeAnchorv3 字符串偏移锚点({ id, nodeId, type: "range"|"point", start, end?, textPreview?, invalid? }V4 中通过 AnchorMigrationBanner 提示失效
  • v4当前{ version: 4, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }
    • 锚点改为 Tiptap MarkAnchorMark(区间锚)+ AnchorPoint(点锚),存储在正文富文本 doc 中
    • nodes 新增 order 字段,按顺序渲染(替代画布 position
    • InteractionBlockData:师生交互对话回合({ designIntent, turns: QATurn[], expectedAnswer? }

导出函数

  • Data-accessdata-access.tsgetLessonPlansV4查询后按 textbookId+chapterId+creatorId 聚合版本,返回代表项 + versionCount + versions 摘要数组)/ getLessonPlanById / createLessonPlan / updateLessonPlanContent / softDeleteLessonPlan / duplicateLessonPlan / getTemplateById / buildInitialContent / migrateV1ToV2 / normalizeDocumentv4 规范化,兼容 v1/v2/v3 旧数据)/ buildDefaultSkeleton(默认骨架)/ getTextbooksForPicker / getChaptersForPicker / findChapterById / publishLessonPlan / unpublishLessonPlan
  • Liblib/document-migration.tsdefaultDataForType / migrateV1ToV2 / migrateV2ToV3 / normalizeDocument / buildInitialContent / buildDefaultSkeleton / isTextbookContentNode / isAnchorEdge / getAnchorsForNode / getActiveAnchorIds / getAnchorEdges
  • Liblib/anchor-mark.tsV4 新增)Tiptap AnchorMark 扩展(区间锚)+ AnchorPoint 扩展(点锚),替代 v3 字符串偏移锚点
  • Liblib/node-summary.tsgetNodeSummary / getTextbookContentSummary / getNodeColor / NODE_COLORS
  • Liblib/export.tsV5-4 新增 + V4 增强 + i18n 注入):flattenLessonPlanForPrint / flattenBlockDataV4新增 interaction case + flattenInteraction 函数)/ PrintableLessonPlan 类型 / TranslationFn 类型V5-4 i18n所有 label 通过翻译函数注入,调用方 print-view.tsx 注入 useTranslations
  • Data-access-versionsdata-access-versions.tsgetLessonPlanVersions / createLessonPlanVersion / getVersionContent / revertToVersion / pruneAutoVersions
  • Data-access-templatesdata-access-templates.tsgetLessonPlanTemplates / saveAsTemplate / deletePersonalTemplate
  • Data-access-knowledgedata-access-knowledge.tsgetLessonPlansByKnowledgePoint / getLessonPlansByQuestion
  • Publish-servicepublish-service.tspublishLessonPlanHomework
  • AI-suggestai-suggest.tssuggestKnowledgePoints
  • ActionsgetLessonPlansAction / getLessonPlanByIdAction / createLessonPlanAction / updateLessonPlanAction / saveLessonPlanVersionAction / getLessonPlanVersionsAction / revertLessonPlanVersionAction / deleteLessonPlanAction / duplicateLessonPlanAction / getLessonPlanTemplatesAction / saveAsTemplateAction / deleteTemplateAction / suggestKnowledgePointsAction / publishLessonPlanHomeworkAction / getKnowledgePointOptionsAction / getTextbooksForPickerAction / getChaptersForPickerAction / publishLessonPlanAction / unpublishLessonPlanAction

依赖关系

  • 依赖:shared/*@/authshared/lib/ai@tiptap/react@tiptap/starter-kit@tiptap/pm@tiptap/extension-imageV4Tiptap 富文本编辑器)、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
  • V4 纸感重构:三栏布局 StructureTree + PaperEditor + DetailPanel正文采用 Tiptap 富文本编辑器,锚点升级为 Tiptap MarkAnchorMark/AnchorPoint
  • V4删除 8 个 React Flow 画布和字符串锚点相关文件node-editor/nodes/*/anchor-injector/rf-mappers/auto-layout无残留引用
  • V4block-renderer.tsx@deprecated已删除无残留引用

架构变更2026-06-22本次审计修复

  • P0-1 跨模块直查修复publish-service.ts 不再直接 db.insert(examQuestions) 和本地实现 getStudentIdsByClassIds,改为调用 exams/data-access.addExamQuestionsclasses/data-access.getStudentIdsByClassIds,恢复三层架构约束
  • P0-2 i18n 接入:新增 shared/i18n/messages/zh-CN/lesson-preparation.jsonshared/i18n/messages/en/lesson-preparation.json,注册 lessonPreparation 命名空间到 src/i18n/request.ts17 个组件改造为 useTranslations/getTranslations
  • P0-3 DataScope 过滤修复data-access.tsbuildScopeCondition 按 scope 类型精确过滤——class_taught 增加 subjectId IN teacher.subjects AND gradeId IN teacher.gradesgrade_managed 限制 gradeId IN managedGradesclass_members/children 仅允许查看 published 课案
  • P1-1 类型安全修复as never 断言全部替换为类型守卫函数(isQuestionType/isV1Document/isV2Document+ validTypes 数组;block-renderer.tsx 使用 as ExerciseBlockData/as TextStudyBlockData 精确断言;constants.tsBLOCK_TYPE_LABELS/LESSON_PLAN_STATUS_LABELS 改为 i18n 键(BLOCK_TYPE_KEYS/LESSON_PLAN_STATUS_KEYS
  • P1 纯函数抽取:新增 lib/document-migration.tsmigrateV1ToV2/normalizeDocument/buildInitialContent使用类型守卫替代 as 断言)、lib/node-summary.tsgetNodeSummary + NODE_COLORS + getNodeColor接受翻译函数注入lib/rf-mappers.tstoRfNodes/toRfEdges/fromRfEdgesdata-access.ts 改为从 lib/ 导入并 re-export 保持向后兼容
  • P1-2/P1-3 错误边界 + 骨架屏:新增 components/lesson-plan-error-boundary.tsxLessonPlanErrorBoundary 类组件)和 components/lesson-plan-skeleton.tsxVersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton
  • P1-4 阻塞式 UI 修复alert() 全部替换为 sonner toastconfirm() 全部替换为 AlertDialogshadcnwindow.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.tsxLessonPlanProvider + Context + 4 个角色配置 TEACHER/ADMIN/STUDENT/PARENT + LessonPlanDataService 接口 + LessonPlanTracker 埋点接口 + useLessonPlanContextSafe/useRoleConfig 等 hooksservices/default-data-service.ts(包装 Server Actions 为 DataService 实现lesson-plan-list/lesson-plan-card 通过 Context 注入数据服务useRoleConfig 控制按钮可见性
  • P1-8 Block 注册表:新增 config/block-registry.tsxBLOCK_REGISTRY 配置表 + getBlockComponent/isRichTextBlocknode-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-labelinline-question-editor 添加 role="dialog"/aria-modal/aria-label
  • P2-4 监控埋点预留providers/lesson-plan-provider.tsx 定义 LessonPlanTracker 接口 + noopTracker 默认空实现,生产环境可替换为真实埋点

架构变更2026-06-23本次审计修复

  • Zod schema 补全schema.ts 新增 publishLessonPlanHomeworkSchemaplanId/blockId 必填classIds 至少 1 个availableAt/dueAt 日期格式校验),导出类型 PublishLessonPlanHomeworkInput3 个 action 文件actions/actions-publish/actions-ai补全 Zod 校验
  • data-access-versions.ts 事务修复revertToVersion 包裹 db.transaction 确保原子性(回滚版本时同步更新 lessonPlans.content 与版本记录)
  • 统一错误处理:所有 Server Action catch 块改用 handleActionErrorJSON.parse 改用 safeJsonParse
  • block-renderer 拖拽 BUG 修复:修复拖拽时节点位置计算错误

架构变更2026-06-23V3 多角色课案查看):

  • V3-1 课案发布/撤回:新增 publishLessonPlan(planId, userId) / unpublishLessonPlan(planId, userId) data-access 函数 + publishLessonPlanAction(planId) / unpublishLessonPlanAction(planId) Server Actions均 requirePermission(LESSON_PLAN_PUBLISH)),支持课案 status=published 供学生/家长/教研组长只读查看
  • V3-2 只读画布组件:新增 LessonPlanReadonlyView,复用 React FlownodesDraggable=false, nodesConnectable=false供学生/家长/管理员/教研组长查看已发布课案
  • V3-3 多角色视图LessonPlanCard 新增 viewMode propteacher/student/parent/admin/gradeHead动态跳转链接 + 发布/撤回按钮;LessonPlanEditor 新增 initialStatus prop + 发布/撤回按钮AlertDialog 确认);LessonPlanList 新增 viewMode prop
  • V3-4 锚点选择器重写TextbookContentNode 新增 props: anchorableNodes, onCreateNewNodeAnchorNodeSelector 重写为节点列表+创建新节点选项;NodeEditPanel 选中 textbook_content 时显示操作提示 + 锚点列表(含删除功能)
  • V3-5 模板分区显示TemplatePicker 加载并显示个人模板(调用 getLessonPlanTemplatesAction分区显示系统/个人模板
  • V3-6 rf-mappers 增强toRfNodes ctx 新增 anchorableNodesonCreateNewNode 字段;toRfEdges 锚点边颜色使用 getNodeColor(anchor.nodeId) 替代硬编码anchorId 存入 edge.datafromRfEdgese.data.anchorId 读取 anchorId回退到 className 判断
  • V3-7 权限扩展student/parent/grade_head/teaching_head 角色新增 LESSON_PLAN_READ 权限,可查看已发布课案
  • V3-8 DataScope 扩展class_memberschildren 新增可选 gradeIds?: string[] 字段auth-guard resolveDataScope 中 student 通过 classEnrollments.innerJoin(classes) 预解析 gradeIdsparent 通过孩子的 classEnrollments.innerJoin(classes) 预解析 gradeIds
  • V3-9 新增路由/student/lesson-plans/student/lesson-plans/[planId]/view/parent/lesson-plans/parent/lesson-plans/[planId]/view/admin/lesson-plans/admin/lesson-plans/[planId]/view
  • V3-10 导航变更admin 导航新增「课案管理」(/admin/lesson-plansstudent 导航新增「我的课案」(/student/lesson-plansparent 导航新增「孩子课案」(/parent/lesson-plans

架构变更2026-06-24V3 深度审计修复):

  • P0-1 admin 直查 DB 修复admin/lesson-plans/page.tsx 移除 import { db }import { lessonPlans },改用 data-access 新增的 getLessonPlanStats() 函数(返回 total/published/draft/archived 统计)
  • P0-2 publish-service 硬编码中文 + as 断言修复publish-service.ts 移除 as ExerciseBlockData/as typeof validTypes[number] 断言,改用 lib/type-guards.tsisExerciseBlockData/isValidQuestionType 类型守卫;移除硬编码中文标题/描述,改为接受 homeworkTitle/homeworkDescription 参数由 actions 层 i18n 翻译后传入
  • P0-3 duplicateLessonPlan 硬编码修复duplicateLessonPlan 接受 duplicateSuffix 参数(默认 " - Copy"),由 actions 层传入 t("error.duplicateSuffix")
  • P0-4 schema Zod 错误消息 i18n 化schema.ts 所有错误消息从硬编码中文改为 i18n 键(如 error.titleRequired);新增 lib/i18n-errors.tstranslateFieldErrors/safeParseWithI18n)在 actions 层翻译 Zod 错误
  • P0-5 loading/error 边界补全:为 6 个路由新增 12 个 loading.tsxerror.tsx 文件teacher/lesson-plans、teacher/lesson-plans/new、teacher/lesson-plans/[planId]/edit、admin/lesson-plans、admin/lesson-plans/[planId]/view、student/lesson-plans、student/lesson-plans/[planId]/view、parent/lesson-plans、parent/lesson-plans/[planId]/view
  • P1-2 组件完全通过 service 调用lesson-plan-card.tsx/lesson-plan-list.tsx/lesson-plan-editor.tsx 移除所有直接 import { xxxAction } from "../actions",改为通过 useLessonPlanContextSafe() 获取 service 调用;LessonPlanDataService 接口扩展 5 个方法getLessonPlanById/updateLessonPlan/saveLessonPlanVersion/publishLessonPlan/unpublishLessonPlandefault-data-service.ts 实现扩展
  • P1-3 as 断言修复:新增 lib/type-guards.ts 集中类型守卫11 种 BlockData 类型守卫 + 节点/题目类型守卫);block-registry.tsx 所有 as XxxBlockData 替换为类型守卫;node-edit-panel.tsx 移除冗余 as LessonPlanNodeTypeScript 判别联合自动收窄);node-editor.tsx MiniMap nodeColor 使用类型守卫替代 as { node?: AnyLessonPlanNode }rf-mappers.ts 移除冗余 as TextbookContentNode/as LessonPlanNodeuse-lesson-plan-editor.ts updateNode patch 类型改为 Omit<Partial<Block>, "type"> 防止类型变更
  • P1-4 MiniMap 颜色硬编码修复lesson-plan-readonly-view.tsx MiniMap 移除硬编码 #455a64/#1976d2,改用 getNodeColor(nodeData.type) 与编辑器保持一致
  • i18n 错误码扩展zh-CN/lesson-preparation.jsonen/lesson-preparation.json 新增 publish.homeworkTitle/publish.homeworkDescription/error.invalidQuestionType/error.duplicateSuffix/error.titleRequired/error.titleTooLong/error.templateRequired/error.invalidDate/error.classRequired 等键

架构变更2026-06-24V3 深度审计修复续):

  • P0 硬编码中文修复6处actions.tsgetLessonPlanByIdAction/revertLessonPlanVersionAction 错误消息改为 i18n 键(t("error.notFound")/t("error.versionNotFound")data-access-versions.tsrevertToVersion 接受 revertLabel 参数替代硬编码 `回退到 v${versionNo}`lesson-plan-error-boundary.tsx 重写为包装组件模式(内部类组件接受 errorText/retryText props外部函数组件通过 useTranslations 注入 i18n 文案);lesson-plan-provider.tsxuseLessonPlanContext 错误消息改为英文
  • P1 组件完全通过 service 调用5个组件迁移version-history-drawer.tsx/template-picker.tsx/knowledge-point-picker.tsx/publish-homework-dialog.tsx/question-bank-picker.tsx 移除所有直接 import { xxxAction } from "../actions",改用 useLessonPlanContextSafe() 获取 service 调用;LessonPlanDataService 接口扩展 7 个方法createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions+ 6 个导出类型TextbookPickerOption/ChapterPickerOption/KnowledgePointOption/PublishHomeworkInput/QuestionPickerParams/QuestionPickerItemdefault-data-service.ts 实现扩展;新增 providers/lesson-plan-provider-setup.tsx(页面层 Provider 设置包装组件自动注入默认数据服务和角色配置3 个页面teacher/lesson-plans/page、teacher/lesson-plans/new/page、teacher/lesson-plans/[planId]/edit/pageLessonPlanProviderSetup 包裹;question-bank-picker.tsx 新增 isQuestionType 类型守卫替代 as QuestionType 断言
  • P2 文件拆分textbook-content-node.tsx 从 578 行拆分为 3 个文件——主文件471行+ anchor-node-selector.tsxAnchorNodeSelector 组件62行+ textbook-segments.tsxrenderSegments 函数75行
  • P3 as 断言修复8处textbook-content-node.tsxas unknown as TextbookContentNodeProps 替换为 isTextbookContentNodePropsData 类型守卫7 个 block 组件blackboard/import/exercise/objective/key-point/homework/reflection的 select onChange as 断言替换为类型守卫——lib/type-guards.ts 新增 7 个字段值类型守卫isBlackboardLayout/isImportMethod/isExercisePurpose/isObjectiveDimension/isKeyPointType/isHomeworkType/isReflectionAspect

架构变更2026-06-25V3 续审计 P1/P2 修复):

  • P1-1a node-summary.ts as 断言修复:移除 node.data as { html?: string; ... } 断言,改用 isRecord/getArrayLength/getString/getNumber 安全字段提取辅助函数从 unknown 收窄
  • P1-1b ai-suggest.ts as 断言修复:移除 as Record<string, unknown> 断言,改用 isRecord 类型守卫;extractNodeTextunknown 节点安全提取 html/sourceText 字段
  • P1-1c data-access-templates.ts + data-access.ts as 断言修复:新增 normalizeTemplateBlocks 规范化函数(lib/type-guards.ts),替代 as LessonPlanTemplate["blocks"]as unknown as LessonPlanTemplate["blocks"] 断言从 DB unknown 安全转换
  • P1-1d inline-question-editor.tsx as 断言修复:移除 as typeof QUESTION_TYPES[number] 断言,改用 isInlineQuestionType 类型守卫
  • P1-1e data-access-knowledge.ts as 断言修复:移除两处 as unknown as 断言,改用 getKnowledgePointIds/getQuestionIds 安全字段提取辅助函数
  • P1-2/P1-3 actions-ai.ts + actions-kp.ts 隐式 any 修复:显式类型标注 const nodes: unknown[]let kps: KnowledgePointOption[] 替代隐式 any统一使用 handleActionError(e) + translateFieldErrors 处理错误
  • P1-6 findChapterById 提取到共享工具:新增 textbooks/utils.tsfindChapterById 递归函数,消除 4 个页面文件admin/student/parent/teacher lesson-plans view/edit page+ data-access.ts 共 5 处重复本地递归实现
  • P1-7 inline-question-editor.tsx FocusTrap:包裹 FocusTrapshared/components/a11y/focus-trap.tsx)实现焦点陷阱
  • P1-8 a11y + 类型守卫修复:补全 aria-labelinline-question-editor 选项/删除/知识点按钮)+ 新增 optionLabel i18n 键;lesson-node.tsx 使用 isLessonNodeData 类型守卫替代 as { node: LessonPlanNode } 断言 + 空状态回退;template-picker.tsx 通过 ChapterPickerOption.children 递归类型消除 as ChapterPickerOption[] 断言
  • P2-2 lesson-plan-card.tsx useCallback 性能优化4 个异步处理函数handleArchive/handleDuplicate/handlePublish/handleUnpublishuseCallback 包裹
  • P2-4 lesson-plan-list.tsx 筛选加载状态:增加 isLoading 状态,传递给 LessonPlanFilterslesson-plan-filters.tsx 加载时禁用输入并设置 aria-busy
  • P2-6 ai-suggest.ts AI prompt 提取为常量:新增 AI_SUGGEST_PROMPT_TEMPLATE 常量,便于维护和未来国际化

架构变更2026-06-25V4 深度审计修复):

  • P0-1 跨模块越权修复parent/lesson-plans/[planId]/view/page.tsxstudent/lesson-plans/[planId]/view/page.tsx 新增 assertPlanInScope(planId, userId) 调用,防止越权访问非本人/子女范围课案
  • P0-2 权限校验补全9 个 lesson-plans 路由 page.tsxteacher/admin/student/parent新增 requirePermission(LESSON_PLAN_READ) 调用
  • P0-3 锚点 a11y 修复textbook-segments.tsx 锚点 span 补全 role="button"/tabIndex={0}/aria-label/键盘事件处理
  • P0-4 跨模块解耦:新增 default-data-service.tsgetQuestions 方法 + QuestionService 接口;question-bank-picker.tsx 移除直接 import @/modules/questions/actions,改为通过 useQuestionService() 注入;lesson-plan-provider-setup.tsx 自动注入默认 QuestionService
  • P1-1 FocusTrap + ESC4 个 dialog/drawerpublish-homework-dialog/knowledge-point-picker/version-history-drawer/question-bank-picker包裹 FocusTrapshared/components/a11y/focus-trap.tsx+ 添加 ESC 键监听关闭
  • P1-2 骨架屏替换加载文本3 个 picker/drawer 的 {loading ? <p>{t("loading")}</p>} 替换为 <VersionListSkeleton/>/<QuestionBankSkeleton/>/<KnowledgePointSkeleton/>
  • P1-3 ErrorBoundary 独立包裹9 处子组件渲染NodeEditor/VersionHistoryDrawer/QuestionBankPicker/InlineQuestionEditor/PublishHomeworkDialog/KnowledgePointPicker独立用 <LessonPlanErrorBoundary> 包裹,避免单个区块错误导致整个编辑器崩溃
  • P2-1 并行查询3 个 view 页teacher/admin/student/parent lesson-plans/[planId]/view)的 getTextbookById + getChaptersByTextbookId 改为 Promise.all 并行
  • P2-2 notFound() 统一3 个 view 页 if (!plan) return <div>...</div> 改为 notFound(),与 Next.js 约定一致
  • P2-3 async IIFE 替代 Promise 链3 个组件version-history-drawer/knowledge-point-picker/question-bank-pickerPromise.resolve().then()/queueMicrotask 改为 useEffect + async IIFE + cancelled flag避免竞态
  • P2-4 废弃文件删除components/block-renderer.tsx@deprecated无源码引用删除
  • P2-5 anchor-injector regex 一致性markdownToPlainText/^>\s+/gm 改为 /^\s*>\s+/gmskipPatterns 5 个 regex 统一添加 g 标志,与 markdownToPlainText 保持一致
  • P2-6 Zustand store 拆 slicehooks/use-lesson-plan-editor.ts 从 303 行单体 store 拆分为 3 个独立 slice——hooks/editor-slice.ts(文档结构 + 文档操作)/hooks/selection-slice.ts(选中状态)/hooks/version-slice.ts(版本与保存状态);主文件重写为 25 行薄层,组合 3 个 slice 并导出 EditorState 类型
  • type-guards 导出补全lib/type-guards.tsVALID_QUESTION_TYPES 常量从模块私有改为 export(供 inline-question-editor.tsx 复用,消除重复定义)

文件清单

文件 职责
types.ts 类型定义(含 v1/v2/v3 文档类型、TextbookContentNode、LessonPlanNode、NodeAnchor、AnchorEdge、FlowEdge、11 种 BlockData 接口)
constants.ts 常量定义
schema.ts Zod 验证V3错误消息改为 i18n 键,如 error.titleRequired
lib/type-guards.ts 集中类型守卫V3 新增)11 种 BlockData 类型守卫isRichTextBlockData/isTextStudyBlockData/isExerciseBlockData/isObjectiveBlockData/isKeyPointBlockData/isImportBlockData/isNewTeachingBlockData/isSummaryBlockData/isHomeworkBlockData/isBlackboardBlockData/isReflectionBlockData+ 节点类型守卫isTextbookContentNode/isLessonPlanNode+ 题目类型守卫isValidQuestionType + V4 导出 VALID_QUESTION_TYPES 常量 + ValidQuestionType 类型+ 基础类型守卫isLessonPlanStatus/isTemplateType/isTemplateScope/isBlockType/isReviewDecision+ Block 字段值类型守卫V3 续新增)isBlackboardLayout/isImportMethod/isExercisePurpose/isObjectiveDimension/isKeyPointType/isHomeworkType/isReflectionAspect用于 select onChange 替代 as 断言)+ normalizeTemplateBlocks 规范化函数V3 续审计新增):从 DB unknown 安全转换为 TemplateBlockSkeleton[],替代 as LessonPlanTemplate["blocks"] 断言 + 类型守卫专项重构2026-07-04:所有 isXxxBlockData 守卫参数从 BlockData 拓宽为 unknown(守卫内部已用 isObject 检查,安全;使守卫可从 unknown 直接收窄,支持 AnyLessonPlanNode.data 联合类型);新增 mergeBlockDataPatch(node, patch) 辅助函数 + BLOCK_DATA_GUARDS 注册表BlockType → 守卫映射),替代 AI patch 合并中的 { ...node.data, ...patch } as BlockData 断言,校验失败回退原 data 保证编辑器完整性
lib/i18n-errors.ts Zod 错误 i18n 翻译辅助V3 新增)translateFieldErrors(将 Zod fieldErrors 中的 i18n 键翻译为实际消息)/ safeParseWithI18n(安全解析 Zod 结果并返回带翻译的 ActionState 错误格式)
lib/document-migration.ts 纯函数v1→v2migrateV1ToV2/ v2→v3migrateV2ToV3/ 规范化normalizeDocument兼容 v1/v2/v3/v4/ 初始内容buildInitialContent/ 默认骨架buildDefaultSkeleton/ defaultDataForType / 工具函数isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges
lib/anchor-mark.ts V4 新增Tiptap AnchorMark 扩展(区间锚)+ AnchorPoint 扩展(点锚),替代 v3 字符串偏移锚点;存储在正文富文本 doc 中
lib/export.ts V5-4 新增 + V4 增强flattenLessonPlanForPrint(按 nodes.order 排序扁平化为 PrintableLessonPlan+ flattenBlockDataV4新增 interaction case + flattenInteraction 函数)+ PrintableLessonPlan 类型;支持 detailed/concise 两种 variant
lib/node-summary.ts 纯函数getNodeSummary支持 12 种节点类型,含 V4 interaction+ getTextbookContentSummary + NODE_COLORS + getNodeColorV3 续审计:移除 as { html?: string; ... } 断言,改用 isRecord/getArrayLength/getString/getNumber 安全字段提取从 unknown 收窄)
config/block-registry.tsx 配置驱动BLOCK_REGISTRY 注册表 + BlockRendererV3使用 lib/type-guards.ts 类型守卫替代所有 as XxxBlockData 断言)
providers/lesson-plan-provider.tsx Provider + ContextLessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口V3扩展 5 个方法 getLessonPlanById/updateLessonPlan/saveLessonPlanVersion/publishLessonPlan/unpublishLessonPlanV3 续扩展 7 个方法 createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions + 6 个导出类型、4 个角色配置、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTrackerhooksuseLessonPlanContextSafe/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe
providers/lesson-plan-provider-setup.tsx 页面层 Provider 设置包装组件V3 续新增)LessonPlanProviderSetup 自动注入默认数据服务createDefaultDataService和角色配置TEACHER_ROLE_CONFIG3 个 teacher 页面用此组件包裹
services/default-data-service.ts 默认数据服务实现createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现V3实现扩展的 5 个方法;V3 续扩展 7 个方法 createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions),测试可替换为 mock
data-access.ts 课案 CRUD + 模板查询V3新增 getLessonPlanStats() 统计函数 + LessonPlanStats 接口;duplicateLessonPlan 接受 duplicateSuffix 参数消除硬编码V3 续审计:使用 normalizeTemplateBlocks 替代 as unknown as LessonPlanTemplate["blocks"] 断言;从 textbooks/data-access 导入 findChapterById 消除本地重复实现)
data-access-versions.ts 版本管理(创建/查询/回滚/清理)
data-access-templates.ts 个人模板 CRUDV3 续审计:使用 normalizeTemplateBlocks 替代 as LessonPlanTemplate["blocks"] 断言)
data-access-knowledge.ts 按知识点/题目反查课案V3 续审计:移除两处 as unknown as 断言,改用 getKnowledgePointIds/getQuestionIds 安全字段提取辅助函数)
actions.ts 课案 CRUD/版本/模板 Server ActionsV3所有 action 使用 translateFieldErrors 翻译 Zod 错误;duplicateLessonPlanAction 传入 i18n 翻译的副本后缀;getLessonPlanByIdAction 返回类型改为 ActionState<{ plan: LessonPlan }>
actions-publish.ts 发布作业 Server ActionV3actions 层注入 i18n 翻译的作业标题/描述/日期标签;新增 INVALID_QUESTION_TYPE 错误码映射)
actions-ai.ts AI 知识点建议 Server ActionV3 续审计:显式 unknown[] 类型标注替代隐式 any统一使用 handleActionError + translateFieldErrors 处理错误)
actions-kp.ts 知识点选项 Server ActionV3 续审计:显式 KnowledgePointOption[] 类型标注替代隐式 any统一使用 handleActionError + translateFieldErrors 处理错误)
publish-service.ts 发布作业服务V3移除 as 断言改用类型守卫;移除硬编码中文,接受 homeworkTitle/homeworkDescription 参数;新增 INVALID_QUESTION_TYPE 错误码)
ai-suggest.ts AI 知识点建议服务V3 续审计:移除 as Record<string, unknown> 断言改用 isRecord 类型守卫AI prompt 提取为 AI_SUGGEST_PROMPT_TEMPLATE 模块常量)
seed-templates.ts 模板种子数据
hooks/use-lesson-plan-editor.ts 课案编辑器 Zustand store 主文件V4从 303 行单体 store 重写为 25 行薄层,组合 3 个 slice 并导出 EditorState 类型)V3updateNode patch 类型改为 Omit<Partial<Block>, "type"> 防止类型变更)
hooks/editor-slice.ts 编辑器 sliceV4 P2-6 新增 + V4 纸感重构)Zustand StateCreator,包含 planId/title/doc 状态及所有文档操作方法addNode/updateNode/removeNode/updateTextbookContent/getTextbookContentNode/addAnchor/removeAnchor/updateAnchor/setTitle/setPlanIdV4 移除updateNodePosition/connect/disconnect/setEdges/autoLayout画布相关方法
hooks/selection-slice.ts 选中状态 sliceV4 P2-6 新增)Zustand StateCreator,包含 selectedNodeId 状态 + selectNode 方法
hooks/version-slice.ts 版本与保存状态 sliceV4 P2-6 新增)Zustand StateCreator,包含 isDirty/isSaving/lastSavedAt/saveError/isOnline 状态 + hydrate/markSaved/setSaving/replaceDoc/setSaveError/setOnline 方法
hooks/history-slice.ts 撤销/重做 sliceV5-2 新增)Zustand StateCreatorpast/future 栈50 步上限)+ canUndo/canRedo/pushHistory/undo/redo/clearHistory 方法
hooks/expanded-slice.ts V4 纸感重构新增Zustand StateCreator节点展开到正文流状态expandedNodeIds/toggleExpand/setExpanded
components/lesson-plan-list.tsx 课案列表V3完全通过 service 调用,移除直接 import getLessonPlansActionV3 续审计:新增 isLoading 状态,加载时显示加载文本,传递给 LessonPlanFilters
components/lesson-plan-card.tsx 课案卡片V3完全通过 service 调用,移除直接 import actionsV3 续审计4 个异步处理函数用 useCallback 包裹性能优化)
components/lesson-plan-filters.tsx 课案筛选器V3 续审计:新增 isLoading prop加载时禁用输入并设置 aria-busy
components/lesson-plan-editor.tsx 课案编辑器V3完全通过 service 调用;V4 纸感重构:移除 NodeEditor/NodeEditPanel改用三栏布局 StructureTree + PaperEditor + DetailPanel保留顶部工具栏 + 5 个对话框)
components/lesson-plan-readonly-view.tsx 只读视图V3MiniMap 使用 getNodeColor 替代硬编码颜色;V4:移除 React Flow 依赖,委托 LessonPlanMobileView 线性卡片视图渲染所有视口)
components/anchor-migration-banner.tsx V4 新增v3 锚点失效提示 banner检测 doc.anchorsinvalid:true 或含 start 字段的旧锚点;通过 key={planId} 重新挂载 + lazy initial state 读取 localStorage 避免 setState in effect
components/print-view.tsx V5-4 新增 + V4 适配:打印视图组件,按 nodes.order 排序渲染,详细版/简洁版切换 + window.print()
components/schedule-dialog.tsx V5-7 新增:课案绑定课时对话框
components/consistency-check-dialog.tsx V5-19 新增:目标-评价一致性校验对话框
components/ai-feedback-dialog.tsx V5-17 新增AI 反馈闭环对话框4 维度 strengths/improvements/alignment/differentiation
components/ai-differentiation-dialog.tsx V5-21 新增AI 差异化教学对话框3 Tab差异化建议/课标核对/可解释评估)
components/curriculum-heatmap.tsx V5-20 新增:课标覆盖热力图组件
components/version-diff-view.tsx V5-16 新增:版本对比视图组件
components/lesson-plan-mobile-view.tsx V5-13 新增:移动端只读视图(按教学阶段分组线性卡片视图);V4:被 lesson-plan-readonly-view.tsx 委托渲染所有视口
components/attachment-picker.tsx V5-5 新增:素材库 picker复用 use-file-upload + M6 附件表 data-access
components/paper-editor/paper-editor.tsx V4 新增:纸感编辑器容器(中栏),承载 Tiptap 富文本编辑器
components/paper-editor/paper-toolbar.tsx V4 新增:纸感编辑器工具栏
components/paper-editor/paper-context-menu.tsx V4 新增:纸感编辑器右键菜单
components/paper-editor/textbook-tiptap-editor.tsx V4 新增:教材正文 Tiptap 编辑器(集成 AnchorMark/AnchorPoint 扩展)
components/paper-editor/inline-node.tsx V4 新增:内联节点渲染组件
components/paper-editor/inline-qa-dialog.tsx V4 新增:内联问答对话框
components/structure-tree/structure-tree.tsx V4 新增:结构树容器(左栏 260px支持节点展开到正文流
components/structure-tree/tree-node-row.tsx V4 新增:结构树节点行组件
components/detail-panel/detail-panel.tsx V4 新增:详情面板容器(右栏 380px
components/detail-panel/detail-head.tsx V4 新增:详情面板头部
components/detail-panel/detail-props.tsx V4 新增:详情面板属性编辑
components/detail-panel/qa-editor.tsx V4 新增师生问答编辑器interaction block 配套)
components/blocks/interaction-block.tsx V4 新增:师生交互 BlockdesignIntent + turns[] + expectedAnswer
components/lesson-plan-error-boundary.tsx 错误边界V3 续:重写为包装组件模式——内部类组件接受 errorText/retryText props外部函数组件通过 useTranslations 注入 i18n 文案)
components/lesson-plan-skeleton.tsx 骨架屏VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton
components/template-picker.tsx 模板选择器V3 续:完全通过 service 调用,移除直接 import 4 个 actionsV3 续审计:通过 ChapterPickerOption.children 递归类型消除 as ChapterPickerOption[] 断言)
components/version-history-drawer.tsx 版本历史抽屉V3 续:完全通过 service 调用,移除直接 import 2 个 actions
components/knowledge-point-picker.tsx 知识点选择器V3 续:完全通过 service 调用,移除直接 import actions-kp
components/question-bank-picker.tsx 题库选择器V3 续:完全通过 service 调用,移除跨模块直接 import @/modules/questions/actions;新增 isQuestionType 类型守卫替代 as QuestionType
components/inline-question-editor.tsx 内联题目编辑器V3 续审计:移除 as typeof QUESTION_TYPES[number] 断言改用 isInlineQuestionType 类型守卫;包裹 FocusTrap 实现焦点陷阱;补全 aria-label + 新增 optionLabel i18n 键)
components/publish-homework-dialog.tsx 发布作业对话框V3 续:完全通过 service 调用,移除直接 import actions-publish
components/blocks/rich-text-block.tsx 富文本 Block
components/blocks/text-study-block.tsx 课文研读 Block
components/blocks/objective-block.tsx 教学目标 BlockV3 续select onChange 使用 isObjectiveDimension 类型守卫替代 as 断言)
components/blocks/key-point-block.tsx 重难点 BlockV3 续select onChange 使用 isKeyPointType 类型守卫替代 as 断言)
components/blocks/import-block.tsx 导入 BlockV3 续select onChange 使用 isImportMethod 类型守卫替代 as 断言)
components/blocks/new-teaching-block.tsx 新授 Block
components/blocks/summary-block.tsx 总结 Block
components/blocks/homework-block.tsx 作业 BlockV3 续select onChange 使用 isHomeworkType 类型守卫替代 as 断言)
components/blocks/blackboard-block.tsx 板书 BlockV3 续select onChange 使用 isBlackboardLayout 类型守卫替代 as 断言)
components/blocks/exercise-block.tsx 练习 BlockV3 续select onChange 使用 isExercisePurpose 类型守卫替代 as 断言)
components/blocks/reflection-block.tsx 反思 BlockV3 续select onChange 使用 isReflectionAspect 类型守卫替代 as 断言)
components/calendar-view.tsx M9 日历视图组件2026-07-01 新增):客户端组件,支持周历/月历切换6 种事件类型颜色映射
components/curriculum-map-view.tsx M4 课程地图热力图组件2026-07-01 新增):学科×年级矩阵表格,颜色编码覆盖率
data-access-calendar.ts M9 日历事件数据访问层2026-07-01 新增)
data-access-review.ts M3 审核工作流数据访问层2026-07-01 新增)
data-access-comments.ts M2 协同备课评论数据访问层2026-07-01 新增)
data-access-formative.ts M5 形成性评价数据访问层2026-07-01 新增)
data-access-attachments.ts M6 资源附件库数据访问层2026-07-01 新增)
data-access-substitutes.ts M12 代课教师数据访问层2026-07-01 新增)
data-access-ai-evaluation.ts M8 AI 评估数据访问层2026-07-01 新增)
data-access-analytics.ts M10 备课分析仪表盘数据访问层2026-07-01 新增)
actions-calendar.ts M9 日历 Server Actions2026-07-01 新增)
actions-review.ts M3 审核工作流 Server Actions2026-07-01 新增)
actions-comments.ts M2 评论 Server Actions2026-07-01 新增)
actions-formative.ts M5 形成性评价 Server Actions2026-07-01 新增)
actions-attachments.ts M6 附件库 Server Actions2026-07-01 新增)
actions-substitutes.ts M12 代课教师 Server Actions2026-07-01 新增)
actions-ai-evaluation.ts M8 AI 评估 Server Actions2026-07-01 新增)
actions-analytics.ts M10 分析仪表盘 Server Actions2026-07-01 新增)

架构变更2026-07-01M1-M12 中长期计划完整实施):

  • M1 课标对标体系:新增 modules/standards/ 模块;新增 3 个 STANDARD_* 权限点;新增路由 /admin/curriculum-map
  • M2 协同备课评论:新增 data-access-comments.ts + actions-comments.ts支持 Block 级评论、resolved 状态
  • M3 审核工作流:新增 data-access-review.ts + actions-review.ts状态机 draft → submitted → approved/rejected → published
  • M4 课程地图:新增 admin/curriculum-map 路由 + curriculum-map-view.tsx 热力图组件
  • M5 形成性评价闭环:新增 data-access-formative.ts + actions-formative.ts4 种互动类型
  • M6 资源附件库:新增 data-access-attachments.ts + actions-attachments.ts
  • M7 移动端 PWA:见 1.1.1 章节
  • M8 AI 差异化生成 + 评估:新增 data-access-ai-evaluation.ts + actions-ai-evaluation.ts
  • M9 日历视图:新增 data-access-calendar.ts + actions-calendar.ts + calendar-view.tsx + teacher/lesson-plans/calendar 路由
  • M10 备课分析仪表盘:新增 data-access-analytics.ts + actions-analytics.ts3 个分析维度
  • M11 版本 diff 预览data-access-versions.ts 的 getVersionContent 支持读取历史版本内容
  • M12 代课教师:新增 data-access-substitutes.ts + actions-substitutes.ts
  • 权限扩展:新增 STANDARD_READ/MANAGE/LINK + LESSON_PLAN_SUBMIT/REVIEW/ANALYTICS_READ/SUBSTITUTE_MANAGE
  • i18n 扩展zh-CN 新增 9 个命名空间status 扩展为 6 状态
  • 数据库表:新增 6 张表lessonPlanComments/lessonPlanReviewRecords/lessonPlanFormativeItems/lessonPlanAttachments/lessonPlanAiEvaluations/lessonPlanSubstituteAssignments

架构变更2026-07-03V5 场景缺口修复第一阶段):

  • V5-1 自动保存失败 UI 兜底version-slice.ts 新增 saveError/isOnline 状态lesson-plan-editor.tsx 监听 online/offline 事件 + toast 提示 + 重试按钮
  • V5-2 撤销/重做:新增 hooks/history-slice.tspast/future 栈50 步上限editor-slice.ts 所有 mutation 包装 pushHistory编辑器顶部 Undo/Redo 按钮 + Ctrl/Cmd+Z/Shift+Z 快捷键
  • V5-3 发布前作业单预览publish-homework-dialog.tsx 改为 3 步流程(选班级 → 预览题目+总分+学生数 → 确认发布exercise-block.tsx 传入 items
  • V5-4 导出/打印:新增 lib/export.ts文档扁平化详细版/简洁版)+ components/print-view.tsx打印视图编辑器顶部新增"导出"按钮
  • V5-5 多媒体嵌入 + 素材库types.ts RichTextBlockData 新增 attachments 字段rich-text-block.tsx 集成 @tiptap/extension-image新增 components/attachment-picker.tsx复用 use-file-upload + M6 附件表 data-accessLessonPlanDataService 接口新增 getLessonPlanAttachments/createLessonPlanAttachment/deleteLessonPlanAttachment 三个方法;新增依赖 @tiptap/extension-imageFileTargetType 新增 "lesson_plan"
  • i18n 扩展zh-CN/en 同步新增 action.undo/redo、status.retrySave/saveFailed/recovered/offline、publish.step1-3/preview*/confirm*、questionBank.type 多题型、export.、attachment.、editor.textbookContent
  • 审计报告:新增 docs/architecture/audit/lesson-preparation-audit-report-v5.md场景缺口维度对照 v4 架构合规维度)

架构变更2026-07-03V5 场景缺口修复第二阶段 V5-6~V5-21

  • V5-6 W1 题库组卷预览exercise-block.tsx 增加组卷预览模式(题目列表 + 总分统计)
  • V5-7 W3/W4 课案绑定课时:新增 data-access-schedules.ts + actions-schedules.ts + components/schedule-dialog.tsx编辑器新增「安排课时」按钮数据库新增 lesson_plan_schedules 表
  • V5-8 U2 画布自动布局:新增 lib/auto-layout.ts@dagrejs/dagre DAG 自动布局项目已安装依赖无需新增editor-slice.ts 新增 autoLayout action
  • V5-9 F5 教材模糊搜索 + 最近使用template-picker.tsx 增加客户端模糊搜索(标题/学科/年级/出版社)+ localStorage 最近使用 5 个教材
  • V5-10 P1 画布性能优化node-editor.tsx ReactFlow 新增 onlyRenderVisibleElements 虚拟化 + minZoom={0.2}/maxZoom={2.5}/elevateNodesOnSelect={false}
  • V5-12 R4 校内课案库:新增 teacher/lesson-plans/library 路由 + loading.tsx + error.tsxactions.ts 新增 duplicateLessonPlanFormActionFormData wrapper 适配 form action
  • V5-13 P3 移动端只读视图:新增 components/lesson-plan-mobile-view.tsx按教学阶段分组线性卡片视图lesson-plan-readonly-view.tsx 集成 useMediaQuery("(max-width: 768px)") 小屏切换
  • V5-14 F4 板书可视化工具blackboard-block.tsx 增加轻量级可视化预览(不引入新库)——编辑/预览模式切换、structure 层级树、mindmap 中心+分支、text 等宽展示;新增 BlackboardPreview 子组件 + parseContent 纯函数(按缩进识别层级)
  • V5-15 T1 教学阶段分组types.ts 新增 TeachingStage 类型 + TEACHING_STAGE_KEYS 常量Block 接口新增 stage? 字段node-edit-panel.tsx 添加教学阶段选择器 UI
  • V5-16 T2 反思闭环(版本对比):新增 lib/version-diff.ts纯函数 diffDocuments 对比两个 LessonPlanDocument 节点级差异added/removed/modified/unchanged + changedFields新增 components/version-diff-view.tsxversion-history-drawer.tsx 集成版本对比currentDoc prop + 对比按钮)
  • V5-17 A1/A2 AI 反馈闭环:新增 lib/ai-feedback.tsgenerateLessonPlanFeedback 纯服务端函数4 维度 strengths/improvements/alignment/differentiation含解释性 reason 字段actions-ai.ts 新增 generateLessonPlanFeedbackAction新增 components/ai-feedback-dialog.tsx编辑器工具栏新增「AI 反馈」按钮
  • V5-18 W6 差异化教学标记types.ts 新增 DifferentiationLevel 类型 + DIFFERENTIATION_LEVEL_KEYS 常量Block 接口新增 differentiation? 字段node-edit-panel.tsx 添加差异化标记选择器 UI
  • V5-19 T3 目标-评价一致性校验:新增 lib/consistency-check.ts纯函数 checkConsistency 输出 ConsistencyResult校验规则缺少 objective/exercise 警告、exercise 无知识点警告、objective 未被评价覆盖警告);新增 components/consistency-check-dialog.tsx编辑器工具栏新增「一致性校验」按钮
  • V5-20 T4 课标覆盖热力图:新增 lib/curriculum-coverage.ts纯函数 computeCurriculumCoverage 按 chapterId 分组统计覆盖率,识别 isBlindSpot 教学盲点getHeatLevel 0-4 热力等级);新增 components/curriculum-heatmap.tsx新增 teacher/lesson-plans/heatmap 路由 + loading.tsx + error.tsx
  • V5-21 A3/A4/A5 AI 差异化模块:新增 lib/ai-differentiation.ts3 个纯服务端函数generateDifferentiationSuggestions/checkCurriculumAlignment/generateExplainableAssessment复用 ai-feedback.ts 的 createAiChatCompletion + Zod + JSON 提取模式actions-ai.ts 新增 4 个 server actionsgenerateDifferentiationSuggestionsAction/checkCurriculumAlignmentAction/generateExplainableAssessmentAction/getKnowledgePointsForAlignmentAction新增 components/ai-differentiation-dialog.tsx3 Tab 视图:差异化建议/课标核对/可解释评估A4 按教材 ID 按需加载知识点编辑器工具栏新增「AI 差异化」按钮
  • i18n 扩展zh-CN/en 同步新增 editor.stage*/editor.differentiation*/editor.consistency*/consistency./diff./library./feedback./heatmap./aiDifferentiation./blackboard.editMode/previewMode/editHint/previewEmpty/untitled
  • V5-11 R3 协同编辑需引入新依赖Yjs/Liveblocks或采用轻量协作方案评论+@提醒+版本对比),暂标记为待规划,待用户决策

架构变更2026-07-04V4 纸感重构 — 抛弃 React Flow 画布,改用三栏布局 + Tiptap 富文本编辑器):

  • H1 interaction block 注册config/block-registry.tsx 新增 interaction case导入 InteractionBlock 组件 + isInteractionBlockData 类型守卫BLOCK_REGISTRY 支持 12 种 Block 类型(含 V4 interaction
  • H2 三栏布局重构lesson-plan-editor.tsx 移除 NodeEditor/NodeEditPanel 导入与 JSX改用 StructureTree + PaperEditor + DetailPanel 三栏布局(grid-template-columns: 260px 1fr 380px),保留顶部工具栏(标题/保存/撤销重做/发布/导出/安排课时/一致性校验/AI 反馈/AI 差异化)+ 5 个对话框VersionHistoryDrawer/ConsistencyCheckDialog/AiFeedbackDialog/AiDifferentiationDialog/PrintView/ScheduleDialog删除 components/node-edit-panel.tsx + app/(dashboard)/teacher/lesson-plans/[planId]/edit/ai-content-generator-slot.tsx(依赖 NodeEditPanel 类型)
  • I1 v3 锚点失效 banner:新增 components/anchor-migration-banner.tsx,检测 doc.anchorsinvalid:true 或含 start 字段的旧锚点并提示用户;通过 key={planId} 实现 planId 变化时重新挂载内部组件,使用 lazy initial state 读取 localStoragedismiss 状态按 planId 隔离),避免 useEffect 中 setState符合 V4 react-hooks/set-state-in-effect lint 规则)
  • J1 删除废弃文件:删除 8 个 React Flow 画布和字符串锚点相关文件——components/node-editor.tsxcomponents/nodes/lesson-node.tsxcomponents/nodes/textbook-content-node.tsxcomponents/nodes/textbook-segments.tsxcomponents/nodes/anchor-node-selector.tsxlib/anchor-injector.tslib/rf-mappers.tslib/auto-layout.tslesson-plan-readonly-view.tsx 重写为委托 LessonPlanMobileView(线性卡片视图)渲染所有视口,移除 React Flow 依赖
  • J2 PrintView 适配 V4lib/export.tsflattenBlockData switch 新增 interaction case + flattenInteraction 函数(输出设计意图 + 师生对话回合 + 预期回答export.ts 已使用 nodes.sort((a, b) => a.order - b.order) 按顺序扁平化无画布依赖docstring 更新为 12 种 Block含 V4 interaction
  • J3 lint + tsc 零错误验证tsc --noEmit 0 错误lint 中 V4 引入的 react-hooks/set-state-in-effect 错误已通过 anchor-migration-banner.tsx 的 key-based remount + lazy initial state 模式修复;剩余 6 个 lint 错误为预存无关模块问题exams/use-exam-preview-tasks react-hooks/refs 3 个、template-picker V5-9 set-state-in-effect 1 个),按计划要求记录不修复
  • J4 架构图同步004_architecture_impact_map.md 新增 V4 纸感重构章节005_architecture_data.json 更新 lesson_preparation 节点——description 改为 V4 描述、dependencies 移除 @xyflow/react 改为 @tiptap/* 包、files 数组移除 9 个废弃文件并新增 V4 组件、auditFixes 新增 V4-PAPER-1~8 + V4-PAPER-DOC 条目
  • J5 known-issues V4 规则docs/troubleshooting/known-issues.md 新增 V4 纸感重构规则表,记录 8 条规则——React Flow 弃用、Tiptap Mark 锚点替代字符串偏移、setState in effect 禁用、doc.version=4、editor-slice 移除画布相关方法、interaction block 注册、PrintView 按 order 排序、AnchorMigrationBanner 失效提示
  • 数据模型 v4types.ts LessonPlanDocument.version = 4lib/document-migration.ts normalizeDocument 兼容 v1/v2/v3/v4 链式迁移;editor-slice.ts 移除 updateNodePosition/connect/setEdges/autoLayout 画布相关方法;hooks/expanded-slice.ts 新增 expandedNodeIds/toggleExpand/setExpanded 状态(节点展开到正文流);lib/anchor-mark.ts 新增 Tiptap AnchorMark + AnchorPoint 扩展(替代 v3 字符串偏移锚点)
  • V2-1 复制节点hooks/editor-slice.ts 新增 duplicateNode(id, copySuffix) 方法(深拷贝 BlockData + 新 id + 标题加 copySuffix + order 置末interaction 节点的 turns 重新生成 id 避免冲突;copySuffix 由调用方注入 i18n 文案);components/paper-editor/paper-context-menu.tsxcopyNode 从 toast 占位改为调用 duplicateNode 并 toast 成功提示i18n 新增 v4.contextMenu.copied/copyFailed/copySuffixzh-CN + en
  • V2-2 节点级 AI 协助 4 项lib/ai-node-assist.ts 新增 4 个服务函数(generateNodeContent/optimizeNodeExpression/suggestNodeDifferentiation/generateLayeredQuestions全部纯服务端、Zod 校验、失败返回 nullactions-ai.ts 新增 4 个 Server Action每个 action 校验 LESSON_PLAN_READ + LESSON_PLAN_CREATE + AI_CHAT 三个权限点;hooks/use-node-ai-assist.ts 新增客户端 hookcomponents/paper-editor/paper-editor.tsx 注入 onAiAction 回调;components/detail-panel/detail-panel.tsx 的 4 个 AiButton 接入真实 actioni18n 新增 v4.contextMenu.aiRunning/aiSuccess/aiFailed/aiComingSoonzh-CN + en
  • V4-I18N-1 i18n 完整审查与修复:对 lesson-preparation 模块 i18n 配置进行全量审查并修复——P0 修复运行时 MISSING_MESSAGEversion.diff.* 3 键title/summary/noChanges+ schema.ts 引用的 8 个 error.*labelTooLong/versionNoInvalid/nameRequired/nameTooLong/queryTooLong/blockIdRequired/commentTooLong/reviewerRequired+ v4.contextMenu 6 键copied/copyFailed/aiComingSoon/aiRunning/aiSuccess/aiFailedzh-CN + en 同步P1 修复 en locale 下大面积报错en calendar 节键名对齐 zh-CNweekView/monthView/loadFailed + prev/next/eventMeta/weekDays 子节en analytics 节补齐 totalPublished/totalSubmitted/totalStandardsLinked/loadFailedP2 修复 7 处硬编码中文detail-props.tsx 的 stageLabel/differentiationLabel、inline-node.tsx 的 exerciseCount、inline-qa-dialog.tsx 的 expectedAnswer、qa-editor.tsx 的 turnContentPlaceholder、curriculum-map-view.tsx 的 legendTitle/noData并新增对应 6 个 i18n 键(v4.detail.stageLabel/differentiationLabel/exerciseCount/turnContentPlaceholderv4.heatmap.legendTitle/noData);清理 7 个 dead 节review/comment/formative/substitute/evaluation/standards/gradeHeadanalytics 因被 app/(dashboard)/admin/curriculum-map 引用而保留);lib/i18n-errors.ts 新增 t.has(msg) 运行时守卫,避免 as 断言绕过类型检查(键不存在时返回原消息而非抛出 MISSING_MESSAGEzh-CN 与 en 顶层键集一致性验证通过43 = 43005_architecture_data.json 同步追加 V4-I18N-1 auditFixes 条目known-issues.md 追加 3 个 i18n 规则表(键缺失与中英文不同步 / dead 节清理规则 / 动态键翻译守卫)

架构变更2026-07-05Phase 4.2 — Zustand selector 细粒度拆分,避免整体订阅级联 re-render

  • lesson-plan-editor.tsxconst editor = useLessonPlanEditor() 整体订阅拆分为 9 个细粒度 selectorisDirty/isOnline/doc/title/isSaving/saveError/lastSavedAt/setTitle + canUndo/canRedo 派生 booleancanUndo/canRedo 改为 useLessonPlanEditor((s) => s.canUndo()) 订阅派生 boolean移除每次渲染时调用 editor.canUndo() 函数;useEffect 依赖与 printablePlan useMemo 同步更新使用拆分后的变量
  • text-study-block.tsxconst { updateNode } = useLessonPlanEditor()useLessonPlanEditor((s) => s.updateNode)
  • exercise-block.tsx:拆为 useLessonPlanEditor((s) => s.updateNode) + useLessonPlanEditor((s) => s.planId)
  • paper-context-menu.tsx:拆为 7 个细粒度 selectortoggleExpand/expandedNodeIds/updateNode/removeNode/addAnchor/duplicateNode/nodes);特别地将 doc 改为 s.doc.nodes 更精细订阅(addAnchor 等仅修改 anchors 不会触发本组件 re-render
  • 关键决策:不使用 useShallow 包装多字段 selector因为单字段 selector 已经够细,且避免了 useShallow 自身的浅比较开销;函数引用(setTitle/updateNode 等)天然稳定,无需 useShallow
  • 验证tsc 零新增错误lint 零新增错误4 个 modified 文件均未出现在 lint 输出中)

2.27b standards课标模块2026-07-01 新增)

职责K12 课标管理,支持课标树、按学科+年级筛选、课案与课标多对多关联。

导出函数

  • Data-accessgetStandards / getStandardsTree / getStandardById / createStandard / updateStandard / deactivateStandard / searchStandards / getStandardsByPlanId / linkPlanToStandard / unlinkPlanFromStandard
  • Actions10 个 Server Actions全部使用 requirePermission() + ActionState
  • SchemacreateStandardSchema / updateStandardSchema / getStandardsParamsSchema / linkPlanToStandardSchema
  • TypesStandard / StandardTreeNode / LessonPlanStandardLink

权限点

  • STANDARD_READstandard:read
  • STANDARD_MANAGEstandard:manage
  • STANDARD_LINKstandard:link

依赖关系

  • 依赖shared/*、modules/lesson-preparation/lib/i18n-errors
  • 被依赖modules/lesson-preparation、app/(dashboard)/admin/curriculum-map

文件清单

文件 职责
types.ts 类型定义
schema.ts Zod 验证
data-access.ts 课标 CRUD + 树形查询 + 课案关联
actions.ts 10 个 Server Actions

2.28 error-book错题本模块

职责:自动采集考试/作业错题,基于 SM-2 间隔重复算法科学复习,提供知识点薄弱度分析与多角色视图(学生/教师/家长/管理员)。

导出函数

  • ActionsgetErrorBookItemsAction / getErrorBookItemDetailAction / getErrorBookStatsAction / createErrorBookItemAction / updateErrorBookNoteAction / reviewErrorBookItemAction / deleteErrorBookItemAction / archiveErrorBookItemAction / collectFromSubmissionAction
  • Data-accessdata-access.ts,学生端 CRUDgetErrorBookItems / getErrorBookItemById / getErrorBookStatsSQL 聚合优化)/ createErrorBookItem / updateErrorBookNote / recordReview / deleteErrorBookItem / archiveErrorBookItem / buildStudentErrorWhereClause(共享)/ toStatus(共享)/ toStringArray(共享)
  • Data-accessdata-access-analytics.ts,教师/管理员分析查询):getStudentErrorBookSummariesSQL 聚合)/ getTopWrongQuestionsByStudentIdsSQL 聚合)/ getKnowledgePointWeakness / getSubjectErrorDistributionSQL 聚合)/ getChapterWeakness / getClassErrorOverviewsSQL 聚合)/ getSubjectErrorOverviewsSQL 聚合)/ getStudentNameMap / getStudentIdsByClassIdList / getAllStudentIds
  • Data-accessdata-access-collection.ts,自动采集):collectFromExamSubmission / collectFromHomeworkSubmission(通过 data-access.ts re-export
  • SchemaCreateErrorBookItemSchema / UpdateErrorBookNoteSchema / ReviewErrorBookItemSchema / CollectFromSubmissionSchema
  • TypesErrorBookItem / ErrorBookItemDetail / ErrorBookReviewRecord / ErrorBookStats / StudentErrorBookSummary / KnowledgePointWeakness / SubjectErrorDistribution
  • ComponentsErrorBookStatsCards / ErrorBookFilters / ErrorBookItemCard / ReviewButtons / ErrorBookDetailDialog / ErrorBookList / AddErrorBookDialog / TopWrongQuestions

权限点

  • ERROR_BOOK_READerror_book:read查看错题本student 自己、parent 子女)
  • ERROR_BOOK_MANAGEerror_book:manage):管理错题(添加/复习/删除/归档student
  • ERROR_BOOK_ANALYTICS_READerror_book:analytics_read查看错题分析teacher/admin/grade_head/teaching_head

依赖关系

  • 依赖:shared/*db、auth-guard、types、utilsmodules/classesgetStudentIdsByClassIds
  • 被依赖:app/(dashboard)/student/error-bookapp/(dashboard)/teacher/error-bookapp/(dashboard)/parent/error-bookapp/(dashboard)/admin/error-bookmodules/homeworkcollectFromHomeworkSubmission作业批改后自动采集modules/gradescollectFromExamSubmission 2026-06-25 新增:教师录入考试成绩后自动采集考试错题)
  • 2026-06-24 i18n + 解耦改造:error-book-detail-dialog 移除直接 import AiErrorBookAnalysismodules/aicreatePracticeSessionActionmodules/adaptive-practice改为通过 aiAnalysisSlotonStartVariantPractice props 注入;add-error-book-dialog 移除直接 import getQuestionsActionmodules/questions改为通过 questionOptionsonLoadQuestions props 注入。跨模块依赖上移至 app/(dashboard)/student/error-book/page.tsx

DataScope 行级权限

角色 DataScope 说明
student owned 仅查看/管理自己的错题
parent children 查看子女的错题统计
teacher class_taught 查看所教班级学生的错题分析
admin all 查看全校错题分析
grade_head grade_managed 查看所管年级学生的错题分析
teaching_head grade_managed 查看所管年级学生的错题分析

核心算法SM-2 间隔重复(简化版)

  • 独立纯函数模块:sm2-algorithm.ts(可替换为其他算法如 FSRS
  • 4 级评级:again(重来)/ hard(困难)/ good(良好)/ easy(简单)
  • 初始间隔1/2/4/7 天
  • 间隔增长:again 重置为 1 天;hard ×1.2good ×1.5easy ×2
  • 掌握度0-5 级,again -1hard ±0good +1easy +2
  • 已掌握判定:掌握度 ≥5 或连续答对 ≥3 次
  • 复习时间:次日早上 9 点
  • 单元测试:sm2-algorithm.test.ts39 个测试用例,全部通过)
  • 导出函数:calculateNewInterval / calculateNewMastery / deriveStatus / calculateNextReviewAt / calculateNewCorrectStreak / calculateSm2Result

自动采集机制

  • collectFromExamSubmission:从考试提交记录中筛选得分 < 满分的题目,去重后批量插入
  • collectFromHomeworkSubmission:从作业提交记录中筛选错题,去重后批量插入
  • 自动关联知识点(通过 questionsToKnowledgePoints 表)
  • subjectId 来源:questions 表无 subjectId 字段,考试错题从 exams.subjectId 获取;作业错题从 homeworkAssignments.sourceExamId 关联到源试卷的 exams.subjectId 获取(独立作业无学科归属则为 null

文件清单

文件 行数 职责
actions.ts ~180 9 个 Server Actions全部使用 requirePermission() + ActionState<T>
data-access.ts ~480 学生端 CRUD10 个函数)+ 共享工具函数buildStudentErrorWhereClause/toStatus/toStringArray+ re-export 采集函数。 2026-06-24 拆分:分析函数迁移至 data-access-analytics.ts修复 as 断言/or()! 非空断言/JSON 列类型安全getErrorBookStats 优化为 SQL GROUP BY 聚合
data-access-analytics.ts ~430 2026-06-24 新增:教师/管理员分析查询9 个函数SQL 聚合优化getStudentErrorBookSummaries/getTopWrongQuestionsByStudentIds/getSubjectErrorDistribution/getClassErrorOverviews/getSubjectErrorOverviews 使用 GROUP BY + 条件聚合)
data-access-collection.ts ~170 自动采集逻辑(从考试/作业提交采集错题,通过跨模块接口避免直查多表)
sm2-algorithm.ts ~180 SM-2 间隔重复算法(独立纯函数模块,可替换,支持时间注入测试)
sm2-algorithm.test.ts ~280 SM-2 算法单元测试39 个测试用例,覆盖所有函数和边界条件)
schema.ts ~60 4 个 Zod 验证 schema
types.ts ~245 11 个类型定义 + 状态映射常量 + 错误标签常量(新增 ChapterWeakness/ClassErrorOverview/SubjectErrorOverview
components/error-book-stats-cards.tsx ~80 5 个统计卡片(总数/待学习/学习中/已掌握/待复习)
components/analytics-stats-cards.tsx ~110 教师/管理员分析视图 5 个统计卡片(覆盖学生/错题总数/平均掌握率/待复习/知识点数)
components/error-book-filters.tsx ~100 筛选栏(搜索/状态/来源/待复习),使用 nuqs
components/error-book-item-card.tsx ~150 错题卡片(预览/标签/笔记/掌握度/操作)
components/review-buttons.tsx ~80 4 按钮复习面板again/hard/good/easy
components/error-book-detail-dialog.tsx ~340 详情对话框(题目/答案/复习/笔记/历史)。 2026-06-24 i18n + 解耦:移除直接 import AiErrorBookAnalysis/createPracticeSessionAction,改为 aiAnalysisSlot/onStartVariantPractice props 注入;移除 as 断言
components/error-book-list.tsx ~60 网格列表。 2026-06-24 i18n + 解耦:新增 aiAnalysisSlot?: (item: ErrorBookItem) => React.ReactNodeonStartVariantPractice?: (item: ErrorBookItem) => void props透传至 ErrorBookItemCard/ErrorBookDetailDialog
components/add-error-book-dialog.tsx ~170 手动添加对话框(题库选择 + 标签)。 2026-06-24 i18n + 解耦:移除直接 import getQuestionsAction,改为 questionOptions/onLoadQuestions props 注入;移除 as 断言
components/subject-tabs.tsx ~95 学科切换 Tab显示每个学科错题数概览URL 参数持久化)
components/class-filter.tsx ~85 班级筛选器显示每个班级错题数和待复习数URL 参数持久化)
components/class-error-bar-chart.tsx ~115 班级错题数对比柱状图rechartstooltip 显示学生数/人均/掌握率)
components/subject-distribution-chart.tsx ~110 学科错题分布柱状图管理员视图recharts
components/knowledge-point-weakness-chart.tsx ~150 知识点薄弱度横向柱状图(按掌握率红/黄/绿着色,显示章节归属)
components/chapter-weakness-chart.tsx ~165 章节错题分布横向柱状图(哪些课在错,含 Top 3 薄弱知识点)
components/grouped-student-error-table.tsx ~180 按班级分组的学生错题表格(可展开/折叠,显示每个学生的错题详情)
components/top-wrong-questions.tsx ~80 高频错题列表Top 10

路由清单

路由 文件 说明
/student/error-book page.tsx + loading.tsx + error.tsx + student-error-book-list-client.tsx + add-error-book-dialog-with-questions.tsx 学生错题本(统计/筛选/列表/手动添加/详情复习)。 2026-06-24 解耦app 层新增两个包装组件承接跨模块依赖:StudentErrorBookListClient 注入 AiErrorBookAnalysismodules/ai+ createPracticeSessionActionmodules/adaptive-practiceErrorBookListaiAnalysisSlot/onStartVariantPractice propsAddErrorBookDialogWithQuestions 注入 getQuestionsActionmodules/questions+ extractQuestionPreviewshared/libAddErrorBookDialogonLoadQuestions prop
/teacher/error-book page.tsx + loading.tsx + error.tsx 教师错题分析学科Tab/班级筛选/统计卡片/班级对比图/章节错题图/知识点薄弱度图/按班级分组学生表/高频错题)
/parent/error-book page.tsx + loading.tsx + error.tsx 家长错题本(子女错题统计/薄弱知识点/高频错题)
/admin/error-book page.tsx + loading.tsx + error.tsx 管理员错题分析学科Tab/统计卡片/学科分布图/章节错题图/知识点薄弱度图/按班级分组学生Top50/高频错题)

数据库表

说明
errorBookItems 错题条目主表18 列/4 索引/2 外键studentId、questionId、sourceType、sourceId、studentAnswer、correctAnswer、subjectId、knowledgePointIds、status、masteryLevel、nextReviewAt、reviewInterval、reviewCount、correctStreak、note、errorTags、createdAt、updatedAt
errorBookReviews 复习记录表8 列/2 索引/2 外键itemId、studentId、result、reviewedAt、newInterval、newMasteryLevel

i18n

  • src/shared/i18n/messages/zh-CN/error-book.json
  • src/shared/i18n/messages/en/error-book.json

导航配置

  • 6 个角色admin/teacher/student/parent/grade_head/teaching_head均添加「错题分析」导航项
  • 图标:BookXlucide-react

2.29 aiAI 模块)— 新增 / V2 增强 / V3 安全加固+竞品对标

职责:统一 AI 能力封装,为备课、错题集、试卷、改题等业务模块提供 AI 服务。V2 增加流式响应、Markdown 渲染、全局助手、内容安全过滤、家长学情摘要、管理员使用统计、学生学习路径推荐。V3 安全加固(原子配额/苏格拉底校验/重试机制)+ 竞品对标(知识图谱集成/苏格拉底式辅导强化)。

架构定位

  • 位于 modules/ 层,通过 shared/lib/ai 调用底层 AI SDK
  • 通过 AiClientProviderReact Context向客户端组件注入 Server Action 引用
  • 业务模块不直接 import ai/actions,仅通过 Context 消费
  • V2在 dashboard layout 全局注入 AiClientProvider,所有页面均可使用 AI 助手

核心导出

类型 名称 文件 说明
Server Actions aiChatAction modules/ai/actions.ts AI 对话权限AI_CHAT
Server Actions suggestSimilarQuestionsAction modules/ai/actions.ts 相似题推荐权限AI_CHAT + ERROR_BOOK_READ
Server Actions suggestGradingAction modules/ai/actions.ts AI 辅助批改权限AI_CHAT + HOMEWORK_GRADE
Server Actions generateLessonContentAction modules/ai/actions.ts 备课内容生成权限AI_CHAT + LESSON_PLAN_READ
Server Actions generateQuestionVariantAction modules/ai/actions.ts 题目变体生成权限AI_CHAT + EXAM_AI_GENERATE
Server Actions analyzeWeaknessAction modules/ai/actions.ts 薄弱点分析权限AI_CHAT + ERROR_BOOK_READ
Server Actions generateChildSummaryAction modules/ai/actions.ts 家长学情摘要权限AI_CHAT— V2 新增
Server Actions recommendStudyPathAction modules/ai/actions.ts 学习路径推荐权限AI_CHAT— V2 新增
Server Actions getAiUsageStatsAction modules/ai/actions.ts AI 使用统计权限AI_CONFIGURE— V2 新增
Server Actions explainErrorAction modules/ai/actions.ts 错题 AI 解释权限AI_CHAT + ERROR_BOOK_READ— V3 P2-1 新增
SSE Route POST /api/ai/chat/stream app/api/ai/chat/stream/route.ts 流式 AI 对话SSE— V2 新增
SSE Route GET /api/notifications/stream app/api/notifications/stream/route.ts 通知实时推送SSE15s 心跳 + 5min 超时)— V2-P3 新增
Service AiService modules/ai/types.ts 服务端 AI 服务接口(含 8 个方法)
Service AiClientService modules/ai/types.ts 客户端 AI 服务接口Server Action 引用集合)
Provider AiClientProvider modules/ai/context/ai-client-provider.tsx React Context Provider注入 AiClientService
Hook useAiClient modules/ai/context/ai-client-provider.tsx 消费 AiClientService必须在 Provider 内使用)
Hook useAiClientOptional modules/ai/context/ai-client-provider.tsx 可选消费 AiClientService未注入返回 null
Hook useAiChatStream modules/ai/hooks/use-ai-chat-stream.ts 流式 AI 对话 HookSSE + AbortController— V2 新增
Hook useAiSuggestion modules/ai/hooks/use-ai-suggestion.ts AI 建议 Hook
Hook useFloatingBall modules/ai/hooks/use-floating-ball.ts 可拖拽悬浮球 Hook边缘吸附+半隐藏+位置持久化)— V3 拆分
Hook usePositionPersistence modules/ai/hooks/use-position-persistence.ts 位置持久化 HooklocalStorage + resize 校正)— V3 新增
Hook useDragPosition modules/ai/hooks/use-drag-position.ts 拖拽位置 Hookpointer 事件 + 拖拽状态)— V3 新增
Context createFullAiClientService modules/ai/context/create-ai-client-service.ts 创建完整 AI 客户端服务工厂(含全部 9 个 Action— V3 新增
Context createCoreAiClientService modules/ai/context/create-ai-client-service.ts 创建核心 AI 客户端服务工厂(仅 6 个常用 Action— V3 新增
Component AiAssistantWidget modules/ai/components/ai-assistant-widget.tsx 全局 AI 助手悬浮按钮(上下文感知)— V2 新增 / Phase 4.7 改造为懒加载 wrappernext/dynamic ssr:false仅渲染 Skeleton 占位 + LazyInner
Component AiAssistantWidgetInner modules/ai/components/ai-assistant-widget-inner.tsx 全局 AI 助手悬浮球内部实现(悬浮球 + 抽屉 + AiChatPanel + 上下文推断)— Phase 4.7 从 ai-assistant-widget.tsx 拆出,通过 dynamic 懒加载,使 AI SDK / Markdown 渲染等重型依赖打入独立 chunk
Component AiChatPanel modules/ai/components/ai-chat-panel.tsx AI 对话面板(流式 + Markdown + 复制 + 停止 + 建议)— V2 增强
Component AiMarkdownRenderer modules/ai/components/ai-markdown-renderer.tsx Markdown 渲染器GFM + 复制按钮)— V2 新增
Component AiGradingAssist modules/ai/components/ai-grading-assist.tsx AI 批改辅助(主观题预评分 + 反馈建议)
Component AiErrorBookAnalysis modules/ai/components/ai-error-book-analysis.tsx 错题本 AI 分析(相似题 + 薄弱点)
Component AiLessonContentGenerator modules/ai/components/ai-lesson-content-generator.tsx 备课内容生成器(活动/评估/讨论题/素材)
Component AiQuestionVariantGenerator modules/ai/components/ai-question-variant-generator.tsx 题目变体生成器(同知识点/不同难度/不同题型)
Component AiChildSummary modules/ai/components/ai-child-summary.tsx 家长 AI 学情摘要 — V2 新增
Component AiUsageDashboard modules/ai/components/ai-usage-dashboard.tsx 管理员 AI 使用统计仪表盘 — V2 新增
Component AiStudyPath modules/ai/components/ai-study-path.tsx 学生学习路径推荐 — V2 新增
Component AiErrorBoundary modules/ai/components/ai-error-boundary.tsx AI 功能错误边界
Component AiSuggestionSkeleton modules/ai/components/ai-skeleton.tsx AI 建议加载骨架屏
Component AiChartRenderer modules/ai/components/ai-chart-renderer.tsx AI 图表渲染器bar/line/pie/radar— V3 新增
Component AiProviderSelector modules/ai/components/ai-provider-selector.tsx AI 服务商选择器
Safety filterUserInput modules/ai/services/content-safety.ts 输入安全过滤 — V2 新增
Safety filterAiOutput modules/ai/services/content-safety.ts 输出安全过滤 — V2 新增
Safety checkDailyLimit modules/ai/services/content-safety.ts 每日交互限制 — V2 新增
Safety tryConsumeDailyQuota modules/ai/services/content-safety.ts 原子化每日限额(防 TOCTOU 竞态)— V3 新增
Safety refundDailyQuota modules/ai/services/content-safety.ts 配额回退(过滤/失败时不扣配额)— V3 新增
Safety validateSocraticOutput modules/ai/services/content-safety.ts 苏格拉底式辅导输出校验(问号结尾/连续陈述句限制)— V3 新增
Data Access recordAiEvent modules/ai/data-access.ts AI 事件记录(内存存储,生产环境替换为 DB— V3 新增
Data Access getAiUsageStats modules/ai/data-access.ts AI 使用统计聚合(真实聚合,替代硬编码零)— V3 新增
Prompt SOCRATIC_TUTOR_SYSTEM_PROMPT modules/ai/services/prompt-templates.ts 苏格拉底式辅导专用提示词3 级提示升级/禁止直接给答案)— V3 新增
Prompt EXPLAIN_ERROR_SYSTEM_PROMPT modules/ai/services/prompt-templates.ts 错题解释专用提示词(错误分析/正确思路/关键知识点/防范建议)— V3 P2-1 新增
Util consumeSseStream / getStreamErrorKey / removeTrailingEmptyAssistant modules/ai/hooks/stream-utils.ts SSE 流解析工具(从 hook 抽取)— V3 新增

集成点

业务模块 集成组件 页面 说明
homework AiGradingAssist teacher/homework/submissions/[submissionId] 主观题辅助评分
error-book AiErrorBookAnalysis student/error-book 相似题推荐 + 薄弱点分析
lesson-preparation AiLessonContentGenerator teacher/lesson-plans/[planId]/edit 备课内容生成
exams AiQuestionVariantGenerator teacher/exams/[id]/build 题目变体生成
全局 AiAssistantWidget app/(dashboard)/layout.tsx 全局 AI 助手悬浮按钮(所有页面)— V2 新增
parent AiChildSummary parent/dashboard 家长学情 AI 摘要 — V2 新增V4 实际接入到 ParentDashboard 的 aiSummarySlot按子女渲染
admin AiUsageDashboard admin/ai-settings AI 使用统计仪表盘 — V2 新增V4 校正:原误记为 admin/ai-usage,实际位于 /admin/ai-settings
student AiStudyPath student/learning/study-path 学习路径推荐 — V2 新增V4 实际接入页面(含 loading.tsx + error.tsx

依赖关系

  • modules/aishared/lib/aiAI SDK 封装,含流式 createAiChatCompletionStreamV3 增加 withRetry 重试机制)
  • modules/aishared/lib/auth-guard(权限校验)
  • modules/aishared/lib/track-event(使用量埋点)
  • modules/aishared/types/permissions(权限常量)
  • modules/aishared/types/action-state(返回值类型)
  • modules/aimodules/textbooks/data-access-graph(知识图谱+掌握度查询)— V3 新增
  • app/(dashboard)/layoutmodules/ai(全局 Provider + Widget— V2 新增
  • app/api/ai/chat/streammodules/ai/services/content-safetySSE 端点安全过滤)— V3 加固
  • 业务模块 → modules/ai/context/ai-client-provider(通过 Context 注入)
  • 业务模块 → modules/ai/components/*(组合 AI 组件)

安全机制V2 新增 / V3 加固)

  • 输入过滤:filterUserInput 拦截暴力/自残/色情/PII
  • 输出过滤:filterAiOutput 扫描 AI 回复
  • 每日限制:学生 50 次/天,教师 200 次/天,家长 30 次/天
  • 学生 Socratic 模式system prompt 强制不直接给答案
  • SSE 端点权限校验:requirePermission(AI_CHAT)
  • V3 原子配额:tryConsumeDailyQuota 解决 TOCTOU 竞态,refundDailyQuota 过滤/失败时回退
  • V3 苏格拉底校验:validateSocraticOutput 检查问号结尾+连续陈述句限制,SOCRATIC_TUTOR_SYSTEM_PROMPT 3 级提示升级
  • V3 SSE 端点加固Zod 校验 + rate limit + 服务端强制 systemPrompt忽略客户端
  • V3 重试机制:withRetry 指数退避429/5xx2 次重试1s/2s/4s 延迟)
  • V3 知识图谱集成:recommendStudyPathAction 自动从 textbooks 模块获取图谱+掌握度注入 prompt
  • V4 超时优化:shared/lib/ai/client.ts 按场景分离超时(普通 30s / 测试连接 120s / 流式 180s解决添加 Provider 时检测超时过短导致测试失败
  • V4 规范修复:ai-service.ts 消除 as unknown as Record 双重断言(改用 in 类型缩小);actions.ts 消除非空断言 !(改用 flatMapai-assistant-widget.tsx 硬编码英文提示词迁移至 i18n新增 chat.suggestedPrompts.context.* 翻译键)

文件清单

文件 行数 职责
modules/ai/types.ts ~290 类型定义8 个业务场景类型 + AiService/AiClientServiceV3 新增 knowledgeGraph/textbookId
modules/ai/schema.ts ~230 Zod 验证 schema8 个输入 + 8 个输出V3 新增 knowledgeGraph/textbookId
modules/ai/actions.ts ~400 9 个 Server Actions含权限校验V3 新增知识图谱获取V4 消除非空断言)
modules/ai/data-access.ts ~138 AI 事件存储+统计聚合 — V3 新增
modules/ai/services/ai-service.ts ~430 DefaultAiService 实现8 个方法V3 新增知识图谱上下文注入V4 消除 as 断言)
modules/ai/services/prompt-templates.ts ~250 9 个系统提示词模板V3 新增 SOCRATIC_TUTOR_SYSTEM_PROMPT
modules/ai/services/usage-tracker.ts ~90 AI 使用量埋点V3 新增 child_summary/study_path 能力)
modules/ai/services/content-safety.ts ~290 内容安全过滤(输入/输出/每日限制/原子配额/苏格拉底校验)— V2 新增 / V3 加固
modules/ai/context/ai-client-provider.tsx ~62 React Context Provider + Hooks
modules/ai/components/ai-assistant-widget.tsx ~25 懒加载 wrappernext/dynamic ssr:false + Skeleton 占位)— Phase 4.7 改造 / V2 新增 / V4 i18n 修复
modules/ai/components/ai-assistant-widget-inner.tsx ~330 全局 AI 助手悬浮球内部实现(悬浮球 + 抽屉 + AiChatPanel + 上下文推断)— Phase 4.7 新增(从 ai-assistant-widget.tsx 拆出)
modules/ai/components/ai-chat-panel.tsx ~305 AI 对话面板(流式 + Markdown— V2 增强
modules/ai/components/ai-markdown-renderer.tsx ~100 Markdown 渲染器 — V2 新增
modules/ai/components/ai-child-summary.tsx ~170 家长学情摘要 — V2 新增
modules/ai/components/ai-usage-dashboard.tsx ~180 管理员使用统计 — V2 新增
modules/ai/components/ai-study-path.tsx ~170 学生学习路径 — V2 新增
modules/ai/components/ai-grading-assist.tsx 173 AI 批改辅助组件
modules/ai/components/ai-error-book-analysis.tsx 246 错题本 AI 分析组件
modules/ai/components/ai-lesson-content-generator.tsx ~187 备课内容生成器
modules/ai/components/ai-question-variant-generator.tsx ~208 题目变体生成器
modules/ai/components/ai-error-boundary.tsx ~88 AI 错误边界
modules/ai/components/ai-skeleton.tsx ~47 AI 骨架屏
modules/ai/components/ai-provider-selector.tsx ~129 服务商选择器
modules/ai/hooks/use-ai-chat-stream.ts ~107 流式 AI 对话 HookV3 拆分至 ≤80 行函数体)— V2 新增 / V3 重构
modules/ai/hooks/stream-utils.ts ~120 SSE 流解析工具consumeSseStream/getStreamErrorKey 等)— V3 新增
modules/ai/hooks/use-ai-suggestion.ts ~72 AI 建议 Hook
app/api/ai/chat/stream/route.ts ~160 SSE 流式端点 — V2 新增
app/api/notifications/stream/route.ts ~120 通知实时推送 SSE 端点 — V2-P3 新增

i18n

  • 翻译文件:shared/i18n/messages/{locale}/ai.json
  • 命名空间:ai
  • V2 新增键:chat.streaming/stopGeneration/copy/clearConfirm/suggestedPromptsgrading.description/batch*lessonPrep.description/additionalContext/insertContentexam.variantType.*/targetDifficulty/addVariantparent.*admin.*studyPath.*widget.*safety.*
  • V4 新增键:chat.suggestedPrompts.context.{teacherGrading,teacherLesson,teacherExam,studentHomework}(上下文感知提示词 i18n 化)

2.30 adaptive-practice专项练习模块 新增(核心教学链路闭环)

职责:实现"错题集 → 知识点 → 专项出题 → 学生答题 → 自动判分 → 教师/年级主任宏观数据分析"的完整闭环。支持四种出题策略错题变式、知识点专项、薄弱章节、AI 推荐。提供教师端与年级主任端的宏观数据分析工作台。

架构定位

  • 位于 modules/ 层,严格遵循三层架构
  • 通过 data-access.ts 提供 CRUD 操作,通过 data-access-strategy.ts 实现出题策略,通过 data-access-analytics.ts 提供教师/年级分析查询
  • 跨模块通信:通过 modules/error-book/data-access 获取错题数据,通过 modules/questions/data-access 获取题目数据,通过 modules/classes/data-access 获取班级/学生数据,通过 modules/users/data-access 获取用户数据
  • Server Actions 通过 requirePermission() 校验 ADAPTIVE_PRACTICE_READ / ADAPTIVE_PRACTICE_MANAGE 权限

核心导出

类型 名称 文件 说明
DB Schema practiceSessions shared/db/schema.ts 练习会话表(学生 ID/类型/状态/统计)
DB Schema practiceAnswers shared/db/schema.ts 练习答题记录表(含变式题支持)
Server Actions getPracticeSessionsAction modules/adaptive-practice/actions.ts 获取练习列表权限ADAPTIVE_PRACTICE_READ
Server Actions getPracticeSessionDetailAction modules/adaptive-practice/actions.ts 获取练习详情权限ADAPTIVE_PRACTICE_READ
Server Actions getPracticeStatsAction modules/adaptive-practice/actions.ts 获取练习统计权限ADAPTIVE_PRACTICE_READ
Server Actions createPracticeSessionAction modules/adaptive-practice/actions.ts 创建练习会话权限ADAPTIVE_PRACTICE_MANAGE
Server Actions submitPracticeAnswerAction modules/adaptive-practice/actions.ts 提交答案+自动判分权限ADAPTIVE_PRACTICE_MANAGE
Server Actions completePracticeSessionAction modules/adaptive-practice/actions.ts 完成练习权限ADAPTIVE_PRACTICE_MANAGE
Server Actions abandonPracticeSessionAction modules/adaptive-practice/actions.ts 放弃练习权限ADAPTIVE_PRACTICE_MANAGE
Data Access getPracticeSessions / getPracticeSessionById data-access.ts 学生端 CRUD
Data Access createPracticeSession data-access.ts 创建会话(事务:会话+答题记录)
Data Access submitPracticeAnswer data-access.ts 提交答案+自动判分
Data Access autoGradeAnswer data-access.ts 自动判分逻辑(选择题/判断题自动,填空题返回 null
Strategy selectQuestionsForPractice data-access-strategy.ts 出题策略主入口
Strategy selectForErrorVariant data-access-strategy.ts 错题变式策略
Strategy selectForKnowledgePoint data-access-strategy.ts 知识点专项策略
Strategy selectForWeakChapter data-access-strategy.ts 薄弱章节策略
Strategy selectForAiRecommended data-access-strategy.ts AI 推荐策略
Analytics getClassPracticeStats data-access-analytics.ts 班级练习统计
Analytics getClassStudentPracticeSummaries data-access-analytics.ts 班级学生练习摘要
Analytics getGradePracticeStats data-access-analytics.ts 年级练习统计
Analytics getPracticeTypeBreakdown data-access-analytics.ts 练习类型分布
Analytics getStudentsWithoutPractice data-access-analytics.ts 识别未参与练习学生
Analytics getTeacherClassPracticeOverviews data-access-analytics.ts 教师所教班级练习概览
Analytics getClassKnowledgePointWeakness data-access-analytics.ts 班级知识点薄弱度(基于练习答题)
Analytics getGradeClassPracticeComparison data-access-analytics.ts 年级各班级练习对比
Analytics getClassLearningProfile data-access-analytics.ts 班级综合学习画像(跨模块整合)
Analytics getGradePracticeOverview data-access-analytics.ts 年级综合练习统计
Component PracticeStarter components/practice-starter.tsx 练习发起器四种模式选择V4 重构:通过 usePracticeService() 调用 actionsa11y 增强,原生 input → shadcn Checkboxreason 改为枚举值)
Component PracticeStarterWithNav components/practice-starter-with-nav.tsx V4 新增:客户端路由导航包装器,注入 onSessionCreated 回调,使 Server Component 页面可直接渲染 PracticeStarter
Component PracticeSessionView components/practice-session-view.tsx 答题界面(逐题作答+自动判分V4 重构:通过 usePracticeService() 调用 actions埋点接入 usePracticeAnalytics()onAbandoned 回调注入路由跳转)
Component PracticeHistory components/practice-history.tsx 练习历史列表V4新增 routePrefix? 可选 prop未传时渲染只读卡片供家长端复用
Component PracticeStatsCards components/practice-stats-cards.tsx 学生端统计卡片
Component PracticeOverviewStatsCards components/practice-overview-stats-cards.tsx 教师/年级端统计卡片 — 新增
Component ClassPracticeComparisonTable components/class-practice-comparison-table.tsx 班级练习对比表格 — 新增
Component PracticeTypeBreakdownChart components/practice-type-breakdown-chart.tsx 练习类型分布柱状图 — 新增
Component ClassKnowledgePointWeaknessChart components/class-knowledge-point-weakness-chart.tsx 知识点薄弱度柱状图 — 新增
Component StudentPracticeRankingTable components/student-practice-ranking-table.tsx 学生练习排名表格 — 新增
Component InactiveStudentsAlert components/inactive-students-alert.tsx 未参与练习学生提醒 — 新增
Service (DI) PracticeService / PracticeServiceProvider / usePracticeService / usePracticeAnalytics services/practice-service.tsx V4 新增:依赖注入基础设施(接口 + 默认实现代理 actions + Provider + hooks参考 dashboard-service.tsx 模式;未包裹 Provider 时回退默认实现
Lib grading / source-meta / type-guards lib/grading.ts / lib/source-meta.ts / lib/type-guards.ts V4 提取纯函数库判分逻辑、sourceMeta 类型守卫、PracticeType 类型守卫),便于单测

集成点

业务模块 集成组件 页面 说明
error-book createPracticeSessionAction student/error-book 详情弹窗 错题详情页"发起变式练习"按钮(通过 onStartVariantPractice props 注入,已解耦)
student PracticeStarterWithNav / PracticeSessionView student/practice / student/practice/[sessionId] 学生端练习入口与答题V4通过 PracticeServiceProvider 注入 service
teacher PracticeOverviewStatsCards teacher/practice 教师端专项练习分析V4班级筛选改用 shared/components/class-filter,移除对 error-book 的跨模块依赖)
management PracticeOverviewStatsCards management/grade/practice 年级主任宏观数据分析
parent PracticeStatsCards / PracticeHistoryroutePrefix=undefined parent/practice V4 新增2026-06-25 P0-修复-11家长查看子女练习统计与历史只读视图PracticeServiceProvider 注入

依赖关系

  • modules/adaptive-practiceshared/dbschema: practiceSessions/practiceAnswers/questions/knowledgePoints 等)
  • modules/adaptive-practiceshared/lib/auth-guard(权限校验)
  • modules/adaptive-practiceshared/types/permissions(权限常量)
  • modules/adaptive-practiceshared/types/action-state(返回值类型)
  • modules/adaptive-practicemodules/classes/data-accessgetActiveStudentIdsByClassId/getClassNameById/getClassesByGradeId/getClassIdsByGradeIds/getStudentIdsByClassIds
  • modules/adaptive-practicemodules/users/data-accessgetUserIdsByGradeId/getUserNamesByIds
  • modules/adaptive-practicemodules/questions/data-access(题目查询,出题策略使用)
  • modules/adaptive-practicemodules/error-book/data-access(错题数据,错题变式策略使用)
  • app/(dashboard)/student/practicemodules/adaptive-practice学生端页面V4通过 PracticeServiceProvider 注入 PracticeService
  • app/(dashboard)/teacher/practicemodules/adaptive-practice(教师端分析页面)— 新增V4班级筛选改用 shared/components/class-filter(通用组件,ClassFilterItem 接口 + 预翻译文本 props不再依赖 modules/error-book/components/class-filter
  • app/(dashboard)/management/grade/practicemodules/adaptive-practice(年级主任分析页面)— 新增
  • app/(dashboard)/parent/practicemodules/adaptive-practice(家长端查看子女练习)— V4 新增2026-06-25 P0-修复-11
  • modules/error-book/components/error-book-detail-dialogmodules/adaptive-practice/actions(变式练习入口,通过 onStartVariantPractice props 注入,已解耦)

权限点

  • ADAPTIVE_PRACTICE_READ查看练习数据admin/teacher/student/parent/grade_head/teaching_head 均有)
  • ADAPTIVE_PRACTICE_MANAGE:创建/提交/完成/放弃练习(仅 student 有)

自动判分逻辑

  • single_choice:学生答案 ID 与正确答案 ID 完全匹配
  • multiple_choice:学生答案 ID 集合与正确答案 ID 集合完全匹配(集合比对)
  • judgment:学生布尔答案与正确布尔答案一致
  • text(填空题):不自动判分,返回 null,待教师批阅

出题策略

  • error_variant:按难度升序选取原题(确保 AI 不可用时也能练习)
  • knowledge_point:按知识点+难度筛选,随机抽取
  • weak_chapter:排除已答题目,避免重复
  • ai_recommended:从 AI 推荐知识点抽题

文件清单

文件 行数 职责
modules/adaptive-practice/types.ts ~143 类型定义PracticeType/PracticeSourceMeta 联合类型等V4WeakChapterSourceMeta.chapterId 改为可选)
modules/adaptive-practice/schema.ts ~75 Zod 验证 schemaV4discriminatedUnion
modules/adaptive-practice/data-access.ts ~620 CRUD 操作+自动判分逻辑V4类型守卫替换 as、submitPracticeAnswer 事务化、completePracticeSession 校验答题完整性、抛 PracticeError
modules/adaptive-practice/data-access-strategy.ts ~343 四种出题策略实现V4移除 as WeakChapterSourceMeta 断言)
modules/adaptive-practice/data-access-analytics.ts ~634 教师/年级宏观数据分析查询V4N+1 优化,单 SQL GROUP BY
modules/adaptive-practice/actions.ts ~267 7 个 Server ActionsV4i18n 错误码返回 + dataScope 下 studentId 归属校验)
modules/adaptive-practice/lib/grading.ts ~40 V4 新增:纯函数判分逻辑
modules/adaptive-practice/lib/source-meta.ts ~60 V4 新增sourceMeta 类型守卫 + parseSourceMeta
modules/adaptive-practice/lib/type-guards.ts ~30 V4 新增isPracticeType / asPracticeType / asPracticeSourceMeta
modules/adaptive-practice/services/practice-service.tsx ~150 V4 新增依赖注入基础设施PracticeService 接口 + 默认实现 + Provider + hooks + PracticeAnalytics 接口)
modules/adaptive-practice/components/practice-starter.tsx ~330 练习发起器V4usePracticeService 注入 + Checkbox + a11y + 枚举 reason + onSessionCreated 回调)
modules/adaptive-practice/components/practice-starter-with-nav.tsx ~25 V4 新增:路由导航包装器(注入 routePrefix 跳转)
modules/adaptive-practice/components/practice-session-view.tsx ~550 答题界面V4拆分为 5 个子组件 + usePracticeService 注入 + 埋点)
modules/adaptive-practice/components/practice-history.tsx ~110 练习历史列表V4新增 routePrefix 可选 prop
modules/adaptive-practice/components/practice-stats-cards.tsx ~73 学生端统计卡片
modules/adaptive-practice/components/practice-overview-stats-cards.tsx ~99 教师/年级端统计卡片 — 新增
modules/adaptive-practice/components/class-practice-comparison-table.tsx ~88 班级练习对比表格 — 新增
modules/adaptive-practice/components/practice-type-breakdown-chart.tsx ~123 练习类型分布柱状图 — 新增
modules/adaptive-practice/components/class-knowledge-point-weakness-chart.tsx ~144 知识点薄弱度柱状图 — 新增
modules/adaptive-practice/components/student-practice-ranking-table.tsx ~112 学生练习排名表格 — 新增
modules/adaptive-practice/components/inactive-students-alert.tsx ~64 未参与练习学生提醒 — 新增
app/(dashboard)/student/practice/page.tsx - 学生端练习列表页V4注入 PracticeServiceProvider + routePrefix
app/(dashboard)/student/practice/[sessionId]/page.tsx - 学生端答题页V4注入 PracticeServiceProvider
app/(dashboard)/teacher/practice/page.tsx ~250 教师端专项练习分析页V4改用 shared/components/class-filter移除 error-book 依赖)
app/(dashboard)/teacher/practice/loading.tsx - 教师端加载骨架
app/(dashboard)/teacher/practice/error.tsx - 教师端错误边界V4i18n 化)
app/(dashboard)/management/grade/practice/page.tsx ~140 年级主任宏观数据分析页
app/(dashboard)/management/grade/practice/loading.tsx - 年级端加载骨架
app/(dashboard)/management/grade/practice/error.tsx - 年级端错误边界V4i18n 化)
app/(dashboard)/parent/practice/page.tsx ~135 V4 新增2026-06-25家长查看子女练习统计与历史
app/(dashboard)/parent/practice/loading.tsx - V4 新增:家长端加载骨架
app/(dashboard)/parent/practice/error.tsx - V4 新增家长端错误边界i18n
shared/components/class-filter.tsx ~50 V4 新增:通用班级筛选器(ClassFilterItem 接口 + 预翻译文本 props消除 teacher/practice 对 error-book 的跨模块依赖

i18n

  • 翻译文件:shared/i18n/messages/{locale}/practice.json
  • 命名空间:practice
  • 键:
    • starter.*发起器V4a11y aria-label
    • session.*result.*history.*stats.*types.*status.*
    • teacher.*(含 overview/classComparison/typeBreakdown/knowledgePointWeakness/studentRanking/inactiveStudents
    • grade.*(含 overview/classComparison
    • V4 新增:parent.*家长端title/description/noChild/noChildDescription/stats/history/noChildSelected
    • V4 新增:errors.*结构化错误码session_not_found/session_ended/answer_not_found/answer_already_submitted/question_not_found/session_not_complete/no_questions_found/forbidden_scope/invalid_input/validation_error/invalid_source_meta页面错误pageErrorPractice/pageErrorGrade/pageErrorParent + pageErrorTitle* + loadFailed/loadFailedDescription/retry
    • V4 新增:messages.*用户可见操作反馈sessionCreated/answerSubmitted/answerCorrect/answerIncorrect/answerSkipped/sessionCompleted/sessionAbandoned
    • V4 新增:reasons.*AI 推荐理由枚举翻译student_initiated/teacher_assigned/parent_suggested
    • V4 新增:classFilter.*班级筛选器徽章sessionCount plural 规则)

数据库表

  • practice_sessions练习会话表id/studentId/subjectId/practiceType/sourceMeta/status/totalQuestions/answeredQuestions/correctCount/startedAt/completedAt
  • practice_answers练习答题记录表id/sessionId/studentId/questionId/variantContent/isVariant/orderIndex/status/studentAnswer/isCorrect/score/maxScore

2.31 rbac角色与权限管理模块 新增

职责角色与权限管理RBAC。提供角色 CRUD、角色启停、角色-权限分配、用户-角色分配、权限目录查询能力,支撑管理员对系统权限体系的动态化管理。

架构定位

  • 位于 modules/ 层,严格遵循三层架构
  • 通过 data-access.ts 提供角色 CRUD通过 data-access-assignments.ts 提供用户-角色分配查询,通过 lib/permission-catalog.ts 提供权限元数据目录
  • Server Actions 通过 requirePermission() 校验 ROLE_CREATE / ROLE_READ / ROLE_UPDATE / ROLE_DELETE / ROLE_ASSIGN / PERMISSION_READ 权限
  • shared/types/permissions.ts 协作:Role 类型从固定联合类型改为 string,支持动态角色;BUILTIN_ROLES 常量 + isBuiltinRole() 类型守卫区分内置角色与自定义角色
  • shared/lib/permissions.ts 协作:resolvePermissions 改为 async 从 DB 读取角色权限;ROLE_PERMISSIONS 重命名为 ROLE_PERMISSIONS_SEED(保留旧名 re-export 兼容)

导出函数

  • ActionscreateRoleAction / updateRoleAction / deleteRoleAction / toggleRoleEnabledAction / setRolePermissionsAction / assignUserRolesAction / getRoleDetailAction / isAdminRole 每个 Action 调用 requirePermission() 进行权限校验;内置角色不可删除/改名的校验在 data-access 层P2-7 新增 setRolePermissionsAction / assignUserRolesAction 调用 trackPermissionChange 埋点)
  • Data-accessdata-access.tsgetRoles / getRoleById P2-3 新增返回 userCount 字段)/ getRoleByName / createRole / updateRole / deleteRole / setRoleEnabled / getRolePermissions / setRolePermissions / ADMIN_ROLE_NAME
  • Data-accessdata-access-assignments.tsgetUserRoleNames / assignRolesToUser / getUserRoleAssignments
  • Liblib/permission-catalog.tsPERMISSION_CATALOG(权限目录常量,按模块分组)/ getAllPermissionMetas(获取所有权限元数据)/ getPermissionMeta(按权限点获取元数据)
  • Liblib/track.tstrackPermissionChange P2-7 新增RBAC 关键操作埋点接口no-op 实现,预留未来 analytics 接入)/ TrackPermissionChangeParams 接口

依赖关系

  • 依赖:shared/dbschema: roles/usersToRoles/rolePermissionsshared/types/permissions(权限常量与 Role 类型)、shared/lib/auth-guard(权限校验)、shared/lib/audit-logger(操作日志)、shared/lib/change-logger(数据变更日志)
  • 被依赖:app/(dashboard)/admin/rolesapp/(dashboard)/admin/permissionsapp/(dashboard)/admin/users(用户-角色分配)、modules/users(通过 UserRoleAssignDialog 组件)

已知问题

  • 职责清晰data-access 按职责拆分为 data-access.ts(角色 CRUDdata-access-assignments.ts(用户-角色分配)
  • 内置角色保护:ADMIN_ROLE_NAME 常量标识管理员角色,isSystem 字段标记系统内置角色,禁止删除/改名
  • 角色启停:isEnabled 字段控制角色启用/停用,停用角色不参与权限解析

文件清单

文件 职责
modules/rbac/actions.ts 8 个 Server Action含权限校验 + 审计日志 + 变更日志 + P2-7 埋点)
modules/rbac/data-access.ts 角色 CRUD + 角色权限分配getRoles/getRoleById[含userCount]/createRole/updateRole/deleteRole/setRoleEnabled/getRolePermissions/setRolePermissions
modules/rbac/data-access-assignments.ts 用户-角色分配getUserRoleNames/assignRolesToUser/getUserRoleAssignments
modules/rbac/lib/permission-catalog.ts 权限目录常量 + 元数据查询PERMISSION_CATALOG/getAllPermissionMetas/getPermissionMeta
modules/rbac/lib/track.ts P2-7 新增RBAC 关键操作埋点接口trackPermissionChangeno-op 实现)

组件清单

组件 职责
components/role-list.tsx 角色列表(含启停切换、删除; P2-5 新增 caption/aria-label
components/role-form-dialog.tsx 角色创建/编辑表单对话框
components/role-permission-matrix.tsx 角色-权限分配矩阵(按模块分组的权限勾选; P2-1 搜索+折叠P2-3 userCount 影响提示P2-5 fieldset/legend
components/permission-catalog-view.tsx 权限目录浏览视图(只读,展示所有权限点元数据; P2-5 aria-label
components/user-role-assign-dialog.tsx 用户-角色分配对话框(导出 UserRoleAssignDialog + AssignableRole 类型; P2-5 aria-label
components/role-management-view.tsx 角色管理主视图(列表 + 创建按钮)
components/role-detail-edit-button.tsx 角色详情页编辑按钮

路由清单

路由 文件 说明
/admin/roles app/(dashboard)/admin/roles/page.tsx 角色管理列表页权限ROLE_READ
/admin/roles/[id] app/(dashboard)/admin/roles/[id]/page.tsx 角色详情页权限ROLE_READ含权限矩阵
/admin/permissions app/(dashboard)/admin/permissions/page.tsx 权限目录浏览页权限PERMISSION_READ

权限点

  • ROLE_CREATE:创建角色(role:create
  • ROLE_READ:查看角色(role:read
  • ROLE_UPDATE:更新角色(role:update
  • ROLE_DELETE:删除角色(role:delete
  • ROLE_ASSIGN:分配用户角色(role:assign
  • PERMISSION_READ:查看权限目录(permission:read

数据库表

  • roles 字段更新id / name / description / createdAt / updatedAt / isSystemboolean DEFAULT FALSE标记内置角色/ isEnabledboolean DEFAULT TRUE角色启停开关
  • 迁移文件:drizzle/0011_rbac_role_flags_and_seed.sql

i18n

  • 翻译文件:shared/i18n/messages/{zh-CN,en}/rbac.json
  • 命名空间:rbac

2.32 invitation-codes邀请码模块 audit-P2-3 新增

职责:批量生成邀请码替代开放注册,支持预分配角色/班级/限定邮箱/过期时间;注册流程集成邀请码校验与消费(乐观锁),管理员可生成、列表、删除邀请码。

导出函数

  • Actionsactions.ts
    • generateInvitationCodesAction(input) — 批量生成邀请码USER_MANAGE 权限Zod 校验 1-100 张/批trackEvent invitation_codes.generated,返回明文 codes 仅此一次)
    • listInvitationCodesAction() — 列出邀请码USER_MANAGE 权限,含已使用+未使用,按 createdAt desc最多 200 条)
    • deleteInvitationCodeAction(codeId) — 删除邀请码USER_MANAGE 权限,仅允许删除未使用,已使用保留作审计记录)
    • validateInvitationCodeAction(code, email) — 校验邀请码(公开接口,注册流程预检用,不返回明文)
  • Data-accessdata-access.ts
    • generateInvitationCodes(input, createdBy) — 批量插入 + 查询回记录
    • listInvitationCodes(limit, includeUsed) — 列表查询
    • validateInvitationCode(code, email) — 校验(存在/已用/过期/邮箱匹配 4 项)
    • consumeInvitationCode(input) — 乐观锁消费(UPDATE ... WHERE usedBy IS NULL + affectedRows 检查)
    • deleteInvitationCode(codeId) — 删除(仅未使用)
    • purgeExpiredInvitationCodes() — 清理已过期且未使用cron 预留)
  • Liblib/code-generator.ts
    • generateInvitationCode() — 8 字符邀请码(node:crypto.randomBytes + 安全字母表 ABCDEFGHJKLMNPQRSTUVWXYZ23456789,剔除 I/O/0/1
    • generateUniqueInvitationCodes(count) — 批量生成Set 去重 + 最多 3× 重试)
    • generateBatchId() — 批次 IDbatch_<timestamp36>_<random6hex>
  • Schemaschema.tsGenerateInvitationCodesSchemacount 1-100、role enum、可选 email/classId/expiresAt/notesInvitationCodeFieldSchema(嵌入 RegisterSchema8-64 字符大写字母数字)、INVITATION_ROLE_VALUES
  • Typestypes.tsInvitationRole / InvitationCodeRecord / GenerateInvitationCodesInput / GenerateInvitationCodesResult / ValidateInvitationCodeResult / ConsumeInvitationCodeInput
  • Components
    • InvitationCodesView — 管理视图(统计卡片 + 表格 + 删除确认 + 生成对话框入口,接受 initialCodes + now props 由 Server Component 注入避免 Date.now() 在 render 调用)
    • GenerateInvitationCodesDialog — 生成对话框form/result 两态切换result 视图仅此一次展示明文供复制)

依赖关系

  • 依赖 shared/dbdrizzle 客户端)、shared/db/schemainvitationCodes 表)、shared/lib/auth-guardrequirePermissionshared/lib/track-eventshared/types/action-stateshared/types/permissions
  • 被依赖:modules/auth/actions.tsregisterAction 调用 validateInvitationCode + consumeInvitationCodeapp/(dashboard)/admin/invitation-codes/page.tsxServer Component 调用 listInvitationCodes + 渲染 InvitationCodesView

已知问题

  • audit-P2-3 已实施:系统开放注册缺少预分配角色/班级的机制 完整邀请码模块 + 注册流程集成 + admin 管理页

数据库表

  • invitation_codes 新增id / code(varchar 64, unique) / email(varchar 255, nullable) / role(varchar 50) / class_id(varchar 128, nullable) / notes(varchar 500, nullable) / created_at(timestamp) / created_by(varchar 128, nullable) / expires_at(timestamp, nullable) / used_by(varchar 128, nullable) / used_at(timestamp, nullable)
  • 索引:invitation_codes_code_idx(code) / invitation_codes_email_idx(email) / invitation_codes_used_by_idx(used_by)
  • 迁移文件:drizzle/0016_invitation_codes.sql

i18n

  • 翻译文件:shared/i18n/messages/{zh-CN,en}/invitation-codes.json
  • 命名空间:invitationCodes
  • 注册位置:src/i18n/request.tsPromise.all 数组 + messages 对象)

文件清单

文件 行数 职责
actions.ts 135 Server Actionsgenerate/list/delete/validateUSER_MANAGE 权限trackEvent 埋点)
data-access.ts 247 数据访问层generate/list/validate/consume/delete/purge乐观锁消费模式
schema.ts 45 Zod 验证GenerateInvitationCodesSchema + InvitationCodeFieldSchema + INVITATION_ROLE_VALUES
types.ts 65 类型定义InvitationRole / InvitationCodeRecord / Generate/Validate/Consume 接口)
lib/code-generator.ts 67 邀请码生成器8 字符安全字母表 + Set 去重 + 批次 ID
components/invitation-codes-view.tsx 302 管理视图(统计卡片 + 表格 + 删除确认 + 生成对话框入口)
components/generate-invitation-codes-dialog.tsx 387 生成对话框form/result 两态,结果可复制全部/单个)

路由

  • /admin/invitation-codes — 管理员邀请码管理页USER_MANAGE 权限Server Component → InvitationCodesView 客户端组件)

2.33 leave-requests在线请假模块 L-5 新增

职责:学生/家长代子女提交请假申请 → 教师/年级主任/教务主任审批 → 审批通过自动同步 excused 考勤记录(幂等)→ 申请人 pending 状态可撤销。

状态机pending → approved/rejectedpending/approved → cancelled(仅 requester 本人可撤销 pending

导出函数

  • Actionsactions.ts
    • createLeaveRequestAction(prevState, formData) — 创建请假申请LEAVE_REQUEST_CREATE 权限Zod 校验;二次校验 requester-student 关系:家长代子女需在 parentStudentRelations 中、学生本人 requesterId === studentId;校验 classId 确为学生当前活跃班级;hasOverlappingLeave 防止日期重叠trackEvent leave_request.created
    • reviewLeaveRequestAction(prevState, formData) — 审批请假申请LEAVE_REQUEST_REVIEW 权限pending → approved/rejectedrejected 必填 reviewComment审批通过后调用 attendance/data-access.syncExcusedFromLeaveRequest 同步考勤try/catch 不阻断;markAttendanceSynced 标志位防重复trackEvent leave_request.reviewed
    • cancelLeaveRequestAction(id) — 撤销请假LEAVE_REQUEST_CREATE 权限;仅 requester 本人的 pending 状态可撤销trackEvent leave_request.cancelled
  • Data-accessdata-access.ts
    • getLeaveRequests(params) — 分页查询(按 DataScope 过滤all/class_taught/children/class_members/ownedclass_members 由 currentUserId 过滤本人提交;批量查询学生/班级/提交人/审批人名称避免 N+1
    • getLeaveRequest(id, scope, currentUserId?) — 单条详情(按 DataScope 校验访问权限)
    • hasOverlappingLeave(studentId, startDate, endDate, excludeId?) — 日期重叠校验SQL startDate <= endDate AND endDate >= startDate,仅查 pending/approved
    • createLeaveRequest(data, requesterId) — 创建(new Date(startDate) 转换字符串为 Date状态默认 pending
    • reviewLeaveRequest(id, review, reviewerId) — 审批Drizzle db.update() 返回 ResultSetHeader需重新 db.select() 获取更新行)
    • cancelLeaveRequest(id, requesterId) — 撤销(仅 requester 本人的 pending 状态)
    • markAttendanceSynced(id) — 标记 attendanceSynced=true防止重复触发同步
    • getLeaveRequestsByIds(ids) — 按 ID 批量查询(按 startDate 升序)
    • countPendingForClasses(classIds) — 教师审批待办计数
  • Schemaschema.tsCreateLeaveRequestSchema(含 .refine() 跨字段校验 endDate >= startDateReviewLeaveRequestSchemarejected 时 reviewComment 必填)
  • Typestypes.tsLeaveType / LeaveStatus / LeaveRequest / LeaveRequestListItem / LeaveQueryParams / PaginatedLeaveResult / LeaveReviewInput
  • Components
    • LeaveRequestForm — 请假表单(useActionState + useFormStatus);家长模式 childOptions 下拉选择子女,学生模式 hidden input 写入 studentId/classId。注意prop 名为 childOptions 而非 children,避免与 React 特殊 children prop 冲突触发 react/no-children-prop ESLint 规则
    • LeaveRequestList — 只读列表展示STATUS_STYLES + STATUS_ICONS 状态徽章)
    • LeaveReviewDialog — 审批对话框(批准/拒绝按钮切换,拒绝必填 reviewComment
    • LeaveReviewList — 教师审批列表pending 项显示审批按钮,打开 LeaveReviewDialog

依赖关系

  • 依赖:shared/*db/auth-guard/track-event/types/permissions@/authclassesgetStudentActiveClass 校验当前班级、getClassNamesByIds 列表展示)、usersgetUserNamesByIds 列表展示学生/提交人/审批人姓名)、parentgetParentStudentMapByStudentIds 校验家长-子女关系)、attendancesyncExcusedFromLeaveRequest 审批通过后同步考勤)、next-intl
  • 被依赖:app/(dashboard)/{parent,student,teacher}/leave/page.tsxServer Component 直接调用 data-access 与渲染组件)

数据库表

  • leave_requests L-5 新增id / student_id / requester_id / class_id / leave_type(enum: sick/personal/family/other) / start_date(date) / end_date(date) / reason(text) / status(enum: pending/approved/rejected/cancelled, default pending) / reviewer_id(varchar, nullable) / review_comment(text, nullable) / reviewed_at(timestamp, nullable) / attendance_synced(boolean, default false) / created_at / updated_at
  • 索引:leave_requests_student_idx / leave_requests_requester_idx / leave_requests_class_idx / leave_requests_status_idx / leave_requests_reviewer_idx / leave_requests_class_status_idx(复合)/ leave_requests_student_status_idx(复合)
  • 外键:lr_c_fk(class_id → classes.id ON DELETE CASCADE) / lr_s_fk(student_id → users.id ON DELETE CASCADE) / lr_rq_fk(requester_id → users.id ON DELETE CASCADE) / lr_rv_fk(reviewer_id → users.id ON DELETE SET NULL)
  • 迁移文件:drizzle/0020_leave_requests.sql(含 INSERT IGNORE role_permissions 为 admin/teacher/parent/student/grade_head/teaching_head 注入权限)

权限

  • LEAVE_REQUEST_CREATEleave_request:create— admin/parent/student
  • LEAVE_REQUEST_READleave_request:read— admin/teacher/parent/student/grade_head/teaching_head
  • LEAVE_REQUEST_REVIEWleave_request:review— admin/teacher/grade_head/teaching_head

i18n

  • 翻译文件:shared/i18n/messages/{zh-CN,en}/leave.json
  • 命名空间:leave
  • 注册位置:src/i18n/request.tsPromise.all 数组 + messages 对象)

埋点事件

  • leave_request.created / leave_request.reviewed / leave_request.cancelled(在 shared/lib/track-event.ts 的 EventName 联合类型中扩展)

文件清单

文件 行数 职责
actions.ts ~180 3 个 Server Actioncreate/review/cancel权限校验 + Zod + 跨模块校验 + trackEvent
data-access.ts ~330 数据访问层CRUD + 分页 + DataScope 过滤 + 日期重叠校验 + 批量名称查询)
schema.ts ~50 Zod 校验CreateLeaveRequestSchema + ReviewLeaveRequestSchema
types.ts ~70 类型定义LeaveType/Status/Request/ListItem/Query/Result/ReviewInput
components/leave-request-form.tsx ~190 请假表单useActionState + 家长/学生双模式)
components/leave-request-list.tsx ~120 只读列表 + 状态徽章
components/leave-review-dialog.tsx ~150 审批对话框
components/leave-review-list.tsx ~100 教师审批列表

路由

  • /parent/leave — 家长请假页LEAVE_REQUEST_CREATE + LEAVE_REQUEST_READDataScope.children表单 childOptions 模式 + 列表)
  • /student/leave — 学生请假页LEAVE_REQUEST_CREATE + LEAVE_REQUEST_READDataScope.class_members 由 currentUserId 过滤;表单 defaultStudentId/classId 模式 + 列表)
  • /teacher/leave — 教师审批页LEAVE_REQUEST_REVIEWDataScope.class_taughtLeaveReviewList + LeaveReviewDialog

设计要点

  • 幂等考勤同步syncExcusedFromLeaveRequest 遍历日期区间,仅插入不存在的 excused 记录,避免重复同步;attendanceSynced 标志位防止审批重复触发
  • 跨模块联动容错:审批通过后调用考勤同步使用 try/catch 包裹,同步失败不阻断审批结果(审批已成功,考勤可由管理员后续手动补录)
  • 日期重叠校验hasOverlappingLeave 使用 SQL 条件检测区间重叠,防止重复请假
  • DataScope 行级过滤buildScopeFilter 支持 5 种 scopeall/class_taught/children/class_members/ownedclass_members 由 currentUserId 过滤本人提交
  • Drizzle update 返回值处理db.update() 返回 ResultSetHeader 而非行数据,需重新 db.select() 查询更新后的行
  • React children prop 冲突规避LeaveRequestForm 的 prop 名为 childOptions 而非 children,避免触发 react/no-children-prop ESLint 规则

2.34 search全文检索模块 2026-07-04 API 规范化重构新增

职责:封装跨 4 张表questions / textbooks / exams / announcements的全文检索数据访问层app/api/search/route.ts 调用。本次 API 规范化重构从 app/api/search/route.ts 下沉而来,消除 app/ 层直接查询 DB 的架构违规违反「app → modules → shared」三层架构单向依赖

导出函数data-access.tsserver-only

  • searchQuestions(q, limit?) — 题库检索P3-6 改用 MATCH(questions.contentText) AGAINST(? IN BOOLEAN MODE) 全文检索(基于 content_text STORED 生成列的 FULLTEXT 索引);toBooleanModeQuery 工具函数将原始查询转为 +word1* +word2* 格式;返回 SearchResultItem[]href 指向 /admin/questions?id=
  • searchTextbooks(kw, limit?) — 教材检索title/subject/publisher 三字段 OR 模糊匹配
  • searchExams(kw, limit?) — 试卷检索title/description 模糊匹配
  • searchAnnouncements(kw, limit?) — 公告检索:仅返回 status=published 的公告title/content 模糊匹配HTML 内容通过 stripHtml 提取纯文本摘要
  • DEFAULT_SEARCH_PAGE_SIZE= 10— 默认分页大小常量

类型定义types.ts

  • SearchType = "all" | "question" | "textbook" | "exam" | "announcement"
  • SearchResultItem{ id, title, snippet, type, href, createdAt }
  • SearchResponse{ success, query, type, results, total, page, pageSize }
  • isSearchType(v): v is SearchType — 类型守卫

设计要点

  • 容错降级:每个查询函数 try/catch 包裹,单表查询失败返回空数组而非抛错,避免拖垮整体搜索
  • 职责分离:本模块仅负责 DB 查询;角色过滤(学生不能搜题目/考试)由路由层基于 getAuthContext() 处理
  • 摘要生成extractTextFromJsonJSON content → stringstripHtml(去 HTML 标签)、truncate(截断+省略号)三个内部纯函数
  • as 断言清理:所有 as constas SearchResultItem 类型收窄均使用类型守卫或字面量标注,符合项目 TypeScript 严格模式

依赖关系

  • 依赖:shared/dbdb 客户端)、shared/db/schemaquestions/textbooks/exams/announcements 表)、drizzle-ormand/or/eq/like/desc/sql
  • 被依赖:app/api/search/route.ts

数据库表(只读):

  • questionscontent JSON 字段、P3-6 新增 content_text STORED 生成列 CAST(content AS CHAR) 用于 FULLTEXT 索引、type、createdAt
  • textbookstitle、subject、grade、publisher、createdAt
  • examstitle、description、status、createdAt
  • announcementstitle、content、type、status、createdAt

文件清单

文件 行数 职责
data-access.ts ~230 4 个 search 函数P3-6: searchQuestions 改 MATCH AGAINST+ 4 个内部纯函数extractTextFromJson/stripHtml/truncate/toBooleanModeQuery+ DEFAULT_SEARCH_PAGE_SIZE 常量
types.ts 31 SearchType/SearchResultItem/SearchResponse 类型 + isSearchType 类型守卫

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

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 → 626 混入排名计算业务逻辑 已拆分 已拆为 data-access.ts(626行2026-06-25 删除 re-export) + stats-service.ts(414行) + data-access-student.ts(293行) + data-access-scans.ts(44行),统计函数迁移至 stats-service.ts
shared/db/schema.ts 1111 54 张表混合 按业务域拆分为 schema/auth.ts + schema/academic.ts + schema/exam.ts + ...,通过 index.ts 聚合

P0-2shared/lib ↔ auth 循环依赖 已修复

shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*

影响shared 层无法独立测试/复用;架构上基础设施不应反向依赖应用层。

修复方案(已实施):

  • 创建 shared/lib/session.ts 封装 session 获取(getSession()server-only
  • session.ts 内部使用 dynamic import("@/auth") 打破模块级静态循环
  • audit-logger.ts / change-logger.ts / auth-guard.ts 改为 import { getSession } from "@/shared/lib/session",不再直接依赖 @/auth
  • 运行时调用链保持不变,模块加载图无环

P0-3dashboard 跨模块直接查询 11 张表 已修复

dashboard/data-access.tsgetAdminDashboardData 原直查 sessions/users/usersToRoles/roles/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions。

修复方案(已实施):

  • 各模块新增 dashboard stats 函数:
    • users/data-access.tsgetUsersDashboardStats()userCount/activeSessionsCount/userRoleCounts/recentUsers
    • classes/data-access.tsgetClassesDashboardStats()classCount
    • textbooks/data-access.tsgetTextbooksDashboardStats()textbookCount/chapterCount
    • questions/data-access.tsgetQuestionsDashboardStats()questionCount
    • exams/data-access.tsgetExamsDashboardStats(scope?)examCount支持 scope 过滤)
    • homework/stats-service.tsgetHomeworkDashboardStats(scope?)4 个计数,支持 scope 过滤)
  • dashboard 改为并行调用:Promise.all([getUsersDashboardStats(), getClassesDashboardStats(), ...])
  • 返回值结构保持不变,调用方无需修改

P0-4messaging 绕过 notifications 直接写通知 已修复

messaging/actions.ts 原直接调用 createNotificationmessageNotifications 表,导致用户通知偏好失效、多渠道通知无效。notifications/data-access.tsin-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 通知 CRUDmessaging/notification-preferences.ts 转为 re-export shimmessaging/types.ts re-export 通知类型
  • 依赖方向变为单向messaging → notificationsmessaging 调用 sendNotification dispatcher 发送通知)

P0-5classSchedule 表三处写入口 已修复

  • classes/data-access.tscreateClassScheduleItem 等)
  • scheduling/actions.tsapplyAutoScheduleAction 直接 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 / UpdateClassScheduleItemInputclasses/types.ts 迁移到 scheduling/types.ts
  • 所有 classSchedule DB 写入统一由 scheduling 模块管理(insertClassScheduleItem / updateClassScheduleItemById / deleteClassScheduleItemById / replaceClassSchedule

P0-6proctoring 死代码与重复实现

  • exam-mode-config.tsx 未集成到考试表单(监考功能无法启用)
  • 事件上报存在 Server Action 与 REST API 双通道重复 已修复

修复方案(已实施):

  • src/app/api/proctoring/event/route.ts 移至 deletes/api/proctoring/event/route.ts,消除事件上报的 REST API 重复通道
  • Server Action recordProctoringEventActionproctoring/actions.ts)为唯一规范路径
  • exam-mode-config.tsx 暂保留原位(集成到考试表单属于功能新增,不在本次修复范围)

P0-7proxy.ts 路由权限跨角色访问漏洞 已修复webapp-testing 发现)

问题proxy.ts/teacher/parent 路由前缀都使用 EXAM_READ 权限校验,但 studentparent 角色也拥有 EXAM_READ 权限,导致:

  • 学生可访问 /teacher/* 所有页面
  • 教师可访问 /parent/* 所有页面

修复方案(已实施):

  • /teacher 改为 Permissions.EXAM_CREATE(仅 teacher/admin 拥有)
  • /parent 改为 Permissions.DASHBOARD_PARENT_READ(仅 parent 拥有)
  • 新增 DASHBOARD_ROUTE_PERMISSIONS 细粒度仪表盘权限表,覆盖各角色 dashboard 路由
  • 跨角色访问测试验证teacher/student/parent 访问其他角色路由均被重定向回各自 dashboard

P0-8list-pagination.tsx 客户端/服务端边界错误 已修复webapp-testing 发现)

问题shared/components/ui/list-pagination.tsx 文件顶部声明 "use client",但导出的 computePagination()paginate() 纯工具函数被 4 个服务端组件页面直接调用:

  • teacher/attendance/page.tsx
  • teacher/homework/assignments/page.tsx
  • teacher/homework/submissions/page.tsx
  • teacher/grades/page.tsx

导致 Next.js 16 / React 19 抛出错误:Attempted to call computePagination() from the server but computePagination is on the client,页面渲染时触发 ErrorBoundary。

修复方案(已实施):

  • 移除 list-pagination.tsx"use client" 指令
  • ListPagination 组件仅使用 LinkButton 和图标,无需客户端交互,可作为服务端组件渲染
  • computePaginationpaginate 恢复为服务端可调用的纯函数

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-accessgetClassGradeIdsByClassIds 已实现 / getClassStudentsByClassId / getActiveClassStudents / getClassExists / getClassNameById / getClassNamesByIds / getActiveStudentIdsByClassId / getStudentActiveClassId / getClassesByGradeId / verifyTeacherOwnsClass / getTeacherIdForMutations
    • exams/data-accessgetExamForHomeworkCreation 已实现(getExamIdsByGradeIds / getExamSubjectIdMap / getExamWithQuestionsForHomework / getExamSubmissionWithAnswers / getExamForProctoringCrossModule / getExamSubmissionForProctoringCrossModule / getExamSubmissionsForExam / getExamTitleById
    • school/data-accessgetSubjectOptions / getGradeOptions 已实现(含 getSubjectNameMapByIds 批量科目名称映射P1-1 新增供 homework/data-access-classes 调用)
    • users/data-accessgetUserNameByIds / getStudentInfo 已实现(getUserNamesByIds / getUserWithRole / getUserBasicInfo / getUserIdsByGradeId / getCurrentStudentUser
    • textbooks/data-accessgetKnowledgePointOptions 已实现
    • questions/data-accessinsertQuestionWithRelations 已通过 createQuestionWithRelations 供 exams 调用 / getKnowledgePointsForQuestions
    • homework/data-access-classes 新增 7 个函数供 classes 模块跨模块调用

P1-2actions 层混入数据访问逻辑 已修复

已完成修复2026-06-17commit 84d66364 个模块的 actions 层 DB 操作全部下沉到 data-access

模块 问题 Action 修复内容
exams updateExamAction / deleteExamAction / duplicateExamAction / getExamPreviewAction / getSubjectsAction / getGradesAction 新增 7 个 data-access 函数getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGradesactions.ts 831→691 行data-access.ts 374→471 行。 2026-06-24 进一步拆分actions.ts 1525→633 行(拆出 actions-helpers.ts/actions-rich-editor.ts/ai-pipeline/auto-mark.tsP0-4 修复:新增 updateExamWithRichEditorData 下沉富文本编辑器更新事务
homework createHomeworkAssignmentAction157 行)/ startHomeworkSubmissionAction / saveHomeworkAnswerAction / submitHomeworkAction / gradeHomeworkSubmissionAction 新建 data-access-write.ts285 行10 个写函数actions.ts 387→239 行
questions createQuestionAction / updateQuestionAction / deleteQuestionAction / getKnowledgePointOptionsAction 新增 4 个 data-access 函数createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptionsactions.ts 294→149 行data-access.ts 138→260 行; 审计新增 3 个级联 data-access 函数getTextbookOptions/getChapterOptions/getKnowledgePointOptionsByChapter+ 3 个对应 Server Action + 批量删除/导入导出 data-access 与 Actionactions.ts 149→380+ 行data-access.ts 260→600+ 行
announcements 所有写操作 Action 新增 5 个 data-access 函数insertAnnouncement/updateAnnouncementById/deleteAnnouncementById/publishAnnouncementById/archiveAnnouncementByIdactions.ts 242→197 行data-access.ts 120→171 行

剩余未修复模块(不在本次 P1-2 范围):

  • usersupdateUserProfileAction 直接 db.update 已下沉到 data-accessP1-1 修复)
  • schedulingapplyAutoScheduleAction / autoScheduleAction 直接 db.transaction + db.select 已改为调用 replaceClassSchedule 统一写入口;autoScheduleAction 直查 users 表已改为通过 users data-accessP0-5 / P1-1 修复)

P1-3auth.ts 混合 5 类职责 已完成

src/auth.ts 原 293 行混合NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数。

已完成拆分auth.ts 现 193 行,仅保留 NextAuth 配置):

  • 密码安全 DB 操作 → shared/lib/password-security-service.tsgetOrCreatePasswordSecurity / recordFailedLogin / resetFailedLoginserver-only
  • 角色规范化 → shared/lib/role-utils.tsnormalizeRole / resolvePrimaryRole纯函数
  • bcrypt 哈希规范化 → shared/lib/bcrypt-utils.tsnormalizeBcryptHash纯函数
  • IP 解析 → shared/lib/http-utils.tsresolveClientIpserver-only

后续可选优化audit-P1-3 已完成

  • authorize 回调可进一步拆分为 checkRateLimit / checkAccountLockout / verifyPassword / loadUserRoles,使 auth.ts 降至 ≤150 行 已抽取至 modules/auth/services/login-service.tsauthenticateUser() 函数7 步:输入解析/速率限制/用户查询/账户锁定/密码校验/2FA/角色加载auth.ts authorize 降至 3 行薄包装,整体文件 132 行

audit-P0 重构2026-06-25:在 P1-3 拆分基础上进一步审计 auth.ts识别并修复以下问题

  • audit-P0-2auth.ts 跨模块依赖 modules/settings/actions-security.verifyTwoFactorForLogin 改为同模块 modules/auth/services/two-factor-service.ts 编排2FA 仍通过 modules/settings/data-access-two-factor + modules/settings/lib/totp 获取数据(合理跨模块 data-access
  • audit-P0-5auth.ts 3 处 as 断言 通过 next-auth.d.ts 扩展 User 接口(添加 role?/roles? 字段)+ "token" in message 类型收窄消除
  • audit-P0-6proxy.tsonboarding/data-access.resolveDefaultPathByRoles 重复实现角色路径解析 提取 shared/lib/route-resolver.ts 纯函数 resolveDefaultPath(roles)proxy.ts 与 onboarding/actions.ts 共用
  • audit-P0-7e2e smoke-auth 测试选择器与实际中文 UI 不符 修正为 getByLabel("姓名") / getByLabel("邮箱") / getByLabel("密码") / getByRole("button", { name: "创建账户" })

P1-4users/import-export.ts 四重职责 已完成

users/import-export.ts 原 291 行混合:导入解析 + 导出 + 用户创建(含密码哈希)+ 班级注册(跨模块写 classEnrollments

已完成拆分import-export.ts 现 157 行,仅保留文件解析/生成):

  • 用户创建(含密码哈希、角色分配)→ user-service.tsbatchImportUsers82 行server-only
  • 班级注册 → class-registration.tsregisterStudentByInvitationCode21 行server-only
  • batchImportUsers 不再直写 classEnrollments,改为调用 classes/data-access.enrollStudentByInvitationCode
  • import-export.ts 通过 re-export batchImportUsers / UserImportResult 保持向后兼容(actions.tsapp/api/export/route.ts 无需修改)

P1-5notifications 反向依赖 messaging 已完成

notifications/data-access.tsin-app-channel.ts 原反向依赖 messaging 模块的偏好和 createNotification。

已完成修复(与 P0-4 一并解决):

  • messageNotificationsnotificationPreferences 表所有权移交 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 导入 resolveClientIpgetUserAgent,删除本地重复实现
  • auth.ts 已在 P1-3 中改用 resolveClientIp
  • 四处实现统一,消除不一致风险(resolveClientIpx-forwarded-for 第一段,更准确)

3.3 P2 代码质量问题(机会修复)

序号 问题 模块
P2-1 exams/ai-pipeline.ts 857 行,混合 4 类职责 exams
P2-2 exams/actions.ts 832 行(超 800 建议) 已修复P1-2 后降至 691 行) exams
P2-3 shared/lib/ai.ts 218 行,混合 5 类职责 已修复P2-2 已拆分为 ai/ 目录) shared
P2-4 onboarding-gate.tsx 业务逻辑泄漏到 shared 已修复(迁移至 modules/onboarding/,改用独立路由 + Server Action + middleware 重定向) shared/onboarding
P2-5 global-search.tsx 业务类型硬编码在 shared shared
P2-6 proxy.ts 硬编码权限字符串,未复用 Permissions 常量 已修复(改用 Permissions 常量) proxy
P2-7 useA11yId Hook 错放在 lib/ 而非 hooks/ 已修复(文件已不存在;use-aria-live.ts 已在 hooks/ 目录) shared
P2-8 schema.ts 分节编号混乱section 12 出现在 14b 之后) 已修复(重新编号为连续 1-24 shared/db
P2-9 audit/actions.ts Excel 导出逻辑内联 audit
P2-10 school 模块审计日志不一致(仅 school 实体记录) school
P2-11 announcements 死代码 void wasPublished 已修复(代码中已不存在) announcements
P2-12 announcements 权限模式不一致requireAuth vs requirePermission 已修复 announcements
P2-13 files try-catch 吞错误 已修复(所有 catch 块已添加 console.errorconditions 隐式 any[] 改为 SQL[] files
P2-14 elective runLottery 使用 Math.random 已修复(改为 Fisher-Yates 无偏洗牌) elective
P2-15 elective selectCourse FCFS 并发超卖风险 已修复db.transaction + FOR UPDATE 行锁) elective
P2-16 diagnostic 班级报告 studentId 字段复用 diagnostic
P2-17 layout 用权限反推角色 已修复(app-sidebar.tsx 改用 hasRole() 判断角色N3 新增多角色切换机制:SidebarContext.currentRole/setCurrentRole + 侧边栏底部 Select 下拉) layout
P2-18 scheduling/actions.ts 末尾 re-export data-access 已修复(移除 re-export4 个页面改为从 data-access 导入) scheduling
P2-19 ExamAssembly / ExamPreviewQuestionEditor 10 个 props exams
P2-20 homework/data-access.getDemoStudentUser 使用 auth() 而非 auth-guard 已修复(已迁移至 users/data-access.getCurrentStudentUser6 个 student 页面改用 users 模块;elective 页面改用 getAuthContext()homework 保留 re-export 向后兼容) homework

3.4 解耦优先级路线图

立即执行P0

  1. 拆分 classes/data-access.ts2104 行 → 按职责拆 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.ts1038 行 → 分离排名逻辑) 已完成(拆为 data-access.ts 626行 + stats-service.ts 414行 + data-access-student.ts 293行 + data-access-scans.ts 44行
  3. 修复 shared/libauth 循环依赖 已完成(新增 shared/lib/session.ts 单一入口3 个 shared/lib 文件改为通过 getSession 获取 sessiondynamic import 打破静态循环)
  4. dashboard 改为通过各模块 data-access 获取数据 已完成P0-3 修复:并行调用各模块 dashboard stats 函数)
  5. messaging 写通知改为通过 notifications dispatcher 已完成P0-4 / P1-5 修复:通知 CRUD 和偏好迁移至 notifications 模块messaging 通过 dispatcher 发送通知)
  6. 统一 classSchedule 写入口到 scheduling 模块 已完成P0-5 修复classSchedule 写函数从 classes/data-access-schedule.ts 迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留读函数)
  7. 集成 proctoring/exam-mode-config 到考试表单 部分完成P0-6 修复:删除重复的 /api/proctoring/event REST 路由Server Action 为唯一规范路径exam-mode-config.tsx 集成属于功能新增,暂保留原位)

短期执行P1

  1. actions 层移除直接 DB 操作exams/homework/questions/announcements/users/scheduling 已完成(全部 6 个模块均已修复)
  2. 拆分 auth.ts 已完成4 个辅助函数组迁移至 shared/libauth.ts 保留 NextAuth 配置)
  3. 拆分 users/import-export.ts 已完成(拆为 import-export.ts 157行 + user-service.ts 82行 + class-registration.ts 21行班级注册改为调用 classes/data-access
  4. 消除 notifications → messaging 反向依赖 已完成P0-4 / P1-5 修复:通知表所有权迁移至 notificationsin-app-channel 改为静态导入)
  5. 提取 shared/lib/http-utils.ts 统一 IP 提取 已完成(新增 getUserAgent,三个 logger 统一复用 resolveClientIp / getUserAgent
  6. 各模块暴露跨模块查询接口(见 P1-1 已完成(所有跨模块直查已改为通过对方 data-access 接口)

中期执行P2

  1. 建立模块间数据访问规范(通过对方 data-access 或导出查询函数) 已完成P1-1 修复)
  2. schema.ts 按业务域分节 已完成P2-8 修复:重新编号为连续 1-24消除 8b/14b/乱序问题)
  3. 拆分 exams/ai-pipeline.ts
  4. 拆分 shared/lib/ai.ts 已完成P2-2commit 6588f74拆分为 ai/ 目录 6 个文件,原 ai.ts 保留为重导出)
  5. shared 层业务逻辑下沉到 modules 层
  6. 代码质量问题逐项修复( 大部分已修复React.cache 包装、Promise.all 并行化、错误吞没清理、非空断言清理、函数返回类型补齐、重复代码提取、合并 filter 遍历、Set/Map 优化、P2-6 proxy.ts 权限常量复用、P2-11 announcements 死代码清理、P2-17 layout 角色判断、P2-18 scheduling re-export 移除)

3.5 标杆实践(建议推广)

实践 模块 说明
算法纯函数化 scheduling/auto-scheduler.ts 无 DB 依赖,可独立测试,应作为算法抽取模板
stats 文件拆分 attendance/data-access-stats.ts 统计逻辑独立成文件classes 应效仿
data-access 多文件拆分 grades/data-access*.ts 按职责拆分为 3 个文件CRUD/分析/排名)
actions 辅助函数 course-plans/actions.ts handleError / revalidatePlanPaths 消除重复
actions 编排模式 textbooks/actions.ts 权限校验 → 调用 data-access → revalidatePath标杆
DataScope 接入 attendance/actions.ts 6 种数据范围完整支持
权限统一接入 school / attendance / course-plans 全部 Action 使用 requirePermission
跨模块解耦 grades 通过外键引用 exams/homework不直接访问其表
渠道抽象 notifications/channels/ 接口 + 工厂 + Mock 实现

3.6 教师端全模块审计修复2026-06-23

本次审计修复覆盖教师端 13 个模块组、约 80+ 个文件,聚焦越权访问漏洞、错误处理一致性、类型安全与 React 性能。以下仅记录涉及架构图同步的变更(函数签名/导出/新增文件),其余纯实现修复(如 try/catch 补齐、useMemo/useCallback、key prop、as 断言清理)不在此列出。

3.6.1 新增共享文件 src/shared/lib/action-utils.ts

统一 Server Action 错误处理与客户端 Action 调用模式,避免内部错误消息暴露给客户端。

导出 类型 签名 用途
handleActionError function (e: unknown) => ActionState<never> 统一 Server Action catch 块错误处理PermissionDeniedError/BusinessError 返回其 message其他 Error 返回通用消息并 console.error 记录
safeActionCall function <T>(action: () => Promise<ActionState<T>>, options?: { onError?, onFinally? }) => Promise<ActionState<T> | null> 客户端调用 Server Action 的 try/catch/finally 包装器,防止 UI 永久卡 loading
safeJsonParse function <T>(json: string, errorMessage: string) => T 安全 JSON.parse失败抛 ValidationError替代裸 JSON.parse
safeParseDate function (value: string, fieldName: string) => Date 校验日期字符串有效性,无效抛 ValidationError
safeParseNumber function (value: string, fieldName: string) => number 校验数字字符串,无效抛 ValidationError
escapeLikePattern function (input: string) => string 转义 SQL LIKE 通配符(% _ \),防止用户输入干扰模糊查询
BusinessError class extends Error 已知业务错误基类message 可安全返回客户端(含可选 code
NotFoundError class extends BusinessError 资源不存在错误(自动生成 ${resource} 不存在 消息code: not_found
ValidationError class extends BusinessError 输入校验错误code: validation_error

3.6.2 grades 模块签名变更

data-access 层新增 scope 参数P3 修复:所有查询函数应用 buildScopeClassFilter 进行行级 scope 过滤):

函数 文件 旧签名 新签名
getGradeRecords data-access.ts (params: GradeQueryParams) => Promise<GradeRecordListItem[]> (params: GradeQueryParams & { scope: DataScope; currentUserId?: string; limit?: number; offset?: number }) => Promise<PaginatedGradeRecords>
getClassGradeStats data-access.ts (classId, subjectId?, examId?) => Promise<GradeStats> (classId, subjectId?, examId?, scope?: DataScope, currentUserId?: string) => Promise<GradeStats | null>
getStudentGradeSummary data-access.ts (studentId) => Promise<StudentGradeSummary> (studentId, scope?: DataScope) => Promise<StudentGradeSummary | null>class_taught scope 校验学生归属)
getClassRanking data-access.ts (classId, subjectId?, examId?) => Promise<ClassRankingItem[]> (classId, subjectId?, examId?, scope?: DataScope, currentUserId?: string) => Promise<ClassRankingItem[]>(含并列排名处理)
getClassStudentsForEntry data-access.ts (classId) => Promise<{id,name}[]> (classId, scope?: DataScope) => Promise<{id,name,email}[]>class_taught scope 校验 classId
getRankingTrend data-access-ranking.ts (studentId, subjectId?, semester?) => Promise<RankingTrendResult | null> (studentId, subjectId?, semester?, scope?: DataScope) => Promise<RankingTrendResult | null>class_taught scope 校验)

lib 层签名变更

  • buildScopeClassFilter(scope: DataScope, currentUserId?: string): SQL | null(新增 currentUserId 参数,class_members scope 内置 eq(gradeRecords.studentId, currentUserId) 过滤P3-26lib/grade-utils.ts 迁移至 lib/scope-filter.ts

新增导出

  • assertClassInScope(scope: DataScope, classId: string): string | null v4-P2-6 从 actions.ts 迁移至 lib/scope-check.ts:校验 classId 是否在 scope 允许范围内,供 actions.ts 与 actions-analytics.ts 复用。迁移原因actions.ts 是 "use server" 文件要求所有 export 为 async而 assertClassInScope 是同步函数)
  • PaginatedGradeRecords 接口(data-access.ts{ records: GradeRecordListItem[]; total: number },配合 DB 层分页)

3.6.3 homework 模块签名变更

data-access 层新增 scope 参数P3 修复:教师仅查看自己创建的作业,学生/家长仅查看相关作业):

函数 文件 新签名
getHomeworkAssignments data-access.ts (params?: { creatorId?, ids?, classId?, scope?: DataScope }) => Promise<HomeworkAssignmentListItem[]>
getHomeworkAssignmentById data-access.ts (id: string, scope?: DataScope) => Promise<HomeworkAssignmentListItem | null>
getHomeworkSubmissions data-access.ts (params?: { assignmentId?, classId?, creatorId?, scope?: DataScope }) => Promise<HomeworkSubmissionListItem[]>
getHomeworkAssignmentReviewList data-access.ts (params: { creatorId: string; scope?: DataScope }) => Promise<HomeworkReviewListItem[]>

3.6.4 lesson-preparation 模块变更

新增 Zod schemaschema.ts

  • publishLessonPlanHomeworkSchema发布作业输入校验planId/blockId 必填classIds 至少 1 个availableAt/dueAt 日期格式校验)
  • 导出类型 PublishLessonPlanHomeworkInput = z.infer<typeof publishLessonPlanHomeworkSchema>

data-access-versions.ts 事务变更

  • revertToVersion 包裹 db.transaction 确保原子性(回滚版本时同步更新 lessonPlans.content 与版本记录)
  • createLessonPlanVersion 已使用 db.transaction(版本号自增 + 插入版本记录原子化)

3.6.5 其他修复(不涉及签名变更,仅记录范围)

  • 越权访问修复15+ 处页面添加 requirePermissiondata-access 层添加 scope 过滤
  • 功能性 BUG 修复question-actions 删除字段名、question-columns 时间显示、textbook-card 删除按钮、block-renderer 拖拽
  • 统一错误处理:所有 Server Action catch 块改用 handleActionError
  • 客户端 Action 调用添加 try/catch/finally20+ 处,使用 safeActionCall
  • JSON.parse 改用 safeJsonParse8+ 处)
  • Date 解析添加校验15+ 处,使用 safeParseDate
  • 数据库事务添加:batchCreateGradeRecordsdeleteChapterrevertToVersion
  • 性能优化:Promise.all 并行化、DB 层分页grades getGradeRecords
  • TypeScript as 断言清理30+ 处,改用类型守卫)
  • React 性能修复:useMemouseCallbackkey prop、渲染期间副作用移除

3.7 API 规范化专项重构2026-07-04

本次重构统一 app/api/**/route.ts 的响应信封、错误处理与状态码映射,消除 11 个路由的重复模板代码与脆弱字符串匹配模式。新增 2 个共享工具文件 + 1 个 search 模块。

3.7.1 新增共享工具

文件 用途
shared/lib/api-response.ts API 路由层统一响应工具:apiSuccess / apiError / apiFromAction / handleApiError / withApiErrorHandler HOF / parseJsonBody / errorToStatus
shared/lib/sse.ts SSE 共享工具:formatSseEvent / formatSseError / formatSseDone / createSseResponse / createSseError

3.7.2 新增模块

modules/search/2 个文件):从 app/api/search/route.ts 下沉 4 个查询函数searchQuestions/searchTextbooks/searchExams/searchAnnouncements消除 app/ 直查 DB 的架构违规。

3.7.3 重构路由清单11 个)

路由 重构内容
api/search 改用 modules/search/data-access(消除直查 DBwithApiErrorHandler + apiSuccess
api/proctoring/event 改用 getExamSubmissionForProctoring(消除与 data-access 重复实现);parseJsonBody + Zod + ValidationError;修复 401→403
api/upload apiFromAction(result, { headers }) 替代字符串匹配415 等状态码改为 errorCode 驱动
api/files/[id] GET/DELETE 用 apiFromAction;路由从 80+ 行简化到 38 行
api/files/batch-delete 使用 apiFromAction
api/export EXPORT_TYPES: Record<ExportType, Permission | null> 映射表替代 4 处嵌套 try/catch统一 requirePermission
api/import ValidationError + apiError;修复 401→403
api/ai/chat apiSuccess / apiError + withApiErrorHandler
api/ai/chat/stream 改用 createSseError / createSseResponse / formatSseEvent / formatSseDone;补充 dynamic = "force-dynamic"
api/notifications/stream 改用 SSE 共享 helpers补充 dynamic = "force-dynamic"PermissionDeniedError → 403其他 → 401
api/cron/audit-cleanup withApiErrorHandler;字段名 errormessageoksuccess
api/onboarding/complete 410 响应改用 apiError(..., 410, "deprecated")
api/onboarding/status 同上
api/rate-limit-test apiSuccess / apiError + withApiErrorHandler

3.7.4 模块改动

modules/files/actions.ts:移除本地 handleActionError,改用 @/shared/lib/action-utils 统一版本;为所有失败分支添加 errorCodevalidation_error / not_found / permission_denied / unexpected),驱动 apiFromAction 状态码映射。

3.7.5 客户端适配6 个文件)

所有客户端 fetch 调用适配新响应信封 { success, message?, errorCode?, data? }

文件 改动
modules/files/hooks/use-file-upload.ts 响应体类型改为 { success, data: Partial<FileUploadResult> }
modules/exams/editor/selection-toolbar.tsx data.urldata.data.urldata.iddata.data.id
modules/homework/components/scan-uploader.tsx 同上
modules/settings/components/avatar-upload.tsx 修复原双重 .json() bug适配 responseJson.data.url
modules/files/hooks/use-file-batch-operations.ts body.deletedCountbody.data?.deletedCount
modules/users/components/user-import-dialog.tsx data.sheets?.[0]?.rowsdata.data?.sheets?.[0]?.rows

3.7.6 设计决策

问题 决策 理由
搜索 API 架构违规 新建 modules/search/ 模块 遵循三层架构 app → modules → shared,下沉 DB 查询到 data-access 层
deprecated onboarding 路由 保留 410 响应 + 统一信封 兼容旧客户端,apiError(..., 410, "deprecated") 表明废弃原因
rate-limit-test 测试端点 保留并规范化 测试基础设施,使用统一信封便于客户端解析
错误→状态码映射 errorCode 驱动 替代 result.message?.includes("not found") 字符串匹配脆弱模式
异常处理 withApiErrorHandler HOF 消除每个 route 重复 try/catch统一 PermissionDeniedError→403 映射

3.7.7 响应信封规范

type ApiResponse<T = unknown> = {
  success: boolean
  message?: string
  errorCode?: string
  data?: T
}

错误→状态码映射规则:

  • PermissionDeniedError → 403已认证但无权限
  • NotFoundError → 404
  • ValidationError / BusinessError → 400
  • 未认证 → 401
  • 限流 → 429
  • 其他 → 500

ActionState.errorCode → 状态码映射:

  • not_found → 404
  • validation_error → 400
  • auth_required → 401
  • permission_denied → 403
  • 其他 → 500

附录 A模块间依赖矩阵

行表示使用方,列表示被使用方。 合理依赖, 违规直查, 循环依赖。 P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口。

↓ 使用 → shared auth exams homework questions textbooks classes school dashboard users grades messaging notifications lesson-prep error-book 其他
shared - 已修复 - - - - - - - - - - - - - -
auth(root) db/lib - - - - - - - - - - - - - - -
exams - - data-access - data-access data-access - - - - - - - -
homework data-access - 关系 - data-access data-access - data-access - - - - - -
questions - - - data-access - - - - - - - - - -
textbooks - - UI - - - - - - - - - - -
classes data-access data-access - - - data-access - - - - - - - schedulingP0-5data-access-class-schedule 写函数)
school - - - - - - - ⚠️可接受 - - - - - -
grades 外键 外键 - - data-access data-access - data-access - - - - - -
dashboard data-access data-access data-access data-access data-access - - data-access - - - - - -
users - - - - data-access - - - - - - - - rbacUserRoleAssignDialog 组件)
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 - - data-access - - parent/notificationsv2-P2-6/v4-P1-5
parent - data-access - - data-access data-access - data-access data-access - - - - -
elective - - - - data-access data-access - data-access - - - - - -
course-plans - - - - - - - - - - -
audit - - - - - - - - - - - - - -
announcements - - - - - - - - - - - - -
files - - - - - - - - - - - - - -
settings - - - - - - - - - - - - -
layout - - - - - - - - - - - - -
lesson-preparation data-access data-access data-access data-access data-access - - - - - - - - files/ai
error-book - - actions - data-access - - - - - - - - -
rbac db/types/lib - - - - - - - - - - - - - -

附录 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/gradesDataScope
  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/actionsupdateExamAction/deleteExamAction/duplicateExamAction 用于定位考试
  4. 传入 homework/actions.createHomeworkAssignmentActionsourceExamId 参数
  5. homeworkAssignments 表中作为外键关联到源考试
  6. homework/data-access.getHomeworkAssignmentAnalytics 用于追溯作业来源

classId

  1. classes/actionscreateTeacherClassAction/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.tsPermissions 常量定义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.tsresolveDataScope(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 层核心函数

// 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[]): Promise<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/ (audit-P1-6 重构:单文件拆分为目录,抽象 RateLimiter 接口)
//   index.ts —— 门面getRateLimiter() 按 env.RATE_LIMIT_DRIVER 选择实现rateLimit()/resetRateLimit() 返回 Promise
//   types.ts —— RateLimiter 接口 + RateLimitParams/RateLimitResult 类型
//   rules.ts —— RATE_LIMIT_RULES / rateLimitKey / rateLimitHeaders 纯函数(同步)
//   memory-limiter.ts —— MemoryRateLimiter 类(默认,进程级 Map
//   redis-limiter.ts —— RedisRateLimiter 类(基于 @upstash/ratelimit + @upstash/redis懒加载
//   upstash-modules.d.ts —— @upstash/* 可选依赖类型声明(仅 RATE_LIMIT_DRIVER=redis 时安装)
rateLimit(params: RateLimitParams): Promise<RateLimitResult>  // audit-P1-6改为 Promise 返回
resetRateLimit(key: string): Promise<void>  // audit-P1-6改为 Promise 返回
rateLimitKey(prefix: string, identifier: string): string  // 同步纯函数
rateLimitHeaders(result: RateLimitResult): Record<string, string>  // 同步纯函数
getRateLimiter(): Promise<RateLimiter>  // audit-P1-6 新增返回当前进程的限流器实例redis 驱动下动态 import redis-limiter 故为异步
RATE_LIMIT_RULES  // 同步常量(含 LOGIN/API/UPLOAD/AI_CHAT/PASSWORD_CHANGE/ONBOARDING_BINDaudit-P1-8 新增 ONBOARDING_BIND: 5次/小时)
// RateLimiter interface (audit-P1-6 新增)
//   limit(params: RateLimitParams): Promise<RateLimitResult>
//   reset(key: string): Promise<void>

// shared/lib/permission-bitmap.ts (audit-P1-7 新增:权限位图编解码)
//   纯函数无 server-only可在 edge runtime 使用proxy.ts 直接调用)
//   PERMISSION_BITMAP_ORDER: readonly Permission[] —— 67 个权限点的固定顺序(不可变,新增权限只能追加末尾)
//   编码:每个权限按位图中位置分配 bit (0-66)67 bit 打包为 BigInt → base36 字符串(约 13 字符 vs JSON 数组 1.1KB,减少 99%
//   BigInt 精度安全:手工按位累加 base36 → BigIntparseBase36ToBigInt避免 parseInt(bitmap, 36) 的 Number 53 bit 精度丢失
encodePermissionsBitmap(permissions: Permission[]): string  // 编码Permission[] → base36 字符串
decodePermissionsBitmap(bitmap: string): Permission[]  // 解码base36 字符串 → Permission[](用于 session callback 还原数组)
hasPermissionInBitmap(bitmap: string, permission: Permission): boolean  // 单权限检查不解码全部proxy.ts 性能优化)

// 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

// shared/lib/cache/ (服务端缓存基础设施P1 重构新增)
//   store-factory.ts —— getCacheStore() 单例工厂:默认 MemoryCacheStoreCACHE_DRIVER=redis 时懒加载 RedisCacheStore
//   memory-store.ts / redis-store.ts —— CacheStore 两种实现
//   cache-fn.ts —— cacheFn 双层包装器:外层 react.cache 请求级 memoization内层 cacheStore.getOrSet 跨请求缓存(按 tag 失效TTL 可选)
//   invalidation-map.ts —— INVALIDATION_MAP 集中式失效映射表 + fillTemplate 模板填充
//   invalidate.ts —— invalidateFor 三步编排函数Server Action 写后调用)
getCacheStore(): Promise<CacheStore>
cacheFn<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>, options: CacheFnOptions): (...args: TArgs) => Promise<TResult>
fillTemplate(template: string, params: Record<string, string>): string
INVALIDATION_MAP  // 集中式失效映射表常量
invalidateFor(actionId: string, params?: Record<string, string>): Promise<void>
  // 三步编排1) CacheStore.invalidateTags 2) revalidateTag × N 3) revalidatePath × N
  // 未知 actionId 抛错 "[cache] Unknown actionId: <id>. Update INVALIDATION_MAP."

业务模块核心 Actions

// exams/actions.ts
createExamAction(prevState: ActionState, formData: FormData): Promise<ActionState<Exam>>
createAiExamAction(prevState: ActionState, formData: FormData): Promise<ActionState<{ examId: string }>>
updateExamAction(prevState: ActionState, formData: FormData): Promise<ActionState>
deleteExamAction(prevState: ActionState, formData: FormData): Promise<ActionState>
duplicateExamAction(prevState: ActionState, formData: FormData): Promise<ActionState<{ examId: string }>>

// homework/actions.ts
createHomeworkAssignmentAction(prevState: ActionState, formData: FormData): Promise<ActionState>
startHomeworkSubmissionAction(prevState: ActionState, formData: FormData): Promise<ActionState<{ submissionId: string }>>
saveHomeworkAnswerAction(prevState: ActionState, formData: FormData): Promise<ActionState>
submitHomeworkAction(prevState: ActionState, formData: FormData): Promise<ActionState>
gradeHomeworkSubmissionAction(prevState: ActionState, formData: FormData): Promise<ActionState>

// classes/actions.ts
createTeacherClassAction(prevState: ActionState, formData: FormData): Promise<ActionState>
createAdminClassAction(prevState: ActionState, formData: FormData): Promise<ActionState>
createGradeClassAction(prevState: ActionState, formData: FormData): Promise<ActionState>
// + update/delete 各 3 个,共 9 个

// grades/actions.ts
getGradeRecordsAction(params: GetGradeRecordsParams): Promise<ActionState<PaginatedGradeRecords>>
createGradeRecordAction(prevState: ActionState, formData: FormData): Promise<ActionState>
exportGradesAction(params: ExportGradesParams): Promise<ActionState<Buffer>>

// scheduling/actions.ts
autoScheduleAction(prevState: ActionState, formData: FormData): Promise<ActionState>
applyAutoScheduleAction(prevState: ActionState, formData: FormData): Promise<ActionState>

业务模块核心 Data-access

// exams/data-access.ts
getExams(params: GetExamsParams & { scope: DataScope }): Promise<{ items: Exam[]; total: number }>
getExamById(id: string, scope: DataScope): Promise<Exam | null>
persistExamDraft(input: ExamDraftInput): Promise<{ examId: string }>
persistAiGeneratedExamDraft(input: AiExamDraftInput): Promise<{ examId: string }>

// homework/data-access.ts
getHomeworkAssignments(params: GetHomeworkAssignmentsParams & { scope: DataScope }): Promise<{ items, total }>
getStudentHomeworkAssignments(studentId: string): Promise<StudentHomeworkAssignment[]>
getStudentDashboardGrades(studentId: string): Promise<StudentDashboardGrades>
getHomeworkAssignmentAnalytics(assignmentId: string): Promise<HomeworkAnalytics>

// classes/data-access.ts
getAdminClasses(scope: DataScope): Promise<Class[]>
getTeacherClasses(teacherId: string): Promise<Class[]>
getStudentClasses(studentId: string): Promise<Class[]>
getClassStudents(classId: string): Promise<Student[]>
getClassHomeworkInsights(classId: string): Promise<ClassHomeworkInsights>  // ✅ P0-7 已修复:通过 homework/data-access-classes 获取数据

// grades/data-access.ts
getGradeRecords(params: GetGradeRecordsParams & { scope: DataScope; currentUserId?: string; limit?: number; offset?: number }): Promise<PaginatedGradeRecords>
getStudentGradeSummary(studentId: string, scope?: DataScope): Promise<StudentGradeSummary | null>
getClassRanking(classId: string, subjectId?: string, examId?: string, scope?: DataScope, currentUserId?: string): Promise<ClassRankingItem[]>

// scheduling/auto-scheduler.ts纯函数标杆
findOptimalSlot(input: FindOptimalSlotInput): ScheduleSlot | null
validateSchedule(schedule: ScheduleItem[]): ValidationResult
autoSchedule(input: AutoScheduleInput): AutoScheduleResult
buildDefaultTimeSlots(): TimeSlot[]

相关文档

文档 说明
005_architecture_data.json AI 友好格式的结构化架构数据
006_k12_feature_checklist.md 企业级 K12 标准功能模块清单
007_gap_audit_report.md 差距审计报告
008_module_role_mapping.md 模块-角色-功能映射总览

文档维护说明

  • 修改源码后:同步更新本文档对应模块章节 + 005_architecture_data.json
  • 新增模块:在第二部分添加模块清单 + 更新 1.1 分层架构图 + 更新附录 A 依赖矩阵
  • 新增/删除导出函数:更新对应模块的"导出函数"清单 + 附录 C
  • 修改依赖关系:更新 1.2 模块依赖关系图 + 附录 A 依赖矩阵
  • 新增路由:更新 005_architecture_data.jsonroutes 节点
  • 新增数据库表:更新 shared/db/schema.ts 分节 + 005dbTables 节点

完整路由表、DevOps 脚本、E2E 测试等信息见 005_architecture_data.json