- Update 004_architecture_impact_map.md and 005_architecture_data.json - Add audit reports: data-access-audit-framework-v1, data-access-audit-v1-data.json, data-access-audit-v1, g1-g5 audit outputs - Add superpowers plans and specs (logging-refactor, documentation-system-redesign) - Update troubleshooting/known-issues.md
623 KiB
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,不直接访问 DBmodules/之间通过对方 data-access 通信,不直接查询对方表shared/是被依赖方,不应反向依赖app/或modules/src/auth.ts和src/proxy.ts位于根目录,属于应用层
1.1.1 M7 移动端 PWA 支持(2026-07-01 新增)
- PWA 清单:新增
src/app/manifest.ts(Next.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-width、initialScale=1、maximumScale=5、userScalable=true、themeColor按明暗主题切换)+metadata新增manifest/appleWebApp配置,支持添加到主屏幕与独立显示。 - 触控友好:
lesson-preparation模块node-editor.tsx与lesson-plan-readonly-view.tsx的<ReactFlow>组件新增panOnDrag/zoomOnPinch/zoomOnDoubleClick={false}/selectionOnDrag={false}属性,防止移动端双击误缩放、优化触控拖拽体验。
1.1.2 设计令牌体系(2026-07-04 新增)
项目采用双层令牌架构(Primitive + Semantic),Tailwind 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(邮件 HTML)、manifest.ts(PWA) - 任意值
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-render 与 useState + 等待 Server Action 模式导致的 UI 不即时反馈。
Phase 4.2:lesson-plan-editor Zustand selector 拆分
useLessonPlanEditor 是组合了 5 个 slice 的 zustand store。原消费方使用 const editor = useLessonPlanEditor() 整体订阅,导致任一字段变化都触发整体 re-render。
| 文件 | 修改要点 |
|---|---|
modules/lesson-preparation/components/lesson-plan-editor.tsx |
拆分为 9 个细粒度 selector(isDirty/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 个细粒度 selector(toggleExpand/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.5:useOptimistic + useTransition 乐观更新
将 4 个组件从 useState + 等待 Server Action 模式迁移到 React 19 useOptimistic + useTransition,自动管理乐观状态与回滚。
| 文件 | 修改要点 |
|---|---|
modules/messaging/components/message-detail.tsx |
星标 useState → useOptimistic<boolean> + useTransition。handleToggleStar 在 startStarTransition 内 addOptimisticStarred(nextStarred) + await toggleMessageStarAction + router.refresh()。JSX 中所有 isStarred 引用改为 optimisticIsStarred(Button variant/aria-pressed/Star className/星标徽章) |
modules/messaging/components/message-list.tsx |
星标 starredOverride useState → useOptimistic<Map<string, boolean>>(key=messageId, value=isStarred)。getIsStarred 改为 optimisticStarredMap.get(m.id)。撤回 recalledOverride 仍保留 useState(不需要乐观更新) |
modules/grades/components/grade-record-list.tsx |
编辑保存 isSaving useState → useOptimistic<GradeRecordListItem[], GradeRecordListItem> + useTransition。handleEditSave 构造 optimisticRecord 在 startSaveTransition 内 addOptimisticRecord + await updateGradeRecordAction + router.refresh()。两处 records.map 改为 optimisticRecords.map(桌面表格 + 移动端卡片视图)即时显示乐观记录 |
modules/attendance/components/attendance-sheet.tsx |
isSubmitting useState → useOptimistic<boolean>。保留 <form action={handleSubmit}> 模式 + useFormStatus SubmitButton(progressive enhancement)。setOptimisticSubmitting(true) 在 form action 内调用,action 完成后 optimisticSubmitting 自动恢复 false,移除 finally 块 |
关键决策:
- attendance-sheet 保留 form action 模式而非改用
useTransition包裹,因为useFormStatus().pending依赖 form action 的 promise 状态,若用startTransition包裹会导致 form action 立即返回、useFormStatus不可靠 useOptimistic的addOptimistic必须在 transition 或 action 内调用(form action 也算 action)- 成功后调用
router.refresh()同步数据源,让useOptimistic回滚到最新服务端值 - 不为撤回(recall)操作使用 useOptimistic,因为撤回需要根据服务端返回判断是否成功(2 分钟窗口校验),失败时不应乐观显示已撤回
验证结果
npx tsc --noEmit:零新增错误(pre-existing 错误:providers.tsx缺少@tanstack/react-query-devtools、web-vitals-reporter.tsxMetric 导出、homework/data-access-write.tsMySqlTransaction.run、paper-context-menu.tsx第 58 行duplicateNode调用签名不匹配——此为 pre-existing bug,duplicateNode(id: string)只接受 1 参数但调用方传 2 个、print-view.tsxObject.entries 类型推断)npm run lint:零新增错误(8 个 modified 文件均未出现在 lint 输出中,pre-existing 3 errors + 13 warnings 均位于未修改文件:scripts/check-db-state.mjs、scripts/seed-grade5-chinese.ts、practice-result-view.tsx、exam-rich-form.tsx、use-exam-preview-state.ts、use-exam-preview-tasks.ts、homework/data-access-write.ts、unread-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.6:searchQuestions 改 FULLTEXT 索引
将 questions 表的模糊查询从 LIKE '%xxx%' 升级为 MySQL FULLTEXT 索引 + MATCH AGAINST ... IN BOOLEAN MODE,提升大表检索性能。
| 文件 | 修改要点 |
|---|---|
src/shared/db/schema.ts |
添加 import { sql } from "drizzle-orm";在 questions 表添加 STORED 生成列 contentText(text("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.7:getAllUserIds 加 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.sendBatch 从 Promise.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:276(limit: 200)和:345(limit: 5000)不在data-access*.tsglob 范围内,本次未修改
验证结果
npx tsc --noEmit:零新增错误(pre-existing 错误:providers.tsx、web-vitals-reporter.tsx、homework/data-access-write.ts、paper-context-menu.tsx、print-view.tsx均为未修改文件)npm run lint:零新增错误(16 个 pre-existing problems 均位于未修改文件:scripts/check-db-state.mjs、scripts/seed-grade5-chinese.ts、practice-result-view.tsx、exam-rich-form.tsx、use-exam-preview-state.ts、use-exam-preview-tasks.ts、homework/data-access-write.ts、unread-message-badge.tsx)
1.1.5 性能预算重构专项(2026-07-05 新增 + 2026-07-07 补漏)
完整审计报告见
docs/architecture/audit/performance-budget-audit-report.md。 本章节记录本次重构对架构产生的关键变更,以便后续维护时定位。 2026-07-07 补漏:Phase 1 配置基线(next.config.ts optimizePackageImports + serverExternalPackages 扩展 + WebVitalsReporter 注入闭环)+ Phase 2 实际动态导入实现(TipTap 3 处 + ReactFlow 1 处之前仅文件拆分未真正 dynamic import,本次补齐 next/dynamic ssr:false)+ chart.tsx 具名导入 + 12 个 recharts 图表 CHART_MARGIN 提取 + layout.tsx Promise.all + metadata 增强。
影响范围概览
| Phase | 改造内容 | 关键文件 |
|---|---|---|
| 1.1 | next.config.ts optimizePackageImports | next.config.ts 新增 experimental.optimizePackageImports 覆盖 lucide-react/recharts/@xyflow/react/@tiptap/* /@radix-ui/*/date-fns |
| 1.2 | serverExternalPackages 扩展 | next.config.ts 追加 tencentcloud-sdk-nodejs + exceljs(原仅 mysql2) |
| 1.3 | Web Vitals 上报闭环 | src/app/providers.tsx 注入 <WebVitalsReporter />(之前组件存在但未注入,链路断裂) |
| 1.4 | Lighthouse CI | .gitea/workflows/lighthouse.yml + lighthouserc.json(PR + 每日凌晨 3 点采样,LCP/CLS error 级) |
| 2.1 | tiptap 动态导入(3 处) | shared/components/ui/rich-text-editor.tsx + rich-text-editor-inner.tsx;modules/exams/editor/exam-rich-editor.tsx + exam-rich-editor-inner.tsx(forwardRef wrapper);modules/lesson-preparation/components/blocks/rich-text-block.tsx + rich-text-block-inner.tsx |
| 2.2 | ReactFlow 动态导入 | modules/textbooks/components/knowledge-graph.tsx 改用 next/dynamic 加载 knowledge-graph-inner.tsx,ReactFlowProvider 留外层同步壳 |
| 2.3 | exams/editor barrel 拆分 | 新增 modules/exams/editor/types.ts 作为纯类型入口,供 server-only 消费方引用 |
| 2.4 | chart.tsx 具名导入 + status-badge 移除 use client | shared/components/ui/chart.tsx 改 import { ResponsiveContainer, Tooltip, Legend } from "recharts" 替代 import * as RechartsPrimitive;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 模块级常量 | 12 个 chart 文件提取 CHART_MARGIN 模块级常量(parent/grades/dashboard/homework 模块下) |
| 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 增强 + Promise.all | app/layout.tsx 补 metadataBase/openGraph/twitter/robots/applicationName/authors/creator;getLocale()+getMessages()+auth() 串行 await 改 Promise.all 并行 |
未完成项(待后续专项)
- React Compiler:
babel-plugin-react-compiler仍为 RC 状态,暂不引入。待 React 19 稳定后评估。 - force-dynamic 96 处评估:全项目 96 处
export const dynamic = "force-dynamic",需逐个评估页面数据变更频率,低频变更页面改revalidate = 3600。 - Playwright 性能断言:待 e2e 测试基础设施完善后接入。
关键架构决策
- 动态导入模式标准化:重 UI 库(tiptap / @xyflow/react / AI SDK)统一采用
xxx-inner.tsx+xxx.tsxlazy wrapper 模式。ReactFlowProvider 等 context provider 留在外层同步渲染,保证 lazy 子组件的useReactFlow()等 hook 可用。 - 请求级去重优先于跨请求缓存:auth 相关 4 个核心函数(getSession/resolveDataScope/getAuthContext/getSessionTeacherId)全部用 React
cache()包装。不引入unstable_cache,因权限数据易变,跨请求缓存风险高于收益。 - 批量 SQL 优于循环 SQL:
- INSERT 场景:用
db.insert().values([...])批量 - UPDATE 场景:用
sql.join+CASE WHEN单次执行 - 复杂事务:用
db.transaction包裹多个批量操作
- INSERT 场景:用
- React 19 useOptimistic 替代手动 isPending:消息星标/出勤/成绩录入统一改为
useOptimistic+useTransition,移除手动useState<boolean>跟踪 pending。 - 细粒度 Zustand selector:单字段 selector 优先于
useShallow多字段 selector,因为函数引用天然稳定。
1.1.6 缓存策略落地专项重构(2026-07-05 新增)
目标:全栈缓存策略一致性重构 — 服务端
cacheFn双层包装 + 集中式INVALIDATION_MAP失效编排 + 客户端queryKeys工厂 +useActionQuery/useActionMutation向后兼容双模式 Hook。
架构总览
┌─────────────────────────────────────────────────────────────────────┐
│ Client(TanStack Query + useActionQuery/useActionMutation) │
│ queryKeys.{module}.{resource}(...args) → queryKey │
│ useActionMutation({ mutationFn, actionId, params }) │
│ → 成功后按 CLIENT_INVALIDATION_MAP[actionId].queryKeys 自动失效 │
└──────────────────────────────┬──────────────────────────────────────┘
│ mutationFn = Server Action
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Server Actions(编排层) │
│ mutation 成功分支末尾: │
│ await invalidateFor("module.action", { id }) │
│ → 1) CacheStore.invalidateTags 2) revalidateTag × N 3) revalidatePath × N │
└──────────────────────────────┬──────────────────────────────────────┘
│ 调用 data-access
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Data-access(数据访问层) │
│ export const fnRaw = async (...) => { ... } │
│ export const fn = cacheFn(fnRaw, { tags, ttl, keyParts }) │
│ → 外层 react.cache(请求级 memoization) │
│ → 内层 cacheStore.getOrSet(跨请求/跨实例,按 tag 失效) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ CacheStore(CACHE_DRIVER 切换) │
│ memory(默认,单实例,Map + LRU + tag 反查索引) │
│ redis(@upstash/redis,多实例共享,故障降级) │
└─────────────────────────────────────────────────────────────────────┘
核心基础设施(9 个文件)
| 文件 | 职责 |
|---|---|
shared/lib/cache/types.ts |
CacheFnOptions / CacheStore / InvalidationRule 接口 |
shared/lib/cache/memory-store.ts |
MemoryCacheStore(Map + LRU maxEntries=500 + tag 反查索引) |
shared/lib/cache/redis-store.ts |
RedisCacheStore(key=next-edu:cache:{key},故障降级) |
shared/lib/cache/store-factory.ts |
getCacheStore() 单例工厂(按 env.CACHE_DRIVER 切换) |
shared/lib/cache/cache-fn.ts |
cacheFn 双层包装器(外层 react.cache + 内层 cacheStore.getOrSet) |
shared/lib/cache/invalidation-map.ts |
INVALIDATION_MAP 集中式失效映射(203 个 actionId)+ fillTemplate |
shared/lib/cache/client-invalidation-map.ts |
CLIENT_INVALIDATION_MAP 客户端可见子集(仅 queryKeys) |
shared/lib/cache/invalidate.ts |
invalidateFor 三步编排函数 |
shared/lib/cache/index.ts |
barrel re-export |
shared/lib/redis-client.ts |
共享 Redis 单例(rate-limit + cache 共用) |
shared/lib/query-keys.ts |
客户端 queryKey 工厂(queryKeys.classes.*) |
迁移覆盖范围(28 个模块全部完成)
data-access 层迁移至 cacheFn(83 个文件,250+ 个查询函数):
| 组别 | 模块 | 文件数 | 备注 |
|---|---|---|---|
| 标杆 | classes | 5 | data-access-{teacher,admin,students,stats,schedule}.ts |
| A | users / school / rbac / textbooks / questions / course-plans / files / dashboard / parent / proctoring | 11 | 77 个函数 |
| B | exams / grades / homework / diagnostic / error-book / adaptive-practice | 8 | 含跨模块接口 |
| C | lesson-preparation / elective / announcements / messaging / notifications | 6 | 24 个函数 |
actions 层迁移至 invalidateFor(43 个文件,247 处 revalidatePath 替换):
| 组别 | 模块 | 文件数 | 替换数 |
|---|---|---|---|
| 标杆 | classes | 5 | 47 |
| A | users / school / textbooks / questions / course-plans / standards / scheduling / audit / onboarding / invitation-codes / proctoring / i18n | 13 | 70 |
| B | exams / grades / homework / attendance / leave-requests / diagnostic / error-book / adaptive-practice | 9 | 77 |
| C | lesson-preparation / elective / announcements / messaging / notifications / settings / rbac | 8 | 100 |
INVALIDATION_MAP:扩展至 203 个 actionId,覆盖全部 28 个模块。每个 actionId 声明 { tags, queryKeys, paths } 三段副作用。
强制规则(已通过 ESLint 落地)
| 规则 | 落地方式 |
|---|---|
Server Actions 中禁止直接调用 revalidatePath / revalidateTag |
no-restricted-syntax ESLint 规则,仅 shared/lib/cache/invalidate.ts 豁免 |
data-access 查询函数必须用 cacheFn 包装 |
双导出 raw 模式(fnRaw + cacheFn(fnRaw, opts)),单测可 mock |
| 未知 actionId 必须抛错 | invalidateFor 内部强校验,提示更新 INVALIDATION_MAP |
| Tag 命名规范 | {module} / {module}:{resource} / {module}:{resource}:{id} |
向后兼容 Hook 双模式
useActionQuery:传queryKey走useQuery(跨页共享缓存 + 自动 invalidate);不传走旧useEffect + useState(向后兼容)useActionMutation:传mutationFn + actionId自动 invalidate 相关 queryKeys;旧mutate(action)模式仍可用
验证结果
npx tsc --noEmit:零错误npm run lint:零no-restricted-syntax错误(仅 3 个 pre-existingreact-hooks/refs错误位于未修改文件)npx vitest run src/shared/lib/cache/:25/25 测试通过(5 个测试文件:types/memory-store/redis-store/cache-fn/invalidate/invalidation-map)
1.1.7 日志系统重构(2026-07-07 新增)
目标
引入 pino 结构化日志、Request ID 贯穿、错误边界上报、静默失败修复、track-event 去重,统一全项目日志基础设施。仅应用层 SDK 改造,不部署外部可观测性服务。
架构
proxy.ts (Edge Runtime) Node.js Runtime
│ 生成 requestId (crypto.randomUUID) │
│ 注入 x-request-id 请求头 │ headers() 读取 x-request-id
▼ ▼
NextResponse.next({ request: { headers } }) withRequestContext(fn)
│ requestContextStorage.run({ requestId })
▼
createModuleLogger("module")
│ pino mixin: getRequestContext()
▼
stdout (JSON / pino-pretty)
客户端 error.tsx 服务端 /api/client-error
│ useErrorReport(error) │ createModuleLogger("client-error")
│ navigator.sendBeacon │ log.error({ message, stack, digest, path })
▼ ▼
/api/client-error (POST) pino 日志流
核心组件
| 文件 | 职责 |
|---|---|
shared/lib/logger.ts |
pino 实例 + createModuleLogger(module) 工厂;mixin 自动混入 requestId/userId;开发环境 pino-pretty,生产环境 JSON |
shared/lib/request-context.ts |
AsyncLocalStorage<RequestContext>;getRequestContext() 读取当前上下文;仅 Node.js Runtime |
shared/lib/with-request-context.ts |
withRequestContext(fn) 高阶函数;从 headers() 读取 x-request-id 或生成 UUID,注入 AsyncLocalStorage |
shared/hooks/use-error-report.ts |
客户端错误上报 Hook;navigator.sendBeacon 上报到 /api/client-error;sessionStorage 节流(1 分钟/digest) |
src/proxy.ts |
Edge Runtime;生成/复用 requestId,通过 NextResponse.next({ request: { headers } }) 注入 x-request-id |
src/app/api/client-error/route.ts |
接收客户端错误,用 createModuleLogger("client-error") 记录到 pino 日志流 |
Request ID 贯穿链路
proxy.ts(Edge Runtime)生成requestId = crypto.randomUUID()(Web Crypto API,非 Node.js crypto)- 通过
NextResponse.next({ request: { headers: injectRequestId(request, requestId) } })注入到下游请求 withRequestContext(fn)在 Server Action / Route Handler 中从headers()读取x-request-idrequestContextStorage.run({ requestId }, () => fn(...))注入 AsyncLocalStoragepino的mixin: () => getRequestContext()自动将 requestId 混入每条日志
Edge Runtime 限制
proxy.ts和原web-vitals/route.ts声明runtime = "edge"- Edge Runtime(V8 Isolate)不支持
node:async_hooks(AsyncLocalStorage)和node:stream(pino 依赖) - 解决方案:
proxy.ts仅用 Web Crypto API(crypto.randomUUID()),不导入 Node.js 模块web-vitals/route.ts从runtime = "edge"改为runtime = "nodejs"(pino 依赖 Node.js stream)- 客户端组件不能导入服务端 pino logger(
env.LOG_LEVEL是 server-only)
替换范围
- 34 个服务端 .ts 文件:86 处
console.*替换为createModuleLogger("module")的log.* - 129 个 error.tsx:接入
useErrorReport(error)Hook,移除 7 处useEffect + console.error - 3 个 audit-logger:catch 块
/* Silently fail */→log.warn({ err, action, module }, "...") - 4 个 track-event:
console.*替换为log.*(客户端 track-event 改为纯 no-op) - ESLint
no-console: error:强制服务端 .ts 使用 logger;客户端 .tsx/hooks 暂时豁免(留待客户端错误上报机制处理)
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
LOG_LEVEL |
info |
日志级别(debug/info/warn/error) |
验证结果
npx tsc --noEmit:零错误npm run lint:零错误(no-console 规则启用)
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 等多处重复实现抽取)serializeDate(date)/serializeDateRequired(date)— 日期序列化工具(✅ 审计 v1 P0-6 新增:位于lib/date-utils.ts,S-04 DRY 修复;serializeDate(date: Date | null | undefined): string | null与serializeDateRequired(date: Date): string;messaging / course-plans / announcements / elective / audit / files / school / notifications 等 10+ data-access 文件迁移至共享 util,消除各模块重复定义const toIso = (d: Date) => d.toISOString())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.ts,server-only,当前输出到 console.info,预留接入外部监控服务;EventName 联合类型覆盖 announcement/message/notification/attendance/elective/exam/homework/ai/audit/files/auth 模块事件;✅ P3 新增事件名homework.excellent_viewed、homework.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 状态码映射替代字符串匹配推导;withApiErrorHandlerHOF 统一异常处理消除每个 route 重复 try/catch;apiFromAction通过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 |
通用分页 UI(Showing X-Y of Z + Page X of Y + 上一页/下一页按钮) | 3 个(P0-2: audit-log-table, login-log-table, data-change-log-table) |
| UI 组件 | EmptyTableRow |
components/ui/empty-table-row.tsx |
表格空状态行(TableRow + TableCell 居中显示空状态文案) | 3 个(P0-3: audit-log-table, login-log-table, data-change-log-table) |
| UI 组件 | 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 自动失效,向后兼容旧调用方 | 示范:P1-4 schools-view;Task 19 classes/class-invitation-manager(revoke mutation + actionId 自动失效)。潜在影响 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,向后兼容旧调用方 | 示范:P1-4 create-question-dialog;Task 19 classes/class-invitation-manager(listClassInvitationCodesAction + queryKeys.classes.invitations(classId))。潜在影响 11 个文件 |
V5 状态管理统一专项基础设施(阶段 1 新增,零业务行为变更):
| 资产 | 文件 | 签名 | 用途 | 后续消费方 |
|---|---|---|---|---|
createQueryClient |
lib/query-client.ts |
createQueryClient(): QueryClient |
TanStack Query 工厂,默认 staleTime 30s / retry 1 / refetchOnWindowFocus false / mutations 全局 onError 兜底 | app/providers.tsx |
notify + rawToast |
lib/notify.ts |
notify.{success,error,info,warning,message,promise}(message: string | undefined, opts?: ExternalToast); rawToast |
统一 toast 接口,包装 sonner。V5 Phase 4 重写:移除 configureNotify + i18n resolver 注入,简化为直接接受 string | undefined(兼容 result.message),通过 ExternalToast options 转发 sonner 高级特性(description/duration/action)。i18n 由调用方通过 t() 控制。已完成 131 文件批量替换(业务代码 import { toast } from "sonner" → import { notify } from "@/shared/lib/notify" + toast.X(...) → notify.X(...)) |
query-client.ts 全局兜底 + 所有业务模块(exams/grades/lesson-preparation/settings/classes 等 131 文件) |
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 + ModalRoot |
stores/ui-store.ts + components/modal-root.tsx |
useUiStore: { modals, openModal, closeModal, isOpen, closeAll }; useModal(name); ModalRoot({ registry }): ReactNode |
全局 UI store(zustand),集中管理 modal registry。命令式 API + Hook 双入口。V5 Phase 4 新增 ModalRoot 组件:监听 ui-store modals 状态渲染已注册 modal,路由变化时自动 closeAll。与 sonner 的分工:toast 走 notify,modal 走本 store |
待迁移:新增跨组件触发 modal 推荐使用此模式;存量 Dialog 保持 useState |
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 → ThemeProvider → SessionProvider → NuqsAdapter → children + Toaster。V5 Phase 4 调整:移除 NotifyConfigurator(notify.ts 简化为直接接受 string,不再依赖 i18n resolver 注入)。DevTools 仅 dev 环境 |
app/layout.tsx |
V5 状态分层模型(5 层):
| 层 | 责任 | 选型 |
|---|---|---|
| URL 状态 | 列表过滤、tab 持久化、深链接 | nuqs useQueryState(单一来源,逐步替换 useSearchParams + window.location.search) |
| 服务端状态 | 列表/详情/分页数据,mutation + 缓存失效 | TanStack Query(混合模式:RSC 拿首屏 props,useQuery 负责交互数据) |
| 客户端业务状态 | 模块级状态机(如 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-CN,weekday=short | 1 个(P1-3: teacher-dashboard-header) |
注:
SettingsView位于modules/settings/components/(非 shared 层),因仅被 settings 模块消费,未下沉到 shared。此处列出以完整反映本次重构的组件抽取范围。
依赖关系:
- 被依赖方:所有模块依赖 shared
- ✅ 反向依赖已修复:
shared/lib/{audit-logger, change-logger, auth-guard}通过shared/lib/session.ts单一入口获取 session,不再直接依赖@/auth
已知问题:
- ✅ P0:
已修复(新增shared/lib/*↔@/auth循环依赖shared/lib/session.ts封装 session 获取,3 个文件改为import { getSession } from "@/shared/lib/session") - ⚠️ P1:
schema.ts1111 行(54 张表混合,超 1000 硬上限) - ✅ P1:
已拆分(4 个辅助函数组迁移至auth.ts293 行混合 5 类职责shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service},auth.ts 仅保留 NextAuth 配置) - ✅ P2-2 已修复:
已拆分为ai.ts218 行混合 5 类职责ai/目录(payload-parser.ts/api-key-crypto.ts/provider-config.ts/client.ts/errors.ts/index.ts),原ai.ts保留为向后兼容的重导出文件(9 行) - ✅ P2-4 已修复:
已迁移至onboarding-gate.tsx业务逻辑泄漏到 sharedmodules/onboarding/(actions/data-access/schema/types/components),引导流程改为独立路由/onboarding+ middleware 重定向 + Server Action - ✅ P0-2/P0-3/P0-4/P0-5/P1-1/P1-2/P1-4/P1-5 已修复(v3 对标 PowerSchool/Veracross/Auth0):家长绑定三因子验证(邮箱+生日+手机号后4位)、教师多科目循环绑定、审计日志、服务端幂等、URL query 持久化步骤、局部错误收集、家长多子女动态行、跳过机制明确化
- ✅ v3 i18n 体系引入:采用 next-intl 4.x(without i18n routing 模式),cookie 驱动 locale 切换,字典放在
shared/i18n/messages/{locale}/,支持 zh-CN/en 两种语言,不破坏现有路由组结构 - ✅ v3 班级邀请码体系引入(对标 Google Classroom / 钉钉教育 / 智学网):新增
class_invitation_codes表(独立表,支持有效期/次数限制/审计/多码并存),6 位字母数字(剔除歧义字符 0/O/1/I/L,空间 1.13 亿),rate limit 防爆破(10 次/5 分钟),审计日志全链路记录,懒清理过期码
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
db/schema.ts |
1111 | 54 张表定义(超硬上限) |
db/relations.ts |
- | 表关系定义 |
db/index.ts |
- | Drizzle 客户端 |
lib/auth-guard.ts |
- | 认证上下文 + 权限校验 + DataScope |
lib/permissions.ts |
- | 角色-权限映射 |
lib/session.ts |
38 | session 获取单一入口(getSession,server-only,dynamic import 打破循环) |
lib/ai.ts |
9 | 向后兼容重导出(P2-2 已拆分到 ai/ 目录) |
lib/ai/payload-parser.ts |
78 | 请求负载解析 |
lib/ai/api-key-crypto.ts |
28 | API Key 加密/解密 |
lib/ai/provider-config.ts |
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 获取 session,http-utils 获取 IP/UA) |
lib/change-logger.ts |
- | 数据变更日志(通过 session.ts 获取 session,http-utils 获取 IP) |
lib/login-logger.ts |
- | 登录日志(通过 http-utils 获取 IP/UA) |
lib/password-policy.ts |
- | 密码策略纯函数 |
lib/rate-limit/ |
- | 速率限制器抽象(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 秒超时 + AbortController;fail-open 策略:API 不可用时返回 checkSkipped=true 不阻断注册/改密;server-only) |
lib/http-utils.ts |
44 | 请求头解析(resolveClientIp / getUserAgent,server-only) |
lib/password-security-service.ts |
84 | 密码安全 DB 操作(账户锁定/失败登录追踪,server-only) |
lib/route-resolver.ts |
21 | 角色默认路径解析纯函数 resolveDefaultPath(roles: string[]): string(✅ audit-P0-6 新增:合并 proxy.ts 与 onboarding/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_PERMISSIONS(15 个精确路由权限,audit-P1-10 扩展自 3 个至 15 个覆盖 /admin/* 子路径)/ ROUTE_PREFIX_PERMISSIONS(5 个角色路由前缀权限)/ DASHBOARD_ROUTE_PERMISSIONS(4 个仪表盘细粒度权限防跨角色访问)/ API_ROUTE_PERMISSIONS(1 个 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/logger.ts |
32 | pino 结构化日志实例(✅ 2026-07-07 日志重构新增:createModuleLogger(module) 工厂创建模块级 child logger;开发环境 pino-pretty 彩色输出,生产环境 JSON 到 stdout;mixin 自动从 AsyncLocalStorage 混入 requestId/userId;LOG_LEVEL 环境变量控制级别;server-only,不兼容 Edge Runtime) |
lib/request-context.ts |
22 | AsyncLocalStorage 请求上下文(✅ 2026-07-07 日志重构新增:RequestContext 接口含 requestId/userId/module;requestContextStorage 存储;getRequestContext() 读取当前上下文;仅 Node.js Runtime 可用,proxy.ts 不导入) |
lib/with-request-context.ts |
16 | Server Action 高阶函数包装(✅ 2026-07-07 日志重构新增:withRequestContext(fn) 从 headers() 读取 x-request-id 或生成 UUID,注入 AsyncLocalStorage;保留泛型类型推断) |
lib/track-event.ts |
- | 监控埋点接口(✅ 2026-07-07 日志重构:console.info 替换为 createModuleLogger("track") 的 log.info;EventName 联合类型覆盖 11 个模块事件) |
lib/excel.ts |
- | Excel 导入导出 |
lib/file-storage.ts |
- | 文件存储抽象 |
hooks/use-permission.ts |
- | 客户端权限 Hook |
hooks/use-error-report.ts |
72 | 客户端错误上报 Hook(✅ 2026-07-07 日志重构新增:在 error.tsx 中使用,通过 navigator.sendBeacon 上报到 /api/client-error;sessionStorage 节流:同一 digest 1 分钟内只上报一次;降级到 fetch keepalive) |
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 + FilterResetButton(P3-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-only,no-op console.info 实现;EventName 联合类型覆盖 11 个模块事件;P3 新增 homework.excellent_viewed、homework.remind_unsubmitted;audit-P1-9 新增 auth 模块 8 个事件 + trackAuthEvent 便捷函数) |
lib/utils.ts |
- | 通用工具(P1-a/P1-c 新增 getInitials + formatDateForFile) |
lib/date-utils.ts |
- | ✅ 审计 v1 P0-6 新增(S-04 DRY):日期序列化工具 serializeDate(date: Date | null | undefined): string | null + serializeDateRequired(date: Date): string;messaging / course-plans / announcements / elective / audit / files / school / notifications 等 10+ data-access 文件迁移至此共享 util |
components/onboarding-gate.tsx |
✅ 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/stream 与 notifications/stream 重复实现 |
2.2 exams(考试模块)
职责:考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题。
导出函数:
- Actions:
createExamAction/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.ts,actions.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.ts,actions.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.ts,actions.ts re-export 保持向后兼容) - Data-access:
getExams/getExamById/persistExamDraft/persistAiGeneratedExamDraft/buildExamDescription/resolveSubjectGradeNames/getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGrades/getExamsByGradeId(✅ v4-P2-7 新增:年级仪表盘维度3,exams 表有直接 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 Pipeline:
generateAiCreateDraftFromSource/generateAiPreviewData/regenerateAiQuestionByInstruction/loadAiDraftQuestionsAndStructure(✅ 2026-06-24 新增:从 actions.ts 下沉,加载 AI 草稿题目与结构,优先从 rawAiQuestions JSON 解析,否则从源文本生成) - Utils:
normalizeStructure(v3 新增:将持久化的exam.structureunknown JSON 运行时校验并归一化为类型安全的ExamNode[],类型守卫模式无as断言,从teacher/exams/[id]/build/page.tsx提取;✅ 2026-06-24 升级:支持 section/group/question 三级层级递归校验,section 节点含 title/level/children,group 节点含 title/instruction/children) - Lib(✅ 类型守卫专项重构 2026-07-07 新增
lib/type-guards.ts):isRecord(unknown →Record<string, unknown>,消除as Record<string, unknown>断言)/isSimpleTextContent(校验可选 text 字段)/isQuestionContentObj(校验题目内容 text+options 结构,options 内仅校验 id/text 字段)/isJSONContent+isJSONContentArray(Tiptap JSONContent 守卫,返回类型为v is JSONContent)/toSortableId(dnd-kitUniqueIdentifier(string|number)→ string,消除as string断言)/isQuestionBlockAttrs(QuestionBlock 节点 attrs 守卫 { questionId, type, score })。用途:消除 ai-pipeline/components/editor 子目录 13 个文件中的违规as断言;合规as(as unknown as从 unknown 转换、as const字面量常量)保留未改。使用者:ai-pipeline/request.ts、components/exam-preview-utils.ts、components/exam-actions.tsx、components/assembly/{structure-editor,question-bank-list,exam-paper-preview}.tsx、editor/selection-toolbar.tsx、editor/exam-nodes-to-editor-doc.ts、editor/exam-rich-editor-inner.tsx、editor/extensions/question-block.tsx、components/exam-rich-form.tsx - Preview Utils(
components/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(状态子 Hook,67 行,10 个 useState + 3 个更新函数)/usePreviewBackgroundTasks(后台任务子 Hook,80 行,PQueue 队列 + localStorage 持久化 + 入队/打开任务)/usePreviewRewrite(AI 重写子 Hook,79 行,handleRewriteSelectedQuestion 调用 regenerateAiQuestionAction) - Stats-service(V3-8 新增):
getExamAnalytics(cache 包装,聚合考试所有作业的已批改提交,计算平均分/及格率/分数段分布/逐题错误率与难度等级,对标智学网考试分析)+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 attrs,isolating: true,对应第Ⅰ卷/第Ⅱ卷层级) - 类型(
editor/exam-rich-editor-types.ts):RichQuestionType(single_choice | multiple_choice | judgment | text | composite)/RichQuestionContent(题干文本+选项+填空+图片+子题+正确答案)/EditorQuestion/EditorStructureNode(✅ 2026-06-24 升级:section|group|question 三级递归,section 含 title/level/children,group 含 title/instruction/children)/EditorDoc({ title, questions, structure })/EditorJSONContent(JSONContent 别名) - 转换工具:
editorDocToStructure(editor/editor-to-structure.ts,Tiptap JSONContent → EditorDoc,供createExamFromRichEditorAction/updateExamFromRichEditorAction持久化使用)/structureToEditorDoc(editor/structure-to-editor.ts,EditorDoc → Tiptap JSONContent,✅ 2026-06-24 升级支持 section 节点回填,供编辑器回填已保存试卷使用)/examNodesToEditorDoc(✅ 2026-06-24 新增:editor/exam-nodes-to-editor-doc.ts,BuildExam 的ExamNode[]+Question[]→ EditorDoc 桥梁函数,递归转换 section/group/question,供 edit-rich 页面加载已有试卷到富文本编辑器) - Barrel 导出:
editor/index.ts聚合导出上述所有组件、扩展、类型与转换函数
- 组件:
- Components(V3-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/*、@/auth、questions(✅ P0-1 已修复:通过 data-access.createQuestionWithRelations)、classes(✅ P0-2 已修复:通过 data-access.getClassGradeIdsByClassIds)、school(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions)、homework(V3-8 新增:stats-service 通过homework/data-access.getHomeworkAssignmentsByExamId/getGradedSubmissionsByExamId获取作业与提交数据,合理跨模块调用) - 被依赖:
homework(通过 sourceExamId 外键,合理)、dashboard(通过 data-access,P0-4 已修复)、proctoring(✅ P1-1 已修复:通过 exams data-access)、diagnostic(✅ P1-1 已修复:通过 exams data-access)、grades(✅ 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 已修复:
DB 操作已下沉到 data-access,actions.ts 现 691 行actions.ts832 行(超 800 建议),多处直接 DB 操作 - ✅ P0-4 已修复(2026-06-24):
DB 事务下沉到 data-access.updateExamWithRichEditorDataupdateExamFromRichEditorAction直接 db.transaction 写 exams/examQuestions 表 - ✅ actions.ts 拆分(2026-06-24):
拆分为 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 保持向后兼容actions.ts1525 行(超 1000 硬上限) - ✅ P0-8 已修复:
拆分为data-access.ts1036 行(超 1000 硬上限)data-access.ts(542 行,考试 CRUD) +data-access-cross-module.ts(462 行,13 个跨模块查询/写接口),文末 re-export 保持向后兼容 - ⚠️ P1:
ai-pipeline.ts857 行(超 800 建议),混合 4 类职责 - ✅ P2 已修复:
ai-pipeline.ts中 3 处非空断言清理(draft.sections!.forEach→ 安全守卫、aiParsed.sections!.flatMap→?? []、aiParsed.sections!.map→?? []) - ✅ V3-5/V3-8/V3-12:
exam-actions.tsx增强:V3-5 角色化菜单(按角色显示不同操作项)、V3-8 新增 analytics 菜单项(BarChart3 图标,跳转/teacher/exams/[id]/analytics)、V3-12 移动端触摸目标尺寸优化
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
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 Action(createExamFromRichEditorAction/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.ts,RichExamCreateSchema/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 新增:editorDocToStructure(Tiptap JSONContent → EditorDoc) |
editor/structure-to-editor.ts |
- | 2026-06-24 新增:structureToEditorDoc(EditorDoc → 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 新增:预览状态子 Hook(10 个 useState + updatePreviewQuestionNode/updateSelectedQuestionFromAi/applyPreviewResult 三个更新函数) |
hooks/use-exam-preview-tasks.ts |
80 | ✅ 2026-06-24 新增:预览后台任务子 Hook(PQueue 并发队列 + localStorage 持久化恢复 + handleBackgroundPreview/handleOpenPreviewTask) |
hooks/use-exam-preview-rewrite.ts |
79 | ✅ 2026-06-24 新增:预览 AI 重写子 Hook(handleRewriteSelectedQuestion 调用 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 提取) |
lib/type-guards.ts |
80 | ✅ 类型守卫专项重构(2026-07-07)新增:exams 模块通用类型守卫集合。导出 isRecord / isSimpleTextContent / isQuestionContentObj / isJSONContent / isJSONContentArray / toSortableId / isQuestionBlockAttrs 共 7 个守卫/转换函数。消除 ai-pipeline/components/editor 子目录 13 个文件中的违规 as 断言(as Record<string, unknown> / as { text?: string } / as JSONContent[] / as string / as QuestionBlockAttrs 等),保留合规 as unknown as(从 unknown 转换)与 as const(字面量常量)未改 |
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.ts(autoMarkExamAction)和 actions-rich-editor.ts(createExamFromRichEditorAction/updateExamFromRichEditorAction),支持 create/edit 双模式 |
components/exam-boundaries.tsx |
189 | ✅ 2026-06-25 新增(P2-3):模块统一边界组件。导出 ExamErrorBoundary(薄包装 SectionErrorBoundary,固定 examHomework 命名空间)、ExamEmptyState(i18n 键驱动的空状态)、ExamSkeleton(list/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-4):ExamServicePort 的 React Context 注入点。导出 ExamServiceProvider(Provider 组件)、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_CONFIG)、getExamWidgetConfig(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 已修复:
DB 操作已下沉到 data-access,actions.ts 现 691 行actions.ts832 行(超 800 建议),多处直接 DB 操作 - ✅ P0-4 已修复(2026-06-24):
DB 事务下沉到 data-access.updateExamWithRichEditorDataupdateExamFromRichEditorAction直接 db.transaction 写 exams/examQuestions 表 - ✅ actions.ts 拆分(2026-06-24):
拆分为 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 保持向后兼容actions.ts1525 行(超 1000 硬上限) - ✅ P0-8 已修复:
拆分为data-access.ts1036 行(超 1000 硬上限)data-access.ts(542 行,考试 CRUD) +data-access-cross-module.ts(462 行,13 个跨模块查询/写接口),文末 re-export 保持向后兼容 - ⚠️ P1:
ai-pipeline.ts857 行(超 800 建议),混合 4 类职责 - ✅ P2 已修复:
ai-pipeline.ts中 3 处非空断言清理(draft.sections!.forEach→ 安全守卫、aiParsed.sections!.flatMap→?? []、aiParsed.sections!.map→?? []) - ✅ V3-5/V3-8/V3-12:
exam-actions.tsx增强:V3-5 角色化菜单(按角色显示不同操作项)、V3-8 新增 analytics 菜单项(BarChart3 图标,跳转/teacher/exams/[id]/analytics)、V3-12 移动端触摸目标尺寸优化 - ✅ 2026-06-25 P2 落地(exams 审计报告实施):
- ✅ P2-1:在
editor/exam-rich-editor-types.ts新增isRichQuestionType/isStandaloneQuestionType/toRichQuestionType/toStandaloneQuestionType类型守卫;editor/editor-to-structure.ts与editor/exam-nodes-to-editor-doc.ts改用toRichQuestionType;actions-rich-editor.ts两处as "single_choice"|"multiple_choice"|"text"|"judgment"断言改为toStandaloneQuestionType安全收窄(消除 4 处as违规) - ✅ P2-3:新增
components/exam-boundaries.tsx(ExamErrorBoundary/ExamEmptyState/ExamSkeleton 三组合单元,5 种骨架变体) - ✅ P2-4:新增
services/exam-service-port.ts+services/exam-service-context.tsx(ExamServicePort 接口 + Context 注入骨架,与 shared 层 ExamHomeworkServicePort 区分定位) - ✅ P2-5:新增
config/exam-widgets.ts(四角色 Widget 配置 + getExamWidgetConfig/getWidgetsBySlot)
- ✅ P2-1:在
2.3 homework(作业模块)
职责:作业全生命周期(创建/发布/作答/批改/分析)。
导出函数:
- Actions:
createHomeworkAssignmentAction/startHomeworkSubmissionAction/saveHomeworkAnswerAction/submitHomeworkAction/gradeHomeworkSubmissionAction/batchAutoGradeSubmissionsAction(V3-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-access:
getHomeworkAssignments/getHomeworkAssignmentById/getHomeworkSubmissions/getStudentHomeworkAssignments/getStudentHomeworkTakeData/getHomeworkAssignmentReviewList/getHomeworkSubmissionDetails/getDemoStudentUser(已迁移至 users 模块getCurrentStudentUser,此处为 re-export 向后兼容)/toQuestionContent/getAssignmentMaxScoreById(后两者供 stats-service 使用;isRecord已于 2026-06-24 迁移至@/shared/lib/type-guards统一复用)/getHomeworkAssignmentsByExamId(V3-8 新增:按考试 ID 查作业+目标/提交/批改计数)/getGradedSubmissionsByExamId(V3-8 新增:按考试 ID 查已批改提交,按学生去重)/getStudentSubmissionResult(V3-9 新增:查学生指定作业最新提交,用于结果页)/getStudentExamResults(V3-11 新增:查学生考试结果列表,供家长端展示)/getExcellentSubmissions(✅ P3-1 新增:查优秀作业列表,按得分率过滤 minPercentage 默认 80,同一学生取最高分,按得分率降序,支持 scope 过滤)/getUnsubmittedStudents(✅ P3-2 新增:查未提交作业的学生列表,对比 targets 与已提交学生集合返回差集,供催交提醒使用) - Data-access-classes:
getAssignmentIdsForStudents/getHomeworkAssignmentsWithSubject/getHomeworkAssignmentsByIds/getAssignmentTargetCounts/getHomeworkSubmissionsForStudents/getPublishedHomeworkAssignmentsWithSubject/getHomeworkSubmissionsForAssignments(P0-7 新增,供 classes 模块跨模块调用;✅ P1-1 已修复:内部不再直查 exams/subjects 表,改为调用exams/data-access.getExamSubjectIdMap+school/data-access.getSubjectNameMapByIds) - Data-access-write:11 个写操作函数(P1-2 新增 10 个从 actions 下沉 + V3-7 新增
batchAutoGradeSubmissions) - Stats-service:
getTeacherGradeTrends/getHomeworkAssignmentAnalytics/getStudentDashboardGrades(从 data-access.ts re-export 以保持向后兼容) - Components(V3-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-100,isLate 标注迟交但质量优秀样例)/ExcellentSubmissionQuery(优秀作业查询参数 { assignmentId, minPercentage?, limit? },minPercentage 默认 80,limit 默认 10)
依赖关系:
- 依赖:
shared/*、@/auth、exams(✅ P1-1 已修复:通过 exams data-access.getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework/getExamForProctoringCrossModule)、classes(✅ P1-1 已修复:通过 classes data-access.getStudentIdsByClassId 等 7 个函数)、school(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getSubjectNameMapByIds)、users(✅ P1-1 已修复:通过 users data-access.getUserWithRole/getUserNamesByIds)、files(✅ 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 表)、exams(V3-8 新增:exams/stats-service.getExamAnalytics调用getHomeworkAssignmentsByExamId/getGradedSubmissionsByExamId,合理跨模块调用)
已知问题:
- ✅ P0 已解决:
data-access.ts已拆分至 598 行(原 1038 行超 1000 硬上限),统计函数迁移至stats-service.ts - ✅ P0 已解决:
getStudentDashboardGrades排名计算逻辑迁移至stats-service.ts - ✅ P0 已解决:
getHomeworkAssignmentAnalytics错误率统计逻辑迁移至stats-service.ts - ✅ P0-7 已修复:新增
data-access-classes.ts,将 classes 模块对 homework/exams 表的直查封装为 homework 模块的导出函数,恢复三层架构 - ✅ P1-1 已修复:
5 处直查改为调用exams表exams/data-access.getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework;data-access-classes.ts内部 JOIN exams+subjects 也改为调用exams/data-access.getExamSubjectIdMap+school/data-access.getSubjectNameMapByIds,彻底消除跨模块直查 - ✅ P0-竞品已修复:新增
hooks/use-exam-countdown.ts倒计时 Hook(对标智学网/猿题库),homework-take-view.tsx集成限时/监考模式倒计时显示与到时自动提交;data-access.ts的getStudentHomeworkTakeData新增examModeConfig+startedAt字段供客户端计算截止时间 - ✅ P1-2 已修复:
DB 操作已下沉到actions.ts多处直接 DB 操作(createHomeworkAssignmentAction157 行)data-access-write.ts,actions.ts 现 239 行 - ✅ V3-7:新增
batchAutoGradeSubmissionsAction+batchAutoGradeSubmissions+HomeworkBatchGradingView,提交列表页接入批量批改 - ✅ V3-8:新增
getHomeworkAssignmentsByExamId+getGradedSubmissionsByExamId,供 exams 模块跨模块调用 - ✅ V3-9:新增
getStudentSubmissionResult+HomeworkSubmissionResult+ 路由/student/learning/assignments/[assignmentId]/result,homework-take-view.tsx提交后跳转结果页 - ✅ V3-12:
homework-take-view.tsx移动端触摸目标尺寸优化 - ✅ 2026-06-23 审计修复:data-access 层 4 个查询函数新增
scope?: DataScope参数(getHomeworkAssignments/getHomeworkAssignmentById/getHomeworkSubmissions/getHomeworkAssignmentReviewList),教师仅查看自己创建的作业,学生/家长仅查看相关作业;Server Action catch 块改用handleActionError统一错误处理 - ✅ 2026-06-24 i18n 完成:11 个文件(6 个客户端组件 + 3 个服务端页面 + 2 个已有
t的页面)全部接入next-intl,硬编码中文/英文文案替换为examHomework.homework.*翻译键(zh-CN + en 双语);客户端组件使用useTranslations("examHomework"),服务端页面使用getTranslations("examHomework") - ✅ 2026-06-25 审计修复 P0:13 个缺失的
loading.tsx/error.tsx路由文件全部补全(teacher/homework 根 + assignments + submissions + submissions/[submissionId] + scan-grading;student/learning/assignments + result);homework-take-view.tsx拆分为 5 个组件(HomeworkTakeView主容器 396 行 +HomeworkTakeSidebar侧边栏 +HomeworkTakeConfirmDialog提交确认对话框);schema.ts错误消息改为 i18n 键homework.form.error.*,Server Action 通过translateZodErrors翻译后返回fieldErrors;ScanAttachment类型从actions.ts迁移至types.ts - ✅ 2026-06-25 审计修复 P1:
homework-grading-view.tsx删除本地 wrapper 函数(isAutoGradable/applyAutoGrades/getCorrectnessState/extractQuestionText),直接调用lib/question-content-utils;data-access.ts末尾 re-export(getHomeworkAssignmentsByExamId/getGradedSubmissionsByExamId/getStudentSubmissionResult/getStudentExamResults/getStudentHomeworkAssignments/getStudentHomeworkTakeData)已删除,调用方改为直接 import 子模块;teacher/homework/assignments/[id]/page.tsxAnalytics 区块接入SectionErrorBoundary + Suspense + Skeleton;getScansAction中的 DB 查询下沉到新建的data-access-scans.ts::getScansBySubmissionId - ✅ 2026-06-25 审计修复 P2:
startHomeworkSubmissionAction/saveHomeworkAnswerAction/deleteScanAction补全trackExamEvent埋点(homework.started/homework.answer_saved/homework.scan_deleted);extractQuestionText从homework-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 Action(P1-2 已修复,无直接 DB 操作;✅ 2026-06-25:ScanAttachment 类型迁移至 types.ts;schema.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-25:extractQuestionText 从 grading-view 迁入) |
lib/question-content-utils.test.ts |
415 | 工具函数单测 |
hooks/use-exam-countdown.ts |
106 | P0-竞品新增:考试倒计时 Hook(每秒更新、紧急状态高亮、到时回调自动提交) |
hooks/use-debounced-auto-save.ts |
182 | 答案防抖自动保存 Hook(localStorage 离线缓存) |
components/homework-take-view.tsx |
396 | ✅ 2026-06-25 重构:从 520 行拆分为 3 个组件(主容器 + Sidebar + ConfirmDialog);V3-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/upload,targetType="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 修复:getTranslations 从 next-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 i18n:Fallback 接入 useTranslations) |
components/homework-assignment-question-error-detail-panel.tsx |
132 | 题目错误详情面板(✅ 2026-06-24 i18n:接入 useTranslations,formatAnswer 移入组件内闭包以访问 t) |
components/homework-assignment-question-error-overview-card.tsx |
89 | 题目错误概览卡片(✅ 2026-06-24 i18n:接入 useTranslations) |
2.4 questions(题库模块)
职责:题库管理(题目 CRUD、知识点关联、题型支持、教材→章节→知识点级联筛选)。
导出函数:
- Actions(11 个,全部调用
requirePermission()):getQuestionsAction/createQuestionAction/updateQuestionAction/deleteQuestionAction/getKnowledgePointOptionsAction/getTextbookOptionsAction(✅ 审计新增:级联第一级)/getChapterOptionsAction(textbookId)(✅ 审计新增:级联第二级)/getKnowledgePointOptionsByChapterAction(chapterId)(✅ 审计新增:级联第三级)/deleteQuestionsBatchAction(✅ 审计新增:批量删除)/exportQuestionsAction(✅ 审计新增:导出)/importQuestionsAction(✅ 审计新增:导入) - Data-access:
getQuestions(✅ 审计升级:支持textbookId/chapterId级联筛选参数,通过 textbooks data-access 获取知识点 ID 集合)/createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptions/getTextbookOptions(✅ 审计新增)/getChapterOptions(textbookId)(✅ 审计新增)/getKnowledgePointOptionsByChapter(chapterId)(✅ 审计新增)/deleteQuestionsBatch(✅ 审计新增:批量删除,事务原子性)/exportQuestions(✅ 审计新增:导出,权限范围过滤)/importQuestions(✅ 审计新增:批量导入,事务原子性) - Types:
Question/QuestionWithRelations/GetQuestionsParams(✅ 审计升级:新增textbookId/chapterId字段)/CreateQuestionInput/KnowledgePointOption/TextbookOption(✅ 审计新增)/ChapterOption(✅ 审计新增)/QuestionContent(✅ 审计新增:类型守卫接口)/QuestionOption(✅ 审计新增)/QUESTION_TYPE_I18N_KEY(✅ 审计新增:题型 i18n 键映射)/DIFFICULTY_I18N_KEY(✅ 审计新增:难度 i18n 键映射) - Utils:
parseQuestionContent(raw)(✅ 审计新增:类型守卫,替代as断言)/getQuestionPreview/getQuestionOptions/getQuestionText/trackQuestionEvent(✅ 审计新增:埋点接口 no-op)/trackQuestionCreated/trackQuestionUpdated/trackQuestionDeleted/trackQuestionSearched - Components:
QuestionFilters(✅ 审计重写:组合 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/*、@/auth、textbooks(✅ 审计升级:通过 textbooks data-access 的getTextbooks/getChaptersByTextbookId/getKnowledgePointsByChapterId/getKnowledgePointsByTextbookId实现级联筛选) - 被依赖:
exams(通过类型导入,合理)、textbooks(UI 组合,合理)、error-book(通过 data-access,合理)、adaptive-practice(通过 data-access,合理)
已知问题:
- ✅ P1-2 已修复:
写操作函数错放在已下沉到 data-accessactions.ts - ✅ P1-1 已修复:
改为调用 textbooks data-accessgetKnowledgePointOptionsAction直查 textbooks 模块表 - ✅ P2 已解决:
扩充至 260+ 行data-access.ts仅 129 行 - ✅ 审计 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改为useQuestionColumnsHook,通过QuestionBankResultsClient客户端桥接 - ✅ 审计 P1 已修复:
缺监控埋点预留trackQuestionEventno-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 格式,权限范围过滤,导入预览确认) - ✅ 2026-07-07 S-06:
data-access.ts7 个公开导出函数(getQuestionsRaw/getQuestionsDashboardStatsRaw/createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptions/getKnowledgePointsForQuestionsRaw)补全 JSDoc(@param + @returns + @throws)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
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+ | 列定义 Hook(useQuestionColumns) |
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 内容编辑、知识图谱)。
导出函数:
- Actions(14 个,均为写操作;读操作由 RSC 页面直接调用 data-access):
createTextbookAction/updateTextbookAction/deleteTextbookAction/createChapterAction/updateChapterContentAction/deleteChapterAction/reorderChaptersAction/createKnowledgePointAction/updateKnowledgePointAction/deleteKnowledgePointAction/getKnowledgePointsByChapterAction/getKnowledgeGraphDataAction(✅ Task 7 新增:知识图谱数据查询,支持 structure/student-mastery/class-mastery 三种视图)/createPrerequisiteAction(✅ Task 7 新增:声明前置依赖,含循环检测)/deletePrerequisiteAction(✅ Task 7 新增:删除前置依赖) - Data-access:
getTextbooks/getTextbookById/getChaptersByTextbookId/getKnowledgePointsByChapterId/getKnowledgePointsByTextbookId/createTextbook/updateTextbook/deleteTextbook/createChapter/updateChapterContent/deleteChapter/createKnowledgePoint/updateKnowledgePoint/deleteKnowledgePoint/reorderChapters/getTextbooksDashboardStats/getKnowledgePointOptions(跨模块接口,供 questions 调用)/getTextbooksWithScope(P1-1 新增:按数据范围获取教材列表,学生端强制按年级过滤)/verifyChapterBelongsToTextbook(P0-4 新增:资源归属校验)/verifyKnowledgePointBelongsToTextbook(P0-4 新增:资源归属校验)/verifyKnowledgePointBelongsToChapter(P0-4 新增:校验知识点是否属于指定章节)/createPrerequisite(✅ Task 7 新增:创建前置依赖)/deletePrerequisite(✅ Task 7 新增:删除前置依赖)/getPrerequisiteEdgesForTextbook(✅ Task 7 新增:获取教材下所有前置依赖边,用于循环检测)/getSubjectLabelKey/getGradeLabelKey(i18n 标签键) - Data-access-graph(✅ Task 5 新增,图谱只读查询):
getKnowledgePointsWithRelations(知识点+依赖+题目数聚合查询)/getStudentKpMastery(学生掌握度)/getClassKpMastery(班级平均掌握度)/getPrerequisitesForKp/getSuccessorsForKp - Constants(✅ 新增):
SUBJECTS/GRADES/SUBJECT_COLORS/getSubjectColor/getSubjectLabelKey/getGradeLabelKey- ✅ v1 测试修复:
SUBJECTS新增Chinese学科、GRADES新增Grade 1/Grade 2,与 seed 数据对齐 - ✅ v1 测试修复:
SUBJECT_COLORS新增Chinese主题色(rose)
- ✅ v1 测试修复:
- Utils(✅ 新增,纯函数 + 单测):
sortChapters/buildChapterTree/buildChapterIndex/findChapterParent/filterKnowledgePointsByChapter/normalizeOptional/highlightKnowledgePoints/hasCycleAfterAddingEdge(Task 4 新增:循环依赖检测,DFS 算法)/findChapterById(V3 续审计新增:递归查找章节树中指定 ID 的章节,替代各模块本地重复实现) - Graph-layout(✅ 新增,纯函数 + 单测):
computeGraphLayout - Lib(✅ v6 审计修复新增,类型守卫集合):
isKpWithRelations/isGraphNodeData/isGraphLayoutNodeData/isGraphEdgeData/isKpGraphNode/isKpGraphLink(位于lib/type-guards.ts,替代 components/* 中针对 React Flownode.data与 react-force-graph-2dNodeObject/LinkObject的as断言,从 unknown 安全收窄到具体业务类型) - Components(✅ Task 13 新增):
GraphNodeDetailPanel— 知识图谱节点详情侧边面板,展示知识点名称/描述/掌握度/关联题目/前置后置知识点,支持跳转与前置关系增删 - Analytics(✅ 新增):
TextbookAnalytics/TextbookAnalyticsProvider/useTextbookAnalytics
依赖关系:
- 依赖:
shared/*、@/auth、@xyflow/react(知识图谱可视化)、@dagrejs/dagre(图谱分层布局算法)、react-force-graph-2d(v4 新增:Obsidian 风格力导向图;v6 types.ts 导入其 NodeObject/LinkObject 类型供 KpGraphNode/KpGraphLink 接口继承) - 被依赖:
questions(✅ P1-1 已修复:通过 textbooks data-access)、exams(通过类型)、dashboard(通过 data-access,P0-4 已修复) - ✅ UI 层跨模块依赖已解耦:
textbooks/components/knowledge-point-dialogs.tsx不再直接 import questions 模块,改为通过 render prop 注入创建题目入口
已知问题:
- ✅ 无跨模块 DB 访问(data-access 层)
- ✅ actions 层编排模式标杆(权限校验 → 调用 data-access → revalidatePath)
- ✅ data-access 层职责单一
- ✅ P2 已修复:
data-access.ts中byId.get(pid)!.children.push非空断言清理为安全守卫;or(...)!非空断言清理为条件 push - ✅ P0 跨模块 UI 依赖已修复:
knowledge-point-dialogs.tsx通过 render prop 解耦,不再直接 import questions 模块 - ✅ P0 前端权限硬编码已修复:改用
usePermission().hasPermission() - ✅ P0 i18n 已基本接入:
chapter-sidebar-list.tsx/actions.ts/section-error-boundary.tsx(默认值改英文)均已接入 next-intl - ✅ P1 Server Action 资源归属校验已修复:新增
verifyChapterBelongsToTextbook/verifyKnowledgePointBelongsToTextbook - ✅ P1 data-access 数据范围过滤已修复:新增
getTextbooksWithScope,学生端强制按年级过滤 - ✅ P1 Error Boundary 已补齐:新增
section-error-boundary.tsx - ✅ P1 知识点列表/弹窗重复实现已清理:移除无调用方的
knowledge-point-panel.tsx - ✅ P1 学科/年级选项统一:抽取到
constants.ts(SUBJECTS/GRADES/SUBJECT_COLORS) - ✅ v1 测试修复:
textbook-reader.tsx移除SheetTrigger(错误置于Sheet外部导致DialogTrigger must be used within Dialog),改用受控Button+onClick打开移动端抽屉 - ✅ v1 测试修复:新增
teacher-textbook-reader.tsx客户端包装组件,解决 Server Component 向 Client Component 传递函数 prop(renderQuestionCreator)违反 Next.js 序列化约束的问题 - ✅ v1 测试修复:
scripts/seed.ts教材 subject/grade 改为英文 value(与constants.ts对齐),消除 i18nMISSING_MESSAGE错误 - ✅ P1 纯逻辑已导出并补单测:新增
utils.ts/graph-layout.ts及对应.test.ts - ✅ v3 审计修复(2026-06-24):学生端两个页面添加
requirePermission(Permissions.TEXTBOOK_READ)显式权限校验 - ✅ v3 审计修复(2026-06-24):
getKnowledgeGraphDataAction添加学生年级 scope 过滤,防止跨年级越权查看图谱数据 - ✅ v3 审计修复(2026-06-24):
getKnowledgeGraphDataAction的 class-mastery 模式限制为教师/管理员使用 - ✅ v3 审计修复(2026-06-24):
deleteChapter级联删除knowledgePointPrerequisites孤儿记录,保证数据完整性 - ✅ v3 审计修复(2026-06-24):
knowledge-graph.tsx前置依赖操作添加 try/catch/finally,防止 loading 状态卡死 - ✅ v3 审计修复(2026-06-24):
textbook-reader.tsxonCreateKnowledgePoint添加 try/finally - ✅ v3 审计修复(2026-06-24):
section-error-boundary.tsx添加componentDidCatch错误日志记录 +role="alert"a11y - ✅ v3 审计修复(2026-06-24):4 个 error.tsx 抽取为共享
RouteError组件,消除重复代码 - ✅ v3 审计修复(2026-06-24):所有 loading.tsx 添加
role="status"/aria-busya11y 标记 - ✅ v3 审计修复(2026-06-24):
chapter-sidebar-list.tsx章节项添加role="button"/tabIndex/onKeyDown键盘支持 - ✅ v3 审计修复(2026-06-24):
graph-node-detail-panel.tsx图标按钮添加aria-label - ✅ v3 审计修复(2026-06-24):
data-access.tsas [string, string]断言改为返回类型标注 - ✅ v3 审计修复(2026-06-24):
knowledge-point-dialogs.tsxas断言改为 void 包装 - ✅ v3 审计修复(2026-06-24):
graph-kp-node.tsx双重as unknown as断言改为类型守卫函数extractNodeData - ✅ v3 审计修复(2026-06-24):
graph-layout.tsGraphLayoutNodeData新增graphData字段类型定义 - ✅ v3 审计修复(2026-06-24):删除死代码
verifyKnowledgePointBelongsToChapter、ReorderChaptersSchema、deprecatedcanEditprop - ✅ v3 审计修复(2026-06-24):
textbook-settings-dialog.tsx新增redirectAfterDeleteprop 消除硬编码 URL - ✅ v3 审计修复(2026-06-24):
teacher/textbooks/[id]/loading.tsx骨架屏布局改为匹配阅读器布局 - ✅ v3 P2 修复(2026-06-24):
data-access.ts3 处硬编码英文异常文案改为NotFoundError(教材/章节),消息可安全返回客户端 - ✅ v3 P2 修复(2026-06-24):
graph-layout.tsO(n²)some()查找改为Set查找,性能降为 O(n) - ✅ v3 P2 修复(2026-06-24):抽取
textbook-form-fields.tsx共享组件,消除textbook-form-dialog与textbook-settings-dialog表单字段重复 - ✅ v3 P2 修复(2026-06-24):
textbook-content-panel.tsx17 个 props 按chapter/editing/selection/highlight四组重构,提升可读性 - ✅ v3 P2 修复(2026-06-24):
textbook-reader.tsx移除命令式classList操作,视觉高亮改为isHighlighted状态驱动,仅保留scrollIntoView - ✅ v3 P2 修复(2026-06-24):
use-kp-crud.ts(122 行)拆分为use-kp-create/use-kp-delete/use-kp-update三个子 Hook + 门面,均在 80 行限制内 - ✅ v6 审计修复(2026-07-07):
graph-kp-node.tsx2 处as断言(data.kp as KpWithRelations、data.graphData as GraphNodeData | undefined)改用lib/type-guards.isKpWithRelations/isGraphNodeData守卫,extractNodeData返回| null,校验失败组件渲染 null - ✅ v6 审计修复(2026-07-07):
graph-prerequisite-edge.tsxdata as unknown as GraphEdgeData | undefined双重断言改用lib/type-guards.isGraphEdgeData守卫 - ✅ v6 审计修复(2026-07-07):
knowledge-graph-node.tsxnode.data as GraphLayoutNodeData断言改用lib/type-guards.isGraphLayoutNodeData守卫 - ✅ v6 审计修复(2026-07-07):
force-graph.tsx6 处as断言(node as KpGraphNode×3、link as KpGraphLink、kpLink.source as KpGraphNode、kpLink.target as KpGraphNode)改用lib/type-guards.isKpGraphNode/isKpGraphLink守卫;KpGraphNode/KpGraphLink接口从 force-graph.tsx 迁移至 types.ts 供守卫模块引用;保留 Line 80as unknown as ComponentType第三方库(next/dynamic)类型限制并加注释 - ✅ 2026-07-07 S-06:
data-access.ts全部公开导出函数(getTextbooksRaw/getTextbookByIdRaw/getChaptersByTextbookIdRaw/createTextbook/updateTextbook/deleteTextbook/createChapter/updateChapterContent/deleteChapter/getKnowledgePointsByChapterIdRaw/getKnowledgePointsByTextbookIdRaw/createKnowledgePoint/updateKnowledgePoint/deleteKnowledgePoint/reorderChapters/getTextbooksDashboardStatsRaw/createPrerequisite/deletePrerequisite/getTextbookTitlesByIdsRaw/getChapterTitlesByIdsRaw/getKnowledgePointNamesByIdsRaw 共 22 个)补全 JSDoc(@param + @returns + @throws) - ✅ 2026-07-07 修复:
getKnowledgePointsByTextbookIdRaw箭头函数缺失=>(pre-existing 语法错误,tsc 在添加后续 JSDoc 时暴露),补全后 tsc 通过 - ⚠️ 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 + 跨模块查询接口 + 资源归属校验 + 数据范围过滤 + 前置依赖 CRUD(v3 P2:硬编码异常改为 NotFoundError) |
data-access-graph.ts |
188 | 知识图谱只读查询(✅ Task 5 新增:知识点关联聚合、学生/班级掌握度、前置后置知识点,标记 server-only;v2 修复:前置依赖双向 IN 过滤防跨教材污染) |
types.ts |
133 | 类型定义(含知识图谱类型:GraphViewMode/GraphLayoutMode/MasteryInfo/KpWithRelations/GraphNodeData/GraphEdgeData/KnowledgeGraphData/MasteryLevel;v6 审计修复新增:KpGraphNode/KpGraphLink 从 force-graph.tsx 提取至此,供 lib/type-guards.ts 引用) |
schema.ts |
56 | Zod 校验(含 CreatePrerequisiteSchema/DeletePrerequisiteSchema;v2 修复: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 P2:O(n²) 改为 Set 查找) |
lib/type-guards.ts |
122 | v6 审计修复新增:教材模块类型守卫集合(isKpWithRelations/isGraphNodeData/isGraphLayoutNodeData/isGraphEdgeData/isKpGraphNode/isKpGraphLink),替代 components/* 中针对 React Flow node.data 与 react-force-graph-2d NodeObject/LinkObject 的 as 断言 |
analytics.tsx |
37 | 教材分析 Context/Provider/Hook(✅ 新增) |
hooks/use-knowledge-point-actions.ts |
45 | 知识点操作门面 Hook(v2 拆分:组合 useKpDialogState + useKpCrud,对外 API 不变) |
hooks/use-kp-dialog-state.ts |
33 | 知识点对话框状态管理 Hook(v2 新增:从 use-knowledge-point-actions 拆分) |
hooks/use-kp-crud.ts |
52 | 知识点 CRUD 门面 Hook(v3 P2 拆分:组合 useKpCreate/useKpDelete/useKpUpdate,对外 API 不变) |
hooks/use-kp-create.ts |
43 | 知识点创建 Hook(v3 P2 新增:从 use-kp-crud 拆分) |
hooks/use-kp-delete.ts |
49 | 知识点删除 Hook(v3 P2 新增:从 use-kp-crud 拆分) |
hooks/use-kp-update.ts |
36 | 知识点更新 Hook(v3 P2 新增:从 use-kp-crud 拆分) |
hooks/use-text-selection.ts |
49 | 文本选区捕获 Hook |
hooks/use-graph-data.ts |
65 | 知识图谱数据加载 Hook(✅ Task 11 新增:派生值模式避免 effect 中 setState;v2 修复:区分 isLoading/isRefreshing 避免 UI 闪烁) |
components/knowledge-graph.tsx |
408 | 知识图谱主组件(✅ Task 10/15 重写:全书视图 + 搜索高亮 + 关联节点高亮 + 章节着色;v2 新增:添加/删除前置依赖 Dialog;v4 新增:hierarchical/force 布局模式切换,根据布局模式渲染分层 ReactFlow 或 Obsidian 风格力导向图,状态/详情面板/依赖增删对话框共享) |
components/graph-kp-node.tsx |
105 | React Flow 自定义节点(✅ Task 9 新增:知识点名称+题目数徽章+掌握度进度条;v2 修复:节点宽度常量化 NODE_WIDTH;v3 修复:类型守卫替代 as 断言;v6 审计修复:extractNodeData 改用 lib/type-guards.isKpWithRelations/isGraphNodeData 守卫,校验失败返回 null) |
components/graph-prerequisite-edge.tsx |
47 | React Flow 自定义边(✅ Task 9 新增:虚线+箭头表示前置依赖;v2 修复:GraphEdgeData 类型安全转换;v6 审计修复:改用 lib/type-guards.isGraphEdgeData 守卫替代 as unknown as 双重断言) |
components/graph-toolbar.tsx |
121 | 图谱工具栏(✅ Task 9 新增:视图模式切换 + 搜索 + 重置视图;v2 新增:isRefreshing 轻量指示器;v4 新增:hierarchical/force 布局模式切换 Select) |
components/force-graph.tsx |
446 | Obsidian 风格力导向图谱组件(v4 新增:基于 react-force-graph-2d + d3-force;v5 视觉对齐 Obsidian Graph View:小圆点节点+极细半透明边线+标签右侧带背景+hover/选中邻居高亮+紫色外圈焦点环;通过 next/dynamic ssr:false + import type 加载避免 SSR canvas/window 报错;v6 审计修复:KpGraphNode/KpGraphLink 接口迁移至 types.ts,6 处 as 断言改用 lib/type-guards.isKpGraphNode/isKpGraphLink 守卫;保留 Line 80 as unknown as ComponentType 第三方库类型限制) |
components/graph-node-detail-panel.tsx |
171 | 节点详情侧边面板(✅ Task 13 新增:描述/掌握度/关联题目/前置后置列表;v2 修复:移除未使用 textbookId prop;v3 修复:aria-label) |
components/chapter-sidebar-list.tsx |
325 | 章节侧边栏列表(v2 修复:canEdit 默认 false + orderUpdateFailed i18n key;v3 修复:键盘导航) |
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 P2:17 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-* 子 Hook;teacher-textbook-reader.tsx 已移至 app 层) |
2.6 grades(成绩模块)— 标杆模块(拆分范例)
职责:成绩分析(录入/查询/统计/导出/趋势对比分析)。
导出函数:
- Actions:
getGradeRecordsAction/createGradeRecordAction(v4-P1-6 增强:成绩录入后通知学生和家长,调用notifyGradeEntered)/updateGradeRecordAction/deleteGradeRecordAction/exportGradesAction(v4-P1-12 增强:新增可选studentId参数,支持按学生导出,家长视角调用exportStudentGradeRecordsToExcel,校验 studentId 属于家长子女)/getGradeTrendAction/getClassComparisonAction/getSubjectComparisonAction/getGradeDistributionAction/getGradeDistributionByGradeIdAction(✅ v4-P2-7 新增:年级仪表盘维度1,按 gradeId 查询年级整体 + 按班级拆分的成绩分布,GRADE_RECORD_READ 权限)/getClassRankingAction/getRankingTrendAction/getGradeRecordByIdAction/getClassGradeStatsAction/getStudentGradeSummaryAction/batchCreateGradeRecordsAction(v4-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 为 async,而assertClassInScope是同步函数,已迁移至独立文件lib/scope-check.ts,actions.ts 与 actions-analytics.ts 均从./lib/scope-check导入 - Data-access:
getGradeRecords/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 新增:成绩录入草稿 CRUD,upsert + 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-check):
toNumber/normalize(位于lib/grade-utils.ts);buildScopeClassFilter(scope, currentUserId?)(P3-26 从 grade-utils.ts 迁移至lib/scope-filter.ts,P3 修复:class_membersscope 内置 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/exportStudentGradeRecordsToExcel(v4-P1-12 新增:导出单个学生成绩单家长视角,仅含成绩明细 + 个人统计,不含班级数据,scope 为 children 自动按 studentId 过滤)/formatDateForFile(已迁移至 shared/lib/utils) - Components(✅ P1-5 新增,✅ Phase 4.6 拆分扩展):
WidgetBoundary(Error Boundary + Suspense + Skeleton 组合,含 a11y 属性)/SchoolWideSummaryCard(✅ v3-P2 新增:管理员全校成绩汇总卡片,4 个统计卡片 + 各年级对比表格)/ScoreCell(✅ v4-P1-7 新增:成绩单元格组件,根据得分率着色——红<60%/黄60-84%/绿≥85%,使用语义化 Tailwind 类名避免动态拼接,fullScore<=0 时不着色)/BatchGradeEntrySelectors(✅ Phase 4.6 新增:批量录入试卷/班级选择器子组件)/BatchGradeEntryToolbar(✅ Phase 4.6 新增:批量录入底部工具栏)/GradeRecordRow(✅ Phase 4.6 新增:成绩记录行渲染:桌面表格+移动端卡片)/GradeRecordDetail(✅ Phase 4.6 新增:成绩记录编辑/删除对话框集合,导出 EditableFields 类型与 toEditableFields 工具函数)
依赖关系:
- 依赖:
shared/*、@/auth、classes(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId/getClassGradeIdsByClassIds)、school(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions)、users(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds)、exams(✅ 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 已修复:
多处直查改为调用对应模块 data-access 函数(classes/school/users)classes/classEnrollments/subjects/users表 - ✅ P1-1 已修复:
统计计算业务逻辑混入 data-access(抽取为纯函数到getClassGradeStats/getGradeDistribution)stats-service.ts,数据层专注 DB I/O - ✅ P1-2 已修复:
抽取到toNumber/normalize/buildScopeClassFilter在 3 个 data-access 文件中重复定义lib/grade-utils.ts统一维护 - ✅ P1-3 已修复:
12 个查询/分析 Action 缺少 Zod 校验新增 12 个查询 schema(DeleteGradeRecordSchema/GetGradeRecordByIdSchema/GradeQuerySchema/ClassGradeStatsQuerySchema/StudentGradeSummaryQuerySchema/ClassRankingQuerySchema/ExportGradesSchema/GradeTrendQuerySchema/ClassComparisonQuerySchema/SubjectComparisonQuerySchema/GradeDistributionQuerySchema/RankingTrendQuerySchema),所有 Action 使用 safeParse 校验 - ✅ P1-4 已修复:
改用类型守卫函数(isGradeType/isSemester/isDistributionTooltipPayload)batch-grade-entry.tsx/grade-record-form.tsx/grade-distribution-chart.tsx中存在as断言 - ✅ P1-5 已修复:
teacher/grades 与 teacher/diagnostic 路由缺少 loading.tsx/error.tsx已为 7 个路由补齐 loading.tsx + error.tsx,并新增WidgetBoundary通用组件 - ✅ P2-1 已修复:
图表/表格/列表缺少 a11y ARIA 属性为 4 个成绩图表添加role="img"+aria-label,2 个表格添加<caption>,3 个图标按钮添加aria-label - ✅ P2-2 已修复:
diagnostic 组件中存在 Tailwind 任意值改用标准 Tailwind 类 - ✅ P2-4 已修复:~~
buildScopeClassFilter中grade_managedscope 返回sql\1=0`(空数据)~~ 改为子查询classId IN (SELECT id FROM classes WHERE grade_id IN (...))` 过滤所管年级的班级 - ✅ P2-6 已修复:
student/grades 和 management/grade/insights 各自重复定义改为统一从SearchParams类型与getParam函数@/shared/lib/search-params导入 - ✅ actions 层无直接 DB 访问(标杆)
- ✅ data-access 按职责拆分为 3 个文件(标杆)
- ✅ P2 已修复:
export.ts中scoreMap.get(r.studentId)!非空断言清理为安全守卫(if (!subjMap) continue) - ✅ P3 修复(2026-06-22):
score 字段添加schema.ts缺少输入边界约束.max(1000),records 数组添加.max(500),查询 schema 补全 studentId/semester/examId 字段 - ✅ P3 修复(2026-06-22):
新增buildScopeClassFilter的class_membersscope 未应用 studentId 过滤currentUserId参数,class_membersscope 内置eq(gradeRecords.studentId, currentUserId)过滤 - ✅ P3 修复(2026-06-22):
改为 DB 层分页(limit/offset),并行查询总数与当前页数据,返回getGradeRecords内存分页(全量查询后切片)PaginatedGradeRecords { records, total } - ✅ P3 修复(2026-06-22):
包裹batchCreateGradeRecords非原子操作db.transaction确保原子性 - ✅ P3 修复(2026-06-22):
新增存在性检查,不存在时抛updateGradeRecord/deleteGradeRecord未检查记录是否存在NotFoundError("成绩记录") - ✅ P3 修复(2026-06-22):
所有函数新增getClassGradeStats/getStudentGradeSummary/getClassRanking/getClassStudentsForEntry/getClassGradeStatsWithMeta缺少 scope 过滤scope?/currentUserId?参数,应用buildScopeClassFilter;getStudentGradeSummary/getClassStudentsForEntry对class_taughtscope 校验学生/班级归属 - ✅ P3 修复(2026-06-22):
相同平均分获得相同名次,下一名次跳过占位getClassRanking未处理并列排名 - ✅ P3 修复(2026-06-22):
应用getClassComparison/getRankingTrend缺少 scope 过滤buildScopeClassFilter;getRankingTrend对class_taughtscope 校验 - ✅ P3 修复(2026-06-22):
actions.ts/actions-analytics.ts catch 块直接返回改用e.messagehandleActionError(e)统一错误处理;batchCreateGradeRecordsAction使用safeJsonParse解析 JSON - ✅ P3 修复(2026-06-22):
actions-analytics.ts 缺少 class scope 校验getGradeTrendAction/getSubjectComparisonAction/getGradeDistributionAction新增assertClassInScope校验 - ✅ P3 修复(2026-06-22):
组件直接 try/catch 调用 Server Action改用safeActionCall包装器(onError/onFinally 回调);batch-grade-entry.tsxlocalStorage 访问包裹typeof window !== "undefined"检查;区分"未录入"与"录入 0" - ✅ P3 修复(2026-06-22):
新增日期有效性检查与grade-trend-card.tsx排序未处理 NaN 日期、除法未检查 fullScore > 0fullScore > 0守卫 - ✅ P3 修复(2026-06-22):
页面文件缺少 scope 传递teacher/grades/page.tsx 使用 DB 层分页;analytics/page.tsx 添加 EmptyState;entry/page.tsx 与 stats/page.tsx 过滤 class_taught scope 班级;student/grades/page.tsx 与 parent/grades/page.tsx 传递ctx.dataScope到 data-access - ✅ v3-P2 改进(2026-06-23):新增
grade_drafts表(成绩录入草稿,userId+classId+subjectId+type 唯一键,content JSON 存储 scores,24 小时过期,schema.ts 第 1444-1469 行);新增saveGradeDraft/getGradeDraft/deleteGradeDraftdata-access 函数 + 对应 3 个 Server Actions,支持成绩批量录入草稿服务端持久化 - ✅ v3-P2 改进(2026-06-23):新增
getExamOptionsForGradesdata-access 函数,获取指定班级/科目下有成绩记录的考试列表;AnalyticsFilters组件新增exams/currentExamId/currentSemesterprops,添加学期和考试筛选 ChipNav;teacher/grades/analytics/page.tsx 新增 semester/examId 搜索参数解析 - ✅ v3-P2 改进(2026-06-23):
getGradeTrend/getGradeDistribution/getSubjectComparison/getClassComparison均新增semester和examId可选参数,支持按学期和考试筛选分析 - ✅ v3-P2 改进(2026-06-23):新增
getSchoolWideGradeSummarydata-access 函数 +SchoolWideSummaryCard组件 +SchoolWideGradeSummary/SchoolWideGradeSummaryItem类型,管理员全校成绩汇总视图(按年级聚合平均分/及格率/优秀率/学生数/班级数,加权平均计算全校汇总);admin/school/grades/insights/page.tsx 顶部新增 SchoolWideSummaryCard - ✅ v3-P2 改进(2026-06-23):parent/grades/page.tsx 为每个子女并行查询
getClassAverageTrend,渲染 GradeTrendCard - ✅ v3-P3-4 改进(2026-06-23):GradeTrendCard 新增日期范围选择器(全部/近7天/近30天/近90天),通过 nuqs
trendRangeURL 参数持久化,useEffect 中计算截止时间戳避免渲染阶段调用 Date.now() - ✅ v4-P3-4 改进(2026-06-23):GradeDistributionChart 色盲友好双重编码——每个分数段使用不同 SVG pattern(条纹/点状/交叉线/反向条纹/网格)+ 颜色,
SimpleBarChart新增defsprop 支持自定义 SVG 图案 - ✅ P3 修复(2026-06-23):
P3-26 将lib/grade-utils.ts72 行超 40 行工具函数上限buildScopeClassFilter迁移至lib/scope-filter.ts,grade-utils.ts 仅保留 toNumber/normalize(20 行) - ✅ P3 修复(2026-06-23):
P3-10 移除 export 关键字,改为内部函数stats-service.tscreateDefaultBuckets 不必要导出 - ✅ P3 修复(2026-06-23):
P3-24 新增 isGradeTrendType 类型守卫函数替代 as 断言stats-service.tsbuildGradeTrendPoints 使用 as 断言 - ✅ P3 修复(2026-06-23):
P3-6 删除局部 avg,复用 computeAverageScoreexport.ts局部 avg 函数与 stats-service.computeAverageScore 重复 - ✅ P3 修复(2026-06-23):
P3-7 改用 next-intl getTranslations,新增 export.sheets/columns/metrics i18n 键(zh-CN/en 同步)export.tsTYPE_LABELS 硬编码中文 - ✅ v4-P1-6 改进(2026-06-23):
createGradeRecordAction/batchCreateGradeRecordsAction成绩录入后通知学生和家长(调用notifyGradeEntered,内部使用parent/data-access.getParentIdsByStudentIds批量查询家长 ID),通知失败不阻断成绩录入 - ✅ v4-P1-7 改进(2026-06-23):新增
ScoreCell组件(components/score-cell.tsx),根据得分率着色(红<60%/黄60-84%/绿≥85%),使用语义化 Tailwind 类名避免动态拼接 - ✅ v4-P1-10 改进(2026-06-23):
grade-record-list.tsx使用ScoreCell替代纯文本分数展示 + 表格overflow-x-auto水平滚动 - ✅ v4-P1-12 改进(2026-06-23):
exportGradesAction新增可选studentId参数,支持按学生导出(家长视角);新增exportStudentGradeRecordsToExcel导出函数(仅含成绩明细 + 个人统计,不含班级数据);parent/grades/page.tsx 传 studentId 到 ParentExportButton;ParentExportButton 接入 exportGradesAction - ✅ v3-P3-1 改进(2026-06-23):
batch-grade-entry.tsx新增"下载模板"按钮,客户端生成 CSV 模板(含学生姓名/分数/备注列头 + BOM 支持 Excel UTF-8),教师可下载填好后粘贴到录入表格 - ✅ v3-P3-2 改进(2026-06-23):
grade-record-list.tsx新增多选复选框(全选/单选)+ 批量删除工具栏 + 批量删除确认对话框;新增bulkDeleteGradeRecordsdata-access 函数(使用 inArray 一次性删除避免 N+1)+bulkDeleteGradeRecordsActionServer Action(限制单次最多 500 条) - ✅ v4-P3-2 改进(2026-06-23):
batch-grade-entry.tsx顶部新增可折叠新手引导提示框(4 步使用说明),使用 localStorage 记住用户关闭状态避免重复显示 - ✅ v4-P2-6 修复(2026-06-23):
Next.js 要求 "use server" 文件中所有 export 必须为 async,同步 export 会导致构建错误。修复:将assertClassInScope是同步函数但位于 "use server" 文件 actions.ts 中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.sql)。batchCreateGradeRecordsByExamdata-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-22):
改为 async 组件使用school-wide-summary-card.tsx整个组件无 i18n(13 处硬编码中文)getTranslations("grades"),新增schoolWide翻译键节(zh-CN/en 同步 14 键) - ✅ 审计修复(2026-06-22):
5 个 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-22):
actions.ts15+ 处硬编码中文错误消息/通知内容notifyGradeEntered使用getTranslations("grades")获取通知标题和内容;batchCreateGradeRecordsByExamAction错误消息改用t("action.*")翻译键;exportGradesAction文件名和错误消息改用翻译键,新增action翻译键节(zh-CN/en 同步 ~20 键) - ✅ 审计修复(2026-06-22):
改为英文错误消息(data-access 层为防御性检查,action 层已先行校验)data-access.ts2 处硬编码中文 throw Error - ✅ 审计修复(2026-06-22):
新增data-access.ts:782as断言无类型守卫isGradeDraftData类型守卫函数(lib/type-guards.ts),从 unknown 安全验证 DB JSON 结构 - ✅ 审计修复(2026-06-22):
改用类型谓词data-access-analytics.ts:360-362非空断言!filter((r): r is typeof r & { stats: NonNullable<typeof r.stats> } => r.stats !== null)消除非空断言 - ✅ 审计修复(2026-06-22):
export.ts内存过滤而非 DB 层过滤exportStudentGradeRecordsToExcel传入studentId到getGradeRecordsDB 层过滤 - ✅ 审计修复(2026-06-22):
改用teacher/grades/page.tsx内联类型守卫 +as断言lib/type-guards.ts的isGradeType/isSemester函数 - ✅ 审计修复(2026-06-22):
11 个组件函数缺少返回值类型标注全部添加: JSX.Element或: Promise<JSX.Element>返回值类型 - ✅ 审计修复(2026-06-24):
17 处 Tailwind 任意值(图表组件移除匹配默认值的 props 或改用标准类(w-[140px]/h-[280px]/min-w-[80px]等)h-60/w-52/w-60),过滤器改用w-36/w-32,表格改用min-w-32/min-w-20 - ✅ 审计修复(2026-06-24):
改用声明式 AlertDialog +batch-grade-entry.tsx2 处window.confirm()pendingSwitch状态管理,新增 3 个 i18n 键(confirmTitle/confirmAction/cancelAction) - ✅ 审计修复(2026-06-24):
补充class-grade-report.tsx缺少返回值类型标注: Promise<JSX.Element>返回值类型 - ✅ 审计修复(2026-06-25 P0):
3 个 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-1):
拆分草稿相关 3 个 Action 到actions.ts超 800 行(856 行)actions-draft.ts(106 行);通知逻辑notifyGradeEntered抽取至lib/notify.ts(60 行);actions.ts 降至 646 行 - ✅ 审计修复(2026-06-25 P1-2):
拆分草稿 CRUD 到data-access.ts超 800 行(816 行)data-access-drafts.ts(107 行);拆分按试卷录入到data-access-exam-entry.ts(167 行);data-access.ts 降至 536 行 - ✅ 审计修复(2026-06-25 P1-3):
新增actions.ts多处英文错误消息未 i18naction.error.*翻译键,所有错误消息改用t("action.error.*");scope 校验错误改用t("action.scopeError.{code}") - ✅ 审计修复(2026-06-25 P1-4):
改为返回lib/scope-check.ts返回硬编码英文消息ScopeCheckErrorCode枚举("class_taught" | "class_members" | "denied"),由 Action 层通过 i18n 键grades.action.scopeError.{code}翻译 - ✅ 审计修复(2026-06-25 P1-5):
所有 6 个 Action 使用actions-analytics.tsscope 校验消息未 i18ngetTranslations("grades")获取翻译;新增ownRankingTrendOnly/childrenRankingTrendOnly翻译键 - ✅ 审计修复(2026-06-25 P1-6/P1-7):
拆分为batch-grade-entry.tsx超 500 行(604 行)+ as 断言BatchGradeEntryTable+BatchGradeEntryStats+BatchGradeEntryDialog子组件 +useBatchGradeEntryUndoHook;主组件降至 422 行;撤销数据使用isUndoData类型守卫(in 运算符)替代as { ids: string[]; timestamp: number }断言 - ✅ 审计修复(2026-06-25 P1-8):
改用parent/grades/page.tsx第 63 行 as 断言for循环 + 类型守卫if (summary === null) continue替代.filter().map()+as NonNullable<...>断言 - ✅ 审计修复(2026-06-25 P1-9):
新增teacher/grades/page.tsx列表与统计未 WidgetBoundary 包裹<WidgetBoundary title={t("title.list")}>包裹<GradeRecordList>,避免单数据区块失败导致整页崩溃 - ✅ 审计修复(2026-06-25 P2-2):
补全grade-filters.tsx/grade-query-filters.tsx/export-button.tsx函数缺返回值类型: JSX.Element/: Promise<void>返回值类型标注 - ✅ 审计修复(2026-06-25 P2-3):
改用updateGradeRecord使用Record<string, unknown>累积更新字段Partial<typeof gradeRecords.$inferInsert>类型安全更新 - ✅ 审计修复(2026-06-25 P2-4):
新增getStudentGradeSummary内部调用getClassRanking形成 data-access 层内部耦合getStudentRankInClass独立函数(两步法:查学生平均分 → 查所有学生平均分 → 计算严格高于的不同平均分数),避免获取整个班级排名(含姓名查询)只为查单个学生排名 - ✅ 审计修复(2026-06-25 P2-9):
lib/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-12:exportGradesAction 新增 studentId 参数;v3-P3-2 新增:bulkDeleteGradeRecordsAction 批量删除;v4-P2-6:assertClassInScope 迁移至 lib/scope-check.ts;2026-06-24 新增:batchCreateGradeRecordsByExamAction 按试卷录入每题得分;P1-1 重构(2026-06-25):草稿相关 3 个 Action(saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftAction)迁移至 actions-draft.ts;通知逻辑 notifyGradeEntered 抽取至 lib/notify.ts;错误消息全部 i18n(t("action.*"));scope 校验错误改用 ScopeCheckErrorCode + t("action.scopeError.{code}") 翻译) |
actions-draft.ts |
106 | P1-1 新增(2026-06-25):成绩录入草稿 3 个 Server Action(saveGradeDraftAction/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 Action(getDraftLockStatusAction/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-26):Excel 批量导入成绩 3 个 Server Action(downloadGradeImportTemplateAction/importGradesFromExcelAction/previewGradeImportAction)。权限 GRADE_RECORD_MANAGE + assertClassInScope,调用 import-export.ts 解析 Excel + 匹配学生姓名 + 批量入库。支持模板下载(含班级学生姓名)、预校验、正式导入 |
actions-appeal.ts |
197 | P3-7 新增(2026-06-26):成绩申诉 5 个 Server Action(createGradeAppealAction/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/withdrawn,originalScore 快照留痕,reviewGradeAppeal 同步更新成绩记录 |
import-export.ts |
278 | P3-10 新增(2026-06-26):Excel 导入成绩逻辑。generateGradeImportTemplate 生成含班级学生姓名的模板,parseGradeExcelBuffer 解析 Excel(支持中英双语表头),parseGradeImportData 匹配学生姓名到 ID(处理重名),buildBatchInputFromImport 转换为批量录入输入。依赖 @/shared/lib/excel |
actions-analytics.ts |
219 | 6 个分析 Action(含 Zod 校验,P3 修复:handleActionError + assertClassInScope 校验;v4-P2-6:assertClassInScope 改从 ./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):草稿 CRUD(saveGradeDraft/getGradeDraft/deleteGradeDraft)迁移至 data-access-drafts.ts;按试卷录入(batchCreateGradeRecordsByExam)迁移至 data-access-exam-entry.ts;P2-3:updateGradeRecord 类型强化为 Partial<typeof gradeRecords.$inferInsert>;P2-4:新增 getStudentRankInClass 解耦 getStudentGradeSummary 与 getClassRanking) |
data-access-drafts.ts |
405 | P1-2 新增(2026-06-25):成绩录入草稿 CRUD(saveGradeDraft/getGradeDraft/deleteGradeDraft)+ GradeDraftData 接口,从 data-access.ts 拆分。使用 cache() memoize getGradeDraft,isGradeDraftData 类型守卫安全解析 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 |
23 | G2-005 / S-01 治理(2026-07-07):barrel 重新导出,原 831 行超过 800 行建议上限,拆分为 4 个按分析维度划分的子文件(trend/class/student/overview),保持向后兼容,所有消费者无需修改 import 路径 |
data-access-analytics-trend.ts |
135 | G2-005 / S-01 新增(2026-07-07):成绩趋势分析(getGradeTrendRaw/getGradeTrend + getExamOptionsForGradesRaw/getExamOptionsForGrades + GradeTrendParams 接口),从 data-access-analytics.ts 拆分 |
data-access-analytics-class.ts |
340 | G2-005 / S-01 新增(2026-07-07):班级/科目/年级维度分析(getClassComparison/getClassComparisonWithSignificance + getSubjectComparison + getGradeDistribution + getGradeDistributionByGradeId + 4 个 Params 接口),从 data-access-analytics.ts 拆分 |
data-access-analytics-student.ts |
225 | G2-005 / S-01 新增(2026-07-07):学生维度分析(getStudentPositionInClassDistribution + getStudentGrowthArchive),从 data-access-analytics.ts 拆分 |
data-access-analytics-overview.ts |
135 | G2-005 / S-01 新增(2026-07-07):全校汇总分析(getSchoolWideGradeSummary,管理员视图,按年级聚合),从 data-access-analytics.ts 拆分 |
data-access-ranking.ts |
166 | 排名查询(P3 修复:getRankingTrend 接受 scope 参数 + class_taught 校验) |
stats-service.ts |
285 | 统计计算纯函数(P1-1 新增:8 个纯函数 + 2 个常量 + 2 个接口;P3-10:createDefaultBuckets 改为内部函数;P3-24:buildGradeTrendPoints 使用 isGradeTrendType 类型守卫替代 as 断言) |
export.ts |
290+ | Excel 导出(v2-P1-5 修复:传递 currentUserId 到 data-access;P3 修复:适配 PaginatedGradeRecords 结构 + 传递 scope;P3-6:复用 stats-service.computeAverageScore 替代局部 avg;P3-7:硬编码中文改用 next-intl getTranslations;v4-P1-12 新增:exportStudentGradeRecordsToExcel 家长视角单学生导出) |
schema.ts |
237 | Zod 校验(含 12 个查询 schema;P3 修复:score .max(1000) + records .max(500) + 补全查询字段;v3-P2 新增:grade_drafts 表定义;2026-06-24 新增:BatchGradeEntryByExamSchema;P3-10 新增(2026-06-26):ExcelImportRowSchema/ExcelImportGradesSchema + 导出 ExcelImportRowInput/ExcelImportGradesInput 类型;P3-7 新增(2026-06-26):GradeAppealStatusEnum/CreateGradeAppealSchema/ReviewGradeAppealSchema + 导出 CreateGradeAppealInput/ReviewGradeAppealInput 类型) |
lib/grade-utils.ts |
20 | 公共工具函数(toNumber/normalize;P3-26:buildScopeClassFilter 迁移至 scope-filter.ts) |
lib/scope-filter.ts |
56 | DB 行级权限过滤(buildScopeClassFilter;P3-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 获取通知标题和内容(i18n);P2-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):成绩报告卡数据访问层。getReportCardData(cached)聚合学生成绩+班级信息+班主任+学年+科目排名+总体统计,computeSubjectRank 计算单科排名。三层 scope 校验:owned(学生自己)/children(家长子女)/class_taught(教师班级学生)。静态导入 data-access(getStudentRankInClass/getClassNamesByIds)+ stats-service(computeAverageScore) |
lib/type-guards.ts |
25 | 类型守卫纯函数(isGradeType/isSemester/isGradeDraftData;P2-9 修复(2026-06-25):isGradeType/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/SchoolWideGradeSummary;2026-06-24 新增:GradeRecordAnswer/BatchGradeEntryByExamQuestion/BatchGradeEntryByExamItem) |
components/widget-boundary.tsx |
136 | Widget 边界组件(P1-5 新增,v2-P1-1 已在 3 个页面应用;P1-9(2026-06-25):teacher/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 修复:a11y;v2-P1-4:i18n;P3 修复:NaN 日期检查 + fullScore > 0 守卫) |
components/grade-distribution-chart.tsx |
100 | 分数分布图(v2-P1-4:i18n) |
components/subject-comparison-chart.tsx |
62 | 科目对比图(v2-P1-4:i18n) |
components/class-comparison-chart.tsx |
194 | 班级对比图(v2-P1-4:i18n;v3-P3-5 新增:显著性分析区域,基于极差和样本量的经验规则判断班级间差异,含可折叠详细分析) |
components/grade-trend-chart.tsx |
59 | 趋势图(v2-P1-4:i18n) |
components/grade-record-form.tsx |
177 | 录入表单(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;P3 修复:safeActionCall 包装提交) |
components/batch-grade-entry.tsx |
376 | 2026-06-24 完全重写:导出名改为 BatchGradeEntryByExam,按试卷录入每题得分。Excel 式表格(行=学生,列=题目,末列=总分自动计算)。交互:试卷选择器(按 scope 过滤)→ 班级选择器(按试卷 gradeId 过滤);多行多列 Excel 粘贴(Tab 分隔);Enter 跳下一行同一列;Tab 跳下一格;实时统计(已录入/总数/均分/最高/最低);分数校验(超过题目满分标红);撤销机制(sessionStorage 5 分钟有效)。P1-6/P1-7 重构(2026-06-25):拆分为 3 个子组件 + 1 个 Hook(BatchGradeEntryTable + BatchGradeEntryStats + BatchGradeEntryDialog + useBatchGradeEntryUndo);Phase 4.6 重构(2026-07-06):进一步拆出 BatchGradeEntrySelectors(试卷/班级选择器含未选态)+ BatchGradeEntryToolbar(保存/清除按钮+粘贴提示),主组件从 457 行降至 376 行,容器仅保留状态编排与 Server Action 调用。撤销数据使用 isUndoData 类型守卫替代 as { ids: string[]; timestamp: number } 断言;window.confirm() 已替换为声明式 AlertDialog。Props: exams/classes/classGradeMap/exam/students/defaultExamId?/defaultClassId? |
components/batch-grade-entry-selectors.tsx |
124 | Phase 4.6 新增(2026-07-06):批量录入试卷/班级选择器子组件(含未选试卷态引导)。从 batch-grade-entry.tsx 拆分,承接试卷选择器、班级选择器、试卷信息徽章三个区块的渲染 |
components/batch-grade-entry-toolbar.tsx |
53 | Phase 4.6 新增(2026-07-06):批量录入底部工具栏子组件(清除按钮 + 保存按钮 + 粘贴提示)。从 batch-grade-entry.tsx 拆分 |
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/student-grade-summary.tsx |
107 | 学生成绩摘要(v2-P1-4:i18n) |
components/export-button.tsx |
79 | 导出按钮(v2-P1-4:i18n;P3 修复:safeActionCall 包装导出操作;P2-2(2026-06-25):handleExport 补全 : Promise 返回值类型) |
components/stats-class-selector.tsx |
40 | 统计班级选择器(v2-P1-4:i18n) |
components/class-grade-report.tsx |
130 | 班级成绩报告(v2-P1-4:i18n;Phase 4.6 重构(2026-07-06):内联原 GradeStatsCard 实现,使用 StatsGrid + 8 个 StatCard(average/median/max/min/stdDev/passRate/excellentRate/count),含 count=0 空态 Card) |
components/grade-record-list.tsx |
232 | 成绩记录列表(v2-P1-4:i18n;P3 修复:safeActionCall 包装删除操作;v4-P1-7:使用 ScoreCell;v4-P1-10:overflow-x-auto;v3-P3-2 新增:多选复选框 + 全选 + 批量删除工具栏 + 批量删除确认对话框;Phase 4.6 重构(2026-07-06):行渲染(桌面表格+移动端卡片)拆分至 GradeRecordRow,编辑/删除/批量删除对话框拆分至 GradeRecordDetail,主组件从 523 行降至 232 行) |
components/grade-record-row.tsx |
175 | Phase 4.6 新增(2026-07-06):成绩记录行渲染子组件,承接桌面端表格行 + 移动端卡片堆叠视图,含 v3-P3-2 多选复选框 + 全选 + v4-P1-7 ScoreCell 颜色编码 |
components/grade-record-detail.tsx |
215 | Phase 4.6 新增(2026-07-06):成绩记录详情/编辑/删除对话框集合,含编辑表单、单条删除确认、批量删除确认三个对话框。导出 EditableFields 类型与 toEditableFields 工具函数 |
components/report-card-view.tsx |
323 | P3-1 新增(2026-06-26):A4 打印格式成绩报告卡服务端组件。含表头(学校名+标题+学期)、学生信息网格、科目成绩表格(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-26):Excel 批量导入成绩对话框(3 步流程:下载模板→填写参数→上传文件)。展示导入结果(成功/失败计数、未匹配学生、无效行表格前 20 条)。使用 safeActionCall 包装 Action 调用 |
components/draft-lock-banner.tsx |
75 | P3-6 新增(2026-06-26):协同录入锁状态提示条。三种状态:acquiring(loading)/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-05,Phase 4.5 — grade-record-list.tsx 编辑保存改用 useOptimistic + useTransition):
- grade-record-list.tsx:编辑保存
isSavinguseState →useOptimistic<GradeRecordListItem[], GradeRecordListItem>+useTransition。handleEditSave构造optimisticRecord在startSaveTransition内addOptimisticRecord+await updateGradeRecordAction+router.refresh();两处records.map改为optimisticRecords.map(桌面表格 + 移动端卡片视图)即时显示乐观记录- 关键决策:成功后调用
router.refresh()同步数据源,让useOptimistic回滚到最新服务端值;不为删除操作使用 useOptimistic,因为删除需要服务端确认(外键约束、关联记录检查等)
架构变更(2026-07-06,Phase 4.6 — grades 模块组件化重构闭环):
- batch-grade-entry.tsx(457→376 行):拆出
BatchGradeEntrySelectors(试卷/班级选择器含未选态)+BatchGradeEntryToolbar(保存/清除按钮+粘贴提示),容器仅保留状态编排与 Server Action 调用- grade-record-list.tsx(523→232 行):拆出
GradeRecordRow(行渲染:桌面表格+移动端卡片)+GradeRecordDetail(编辑/单条删除/批量删除对话框集合)- 删除 4 个重复组件:
grade-stats-card.tsx(迁移至StatsGrid,由 class-grade-report.tsx 直接使用)/grade-filters.tsx(迁移至调用方student/grades/student-grade-filters.tsx,复用FilterBar+FilterSearchInput)/grade-query-filters.tsx(迁移至调用方teacher/grades/teacher-grade-query-filters.tsx,复用FilterBar+ Select)/analytics-filters.tsx(迁移至调用方teacher/grades/analytics/analytics-grade-filters.tsx,复用FilterBar+ChipNav)
2.7 classes(班级模块)— 耦合最严重
职责:班级 CRUD + 学生/教师管理 + 邀请码注册。
导出函数:
- Actions:
createTeacherClassAction/updateTeacherClassAction/deleteTeacherClassAction/createAdminClassAction/updateAdminClassAction/deleteAdminClassAction/createGradeClassAction/updateGradeClassAction/deleteGradeClassAction - Data-access:
getAdminClasses/getTeacherClasses/getGradeManagedClasses/getStudentClasses/getClassDetails/getClassStudents/getClassSchedule/getClassHomeworkInsights/getGradeHomeworkInsights/getStudentsSubjectScores/verifyTeacherOwnsClass/getTeacherIdsByClassIds(获取多个班级的所有教师 ID:班主任 + 任课教师,跨模块接口,供 messaging 模块调用)/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.ts,classes 模块仅保留 classSchedule 读函数;✅ P2 已修复:getAccessibleClassIdsForTeacher使用Promise.all并行化 ownedIds 与 assignedIds 查询) - Schema:
CreateTeacherClassSchema/UpdateTeacherClassSchema/DeleteTeacherClassSchema/CreateAdminClassSchema/UpdateAdminClassSchema/DeleteAdminClassSchema/CreateGradeClassSchema/UpdateGradeClassSchema/DeleteGradeClassSchema/CreateClassScheduleItemSchema/UpdateClassScheduleItemSchema/DeleteClassScheduleItemSchema/EnrollStudentByEmailSchema - Components:
AdminClassesClient/GradeClassesClient(P1-D 重构:降至 ≤160 行,组合使用共享组件)/ClassListTable(admin/grade 共享班级列表表格,P1-D 抽取)/ClassFormDialog(admin/grade 共享创建/编辑表单弹窗,schools可选控制学校选择显隐,P1-D 抽取)/ClassDeleteDialog(admin/grade 共享删除确认弹窗,基于 ConfirmDeleteDialog,P1-D 抽取)/ClassListToolbar(admin/grade 共享工具栏:新建按钮+计数徽章,P1-D 抽取)/class-form-utils(formatSubjectTeachers工具函数 +ClassFormGrade/SubjectTeacherState类型,P1-D 抽取)/ClassErrorBoundary(P1-C 新增:class component Error Boundary + i18n + router.refresh 重试)/ClassListSkeleton/ClassCardSkeleton/ClassGridSkeleton/StudentListSkeleton/ScheduleGridSkeleton(P1-C 新增:5 个骨架屏组件)/MyClassesGrid(P0-A 修复:已接入 i18n)/ScheduleView(✅ P2 修复:已拆分出ScheduleCreateDialog/ScheduleEditDialog/ScheduleDeleteDialog3 个对话框子组件,主组件从 464 行降至 175 行;不再持有 isWorking/handleCreate/handleUpdate/handleDelete 状态,下沉到子对话框;WEEKDAYS 常量改用从./schedule-utils导入的SCHEDULE_WEEKDAYS)/ScheduleCreateDialog(P2 修复新增:课表新增对话框,从 schedule-view 拆分)/ScheduleEditDialog(P2 修复新增:课表编辑对话框,从 schedule-view 拆分)/ScheduleDeleteDialog(P2 修复新增:课表删除确认对话框,从 schedule-view 拆分)/ScheduleFilters/StudentsTable/StudentsFilters(P0-A 修复:已接入 i18n)/ClassHeader/ClassOverviewStats/ClassQuickActions/ClassScheduleWidget/ClassAssignmentsWidget/ClassStudentsWidget/ClassTrendsWidget/EditClassDialog(P0-A 修复:class-detail 下 7 个子组件 + 编辑对话框已接入 i18n) - 工具函数(components/schedule-utils.ts,2026-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 常量) - Hooks:
useClassData(admin/grade 共享对话框+表单状态管理+重置逻辑,P1-E 抽取)/useClassFilters(按学校筛选年级,admin 模式使用,P1-E 抽取)
依赖关系:
- 依赖:
shared/*、@/auth、school(✅ P1-1 已修复:通过 school data-access.isGradeHead/isGradeManager/findGradeIdByHeadAndName)、homework(✅ P0-7 已修复:通过homework/data-access-classes暴露的函数获取作业数据,不再直查 homework/exams 表) - 被依赖:
exams/homework/grades/attendance/scheduling/dashboard(通过 data-access,P0-4 已修复)/parent/course-plans/users(✅ P1-1 已修复:8+ 处直查 classes 表改为通过 classes data-access)/messaging(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId,支持学生/家长给班级教师发消息)
已知问题:
- ✅ P0-1 已修复:
data-access.ts已拆分为 5 个文件(data-access/data-access-stats/data-access-schedule/data-access-students/data-access-admin),所有文件均 ≤800 行 - ✅ P0-5 已修复:classSchedule 写函数(createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem)已迁移至 scheduling/data-access-class-schedule.ts,classes 模块仅保留 classSchedule 读函数(getStudentSchedule/getClassSchedule);新增 verifyTeacherOwnsClass 供 scheduling 模块跨模块校验教师班级归属
- ✅ P0-7 已修复:
data-access-stats.ts和data-access-students.ts不再直查homeworkAssignmentQuestions/homeworkAssignmentTargets/homeworkAssignments/homeworkSubmissions/exams表,改为调用homework/data-access-classes.ts暴露的函数(getAssignmentIdsForStudents/getHomeworkAssignmentsWithSubject/getHomeworkAssignmentsByIds/getAssignmentMaxScoreById/getAssignmentTargetCounts/getHomeworkSubmissionsForStudents/getPublishedHomeworkAssignmentsWithSubject/getHomeworkSubmissionsForAssignments) - ✅ P1-1 已修复:
改为调用actions.ts直查grades表做权限校验school/data-access函数 - ✅ P1-1 已修复:
改为通过getSessionTeacherId在 data-access 调用auth()shared/lib/auth-guard.getAuthContext()获取 - ✅ P2 已修复:
data-access.ts中idByName.get(name)!非空断言清理为flatMap安全过滤;data-access-admin.ts中同类非空断言清理 - ✅ P0-3 修复(2026-06-22):
拆分为 6 个文件(actions-teacher/actions-admin/actions-grade/actions-invitations/actions-schedule/actions-shared),原actions.ts974 行接近 1000 行硬上限actions.ts改为 50 行 barrel re-export - ✅ P1-1 修复(2026-06-22):
改为ctx.roles.includes("admin"/"teacher"/"student")角色硬编码hasAdminScope(ctx)/hasTeacherScope(ctx)/hasStudentScope(ctx)基于dataScope.type判断 - ✅ P1-4 修复(2026-06-22):
types.ts中 ClassHomeworkInsights 等跨领域类型保留在 classes 模块(因为是 classes 对 homework 数据的视图),添加注释说明归属决策 - ✅ P1-D 修复(2026-06-24):~
拆分为组合模式:抽取admin-classes-view.tsx533 行与grade-classes-view.tsx439 行80% 代码重复ClassListTable/ClassFormDialog/ClassDeleteDialog/ClassListToolbar4 个共享组件 +class-form-utils(formatSubjectTeachers工具函数 +ClassFormGrade/SubjectTeacherState类型);admin-classes-view 降至 159 行,grade-classes-view 降至 153 行;admin 删除弹窗统一改用ConfirmDeleteDialog(与 grade 一致) - ✅ P1-E 修复(2026-06-24):抽取
useClassDatahook(对话框开关+表单字段+提交中标记+重置逻辑,admin/grade 共享)+useClassFiltershook(按学校筛选年级,admin 模式使用) - ✅ P0-A 修复(2026-06-24):
classes 模块 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-24):
classes.json 仅 55 行扩充至 ~370 行,覆盖 invitation/class/list/form/delete/filters/myClasses/students/schedule/detail(header/overview/trends/assignments/widgets/edit/empty)/grade/bulk/errors 全部场景;en 镜像同步 - ✅ P1-C 修复(2026-06-24):新增
class-error-boundary.tsx(class component Error Boundary + i18n + router.refresh 重试)和class-skeleton.tsx(ClassListSkeleton/ClassCardSkeleton/ClassGridSkeleton/StudentListSkeleton/ScheduleGridSkeleton);6 个页面均已包裹 Error Boundary:admin/school/classes + teacher/classes/my + teacher/classes/my/[id] + teacher/classes/schedule + teacher/classes/students(ClassErrorBoundary)、management/grade/dashboard(SchoolErrorBoundary);schedule/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+verifyTeacherOwnsClass),create/update/delete 三个 Action 增加班级归属校验,防止教师通过 scheduleId 修改他人班级课表;actions-teacher.ts的updateTeacherClassAction/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直接 import;actions-invitations.ts的assertClassOwnership签名变更:新增t参数;actions-shared.ts的toWeekday/parseSubjectTeachers改为 async,使用ValidationError代替throw new Error - DEFAULT_CLASS_SUBJECTS 回退逻辑:
data-access.ts的getClassSubjects()新增回退逻辑——subjects 表为空时返回DEFAULT_CLASS_SUBJECTS;data-access-teacher.ts/data-access-admin.ts中inArray(subjects.name, DEFAULT_CLASS_SUBJECTS)改为inArray(subjects.name, await getClassSubjects()),确保筛选条件与回退数据源一致 - DB 错误处理修正:
data-access-teacher.ts的getTeacherClasses()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/timeToMinutes3 个纯函数(修复中文科目颜色分组失效 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-intlgetTranslations生成页面标题):teacher/classes/my/page.tsx、teacher/classes/my/[id]/page.tsx、teacher/classes/schedule/page.tsx、teacher/classes/students/page.tsx、student/learning/page.tsx、student/learning/courses/page.tsx、student/learning/courses/[classId]/page.tsx、student/schedule/page.tsx
- i18n 翻译键扩充:classes.json 新增
架构变更(2026-07-07,S-03 重复 Helper 提取):
- G3-020 admin 班级列表去重:新增
lib/admin-class-mappers.ts(115 行),抽出buildSubjectsByClassIdMap(subjectTeachers 行 → Map<classId, Map<ClassSubject, TeacherOption|null>>)+composeAdminClassList(classRows + subjectsByClassId → AdminClassListItem[],含 compareClassLike 排序);消除data-access-admin.ts中 145+124 行重复的 subjectsByClassId 构建与 list 排序逻辑,data-access-admin.ts 从 406 行降至 353 行- G3-036 isClassSubject/toClassSubject 去重:从
data-access-admin.ts/data-access-teacher.ts/actions-shared.ts移除本地isClassSubject/toClassSubject,统一从lib/admin-class-mappers.ts导入;actions-shared.ts通过export { isClassSubject } from "./lib/admin-class-mappers"re-export 保持向后兼容- lib/admin-class-mappers.ts 接口:
isClassSubject/toClassSubject/buildSubjectsByClassIdMap/composeAdminClassList4 个函数 +SubjectTeacherRow/AdminClassRow2 个接口(供 data-access-admin 类型标注)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
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 |
353 | 管理员班级管理(管理员班级 CRUD、年级管理班级查询;2026-07-07 S-03:移除 145+124 行重复的 subjectsByClassId 构建与 list 排序,改调 lib/admin-class-mappers) |
lib/admin-class-mappers.ts |
115 | admin 班级列表共享辅助(2026-07-07 S-03 新增):isClassSubject/toClassSubject/buildSubjectsByClassIdMap/composeAdminClassList 4 个函数 + SubjectTeacherRow/AdminClassRow 2 个接口;消除 data-access-admin/data-access-teacher/actions-shared 三处重复定义 |
actions.ts |
50 | Barrel re-export(P0-3 修复:从 974 行拆分为 6 个文件) |
actions-teacher.ts |
100 | 教师班级 CRUD(3 个 Action) |
actions-admin.ts |
120 | 管理员班级 CRUD(3 个 Action) |
actions-grade.ts |
110 | 年级组长班级 CRUD(3 个 Action) |
actions-invitations.ts |
502 | 邀请码与注册 + 批量操作(10 个 Action,P2-4 新增批量导入学生/批量分配教师) |
actions-schedule.ts |
90 | 班级课表 CRUD(3 个 Action) |
actions-shared.ts |
60 | 共享工具(hasAdminScope/hasTeacherScope/hasStudentScope/parseSubjectTeachers/toWeekday) |
schema.ts |
152 | Zod 校验(13 个 schema:教师/管理员/年级班级 CRUD + 课表 CRUD + 邮箱注册) |
types.ts |
201 | 类型定义(含跨领域类型说明注释,P1-4 修复) |
| 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 Boundary(P1-C 新增) |
| components/class-skeleton.tsx | 80 | 班级模块骨架屏组件(5 个:List/Card/Grid/StudentList/ScheduleGrid,P1-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 | 课表 Widget(P0-A 修复:已接入 i18n) |
| components/class-detail/class-assignments-widget.tsx | 100 | 作业 Widget(P0-A 修复:已接入 i18n) |
| components/class-detail/class-students-widget.tsx | 120 | 学生 Widget(P0-A 修复:已接入 i18n) |
| components/class-detail/class-trends-widget.tsx | 150 | 趋势 Widget(P0-A 修复:已接入 i18n) |
| components/class-detail/edit-class-dialog.tsx | 145 | 编辑班级对话框(P0-A 修复:已接入 i18n) |
| hooks/use-class-data.ts | 95 | 对话框+表单状态管理 hook(P1-E 抽取) |
| hooks/use-class-filters.ts | 30 | 按学校筛选年级 hook(P1-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。
导出函数:
- Actions:
createSchoolAction/updateSchoolAction/deleteSchoolAction/createAcademicYearAction/updateAcademicYearAction/deleteAcademicYearAction/createDepartmentAction/updateDepartmentAction/deleteDepartmentAction/createGradeAction/updateGradeAction/deleteGradeAction/promoteGradesAction(编排层:权限校验 + Zod 校验 + 调用 data-access + revalidatePath + after(logAudit);promoteGradesAction年级升级,审计日志grade.promote) - Data-access:只读查询(
getSchools/getGrades/getDepartments/getAcademicYears/getStaffOptions/getGradesForStaff/getGradesByIds(✅ G3-007 / S-03 新增:按 ID 列表批量获取年级,供 lib/school-permissions teacher 分支使用)/getOrgTree/getSubjectOptions/getGradeOptions/getSubjectNameMapByIds(P1-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;✅ G3-012 / F-09:使用 db.transaction 包裹循环 UPDATE) - Data-access 文件结构(✅ G3-007 / S-01 修复:从 938 行单文件拆分为 barrel + 6 个子文件):
data-access.ts(barrel)→data-access-departments.ts/data-access-grades.ts/data-access-subjects.ts/data-access-classrooms.ts/data-access-semesters.ts/data-access-schools.ts - Lib(✅ G3-011 / A-02 新增):
lib/school-permissions.ts包含getSchoolsForUser(userId)/getGradesForUser(userId)角色感知查询(admin/grade_head/teaching_head/teacher 分支),从 data-access 层迁出 - Types(✅ v4-P2-6 新增):
GradeOverviewStats(年级概览统计接口:{ gradeId, classCount, studentCount, teacherCount },位于 data-access-grades.ts) - Components:
SchoolsClient/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/*、@/auth、users(⚠️getStaffOptions直查 users/roles,可接受) - 被依赖:
exams(✅ P1-1 已修复:通过 school data-access)、homework(✅ P1-1 已修复:通过 school data-access)、grades(✅ P1-1 已修复:通过 school data-access)、questions(✅ P1-1 已修复:通过 textbooks data-access)、classes(✅ P1-1 已修复:通过 school data-access)、course-plans(合理)
已知问题:
- ✅ P0-8 已修复:
actions.ts不再直接导入db和 schema,所有 DB 写操作下沉到data-access.ts,符合三层架构 - ✅ P2 已修复:
logAudit()通过 Next.jsafter()异步非阻塞执行 - ✅ G3-010 / A-10 修复(2026-07-07):
改为移除所有 try-catch 错误吞没 + log.error,让错误向上抛出(data-access 层不应吞没错误)data-access.ts中 8 处 catch 块添加console.error输出错误上下文 - ✅ G3-007 / S-01 修复(2026-07-07):
拆分为 6 个按职责划分的子文件 + 1 个 lib(school-permissions),单文件均 < 460 行data-access.ts938 行超过 800 行上限 - ✅ G3-011 / A-02 修复(2026-07-07):
角色判断逻辑(getSchoolsForUser / getGradesForUser)混在 data-access 层迁出至lib/school-permissions.ts,data-access 层只接受显式参数 - ✅ G3-012 / F-09 修复(2026-07-07):
改用promoteGrades循环 UPDATE 无事务db.transaction(async (tx) => {...})包裹整个循环,保证原子性 - ✅ S-03 修复(2026-07-07):抽取
fetchGradesWithHeadshelper,消除 getGrades / getGradesForStaff / getGradesForUser 三处重复的"查询年级+主任用户+映射"代码 - ⚠️ P2:审计日志不一致(仅 school 实体记录,department/academicYear/grade 未记录)
- ⚠️ P2:
getStaffOptions/getGrades直查 users/roles(展示用,可接受) - ✅ P0-2 修复(2026-06-22):
年级 CRUD 逻辑与grade-management模块重复定义grade-management死模块已删除,年级 CRUD 统一由 school 模块负责 - ✅ P0-5 修复(2026-06-22):
全部 4 个组件(schools-view/grades-view/departments-view/academic-year-view)已接入school/components/*4 个组件缺少 i18nuseTranslations("school");school.jsoni18n 文件已创建并扩充 - ✅ P1-3 修复(2026-06-22):新增 school-error-boundary.tsx(class component Error Boundary + i18n + router.refresh 重试)和 school-skeleton.tsx(SchoolListSkeleton 表格骨架 + SchoolCardSkeleton 卡片骨架);4 个页面(schools/grades/departments/academic-year)均已包裹 SchoolErrorBoundary;school.json 补充 errors.boundary.* 翻译键
- ✅ P1-5 修复(2026-06-22):
拆分为组合模式:SchoolListToolbar + SchoolFormDialog + SchoolDeleteDialog + useSchoolData hookschools-view.tsx硬编码 Table+Dialog+AlertDialog - ✅ P1-6 修复(2026-06-22):新增
getSchoolsForUser(userId)/getGradesForUser(userId)权限感知查询函数,根据用户角色返回可见数据范围 - ✅ P2-1 修复(2026-06-22):抽取
use-school-datahook,将对话框状态管理逻辑与 UI 分离 - ✅ P2-2 修复(2026-06-23):新增
getOrgTree()data-access 函数(学校→年级→班级三级树,通过import("@/modules/classes/data-access")动态导入获取班级数据避免循环依赖)+OrgTreeNode类型 +OrgTreeNav组件(三级树形导航,支持搜索过滤/选中高亮/展开折叠/不同节点类型图标);school.json 补充orgTree.*翻译键 - ✅ P2-3 修复(2026-06-23):新增
promoteGradesAction+promoteGrades(schoolId)+promoteGradeName(name)辅助函数(中文数字 一→二…十二、阿拉伯数字 1→2…12 识别),按 order 降序逐条 +1 避免唯一约束冲突;含after(() => logAudit({ action: "grade.promote" }))审计日志 - ✅ P2-4 修复(2026-06-23):新增
bulkEnrollStudentsAction(CSV 批量导入学生,复用 enrollStudentByEmail)+bulkAssignSubjectTeachersAction(CSV 批量分配教师,简化实现含 TODO 待完善查找逻辑);classes/actions.ts barrel 导出 - ✅ P2-5 修复(2026-06-23):为 department/academicYear/grade 的 9 个 CRUD Action 补充
after(() => logAudit(...))审计日志(action: department.create/update/delete、academicYear.create/update/delete、grade.create/update/delete),与 school 实体审计日志策略一致 - ✅ v4-P2-6 改进(2026-06-23):年级管理体验增强——新增
getGradeOverviewStats()data-access 函数(返回GradeOverviewStats[]:每个年级的 classCount/studentCount/teacherCount,动态导入 classes/data-access 避免循环依赖);GradesClient新增gradeStatsprop,渲染年级概览卡片视图(班级/学生/教师数 + 年级主任/教学主任 + 快捷操作入口);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-24):
拆分为组合模式:grades-view.tsx860 行超出组件行数限制GradeFormDialog(创建/编辑表单对话框,含客户端校验+去重+isDirty 检测)+GradeDeleteDialog(删除确认)+GradeListToolbar(搜索/筛选/排序工具栏)+GradeOverviewCards(年级概览卡片视图)+useGradeDatahook(对话框状态管理);主容器grades-view.tsx降至 290 行,参照schools-view.tsx组合模式
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
457 | 13 个 Server Action(编排层,无 DB 直访;含 promoteGradesAction 年级升级) |
data-access.ts |
33 | barrel 重新导出(G3-007 / S-01 修复:从 938 行拆分为 6 个子文件 + 1 个 lib) |
data-access-departments.ts |
65 | 部门 CRUD(G3-007 / S-01 拆分;G3-010 / A-10 移除 try-catch 错误吞没) |
data-access-grades.ts |
452 | 年级 CRUD + promoteGrades(G3-012 / F-09 事务包裹)+ 跨模块年级接口 + 年级概览统计 + fetchGradesWithHeads helper(S-03 抽取)+ getGradesByIds(新增供 school-permissions 使用) |
data-access-subjects.ts |
98 | 科目只读接口(跨模块:getSubjectOptions / getSubjectNameById / getSubjectNameMapByIds) |
data-access-classrooms.ts |
42 | 教室只读接口(跨模块:getClassroomsForScheduling 供排课使用) |
data-access-semesters.ts |
86 | 学年 CRUD(createAcademicYear / updateAcademicYear 已用 db.transaction) |
data-access-schools.ts |
161 | 学校 CRUD + getStaffOptions 员工选项 + getOrgTree 组织架构树 |
lib/school-permissions.ts |
145 | 角色感知查询(G3-011 / A-02 迁出: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 Boundary(P1-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 | 学校数据管理 hook(P2-1 修复) |
| hooks/use-grade-data.ts | 42 | 年级数据管理 hook(P1-A 新增:对话框状态管理,参照 use-school-data 模式) |
2.8b grade-management(年级管理模块)— ✅ 已删除
2026-06-22 审计发现:该模块拥有完整的理想架构(Service 接口 + Context DI + 角色配置 + Error Boundary + Skeleton + i18n + hooks 分离),但 13 个相关页面中无任何一个导入此模块。
management/grade/*页面实际依赖classes和school模块的 data-access。2026-06-22 处置决策(P0-1/P0-2 修复):该死模块已完整删除。年级 CRUD 统一由
school模块负责(school/actions.ts+school/data-access.ts),避免两套重复实现。详见docs/architecture/audit/school-grade-class-audit-report.md。
2.9 scheduling(排课模块)
职责:自动排课算法 + 课表调整 + 排课规则管理。
导出函数:
- Actions:
autoScheduleAction/applyAutoScheduleAction/getSchedulingRulesAction/updateSchedulingRulesAction/getScheduleChangesAction/createScheduleChangeAction/updateScheduleChangeAction/deleteScheduleChangeAction - Data-access:
getSchedulingRules/getScheduleChanges/getAdminClassesForScheduling/getTeachersForScheduling/getClassroomsForScheduling/getClassSubjectsForScheduling - Data-access-class-schedule(✅ P0-5 新增):
createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem(从 classes 模块迁移,含教师班级归属校验,通过classes/data-access.verifyTeacherOwnsClass跨模块校验) - Data-access(低级写入):
insertClassScheduleItem/updateClassScheduleItemById/deleteClassScheduleItemById/replaceClassSchedule(统一 classSchedule DB 写入口) - 算法:
findOptimalSlot/validateSchedule/autoSchedule/buildDefaultTimeSlots(纯函数,标杆)
依赖关系:
- 依赖:
shared/*、@/auth、classes(✅ P0-5 已修复:通过classes/data-access.verifyTeacherOwnsClass/getTeacherIdForMutations校验教师班级归属,不再直写 classSchedule 表的写入口分散在 classes 模块)、school(⚠️ 排课辅助查询,可接受)、users(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds) - 被依赖:
classes/actions.ts(✅ P0-5 已修复:通过scheduling/data-access-class-schedule调用写函数)
已知问题:
- ✅ P0-5 已修复:
改为调用applyAutoScheduleAction直接 transaction 写classSchedule表(第三个写入口)replaceClassSchedule统一写入口;classSchedule 所有写函数统一在 scheduling 模块 - ✅ P1-1 已修复:
改为调用autoScheduleAction直查users表users/data-access.getUserNamesByIds - ⚠️ P2:
actions.ts末尾 re-export data-access 函数(反模式) - ✅ P2 已修复:
data-access.ts中 3 处非空断言清理(userIds[0]!、rows[i]!、rows[j]!);auto-scheduler.ts中 2 处非空断言清理(schedule[i]!、schedule[j]!) - ✅
auto-scheduler.ts是算法独立化的最佳实践(纯函数、无 DB、可测试)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
auto-scheduler.ts |
310 | 排课算法(纯函数,标杆) |
actions.ts |
302 | 8 个 Server Action |
data-access.ts |
398 | 排课辅助查询 + 规则/变更 CRUD + classSchedule 低级写入(insert/update/delete/replace) |
data-access-class-schedule.ts |
165 | classSchedule 业务写入(P0-5 新增,从 classes 模块迁移,含教师归属校验;A-02 修复:字段校验已抽离至 lib/schedule-validation.ts,仅保留 DB 归属校验 + CRUD) |
lib/schedule-validation.ts |
173 | 课表项纯校验函数(A-02 新增:isTimeHHMM/isValidWeekday/isTimeRangeValid/normalizeLocation/normalizeAndValidateCreateInput/buildValidatedScheduleUpdate) |
schema.ts |
- | Zod 校验 |
types.ts |
- | 类型定义(含 P0-5 迁移的 CreateClassScheduleItemInput / UpdateClassScheduleItemInput) |
2.10 attendance(考勤模块)— 结构典范
职责:考勤记录管理 + 统计分析 + 规则配置。✅ L-6 节次考勤:支持按节次(早读/上午/下午/晚自习/全日)记录与过滤。✅ L-9 关联分析: 考勤与成绩跨模块关联分析(Pearson 相关系数 + 风险分级)。
导出函数:
- Actions(5 个写 Action):
recordAttendanceAction(✅ L-6:formData 读取 period)/batchRecordAttendanceAction(✅ L-2:保存成功后调用notifyParentsOfAbsence异步通知家长,失败不阻断主流程;✅ L-6:每条记录携带 period)/updateAttendanceAction(✅ L-6:formData 读取 period)/deleteAttendanceAction/saveAttendanceRulesAction(✅ 审计修复:均调用requirePermission(ATTENDANCE_MANAGE)+ 资源归属校验 +trackEvent埋点) - Data-access:
getAttendanceRecords(✅ 审计修复:移除跨模块 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_day;被leave-requests/actions.reviewLeaveRequestAction调用) - Data-access-stats:
getStudentAttendanceSummary(✅ 审计修复:SQL 聚合 + 委托 data-access;✅ L-6:recentRecords 映射 period)/getClassAttendanceStats(✅ 审计修复:SQL 聚合 + 委托 data-access;✅ L-6:studentRecords 映射 period)/computeStats(✅ 纯函数导出,可单测) - Notifications(✅ L-2 新增):
notifyParentsOfAbsence(批量考勤录入后多渠道通知家长:in_app/sms/email/wechat,一次性批量查询 parent-student 映射避免 N+1) - Import-export:
exportAttendanceRecordsToExcel(✅ 审计修复:类型守卫 + i18n 列头) - Services(✅ 审计新增 P1-12/P2-6):
AttendanceDataService接口 /AttendanceReadService/AttendanceWriteService/AttendanceRepository/AttendanceProvider/useAttendanceService/createAttendanceDataService/createAttendanceReadService - Components:
AttendanceSheet(✅ 审计修复: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)、@/auth、classes(✅ 审计修复:通过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 节次考勤数据模型:
- DB:
attendanceRecords.periodenum('morning_reading','morning','afternoon','evening','full_day') nullable(向后兼容 NULL 视为 full_day) - 索引:
classDatePeriodIdx(class_id + date + period 复合索引) - 迁移:
drizzle/0021_attendance_period.sql - 类型:
AttendancePeriod(types.ts),AttendanceRecord/AttendanceListItem/AttendanceQueryParams新增 period 字段 - Schema:
AttendancePeriodEnum(Zod),RecordAttendanceSchema默认 full_day,UpdateAttendanceSchema可选 period - i18n:
attendance.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;canAccessCorrelationAnalysisscope 校验 - 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 已修复:
safeParseDatefieldName 改为中性 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.ts(re-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) |
lib/type-guards.ts |
- | ✅ as 断言修复新增(2026-07-07):isAttendancePeriod 守卫,替代 components/attendance-sheet.tsx 中 Select onValueChange 的 as AttendancePeriod 断言 |
架构变更(2026-07-05,Phase 4.5 — attendance-sheet.tsx 提交状态改用 useOptimistic):
- attendance-sheet.tsx:
isSubmittinguseState →useOptimistic<boolean>。保留<form action={handleSubmit}>模式 +useFormStatusSubmitButton(progressive enhancement);setOptimisticSubmitting(true)在 form action 内调用,action 完成后optimisticSubmitting自动恢复 false,移除finally块- 关键决策:保留 form action 模式而非改用
useTransition包裹,因为useFormStatus().pending依赖 form action 的 promise 状态,若用startTransition包裹会导致 form action 立即返回、useFormStatus不可靠;useOptimistic的addOptimistic必须在 transition 或 action 内调用(form action 也算 action)
2.11 users(用户模块)
职责:用户资料管理 + 批量导入导出 + 管理员用户列表管理。
导出函数:
- Actions:
getUserProfileAction/updateUserProfileAction/importUsersAction/exportUsersAction/downloadUserTemplateAction/updateUserRoleAction/deleteUserAction - Data-access:
getUserProfile/getCurrentStudentUser(✅ P2-20 已修复:从 homework 模块迁移而来,6 个 student 页面通过此函数获取学生身份,不再依赖 homework 模块)/getAdminUsers(管理员用户列表分页查询,支持搜索+角色聚合)/getAdminUserRoles(角色名列表,用于筛选下拉框)/getAllUserIds(limit=1000, offset=0)(✅ P3-7:全校用户 ID 查询,默认 LIMIT 1000 防止 OOM + 分页参数支持)/getUserIdsByGradeId/getUserNamesByIds - Import-export:
generateUserImportTemplate/parseUserImportData/exportUsersToExcel(+ re-exportbatchImportUsers/UserImportResult保持向后兼容) - User-service:
batchImportUsers(用户创建 + 密码哈希 + 角色分配) - Class-registration:
registerStudentByInvitationCode(委托 classes/data-access 完成班级注册) - Components:
UserImportDialog(批量导入对话框)/AdminUsersView(管理员用户列表客户端组件,搜索+筛选+分页+删除)
依赖关系:
- 依赖:
shared/*(含shared/lib/role-utils,✅ P2 已修复:删除本地normalizeRoleName/resolvePrimaryRole/rolePriority,统一复用shared/lib/role-utils.resolvePrimaryRole)、@/auth、classes(✅ P1-4 已修复:通过class-registration.ts调用classes/data-access.enrollStudentByInvitationCode,不再直写 classEnrollments) - 被依赖:
dashboard(通过 data-access,P0-4 已修复)、grades(✅ P1-1 已修复:通过 users data-access)、homework(✅ P1-1 已修复:通过 users data-access)、scheduling(✅ P1-1 已修复:通过 users data-access)、diagnostic(✅ P1-1 已修复:通过 users data-access)、elective(✅ P1-1 已修复:通过 users data-access)、proctoring(✅ P1-1 已修复:通过 users data-access)、parent(✅ P1-1 已修复:通过 users data-access)
已知问题:
- ✅ P1 已解决:
import-export.ts四重职责已拆分为import-export.ts(解析/生成)+user-service.ts(用户创建)+class-registration.ts(班级注册) - ✅ P1 已解决:
batchImportUsers不再跨模块直写classEnrollments,改为调用classes/data-access.enrollStudentByInvitationCode - ✅ P2 已解决:删除本地
normalizeRoleName/resolvePrimaryRole/rolePriority,统一复用shared/lib/role-utils.resolvePrimaryRole,消除重复代码 - ✅ P1-1 已修复:
已下沉到 data-accessupdateUserProfile绕过 data-access 直接 DB 写 - ✅ P2-20 已修复:新增
getCurrentStudentUser函数(从 homework 模块迁移),6 个 student 页面通过此函数获取学生身份,不再依赖 homework 模块 - ✅ P2 已解决:
data-access.ts已扩充写操作(updateUserProfile 已下沉) - ⚠️ 已知限制:
AdminUsersView客户端组件的删除操作通过fetch("/api/admin/users/:id")调用,对应 API 路由尚未实现(deleteUserActionServer Action 已就绪,可作为后续 API 路由的实现基础)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
import-export.ts |
157 | 文件解析/生成(模板生成 + 解析校验 + Excel 导出)+ re-export 向后兼容 |
user-service.ts |
82 | 用户创建(批量导入 + 密码哈希 + 角色分配) |
class-registration.ts |
21 | 班级注册(委托 classes/data-access) |
actions.ts |
218 | 7 个 Server Action(profile 更新 + 模板下载/导入/导出 + 角色更新 + 删除) |
data-access.ts |
394 | getUserProfile + 用户查询 + 管理员用户列表分页查询(getAdminUsers/getAdminUserRoles) |
components/admin-users-view.tsx |
290 | 管理员用户列表客户端组件(搜索+筛选+表格+分页+删除对话框) |
2.12 dashboard(仪表盘模块)
职责:管理员/教师/学生/家长仪表盘数据聚合 + 权限校验 + i18n + 纯逻辑工具函数 + 数据服务接口抽象 + 配置驱动 Widget 渲染 + 监控埋点接口 + 时间范围筛选 + 数据对比 + 自定义仪表盘 + 通知集成 + 实时更新 + 数据导出 + 移动端优化。
导出函数:
- Actions:
getAdminDashboardAction/getTeacherDashboardAction/getStudentDashboardAction/getParentDashboardAction(均调用requirePermission()校验对应DASHBOARD_*_READ权限) - Data-access:
getAdminDashboardData(并行调用 6 个模块 stats 函数,V4 添加趋势数据 TODO 注释) - Streams:
getAdminDashboardStreams(V4 新增:返回各分区独立 Promise 供 Reactuse()流式消费) - Lib 纯函数:
toWeekday/countStudentAssignments/sortUpcomingAssignments/filterTodaySchedule(V4 修复:移除as T[]断言,改为非泛型函数)/computeTeacherMetrics/getGreetingKey/timeToMinutes(V4 新增)/getScheduleStatus(V4 新增)/getDueUrgency(V4 新增)/getActionLabelKey(V4 新增)/getActionVariant(V4 新增)/computeComparison(L3 新增:数据对比计算)/toCSV(L7 新增:CSV 导出)/downloadFile(L7 新增:浏览器下载)/exportDashboardCSV(L7 新增:仪表盘 CSV 导出便捷方法) - Services(V4 新增):
DashboardService接口(抽象数据依赖,含 4 个角色方法签名)/DashboardAnalytics接口(监控埋点:Widget 点击 / 空状态 / 错误重试 / 页面停留)/DashboardServiceProvider(React Context 依赖注入)/useDashboardService/useDashboardAnalytics - Config(V4 新增):
ADMIN_WIDGET_CONFIG/TEACHER_WIDGET_CONFIG/STUDENT_WIDGET_CONFIG/PARENT_WIDGET_CONFIG/getWidgetConfig(配置驱动 Widget 布局,新增角色只需修改配置) - Hooks(L4/L6 新增):
useDashboardPreferences(L4 自定义仪表盘偏好,localStorage 持久化)/useDashboardRealtime(L6 SSE 实时更新,指数退避重连) - Components:
AdminDashboardView/TeacherDashboardView/StudentDashboard/ParentDashboard(V4 P1-4 从 parent 模块迁移,使用 slots 组合注入)/UserGrowthChart/DashboardGreetingHeader(共享问候头部,V2 抽象,V4 支持空用户名 fallback)/DashboardSection(分区 Error Boundary + Suspense + 骨架屏,V4 P3-1 新增 ariaLabel prop 支持键盘导航)/DashboardTimeRangeFilter(L2 时间范围筛选)/ComparisonBadge(L3 数据对比徽章)/DashboardNotificationWidget(L5 通知中心 Widget)/DashboardResponsiveLayout(L8 移动端响应式布局)/MobileSwipeContainer(L8 移动端水平滑动)/DesktopGrid(L8 桌面端网格) - Types(V4 新增):
DashboardRole/WidgetSkeletonVariant/DashboardWidgetConfig/DashboardLayoutConfig/ComparisonTrend/ComparisonResult(L3)/TimeRange(L2)/ConnectionStatus(L6)/DashboardNotificationItem(L5)/ExportRow/ExportColumn(L7)
依赖关系:
- 依赖:
shared/*、@/auth、classes(通过 data-access)、homework(通过 data-access)、users(通过 data-access)、parent(通过 data-access.getParentDashboardData + 组件 slots 注入 ChildCard/ParentAttentionBanner)、textbooks/questions/exams(通过各模块 dashboard stats 函数)、recharts、next-intl - 被依赖:无
权限点:
DASHBOARD_ADMIN_READ(admin)DASHBOARD_TEACHER_READ(teacher)DASHBOARD_STUDENT_READ(student)DASHBOARD_PARENT_READ(parent)
已知问题:
- ✅ P0-4 已修复:
getAdminDashboardData改为并行调用各模块 dashboard stats 函数,不再直查跨模块表 - ✅ P0 已修复(2026-06-22):所有仪表盘页面通过
actions.ts调用requirePermission()进行权限校验,不再裸调 data-access - ✅ P0 已修复(2026-06-22):根重定向页
/dashboard改用resolvePermissions()+ 权限点判断,不再role === "xxx"硬编码 - ✅ P0 已修复(2026-06-22):所有仪表盘组件接入 next-intl(
useTranslations/getTranslations),翻译文件messages/{zh-CN,en}/dashboard.json - ✅ P1 已修复(2026-06-22):业务逻辑(weekday 转换、作业统计、教师指标计算、问候语时段)抽取至
lib/dashboard-utils.ts纯函数,与 UI 分离 - ✅ P2 已修复(2026-06-22):新增
components/dashboard-section.tsx,每个独立数据区块用 Error Boundary + Suspense + 骨架屏包裹,单区块崩溃/加载不波及整页(5 种骨架变体:stats/card/chart/table/list) - ℹ️ V1 新增:
AdminDashboardData类型含userGrowth/homeworkTrend字段,data-access.ts当前返回空数组占位(V4 P2-4 添加 TODO 注释),待后续接入真实统计 - ✅ V4 P1-4 已修复:
ParentDashboard组件从modules/parent/components/迁移至modules/dashboard/components/parent-dashboard/,使用 slots 组合注入 ChildCard 和 ParentAttentionBanner,parent 模块仅保留数据访问 - ✅ P0 已修复(2026-06-22 V2):10 个子组件 i18n 遗漏全部补齐,新增 ~50 个翻译键
- ✅ P1 已修复(2026-06-22 V2):抽象共享组件
DashboardGreetingHeader,消除 teacher/student 头部 90% 重复代码 - ✅ P2 已修复(2026-06-22 V2):为 6 个纯函数添加 31 个单元测试
- ✅ P2 已修复(2026-06-22 V2):a11y 增强 — admin 表格
<caption>、teacher/student 视图语义化标签 - ✅ P0 已修复(2026-06-22 V4):
filterTodaySchedule移除as T[]类型断言,改为非泛型函数 - ✅ P1 已修复(2026-06-22 V4):5 个组件内嵌纯函数抽取至
lib/dashboard-utils.ts - ✅ P1 已修复(2026-06-22 V4):
teacherName硬编码 "Teacher" fallback 改为空字符串 - ✅ P1 已修复(2026-06-22 V4):
teacher-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.ts,4 个角色的 Widget 布局配置,支持配置驱动渲染 - ✅ V4 P2-4 已修复:admin 趋势数据占位添加 TODO 注释,标记后续实现
- ✅ V4 P2-5 已修复:4 个角色 StatCard 使用模式统一(均传 color + valueClassName="tabular-nums")
- ✅ V4 P3-1 已修复:
DashboardSection新增ariaLabelprop,渲染<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 已实施:自定义仪表盘 — 新增
useDashboardPreferencesHook,localStorage 持久化 Widget 显示/隐藏偏好 - ✅ L5 已实施:通知中心集成 — 新增
DashboardNotificationWidget组件 - ✅ L6 已实施:实时更新 — 新增
useDashboardRealtimeHook(SSE + 指数退避重连) - ✅ L7 已实施:数据导出 — 新增
lib/export-utils.ts(CSV 导出 + 浏览器下载) - ✅ 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 新增:自定义仪表盘偏好 Hook(localStorage 持久化) |
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)。
导出函数:
- Actions:
sendMessageAction(✅ 审计 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:使用MessageDraftSchemaZod 校验替代as断言)/deleteMessageDraftAction草稿删除;✅ 审计 V1-P0-4 已删除:getMessageDetailAction(V2-P1-3 重构遗留死代码,详情数据由 RSC 通过getMessageDetailPageData直接获取);✅ 审计 V1-P1-3 新增:getMessageThreadAction获取消息线程(替代组件直接调用 data-access,统一权限边界)) - Data-access:
getMessages(✅ 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-3:getMessagesPageData已删除,编排迁回 messages/page.tsx 页面层Promise.all调用 messaging + notifications data-access,保持模块独立性)/getMessageDrafts/createMessageDraft/updateMessageDraft/deleteMessageDraft/getMessageDraftById(✅ V2-P2-13c 新增:消息草稿 CRUD,操作 message_drafts 表) - Hooks:
useMessageSearch(✅ P1-7 新增:消息搜索 hook,含防抖和请求竞态取消) - Lib:
buildReplyHref(✅ 审计 V1-P1-7 新增:构建回复消息 URL 纯函数,使用 URLSearchParams 安全编码,替代组件内字符串拼接) - Notification-preferences:
re-export shim(实际逻辑在✅ P0-b 已修复:notifications/preferences.ts)notification-preferences.ts文件已删除(通知模块去重),消费方改为直接从@/modules/notifications/preferences导入getNotificationPreferences/upsertNotificationPreferences
依赖关系:
- 依赖:
shared/*、@/auth、notifications(✅ P0-4 / P1-5 已修复:通过sendNotificationdispatcher 发送通知,通知 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 模块导入)
已知问题:
- ✅ 审计 v1 P0-5 已修复:
拆分为 5 个子文件(data-access.ts1089 行超 1000 行硬限(规则 S-01),混合 CRUD/批量/群组/模板/举报多职责data-access-core.ts/data-access-bulk.ts/data-access-group.ts/data-access-templates.ts/data-access-reports.ts),data-access.ts改为 barrel 文件export * from "./data-access-*"保持向后兼容;同时迁移至shared/lib/date-utils.ts的serializeDate统一日期序列化(规则 S-04 DRY) - ✅ P0-4 已修复:
改为调用sendMessageAction绕过 notifications dispatcher 直接调用createNotificationnotifications.sendNotification,通知 CRUD 已迁移至 notifications 模块 - ✅ P0 已修复:
与 notifications 双向依赖 + 职责重叠通知相关表(messageNotifications / notificationPreferences)所有权已移交 notifications 模块,messaging 仅保留 messages 表 - ✅ P1-5 已修复:
同时管理 3 类数据(messages + messageNotifications + notificationPreferences)仅管理 messages 表,通知相关数据由 notifications 模块管理 - ✅ P1 已修复:
通知相关 Action 使用通知 Action 已迁移至 notifications 模块并改为requireAuth()而非requirePermission()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 已修复:
已删除(通知模块去重),8 个消费方改为直接从notification-preferences.tsre-export shim 文件@/modules/notifications/preferences导入getNotificationPreferences/upsertNotificationPreferences,消除 messaging 模块对通知偏好的冗余 re-export 层 - ✅ P1 已修复:
全模块零 i18n,中英文案硬编码所有组件接入 next-intl(useTranslations("messages")),新增src/shared/i18n/messages/{zh-CN,en}/messages.json翻译字典(title/description/tabs/actions/form/status/meta/notificationType/search/empty/messages/error 共 13 个命名空间;✅ 审计 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-labeli18n 化(替代硬编码 "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.ts,messaging 模块仅保留私信组件 - ✅ 审计 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 缺少"星标" Tab,data-accessTab 类型新增starredOnly筛选不可达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 已修复:
catch 块调用sendMessageAction失败路径无埋点trackEvent({event:"message.send_failed"}),权限拒绝除外避免噪音 - ✅ P1-7 已修复:
消息列表客户端搜索逻辑抽离为useEffect+setTimeout防抖搜索未取消已发出的请求useMessageSearchhook(含防抖 + 请求竞态取消);无分页 UI新增客户端分页 UI(PAGE_SIZE=20,ChevronLeft/ChevronRight 按钮) - ✅ P1-9 已修复:
改为deleteMessage两个独立 UPDATE 无事务db.transaction包裹 senderDeletedAt 和 receiverDeletedAt 更新,保证原子性 - ✅ P2-11 已修复:
发送/删除消息无埋点sendMessageAction/markMessageAsReadAction/deleteMessageAction新增trackEvent埋点(message.sent / message.marked_read / message.deleted) - ✅ V2-P0-2 已修复:
通知标题硬编码sendMessageAction通过getTranslations('messages')生成 i18n 通知标题(notification.messageTitle/messageTitleNoSubject) - ✅ V2-P1-2 已修复:
MessageList 客户端过滤冗余客户端过滤仅在初始数据(type=all)时执行,搜索结果已由服务端按 tab 过滤 - ✅ V2-P1-3 已修复:
消息详情页分散编排新增getMessageDetailPageData编排函数,替代 page.tsx 中after()+getMessageById+markMessageAsRead的分散编排 - ✅ V2-P1-4 已修复:
表单无服务端校验错误展示message-compose.tsx新增fieldErrors状态 +aria-invalid字段级错误展示(receiverId/subject/content) - ✅ V2-P2-1 已修复:
轮询间隔魔法数字unread-message-badge.tsx轮询间隔提取为POLL_INTERVAL_MS常量(60_000ms→ 30_000ms,✅ V2-P3 与通知组件保持一致) - ✅ V2-P2-13c 已实现:消息星标和草稿功能。
messages表新增isStarred字段(接收方可标记重要消息)+messages_receiver_starred_idx复合索引;新增message_drafts表(userId/receiverId/subject/content/parentMessageId + updatedAt/createdAt,含message_drafts_user_idx和message_drafts_user_updated_idx索引);data-access 新增toggleMessageStar/getMessageDrafts/createMessageDraft/updateMessageDraft/deleteMessageDraft/getMessageDraftById;actions 新增toggleMessageStarAction/getMessageDraftsAction/saveMessageDraftAction/deleteMessageDraftAction;getMessages支持starredOnly筛选;i18n 新增 actions.star/unstar/saveDraft/deleteDraft + messages.draftSaved/draftDeleted/starToggled + empty.noDrafts/noStarred 翻译键;迁移文件drizzle/0008_message_star_draft.sql - ✅ V3-P0-1 已修复:
改为使用saveMessageDraftAction中 5 处formData.get("xxx") as string \| null断言MessageDraftSchemaZod 校验安全转换 FormData 值 - ✅ V3-P1-3 已修复:
已迁移至getNotificationPreferencesAction/updateNotificationPreferencesAction放在 messaging 模块notifications/actions.ts,messaging 仅保留私信 Action - ✅ V3-P1-6 已修复:
改为顶部getMessageDraftsAction和getMessagesPageData使用内联import("./types").XXX类型导入import type语句 - ✅ V3-P2-7 已修复:
改为message-list.tsx中setTab(v as Tab)断言isTab类型守卫
未实施项(P1-5 / P1-8 / P2- 中长期)*:
- ⏳ P1-5:
UnreadMessageBadge接入 SSE 实时推送(需扩展/api/notifications/stream端点 payload) - ✅ P1-8 已实施:
MessageList依赖注入化(定义MessageListService接口通过MessageListServiceProviderContext 注入,default-message-list-service.ts绑定现有 Server Actions) - ✅ P2-1 已实施:批量操作(
bulkMarkMessagesAsRead/bulkDeleteMessages/bulkToggleMessagesStar,使用 inArray + 事务) - ✅ P2-2 已实施:群组消息(fan-out on write 策略,
messages.groupMessageId列 +sendGroupMessagedata-access +sendGroupMessageAction,教师→全班家长,DataScope 校验) - ✅ P2-3 已实施:附件支持(复用
fileAttachments表,targetType="message"+getMessageAttachmentsdata-access +getMessageAttachmentsAction,UI 含MessageAttachmentUploader上传 +MessageAttachmentList展示下载) - ✅ P2-4 已实施:消息撤回(
messages.recalledAt列 +recallMessagedata-access,2 分钟窗口校验,UI 显示"已撤回"占位) - ✅ P2-5 已实施:举报+屏蔽(新增
message_reports表 +user_blocks表,reportMessage/blockUser/unblockUser/isEitherUserBlockeddata-access,reportMessageAction/blockUserAction/unblockUserAction/getUserBlocksAction;sendMessageAction集成isEitherUserBlocked屏蔽校验) - ✅ P2-6 已实施:快捷回复模板(
message_templates表 + CRUD data-access +MessageTemplatePickerUI 组件集成到 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-9:sendMessageAction 增加 receiverId 二次校验 + parentMessageId 校验 + 失败埋点;✅ V3-P1-3:通知偏好 Action 已迁移至 notifications;✅ V3-P0-1:saveMessageDraftAction 使用 Zod 校验;✅ V2-P0-2:通知标题 i18n 化) |
data-access.ts |
- | ✅ 审计 v1 P0-5 拆分为 barrel 文件:export * from "./data-access-*",向后兼容所有现有 import 路径(原单文件 1089 行超 1000 硬限,规则 S-01) |
data-access-core.ts |
- | ✅ 审计 v1 P0-5 新增:helpers(resolveUserNames / toIso via shared util)+ 核心 CRUD(getMessages / getMessageById / getMessageThread / createMessage / markMessageAsRead / deleteMessage / toggleMessageStar / recallMessage / getUnreadMessageCount / getRecipients / isReceiverAllowed / getMessageDetailPageData) |
data-access-bulk.ts |
- | ✅ 审计 v1 P0-5 新增:批量操作(bulkMarkMessagesAsRead / bulkDeleteMessages / bulkToggleMessagesStar) |
data-access-group.ts |
- | ✅ 审计 v1 P0-5 新增:群组消息 fan-out(sendGroupMessage + SendGroupMessageInput / SendGroupMessageResult 接口) |
data-access-templates.ts |
- | ✅ 审计 v1 P0-5 新增:草稿 + 模板 CRUD(getMessageDrafts / createMessageDraft / updateMessageDraft / deleteMessageDraft / getMessageDraftById + getMessageTemplates / createMessageTemplate / updateMessageTemplate / deleteMessageTemplate) |
data-access-reports.ts |
- | ✅ 审计 v1 P0-5 新增:举报 + 屏蔽 + 附件(reportMessage / hasUserReportedMessage / blockUser / unblockUser / isUserBlocked / isEitherUserBlocked / getBlockedUserIds / getMessageReports / getUserBlocks / getMessageAttachments + MESSAGE_ATTACHMENT_TARGET_TYPE) |
schema.ts |
40 | 私信发送校验 + messageId 校验 + 消息草稿校验(✅ V3-P0-1 新增 MessageDraftSchema;✅ V3-P1-3:UpdateNotificationPreferencesSchema 已迁移至 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.ts 与 message-report-block.tsx 中的 as 断言 |
组件清单:
| 组件 | 职责 |
|---|---|
components/message-list.tsx |
消息列表(✅ P1-7:使用 useMessageSearch hook + 客户端分页 UI,PAGE_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-4:fieldErrors + aria-invalid 字段级错误展示;✅ 审计 V1-P0-5:submittedRef 防止提交后自动保存竞态) |
components/message-draft-list.tsx |
✅ 审计 V1-P1-1 新增:草稿列表组件(继续编辑 + 删除草稿 + 乐观更新) |
components/unread-message-badge.tsx |
未读消息计数徽章(侧边栏,每 30 秒轮询 getUnreadMessageCountAction;✅ V2-P2-1:POLL_INTERVAL_MS 常量;✅ V2-P3:间隔从 60s 缩短为 30s 与通知一致) |
客户端行为:
message-list.tsx:客户端调用getMessagesAction搜索消息(useMessageSearch hook,400ms 防抖,请求竞态取消);V2-P1-2 优化:客户端过滤仅在初始数据(type=all)时执行;✅ 审计 V1-P1-2:starred Tab 单独加载getMessagesAction({type:"all", starredOnly:true})unread-message-badge.tsx:每POLL_INTERVAL_MS(30_000ms)轮询getUnreadMessageCountAction刷新未读计数message-detail.tsx:✅ 审计 V1-P1-3 客户端调用getMessageThreadAction(messageId)加载回复链message-compose.tsx:✅ 审计 V1-P0-5submittedRef标志位阻止发送后自动保存 effect 触发新草稿
架构变更(2026-07-05,Phase 4.5 — 消息星标改用 useOptimistic + useTransition):
- message-detail.tsx:星标
useState→useOptimistic<boolean>+useTransition。handleToggleStar在startStarTransition内addOptimisticStarred(nextStarred)+await toggleMessageStarAction+router.refresh()。JSX 中所有isStarred引用改为optimisticIsStarred(Button variant/aria-pressed/Star className/星标徽章)- message-list.tsx:星标
starredOverrideuseState →useOptimistic<Map<string, boolean>>(key=messageId, value=isStarred)。getIsStarred改为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 组件。
导出函数:
- Actions:
sendNotificationAction/sendClassNotificationAction/getNotificationsAction/getUnreadNotificationCountAction/markNotificationAsReadAction/markAllNotificationsAsReadAction/archiveNotificationAction(✅ P1-4 新增:后 4 个通知 CRUD Action 从 messaging 模块迁移;✅ V2-P2-13b 新增:archiveNotificationAction 归档 Action;✅ V3-P1-3 新增:getNotificationPreferencesAction/updateNotificationPreferencesAction从 messaging 模块迁移) - Dispatcher:
sendNotification(payload)/sendBatchNotifications(payloads)(✅ P3-7:sendBatchNotifications重构为 in_app 单次批量 INSERT,其他渠道保持 per-payload 并行) - Data-access:
createNotification/createNotifications(items)(✅ P3-7 新增:批量 INSERT 多行)/getNotifications/markNotificationAsRead/markAllNotificationsAsRead/getUnreadNotificationCount/archiveNotification/unarchiveNotification/getUserContactInfo/logNotificationSend/logNotificationSendBatch(✅ P0-4 / P1-5 修复后从 messaging 迁移;✅ V2-P2-13b 新增:archiveNotification / unarchiveNotification 归档函数) - Preferences:
getNotificationPreferences/upsertNotificationPreferences(✅ P0-4 / P1-5 修复后从 messaging 迁移) - Channels:
InAppChannelSender/SmsChannelSender/EmailChannelSender/WeChatChannelSender - Components:
NotificationList/NotificationDropdown(✅ P1-4 新增:从 messaging/components 迁移;✅ V2-P2-13b:NotificationList 支持优先级 Badge 显示和归档操作;✅ V2-P2-13c:NotificationList 支持按类型筛选) - Hooks:
useNotificationStream(✅ V2-P3 新增:SSE 实时推送 + 轮询降级 Hook)、useDesktopNotifications(✅ V2-P2-13c 新增:浏览器桌面推送 Hook,支持权限管理和新通知桌面提醒) - Types:
NotificationPriority(✅ V2-P2-13b 新增:通知优先级类型 low/normal/high/urgent)
依赖关系:
- 依赖:
shared/*、@/auth、classes(✅ P1-1 已修复:通过 classes data-access.getClassExists/getStudentIdsByClassId) - 被依赖:
messaging(✅ P0-4 / P1-5 已修复:messaging 通过sendNotificationdispatcher 发送通知;✅ P1-4 已修复:通知 UI 组件由 notifications 模块自持,messaging 不再反向依赖)、layout(✅ P1-4 已修复:通知下拉组件直接从 notifications 模块导入)、app/(dashboard)/messages(✅ P1-4 已修复:通知列表组件直接从 notifications 模块导入)
已知问题:
- ✅ P0-4 已修复:
不拥有任何数据,全部依赖 messaging 模块messageNotifications 和 notificationPreferences 表所有权已从 messaging 迁移至 notifications 模块 - ✅ P0 已修复:
与 messaging 双向依赖notifications 不再反向依赖 messaging,in-app-channel 改为静态导入本地 createNotification - ✅ P1-1 已修复:
改为调用sendClassNotificationAction直查classes/classEnrollmentsclasses/data-access.getClassExists/getStudentIdsByClassId - ✅ P1-4 已修复:
通知 UI 组件放在 messaging/components 下,直接 import notifications 类型和 messaging/actionsnotification-list.tsx和notification-dropdown.tsx已迁移至notifications/components/,通知 CRUD Action 已从 messaging 迁移至notifications/actions.ts,消除 UI 层跨模块耦合 - ✅ P2-11 已修复:
通知标记已读无埋点markNotificationAsReadAction/markAllNotificationsAsReadAction新增trackEvent埋点(notification.marked_read / notification.marked_all_read) - ✅ V2-P0-1 已修复:
通知 i18n 键混在 messages.json 中新增独立的notifications.json命名空间(zh-CN/en),通知组件useTranslations从"messages"切换到"notifications";src/i18n/request.ts新增 notifications 命名空间加载 - ✅ V2-P2-1 已修复:
轮询间隔魔法数字notification-dropdown.tsx轮询间隔提取为POLL_INTERVAL_MS常量(30_000ms) - ✅ V2-P3 已优化:
30 秒轮询notification-dropdown.tsx改为 SSE 实时推送(/api/notifications/stream),SSE 不可用时自动降级为轮询(调用 Server Actions,间隔 30 秒) - ✅ V2-P2-13b 新增:通知优先级和归档功能。schema.ts
messageNotifications表新增priority(low/normal/high/urgent,默认 normal)和isArchived(默认 false)字段 + 2 个索引;types.ts 新增NotificationPriority类型;data-access.ts 新增archiveNotification/unarchiveNotification函数,getNotifications支持归档和优先级筛选(默认仅返回未归档),createNotification支持 priority;actions.ts 新增archiveNotificationAction(含 trackEvent 埋点 notification.archived);NotificationList 组件支持优先级 Badge 显示和归档按钮;i18n 新增 priority/actions.archive/messages.archiveFailed 翻译键 - ✅ V2-P2-13c 新增:通知分类筛选 + 浏览器桌面推送。NotificationList 组件新增按类型筛选按钮组(all/message/announcement/homework/grade),空状态区分"无通知"和"无筛选结果";新增
useDesktopNotificationsHook(浏览器 Notification API,支持权限管理、自动请求权限、新通知桌面推送、点击跳转);NotificationDropdown 集成桌面推送(监听新通知并触发桌面提醒,首次加载不批量推送);i18n 新增 filter.all / empty.noFilterResults / empty.noFilterResultsDesc 翻译键 - ⚠️ P1:发送日志仅 console,无
notification_logs表 - ✅ 渠道抽象优秀(接口 + 工厂 + Mock 实现)
- ✅ V3-P1-3 已修复:
通知偏好 Action 放在 messaging 模块getNotificationPreferencesAction/updateNotificationPreferencesAction已迁移至notifications/actions.ts,新增notifications/schema.ts(UpdateNotificationPreferencesSchema从 messaging 迁移) - ✅ V3-P1-4 已修复:
移除sendBatchNotifications重复日志sendBatchNotifications中的重复logNotificationSendBatch调用(日志已在sendNotification内部记录) - ✅ V3-P1-5 已修复:
腾讯云短信新增SmsSdkAppId使用templateCodeSMS_SDK_APP_ID环境变量和smsSdkAppId配置字段,SmsSdkAppId使用独立配置值 - ✅ V3-P2-7 已修复:
改为notification-list.tsx中Object.keys(TYPE_ICON) as NotificationType[]断言TYPE_KEYS常量数组 - ✅ V3-P2-8 已标记:
logNotificationSend中console.info/console.error添加 TODO 注释,预留统一日志服务接入点
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
dispatcher.ts |
~225 | 渠道选择 + 并行分发(✅ V3-P1-4:移除 sendBatchNotifications 重复日志;✅ P3-7:sendBatchNotifications 重构为 in_app 批量 INSERT + 其他渠道并行) |
data-access.ts |
~290 | 站内通知 CRUD + 用户联系方式 + 发送日志持久化(notification_logs 表;P0-4 / P1-5 修复后新增通知 CRUD;✅ V2-P2-13b:新增归档函数 + 优先级/归档筛选;✅ V3-P2-8:console 日志添加 TODO 统一日志服务标记;✅ P3-7:新增 createNotifications 批量 INSERT 函数) |
preferences.ts |
166 | 通知偏好 CRUD(P0-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 实时推送 Hook(EventSource + 轮询降级) |
hooks/use-desktop-notifications.ts |
~100 | ✅ V2-P2-13c 新增:浏览器桌面推送 Hook(Notification API + 权限管理 + 点击跳转) |
lib/type-guards.ts |
- | ✅ as 断言修复新增(2026-07-07):isStringRecord 守卫,替代 channels/wechat-channel.ts 中 metadata.wechatKeywords as Record<string, unknown> 断言 |
组件清单:
| 组件 | 职责 |
|---|---|
components/notification-list.tsx |
通知列表(消息页底部,展示所有通知,支持标记已读;✅ V2-P0-1:useTranslations 命名空间从 "messages" 切换到 "notifications";✅ V2-P2-13b:支持优先级 Badge 显示和归档操作;✅ V2-P2-13c:支持按类型筛选 + 空状态区分"无通知"/"无筛选结果") |
components/notification-dropdown.tsx |
通知下拉菜单(站点头部,✅ V2-P3:改用 SSE 实时推送 /api/notifications/stream + 轮询降级;✅ V2-P0-1:useTranslations 命名空间切换;✅ V2-P2-1:POLL_INTERVAL_MS 常量;✅ V2-P2-13c:集成 useDesktopNotifications 桌面推送,监听新通知并触发桌面提醒) |
客户端行为:
notification-dropdown.tsx:✅ V2-P3 改用useNotificationStreamHook 消费 SSE 实时推送(/api/notifications/stream),SSE 不可用时自动降级为轮询(调用getNotificationsAction+getUnreadNotificationCountAction,间隔 30 秒);✅ V2-P2-13c 集成useDesktopNotificationsHook,通过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 接口抽象)。
导出函数:
- Actions:
getDataChangeLogsAction/exportAuditLogsAction/exportLoginLogsAction/exportDataChangeLogsAction/getAuditRetentionConfigAction/saveAuditRetentionConfigAction(✅ 审计 v1 P0-3:权限点由AUDIT_LOG_READ提升为AUDIT_RETENTION_MANAGE,修复读权限执行写操作的提权漏洞)/purgeAuditLogsAction(✅ 审计 v1 P0-3:权限点由AUDIT_LOG_READ提升为AUDIT_LOG_PURGE,修复读权限执行物理删除的提权漏洞;✅ 审计重构:导出逻辑下沉到export.ts,actions 仅编排;新增trackEvent埋点audit.exported;✅ P2-5 新增保留策略 3 个 Action;✅ v2 修正:保留策略操作 event 改为audit.retention;✅ v2 新增revalidatePath精确刷新缓存) - Data-access:
getAuditLogs/getLoginLogs/getDataChangeLogs/getAuditModuleOptions/getDataChangeStats/getDataChangeTableOptions/getAuditLogsForExport/getLoginLogsForExport/getDataChangeLogsForExport/getAuditOverviewStats/getAuditTrend/getDataChangeActionStats(✅ P2-4 新增概览统计 3 个函数) - Export(✅ 审计重构新增):
buildAuditLogExport/buildLoginLogExport/buildDataChangeLogExport(Excel 列定义 + 行映射纯函数,从 actions 抽取) - Retention(✅ P2-5 新增):
getAuditRetentionConfig/saveAuditRetentionConfig/purgeExpiredAuditLogs/calculateRetentionThreshold/isValidRetentionDays(保留策略配置读写 + 清理 + 纯函数) - Hooks(✅ 审计重构新增):
useLogPagination(分页 URL 状态管理,三处重复逻辑抽取) - Services(✅ P2-8 新增,✅ v2 接线完成):
AuditService接口 +AuditServiceProviderContext +useAuditService/useAuditAnalyticshook +adminAuditService默认实现(v2 改造为 Client-safe:查询方法抛错强制走 RSC props,写操作委托 Server Action)+mockAuditService测试用实现(v2 修正:消除 9 处as断言,为所有方法添加显式返回类型标注)
依赖关系:
- 依赖:
shared/*、@/auth、next-intl(i18n)、shared/lib/track-event(埋点)、settings(保留策略配置存储于system_settings表audit_retention分类) - 被依赖:无
已知问题:
- ✅ 审计 v1 P0-3 已修复:
新增独立权限点purgeAuditLogsAction与saveAuditRetentionConfigAction用读权限AUDIT_LOG_READ执行物理删除/写配置(权限提权漏洞,规则 A-04)AUDIT_LOG_PURGE("audit_log:purge")与AUDIT_RETENTION_MANAGE("audit_retention:manage"),两个 Action 改用对应专属权限点;权限点已全链路同步:permissions.ts常量 →ROLE_PERMISSIONS_SEEDadmin 角色 →permission-bitmap.ts注册 →permission-catalog.tsaudit 分组 → i18nrbac.json(zh-CN/en)翻译键 - ✅ 审计重构 P0 已修复:
Excel 导出逻辑内联在 actions 层已抽取到export.ts(144 行),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 + 骨架屏新增AuditErrorBoundary(class 组件,捕获错误展示 EmptyState + 重试按钮);4 个页面新增loading.tsx(骨架屏)+error.tsx(错误边界) - ✅ 审计重构 P1 已修复:
DataChangeLogTable 281 行过大拆分为data-change-log-table.tsx(146 行,仅表格)+data-change-log-filters.tsx(138 行,独立筛选器)+data-change-log-view.tsx(53 行,视图组合) - ✅ 审计重构 P1 已修复:
三处分页逻辑重复抽取useLogPaginationhook(27 行) - ✅ 审计重构 P1 已修复:
导出无埋点exportAuditLogsAction/exportLoginLogsAction/exportDataChangeLogsAction新增trackEvent埋点(audit.exported) - ✅ 审计重构 P1 已修复:a11y 改进,筛选器添加
aria-label、aria-expanded属性 - ✅ 审计重构 P1 已修复:
api/export/route.ts 中改为 for 循环 + 类型守卫as Record<string, string>类型断言 - ✅ P2 已修复:
data-access.ts中 6 处 catch 块添加console.error输出错误上下文;toIso/clampPageSize/clampPage工具函数补齐显式返回类型 - ✅ P2-1 已修复:
data-access 错误吞没所有 catch 块改为throw error(不再返回空数组),UI 通过 ErrorBoundary 捕获 - ✅ P2-2 已修复:
无日志详情视图新增AuditLogDetailDialog(discriminated union 模式,支持 audit/login/dataChange 三种类型) - ✅ P2-3 已修复:
无用户维度筛选三个筛选器均新增userIdInput + URL 状态管理 - ✅ P2-4 已修复:
无审计概览仪表盘新增/admin/audit-logs/overview页面(统计卡片 + 趋势图 + 分布图 + 快捷入口 + 保留策略配置) - ✅ P2-5 已修复:
无数据保留策略新增retention.ts(配置读写 + 清理纯函数)+ 3 个 Server Action + cron API 路由 +AuditRetentionSettings组件 - ✅ audit-P2-5(auth 审计)已修复:
新增独立login_logs与audit_logs/data_change_logs共用同一保留期(180 天),无法为登录日志配置更长的安全取证保留期loginLogRetentionDays配置项(DEFAULT_LOGIN_LOG_RETENTION_DAYS = 365),AuditRetentionConfig类型新增loginLogRetentionDays字段;getAuditRetentionConfig/saveAuditRetentionConfig读写新配置项;purgeExpiredAuditLogs(retentionDays, loginLogRetentionDays?)支持为 login_logs 传入独立保留期(向后兼容,第二参数可选);purgeAuditLogsAction/AuditService.purgeExpiredAuditLogs接口同步更新;AuditRetentionSettings组件新增登录日志保留天数 Input;新增vercel.json配置 cron schedule0 3 * * *(每日凌晨 3 点触发/api/cron/audit-cleanup);env.mjs新增CRON_SECRET可选环境变量校验 - ✅ P2-6 已修复:
无单测新增retention.test.ts(9 个测试)+export.test.ts(13 个测试),全部通过 - ✅ 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 Action);overview/page.tsx注入AuditServiceProvider;AuditRetentionSettings与AuditLogExportButton改用useAuditService()+useAuditAnalytics()hook - ✅ v2 P0-2 已修复:
为所有方法对象字面量添加显式返回类型标注mock-audit-service.ts使用 9 处as类型断言违规Promise<T>,消除全部as断言 - ✅ v2 P0-3 已修复:
保留策略 Server Action 无revalidatePathsaveAuditRetentionConfigAction与purgeAuditLogsAction末尾新增revalidatePath("/admin/audit-logs/overview")精确刷新缓存 - ✅ v2 P1-1 已修复:
改为trackEventevent 名称与业务不匹配(保留策略操作用了audit.exported)audit.retention(同步更新actions.ts+api/cron/audit-cleanup/route.ts+shared/lib/track-event.tsEventName 联合类型新增audit.retention) - ✅ v2 P1-2 已修复:
改为AuditErrorBoundary使用namespace="common"不符合模块约定namespace="audit"+ 新增error.boundaryTitle/error.boundaryDescription翻译键 - ✅ v2 P1-3 已修复:
AuditLogExportButton与AuditRetentionSettings未接入埋点 hookAuditLogExportButton调用analytics.trackExport(exportType, 0);AuditRetentionSettings调用analytics.trackRetentionConfigChange(config)+analytics.trackPurge(result) - ✅ v2 P1-4 已修复:
改为getAuditModuleOptions/getDataChangeTableOptionscatch 块吞错误返回空数组throw error,UI 通过 ErrorBoundary 捕获 - ✅ v2 P1-5 已修复:
日期筛选器两个 Input 共用改为aria-label={t("table.time")}无法区分起止aria-label={t("filter.startDate")}和aria-label={t("filter.endDate")}(同步更新三个 filters 组件) - ✅ v2 P1-6 已修复:
改为AuditLogDetailDialog的DialogDescription与 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 埋点;✅ 审计 v1 P0-3:purgeAuditLogsAction 改用 AUDIT_LOG_PURGE,saveAuditRetentionConfigAction 改用 AUDIT_RETENTION_MANAGE,修复读权限执行写/删操作的提权漏洞) |
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-logs→requirePermission(AUDIT_LOG_READ)+AuditErrorBoundary包裹/admin/audit-logs/login-logs→requirePermission(AUDIT_LOG_READ)+AuditErrorBoundary包裹/admin/audit-logs/data-changes→requirePermission(AUDIT_LOG_READ)+AuditErrorBoundary包裹/admin/audit-logs/overview→requirePermission(AUDIT_LOG_READ)+AuditErrorBoundary包裹(✅ P2-4 新增)/api/cron/audit-cleanup→ CRON_SECRET 鉴权(✅ P2-5 新增)- ✅ 4 个页面均新增
loading.tsx(骨架屏)+error.tsx(错误边界)
2.16 announcements(公告模块)
职责:公告 CRUD + 发布/归档 + 发布通知。
导出函数:
- Actions:
getAnnouncementsAction/createAnnouncementAction/updateAnnouncementAction/deleteAnnouncementAction/publishAnnouncementAction/archiveAnnouncementAction(✅ P1 已修复:actions 层不再直接访问 DB,全部下沉到 data-access;✅ 发布公告时触发通知模块sendBatchNotifications)/toggleAnnouncementPinAction/markAnnouncementAsReadAction/getAnnouncementReadStatusAction(✅ V2-P2-13d 新增:置顶切换、已读标记、批量已读状态查询;✅ Audit-P0-3:toggleAnnouncementPinAction/markAnnouncementAsReadAction新增资源所有权/可见性二次校验;✅ Audit-P1-1:所有 Action 返回 i18n 化 message;✅ Audit-P1-7:handleActionError集成 console.error + trackEvent("announcement.action_error") + 区分 PermissionDeniedError / FORBIDDEN_ANNOUNCEMENT) - Data-access:
getAnnouncements(✅ Audit-P0-2:audience字段从单值升级为数组{ 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-7:school 分支改为分页循环遍历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-6:onDuplicateKeyUpdate 实现幂等 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/*、@/auth、school(获取年级列表)、classes(获取班级列表 + 解析受众)、users(获取目标用户 ID 列表)、notifications(发布公告时发送通知)、shared/i18n(✅ Audit-P1-1:Actions 通过getTranslations('announcements')返回 i18n 化 message) - 被依赖:无
已知问题:
- ✅ P1-2 已修复:
所有写操作直接在 actions 层写操作已下沉到 data-access(5 个新函数)db.insert/update/delete,未下沉到 data-access - ✅ P2 已修复:
改为getAnnouncementsAction使用requireAuth()而非requirePermission(ANNOUNCEMENT_READ)requirePermission(Permissions.ANNOUNCEMENT_READ) - ✅ 已修复:用户端列表页传入
audience受众过滤(school/grade/class),管理端返回所有公告 - ✅ 已修复:用户端新增公告详情页
/announcements/[id](只读模式) - ✅ 已修复:管理端列表页传递
classes数据给AdminAnnouncementsView - ✅ 已修复:发布公告时(
publishAnnouncementAction/createAnnouncementAction直接发布 /updateAnnouncementAction状态变为 published)触发通知模块sendBatchNotifications - ✅ 已修复:新增
loading.tsx骨架屏(用户端 + 管理端) - ✅ P1 已修复:
全模块零 i18n,中英文案硬编码所有组件接入 next-intl(useTranslations("announcements")),新增src/shared/i18n/messages/{zh-CN,en}/announcements.json翻译字典(title/description/filter/status/type/form/actions/messages/meta/empty/error 共 11 个命名空间);所有页面page.tsx使用generateMetadata+getTranslations替代硬编码 metadata - ✅ P1 已修复:
缺 Error Boundary新增 4 个error.tsx错误边界(/announcements、/announcements/[id]、/admin/announcements、/admin/announcements/[id]),统一使用EmptyState+ i18n 错误文案 + 重试按钮 - ✅ P2 已修复:a11y 改进,
announcement-card.tsx/announcement-detail.tsx添加aria-label - ✅ P1-5 已修复:
页面层新增Promise.all编排 announcements/school/classes 三个模块的 data-accessgetAdminAnnouncementsPageData和getEditAnnouncementPageData编排函数,页面层仅调用单一函数 - ✅ P1-6 已修复:
targetGradeId/targetClassId为 optional,未根据type做条件必填校验CreateAnnouncementSchema和UpdateAnnouncementSchema添加superRefine(refineAudience),年级公告强制targetGradeId,班级公告强制targetClassId - ✅ P2-11 已修复:
发布/归档/删除公告无埋点createAnnouncementAction/updateAnnouncementAction/deleteAnnouncementAction/publishAnnouncementAction/archiveAnnouncementAction新增trackEvent埋点(announcement.created / announcement.updated / announcement.published / announcement.deleted / announcement.archived) - ✅ V2-P0-2 已修复:
通知标题硬编码createAnnouncementAction/updateAnnouncementAction/publishAnnouncementAction通过getTranslations('announcements')生成 i18n 通知标题(notification.publishedTitle/publishedContent) - ✅ V2-P1-1 已修复:
AnnouncementList 客户端 useState/useMemo 过滤改为纯服务端过滤模式,Select 切换仅更新 URL?status=触发 RSC 重新渲染 - ✅ V2-P1-4 已修复:
表单无服务端校验错误展示announcement-form.tsx新增fieldErrors状态 +aria-invalid字段级错误展示(title/content/targetGradeId/targetClassId) - ✅ V2-P2-13d 已实现:公告置顶 + 已读回执。
announcements表新增isPinned字段(置顶优先排序,getAnnouncementsorderBy 改为desc(isPinned), desc(createdAt));新增announcement_reads表(唯一索引 announcementId+userId 保证幂等);新增 3 个 Server Action(toggleAnnouncementPinAction/markAnnouncementAsReadAction/getAnnouncementReadStatusAction)+ 5 个 data-access 函数(toggleAnnouncementPin/markAnnouncementAsRead/isAnnouncementReadByUser/getAnnouncementReadCount/getAnnouncementReadStatusForUser);Announcement类型新增isPinned/isReadByCurrentUser?/readCount?字段;新增AnnouncementRead类型;埋点新增announcement.pin_toggled/announcement.marked_read - ✅ V3-P0-2 已修复:
用户端公告列表页新增resolveAudience50 行业务逻辑在路由层getUserAnnouncementsPageData编排函数(含resolveAudience内部函数),将受众解析逻辑从page.tsx下沉到data-access.ts,页面层仅调用单一函数 - ✅ Audit-P0-1 已修复:
用户端详情页越权访问草稿/他人班级公告新增getAnnouncementByIdForUser,结合status="published"+ 受众过滤(isAnnouncementVisibleToAudience纯函数)+ 已读状态 + readCount 一次返回,markAnnouncementAsReadAction调用此函数做资源可见性二次校验,不可见时抛FORBIDDEN_ANNOUNCEMENT - ✅ Audit-P0-2 已修复:
audience 类型升级为resolveAudience仅取首个 child/class/grade 导致家长多孩子/教师多班级时只看到首个受众公告{ gradeIds: string[]; classIds: string[] },resolveAudience重命名为resolveUserAudience遍历全部;getAnnouncements过滤逻辑改用inArray支持数组 - ✅ Audit-P0-3 已修复:
前者新增资源存在性校验(不存在返回 404);后者调用toggleAnnouncementPinAction/markAnnouncementAsReadAction缺资源所有权二次校验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 已修复:
下沉到 data-accessresolveTargetUserIds业务逻辑位于 actions 层resolveAnnouncementTargetUserIds - ✅ Audit-P1-6 已修复:
原子化toggleAnnouncementPinread-then-write 竞态UPDATE ... SET isPinned = NOT isPinned;改用markAnnouncementAsReadselect-then-insert 双查询onDuplicateKeyUpdate幂等 upsert - ✅ Audit-P1-7 已修复:
重写为接收handleActionError缺日志/埋点/i18nt函数,集成console.error+trackEvent("announcement.action_error")+ 区分PermissionDeniedError/FORBIDDEN_ANNOUNCEMENT/ 其他错误 - ✅ Audit-P1-8 已修复:
表单与 Dialog 行为耦合(保存后路由跳转关闭不了 Dialog)AdminAnnouncementsViewDialog 内表单通过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 已修复:
删除(已由 V3 的AnnouncementList.detailHrefBuilder死 propdetailHrefPrefix替代,本次彻底清理) - ✅ Audit-P2-5 已修复:
两处 loading.tsx 重复骨架屏 JSX抽取AnnouncementListSkeleton共享组件,支持showCreateButtonprop - ✅ Audit-P2-6 已修复:
用户端公告列表无分页 UI新增AnnouncementPagination组件(URL?page=N持久化、a11y、窗口策略、边界处理);新增countAnnouncementsdata-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.ts(18 个测试,覆盖refineAudience条件校验矩阵:school/grade/class 类型与 target 必填组合 + transform 归一化);is-announcement-visible.test.ts(15 个测试,覆盖isAnnouncementVisibleToAudience纯函数,含 P0-2 多孩子/多班级场景);components/announcement-card.test.tsx(16 个测试,覆盖基础渲染、isRead 视觉区分、isPinned 交互、a11y 键盘导航 Enter/Space、toggling 禁用、错误回滚)。同时修复项目级@testing-library/dompeer dependency 缺失(已影响dashboard-section.test.tsx等现有测试)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
~400 | 9 个 Server Action + 通知触发逻辑 + trackEvent 埋点(P1-2 已修复,无直接 DB 操作;V2-P2-13d 新增置顶/已读/批量已读状态 3 个 Action;Audit-P0-3 新增资源所有权二次校验;Audit-P1-1 i18n 化 message;Audit-P1-7 handleActionError 集成日志/埋点/i18n) |
data-access.ts |
~580 | 公告 CRUD + 发布/归档 + 受众过滤 + getAdminAnnouncementsPageData / getEditAnnouncementPageData / getUserAnnouncementsPageData 编排函数 + getAdminAnnouncementDetailPageData(Audit-P1-2 新增)+ countAnnouncements(Audit-P2-6 新增);V2-P2-13d 置顶切换 + 已读回执 5 个函数;Audit-P0-1 新增 getAnnouncementByIdForUser / isAnnouncementVisibleToAudience;Audit-P0-2 resolveAudience → resolveUserAudience 遍历全部受众;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-4:fieldErrors + 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-8:Dialog 内表单通过 onSuccess 关闭弹窗) |
components/announcement-pagination.tsx |
✅ Audit-P2-6 新增:分页导航(URL 持久化、a11y、窗口策略、边界处理) |
2.17 files(文件模块)
职责:文件附件 CRUD + 批量删除 + 统计 + 上传/预览 UI。
导出函数:
- Actions(✅ 2026-06-25 新增
actions.ts编排层,P0 修复):uploadFileAction(FILE_UPLOAD 权限 + Zod 校验 + storageProvider.save + trackEvent + logAudit)/getFileAction(FILE_READ 权限 + ownership 检查:非 FILE_DELETE 持有者只能读自己上传的)/deleteFileAction(FILE_DELETE 权限 + storageProvider.delete)/batchDeleteFilesAction(FILE_DELETE 权限 + Zod 校验 max 100 ids)/getFileListAction(FILE_READ 权限)/getFileStatsAction(FILE_READ 权限) - Data-access:
createFileAttachment/getFileAttachment(cache)/getFileAttachmentsByTarget(cache)/getFileAttachmentsByUploader(cache)/getAllFileAttachments(cache)/deleteFileAttachment/deleteFileAttachments(批量)/getFileAttachmentsWithFilters(cache)/getFileStats(cache)/getFileByUrl(cache,✅ 2026-06-25 新增:按 url 字段查询)/getFileAttachmentsByIds(cache)(✅ P2 已修复:7 个读函数使用React.cache()包装实现请求级 memoization) - Types(✅ 2026-06-25 更新):
FileTargetType新增"user_avatar"枚举值,现为"exam" | "textbook" | "question" | "announcement" | "homework" | "user_avatar",用于用户头像上传场景(targetId = users.id);FileUploadResult/FileAttachment/CreateFileAttachmentInput/FileAttachmentQueryParams/FileStats/BatchDeleteResult - Schema(✅ 2026-06-25 新增
schema.ts):UploadMetadataSchema/BatchDeleteSchema(max 100 ids)/FileListQuerySchema/FileTargetTypeSchema/validateFileSize - Components:
FileUpload/FilePreview/FilePreviewDialog(✅ 2026-06-25 补录)/FileIcon/AdminFilesView - Hooks(✅ 2026-06-25 新增
hooks/目录):useFileUpload(XHR 上传、进度、状态机、校验)/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-24:homework/actions.deleteScanAction调用deleteFileAttachment;homework/components/scan-uploader调用/api/upload,targetType="homework")/settings(✅ 2026-06-25 新增:settings/actions-avatar.ts通过storageProvider抽象删除旧头像文件,targetType="user_avatar";settings/components/avatar-upload.tsx上传头像走/api/upload)
已知问题:
- ✅ P0 已修复:
无新建actions.ts,data-access 被路由直接调用actions.ts编排层,所有 API 路由(upload/files/[id]/files/batch-delete)改为委托 actions,路由层仅保留速率限制 - ✅ P0 已修复:
改为app/api/upload/route.ts仅requireAuth()无权限校验requirePermission(FILE_UPLOAD) - ✅ P0 已修复:
app/api/files/[id]/route.ts横向越权(任意用户可读/删他人文件)getFileAction加入 ownership 检查(非 FILE_DELETE 持有者只能读自己上传的) - ✅ P1 已修复:
i18n 缺失,组件硬编码英文补全en/files.json+zh-CN/files.json(88 行,含 title/upload/list/preview/admin 等命名空间),所有组件接入useTranslations - ✅ P1 已修复:
缺少新增loading.tsx骨架屏和error.tsx错误边界app/(dashboard)/admin/files/loading.tsx+error.tsx - ✅ P1 已修复:
组件逻辑与 UI 耦合,不可单测抽取 3 个自定义 hooks:useFileUpload/useFileBatchOperations/useFilePreview - ✅ P1 已修复:
改用settings/actions-avatar.ts直接 importfs/promises绕过 storageProvider 抽象storageProvider.delete - ✅ P1 已修复:
已加入枚举FileTargetType缺"user_avatar"枚举值导致 settings 头像上传 targetType 未注册 - ✅ P2 已修复:
已删除file-list.tsx125 行死代码 - ✅ P2 已修复:
所有函数 try-catch 吞错误返回空数组/null所有 catch 块已添加console.error输出错误上下文 - ✅ 审计治理已修复(G5-005/G5-006/G5-007/P-05/A-10/F-03):
data-access.ts 12 处移除全部 try-catch 与log.error+ try-catch 吞错误返回 null/[]/false +db.select()SELECT *log.error,错误自然抛出由 actions 层捕获返回ActionState;db.select()改为显式列枚举db.select(fileAttachmentColumns) - ✅ P2 已修复:
getFileAttachmentsWithFilters中or(...)!非空断言清理为安全守卫 - ✅ P2 已修复:
改为显式getFileAttachmentsWithFilters中conditions隐式any[]SQL[]类型标注 - ✅ P2 已修复:
改为admin-files-view.tsx中t(filter.${key}as never)类型断言renderTypeLabel辅助函数 + 字面量联合类型 + switch - ✅ P2 已修复:
已移除file-preview-dialog.tsx中label变量赋值后未使用 - ✅ P2 新增:监控埋点 —
actions.ts接入trackEvent(file.uploaded/upload_failed/deleted/batch_deleted/viewed/action_error)+logAudit - ✅ P2 新增:a11y — 所有图标加
aria-hidden,按钮加aria-label,AdminFilesView用三层WidgetBoundary包裹(StatsSection / UploadSection / ListSection) - ✅ 职责单一,不跨模块查询
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
data-access.ts |
280 | 文件 CRUD + 批量删除 + 统计(11 个函数 + mapRow,审计治理后移除 try-catch/log.error + 显式列枚举) |
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。
导出函数:
- Actions:
getCoursePlansAction/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/exportCoursePlanReport,CSV 导出不依赖 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 新增:导出列类型与标签接口) - Components:
CoursePlanList(✅ P1 修复:使用<Link>+ 类型守卫 + i18n)/CoursePlanDetail(✅ P1 修复:批量选择 +successHrefprop + 埋点;✅ P2-1 增强:SectionErrorBoundary包裹进度条 + 周计划表 + 日历视图;✅ P2-3 增强:textbooksHref/homeworkHrefprops 支持数据联动跳转;✅ P2-6 增强:CSV 导出按钮;✅ P2-7 增强:嵌入CoursePlanCalendar)/CoursePlanForm(✅ P1 修复:类型守卫 +successHrefprop;✅ 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 导出工具)、@/auth、classes(✅ P1-1 已修复:通过动态 importgetClassNamesByIds批量解析,不再直接 JOIN classes 表)、school(✅ P1-1 已修复:通过动态 importgetSubjectNameMapByIds批量解析;getSubjectOptions已移至 school 模块)、users(✅ P1-1 已修复:通过动态 importgetUserNamesByIds批量解析)、@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 — 移除
buildPlanSelect的leftJoin(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 路径推断角色 — 改为
successHrefprop 显式传入 - ✅ P1-6 已修复:Action 入参未验证 —
getCoursePlansAction/getGradeCoursePlanProgressAction/reorderCoursePlanItemsAction/bulkToggleItemsAction/copyCoursePlanAction增加 Zod schema 验证 - ✅ P1-7 已修复:死代码
reorderCoursePlanItems— 已补充对应 Action + UI 拖拽排序(SortableWeekRow+DndContext+SortableContext,PointerSensor + KeyboardSensor 支持 a11y) - ✅ P2-1 已修复:无 Error Boundary —
SectionErrorBoundary包裹进度条 + 周计划表 + 日历视图三个数据区块 - ✅ P2-2 已修复:parent/student 无路由 — 新增 parent/student 课程计划只读路由(按子女班级/学生班级 scope 过滤)
- ✅ P2-3 已修复:无数据联动 — 周计划项章节文本渲染为教材跳转链接,行尾新增作业跳转按钮(通过
textbooksHref/homeworkHrefprops 按角色注入) - ✅ 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(家长模块)
职责:家长视角的子女数据聚合与展示。
导出函数:
- Actions(✅ 审计 v1 P0-4 新增
actions.ts):getChildrenAction/getChildBasicInfoAction/getChildDashboardDataAction/getParentDashboardDataAction/getChildNameListAction/verifyParentChildRelationAction(6 个 Server Action,每个调用requireAuth()+ 内部通过verifyParentChildRelation做亲子关系校验,替代 app/ 页面直接 import data-access) - Data-access:
getChildren/getChildBasicInfo/getChildDashboardData(V3-11 增强:并行调用homework/data-access.getStudentExamResults获取考试结果)/getParentDashboardData/verifyParentChildRelation/getChildNameList(✅ v4 新增:用于详情页头部多子女切换器,一次批量查询避免 N+1) - Types(V3-11 新增):
ChildExamResultItem(单条考试结果:submissionId/examId/examTitle/assignmentId/assignmentTitle/score/maxScore/submittedAt/status);ChildDashboardData扩展examResults: ChildExamResultItem[]字段 - Components:
ParentDashboard/ChildCard/ChildDetailHeader/ChildDetailPanel(V3-11 增强:新增 exams Tab)/SiblingSwitcher/ChildHomeworkSummary/ChildHomeworkDetail(v4 新增)/ChildGradeSummary/ChildGradeDetail(v4 新增)/ChildScheduleCard/ChildExamDetail(V3-11 新增:子女考试详情视图,汇总卡片+考试列表)/ParentChildrenDataPage/ParentNoChildrenPage/ParentAttentionBanner(v4 新增)/ParentAttendanceWarning(v4 新增)/ParentAttendanceRateCard(v4 新增)/ParentAttendanceCalendar(v4 新增)/ParentExportButton(v4 新增)
v4 修复(产品/UX 维度):
- ✅ FEAT-G01:新增
/parent/leave请假申请占位页(含 loading.tsx) - ✅ FEAT-G02:详情页 Schedule Tab 支持完整周课表(新增
weeklySchedule字段 +ChildWeeklyScheduleItem类型 +buildWeeklySchedule函数) - ✅ FEAT-G03:详情页 Grades Tab 新增
ChildGradeDetail按科目分组展示(平均分、趋势、最近成绩) - ✅ FEAT-G04:详情页 Homework Tab 新增
ChildHomeworkDetail展示完整作业信息(状态、截止、提交时间、尝试次数) - ✅ FEAT-G05:考勤页新增
ParentAttendanceWarning异常预警横幅(聚合缺勤/迟到/低出勤率) - ✅ FEAT-G06:详情页底部新增"Contact Teacher"快捷入口
- ✅ FEAT-G07:详情页头部新增
SiblingSwitcher多子女切换器 - ✅ LAYOUT-P01:仪表盘新增
ParentAttentionBanner待办事项横幅 - ✅ LAYOUT-P02:
ChildCard突出 Overdue 异常(红色边框 + AlertTriangle 图标) - ✅ LAYOUT-P03:仪表盘快捷入口改为 4 宫格大图标卡片
- ✅ LAYOUT-P04:详情页改为 Tab 布局(Overview/Homework/Grades/Schedule/Attendance/Diagnostic)
- ✅ LAYOUT-P05:详情页新增面包屑导航
- ✅ LAYOUT-P07:成绩趋势图 X 轴改用序号,避免日期重叠
- ✅ LAYOUT-P08:成绩页新增
ParentExportButton导出按钮(占位) - ✅ LAYOUT-P09:考勤页新增
ParentAttendanceCalendar月历视图(按状态着色,支持按月切换) - ✅ LAYOUT-P10:考勤异常高亮(与 FEAT-G05 同步实现)
- ✅ NAV-P02:Grades/Attendance 页面描述明确职责(多子女对比 vs 单子女详情)
- ✅ NAV-P03:详情页实现
?tab=参数支持 - ✅ NAV-P04:所有 parent 路由新增
loading.tsx骨架屏 +error.tsx错误边界 - ✅ DATA-P02:成绩卡片新增 TrendIcon 进步/退步/持平标识
- ✅ DATA-P03:排名展示新增"Top X%"百分比
- ✅ DATA-P04:作业列表新增科目标识 Badge
- ✅ DATA-P05:作业分数显示新增"pts"单位
- ✅ DATA-P06:考勤页新增
ParentAttendanceRateCard出勤率汇总卡片 - ✅ HABIT-P01:仪表盘"一眼定位异常"能力(AttentionBanner 聚合)
- ✅ HABIT-P02:待办横幅作业项直接跳转详情页 homework tab(1 次点击到达)
- ✅ HABIT-P03:多子女切换无需返回仪表盘
- ✅ HABIT-P06:仪表盘展示未读/待办数量
- ✅ A11Y-P02:Overdue 状态增加 AlertTriangle 图标辅助
- ✅ A11Y-P04:成绩图表容器新增 aria-label
- ✅ PERF-P01/P02:骨架屏 + 错误边界
- ✅ PERF-P03:空状态新增"Contact support"引导按钮
- ✅ PERF-P04:
ChildCardLink 添加prefetch - ✅ MOBILE-P03:移动端子女卡片改为水平滑动 Carousel(snap-x)
- ✅ MOBILE-P04:作业/成绩列表项
min-h-[44px]触摸区域
V3-11 修复(家长端考试详情):
- ✅ V3-11:
parent/types.ts新增ChildExamResultItem类型;ChildDashboardData扩展examResults字段 - ✅ V3-11:
parent/data-access.tsgetChildDashboardData并行调用homework/data-access.getStudentExamResults获取子女考试结果(加入现有Promise.all) - ✅ V3-11:新增组件
ChildExamDetail(考试成绩汇总卡片:已参加考试数/平均分/最高分 + 考试成绩列表:考试标题/分数/得分率 Progress/提交时间) - ✅ V3-11:
child-detail-panel.tsx新增 exams Tab(对标智学网家长端考试详情)
依赖关系:
- 依赖:
shared/*、@/auth、classes(合理)、homework(合理;V3-11 新增getStudentExamResults调用)、grades(合理)、users(合理)、school(合理)、attendance(v4 新增:考勤页复用StudentAttendanceView;⚠️ 跨模块 UI 类型依赖:3 个组件直接 import@/modules/attendance/types) - 被依赖:无
已知问题:
- ✅ 审计 v1 P0-4 已修复:
parent 模块缺失新建actions.ts,3 个页面(children/[studentId]/page.tsx/leave/page.tsx/elective/page.tsx)直接 import@/modules/parent/data-access,绕过 Server Action 权限边界(规则 A-08)src/modules/parent/actions.ts,封装 6 个 Server Action(getChildrenAction/getChildBasicInfoAction/getChildDashboardDataAction/getParentDashboardDataAction/getChildNameListAction/verifyParentChildRelationAction),每个 Action 调用requireAuth()+ 内部verifyParentChildRelation亲子关系校验;3 个页面改为调用对应 Server Action - ⚠️ P1:3 个 parent 组件(
parent-attendance-warning.tsx/parent-attendance-rate-card.tsx/parent-attendance-calendar.tsx)直接 import@/modules/attendance/types,违反模块解耦原则(应通过 data-access 接口或 shared 类型抽象) - ⚠️ P1:parent-attendance-calendar.tsx 重新定义
STATUS_DOT/STATUS_LABEL常量,与 attendance 模块重复 - ⚠️ P1:3 个 parent 组件的纯函数(
buildWarnings/aggregate/rateTone/formatDateKey/parseDateKey/buildCalendarDays/isSameDay)未导出,无法单测 - ✅ P1 已修复:
改为调用app/(dashboard)/parent/children/[studentId]/page.tsx直接访问 DB(违反三层架构)verifyParentChildRelationdata-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):新增
getParentIdsByStudentIdsdata-access 函数,批量查询多个学生的家长 userId(去重),供 diagnostic/grades 模块通知场景调用 - ✅ v4-P1-9 改进(2026-06-23):parent/diagnostic/page.tsx 新增错误卡片展示子女诊断查询失败原因
- ✅ v4-P1-12 改进(2026-06-23):
ParentExportButton接入exportGradesAction,支持按 studentId 导出单个子女成绩;parent/grades/page.tsx 传 studentId 到 ParentExportButton - ⚠️ v4 保留:详情页 Attendance/Diagnostic Tab 为占位提示,待对应功能实现后填充
- ✅ v3-P2 改进(2026-06-23):parent/grades/page.tsx 为每个子女并行查询
getClassAverageTrend,渲染 GradeTrendCard;parent/diagnostic/page.tsx 传入practiceHrefBase={null}隐藏练习按钮 - ✅ 职责单一,正确复用其他模块 data-access
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
- | ✅ 审计 v1 P0-4 新增:6 个 Server Action(getChildrenAction / getChildBasicInfoAction / getChildDashboardDataAction / getParentDashboardDataAction / getChildNameListAction / verifyParentChildRelationAction),每个调用 requireAuth() + verifyParentChildRelation 亲子关系校验,替代 app/ 页面直接 import data-access |
data-access.ts |
243+ | 子女关系 + 仪表盘数据聚合 + 关系校验 + 子女姓名列表(v4 新增 getChildNameList + buildWeeklySchedule;V3-11 getChildDashboardData 并行调用 getStudentExamResults) |
types.ts |
79+ | 类型定义(含 JSDoc,v4 新增 ChildWeeklyScheduleItem;V3-11 新增 ChildExamResultItem + ChildDashboardData.examResults) |
components/parent-dashboard.tsx |
110 | 仪表盘(v4 重构:待办横幅 + 宫格快捷入口 + 移动端水平滑动) |
components/parent-attention-banner.tsx |
128 | v4 新增:待办事项/异常聚合横幅(作业项直接跳转详情页 homework tab) |
components/parent-attendance-warning.tsx |
89 | v4 新增:考勤异常预警 |
components/parent-attendance-rate-card.tsx |
105 | v4 新增:考勤出勤率汇总卡片 |
components/parent-attendance-calendar.tsx |
175 | v4 新增:考勤月历视图(use client) |
components/parent-export-button.tsx |
50 | v4 新增:成绩导出按钮(v4-P1-12 已接入 exportGradesAction,支持按 studentId 导出单个子女成绩) |
components/child-card.tsx |
148 | 子女卡片(v4 增强:异常突出 + 趋势图标) |
components/child-detail-header.tsx |
78 | 详情页头部(v4 增强:面包屑) |
components/child-detail-panel.tsx |
200+ | 详情页 Tab 面板 + SiblingSwitcher(v4 重写,集成 Homework/Grade Detail;V3-11 新增 exams Tab;✅ i18n:硬编码英文提取为 parent.childDetail.* 翻译键,新增 parent.json;修复 P2-4:as 断言改用 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-P2:practiceHrefBase={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(选课模块)
职责:选修课程管理 + 学生选课 + 抽签。
导出函数:
- Actions(11 个):
getElectiveCoursesAction/createElectiveCourseAction/updateElectiveCourseAction/deleteElectiveCourseAction/getStudentSelectionsAction/selectCourseAction/dropCourseAction/runLotteryAction/getAvailableCoursesForStudentAction/openSelectionAction/closeSelectionAction(✅ P2-2:8 个写 Action 错误消息改用getTranslations("elective")国际化) - Data-access:
getElectiveCourses/getElectiveCourseById/createElectiveCourse/updateElectiveCourse/deleteElectiveCourse/openSelection/closeSelection/buildCourseSelect/mapCourseRow/resolveCourseDisplayNames(✅ P2-7:使用 Reactcache()包装 subject/grade options 请求级去重;✅ P2-8:通过resolvers.ts接口抽象跨模块依赖)/CourseCoreRow - Data-access-operations:
selectCourse(✅ P2-9:新增checkScheduleConflict时间冲突检测;✅ P2-10:新增checkCreditLimit学分上限校验;✅ P2-4:学分上限改用getElectiveCreditLimit配置化,按年级覆盖;✅ P2-4:FCFS 录取后触发notifyCapacityThresholdIfNeeded容量阈值通知)/dropCourse(✅ P2-4:新增dropReason参数 +dropDeadline校验)/runLottery/buildLotteryRankCase(⚠️ 未导出,无法单测)/checkScheduleConflict(P2-9 新增)/checkCreditLimit(P2-10 新增,P2-4 重构为配置化) - Data-access-selections:
getCourseSelections/getStudentSelections/getStudentGradeId(✅ P2-8:通过resolvers.tsStudentGradeResolver 接口抽象;✅ P2-4:被selectCourse调用以获取年级用于学分上限)/getAvailableCoursesForStudent/resolveStudentDisplayNames(✅ P2-8:通过resolvers.tsCourseDisplayResolver 接口抽象) - 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(选课名单导出) - Components:
ElectiveCourseList(课程卡片网格 + 管理操作)/ElectiveCourseForm(课程创建/编辑表单)/ElectiveFilters(nuqs 筛选栏)/StudentMySelectionsSection+StudentAvailableCoursesSection(✅ P2-3:由原StudentSelectionView拆分为两个独立客户端组件,支持 Suspense 流式渲染)/ElectivePageLayout(✅ P2-4 新增:页面布局骨架,admin/teacher 选课页复用)/ElectiveStatsCards(✅ P1-13 新增)/ElectiveCourseDetail(✅ P2-2 新增:课程详情复用组件)/ParentSelectionView(✅ P0-2 新增:家长只读视图)
依赖关系:
- 依赖:
shared/*、@/auth、school(✅ P3 已修复:通过 school data-access.getSubjectOptions/getGradeOptions 获取科目/年级名称;✅ P2-8:通过 resolvers.ts 接口抽象)、users(✅ P3 已修复:通过 users data-access.getUserNamesByIds;✅ P2-8:通过 resolvers.ts 接口抽象)、classes(通过 classes data-access.getStudentActiveGradeId;✅ P2-8:通过 resolvers.ts 接口抽象)、next-intl(✅ P2-2:Server Action 错误消息 i18n;✅ P2-4:容量阈值通知文案 i18n)、notifications(✅ P2-4 新增:通过sendNotification发送容量阈值通知给教师/管理员)、systemSettings表(✅ P2-4:读取 elective 学分上限与容量阈值配置) - 被依赖:
app/api/export(✅ P2-11:通过exportElectiveCoursesToExcel/exportCourseSelectionsToExcel)
已知问题(详见 docs/architecture/audit/elective-audit-report.md 与 attendance-elective-audit-report.md):
- ✅ P2-2 已修复:8 个写 Action 错误消息改用
getTranslations("elective")国际化 - ✅ P2-4 已修复:新增
ElectivePageLayout组件,admin/teacher 选课页复用布局 - ✅ P2-7 已修复:
resolveCourseDisplayNames使用 Reactcache()包装 subject/grade options 请求级去重 - ✅ P2-8 已修复:新增
resolvers.ts,跨模块依赖(users/school/classes)通过接口抽象,支持测试注入 mock - ✅ P2-9 已修复:
selectCourse新增时间冲突检测(parseSchedule+isScheduleConflict纯函数 +checkScheduleConflict异步查询) - ✅ P2-10 已修复:
selectCourse新增学分上限校验(checkCreditLimit,MAX_CREDIT_PER_TERM=10) - ✅ P2-11 已修复:新增
export.ts,课程列表/选课名单可导出 Excel - ✅ A-02 已修复:业务逻辑从 data-access 抽离至 lib/——
lib/schedule-conflict.ts(normalizeDay/parseSchedule/isScheduleConflict/hasAnyScheduleConflict 纯函数)、lib/lottery.ts(buildLotteryRankCase/fisherYatesShuffle/partitionLottery 纯函数);data-access-operations.ts 仅保留 DB CRUD + 调用 lib 函数,并通过 re-export 保持向后兼容 - ✅ P0-1 已修复(2026-06-25):教师页面跳转 admin 路由修复,新增
teacher/elective/create与teacher/elective/[id]/edit路由(含权限校验、教师越权防护) - ✅ P0-2 已修复(2026-06-25):新增
parent/elective/page.tsx(含 loading.tsx + error.tsx),使用ParentChildrenDataPage模式 +parentId+studentId双重校验 - ✅ P0-3 已修复(2026-06-25):
admin/elective/page.tsx与student/elective/page.tsx入口添加requirePermission(Permissions.ELECTIVE_READ)纵深防御 - ✅ P0-4 已修复(2026-06-25):
data-access-operations.ts引入ElectiveBusinessError(带 i18n code + params),由actions.ts通过translateBusinessError翻译后再返回 - ✅ P0-5 已修复(2026-06-25):
export.ts移除as类型断言,改用ElectiveCourseStatusEnum.safeParse()类型守卫 - ✅ P1-6 已修复(2026-06-25):
elective-course-form.tsx全量 i18n 化,所有硬编码英文替换为t("form.*")键 - ✅ P1-7 已修复(2026-06-25):
elective-course-list.tsx中<a>替换为 Next.js<Link> - ✅ P1-8 已修复(2026-06-25):3 个
error.tsx文案修正为t("errors.title")/t("errors.description")/t("actions.retry") - ✅ P1-9 已修复(2026-06-25):
export.ts表头全量 i18n 化(statusHeader/priorityHeader/selectedAtHeader/enrolledAtHeader/indexHeader) - ✅ P1-10 已修复(2026-06-25):
getElectiveCourses/getElectiveCourseById移除 try-catch 静默吞错,让异常冒泡到 RSC 触发 error.tsx - ✅ P1-11 已修复(2026-06-25):
parseSchedule重写,支持完整英文星期(Monday/Tuesday 等)+ 中文(周一/星期一)+ 多时段(逗号/分号分隔) - ✅ P1-12 已修复(2026-06-25):
runLottery不再自动将课程置为closed,保留status="open"支持重抽 - ✅ P1-13 已修复(2026-06-25):新增
data-access-stats.ts与ElectiveStatsCards组件,集成到 admin 列表页顶部展示 4 项关键指标 - ✅ P2-1 已修复(2026-06-25):新增
tests/integration/elective/elective-pure-functions.test.ts(35 个单测,覆盖parseSchedule/isScheduleConflict/normalizeDay/buildLotteryRankCase/mapCourseRow) - ✅ P2-2 已修复(2026-06-25):新增
ElectiveCourseDetail复用组件 + admin/student 课程详情页(含 loading.tsx) - ✅ P2-3 已修复(2026-06-25):student 页面拆分"我的选课"与"可选课程"为两个独立 Suspense 边界;admin 列表与统计卡片分离 Suspense;
StudentSelectionView拆分为StudentMySelectionsSection+StudentAvailableCoursesSection;新增 4 个异步 RSC loader(MySelectionsLoader/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 已修复:
改为只查 electiveCourses 表,通过buildCourseSelect跨模块 join users/subjects/grades 表resolveCourseDisplayNames调用 school/users data-access 获取显示名称 - ✅ P1 已修复:
删除本地实现,改用getSubjectOptions本地直查 subjects 表且与 school 模块重复school/data-access.getSubjectOptions - ✅ P1 已修复:
改为selectCourse/dropCourse缺事务包裹db.transaction包裹,FCFS 模式下使用FOR UPDATE行锁防止并发超卖 - ✅ P2 已修复:
抽取到 data-access.ts 统一导出,data-access-selections.ts 复用mapCourseRow在 data-access.ts 与 data-access-selections.ts 重复定义 - ✅ P2 已修复:
改为 Fisher-Yates 无偏洗牌算法runLottery使用sort(() => Math.random() - 0.5)有偏 shuffle - ✅ P2 已修复:
使用selectCourseFCFS 并发超卖风险db.transaction+.for("update")行锁 - ✅ 权限校验完整(ELECTIVE_MANAGE/SELECT/READ,所有页面入口均调用
requirePermission) - ⚠️ 无 P2 待办:所有审计报告 P0/P1/P2 项均已实施完毕
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
348 | 11 个 Server Action(P0-4:translateBusinessError 翻译 ElectiveBusinessError) |
data-access.ts |
258 | 课程 CRUD + scope 过滤 + 共享映射函数(P1-10:移除静默吞错) |
data-access-operations.ts |
374 | 选课操作(P0-4:ElectiveBusinessError + i18n code;P1-11:parseSchedule 支持完整星期+多时段;P1-12:抽签可重跑;A-02:纯函数抽离至 lib/,仅保留 DB CRUD + re-export 向后兼容) |
lib/schedule-conflict.ts |
108 | 时间冲突检测纯函数(A-02 新增:normalizeDay/parseSchedule/isScheduleConflict/hasAnyScheduleConflict) |
lib/lottery.ts |
72 | 抽签算法纯函数(A-02 新增:buildLotteryRankCase/fisherYatesShuffle/partitionLottery) |
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-7:a 转 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(监考模块)
职责:考试监考事件记录 + 防作弊监控 + 监考面板。
导出函数:
- Actions:
recordProctoringEventAction/getProctoringDashboardAction - Data-access:
recordProctoringEvent/getExamSubmissionForProctoring/getExamForProctoring/getExamProctoringSummary/getStudentProctoringStatuses/getRecentProctoringEvents(✅ P2 已修复:getExamProctoringSummary使用Promise.all并行化考试信息与提交记录查询、事件类型统计与学生事件统计查询;合并两次 filter 为单次循环统计 started/submitted)
依赖关系:
- 依赖:
shared/*、@/auth、exams(✅ P1-1 已修复:通过 exams data-access.getExamForProctoringCrossModule/getExamSubmissionForProctoringCrossModule/getExamSubmissionsForExam/getExamTitleById)、users(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds) - 被依赖:无
已知问题:
- ✅ P1-2 已修复:
exam-mode-config.tsx已集成到考试表单(exam-form.tsx调用<ExamModeConfig />),移除全部 10 处as类型断言,改用useFormContext替代Controlprop 避免Control<T>不变型问题 - ✅ P1-3 已修复:
proctoring-dashboard.tsx用类型守卫函数isProctoringEventType+toProctoringEventTypes替代Object.keys(...) as ProctoringEventType[]断言 - ✅ P0-6 已修复:
事件上报存在 Server Action 与 REST API 双通道重复删除/api/proctoring/eventREST 路由(移至 deletes/),Server ActionrecordProctoringEventAction为唯一规范路径 - ✅ P1-1 已修复:
跨模块直查改为通过 exams/users data-access 函数获取数据exams/examSubmissions/users - ✅ P2 已修复:
actions.ts不再直接 importdb和examSubmissions,submission 归属校验已下沉到 data-access;recordProctoringEventAction改用requirePermission(EXAM_SUBMIT)并增加revalidatePath - ✅ P2 已修复:
改为getStudentProctoringStatuses串行查询(getUserNamesByIds 后再查事件)Promise.all并行拉取学生姓名与事件记录
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
data-access.ts |
409 | 事件记录 + 查询 + 摘要统计 + submission 归属校验 |
actions.ts |
139 | 2 个 Server Action |
types.ts |
136 | 类型定义 + 标签常量 + 阈值常量 |
components/anti-cheat-monitor.tsx |
- | 学生端防作弊监控 |
components/exam-mode-config.tsx |
- | 考试模式配置(✅ P1-2 已修复:已集成到 exam-form,useFormContext 替代 Control prop,无 as 断言) |
components/proctoring-dashboard.tsx |
- | 教师监考面板(✅ P1-3 已修复:类型守卫函数替代 as 断言) |
2.22 diagnostic(学情诊断模块)
职责:知识点掌握度查询 + 诊断报告生成。
导出函数:
- Actions:
generateStudentReportAction/generateClassReportAction/generateGradeReportAction(v4-P2-3 新增:生成年级诊断报告)/publishReportAction(v4-P1-4 + v4-P1-5 增强:班级报告发布时通知全班学生 + 全班学生家长,个人报告通知学生本人 + 其家长,调用parent/data-access.getParentIdsByStudentIds批量查询家长 ID)/deleteReportAction(v2-P2-3 修复:删除死代码getDiagnosticReportsAction/getDiagnosticReportByIdAction,页面直接调用 data-access 并自行权限校验)/exportDiagnosticReportAction(v3-P2-4 新增:导出 Excel)/getClassStudentsByKnowledgePointAction(v3-P2-5 新增:按知识点筛选学生) - Data-access:
updateMasteryFromSubmission(v2-P1-8 修复:累积模式;v2-P2-5 修复:db.transaction 包裹)/getStudentMastery(P3-19 修复:移除 export,改为模块内部函数)/getStudentMasterySummary(P3-18 修复:getUserNamesByIds 与 getStudentMastery 并行查询)/getClassMasterySummary(v2-P2-4 修复:totalStudents 语义 + 班级平均掌握度按学生平均)/getGradeMasterySummary(v4-P2-3 新增:年级掌握度摘要)/getKnowledgePointStats(v2-P1-7 修复:页面先查班级再传参)/getClassStudentsByKnowledgePoint(v3-P2-5 新增) - Data-access-reports:
generateDiagnosticReport/generateClassDiagnosticReport(v2-P2-6 修复:校验掌握度数据;P3-27 修复:使用 DiagnosticReportError 结构化错误码)/generateGradeDiagnosticReport(v4-P2-3 新增:生成年级诊断报告)/getDiagnosticReports(P3-15 修复:支持分页 limit/offset,返回 { reports, total } 结构;v4-P1-1 增强:新增可选scope?: DataScope参数,支持 children/class_taught 行级权限过滤,class_taught 通过getStudentIdsByClassIds解析所教班级学生 ID)/getDiagnosticReportById/publishDiagnosticReport/deleteDiagnosticReport(✅ P2 已修复:使用React.cache()包装实现请求级 memoization;P3-1 修复:toNumber 从 grades 模块导入)/DiagnosticReportError(P3-27 新增:结构化错误码类,v4-P2-3 新增 GRADE_NOT_FOUND/GRADE_NO_MASTERY_DATA 错误码) - Stats-service(✅ v2-P1-6 新增):
serializeMasteryWithKp/computeAverageMastery/classifyStrengthsWeaknesses(P3-16 修复:弱项阈值从 <60 改为 <80,消除 60-79 盲区)/buildStudentMasterySummary/aggregateClassMastery/computeKpStats/computeClassAverageMastery/buildStudentsNeedingAttention/buildClassMasterySummary/buildGradeMasterySummary(v4-P2-3 新增)/buildStudentReportContent/buildClassReportContent/buildGradeReportContent(v4-P2-3 新增)/computeMasteryLevel/serializeMastery(从 data-access / data-access-reports 抽取的纯函数) - Export(v3-P2-4 新增):
exportDiagnosticReportToExcel(v4-P2-1 增强:班级报告导出新增知识点统计+需关注学生 Sheet;i18n 全覆盖)/buildDiagnosticReportFilename(v4-P2 改为 async,使用 i18n 翻译文件名) - Role-config(v4-P2-2 新增):
DIAGNOSTIC_ROLE_CONFIG/getDiagnosticRoleConfig/DiagnosticRole类型(配置驱动角色差异,新增角色只需添加配置项) - Schema:
GenerateStudentReportSchema/GenerateClassReportSchema/GenerateGradeReportSchema(v4-P2-3 新增)/PublishReportSchema/DeleteReportSchema(v2-P2-3 修复:删除死代码GetDiagnosticReportsSchema/GetDiagnosticReportByIdSchema)
依赖关系:
- 依赖:
shared/*、@/auth、exams(✅ P1-1 已修复:通过 exams data-access.getExamSubmissionWithAnswers)、questions(✅ P1-1 已修复:通过 questions data-access.getKnowledgePointsForQuestions)、classes(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getActiveStudentIdsByClassId;v2-P1-7 新增 getStudentActiveClassId)、users(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId)、grades(P3-1 修复:通过 grades/lib/grade-utils.toNumber 复用工具函数)、school(v4-P2-3 新增:通过 school data-access.getGradeNameById 获取年级名称) - 被依赖:无
已知问题:
- ✅ P1-1 已修复:
改为调用updateMasteryFromSubmission跨模块直查 4 张表exams/data-access.getExamSubmissionWithAnswers和questions/data-access.getKnowledgePointsForQuestions - ✅ P2 已修复:
已删除死代码data-access-reports.ts有未使用代码(round2+void round2) - ✅ P2 已修复:
改为updateMasteryFromSubmission循环内串行 await upsertPromise.all并行执行所有 upsert - ✅ P2 已修复:
改为两组getClassMasterySummary串行查询Promise.all并行 - ✅ P2 已修复:
改为显式getDiagnosticReports中conditions隐式any[]SQL[]类型标注 - ✅ P0-2 已修复:
改为通过data-access-reports.ts直查users表获取姓名users/data-access.getUserNamesByIds - ✅ P2-2 已修复:
Tailwind 任意值改用标准 Tailwind 类 - ✅ P2-1 已修复:
图表/表格/列表缺少 a11y ARIA 属性为 5 个图表添加role="img"+aria-label,2 个表格添加<caption>,3 个列表添加role="list",3 个图标按钮添加aria-label - ✅ P2-3 已修复:
班级报告将生成者 ID 存入schema 改为可空,班级报告studentId字段studentId置空 - ✅ v2-P1-6 已修复:
统计逻辑混在 data-access 层抽取stats-service.ts(352 行,12 个纯函数 + 2 个常量 + 4 个接口) - ✅ v2-P1-7 已修复:
页面先查getKnowledgePointStats无参调用getStudentActiveClassId再传参 - ✅ v2-P1-8 已修复:
改为累积计算(读取已有记录后累加)updateMasteryFromSubmission覆盖模式 - ✅ v2-P2-3 已修复:
死代码已删除,页面直接调用 data-accessgetDiagnosticReportsAction/getDiagnosticReportByIdAction全局零调用 - ✅ v2-P2-4 已修复:
改为实际有掌握度记录的学生数;先算学生个人平均再取平均totalStudents语义错误 + 班级平均掌握度计算偏差 - ✅ v2-P2-5 已修复:
多 upsert 无事务包裹使用db.transaction()保证原子性 - ✅ v2-P2-6 已修复:
生成报告未校验掌握度数据添加totalKnowledgePoints === 0和studentCount === 0校验 - ✅ v2-P1-4 已修复:
4 个组件 i18n 完全未接入全部接入useTranslations("diagnostic") - ✅ v2-P2-7 已修复:
添加report-list.tsx过滤器 Label 缺少htmlForhtmlFor和id - ✅ 与 grades 模块无职责重叠
- ✅ v3-P2 改进(2026-06-23):
StudentDiagnosticView新增practiceHrefBaseprop(string | null),null 时隐藏练习按钮;teacher/diagnostic/student/[studentId] 传入practiceHrefBase="/teacher/questions",parent/diagnostic 传入practiceHrefBase={null}隐藏练习按钮 - ✅ P3-1 已修复(2026-06-23):
改为从 grades 模块导入data-access-reports.ts本地定义toNumber与grades/lib/grade-utils.ts重复 - ✅ P3-15 已修复(2026-06-23):
添加 limit/offset 分页支持,返回getDiagnosticReports无分页,可能返回大量数据{ reports, total }结构,Promise.all 并行查询总数和数据 - ✅ P3-16 已修复(2026-06-23):
强弱项分类存在 60-79 盲区弱项阈值从 <60 改为 <80,确保所有知识点都被分类 - ✅ P3-17 已修复:
班级报告 strengths 无数量上限buildClassReportContent中 strengths 已限制为前 5 个(按掌握度降序) - ✅ P3-18 已修复(2026-06-23):
改为 Promise.all 并行查询getStudentMasterySummary串行查询用户名和掌握度 - ✅ P3-19 已修复(2026-06-23):
移除 export,改为模块内部函数getStudentMastery使用 export 但仅内部使用 - ✅ P3-27 已修复(2026-06-23):
改为使用generateDiagnosticReport/generateClassDiagnosticReport中throw new Error("...")直接暴露给用户DiagnosticReportError结构化错误码(继承 BusinessError) - ✅ P3-21 已修复(2026-06-23):
ClassDiagnosticViewView 按钮已有描述性 aria-label(classDiagnostic.viewAriaLabel,含学生名) - ✅ P3-22 已修复(2026-06-23):
StudentDiagnosticViewPractice 按钮添加 aria-label(studentDiagnostic.practiceAriaLabel,含知识点名) - ✅ v4-P3-6 已修复(2026-06-23):
MasteryRadarChart雷达图添加tabIndex={0}键盘可达性 +focus-visible焦点样式(原有role="img"+aria-label保留) - ✅ v4-P1-1 改进(2026-06-23):
getDiagnosticReports新增可选scope?: DataScope参数,支持 children/class_taught 行级权限过滤;teacher/diagnostic/page.tsx 传入 dataScope;student/diagnostic/page.tsx 传 status:"published" + dataScope;parent/diagnostic/page.tsx 传 status:"published" + dataScope + 错误卡片 - ✅ v4-P1-3 改进(2026-06-23):
student-diagnostic-view.tsx移除草稿回退逻辑(不再展示 draft 状态报告) - ✅ v4-P1-4 + v4-P1-5 改进(2026-06-23):
publishReportAction班级报告发布时通知全班学生 + 全班学生家长(通过getStudentIdsByClassId获取全班学生,通过parent/data-access.getParentIdsByStudentIds获取家长 ID);个人报告通知学生本人 + 其家长 - ✅ v4-P1-8 + v4-P1-11 改进(2026-06-23):
class-diagnostic-view.tsx热力图添加图例 + 表格overflow-x-auto水平滚动 - ✅ v4-P1-9 改进(2026-06-23):parent/diagnostic/page.tsx 新增错误卡片(Error Card)展示子女诊断查询失败原因
- ✅ v4-P1 数据库变更(2026-06-23):
learningDiagnosticReports表新增classId字段(varchar(128), references classes.id, onDelete: set null)+diagnostic_class_idx索引;DiagnosticReport接口新增classId: string | null字段(班级报告关联班级 ID,个人报告为 null) - ✅ v4-P1 师生关系校验(2026-06-23):teacher/diagnostic/student/[studentId]/page.tsx 新增 class_taught 师生关系校验
- ✅ v4-P2 审计修复(2026-06-24):P0-1 跨模块 import WidgetBoundary → 提升到
shared/components/widget-boundary.tsx,diagnostic 和 grades 模块统一从 shared 引用 - ✅ v4-P2 审计修复(2026-06-24):P0-2/P0-3 教师页面标题 + error.tsx 硬编码 → 接入
getTranslations("diagnostic")/useTranslations("diagnostic") - ✅ v4-P2 审计修复(2026-06-24):P1-1 parent 错误卡片中英文混用 → 提取完整 i18n 键
error.childLoadFailed/error.childLoadFailedDesc - ✅ v4-P2 审计修复(2026-06-24):P1-2
as类型断言 → 改用类型守卫函数isReportType/isReportStatus返回值收窄 - ✅ v4-P2 审计修复(2026-06-24):P1-3 分享链接指向不存在路由 → 移除分享功能(无报告详情页需求)
- ✅ v4-P2 审计修复(2026-06-24):P1-4 报告内容硬编码中文 → stats-service 接收
ReportContentTranslations接口参数,actions 层从 next-intl 构建翻译注入 - ✅ v4-P2 审计修复(2026-06-24):P1-5 Excel 导出表头硬编码 → export.ts 使用
getTranslations("diagnostic.exportContent"),buildDiagnosticReportFilename改为 async - ✅ v4-P2 审计修复(2026-06-24):P2-1 班级报告导出缺少明细 → 利用
classId字段查询getClassMasterySummary,导出知识点统计 Sheet + 需关注学生 Sheet - ✅ v4-P2 审计修复(2026-06-24):P2-2 角色差异通过 props 硬编码 → 新增
role-config.ts,DIAGNOSTIC_ROLE_CONFIG配置对象驱动practiceHrefBase,组件接受roleprop - ✅ v4-P2 审计修复(2026-06-24):P2-3 无年级诊断报告入口 → 新增
gradeId字段(schema + 迁移 0012)、GradeMasterySummary类型、getGradeMasterySummary/buildGradeMasterySummary/buildGradeReportContent/generateGradeDiagnosticReport/generateGradeReportAction/GenerateGradeReportSchema,i18n 新增gradeSummary/gradeRecommendation/gradeNoWeakness - ✅ v4-P2 审计修复(2026-06-24):P2-4 热力图色块缺少键盘导航 → 添加
tabIndex={0}+focus-visible:ring-2样式,外层容器role从img改为group - ✅ v2-P0-1 审计修复(2026-06-25):3 个子路由 error.tsx 硬编码中文 → teacher/diagnostic/class/[classId]/error.tsx、teacher/diagnostic/student/[studentId]/error.tsx、parent/diagnostic/error.tsx 全部接入
useTranslations("diagnostic"),新增error.classLoadFailed/Desc、error.studentLoadFailed/Desc、error.parentLoadFailed/Desci18n 键 - ✅ v2-P1-1 审计修复(2026-06-25):
在 data-access-reports.ts 中新增getDiagnosticReports未过滤grade_managedDataScopegrade_managed分支,调用users/data-access.getUserIdsByGradeId解析年级学生 ID 后用inArray过滤,修复年级主任越权查看全校报告的安全漏洞 - ✅ v2-P1-2 审计修复(2026-06-25):i18n 弱项标签"弱项(<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.ts(DiagnosticService 接口)、services/diagnostic-service-context.tsx(DiagnosticServiceProvider + useDiagnosticService)、services/default-diagnostic-service.ts(默认实现绑定 Server Actions),report-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 → 移除客户端 filter,DataScope 过滤已在 data-access 层完成
- ✅ v2-P2-3 审计修复(2026-06-25):export.ts 错误消息硬编码英文 → 新增
DiagnosticExportError结构化错误码(继承 BusinessError),throw 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_KEYS,diagnostic/actions.ts 两处 createNotification 改用
type: "diagnostic" - ✅ v2-P2-7 审计修复(2026-06-25):无监控埋点接口 → 新增
services/diagnostic-monitor.ts(DiagnosticMonitor 接口 + noopDiagnosticMonitor 实现)、services/diagnostic-monitor-context.tsx(DiagnosticMonitorProvider + useDiagnosticMonitor)、services/monitored-diagnostic-service.ts(createMonitoredDiagnosticService 工厂函数,组合模式包装服务添加埋点),两个 teacher 页面接入 DiagnosticMonitorProvider - ✅ v2-P2-9 审计修复(2026-06-25):parent/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+ | 诊断报告 CRUD(v2-P2-6 校验;v2-P1-6 改用 stats-service 纯函数;v4-P1-1:getDiagnosticReports 新增 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 Action(v2-P2-3 删除 2 个死代码读 Action;v4-P1-4/v4-P1-5:publishReportAction 新增全班学生 + 家长通知;v4-P2-3 新增 generateGradeReportAction) |
schema.ts |
31+ | Zod 校验(4 个 schema,v2-P2-3 删除 2 个死代码 schema;v4-P2-3 新增 GenerateGradeReportSchema) |
types.ts |
109+ | 类型定义(v4-P2-3 新增 GradeMasterySummary 接口) |
export.ts |
165+ | Excel 导出(v3-P2-4 新增;v4-P2-1 班级报告导出新增知识点统计+需关注学生 Sheet;i18n 全覆盖) |
role-config.ts |
40+ | 角色配置驱动(v4-P2-2 新增:DIAGNOSTIC_ROLE_CONFIG 配置对象,通过 role prop 驱动 practiceHrefBase 等角色差异) |
components/class-diagnostic-view.tsx |
448+ | 班级诊断视图(v2-P1-6 热力图 a11y;v2-P1-4 i18n;v4-P1-8 热力图图例;v4-P1-11 表格 overflow-x-auto;v4-P2-4 热力图色块 tabIndex+focus-visible 键盘导航) |
components/student-diagnostic-view.tsx |
293+ | 学生诊断视图(v2-P1-4 i18n;v3-P2 新增:practiceHrefBase prop;v4-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 htmlFor;v2-P1-4 i18n;v4-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 + useDiagnosticService(v2-P1-4 新增:Context 注入) |
services/default-diagnostic-service.ts |
86 | 默认服务实现绑定 Server Actions(v2-P1-4 新增) |
services/diagnostic-monitor.ts |
95 | DiagnosticMonitor 接口 + noopDiagnosticMonitor(v2-P2-7 新增:监控埋点扩展点) |
services/diagnostic-monitor-context.tsx |
56 | DiagnosticMonitorProvider + useDiagnosticMonitor(v2-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_settings 表 brand 分类读取 4 个键)+ actions-brand.ts Server Action + BrandConfigCard 管理员配置组件。
导出函数:
- Actions:
getAiProviderSummaries(V3.1 增强:按用户身份过滤 public + own private)/upsertAiProviderAction(V3.1 增强:visibility 字段,非管理员强制 private)/testAiProviderAction/deleteAiProviderAction(V3 新增删除能力)/canConfigurePublicAiProvider(V3.1 新增:检查当前用户是否拥有 AI_CONFIGURE 权限) - Actions-password:
changePasswordAction(✅ P1 已修复:使用requirePermission(USER_PROFILE_UPDATE)+ Zod 校验 + DB 操作下沉到 data-access) - Actions-avatar:
updateUserAvatarAction/removeUserAvatarAction(✅ P2-8 新增:头像上传/删除,复用/api/upload路由) - Actions-notifications:
sendTestNotificationAction(✅ P2-10 新增:发送测试通知,占位实现待接入真实通知服务) - Actions-system-settings:
getAdminSystemSettingsAction/saveAdminSystemSettingsAction(✅ P0-3 新增:管理员系统设置 CRUD,4 分类 Zod 校验) - Actions-brand:
getBrandConfigAction/saveBrandConfigAction(✅ audit-P2-6 新增:品牌配置 CRUD,requirePermission(SCHOOL_MANAGE)+ Zod 校验 +revalidatePath刷新/(auth)/login//(auth)/register//admin/settings) - Actions-security:
getSecurityCenterAction/toggleTwoFactorAction(✅ P2-9 新增:2FA 状态查询/切换 + 最近登录历史) - Data-access:
getAiProviderSummaries(管理员视图,返回全部)/getAiProviderSummariesForUser(V3.1 新增:用户视图,返回 public + own private)/countDefaultAiProviders/getAiProviderForUpdate(V3.1 增强:可选 userId 做所有权校验)/updateAiProvider(V3.1 增强:visibility 字段)/createAiProvider(V3.1 增强:visibility 字段)/deleteAiProvider(V3 新增:事务删除 + 自动转移默认;V3.1 增强:可选 userId 做所有权校验)/getUserPasswordHash/getPasswordSecurityByUserId/updateUserPassword/upsertPasswordSecurityOnPasswordChange(P1 新增,从 actions 下沉) - Data-access-system-settings:
getSystemSettingsByCategory/getAllSystemSettings/getSystemSetting/upsertSystemSetting/upsertSystemSettings(✅ P0-3 新增:system_settings 表 CRUD,键值对存储模式;✅ audit-P2-6SystemSettingCategory联合类型新增"brand") - Data-access-brand:
getBrandConfig/saveBrandConfig(✅ audit-P2-6 新增:server-only数据访问,从system_settings表brand分类并行读取 4 个键 schoolName/logoUrl/testimonialQuote/testimonialAuthor,未配置项使用DEFAULT_BRAND_CONFIG默认值) - Brand-config(纯类型,无
server-only):BrandConfig(接口:schoolName / logoUrl: string|null / testimonialQuote / testimonialAuthor)/DEFAULT_BRAND_CONFIG(默认值:schoolName="Next_Edu" / logoUrl=null / 默认英文标语) - Components:
SettingsView(统一设置页布局,V3 移除 AI 标签页后为 4 标签页 General/Notifications/Appearance/Security;角色差异通过resolveRoleSettingsConfig配置驱动 +generalExtraprops 注入;Tab URL 持久化;每个 TabsContent 包裹SettingsSectionErrorBoundary+Suspense骨架屏)、SettingsServiceProvider/useSettingsService(Context 注入SettingsService接口,解耦组件对 users/messaging actions 的直接依赖)、SettingsSectionErrorBoundary(分区 Error Boundary,局部失败不影响整页)、QuickLinksCard(快捷链接卡片,i18n 键驱动)、ProfileStudentOverview/ProfileStudentOverviewSkeleton(学生概览异步 Server Component + 骨架屏)、ProfileTeacherOverview/ProfileTeacherOverviewSkeleton(教师概览异步 Server Component + 骨架屏)、AdminSettingsView(✅ P0-3 已修复:从 mock 改为真实数据层;✅ v3 已拆分:编排 4 个子 Card)、SchoolInfoCard/SecurityPolicyCard/FileUploadCard/NotificationConfigCard(✅ v3 新增:从 AdminSettingsView 拆分的 4 个独立 Card 组件,接收 values + onChange props)、AvatarUpload(✅ P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18n)、SecurityCenterCard(✅ P2-9 新增;✅ v3 已拆分:改为编排器,加载数据 + 渲染两个子区块)、SecurityTwoFactorSection(✅ v3 新增:2FA 启用/关闭/重新生成备份码,从 SecurityCenterCard 拆分)、SecurityRecentLoginsSection(✅ v3 新增:最近登录历史 + 远程登出,从 SecurityCenterCard 拆分)、ThemePreferencesCard(✅ P2-11 已增强:集成LocaleSwitcher语言切换)、AiProviderSettingsCard(V3 增强:新增删除按钮 + AlertDialog 确认;V3.1 增强:visibility 选择器 + 可见性/归属 Badge;✅ v3 已拆分:提取 Selector + DeleteDialog 子组件)、AiProviderSelector(✅ v3 新增:服务商选择器 + 密钥状态展示,从 AiProviderSettingsCard 拆分)、AiProviderDeleteDialog(✅ v3 新增:删除确认对话框,从 AiProviderSettingsCard 拆分)、BrandConfigCard(✅ audit-P2-6 新增:管理员品牌配置卡片,独立表单 + 独立保存,通过getBrandConfigAction/saveBrandConfigAction调用,挂载在AdminSettingsView主表单之后以避免嵌套<form>) - Config:
ROLE_SETTINGS_CONFIG/resolveRoleSettingsConfig(配置驱动角色 → 设置视图映射,新增角色只需添加条目)、ProfileOverviewType/resolveProfileOverviewType(✅ v3 新增:配置驱动角色 → 概览类型映射,消除 roles.includes 硬编码,优先级 student > teacher > none) - Lib:
buildStudentOverviewData/computeStudentStats/sortUpcomingAssignments/filterTodaySchedule/toWeekday(纯数据计算函数,与 UI 分离,便于单元测试)、toSettingItem/SettingItem(✅ v3 新增:从 actions-system-settings.ts 提取的纯函数,将表单值序列化为数据库存储格式,便于单元测试) - Types:
AiProviderSummary(V3.1 增强:新增 visibility/createdBy 字段)/AiProviderName(V3.2 增强:新增 'ollama' 本地部署支持)/AiProviderVisibility(V3.1 新增:'public' | 'private')/AiProviderExisting(V3.1 增强:新增 visibility/createdBy 字段)/SettingsService/ProfileService/NotificationPreferenceService(服务接口定义,用于依赖注入解耦)
依赖关系:
- 依赖:
shared/*(含shared/lib/bcrypt-utils)、@/auth、messaging(页面层通过SettingsService接口注入,组件层不直接 import)、users(页面层通过SettingsService接口注入)、classes/homework/dashboard(ProfileStudentOverview 异步组件获取学生概览数据)、notifications(页面层获取通知偏好) - 被依赖:无
已知问题:
- ⚠️ P2:混合 5 类职责(AI Provider + 密码 + 资料 + 主题 + 通知偏好)
- ✅ P1 已修复:
无新建data-access.ts,actions.ts直接使用dbdata-access.ts,所有 DB 操作已下沉 - ✅ P1 已修复:
改为changePasswordAction使用requireAuth()无 Zod 校验requirePermission(USER_PROFILE_UPDATE)+ChangePasswordSchemaZod 校验 + 并行查询优化 - ✅ 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.ts(server-only数据访问,从system_settings表brand分类读取 4 个键)+actions-brand.ts(requirePermission(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 类已修复:
Progress 组件新增[&>div]:bg-red-500等任意值类indicatorClassNameprop,使用标准颜色类 - ✅ P0 已修复:
改为通过notification-preferences-form.tsx跨模块直接 import messaging/actionsuseSettingsService().notifications.updatePreferences调用,页面层注入实现 - ✅ P0 已修复:
改为通过profile-settings-form.tsx跨模块直接 import users/actionsuseSettingsService().profile.updateProfile调用,页面层注入实现 - ✅ P0 已修复:
i18n 完全缺失新增settings.json翻译文件(zh-CN + en),所有组件改用useTranslations/getTranslations - ✅ P1 已修复:
缺少 Error Boundary新增SettingsSectionErrorBoundary,每个 TabsContent + profile 页面角色概览区块均包裹 - ✅ P1 已修复:
缺少 Suspense 骨架屏每个 TabsContent 包裹Suspense+SettingsSectionSkeleton;profile 页面包裹ProfileStudentOverviewSkeleton/ProfileTeacherOverviewSkeleton - ✅ P1 已修复:
profile/page.tsx 业务逻辑与 UI 混合抽取buildStudentOverviewData等纯函数到lib/student-overview-data.ts;拆分ProfileStudentOverview/ProfileTeacherOverview异步组件 - ✅ 密码修改有速率限制
- ✅ AI Provider 操作有
AI_CONFIGURE权限校验 - ✅ P0-3 已修复:
AdminSettingsView 为 mock 实现,无数据持久化新增system_settings表(键值对存储)+data-access-system-settings.ts+actions-system-settings.ts,AdminSettingsView 改为真实数据层 - ✅ P2-8 已修复:
无头像上传新增updateUserAvatardata-access +actions-avatar.ts+AvatarUpload组件,复用/api/upload路由 - ✅ P2-9 已修复:
无 2FA / 会话管理新增actions-security.ts+SecurityCenterCard组件(2FA 开关占位 + 最近登录历史来自 login_logs 表) - ✅ P2-10 已修复:
通知偏好表单无测试通知按钮新增sendTestNotificationAction,每个已启用渠道旁显示测试按钮 - ✅ P2-11 已修复:
语言切换未集成到设置页ThemePreferencesCard集成LocaleSwitcher到 Appearance 标签页 - ✅ v1.1 已修复:
新增/settings页面 ErrorBoundary 触发(Functions cannot be passed directly to Client Components)actions-service.ts("use server" 文件),导出updateProfileAction+updateNotificationPreferencesAction两个 Server Action wrapper;page.tsx直接传递 Server Action 引用;SettingsService接口的getProfile/getPreferences改为可选 - ✅ v1.1 已修复:
i18n 键双重settings.前缀(MISSING_MESSAGE)role-settings-config.tsx中descriptionKey去掉settings.前缀 - ✅ v1.1 已修复:
AI 标签页 FormLabel 在 Form 上下文外使用(getFieldState null)ai-provider-settings-card.tsx中两处<FormLabel>改为<Label> - ✅ v1.1 已修复:
非管理员访问/settings?tab=ai显示空白settings-view.tsx新增resolveTab()函数,对无权限的 tab 回退到general - ✅ v1.1 已修复:
数据库通过 ALTER TABLE 添加缺失字段notification_preferences表缺少quiet_hours_*字段 - ✅ v1.1 已修复:
数据库通过 CREATE TABLE 创建system_settings表不存在 - ✅ 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 行限制拆分为SecurityTwoFactorSection(2FA 流程)+SecurityRecentLoginsSection(登录历史),主文件改为 ~115 行编排器 - ✅ v3 已修复:
AiProviderSettingsCard 529 行超过 500 行限制拆分出AiProviderSelector(选择器)+AiProviderDeleteDialog(删除对话框),主文件降至 ~443 行 - ✅ v3 已修复:
AdminSettingsView 444 行内联 4 个 Card拆分为SchoolInfoCard/SecurityPolicyCard/FileUploadCard/NotificationConfigCard4 个独立组件,主文件降至 ~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 | 管理员系统设置 CRUD(P0-3 新增:4 分类 Zod 校验 + upsert) |
actions-security.ts |
165 | 2FA 状态查询/切换 + 最近登录历史(P2-9 新增) |
actions-service.ts |
60 | 设置页 Server Action wrapper(v1.1 新增:updateProfileAction + updateNotificationPreferencesAction,作为 Server Action 引用传递给 Client Component,避免 Server→Client 函数传递违规) |
data-access.ts |
158 | AI Provider CRUD + 密码修改 DB 操作(P1 新增) |
data-access-system-settings.ts |
112 | system_settings 表 CRUD(P0-3 新增:键值对存储模式;✅ audit-P2-6 SystemSettingCategory 联合类型新增 "brand") |
data-access-brand.ts |
72 | 品牌配置数据访问(audit-P2-6 新增:server-only,从 system_settings 表 brand 分类并行读取 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 Action(audit-P2-6 新增:getBrandConfigAction / saveBrandConfigAction,requirePermission(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 等,便于单测) |
lib/type-guards.ts |
- | ✅ as 断言修复新增(2026-07-07):isRecord / isMessageBody / isFileUploadResult 守卫,校验 /api/upload 的 HTTP 响应体结构,替代 components/avatar-upload.tsx 中 await response.json() as FileUploadResult 断言 |
components/settings-view.tsx |
236 | SettingsView 统一设置页布局(5 标签页 + Error Boundary + Suspense + i18n + SecurityCenterCard 集成) |
components/settings-service-context.tsx |
39 | SettingsServiceProvider + useSettingsService(Context 注入服务接口) |
components/settings-section-error-boundary.tsx |
64 | 分区 Error Boundary(局部失败不影响整页) |
components/quick-links-card.tsx |
42 | 快捷链接卡片(i18n 键驱动) |
components/profile-student-overview.tsx |
91 | 学生概览异步 Server Component + 骨架屏 |
components/profile-teacher-overview.tsx |
115 | 教师概览异步 Server Component + 骨架屏 |
components/admin-settings-view.tsx |
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-25,audit-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-factor与modules/settings/lib/totp获取数据,属合理跨模块 data-access 调用)。
导出函数:
- UI 组件:
LoginForm/RegisterForm/AuthLayout(✅ audit-P2-6 增强:接受brand?: BrandConfigprop,由app/(auth)/layout.tsxServer Component 调用getBrandConfig()注入;未配置时使用DEFAULT_BRAND_CONFIG默认值;渲染 schoolName / logoUrl(img 或 GraduationCap 图标)/ testimonialQuote / testimonialAuthor) - Server Actions(
actions.ts):registerAction/checkEmailAvailabilityAction - Data-access(
data-access.ts):createUser/isEmailAvailable/buildRegisterInput - Services(
services/two-factor-service.ts):verifyTwoFactorForLogin/preflightTwoFactorByEmail - Schema(
schema.ts):RegisterSchema(Zod,含superRefine未成年人 guardian 字段条件校验、calcAge、isMinorByBirthDate纯函数) - Types(
types.ts):RegisterResult/RegisterInput/EmailAvailabilityResult/RegisterErrorCode
依赖关系:
- 依赖:
shared/*(含shared/lib/rate-limit、shared/lib/login-logger、shared/lib/bcrypt-utils、shared/lib/http-utils)、modules/settings/data-access-two-factor、modules/settings/lib/totp(✅ 合理跨模块 data-access 调用) - 被依赖:
app/(auth)/*、src/auth.ts(动态 importservices/two-factor-service)
已知问题:
- ✅ audit-P0-1 已修复:
下沉至app/(auth)/register/page.tsx内联 167 行registerActionmodules/auth/actions.ts,页面降至 13 行 - ✅ audit-P0-2 已修复:
改为同模块auth.ts跨模块依赖modules/settings/actions-securityservices/two-factor-service.ts编排 - ✅ audit-P0-5 已修复:
通过auth.ts3 处as断言User接口扩展(next-auth.d.ts)与"token" in message类型收窄消除 - ✅ audit-P1-1 已修复:
替换为 next-intlactions.ts的ERROR_MESSAGES硬编码中文字符串getTranslations("auth.register")翻译键(messageForError()按RegisterErrorCode查表errors.${code});register-form.tsx与login-form.tsx同步迁移至useTranslations,含t.rich()嵌入隐私政策/用户协议链接;新增preflightTwoFactorActionServer Action 消除 login-form 对modules/settings/actions-security的跨模块依赖 - ✅ audit-P1-3 已修复:
抽取至auth.tsauthorize 回调混合 7 类职责(107 行)modules/auth/services/login-service.ts的authenticateUser()函数(输入解析/速率限制/用户查询/账户锁定/密码校验/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 返回以支持异步后端,调用方全部更新为await;rateLimitKey/RATE_LIMIT_RULES/rateLimitHeaders保持同步纯函数 - ✅ audit-P1-7 已修复:
JWT 中存储完整新增permissions[]数组(67 项约 1.1KB)导致 cookie 体积过大shared/lib/permission-bitmap.ts,将 67 个权限点编码为 BigInt → base36 字符串(约 13 字符,体积减少 99%)。auth.tsJWT callback 写入token.permissionsBitmap(不再写token.permissions数组);sessioncallback 通过decodePermissionsBitmap()还原为Permission[]填充session.user.permissions,业务代码(getAuthContext()/usePermission())无需感知位图存在;proxy.ts直接调用hasPermissionInBitmap()检查单个权限,无需完整解码(性能优化:边缘运行时每请求都经过)。PERMISSION_BITMAP_ORDER数组顺序固定不可变,新增权限只能追加末尾。next-auth.d.tsJWT 接口新增permissionsBitmap: string,permissions标记为@deprecated - ✅ audit-P1-9 已修复:
auth 模块未定义任何 trackEvent 事件,登录成功/失败/登出/注册/2FA 启用/禁用/账户锁定/限流触发均无业务埋点shared/lib/track-event.tsEventName 新增 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.tsevents.signIn/signOut 调用trackAuthEvent;login-service.ts在限流/账户锁定/密码错误/2FA 失败 4 个失败分支调用trackAuthEvent;modules/auth/actions.tsregisterAction成功后调用trackAuthEvent("auth.signup");modules/settings/actions-security.tsverifyTwoFactorAction/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_PERMISSIONS→ROUTE_PREFIX_PERMISSIONS、API_PERMISSIONS→API_ROUTE_PERMISSIONS以更准确表达语义)。proxy.ts 从此文件 import,新增路由权限只需修改配置文件无需改 proxy.ts 逻辑;所有映射 value 类型从string收紧为Permission,消除as Permission断言 - ✅ logging 重构 Task 5:proxy.ts 注入
x-request-id请求头。每个请求入口通过 Web Crypto APIcrypto.randomUUID()生成 requestId(Edge Runtime 兼容,不导入node:async_hooks/request-context.ts),复用上游传入的x-request-id请求头(若有)。通过内部助手injectRequestId(request, requestId)构造新 Headers 对象,经NextResponse.next({ request: { headers } })转发给下游 RSC;最终response.headers.set("x-request-id", requestId)暴露给客户端便于日志关联。函数签名未变,injectRequestId为非导出内部函数 - ✅ audit-P2-3 已实施:
系统开放注册缺少预分配角色/班级的机制,管理员无法批量引导特定用户(如新教师/家长)注册新增invitation_codes表 +modules/invitation-codes模块(types/schema/lib/code-generator/data-access/actions),8 字符安全字母表(剔除 I/O/0/1)+ Set 去重 + 批量插入;admin 可批量生成邀请码(1-100 张/批)并指定角色/限定邮箱/绑定班级/设置过期/备注,生成结果仅此一次返回明文供复制;注册流程集成invitationCode字段(可选),registerAction调用validateInvitationCode()→consumeInvitationCode()(乐观锁UPDATE ... WHERE usedBy IS NULL),邀请码角色覆盖默认 student 角色;admin 管理页/admin/invitation-codes(USER_MANAGE 权限)含统计卡片/表格/生成对话框/删除确认/复制;i18ninvitationCodes命名空间已注册(zh-CN + en) - ✅ audit-P2-4 已修复:
注册流程未检测密码是否在已知数据泄露中出现,弱密码(如 "Pass1234" 等热门泄露密码)可通过 Zod 最小长度校验新增shared/lib/breached-password.ts接入 Have I Been Pwned API(k-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?: BrandConfigprop;app/(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 Action(registerAction + checkEmailAvailabilityAction + preflightTwoFactorAction,Zod 校验 + 独立 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 不自动创建角色;isEmailAvailable;buildRegisterInput 从 FormData 构造 RegisterInput) |
schema.ts |
95 | Zod RegisterSchema(含 superRefine 未成年人 guardian 字段条件校验、calcAge、isMinorByBirthDate 纯函数) |
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 / logoUrl(img 或 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-25,audit-P1-2 拆分):原
onboarding-stepper.tsx单文件 451 行(接近 500 上限)拆分为三层结构:
- 状态与提交逻辑 →
hooks/use-onboarding-form.ts(208 行,含所有 React state / 导航 /completeOnboardingAction调用)- 家长子女子表单 →
components/parent-children-form.tsx(122 行,纯受控组件,props:childRows/isSubmitting/onUpdate/onAdd/onRemove)- 主文件
onboarding-stepper.tsx降至 259 行,成为纯展示编排器ChildRow/EMPTY_CHILD_ROW类型与常量从组件内提取至types.ts,便于跨文件复用
导出函数:
- UI 组件:
OnboardingStepper/ParentChildrenForm - Hook:
useOnboardingForm/UseOnboardingFormReturn(类型) - Server Actions(
actions.ts):getOnboardingStatusAction/completeOnboardingAction(✅ 审计 v1 P0 修复:actions 层不再直查 DB,改为调用 data-access 的getUserOnboardedAt/markUserOnboarded) - Data-access(
data-access.ts):getOnboardingStatus/updateUserProfile/bindParentToChild/getUserOnboardedAtRaw/getUserOnboardedAt(cacheFn 包装)/markUserOnboarded(✅ 审计 v1 P0-1/P0-2 新增;P-01 文件头补import "server-only") - Schema(
schema.ts):OnboardingSchema(Zod,v3 重构为 children 数组) - Types(
types.ts):OnboardingRoleInfo/OnboardingStatus/OnboardingCompleteData/OnboardingFailureItem/BindParentToChildParams/ChildRow/EMPTY_CHILD_ROW
依赖关系:
- 依赖:
shared/*(含shared/lib/auth-guard)、modules/classes(班级码绑定 data-access)、modules/users(用户表读写) - 被依赖:
app/onboarding/page.tsx、proxy.ts(读token.onboarded)
已知问题:
- ✅ 审计 v1 P0-1/P0-2 已修复:
data-access.ts 文件头补onboarding/data-access.ts缺import "server-only";onboarding/actions.ts直查 DB(违反三层架构 A-09)import "server-only";新增getUserOnboardedAtRaw/getUserOnboardedAt(cacheFn 包装) /markUserOnboarded三个 data-access 函数;actions.ts 移除db.select/db.transaction/tx.update直查,改为调用 data-access 函数;移除eq/db/usersschema 导入 - ✅ audit-P1-2 已修复:
拆分为 Hook + 子组件 + 编排器三层,主文件降至 259 行onboarding-stepper.tsx451 行接近 500 上限 - ✅ audit-P1-8 已修复:
在bindParentToChild未做独立速率限制,单次 Action 内可循环调用 10 次,三因子(生日 × 手机后4)枚举攻击窗口completeOnboardingAction中针对家长角色且children.length > 0时添加独立速率限制 keyonboarding:bind:{parentId},限制每小时 5 次(5 × 10 = 50 次尝试/小时,远低于 3.65M 组合枚举所需量级);触发限流时写审计日志onboarding.bind_rate_limited并返回中文提示(i18n 待后续迁移) - ✅ audit-P1-13 已修复:
步骤列表与角色过滤逻辑硬编码在抽取配置驱动设计:在use-onboarding-form.ts(STEPS_KEYS常量 +isAdmin ? filter : identity三元表达式),新增/调整步骤必须修改 Hook 逻辑types.ts新增OnboardingStepId/OnboardingStepConfig类型 +ONBOARDING_STEPS配置数组 +getOnboardingStepsForRole(role)纯函数过滤步骤;use-onboarding-form.ts消费配置派生stepConfigs/stepKeys/currentStepId/canSkip,不再内联步骤定义;onboarding-stepper.tsx按currentStepId渲染内容(替代数字下标step === N),跳过按钮可见性由配置canSkip驱动。附带修复:原grade_head/teaching_head在 roleInfo 步骤无内容却无法跳过(canSkipStep2 = isAdmin || isStudent || isTeacher未包含他们),现配置applicableRoles: ["teacher","student","parent"]使他们直接跳过该步骤 - ✅ P2-4 已修复:
已删除,迁移至onboarding-gate.tsx业务逻辑泄漏到 sharedmodules/onboarding/
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
- | Server Actions(getOnboardingStatusAction + completeOnboardingAction,含幂等检查/局部错误收集/教师多科目循环绑定/审计日志/audit-P1-8 家长绑定子女独立速率限制 onboarding:bind:{userId} 5次/小时;✅ 审计 v1 P0-2:移除 db.select / db.transaction / tx.update 直查,改为调用 getUserOnboardedAt() / markUserOnboarded() data-access 函数) |
data-access.ts |
- | 数据访问(getOnboardingStatus + updateUserProfile + bindParentToChild v3 三因子验证;✅ 审计 v1 P0-1:文件头补 import "server-only";新增 getUserOnboardedAtRaw / getUserOnboardedAt(cacheFn 包装)/ markUserOnboarded) |
schema.ts |
- | Zod OnboardingSchema(含 children 数组,支持多子女) |
types.ts |
- | 类型定义(含 ChildRow / EMPTY_CHILD_ROW,audit-P1-2 新增) |
hooks/use-onboarding-form.ts |
208 | audit-P1-2 新增:表单状态与处理逻辑 Hook(step/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、@/auth(useSession)、messaging(通知下拉)、shared/components/ui/select(角色切换下拉) - 被依赖:
app/(dashboard)/layout.tsx
已知问题:
- ✅ P2 已修复:
用权限反推角色app-sidebar.tsx改用hasRole()判断角色,并新增多角色切换机制(SidebarContext.currentRole/setCurrentRole,null 表示自动检测;当用户拥有多个角色时在侧边栏底部显示Select下拉切换) - ✅ navigation.ts 无幽灵路由(13 个已修复)
文件清单:
| 文件 | 职责 |
|---|---|
components/app-sidebar.tsx |
侧边栏(根据权限渲染 + 多角色切换下拉) |
components/sidebar-provider.tsx |
侧边栏状态 Context(含 currentRole/setCurrentRole) |
components/site-header.tsx |
顶部导航(含通知下拉) |
config/navigation.ts |
导航配置(4 个角色) |
2.26 student(学生 UI 模块)
职责:学生端 UI 组件(课程视图 + 课表筛选/视图)。
导出函数:StudentCoursesView / StudentScheduleFilters / StudentScheduleView
依赖关系:
- 依赖:
shared/* - 被依赖:
app/(dashboard)/student/*
已知问题:
- ⚠️ P2:与 classes 模块的
schedule-view.tsx/schedule-filters.tsx可能功能重叠 - ✅ 纯 UI 模块,数据由页面通过 classes data-access 获取
- ✅ 认证模式已统一:所有 student 页面使用
getCurrentStudentUser()(users 模块)或getAuthContext()(shared 模块),不再直接调用auth()或getDemoStudentUser()
文件清单:
| 文件 | 职责 |
|---|---|
components/student-courses-view.tsx |
学生课程视图(含 ClassCard memo 组件 + 加入班级表单,使用 useTransition) |
components/student-schedule-filters.tsx |
课表筛选器 |
components/student-schedule-view.tsx |
学生课表视图 |
路由文件清单(app/(dashboard)/student/):
| 文件 | 职责 |
|---|---|
dashboard/page.tsx + loading.tsx |
学生仪表盘 + 骨架屏 |
attendance/page.tsx + loading.tsx |
学生考勤 + 骨架屏 |
diagnostic/page.tsx + loading.tsx |
学情诊断 + 骨架屏 |
elective/page.tsx + loading.tsx |
选课中心 + 骨架屏 |
grades/page.tsx + loading.tsx |
我的成绩 + 骨架屏 |
learning/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)。数据结构从 v1(blocks 数组)升级到 v2(nodes + edges 节点图),旧数据通过
migrateV1ToV2()自动迁移。
架构变更(2026-06-23):数据结构从 v2 升级到 v3(课文锚点画布格式)。正文节点(textbook_content)作为画布中央核心,教学节点围绕正文组织。新增锚点系统(range/point 两种锚定方式)、占位符注入算法、11 种定制节点类型。旧数据通过
migrateV2ToV3()自动迁移。
架构变更(2026-07-04,V4 纸感重构):抛弃 React Flow 画布,改用三栏布局(260px 结构树 + 1fr 纸感编辑器 + 380px 详情面板)。正文采用 Tiptap 富文本编辑器,锚点从字符串偏移升级为 Tiptap Mark(AnchorMark 区间锚/AnchorPoint 点锚),v3 旧锚点通过 AnchorMigrationBanner 提示失效。doc.version=4,editor-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[] }NodeAnchor:v3 字符串偏移锚点({ id, nodeId, type: "range"|"point", start, end?, textPreview?, invalid? }),V4 中通过AnchorMigrationBanner提示失效
- v4(当前):
{ version: 4, textbookContentNodeId: string; nodes: AnyLessonPlanNode[]; edges: AnyLessonPlanEdge[]; anchors: NodeAnchor[] }- 锚点改为 Tiptap Mark:
AnchorMark(区间锚)+AnchorPoint(点锚),存储在正文富文本 doc 中 nodes新增order字段,按顺序渲染(替代画布 position)InteractionBlockData:师生交互对话回合({ designIntent, turns: QATurn[], expectedAnswer? })
- 锚点改为 Tiptap Mark:
导出函数:
- Data-access(
data-access.ts):getLessonPlans(V4:查询后按 textbookId+chapterId+creatorId 聚合版本,返回代表项 + versionCount + versions 摘要数组)/getLessonPlanById/createLessonPlan/updateLessonPlanContent/softDeleteLessonPlan/duplicateLessonPlan/getTemplateById/buildInitialContent/migrateV1ToV2/normalizeDocument(v4 规范化,兼容 v1/v2/v3 旧数据)/buildDefaultSkeleton(默认骨架)/getTextbooksForPicker/getChaptersForPicker/findChapterById/publishLessonPlan/unpublishLessonPlan - Lib(
lib/document-migration.ts):defaultDataForType/migrateV1ToV2/migrateV2ToV3/normalizeDocument/buildInitialContent/buildDefaultSkeleton/isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges - Lib(
lib/anchor-mark.ts,V4 新增):TiptapAnchorMark扩展(区间锚)+AnchorPoint扩展(点锚),替代 v3 字符串偏移锚点 - Lib(
lib/node-summary.ts):getNodeSummary/getTextbookContentSummary/getNodeColor/NODE_COLORS - Lib(
lib/export.ts,V5-4 新增 + V4 增强 + i18n 注入):flattenLessonPlanForPrint/flattenBlockData(V4:新增interactioncase +flattenInteraction函数)/PrintableLessonPlan类型 /TranslationFn类型(V5-4 i18n:所有 label 通过翻译函数注入,调用方 print-view.tsx 注入useTranslations) - Data-access-versions(
data-access-versions.ts):getLessonPlanVersions/createLessonPlanVersion/getVersionContent/revertToVersion/pruneAutoVersions - Data-access-templates(
data-access-templates.ts):getLessonPlanTemplates/saveAsTemplate/deletePersonalTemplate - Data-access-knowledge(
data-access-knowledge.ts):getLessonPlansByKnowledgePoint/getLessonPlansByQuestion - Publish-service(
publish-service.ts):publishLessonPlanHomework - AI-suggest(
ai-suggest.ts):suggestKnowledgePoints - Actions:
getLessonPlansAction/getLessonPlanByIdAction/createLessonPlanAction/updateLessonPlanAction/saveLessonPlanVersionAction/getLessonPlanVersionsAction/revertLessonPlanVersionAction/deleteLessonPlanAction/duplicateLessonPlanAction/getLessonPlanTemplatesAction/saveAsTemplateAction/deleteTemplateAction/suggestKnowledgePointsAction/publishLessonPlanHomeworkAction/getKnowledgePointOptionsAction/getTextbooksForPickerAction/getChaptersForPickerAction/publishLessonPlanAction/unpublishLessonPlanAction
依赖关系:
- 依赖:
shared/*、@/auth、shared/lib/ai、@tiptap/react、@tiptap/starter-kit、@tiptap/pm、@tiptap/extension-image(V4:Tiptap 富文本编辑器)、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 Mark(AnchorMark/AnchorPoint)
- ✅ V4:删除 8 个 React Flow 画布和字符串锚点相关文件(node-editor/nodes/*/anchor-injector/rf-mappers/auto-layout),无残留引用
- ✅ V4:
block-renderer.tsx(@deprecated)已删除,无残留引用
架构变更(2026-06-22,本次审计修复):
- P0-1 跨模块直查修复:
publish-service.ts不再直接db.insert(examQuestions)和本地实现getStudentIdsByClassIds,改为调用exams/data-access.addExamQuestions和classes/data-access.getStudentIdsByClassIds,恢复三层架构约束- P0-2 i18n 接入:新增
shared/i18n/messages/zh-CN/lesson-preparation.json和shared/i18n/messages/en/lesson-preparation.json,注册lessonPreparation命名空间到src/i18n/request.ts,17 个组件改造为useTranslations/getTranslations- P0-3 DataScope 过滤修复:
data-access.ts的buildScopeCondition按 scope 类型精确过滤——class_taught增加subjectId IN teacher.subjects AND gradeId IN teacher.grades,grade_managed限制gradeId IN managedGrades,class_members/children仅允许查看published课案- P1-1 类型安全修复:
as never断言全部替换为类型守卫函数(isQuestionType/isV1Document/isV2Document)+validTypes数组;block-renderer.tsx使用as ExerciseBlockData/as TextStudyBlockData精确断言;constants.ts的BLOCK_TYPE_LABELS/LESSON_PLAN_STATUS_LABELS改为 i18n 键(BLOCK_TYPE_KEYS/LESSON_PLAN_STATUS_KEYS)- P1 纯函数抽取:新增
lib/document-migration.ts(migrateV1ToV2/normalizeDocument/buildInitialContent,使用类型守卫替代 as 断言)、lib/node-summary.ts(getNodeSummary + NODE_COLORS + getNodeColor,接受翻译函数注入)、lib/rf-mappers.ts(toRfNodes/toRfEdges/fromRfEdges),data-access.ts 改为从 lib/ 导入并 re-export 保持向后兼容- P1-2/P1-3 错误边界 + 骨架屏:新增
components/lesson-plan-error-boundary.tsx(LessonPlanErrorBoundary 类组件)和components/lesson-plan-skeleton.tsx(VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton)- P1-4 阻塞式 UI 修复:
alert()全部替换为sonnertoast;confirm()全部替换为AlertDialog(shadcn);window.location.reload替换为router.refresh();涉及 lesson-plan-card/version-history-drawer/inline-question-editor/text-study-block/exercise-block- P1-5/P1-7 多实例 + 角色配置驱动:新增
providers/lesson-plan-provider.tsx(LessonPlanProvider + Context + 4 个角色配置 TEACHER/ADMIN/STUDENT/PARENT + LessonPlanDataService 接口 + LessonPlanTracker 埋点接口 + useLessonPlanContextSafe/useRoleConfig 等 hooks)和services/default-data-service.ts(包装 Server Actions 为 DataService 实现),lesson-plan-list/lesson-plan-card 通过 Context 注入数据服务,useRoleConfig 控制按钮可见性- P1-8 Block 注册表:新增
config/block-registry.tsx(BLOCK_REGISTRY 配置表 + getBlockComponent/isRichTextBlock),node-edit-panel.tsx重构为配置驱动渲染,移除 if/else 链- P1-4 window.location.reload 修复:
exercise-block.tsx改用router.refresh()精确刷新缓存- P2-1 a11y 修复:5 个组件(question-bank-picker/publish-homework-dialog/knowledge-point-picker/exercise-block/text-study-block)添加
role="dialog"/aria-modal/aria-label;inline-question-editor 添加role="dialog"/aria-modal/aria-label- P2-4 监控埋点预留:
providers/lesson-plan-provider.tsx定义LessonPlanTracker接口 +noopTracker默认空实现,生产环境可替换为真实埋点
架构变更(2026-06-23,本次审计修复):
- Zod schema 补全:
schema.ts新增publishLessonPlanHomeworkSchema(planId/blockId 必填,classIds 至少 1 个,availableAt/dueAt 日期格式校验),导出类型PublishLessonPlanHomeworkInput;3 个 action 文件(actions/actions-publish/actions-ai)补全 Zod 校验- data-access-versions.ts 事务修复:
revertToVersion包裹db.transaction确保原子性(回滚版本时同步更新 lessonPlans.content 与版本记录)- 统一错误处理:所有 Server Action catch 块改用
handleActionError;JSON.parse改用safeJsonParse- block-renderer 拖拽 BUG 修复:修复拖拽时节点位置计算错误
架构变更(2026-06-23,V3 多角色课案查看):
- 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 Flow(nodesDraggable=false, nodesConnectable=false),供学生/家长/管理员/教研组长查看已发布课案- V3-3 多角色视图:
LessonPlanCard新增 viewMode prop(teacher/student/parent/admin/gradeHead),动态跳转链接 + 发布/撤回按钮;LessonPlanEditor新增 initialStatus prop + 发布/撤回按钮(AlertDialog 确认);LessonPlanList新增 viewMode prop- V3-4 锚点选择器重写:
TextbookContentNode新增 props:anchorableNodes,onCreateNewNode;AnchorNodeSelector 重写为节点列表+创建新节点选项;NodeEditPanel选中 textbook_content 时显示操作提示 + 锚点列表(含删除功能)- V3-5 模板分区显示:
TemplatePicker加载并显示个人模板(调用 getLessonPlanTemplatesAction),分区显示系统/个人模板- V3-6 rf-mappers 增强:
toRfNodesctx 新增anchorableNodes和onCreateNewNode字段;toRfEdges锚点边颜色使用getNodeColor(anchor.nodeId)替代硬编码,anchorId 存入 edge.data;fromRfEdges从e.data.anchorId读取 anchorId,回退到 className 判断- V3-7 权限扩展:student/parent/grade_head/teaching_head 角色新增
LESSON_PLAN_READ权限,可查看已发布课案- V3-8 DataScope 扩展:
class_members和children新增可选gradeIds?: string[]字段;auth-guardresolveDataScope中 student 通过classEnrollments.innerJoin(classes)预解析 gradeIds,parent 通过孩子的 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-plans);student 导航新增「我的课案」(/student/lesson-plans);parent 导航新增「孩子课案」(/parent/lesson-plans)
架构变更(2026-06-24,V3 深度审计修复):
- 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.ts的isExerciseBlockData/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.ts(translateFieldErrors/safeParseWithI18n)在 actions 层翻译 Zod 错误- P0-5 loading/error 边界补全:为 6 个路由新增 12 个
loading.tsx和error.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/unpublishLessonPlan),default-data-service.ts实现扩展- P1-3 as 断言修复:新增
lib/type-guards.ts集中类型守卫(11 种 BlockData 类型守卫 + 节点/题目类型守卫);block-registry.tsx所有as XxxBlockData替换为类型守卫;node-edit-panel.tsx移除冗余as LessonPlanNode(TypeScript 判别联合自动收窄);node-editor.tsxMiniMapnodeColor使用类型守卫替代as { node?: AnyLessonPlanNode };rf-mappers.ts移除冗余as TextbookContentNode/as LessonPlanNode;use-lesson-plan-editor.tsupdateNodepatch 类型改为Omit<Partial<Block>, "type">防止类型变更- P1-4 MiniMap 颜色硬编码修复:
lesson-plan-readonly-view.tsxMiniMap 移除硬编码#455a64/#1976d2,改用getNodeColor(nodeData.type)与编辑器保持一致- i18n 错误码扩展:
zh-CN/lesson-preparation.json和en/lesson-preparation.json新增publish.homeworkTitle/publish.homeworkDescription/error.invalidQuestionType/error.duplicateSuffix/error.titleRequired/error.titleTooLong/error.templateRequired/error.invalidDate/error.classRequired等键
架构变更(2026-06-24,V3 深度审计修复续):
- P0 硬编码中文修复(6处):
actions.ts的getLessonPlanByIdAction/revertLessonPlanVersionAction错误消息改为 i18n 键(t("error.notFound")/t("error.versionNotFound"));data-access-versions.ts的revertToVersion接受revertLabel参数替代硬编码`回退到 v${versionNo}`;lesson-plan-error-boundary.tsx重写为包装组件模式(内部类组件接受errorText/retryTextprops,外部函数组件通过useTranslations注入 i18n 文案);lesson-plan-provider.tsx的useLessonPlanContext错误消息改为英文- 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/QuestionPickerItem);default-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/page)用LessonPlanProviderSetup包裹;question-bank-picker.tsx新增isQuestionType类型守卫替代as QuestionType断言- P2 文件拆分:
textbook-content-node.tsx从 578 行拆分为 3 个文件——主文件(471行)+anchor-node-selector.tsx(AnchorNodeSelector 组件,62行)+textbook-segments.tsx(renderSegments 函数,75行)- P3 as 断言修复(8处):
textbook-content-node.tsx的as unknown as TextbookContentNodeProps替换为isTextbookContentNodePropsData类型守卫;7 个 block 组件(blackboard/import/exercise/objective/key-point/homework/reflection)的 select onChangeas断言替换为类型守卫——lib/type-guards.ts新增 7 个字段值类型守卫(isBlackboardLayout/isImportMethod/isExercisePurpose/isObjectiveDimension/isKeyPointType/isHomeworkType/isReflectionAspect)
架构变更(2026-06-25,V3 续审计 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类型守卫;extractNodeText从unknown节点安全提取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"]断言从 DBunknown安全转换- 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.ts的findChapterById递归函数,消除 4 个页面文件(admin/student/parent/teacher lesson-plans view/edit page)+data-access.ts共 5 处重复本地递归实现- P1-7 inline-question-editor.tsx FocusTrap:包裹
FocusTrap(shared/components/a11y/focus-trap.tsx)实现焦点陷阱- P1-8 a11y + 类型守卫修复:补全 aria-label(inline-question-editor 选项/删除/知识点按钮)+ 新增
optionLabeli18n 键;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/handleUnpublish)用
useCallback包裹- P2-4 lesson-plan-list.tsx 筛选加载状态:增加
isLoading状态,传递给LessonPlanFilters;lesson-plan-filters.tsx加载时禁用输入并设置aria-busy- P2-6 ai-suggest.ts AI prompt 提取为常量:新增
AI_SUGGEST_PROMPT_TEMPLATE常量,便于维护和未来国际化
架构变更(2026-06-25,V4 深度审计修复):
- P0-1 跨模块越权修复:
parent/lesson-plans/[planId]/view/page.tsx和student/lesson-plans/[planId]/view/page.tsx新增assertPlanInScope(planId, userId)调用,防止越权访问非本人/子女范围课案- P0-2 权限校验补全:9 个
lesson-plans路由page.tsx(teacher/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.ts的getQuestions方法 +QuestionService接口;question-bank-picker.tsx移除直接import@/modules/questions/actions,改为通过useQuestionService()注入;lesson-plan-provider-setup.tsx自动注入默认 QuestionService- P1-1 FocusTrap + ESC:4 个 dialog/drawer(publish-homework-dialog/knowledge-point-picker/version-history-drawer/question-bank-picker)包裹
FocusTrap(shared/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-picker)的
Promise.resolve().then()/queueMicrotask改为useEffect+ async IIFE +cancelledflag,避免竞态- P2-4 废弃文件删除:
components/block-renderer.tsx(@deprecated,无源码引用)删除- P2-5 anchor-injector regex 一致性:
markdownToPlainText的/^>\s+/gm改为/^\s*>\s+/gm;skipPatterns5 个 regex 统一添加g标志,与markdownToPlainText保持一致- P2-6 Zustand store 拆 slice:
hooks/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.ts的VALID_QUESTION_TYPES常量从模块私有改为export(供inline-question-editor.tsx复用,消除重复定义)
架构变更(2026-07-07,S-03 重复 Helper 提取 + S-06 JSDoc 补全):
- G1-026 状态映射去重:新增
lib/status-mappers.ts(20 行),集中toLessonPlanStatus/toReviewDecision;从data-access-review.ts与data-access-calendar.ts移除本地重复实现- G1-027 isStringArray 去重:从
data-access-knowledge.ts移除本地isStringArray,改从lib/type-guards.ts导入(守卫已存在)- G3-036 修复 buggy 守卫:
data-access.ts移除本地isLessonPlanStatus(仅含 3/6 状态:draft/published/archived),统一使用lib/type-guards.ts完整 6 状态守卫(含 submitted/approved/rejected);同时移除本地isTemplateType/isTemplateScope,统一从lib/type-guards.ts导入- S-06 JSDoc 补全:
data-access-versions.ts(5 函数)/data-access-templates.ts(3 函数)所有公开导出函数补全 JSDoc(@param + @returns + @throws)- 修复 textbooks/data-access.ts 语法错误:
getKnowledgePointsByTextbookIdRaw箭头函数缺失=>(pre-existing bug),补全后通过 tsc
文件清单:
| 文件 | 职责 |
|---|---|
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 保证编辑器完整性 + isStringArray 字符串数组守卫(2026-07-07 S-03 去重):从 data-access-knowledge.ts 移入,消除局部重复定义;同时 data-access.ts 移除本地 buggy 的 isLessonPlanStatus(仅含 3/6 状态),统一使用本文件的 6 状态守卫 |
lib/status-mappers.ts |
状态映射辅助(2026-07-07 S-03 去重新增):集中 toLessonPlanStatus(value: string): LessonPlanStatus(非法值回退 "draft")+ toReviewDecision(value: string): ReviewDecision(非法值回退 "rejected");消除 data-access-review.ts 与 data-access-calendar.ts 中重复的本地实现(G1-026) |
lib/i18n-errors.ts |
Zod 错误 i18n 翻译辅助(V3 新增):translateFieldErrors(将 Zod fieldErrors 中的 i18n 键翻译为实际消息)/ safeParseWithI18n(安全解析 Zod 结果并返回带翻译的 ActionState 错误格式) |
lib/document-migration.ts |
纯函数:v1→v2(migrateV1ToV2)/ v2→v3(migrateV2ToV3)/ 规范化(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)+ flattenBlockData(V4:新增 interaction case + flattenInteraction 函数)+ PrintableLessonPlan 类型;支持 detailed/concise 两种 variant |
lib/node-summary.ts |
纯函数:getNodeSummary(支持 12 种节点类型,含 V4 interaction)+ getTextbookContentSummary + NODE_COLORS + getNodeColor(V3 续审计:移除 as { html?: string; ... } 断言,改用 isRecord/getArrayLength/getString/getNumber 安全字段提取从 unknown 收窄) |
config/block-registry.tsx |
配置驱动:BLOCK_REGISTRY 注册表 + BlockRenderer(V3:使用 lib/type-guards.ts 类型守卫替代所有 as XxxBlockData 断言) |
providers/lesson-plan-provider.tsx |
Provider + Context:LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口(V3:扩展 5 个方法 getLessonPlanById/updateLessonPlan/saveLessonPlanVersion/publishLessonPlan/unpublishLessonPlan;V3 续扩展 7 个方法 createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions + 6 个导出类型)、4 个角色配置、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker;hooks:useLessonPlanContextSafe/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe |
providers/lesson-plan-provider-setup.tsx |
页面层 Provider 设置包装组件(V3 续新增):LessonPlanProviderSetup 自动注入默认数据服务(createDefaultDataService)和角色配置(TEACHER_ROLE_CONFIG),3 个 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 |
版本管理(创建/查询/回滚/清理)(2026-07-07 S-06:5 个导出函数补全 JSDoc) |
data-access-templates.ts |
个人模板 CRUD(V3 续审计:使用 normalizeTemplateBlocks 替代 as LessonPlanTemplate["blocks"] 断言)(2026-07-07 S-06:3 个导出函数补全 JSDoc) |
data-access-knowledge.ts |
按知识点/题目反查课案(V3 续审计:移除两处 as unknown as 断言,改用 getKnowledgePointIds/getQuestionIds 安全字段提取辅助函数)(2026-07-07 S-03:移除本地 isStringArray,改从 lib/type-guards 导入) |
actions.ts |
课案 CRUD/版本/模板 Server Actions(V3:所有 action 使用 translateFieldErrors 翻译 Zod 错误;duplicateLessonPlanAction 传入 i18n 翻译的副本后缀;getLessonPlanByIdAction 返回类型改为 ActionState<{ plan: LessonPlan }>) |
actions-publish.ts |
发布作业 Server Action(V3:actions 层注入 i18n 翻译的作业标题/描述/日期标签;新增 INVALID_QUESTION_TYPE 错误码映射) |
actions-ai.ts |
AI 知识点建议 Server Action(V3 续审计:显式 unknown[] 类型标注替代隐式 any;统一使用 handleActionError + translateFieldErrors 处理错误) |
actions-kp.ts |
知识点选项 Server Action(V3 续审计:显式 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 类型)(V3:updateNode patch 类型改为 Omit<Partial<Block>, "type"> 防止类型变更) |
hooks/editor-slice.ts |
编辑器 slice(V4 P2-6 新增 + V4 纸感重构):Zustand StateCreator,包含 planId/title/doc 状态及所有文档操作方法(addNode/updateNode/removeNode/updateTextbookContent/getTextbookContentNode/addAnchor/removeAnchor/updateAnchor/setTitle/setPlanId);V4 移除:updateNodePosition/connect/disconnect/setEdges/autoLayout(画布相关方法) |
hooks/selection-slice.ts |
选中状态 slice(V4 P2-6 新增):Zustand StateCreator,包含 selectedNodeId 状态 + selectNode 方法 |
hooks/version-slice.ts |
版本与保存状态 slice(V4 P2-6 新增):Zustand StateCreator,包含 isDirty/isSaving/lastSavedAt/saveError/isOnline 状态 + hydrate/markSaved/setSaving/replaceDoc/setSaveError/setOnline 方法 |
hooks/history-slice.ts |
撤销/重做 slice(V5-2 新增):Zustand StateCreator,past/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 getLessonPlansAction;V3 续审计:新增 isLoading 状态,加载时显示加载文本,传递给 LessonPlanFilters) |
components/lesson-plan-card.tsx |
课案卡片(V3:完全通过 service 调用,移除直接 import actions;V3 续审计: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 |
只读视图(V3:MiniMap 使用 getNodeColor 替代硬编码颜色;V4:移除 React Flow 依赖,委托 LessonPlanMobileView 线性卡片视图渲染所有视口) |
components/anchor-migration-banner.tsx |
V4 新增:v3 锚点失效提示 banner,检测 doc.anchors 中 invalid: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 新增:师生交互 Block(designIntent + 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 个 actions;V3 续审计:通过 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 |
教学目标 Block(V3 续:select onChange 使用 isObjectiveDimension 类型守卫替代 as 断言) |
components/blocks/key-point-block.tsx |
重难点 Block(V3 续:select onChange 使用 isKeyPointType 类型守卫替代 as 断言) |
components/blocks/import-block.tsx |
导入 Block(V3 续:select onChange 使用 isImportMethod 类型守卫替代 as 断言) |
components/blocks/new-teaching-block.tsx |
新授 Block |
components/blocks/summary-block.tsx |
总结 Block |
components/blocks/homework-block.tsx |
作业 Block(V3 续:select onChange 使用 isHomeworkType 类型守卫替代 as 断言) |
components/blocks/blackboard-block.tsx |
板书 Block(V3 续:select onChange 使用 isBlackboardLayout 类型守卫替代 as 断言) |
components/blocks/exercise-block.tsx |
练习 Block(V3 续:select onChange 使用 isExercisePurpose 类型守卫替代 as 断言) |
components/blocks/reflection-block.tsx |
反思 Block(V3 续: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 新增)(2026-07-07 S-03:移除本地 toLessonPlanStatus/isLessonPlanStatus,改从 lib/status-mappers + lib/type-guards 导入) |
data-access-review.ts |
M3 审核工作流数据访问层(2026-07-01 新增)(2026-07-07 S-03:移除本地 toLessonPlanStatus/toReviewDecision,改从 lib/status-mappers 导入) |
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 Actions(2026-07-01 新增) |
actions-review.ts |
M3 审核工作流 Server Actions(2026-07-01 新增) |
actions-comments.ts |
M2 评论 Server Actions(2026-07-01 新增) |
actions-formative.ts |
M5 形成性评价 Server Actions(2026-07-01 新增) |
actions-attachments.ts |
M6 附件库 Server Actions(2026-07-01 新增) |
actions-substitutes.ts |
M12 代课教师 Server Actions(2026-07-01 新增) |
actions-ai-evaluation.ts |
M8 AI 评估 Server Actions(2026-07-01 新增) |
actions-analytics.ts |
M10 分析仪表盘 Server Actions(2026-07-01 新增) |
架构变更(2026-07-01,M1-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.ts,4 种互动类型
- 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.ts,3 个分析维度
- 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-03,V5 场景缺口修复第一阶段):
- V5-1 自动保存失败 UI 兜底:version-slice.ts 新增 saveError/isOnline 状态;lesson-plan-editor.tsx 监听 online/offline 事件 + toast 提示 + 重试按钮
- V5-2 撤销/重做:新增 hooks/history-slice.ts(past/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-access);LessonPlanDataService 接口新增 getLessonPlanAttachments/createLessonPlanAttachment/deleteLessonPlanAttachment 三个方法;新增依赖 @tiptap/extension-image;FileTargetType 新增 "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-03,V5 场景缺口修复第二阶段 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.tsx;actions.ts 新增 duplicateLessonPlanFormAction(FormData 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.tsx;version-history-drawer.tsx 集成版本对比(currentDoc prop + 对比按钮)
- V5-17 A1/A2 AI 反馈闭环:新增 lib/ai-feedback.ts(generateLessonPlanFeedback 纯服务端函数,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.ts(3 个纯服务端函数:generateDifferentiationSuggestions/checkCurriculumAlignment/generateExplainableAssessment,复用 ai-feedback.ts 的 createAiChatCompletion + Zod + JSON 提取模式);actions-ai.ts 新增 4 个 server actions(generateDifferentiationSuggestionsAction/checkCurriculumAlignmentAction/generateExplainableAssessmentAction/getKnowledgePointsForAlignmentAction);新增 components/ai-differentiation-dialog.tsx(3 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-04,V4 纸感重构 — 抛弃 React Flow 画布,改用三栏布局 + Tiptap 富文本编辑器):
- H1 interaction block 注册:
config/block-registry.tsx新增interactioncase,导入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.anchors中invalid:true或含start字段的旧锚点并提示用户;通过key={planId}实现 planId 变化时重新挂载内部组件,使用 lazy initial state 读取 localStorage(dismiss 状态按 planId 隔离),避免 useEffect 中 setState(符合 V4react-hooks/set-state-in-effectlint 规则)- J1 删除废弃文件:删除 8 个 React Flow 画布和字符串锚点相关文件——
components/node-editor.tsx、components/nodes/lesson-node.tsx、components/nodes/textbook-content-node.tsx、components/nodes/textbook-segments.tsx、components/nodes/anchor-node-selector.tsx、lib/anchor-injector.ts、lib/rf-mappers.ts、lib/auto-layout.ts;lesson-plan-readonly-view.tsx重写为委托LessonPlanMobileView(线性卡片视图)渲染所有视口,移除 React Flow 依赖- J2 PrintView 适配 V4:
lib/export.ts的flattenBlockDataswitch 新增interactioncase +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 失效提示- 数据模型 v4:
types.tsLessonPlanDocument.version = 4;lib/document-migration.tsnormalizeDocument兼容 v1/v2/v3/v4 链式迁移;editor-slice.ts移除updateNodePosition/connect/setEdges/autoLayout画布相关方法;hooks/expanded-slice.ts新增expandedNodeIds/toggleExpand/setExpanded状态(节点展开到正文流);lib/anchor-mark.ts新增 TiptapAnchorMark+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.tsx的copyNode从 toast 占位改为调用duplicateNode并 toast 成功提示;i18n 新增v4.contextMenu.copied/copyFailed/copySuffix键(zh-CN + en)- V2-2 节点级 AI 协助 4 项:
lib/ai-node-assist.ts新增 4 个服务函数(generateNodeContent/optimizeNodeExpression/suggestNodeDifferentiation/generateLayeredQuestions),全部纯服务端、Zod 校验、失败返回 null;actions-ai.ts新增 4 个 Server Action,每个 action 校验LESSON_PLAN_READ + LESSON_PLAN_CREATE + AI_CHAT三个权限点;hooks/use-node-ai-assist.ts新增客户端 hook;components/paper-editor/paper-editor.tsx注入onAiAction回调;components/detail-panel/detail-panel.tsx的 4 个AiButton接入真实 action;i18n 新增v4.contextMenu.aiRunning/aiSuccess/aiFailed/aiComingSoon键(zh-CN + en)- V4-I18N-1 i18n 完整审查与修复:对 lesson-preparation 模块 i18n 配置进行全量审查并修复——P0 修复运行时 MISSING_MESSAGE:
version.diff.*3 键(title/summary/noChanges)+schema.ts引用的 8 个error.*键(labelTooLong/versionNoInvalid/nameRequired/nameTooLong/queryTooLong/blockIdRequired/commentTooLong/reviewerRequired)+v4.contextMenu6 键(copied/copyFailed/aiComingSoon/aiRunning/aiSuccess/aiFailed)(zh-CN + en 同步);P1 修复 en locale 下大面积报错:encalendar节键名对齐 zh-CN(weekView/monthView/loadFailed + prev/next/eventMeta/weekDays 子节),enanalytics节补齐 totalPublished/totalSubmitted/totalStandardsLinked/loadFailed;P2 修复 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/turnContentPlaceholder、v4.heatmap.legendTitle/noData);清理 7 个 dead 节(review/comment/formative/substitute/evaluation/standards/gradeHead,analytics 因被app/(dashboard)/admin/curriculum-map引用而保留);lib/i18n-errors.ts新增t.has(msg)运行时守卫,避免as断言绕过类型检查(键不存在时返回原消息而非抛出 MISSING_MESSAGE);zh-CN 与 en 顶层键集一致性验证通过(43 = 43);005_architecture_data.json 同步追加V4-I18N-1auditFixes 条目;known-issues.md 追加 3 个 i18n 规则表(键缺失与中英文不同步 / dead 节清理规则 / 动态键翻译守卫)
架构变更(2026-07-05,Phase 4.2 — Zustand selector 细粒度拆分,避免整体订阅级联 re-render):
- lesson-plan-editor.tsx:
const editor = useLessonPlanEditor()整体订阅拆分为 9 个细粒度 selector(isDirty/isOnline/doc/title/isSaving/saveError/lastSavedAt/setTitle+canUndo/canRedo派生 boolean);canUndo/canRedo改为useLessonPlanEditor((s) => s.canUndo())订阅派生 boolean,移除每次渲染时调用editor.canUndo()函数;useEffect依赖与printablePlanuseMemo 同步更新使用拆分后的变量- text-study-block.tsx:
const { updateNode } = useLessonPlanEditor()→useLessonPlanEditor((s) => s.updateNode)- exercise-block.tsx:拆为
useLessonPlanEditor((s) => s.updateNode)+useLessonPlanEditor((s) => s.planId)- paper-context-menu.tsx:拆为 7 个细粒度 selector(
toggleExpand/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-access:getStandards / getStandardsTree / getStandardById / createStandard / updateStandard / deactivateStandard / searchStandards / getStandardsByPlanId / linkPlanToStandard / unlinkPlanFromStandard
- Actions:10 个 Server Actions,全部使用 requirePermission() + ActionState
- Schema:createStandardSchema / updateStandardSchema / getStandardsParamsSchema / linkPlanToStandardSchema
- Types:Standard / StandardTreeNode / LessonPlanStandardLink
权限点:
- STANDARD_READ(standard:read)
- STANDARD_MANAGE(standard:manage)
- STANDARD_LINK(standard: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 |
| lib/type-guards.ts | ✅ as 断言修复新增(2026-07-07):isStandardLevel / toStandardLevel 守卫 + 安全窄化函数,替代 data-access.ts 中 row.level as StandardLevel 断言 |
2.28 error-book(错题本模块)
职责:自动采集考试/作业错题,基于 SM-2 间隔重复算法科学复习,提供知识点薄弱度分析与多角色视图(学生/教师/家长/管理员)。
导出函数:
- Actions:
getErrorBookItemsAction/getErrorBookItemDetailAction/getErrorBookStatsAction/createErrorBookItemAction/updateErrorBookNoteAction/reviewErrorBookItemAction/deleteErrorBookItemAction/archiveErrorBookItemAction/collectFromSubmissionAction - Data-access(
data-access.ts,学生端 CRUD):getErrorBookItems/getErrorBookItemById/getErrorBookStats(SQL 聚合优化)/createErrorBookItem/updateErrorBookNote/recordReview/deleteErrorBookItem/archiveErrorBookItem/buildStudentErrorWhereClause(共享)/toStatus(共享)/toStringArray(共享) - Data-access(
data-access-analytics.ts,教师/管理员分析查询):getStudentErrorBookSummaries(SQL 聚合)/getTopWrongQuestionsByStudentIds(SQL 聚合)/getKnowledgePointWeakness/getSubjectErrorDistribution(SQL 聚合)/getChapterWeakness/getClassErrorOverviews(SQL 聚合)/getSubjectErrorOverviews(SQL 聚合)/getStudentNameMap/getStudentIdsByClassIdList/getAllStudentIds - Data-access(
data-access-collection.ts,自动采集):collectFromExamSubmission/collectFromHomeworkSubmission(通过data-access.tsre-export) - Schema:
CreateErrorBookItemSchema/UpdateErrorBookNoteSchema/ReviewErrorBookItemSchema/CollectFromSubmissionSchema - Types:
ErrorBookItem/ErrorBookItemDetail/ErrorBookReviewRecord/ErrorBookStats/StudentErrorBookSummary/KnowledgePointWeakness/SubjectErrorDistribution - Components:
ErrorBookStatsCards/ErrorBookFilters/ErrorBookItemCard/ReviewButtons/ErrorBookDetailDialog/ErrorBookList/AddErrorBookDialog/TopWrongQuestions
权限点:
ERROR_BOOK_READ(error_book:read):查看错题本(student 自己、parent 子女)ERROR_BOOK_MANAGE(error_book:manage):管理错题(添加/复习/删除/归档,student)ERROR_BOOK_ANALYTICS_READ(error_book:analytics_read):查看错题分析(teacher/admin/grade_head/teaching_head)
依赖关系:
- 依赖:
shared/*(db、auth-guard、types、utils)、modules/classes(getStudentIdsByClassIds) - 被依赖:
app/(dashboard)/student/error-book、app/(dashboard)/teacher/error-book、app/(dashboard)/parent/error-book、app/(dashboard)/admin/error-book、modules/homework(collectFromHomeworkSubmission,作业批改后自动采集)、modules/grades(collectFromExamSubmission,✅ 2026-06-25 新增:教师录入考试成绩后自动采集考试错题) - ✅ 2026-06-24 i18n + 解耦改造:
error-book-detail-dialog移除直接 importAiErrorBookAnalysis(modules/ai)和createPracticeSessionAction(modules/adaptive-practice),改为通过aiAnalysisSlot和onStartVariantPracticeprops 注入;add-error-book-dialog移除直接 importgetQuestionsAction(modules/questions),改为通过questionOptions和onLoadQuestionsprops 注入。跨模块依赖上移至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.2;good×1.5;easy×2 - 掌握度:0-5 级,
again-1,hard±0,good+1,easy+2 - 已掌握判定:掌握度 ≥5 或连续答对 ≥3 次
- 复习时间:次日早上 9 点
- 单元测试:sm2-algorithm.test.ts(39 个测试用例,全部通过)
- 导出函数:
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 | 学生端 CRUD(10 个函数)+ 共享工具函数(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.ReactNode 和 onStartVariantPractice?: (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 | 班级错题数对比柱状图(recharts,tooltip 显示学生数/人均/掌握率) |
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 注入 AiErrorBookAnalysis(modules/ai)+ createPracticeSessionAction(modules/adaptive-practice)到 ErrorBookList 的 aiAnalysisSlot/onStartVariantPractice props;AddErrorBookDialogWithQuestions 注入 getQuestionsAction(modules/questions)+ extractQuestionPreview(shared/lib)到 AddErrorBookDialog 的 onLoadQuestions 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.jsonsrc/shared/i18n/messages/en/error-book.json
导航配置:
- 6 个角色(admin/teacher/student/parent/grade_head/teaching_head)均添加「错题分析」导航项
- 图标:
BookX(lucide-react)
2.29 ai(AI 模块)— ✅ 新增 / V2 增强 / V3 安全加固+竞品对标
职责:统一 AI 能力封装,为备课、错题集、试卷、改题等业务模块提供 AI 服务。V2 增加流式响应、Markdown 渲染、全局助手、内容安全过滤、家长学情摘要、管理员使用统计、学生学习路径推荐。V3 安全加固(原子配额/苏格拉底校验/重试机制)+ 竞品对标(知识图谱集成/苏格拉底式辅导强化)。
架构定位:
- 位于
modules/层,通过shared/lib/ai调用底层 AI SDK - 通过
AiClientProvider(React Context)向客户端组件注入 Server Action 引用 - 业务模块不直接 import
ai/actions,仅通过 Context 消费 - V2:在 dashboard layout 全局注入
AiClientProvider,所有页面均可使用 AI 助手
核心导出:
| 类型 | 名称 | 文件 | 说明 |
|---|---|---|---|
| Server Actions | aiChatAction |
modules/ai/actions.ts |
AI 对话(权限:AI_CHAT) |
| Server Actions | suggestSimilarQuestionsAction |
modules/ai/actions.ts |
相似题推荐(权限:AI_CHAT + ERROR_BOOK_READ) |
| Server Actions | suggestGradingAction |
modules/ai/actions.ts |
AI 辅助批改(权限:AI_CHAT + HOMEWORK_GRADE) |
| Server Actions | generateLessonContentAction |
modules/ai/actions.ts |
备课内容生成(权限:AI_CHAT + LESSON_PLAN_READ) |
| Server Actions | generateQuestionVariantAction |
modules/ai/actions.ts |
题目变体生成(权限:AI_CHAT + EXAM_AI_GENERATE) |
| Server Actions | analyzeWeaknessAction |
modules/ai/actions.ts |
薄弱点分析(权限:AI_CHAT + ERROR_BOOK_READ) |
| Server Actions | generateChildSummaryAction |
modules/ai/actions.ts |
家长学情摘要(权限:AI_CHAT)— V2 新增 |
| Server Actions | recommendStudyPathAction |
modules/ai/actions.ts |
学习路径推荐(权限:AI_CHAT)— V2 新增 |
| Server Actions | getAiUsageStatsAction |
modules/ai/actions.ts |
AI 使用统计(权限:AI_CONFIGURE)— V2 新增 |
| 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 |
通知实时推送(SSE,15s 心跳 + 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 对话 Hook(SSE + 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 |
位置持久化 Hook(localStorage + resize 校正)— V3 新增 |
| Hook | useDragPosition |
modules/ai/hooks/use-drag-position.ts |
拖拽位置 Hook(pointer 事件 + 拖拽状态)— 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 改造为懒加载 wrapper(next/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/ai→shared/lib/ai(AI SDK 封装,含流式createAiChatCompletionStream,V3 增加withRetry重试机制)modules/ai→shared/lib/auth-guard(权限校验)modules/ai→shared/lib/track-event(使用量埋点)modules/ai→shared/types/permissions(权限常量)modules/ai→shared/types/action-state(返回值类型)modules/ai→modules/textbooks/data-access-graph(知识图谱+掌握度查询)— V3 新增app/(dashboard)/layout→modules/ai(全局 Provider + Widget)— V2 新增app/api/ai/chat/stream→modules/ai/services/content-safety(SSE 端点安全过滤)— V3 加固- 业务模块 →
modules/ai/context/ai-client-provider(通过 Context 注入) - 业务模块 →
modules/ai/components/*(组合 AI 组件)
安全机制(V2 新增 / V3 加固):
- 输入过滤:
filterUserInput拦截暴力/自残/色情/PII - 输出过滤:
filterAiOutput扫描 AI 回复 - 每日限制:学生 50 次/天,教师 200 次/天,家长 30 次/天
- 学生 Socratic 模式:system prompt 强制不直接给答案
- SSE 端点权限校验:
requirePermission(AI_CHAT) - V3 原子配额:
tryConsumeDailyQuota解决 TOCTOU 竞态,refundDailyQuota过滤/失败时回退 - V3 苏格拉底校验:
validateSocraticOutput检查问号结尾+连续陈述句限制,SOCRATIC_TUTOR_SYSTEM_PROMPT3 级提示升级 - V3 SSE 端点加固:Zod 校验 + rate limit + 服务端强制 systemPrompt(忽略客户端)
- V3 重试机制:
withRetry指数退避(429/5xx,2 次重试,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消除非空断言!(改用flatMap);ai-assistant-widget.tsx硬编码英文提示词迁移至 i18n(新增chat.suggestedPrompts.context.*翻译键)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
modules/ai/types.ts |
~290 | 类型定义(8 个业务场景类型 + AiService/AiClientService,V3 新增 knowledgeGraph/textbookId) |
modules/ai/schema.ts |
~230 | Zod 验证 schema(8 个输入 + 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 | 懒加载 wrapper(next/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 对话 Hook(V3 拆分至 ≤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/suggestedPrompts、grading.description/batch*、lessonPrep.description/additionalContext/insertContent、exam.variantType.*/targetDifficulty/addVariant、parent.*、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() 调用 actions,a11y 增强,原生 input → shadcn Checkbox,reason 改为枚举值) |
| 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 / PracticeHistory(routePrefix=undefined) |
parent/practice |
V4 新增(2026-06-25 P0-修复-11):家长查看子女练习统计与历史,只读视图,PracticeServiceProvider 注入 |
依赖关系:
modules/adaptive-practice→shared/db(schema: practiceSessions/practiceAnswers/questions/knowledgePoints 等)modules/adaptive-practice→shared/lib/auth-guard(权限校验)modules/adaptive-practice→shared/types/permissions(权限常量)modules/adaptive-practice→shared/types/action-state(返回值类型)modules/adaptive-practice→modules/classes/data-access(getActiveStudentIdsByClassId/getClassNameById/getClassesByGradeId/getClassIdsByGradeIds/getStudentIdsByClassIds)modules/adaptive-practice→modules/users/data-access(getUserIdsByGradeId/getUserNamesByIds)modules/adaptive-practice→modules/questions/data-access(题目查询,出题策略使用)modules/adaptive-practice→modules/error-book/data-access(错题数据,错题变式策略使用)app/(dashboard)/student/practice→modules/adaptive-practice(学生端页面;V4:通过PracticeServiceProvider注入 PracticeService)app/(dashboard)/teacher/practice→modules/adaptive-practice(教师端分析页面)— 新增;V4:班级筛选改用shared/components/class-filter(通用组件,ClassFilterItem接口 + 预翻译文本 props),不再依赖modules/error-book/components/class-filterapp/(dashboard)/management/grade/practice→modules/adaptive-practice(年级主任分析页面)— 新增app/(dashboard)/parent/practice→modules/adaptive-practice(家长端查看子女练习)— V4 新增(2026-06-25 P0-修复-11)modules/error-book/components/error-book-detail-dialog→modules/adaptive-practice/actions(变式练习入口,通过onStartVariantPracticeprops 注入,已解耦)
权限点:
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 联合类型等;V4:WeakChapterSourceMeta.chapterId 改为可选) |
modules/adaptive-practice/schema.ts |
~75 | Zod 验证 schema(V4:discriminatedUnion) |
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 | 教师/年级宏观数据分析查询(V4:N+1 优化,单 SQL GROUP BY) |
modules/adaptive-practice/actions.ts |
~267 | 7 个 Server Actions(V4:i18n 错误码返回 + 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 | 练习发起器(V4:usePracticeService 注入 + 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 |
- | 教师端错误边界(V4:i18n 化) |
app/(dashboard)/management/grade/practice/page.tsx |
~140 | 年级主任宏观数据分析页 |
app/(dashboard)/management/grade/practice/loading.tsx |
- | 年级端加载骨架 |
app/(dashboard)/management/grade/practice/error.tsx |
- | 年级端错误边界(V4:i18n 化) |
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.*(发起器;V4:a11y 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 兼容)
导出函数:
- Actions:
createRoleAction/updateRoleAction/deleteRoleAction/toggleRoleEnabledAction/setRolePermissionsAction/assignUserRolesAction/getRoleDetailAction/isAdminRole(✅ 每个 Action 调用requirePermission()进行权限校验;内置角色不可删除/改名的校验在 data-access 层;P2-7 新增setRolePermissionsAction/assignUserRolesAction调用trackPermissionChange埋点) - Data-access(
data-access.ts):getRoles/getRoleById(✅ P2-3 新增返回userCount字段)/getRoleByName/createRole/updateRole/deleteRole/setRoleEnabled/getRolePermissions/setRolePermissions/ADMIN_ROLE_NAME - Data-access(
data-access-assignments.ts):getUserRoleNames/assignRolesToUser/getUserRoleAssignments - Lib(
lib/permission-catalog.ts):PERMISSION_CATALOG(权限目录常量,按模块分组)/getAllPermissionMetas(获取所有权限元数据)/getPermissionMeta(按权限点获取元数据) - Lib(
lib/track.ts):trackPermissionChange(✅ P2-7 新增:RBAC 关键操作埋点接口,no-op 实现,预留未来 analytics 接入)/TrackPermissionChangeParams接口
依赖关系:
- 依赖:
shared/db(schema: roles/usersToRoles/rolePermissions)、shared/types/permissions(权限常量与 Role 类型)、shared/lib/auth-guard(权限校验)、shared/lib/audit-logger(操作日志)、shared/lib/change-logger(数据变更日志) - 被依赖:
app/(dashboard)/admin/roles、app/(dashboard)/admin/permissions、app/(dashboard)/admin/users(用户-角色分配)、modules/users(通过 UserRoleAssignDialog 组件)
已知问题:
- ✅ 职责清晰,data-access 按职责拆分为
data-access.ts(角色 CRUD)与data-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 关键操作埋点接口(trackPermissionChange,no-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 / isSystem(boolean DEFAULT FALSE,标记内置角色)/ isEnabled(boolean 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 新增
职责:批量生成邀请码替代开放注册,支持预分配角色/班级/限定邮箱/过期时间;注册流程集成邀请码校验与消费(乐观锁),管理员可生成、列表、删除邀请码。
导出函数:
- Actions(
actions.ts):generateInvitationCodesAction(input)— 批量生成邀请码(USER_MANAGE 权限,Zod 校验 1-100 张/批,trackEventinvitation_codes.generated,返回明文 codes 仅此一次)listInvitationCodesAction()— 列出邀请码(USER_MANAGE 权限,含已使用+未使用,按 createdAt desc,最多 200 条)deleteInvitationCodeAction(codeId)— 删除邀请码(USER_MANAGE 权限,仅允许删除未使用,已使用保留作审计记录)validateInvitationCodeAction(code, email)— 校验邀请码(公开接口,注册流程预检用,不返回明文)
- Data-access(
data-access.ts):generateInvitationCodes(input, createdBy)— 批量插入 + 查询回记录listInvitationCodes(limit, includeUsed)— 列表查询validateInvitationCode(code, email)— 校验(存在/已用/过期/邮箱匹配 4 项)consumeInvitationCode(input)— 乐观锁消费(UPDATE ... WHERE usedBy IS NULL+affectedRows检查)deleteInvitationCode(codeId)— 删除(仅未使用)purgeExpiredInvitationCodes()— 清理已过期且未使用(cron 预留)
- Lib(
lib/code-generator.ts):generateInvitationCode()— 8 字符邀请码(node:crypto.randomBytes+ 安全字母表ABCDEFGHJKLMNPQRSTUVWXYZ23456789,剔除 I/O/0/1)generateUniqueInvitationCodes(count)— 批量生成(Set 去重 + 最多 3× 重试)generateBatchId()— 批次 ID(batch_<timestamp36>_<random6hex>)
- Schema(
schema.ts):GenerateInvitationCodesSchema(count 1-100、role enum、可选 email/classId/expiresAt/notes)、InvitationCodeFieldSchema(嵌入 RegisterSchema,8-64 字符大写字母数字)、INVITATION_ROLE_VALUES - Types(
types.ts):InvitationRole/InvitationCodeRecord/GenerateInvitationCodesInput/GenerateInvitationCodesResult/ValidateInvitationCodeResult/ConsumeInvitationCodeInput - Components:
InvitationCodesView— 管理视图(统计卡片 + 表格 + 删除确认 + 生成对话框入口,接受initialCodes+nowprops 由 Server Component 注入避免Date.now()在 render 调用)GenerateInvitationCodesDialog— 生成对话框(form/result 两态切换,result 视图仅此一次展示明文供复制)
依赖关系:
- 依赖
shared/db(drizzle 客户端)、shared/db/schema(invitationCodes 表)、shared/lib/auth-guard(requirePermission)、shared/lib/track-event、shared/types/action-state、shared/types/permissions - 被依赖:
modules/auth/actions.ts(registerAction 调用 validateInvitationCode + consumeInvitationCode)、app/(dashboard)/admin/invitation-codes/page.tsx(Server 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.ts(Promise.all 数组 + messages 对象)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
135 | Server Actions(generate/list/delete/validate,USER_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) |
lib/type-guards.ts |
- | ✅ as 断言修复新增(2026-07-07):isInvitationRole / toInvitationRole 守卫 + 安全窄化函数,替代 data-access.ts 中 row.role as InvitationRole 断言与 generate-invitation-codes-dialog.tsx 中 Select onValueChange 的 as InvitationRole 断言 |
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/rejected;pending/approved → cancelled(仅 requester 本人可撤销 pending)。
导出函数:
- Actions(
actions.ts):createLeaveRequestAction(prevState, formData)— 创建请假申请(LEAVE_REQUEST_CREATE 权限,Zod 校验;二次校验 requester-student 关系:家长代子女需在parentStudentRelations中、学生本人requesterId === studentId;校验 classId 确为学生当前活跃班级;hasOverlappingLeave防止日期重叠;trackEventleave_request.created)reviewLeaveRequestAction(prevState, formData)— 审批请假申请(LEAVE_REQUEST_REVIEW 权限;pending → approved/rejected,rejected 必填 reviewComment;审批通过后调用attendance/data-access.syncExcusedFromLeaveRequest同步考勤,try/catch 不阻断;markAttendanceSynced标志位防重复;trackEventleave_request.reviewed)cancelLeaveRequestAction(id)— 撤销请假(LEAVE_REQUEST_CREATE 权限;仅 requester 本人的 pending 状态可撤销;trackEventleave_request.cancelled)
- Data-access(
data-access.ts):getLeaveRequests(params)— 分页查询(按 DataScope 过滤:all/class_taught/children/class_members/owned;class_members 由 currentUserId 过滤本人提交;批量查询学生/班级/提交人/审批人名称避免 N+1)getLeaveRequest(id, scope, currentUserId?)— 单条详情(按 DataScope 校验访问权限)hasOverlappingLeave(studentId, startDate, endDate, excludeId?)— 日期重叠校验(SQLstartDate <= endDate AND endDate >= startDate,仅查 pending/approved)createLeaveRequest(data, requesterId)— 创建(new Date(startDate)转换字符串为 Date;状态默认 pending)reviewLeaveRequest(id, review, reviewerId)— 审批(Drizzledb.update()返回 ResultSetHeader,需重新db.select()获取更新行)cancelLeaveRequest(id, requesterId)— 撤销(仅 requester 本人的 pending 状态)markAttendanceSynced(id)— 标记 attendanceSynced=true,防止重复触发同步getLeaveRequestsByIds(ids)— 按 ID 批量查询(按 startDate 升序)countPendingForClasses(classIds)— 教师审批待办计数
- Schema(
schema.ts):CreateLeaveRequestSchema(含.refine()跨字段校验 endDate >= startDate)、ReviewLeaveRequestSchema(rejected 时 reviewComment 必填) - Types(
types.ts):LeaveType/LeaveStatus/LeaveRequest/LeaveRequestListItem/LeaveQueryParams/PaginatedLeaveResult/LeaveReviewInput - Components:
LeaveRequestForm— 请假表单(useActionState+useFormStatus);家长模式childOptions下拉选择子女,学生模式 hidden input 写入 studentId/classId。注意:prop 名为childOptions而非children,避免与 React 特殊 children prop 冲突触发react/no-children-propESLint 规则LeaveRequestList— 只读列表展示(STATUS_STYLES + STATUS_ICONS 状态徽章)LeaveReviewDialog— 审批对话框(批准/拒绝按钮切换,拒绝必填 reviewComment)LeaveReviewList— 教师审批列表(pending 项显示审批按钮,打开 LeaveReviewDialog)
依赖关系:
- 依赖:
shared/*(db/auth-guard/track-event/types/permissions)、@/auth、classes(getStudentActiveClass校验当前班级、getClassNamesByIds列表展示)、users(getUserNamesByIds列表展示学生/提交人/审批人姓名)、parent(getParentStudentMapByStudentIds校验家长-子女关系)、attendance(syncExcusedFromLeaveRequest审批通过后同步考勤)、next-intl - 被依赖:
app/(dashboard)/{parent,student,teacher}/leave/page.tsx(Server 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_CREATE(leave_request:create)— admin/parent/studentLEAVE_REQUEST_READ(leave_request:read)— admin/teacher/parent/student/grade_head/teaching_headLEAVE_REQUEST_REVIEW(leave_request:review)— admin/teacher/grade_head/teaching_head
i18n:
- 翻译文件:
shared/i18n/messages/{zh-CN,en}/leave.json - 命名空间:
leave - 注册位置:
src/i18n/request.ts(Promise.all 数组 + messages 对象)
埋点事件:
leave_request.created/leave_request.reviewed/leave_request.cancelled(在shared/lib/track-event.ts的 EventName 联合类型中扩展)
文件清单:
| 文件 | 行数 | 职责 |
|---|---|---|
actions.ts |
~180 | 3 个 Server Action(create/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) |
lib/type-guards.ts |
- | ✅ as 断言修复新增(2026-07-07):isLeaveType / toLeaveType / isLeaveStatus / toLeaveStatus 守卫 + 安全窄化函数,替代 data-access.ts 中 row.leaveType as LeaveType 与 row.status as LeaveStatus 断言 |
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_READ;DataScope.children;表单 childOptions 模式 + 列表)/student/leave— 学生请假页(LEAVE_REQUEST_CREATE + LEAVE_REQUEST_READ;DataScope.class_members 由 currentUserId 过滤;表单 defaultStudentId/classId 模式 + 列表)/teacher/leave— 教师审批页(LEAVE_REQUEST_REVIEW;DataScope.class_taught;LeaveReviewList + LeaveReviewDialog)
设计要点:
- ✅ 幂等考勤同步:
syncExcusedFromLeaveRequest遍历日期区间,仅插入不存在的 excused 记录,避免重复同步;attendanceSynced标志位防止审批重复触发 - ✅ 跨模块联动容错:审批通过后调用考勤同步使用 try/catch 包裹,同步失败不阻断审批结果(审批已成功,考勤可由管理员后续手动补录)
- ✅ 日期重叠校验:
hasOverlappingLeave使用 SQL 条件检测区间重叠,防止重复请假 - ✅ DataScope 行级过滤:
buildScopeFilter支持 5 种 scope(all/class_taught/children/class_members/owned),class_members 由 currentUserId 过滤本人提交 - ✅ Drizzle update 返回值处理:
db.update()返回 ResultSetHeader 而非行数据,需重新db.select()查询更新后的行 - ✅ React children prop 冲突规避:LeaveRequestForm 的 prop 名为
childOptions而非children,避免触发react/no-children-propESLint 规则
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.ts,server-only):
searchQuestions(q, limit?)— 题库检索:P3-6 改用MATCH(questions.contentText) AGAINST(? IN BOOLEAN MODE)全文检索(基于content_textSTORED 生成列的 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()处理 - ✅ 摘要生成:
extractTextFromJson(JSON content → string)、stripHtml(去 HTML 标签)、truncate(截断+省略号)三个内部纯函数 - ✅
as断言清理:所有as const与as SearchResultItem类型收窄均使用类型守卫或字面量标注,符合项目 TypeScript 严格模式
依赖关系:
- 依赖:
shared/db(db 客户端)、shared/db/schema(questions/textbooks/exams/announcements 表)、drizzle-orm(and/or/eq/like/desc/sql) - 被依赖:
app/api/search/route.ts
数据库表(只读):
questions(content JSON 字段、P3-6 新增content_textSTORED 生成列CAST(content AS CHAR)用于 FULLTEXT 索引、type、createdAt)textbooks(title、subject、grade、publisher、createdAt)exams(title、description、status、createdAt)announcements(title、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 |
已拆为 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 |
已拆为 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-2:shared/lib ↔ auth 循环依赖 ✅ 已修复
shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
影响:shared 层无法独立测试/复用;架构上基础设施不应反向依赖应用层。
修复方案(已实施):
- ✅ 创建
shared/lib/session.ts封装 session 获取(getSession(),server-only) - ✅
session.ts内部使用dynamic import("@/auth")打破模块级静态循环 - ✅
audit-logger.ts/change-logger.ts/auth-guard.ts改为import { getSession } from "@/shared/lib/session",不再直接依赖@/auth - ✅ 运行时调用链保持不变,模块加载图无环
P0-3:dashboard 跨模块直接查询 11 张表 ✅ 已修复
dashboard/data-access.ts 的 getAdminDashboardData 原直查 sessions/users/usersToRoles/roles/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions。
修复方案(已实施):
- 各模块新增 dashboard stats 函数:
users/data-access.ts→getUsersDashboardStats()(userCount/activeSessionsCount/userRoleCounts/recentUsers)classes/data-access.ts→getClassesDashboardStats()(classCount)textbooks/data-access.ts→getTextbooksDashboardStats()(textbookCount/chapterCount)questions/data-access.ts→getQuestionsDashboardStats()(questionCount)exams/data-access.ts→getExamsDashboardStats(scope?)(examCount,支持 scope 过滤)homework/stats-service.ts→getHomeworkDashboardStats(scope?)(4 个计数,支持 scope 过滤)
- dashboard 改为并行调用:
Promise.all([getUsersDashboardStats(), getClassesDashboardStats(), ...]) - 返回值结构保持不变,调用方无需修改
P0-4:messaging 绕过 notifications 直接写通知 ✅ 已修复
messaging/actions.ts 原直接调用 createNotification 写 messageNotifications 表,导致用户通知偏好失效、多渠道通知无效。notifications/data-access.ts 和 in-app-channel.ts 反向依赖 messaging 模块。
修复方案(已实施):
- 将
messageNotifications表的 CRUD 函数(createNotification/getNotifications/markNotificationAsRead/markAllNotificationsAsRead/getUnreadNotificationCount)从messaging/data-access.ts迁移到notifications/data-access.ts - 将
notificationPreferences表的 CRUD 函数(getNotificationPreferences/upsertNotificationPreferences)从messaging/notification-preferences.ts迁移到新文件notifications/preferences.ts - 将
NotificationType/Notification/NotificationPreferences/UpdateNotificationPreferencesInput/CreateNotificationInput/GetNotificationsParams/PaginatedResult类型从messaging/types.ts迁移到notifications/types.ts notifications/channels/in-app-channel.ts改为静态导入本地createNotification(不再动态 import messaging)notifications/dispatcher.ts改为从notifications/preferences.ts导入getNotificationPreferences(不再通过getUserNotificationPreferences包装器反向依赖 messaging)- messaging 模块通过 re-export 保持向后兼容:
messaging/data-access.tsre-export 通知 CRUD,messaging/notification-preferences.ts转为 re-export shim,messaging/types.tsre-export 通知类型 - 依赖方向变为单向:messaging → notifications(messaging 调用
sendNotificationdispatcher 发送通知)
P0-5:classSchedule 表三处写入口 ✅ 已修复
classes/data-access.ts(createClassScheduleItem 等)scheduling/actions.ts(applyAutoScheduleAction 直接 transaction 写入)scheduling/data-access.ts(间接)
影响:数据完整性高风险。
修复方案(已实施):
- 将 classSchedule 写函数(
createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem)从classes/data-access-schedule.ts迁移到新文件scheduling/data-access-class-schedule.ts - classes 模块仅保留 READ 函数(
getStudentSchedule/getClassSchedule),不再有任何 classSchedule 写入口 - scheduling 模块通过
classes/data-access.verifyTeacherOwnsClass跨模块校验教师班级归属(合理依赖) classes/actions.ts改为从@/modules/scheduling/data-access-class-schedule导入写函数- 类型
CreateClassScheduleItemInput/UpdateClassScheduleItemInput从classes/types.ts迁移到scheduling/types.ts - 所有 classSchedule DB 写入统一由 scheduling 模块管理(
insertClassScheduleItem/updateClassScheduleItemById/deleteClassScheduleItemById/replaceClassSchedule)
P0-6:proctoring 死代码与重复实现
exam-mode-config.tsx未集成到考试表单(监考功能无法启用)事件上报存在 Server Action 与 REST API 双通道重复✅ 已修复
修复方案(已实施):
- 将
src/app/api/proctoring/event/route.ts移至deletes/api/proctoring/event/route.ts,消除事件上报的 REST API 重复通道 - Server Action
recordProctoringEventAction(proctoring/actions.ts)为唯一规范路径 exam-mode-config.tsx暂保留原位(集成到考试表单属于功能新增,不在本次修复范围)
P0-7:proxy.ts 路由权限跨角色访问漏洞 ✅ 已修复(webapp-testing 发现)
问题:proxy.ts 中 /teacher 和 /parent 路由前缀都使用 EXAM_READ 权限校验,但 student 和 parent 角色也拥有 EXAM_READ 权限,导致:
- 学生可访问
/teacher/*所有页面 - 教师可访问
/parent/*所有页面
修复方案(已实施):
/teacher改为Permissions.EXAM_CREATE(仅 teacher/admin 拥有)/parent改为Permissions.DASHBOARD_PARENT_READ(仅 parent 拥有)- 新增
DASHBOARD_ROUTE_PERMISSIONS细粒度仪表盘权限表,覆盖各角色 dashboard 路由 - 跨角色访问测试验证:teacher/student/parent 访问其他角色路由均被重定向回各自 dashboard
P0-8:list-pagination.tsx 客户端/服务端边界错误 ✅ 已修复(webapp-testing 发现)
问题:shared/components/ui/list-pagination.tsx 文件顶部声明 "use client",但导出的 computePagination() 和 paginate() 纯工具函数被 4 个服务端组件页面直接调用:
teacher/attendance/page.tsxteacher/homework/assignments/page.tsxteacher/homework/submissions/page.tsxteacher/grades/page.tsx
导致 Next.js 16 / React 19 抛出错误:Attempted to call computePagination() from the server but computePagination is on the client,页面渲染时触发 ErrorBoundary。
修复方案(已实施):
- 移除
list-pagination.tsx的"use client"指令 ListPagination组件仅使用Link、Button和图标,无需客户端交互,可作为服务端组件渲染computePagination和paginate恢复为服务端可调用的纯函数
3.2 P1 较严重问题(短期执行)
P1-1:跨模块直接 DB 查询普遍存在 ✅ 已修复
| 被访问表 | 访问次数 | 应归属模块 | 主要违规者 |
|---|---|---|---|
classes |
8+ | classes | ✅ exams/homework/grades/dashboard 已改为通过 classes data-access |
classEnrollments |
6+ | classes | ✅ homework/grades/attendance/users 已改为通过 classes data-access |
users |
6+ | users | ✅ 多个模块已改为通过 users data-access |
subjects |
6+ | school | ✅ exams/homework/questions/grades 已改为通过 school data-access |
exams |
5+ | exams | ✅ homework/grades/dashboard/classes 已改为通过 exams data-access |
homeworkAssignments |
5+ | homework | ✅ classes(反向直查)已改为通过 homework/data-access-classes |
修复方案(已实施):
- ✅ 各模块 data-access 暴露查询接口:
classes/data-access:✅ 已实现 /getClassGradeIdsByClassIdsgetClassStudentsByClassId/getActiveClassStudents/getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId/verifyTeacherOwnsClass/getTeacherIdForMutationsexams/data-access:✅ 已实现(getExamForHomeworkCreationgetExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework/getExamSubmissionWithAnswers/getExamForProctoringCrossModule/getExamSubmissionForProctoringCrossModule/getExamSubmissionsForExam/getExamTitleById)school/data-access:✅ 已实现(含getSubjectOptions/getGradeOptionsgetSubjectNameMapByIds批量科目名称映射,P1-1 新增供 homework/data-access-classes 调用)users/data-access:✅ 已实现(getUserNameByIds/getStudentInfogetUserNamesByIds/getUserWithRole/getUserBasicInfo/getUserIdsByGradeId/getCurrentStudentUser)textbooks/data-access:✅ 已实现getKnowledgePointOptionsquestions/data-access:✅ 已通过insertQuestionWithRelationscreateQuestionWithRelations供 exams 调用 /getKnowledgePointsForQuestionshomework/data-access-classes:✅ 新增 7 个函数供 classes 模块跨模块调用
P1-2:actions 层混入数据访问逻辑 ✅ 已修复
已完成修复(2026-06-17,commit 84d6636):4 个模块的 actions 层 DB 操作全部下沉到 data-access:
| 模块 | 问题 Action | 修复内容 |
|---|---|---|
| exams | updateExamAction / deleteExamAction / duplicateExamAction / getExamPreviewAction / getSubjectsAction / getGradesAction |
✅ 新增 7 个 data-access 函数(getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGrades),actions.ts 831→691 行,data-access.ts 374→471 行。✅ 2026-06-24 进一步拆分:actions.ts 1525→633 行(拆出 actions-helpers.ts/actions-rich-editor.ts/ai-pipeline/auto-mark.ts),P0-4 修复:新增 updateExamWithRichEditorData 下沉富文本编辑器更新事务 |
| homework | createHomeworkAssignmentAction(157 行)/ startHomeworkSubmissionAction / saveHomeworkAnswerAction / submitHomeworkAction / gradeHomeworkSubmissionAction |
✅ 新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行 |
| questions | createQuestionAction / updateQuestionAction / deleteQuestionAction / getKnowledgePointOptionsAction |
✅ 新增 4 个 data-access 函数(createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptions),actions.ts 294→149 行,data-access.ts 138→260 行;✅ 审计新增 3 个级联 data-access 函数(getTextbookOptions/getChapterOptions/getKnowledgePointOptionsByChapter)+ 3 个对应 Server Action + 批量删除/导入导出 data-access 与 Action,actions.ts 149→380+ 行,data-access.ts 260→600+ 行 |
| announcements | 所有写操作 Action | ✅ 新增 5 个 data-access 函数(insertAnnouncement/updateAnnouncementById/deleteAnnouncementById/publishAnnouncementById/archiveAnnouncementById),actions.ts 242→197 行,data-access.ts 120→171 行 |
剩余未修复模块(不在本次 P1-2 范围):
- ✅ users:
已下沉到 data-access(P1-1 修复)updateUserProfileAction直接 db.update - ✅ scheduling:
已改为调用applyAutoScheduleAction/autoScheduleAction直接 db.transaction + db.selectreplaceClassSchedule统一写入口;autoScheduleAction直查 users 表已改为通过 users data-access(P0-5 / P1-1 修复)
P1-3:auth.ts 混合 5 类职责 ✅ 已完成
src/auth.ts 原 293 行混合:NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数。
已完成拆分(auth.ts 现 193 行,仅保留 NextAuth 配置):
- ✅ 密码安全 DB 操作 →
shared/lib/password-security-service.ts(getOrCreatePasswordSecurity / recordFailedLogin / resetFailedLogin,server-only) - ✅ 角色规范化 →
shared/lib/role-utils.ts(normalizeRole / resolvePrimaryRole,纯函数) - ✅ bcrypt 哈希规范化 →
shared/lib/bcrypt-utils.ts(normalizeBcryptHash,纯函数) - ✅ IP 解析 →
shared/lib/http-utils.ts(resolveClientIp,server-only)
后续可选优化(audit-P1-3 已完成 ✅):
- ✅
已抽取至authorize回调可进一步拆分为checkRateLimit/checkAccountLockout/verifyPassword/loadUserRoles,使 auth.ts 降至 ≤150 行modules/auth/services/login-service.ts的authenticateUser()函数(7 步:输入解析/速率限制/用户查询/账户锁定/密码校验/2FA/角色加载),auth.ts authorize 降至 3 行薄包装,整体文件 132 行
audit-P0 重构(2026-06-25):在 P1-3 拆分基础上进一步审计 auth.ts,识别并修复以下问题:
- audit-P0-2:
改为同模块auth.ts跨模块依赖modules/settings/actions-security.verifyTwoFactorForLoginmodules/auth/services/two-factor-service.ts编排,2FA 仍通过modules/settings/data-access-two-factor+modules/settings/lib/totp获取数据(合理跨模块 data-access)- audit-P0-5:
通过auth.ts3 处as断言next-auth.d.ts扩展User接口(添加role?/roles?字段)+"token" in message类型收窄消除- audit-P0-6:
提取proxy.ts与onboarding/data-access.resolveDefaultPathByRoles重复实现角色路径解析shared/lib/route-resolver.ts纯函数resolveDefaultPath(roles),proxy.ts 与 onboarding/actions.ts 共用- audit-P0-7:
e2e smoke-auth 测试选择器与实际中文 UI 不符修正为getByLabel("姓名")/getByLabel("邮箱")/getByLabel("密码")/getByRole("button", { name: "创建账户" })
P1-4:users/import-export.ts 四重职责 ✅ 已完成
users/import-export.ts 原 291 行混合:导入解析 + 导出 + 用户创建(含密码哈希)+ 班级注册(跨模块写 classEnrollments)。
已完成拆分(import-export.ts 现 157 行,仅保留文件解析/生成):
- ✅ 用户创建(含密码哈希、角色分配)→
user-service.ts(batchImportUsers,82 行,server-only) - ✅ 班级注册 →
class-registration.ts(registerStudentByInvitationCode,21 行,server-only) - ✅
batchImportUsers不再直写classEnrollments,改为调用classes/data-access.enrollStudentByInvitationCode - ✅
import-export.ts通过 re-exportbatchImportUsers/UserImportResult保持向后兼容(actions.ts和app/api/export/route.ts无需修改)
P1-5:notifications 反向依赖 messaging ✅ 已完成
notifications/data-access.ts 和 in-app-channel.ts 原反向依赖 messaging 模块的偏好和 createNotification。
已完成修复(与 P0-4 一并解决):
- ✅ 将
messageNotifications和notificationPreferences表所有权移交 notifications 模块 - ✅
notifications/data-access.ts不再 import messaging 模块 - ✅
notifications/channels/in-app-channel.ts改为静态导入本地createNotification(不再动态 import messaging) - ✅
notifications/dispatcher.ts改为从本地preferences.ts导入偏好函数 - ✅ messaging 模块通过 re-export 保持向后兼容
P1-6:三个 logger 重复实现 IP/Header 提取 ✅ 已修复
audit-logger.ts / change-logger.ts / login-logger.ts / auth.ts 四处重复实现 IP/User-Agent 提取逻辑,且实现略有差异。
修复方案(已实施):
- ✅
shared/lib/http-utils.ts新增getUserAgent()函数(与已有resolveClientIp()配套) - ✅
audit-logger.ts/change-logger.ts/login-logger.ts改为从@/shared/lib/http-utils导入resolveClientIp和getUserAgent,删除本地重复实现 - ✅
auth.ts已在 P1-3 中改用resolveClientIp - ✅ 四处实现统一,消除不一致风险(
resolveClientIp取x-forwarded-for第一段,更准确)
3.3 P2 代码质量问题(机会修复)
| 序号 | 问题 | 模块 |
|---|---|---|
| P2-1 | exams/ai-pipeline.ts 857 行,混合 4 类职责 |
exams |
exams/actions.ts 832 行(超 800 建议) |
exams | |
shared/lib/ai.ts 218 行,混合 5 类职责ai/ 目录) |
shared | |
onboarding-gate.tsx 业务逻辑泄漏到 sharedmodules/onboarding/,改用独立路由 + Server Action + middleware 重定向) |
shared/onboarding | |
| P2-5 | global-search.tsx 业务类型硬编码在 shared |
shared |
proxy.ts 硬编码权限字符串,未复用 Permissions 常量Permissions 常量) |
proxy | |
useA11yId Hook 错放在 lib/ 而非 hooks/use-aria-live.ts 已在 hooks/ 目录) |
shared | |
schema.ts 分节编号混乱(section 12 出现在 14b 之后) |
shared/db | |
| P2-9 | audit/actions.ts Excel 导出逻辑内联 |
audit |
| P2-10 | school 模块审计日志不一致(仅 school 实体记录) | school |
announcements 死代码 void wasPublished |
announcements | |
announcements 权限模式不一致(requireAuth vs requirePermission) |
announcements | |
| P2-13 | files try-catch 吞错误 |
files |
elective runLottery 使用 Math.random |
elective | |
elective selectCourse FCFS 并发超卖风险 |
elective | |
| P2-16 | diagnostic 班级报告 studentId 字段复用 |
diagnostic |
layout 用权限反推角色app-sidebar.tsx 改用 hasRole() 判断角色;N3 新增多角色切换机制:SidebarContext.currentRole/setCurrentRole + 侧边栏底部 Select 下拉) |
layout | |
scheduling/actions.ts 末尾 re-export data-accessdata-access 导入) |
scheduling | |
| P2-19 | ExamAssembly / ExamPreviewQuestionEditor 10 个 props |
exams |
| P2-20 | homework/data-access.getDemoStudentUser 使用 auth() 而非 auth-guardusers/data-access.getCurrentStudentUser,6 个 student 页面改用 users 模块;elective 页面改用 getAuthContext();homework 保留 re-export 向后兼容) |
homework |
3.4 解耦优先级路线图
立即执行(P0)
拆分✅ 已完成(拆为 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行)classes/data-access.ts(2104 行 → 按职责拆 3-4 个文件)拆分✅ 已完成(拆为 data-access.ts 626行 + stats-service.ts 414行 + data-access-student.ts 293行 + data-access-scans.ts 44行)homework/data-access.ts(1038 行 → 分离排名逻辑)修复✅ 已完成(新增shared/lib↔auth循环依赖shared/lib/session.ts单一入口,3 个 shared/lib 文件改为通过 getSession 获取 session,dynamic import 打破静态循环)dashboard 改为通过各模块 data-access 获取数据✅ 已完成(P0-3 修复:并行调用各模块 dashboard stats 函数)messaging 写通知改为通过 notifications dispatcher✅ 已完成(P0-4 / P1-5 修复:通知 CRUD 和偏好迁移至 notifications 模块,messaging 通过 dispatcher 发送通知)统一 classSchedule 写入口到 scheduling 模块✅ 已完成(P0-5 修复:classSchedule 写函数从 classes/data-access-schedule.ts 迁移至 scheduling/data-access-class-schedule.ts,classes 模块仅保留读函数)集成 proctoring/exam-mode-config 到考试表单部分完成(P0-6 修复:删除重复的 /api/proctoring/event REST 路由,Server Action 为唯一规范路径;exam-mode-config.tsx 集成属于功能新增,暂保留原位)
短期执行(P1)
actions 层移除直接 DB 操作(exams/homework/questions/announcements/users/scheduling)✅ 已完成(全部 6 个模块均已修复)拆分✅ 已完成(4 个辅助函数组迁移至 shared/lib,auth.ts 保留 NextAuth 配置)auth.ts拆分✅ 已完成(拆为 import-export.ts 157行 + user-service.ts 82行 + class-registration.ts 21行,班级注册改为调用 classes/data-access)users/import-export.ts消除 notifications → messaging 反向依赖✅ 已完成(P0-4 / P1-5 修复:通知表所有权迁移至 notifications,in-app-channel 改为静态导入)提取✅ 已完成(新增shared/lib/http-utils.ts统一 IP 提取getUserAgent,三个 logger 统一复用resolveClientIp/getUserAgent)各模块暴露跨模块查询接口(见 P1-1)✅ 已完成(所有跨模块直查已改为通过对方 data-access 接口)
中期执行(P2)
建立模块间数据访问规范(通过对方 data-access 或导出查询函数)✅ 已完成(P1-1 修复)✅ 已完成(P2-8 修复:重新编号为连续 1-24,消除 8b/14b/乱序问题)schema.ts按业务域分节- 拆分
exams/ai-pipeline.ts 拆分✅ 已完成(P2-2,commit 6588f74,拆分为shared/lib/ai.tsai/目录 6 个文件,原 ai.ts 保留为重导出)- shared 层业务逻辑下沉到 modules 层
- 代码质量问题逐项修复(✅ 大部分已修复: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_membersscope 内置eq(gradeRecords.studentId, currentUserId)过滤;P3-26:从lib/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 schema(schema.ts):
publishLessonPlanHomeworkSchema:发布作业输入校验(planId/blockId 必填,classIds 至少 1 个,availableAt/dueAt 日期格式校验)- 导出类型
PublishLessonPlanHomeworkInput = z.infer<typeof publishLessonPlanHomeworkSchema>
data-access-versions.ts 事务变更:
revertToVersion包裹db.transaction确保原子性(回滚版本时同步更新 lessonPlans.content 与版本记录)createLessonPlanVersion已使用db.transaction(版本号自增 + 插入版本记录原子化)
3.6.5 其他修复(不涉及签名变更,仅记录范围)
- 越权访问修复:15+ 处页面添加
requirePermission,data-access 层添加 scope 过滤 - 功能性 BUG 修复:question-actions 删除字段名、question-columns 时间显示、textbook-card 删除按钮、block-renderer 拖拽
- 统一错误处理:所有 Server Action catch 块改用
handleActionError - 客户端 Action 调用添加 try/catch/finally(20+ 处,使用
safeActionCall) JSON.parse改用safeJsonParse(8+ 处)- Date 解析添加校验(15+ 处,使用
safeParseDate) - 数据库事务添加:
batchCreateGradeRecords、deleteChapter、revertToVersion - 性能优化:
Promise.all并行化、DB 层分页(gradesgetGradeRecords) - TypeScript
as断言清理(30+ 处,改用类型守卫) - React 性能修复:
useMemo、useCallback、keyprop、渲染期间副作用移除
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(消除直查 DB);withApiErrorHandler + 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;字段名 error → message、ok → success |
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 统一版本;为所有失败分支添加 errorCode(validation_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.url → data.data.url,data.id → data.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.deletedCount → body.data?.deletedCount |
modules/users/components/user-import-dialog.tsx |
data.sheets?.[0]?.rows → data.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→ 404ValidationError/BusinessError→ 400- 未认证 → 401
- 限流 → 429
- 其他 → 500
ActionState.errorCode → 状态码映射:
not_found→ 404validation_error→ 400auth_required→ 401permission_denied→ 403- 其他 → 500
3.8 数据库访问层审计专项(2026-07-07)
完整审计报告:data-access-audit-v1.md 结构化数据:data-access-audit-v1-data.json 速查手册:known-issues.md 第二十七节
3.8.1 审计概况
| 指标 | 数值 |
|---|---|
| 审计文件 | 101(86 data-access + 15 actions 辅查) |
| 问题总数 | 230 |
| P0 Critical | 17 |
| P1 High | 48 |
| P2 Medium | 105 |
| P3 Low | 60 |
3.8.2 高风险模块(P0+P1 ≥ 3)
✅ Phase 1-4 全部修复完成(2026-07-07),tsc + lint 0 errors。
| 模块 | P0 | P1 | 主要问题 | 修复状态 |
|---|---|---|---|---|
| messaging | 1 | 4 | 单文件 1089 行超硬限 + 业务逻辑嵌入 | ✅ Phase 0 P0-5:拆分为 5 子文件 + barrel |
| classes | 1 | 4 | cacheFn 全缺失 + 邀请码状态机 + N+1 | ✅ Phase 1 F-01 + Phase 3 P-03:N+1 修复 + 21 函数 cacheFn 包装 + lib/admin-class-mappers 去重 |
| school | 0 | 5 | 938 行 + 30+ 导出 + 12 console.error + 角色判断 | ✅ Phase 2 S-01/A-10/A-02/F-09:拆 6 子文件 + lib/school-permissions + 移除 console.error + promoteGrades 事务 |
| lesson-preparation | 1 | 3 | LIKE 全表扫描 + N+1 + 无 LIMIT | ✅ Phase 1 F-01/F-05 + Phase 2 F-02:BFS 批量删除 + DEFAULT_LIMIT + 前缀匹配 + lib/status-mappers 去重 |
| scheduling | 1 | 2 | 跨模块 schema 直查 + 业务逻辑嵌入 | ✅ Phase 1 A-06/A-02:跨模块改 data-access 接口 + 校验逻辑抽离至 lib/schedule-validation.ts |
| adaptive-practice | 1 | 2 | 2N+1 循环查询 | ✅ Phase 1 F-01:getTeacherClassPracticeOverviewsRaw 批量学生 ID + GROUP BY 单条聚合 |
| elective | 0 | 3 | 抽签算法 + 冲突检测 + i18n 通知嵌入 data-access | ✅ Phase 1 A-02:抽签算法抽离至 lib/lottery.ts、冲突检测抽离至 lib/schedule-conflict.ts |
| textbooks | 2 | 1 | 跨模块 schema 直查 + N+1 | ✅ Phase 1 A-06/F-01:graph 改调 questions/diagnostic 接口 + reorderChapters 事务并行 UPDATE |
3.8.3 P0 关键问题(17 条)
安全漏洞(4 条)
audit/actions.tspurgeAuditLogsAction 用读权限执行物理删除(权限提权)parent/模块缺失 actions.ts,3 页面直访 data-access(绕过权限校验)exams/data-access.ts与onboarding/data-access.ts缺import "server-only"(DB 逻辑泄露风险)
架构硬违规(5 条)
textbooks/data-access-graph.ts直查 questions + diagnostic 模块表(✅ A-06 已修复:改调 questions/diagnostic data-access 批量接口)scheduling/data-access.ts直查 classes/users/subjects 三模块表(✅ A-06 已修复:改调 classes/users/school data-access 接口)scheduling/data-access-class-schedule.ts含校验+状态机业务逻辑(✅ A-02 已修复:校验逻辑抽离至 lib/schedule-validation.ts)onboarding/actions.ts直查 DB(违反三层架构)messaging/data-access.ts1089 行超 1000 硬限
必定性能问题(8 条)
- 4 处递归/循环 N+1:questions(deleteQuestionRecursive/Batch)、lesson-preparation(deleteComment)、textbooks(reorderChapters)— ✅ F-01 已修复:BFS 批量收集 + inArray 批量删除/并行 UPDATE
- classes 24+ 读函数未走 cacheFn
- classes getTeacherClassesRaw 2N+1 — ✅ F-01 已修复:课表批量查询 + Map 分组
- course-plans reorderCoursePlanItems N+1 + 未包裹事务 — ✅ F-01/F-09 已修复:db.transaction 包裹并行 UPDATE
- adaptive-practice getTeacherClassPracticeOverviewsRaw 2N+1 — ✅ F-01 已修复:批量学生 ID + GROUP BY studentId 单条聚合
3.8.4 治理路线图
✅ Phase 0-4 全部完成(2026-07-07),tsc + lint 0 errors。
| Phase | 范围 | 工作量 | 完成标准 | 状态 |
|---|---|---|---|---|
| Phase 0 | P0 安全漏洞 | XS-S | server-only 补齐 + parent actions 新建 + audit 权限点新增 | ✅ 已完成 |
| Phase 1 | P0 架构+性能 | M-L | 跨模块 schema 治理 + messaging 拆分 + N+1 修复 + 业务逻辑下移 | ✅ 已完成 |
| Phase 2 | P1 性能+结构 | M-L | LIKE 治理 + 超长文件拆分 + school 重构 + 事务修复 | ✅ 已完成 |
| Phase 3 | P2 模式标准化 | S-M | cacheFn 补齐 + SELECT * 改显式 + helper 提取 + JSDoc | ✅ 已完成 |
| Phase 4 | P3 风格优化 | XS | as 断言 + 非空断言 + export * + 死代码 | ✅ 已完成 |
Phase 1-4 修复明细
Phase 1(P0 架构+性能):
- A-06 跨模块 schema 直查修复:5 文件(textbooks/data-access-graph、scheduling/data-access、lesson-preparation/data-access、lesson-preparation/data-access-schedules、questions/data-access)改调对方 data-access 批量接口;新增 10 个跨模块接口(详见 §3.8.6)
- F-01 N+1 热点修复:8 处(questions 递归删除 BFS + inArray 批量、lesson-preparation deleteComment BFS、textbooks reorderChapters 事务并行 UPDATE、course-plans reorderCoursePlanItems 事务并行、classes getTeacherClassesRaw 批量+Map、classes enrollTeacherByInvitationCode 事务、adaptive-practice getTeacherClassPracticeOverviewsRaw GROUP BY);新增 getActiveStudentIdsByClassIdsBatch
- A-02 业务逻辑下沉:scheduling/lib/schedule-validation.ts(6 纯函数)、elective/lib/lottery.ts(3 函数)+ lib/schedule-conflict.ts(4 函数)
Phase 2(P1 性能+结构):
- F-02 LIKE 全表扫描:6 处改前缀匹配 + escapeLikePattern 转义;questions 改 FULLTEXT MATCH AGAINST
- school 模块重构(S-01/A-10/A-02/F-09):938 行拆为 6 子文件 + barrel + lib/school-permissions.ts;移除 12 console.error;promoteGrades 事务包裹;fetchGradesWithHeads helper 去重
- files 模块错误处理(P-05/A-10/F-03):移除 12 console.error + try-catch 吞错;SELECT * 改显式列
- F-09 事务修复:auth createUser 事务、school promoteGrades 事务
- F-05 LIMIT 保护:lesson-preparation(5 文件)+ textbooks + classes + proctoring 无分页查询加 DEFAULT_LIMIT=1000
- grades-analytics 拆分(S-01):831 行 → 4 子文件(trend/class/student/overview)+ barrel
Phase 3(P2 模式标准化):
- P-03 cacheFn 覆盖:34 读函数包装(classes 21 + questions 5 + textbooks 3 + lesson-preparation 5),均采用 Raw + Wrapper 配对
- F-03 SELECT * 改显式列:diagnostic 3 处;textbooks pre-existing 语法错误修复
- S-03 重复 helper 去重:lesson-preparation/lib/status-mappers.ts(toLessonPlanStatus/toReviewDecision)、classes/lib/admin-class-mappers.ts(buildSubjectsByClassIdMap/composeAdminClassList/isClassSubject/toClassSubject)、lesson-preparation isStringArray 提取到 lib/type-guards.ts
- S-06 JSDoc 补全:lesson-preparation 版本/模板(8 函数)+ questions(7 函数)+ textbooks(22 函数)
Phase 4(P3 风格优化):
- P-09 as 断言:classes/lib/admin-class-mappers.ts + onboarding/actions.ts 改类型标注常量
- P-09 非空断言:lesson-preparation data-access 6 处改 null 检查
- S-05 export * → 显式 re-export:14 个 barrel 子文件改具名导出;5 个保留 export *(>15 导出)
- 死代码:未发现确认死代码
3.8.5 架构图待补记项
✅ Phase 0 P0 修复已完成(2026-07-07),以下待补记项已全部同步至 004/005 架构文档:
- ✅ parent 模块缺失 actions.ts —— 已新建
src/modules/parent/actions.ts,6 个 Server Action 已补记至 004 §2.19 与 005 modules.parent.exports.actions - ✅ onboarding/actions.ts 直查 DB —— 已改为调用 data-access 函数,dependencyMatrix 已修正(005 onboarding.auditNote 已更新)
- ✅ messaging/data-access.ts 拆分后 —— 5 个子文件已补记至 004 §2.13 文件清单与 005 modules.messaging(auditNotes 已更新)
- ✅ 新增权限点 AUDIT_LOG_PURGE / AUDIT_RETENTION_MANAGE —— 已补记至 004 §2.15 与 005 permissions + rolePermissions.admin
- ✅ 新增 shared/lib/date-utils.ts —— 已补记至 004 §2.1 导出函数与文件清单、005 modules.shared.exports.functions
✅ Phase 1-4 修复已完成(2026-07-07),以下待补记项已全部同步至 004/005 架构文档:
- ✅ scheduling/lib/schedule-validation.ts —— 6 个纯函数已补记至 004 §2.9 文件清单与 005 modules.scheduling.exports.lib
- ✅ elective/lib/lottery.ts + lib/schedule-conflict.ts —— 7 个纯函数已补记至 004 §2.20 文件清单与 005 modules.elective.exports.lib
- ✅ school 模块拆分(938 行 → 6 子文件 + barrel + lib/school-permissions.ts)—— 已补记至 004 §2.8 文件清单与 005 modules.school.exports(dataAccess 函数 file 字段更新 + lib 节点新增)
- ✅ grades/data-access-analytics.ts 拆分(831 行 → 4 子文件 + barrel)—— 已补记至 004 §2.6 文件清单与 005 modules.grades.exports.dataAccess(7 函数 file 字段更新 + 3 函数补图)
- ✅ classes/lib/admin-class-mappers.ts —— 4 函数 + 2 接口已补记至 004 §2.7 文件清单与 005 modules.classes.files
- ✅ lesson-preparation/lib/status-mappers.ts —— toLessonPlanStatus/toReviewDecision 已补记至 004 §2.27 文件清单与 005 modules.lesson-preparation.exports
- ✅ 10 个跨模块接口(getTextbookTitlesByIds/getChapterTitlesByIds/getKnowledgePointNamesByIds/getQuestionCountByKpIds/getStudentMasteryByKpIds/getClassMasteryByKpIds/getClassesForScheduling/getClassSubjectAssignmentsByClassId/getDistinctTeacherIdsFromAssignments/getClassroomsForScheduling)—— 已补记至 004 §3.8.6 与 005 对应模块 exports.dataAccess
- ✅ getActiveStudentIdsByClassIdsBatch —— 已补记至 005 modules.classes.exports.dataAccess
- ✅ getGradesByIds(school 模块新增供 school-permissions 使用)—— 已补记至 005 modules.school.exports.dataAccess
- ✅ cacheFn 覆盖 34 读函数(classes 21 + questions 5 + textbooks 3 + lesson-preparation 5)—— Raw + Wrapper 配对已补记至 005 对应模块 exports.dataAccess(file 字段更新)
3.8.6 A-06 跨模块 schema 直查修复(2026-07-07)
Phase 1 A-06 规则修复:5 处 data-access 文件直接 import 并查询其他模块 DB schema 表,改为调用对方 data-access 跨模块接口。
新增跨模块批量接口(cacheFn Raw+Wrapper 模式):
| 模块 | 新增接口 | 用途 |
|---|---|---|
| textbooks | getTextbookTitlesByIds(ids): Map<id, title> |
供 lesson-preparation 批量解析教材标题 |
| textbooks | getChapterTitlesByIds(ids): Map<id, title> |
供 lesson-preparation 批量解析章节标题 |
| textbooks | getKnowledgePointNamesByIds(ids): Map<id, name> |
供 questions 批量解析知识点名称 |
| questions | getQuestionCountByKpIds(ids): Map<id, count> |
供 textbooks/graph 批量查关联题目数 |
| diagnostic | getStudentMasteryByKpIds(sid, ids): Map<id, MasterySnapshot> |
供 textbooks/graph 查学生掌握度 |
| diagnostic | getClassMasteryByKpIds(sids, ids): Map<id, MasterySnapshot> |
供 textbooks/graph 聚合班级掌握度 |
| classes | getClassesForScheduling(): ClassSchedulingOption[] |
供 scheduling 获取班级选择器 |
| classes | getClassSubjectAssignmentsByClassId(cid): ClassSubjectAssignment[] |
供 scheduling 获取班级科目分配 |
| classes | getDistinctTeacherIdsFromAssignments(): string[] |
供 scheduling 获取有任课的教师 ID |
| school | getClassroomsForScheduling(): ClassroomOption[] |
供 scheduling 获取教室选择器 |
修复的 5 个文件:
| 文件 | 原违规 | 修复方式 |
|---|---|---|
textbooks/data-access-graph.ts |
直查 questionsToKnowledgePoints + knowledgePointMastery |
改调 questions/diagnostic data-access 批量接口 |
scheduling/data-access.ts |
直查 classes/classSubjectTeachers/classrooms/subjects/users |
改调 classes/users/school data-access 接口 |
lesson-preparation/data-access.ts |
直查 textbooks + chapters 表 JOIN |
移除 JOIN,改调 textbooks 批量标题接口 |
lesson-preparation/data-access-schedules.ts |
直查 classes 表 JOIN |
移除 JOIN,改调 classes 批量名称接口 |
questions/data-access.ts |
直查 knowledgePoints 表 JOIN |
移除 JOIN,改调 textbooks 批量名称接口 |
✅ npx tsc --noEmit 零错误,npm run lint 零错误
附录 A:模块间依赖矩阵
行表示使用方,列表示被使用方。
✅合理依赖,❌违规直查,⟳循环依赖。 ✅ P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口。
| ↓ 使用 → | shared | auth | exams | homework | questions | textbooks | classes | school | dashboard | users | grades | messaging | notifications | lesson-prep | error-book | 其他 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| shared | - | ⟳✅已修复 | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| auth(root) | ✅ db/lib | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| exams | ✅ | ✅ | - | - | ✅data-access | - | ✅data-access | ✅data-access | - | - | - | - | - | - | - | - |
| homework | ✅ | ✅ | ✅data-access | - | ✅关系 | - | ✅data-access | ✅data-access | - | ✅data-access | - | - | - | - | - | - |
| questions | ✅ | ✅ | - | - | - | ✅data-access | - | - | - | - | - | - | - | - | - | - |
| textbooks | ✅ | ✅ | - | - | ✅UI | - | - | - | - | - | - | - | - | - | - | - |
| classes | ✅ | ✅ | ✅data-access | ✅data-access | - | - | - | ✅data-access | - | - | - | - | - | - | - | ✅scheduling(P0-5:data-access-class-schedule 写函数) |
| school | ✅ | ✅ | - | - | - | - | - | - | - | ⚠️可接受 | - | - | - | - | - | - |
| grades | ✅ | ✅ | ✅外键 | ✅外键 | - | - | ✅data-access | ✅data-access | - | ✅data-access | - | - | - | - | - | - |
| dashboard | ✅ | ✅ | ✅data-access | ✅data-access | ✅data-access | ✅data-access | ✅data-access | - | - | ✅data-access | - | - | - | - | - | - |
| users | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | - | - | - | ✅rbac(UserRoleAssignDialog 组件) |
| 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/notifications(v2-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
- 由
auth.tsJWT callback 从users表查询产生,存入 JWT - 通过
session.user.id传递到所有 Server/Client Components - 通过
getAuthContext().userId传递到所有 Server Actions - 在
auth-guard.ts中用于查询usersToRoles(角色)和classSubjectTeachers/grades(DataScope) - 在
exams/actions.ts中作为creatorId写入exams表 - 在
homework/actions.ts中作为creatorId写入homeworkAssignments表 - 在
classes/data-access.ts中查询getTeacherClasses(teacherId)/getGradeManagedClasses(userId) - 在
elective/actions.ts中作为teacherId/studentId用于选课过滤
examId
- 由
exams/actions.createExamAction产生(CUID2),写入exams表 - 被
exams/data-access.getExamById(id)读取 - 被
exams/actions的updateExamAction/deleteExamAction/duplicateExamAction用于定位考试 - 传入
homework/actions.createHomeworkAssignmentAction的sourceExamId参数 - 在
homeworkAssignments表中作为外键关联到源考试 - 被
homework/data-access.getHomeworkAssignmentAnalytics用于追溯作业来源
classId
- 由
classes/actions的createTeacherClassAction/createAdminClassAction产生 - 被
classes/data-access.getClassStudents(classId)读取学生列表 - 被
classes/data-access.getClassSchedule(classId)读取课表 - 被
classes/data-access.getClassHomeworkInsights(classId)读取作业洞察(✅ P0-7 已修复:通过homework/data-access-classes获取数据) - 被
homework/data-access.getHomeworkAssignments({ classId })过滤作业列表 - 在
auth-guard.ts中通过classSubjectTeachers查询教师关联的 classIds,构建DataScope.class_taught
permission
- 由
shared/types/permissions.ts的Permissions常量定义(61 个权限点) - 在
shared/lib/permissions.ts中通过ROLE_PERMISSIONS映射角色到权限列表 - 在
auth.tsJWT callback 中通过resolvePermissions(roleNames)合并多角色权限,存入 JWT - 在
proxy.tsmiddleware 中通过token.permissions检查路由访问权限 - 在
shared/lib/auth-guard.ts中通过requirePermission(permission)在 Server Action 层断言权限 - 在
shared/hooks/use-permission.ts中通过hasPermission(permission)在客户端组件中条件渲染 - 在
layout/config/navigation.ts中作为NavItem.permission字段过滤侧边栏菜单
DataScope
- 由
auth-guard.ts的resolveDataScope(userId, roles)根据用户角色和 DB 关系动态计算 - 支持类型:
all/grade_managed/class_taught/class_members/children/owned - 传递到
exams/homework/grades/attendance/elective/dashboard的 data-access 进行行级过滤 - 对 parent 角色,查询
parentStudentRelations表构建{ type: "children", childrenIds: string[] } - 在
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_BIND;audit-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 → BigInt(parseBase36ToBigInt),避免 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 重构新增)
// types.ts —— CacheFnOptions / CacheStore / InvalidationRule 接口定义(纯类型,无运行时依赖)
// memory-store.ts —— MemoryCacheStore 实现(Map + LRU + tag 反查索引,单实例 dev/test 用)
// redis-store.ts —— RedisCacheStore 实现(多实例共享,故障降级回退 producer)
// store-factory.ts —— getCacheStore() 单例工厂:默认 MemoryCacheStore,CACHE_DRIVER=redis 时懒加载 RedisCacheStore
// cache-fn.ts —— cacheFn 双层包装器:外层 react.cache 请求级 memoization,内层 cacheStore.getOrSet 跨请求缓存(按 tag 失效,TTL 可选)
// invalidation-map.ts —— INVALIDATION_MAP 集中式失效映射表 + fillTemplate 模板填充(含服务端 tags/queryKeys/paths 三段)
// client-invalidation-map.ts —— CLIENT_INVALIDATION_MAP 客户端可见子集(仅 queryKeys 字段,避免客户端 bundle 拉入 revalidateTag 等 server-only 依赖)
// invalidate.ts —— invalidateFor 三步编排函数(Server Action 写后调用)
// index.ts —— barrel re-export:cacheFn / invalidateFor / INVALIDATION_MAP / fillTemplate / CLIENT_INVALIDATION_MAP + 类型
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."
CLIENT_INVALIDATION_MAP // 客户端可见子集:仅 queryKeys 字段,由 useActionMutation 在 onSuccess 中查询
// shared/lib/redis-client.ts (P1 重构新增:共享 Redis 客户端单例)
// rate-limit + cache 共用,复用 env.UPSTASH_REDIS_REST_URL/TOKEN
// 动态 import + webpackIgnore,包未安装/未配置返回 null(fail-open 降级)
getRedisClient(): Promise<unknown | null>
// shared/lib/query-keys.ts (P1 重构新增:客户端 queryKey 工厂)
// 命名约定:[module, resource, ...args]
// 失效时使用前缀匹配:invalidateQueries({ queryKey: ["classes", "detail"] }) → 失效所有 ["classes", "detail", *]
queryKeys // 工厂对象,含 classes.{all,lists,list,details,detail,students,schedule,stats,invitations}
业务模块核心 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 获取数据
getActiveStudentIdsByClassIdsBatch(classIds: string[]): Promise<Map<string, string[]>> // 批量获取多班级活跃学生 ID,消除 N+1(F-01)
// 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.json的routes节点 - 新增数据库表:更新
shared/db/schema.ts分节 +005的dbTables节点
完整路由表、DevOps 脚本、E2E 测试等信息见
005_architecture_data.json。