@@ -540,8 +540,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**职责 ** :考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题。
**导出函数 ** :
- Actions: `createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` (✅ P1-2 已修复: actions 层不再直接访问 DB, 全部下沉到 data-access)
- Data-access: `getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` / `getExamCreatorId` / `updateExamWithQuestions` / `deleteExamById` / `duplicateExam` / `getExamPreview` / `getExamSubjects` / `getExamGrades` (后 7 个为 P1-2 新增)
- Actions: `createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` / `getExamsByGradeIdAction` (✅ v4-P2-7 新增: 年级仪表盘维度3, 按 gradeId 查询年级下所有考试 + 提交统计, EXAM_READ 权限) (✅ P1-2 已修复: actions 层不再直接访问 DB, 全部下沉到 data-access)
- 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 新增)
- AI Pipeline: `generateAiCreateDraftFromSource` / `generateAiPreviewData` / `regenerateAiQuestionByInstruction`
- Utils: `normalizeStructure` ( v3 新增:将持久化的 `exam.structure` unknown JSON 运行时校验并归一化为类型安全的 `ExamNode[]` ,类型守卫模式无 `as` 断言,从 `teacher/exams/[id]/build/page.tsx` 提取)
- Stats-service( V3-8 新增):`getExamAnalytics` ( cache 包装,聚合考试所有作业的已批改提交,计算平均分/及格率/分数段分布/逐题错误率与难度等级,对标智学网考试分析)+ `ExamAnalyticsSummary` 类型
@@ -725,10 +725,10 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**职责 ** :成绩分析(录入/查询/统计/导出/趋势对比分析)。
**导出函数 ** :
- Actions: `getGradeRecordsAction` / `createGradeRecordAction` ( v4-P1-6 增强:成绩录入后通知学生和家长,调用 `notifyGradeEntered` ) / `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction` ( v4-P1-12 增强:新增可选 `studentId` 参数,支持按学生导出,家长视角调用 `exportStudentGradeRecordsToExcel` ,校验 studentId 属于家长子女)/ `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction` ( v4-P1-6 增强:批量成绩录入后通知学生和家长)/ `assertClassInScope` (✅ P3 新增导出:班级 scope 校验工具,供 actions-analytics 复用)/ `saveGradeDraftAction` / `getGradeDraftAction` / `deleteGradeDraftAction` (✅ v3-P2 新增:成绩录入草稿 Server Actions, 分别使用 GRADE_RECORD_MANAGE/GRADE_RECORD_READ/GRADE_RECORD_MANAGE 权限)
- Data-access: `getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getRankingTrend` / `PaginatedGradeRecords` (✅ P3 新增:分页结果接口 `{ records, total }` ) / `saveGradeDraft` / `getGradeDraft` / `deleteGradeDraft` (✅ v3-P2 新增:成绩录入草稿 CRUD, upsert + 24 小时过期)/ `getExamOptionsForGrades` / `getSchoolWideGradeSummary` (✅ v3-P2 新增:考试选项查询 + 全校各年级成绩汇总,管理员视图按年级聚合平均分/及格率/优秀率/学生数/班级数,加权平均计算全校汇总)
- Types( ✅ v3-P2 新增):`SchoolWideGradeSummaryItem` ( 全校汇总按年级聚合项: gradeId/gradeName/schoolName/classCount/studentCount/averageScore/passRate/excellentRate/recordCount) / `SchoolWideGradeSummary` ( 全校汇总: grades 数组 + totals 汇总对象)/ `GradeDraftData` (草稿数据接口:{ scores: Record<string, string>, timestamp: number },位于 data-access. ts)
- Lib( ✅ P1-2 新增,✅ P3 更新签名,✅ P3-26 拆分):`toNumber` / `normalize` (位于 `lib/grade-utils.ts` ) ; `buildScopeClassFilter(scope, currentUserId?)` ( P3-26 从 grade-utils.ts 迁移至 `lib/scope-filter.ts` , P3 修复:`class_members` scope 内置 studentId 过滤,需传入 currentUserId 参数)
- 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 增强:批量成绩录入后通知学生和家长)/ `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 新增:考试选项查询 + 全校各年级成绩汇总,管理员视图按年级聚合平均分/及格率/优秀率/学生数/班级数,加权平均计算全校汇总)
- Types( ✅ v3-P2 新增,✅ v4-P2-7 新增年级分布类型 ) : `SchoolWideGradeSummaryItem` / `SchoolWideGradeSummary` / `GradeDraftData` (草稿数据接口)/ `GradeDistributionByGradeResult` (✅ v4-P2-7 新增:年级维度成绩分布结果,含 overall 整体分布 + stats 统计 + byClass 按班级拆分数组)/ `GradeDistributionByGradeClassItem` (✅ v4-P2-7 新增: 按班级拆分的分布项: classId/className/distribution/sta ts)
- 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_members` scope 内置 studentId 过滤,需传入 currentUserId 参数); `assertClassInScope(scope: DataScope, classId: string): string | null` (✅ v4-P2-6 从 actions.ts 迁移至 `lib/scope-check.ts` :校验 classId 是否在 scope 允许范围内,供 actions.ts 与 actions-analytics.ts 复用。迁移原因: actions.ts 是 "use server" 文件要求所有 export 为 async, 而 assertClassInScope 是同步函数)
- Stats-service( ✅ P1-1 新增):`computeGradeStats` / `computeAverageScore` / `buildGradeTrendPoints` / `computeTrendAverage` / `computeClassComparisonStats` / `computeSubjectComparisonStats` / `computeGradeDistribution` / `buildRankingTrendPoints` (从 3 个 data-access 文件抽取的纯函数,使数据层专注 DB I/O, 统计逻辑可独立测试)
- Export( ✅ v4-P1-12 新增):`exportGradeRecordsToExcel` / `exportClassGradeReportToExcel` / `exportStudentGradeRecordsToExcel` ( v4-P1-12 新增:导出单个学生成绩单家长视角,仅含成绩明细 + 个人统计, 不含班级数据, scope 为 children 自动按 studentId 过滤)/ `formatDateForFile` (已迁移至 shared/lib/utils)
- Components( ✅ P1-5 新增):`WidgetBoundary` ( Error Boundary + Suspense + Skeleton 组合,含 a11y 属性)/ `SchoolWideSummaryCard` (✅ v3-P2 新增: 管理员全校成绩汇总卡片, 4 个统计卡片 + 各年级对比表格)/ `ScoreCell` (✅ v4-P1-7 新增:成绩单元格组件,根据得分率着色——红<60%/黄60-84%/绿≥85%,使用语义化 Tailwind 类名避免动态拼接, fullScore<=0 时不着色)
@@ -783,12 +783,13 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ 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` 新增多选复选框(全选/单选)+ 批量删除工具栏 + 批量删除确认对话框;新增 `bulkDeleteGradeRecords` data-access 函数(使用 inArray 一次性删除避免 N+1) + `bulkDeleteGradeRecordsAction` Server Action( 限制单次最多 500 条)
- ✅ v4-P3-2 改进( 2026-06-23) : `batch-grade-entry.tsx` 顶部新增可折叠新手引导提示框( 4 步使用说明),使用 localStorage 记住用户关闭状态避免重复显示
- ✅ v4-P2-6 修复( 2026-06-23) : ~~`assertClassInScope` 是同步函数但位于 "use server" 文件 actions.ts 中~~ Next.js 要求 "use server" 文件中所有 export 必须为 async, 同步 export 会导致构建错误。修复:将 `assertClassInScope` 迁移至独立文件 `lib/scope-check.ts` (含 `import "server-only"` ) , actions.ts 与 actions-analytics.ts 均从 `./lib/scope-check` 导入
**文件清单 ** :
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 670+ | 19 个 Server Action( 含 Zod 校验,含 v2-P1-5 安全修复: assertClassInScope + 行级 scope 校验; P3 修复: handleActionError + safeJsonParse + scope 传递 + DB 层分页; v3-P2 新增: saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftAction; v4-P1-6: createGradeRecordAction/batchCreateGradeRecordsAction 新增通知; v4-P1-12: exportGradesAction 新增 studentId 参数; v3-P3-2 新增: bulkDeleteGradeRecordsAction 批量删除) |
| `actions-analytics.ts` | 170 | 5 个分析 Action( 含 Zod 校验, P3 修复: handleActionError + assertClassInScope 校验) |
| `actions.ts` | 670+ | 19 个 Server Action( 含 Zod 校验,含 v2-P1-5 安全修复: assertClassInScope + 行级 scope 校验; P3 修复: handleActionError + safeJsonParse + scope 传递 + DB 层分页; v3-P2 新增: saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftAction; v4-P1-6: createGradeRecordAction/batchCreateGradeRecordsAction 新增通知; v4-P1-12: exportGradesAction 新增 studentId 参数; v3-P3-2 新增: bulkDeleteGradeRecordsAction 批量删除; v4-P2-6: assertClassInScope 迁移至 lib/scope-check.ts) |
| `actions-analytics.ts` | 170 | 5 个分析 Action( 含 Zod 校验, P3 修复: handleActionError + assertClassInScope 校验; v4-P2-6: assertClassInScope 改从 ./lib/scope-check 导入) |
| `data-access.ts` | 450+ | 成绩 CRUD + 统计 + 草稿(含 v2-P2-9 修复: recorderName 批量查询; P3 修复: PaginatedGradeRecords 接口 + DB 层分页 + 事务 + 存在性检查 + scope 过滤 + 并列排名; v3-P2 新增: saveGradeDraft/getGradeDraft/deleteGradeDraft + GradeDraftData 接口; v3-P3-2 新增: bulkDeleteGradeRecords 使用 inArray 批量删除) |
| `data-access-analytics.ts` | 200+ | 趋势/对比分析( P3 修复: getClassComparison 应用 buildScopeClassFilter; v3-P2 新增: getExamOptionsForGrades/getSchoolWideGradeSummary; getGradeTrend/getClassComparison/getSubjectComparison/getGradeDistribution 新增 semester/examId 可选参数) |
| `data-access-ranking.ts` | 83 | 排名查询( P3 修复: getRankingTrend 接受 scope 参数 + class_taught 校验) |
@@ -797,6 +798,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `schema.ts` | 113+ | Zod 校验(含 12 个查询 schema; P3 修复: score .max(1000) + records .max(500) + 补全查询字段; v3-P2 新增: grade_drafts 表定义第 1444-1469 行) |
| `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"` ) |
| `types.ts` | 168+ | 类型定义( v3-P2 新增: SchoolWideGradeSummaryItem/SchoolWideGradeSummary) |
| `components/widget-boundary.tsx` | 136 | Widget 边界组件( P1-5 新增, v2-P1-1 已在 3 个页面应用) |
| `components/school-wide-summary-card.tsx` | - | v3-P2 新增: 管理员全校成绩汇总卡片( 4 个统计卡片 + 各年级对比表格) |
@@ -870,7 +872,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**导出函数 ** :
- 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` / `getOrgTree` / `getSubjectOptions` / `getGradeOptions` / `getSubjectNameMapByIds` ( P1-1 新增:批量科目名称映射,供 homework/data-access-classes 调用))+ 写操作(`create/update/delete` × `Department/School/Grade/AcademicYear` ) + `promoteGrades(schoolId)` 年级升级( order +1 + 名称升级,辅助函数 `promoteGradeName` )
- Data-access: 只读查询( `getSchools` / `getGrades` / `getDepartments` / `getAcademicYears` / `getStaffOptions` / `getGradesForStaff` / `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` )
- Types( ✅ v4-P2-6 新增):`GradeOverviewStats` (年级概览统计接口:{ gradeId, classCount, studentCount, teacherCount },位于 data-access.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, 可接受)
@@ -892,6 +896,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ 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` 新增 `gradeStats` prop, 渲染年级概览卡片视图( 班级/学生/教师数 + 年级主任/教学主任 + 快捷操作入口) ; admin/school/grades/page.tsx 新增 `getGradeOverviewStats()` 查询并传入 GradesClient
- ✅ v4-P2-6 改进( 2026-06-23) : 新增 `GradeInsightsFilters` 组件(使用 ChipNav 替代原生 form get, 点击 chip 即时通过 URL 参数切换, 无整页刷新) ; admin/school/grades/insights/page.tsx 与 management/grade/insights/page.tsx 重写,使用 `GradeInsightsFilters` 替代原生 form get, 添加 i18n, 表格添加 `overflow-x-auto` 水平滚动
**文件清单 ** :
| 文件 | 行数 | 职责 |
@@ -907,6 +913,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| 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 | 920+ | 年级管理客户端( v4-P2-6 更新:新增 gradeStats prop + 年级概览卡片视图,展示班级/学生/教师数 + 年级主任/教学主任 + 快捷操作入口) |
| components/grade-insights-filters.tsx | 48 | v4-P2-6 新增: 年级洞察筛选器( ChipNav 替代原生 form get, 点击 chip 即时通过 URL 参数切换,无整页刷新) |
| hooks/use-school-data.ts | 40 | 学校数据管理 hook( P2-1 修复) |
---
@@ -1327,8 +1335,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**职责 ** :课程计划 CRUD + 周计划项 CRUD + 排序。
**导出函数 ** :
- Actions: `getCoursePlansAction` / `getCoursePlanByIdAction` / `createCoursePlanAction` / `updateCoursePlanAction` / `deleteCoursePlanAction` / `createCoursePlanItemAction` / `updateCoursePlanItemAction` / `deleteCoursePlanItemAction` / `toggleCoursePlanItemCompletedAction`
- Data-access: 与 actions 对应
- Actions: `getCoursePlansAction` / `getCoursePlanByIdAction` / `createCoursePlanAction` / `updateCoursePlanAction` / `deleteCoursePlanAction` / `createCoursePlanItemAction` / `updateCoursePlanItemAction` / `deleteCoursePlanItemAction` / `toggleCoursePlanItemCompletedAction` / `getGradeCoursePlanProgressAction` (✅ v4-P2-7 新增: 年级仪表盘维度4, 按 gradeId 查询年级下所有班级的教学计划进度, COURSE_PLAN_READ 权限)
- Data-access: 与 actions 对应 + `getGradeCoursePlanProgress` (✅ v4-P2-7 新增:通过 getClassesByGradeId 获取年级下所有班级, inArray 查询 course_plans + course_plan_items, 返回整体进度汇总 + 按班级/科目拆分的进度矩阵)
- Types( ✅ v4-P2-7 新增):`GradeCoursePlanProgressItem` ( 年级进度项: planId/classId/className/subjectId/subjectName/teacherName/semester/totalHours/completedHours/progressRate/status/itemCount/completedItemCount) / `GradeCoursePlanProgressResult` ( 年级进度结果: gradeId + overall 汇总 + items 数组)
**依赖关系 ** :
- 依赖:`shared/*` 、`@/auth` 、`classes` ( 合理, getAdminClasses/getStaffOptions) 、`school` ( 合理, getAcademicYears)
@@ -1621,20 +1630,20 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
---
## 2.23 settings( 设置模块)
## 2.23 settings( 设置模块) — V3 AI 配置统一入口
**职责 ** :系统设置(学校信息/安全策略/文件上传/通知配置)+ AI Provider 管理 + 密码修改 + 个人资料 + 主题偏好 + 通知偏好 + 个人信息页(学生/教师概览)。
**职责 ** :系统设置(学校信息/安全策略/文件上传/通知配置)+ AI Provider 管理 + 密码修改 + 个人资料 + 主题偏好 + 通知偏好 + 个人信息页(学生/教师概览)。V3 将 AI Provider 配置统一到 `/admin/ai-settings` 独立页面,移除 `/settings?tab=ai` 标签页和考试页面内嵌弹窗。
**导出函数 ** :
- Actions: `getAiProvidersAction ` / `create AiProviderAction` / `upda teAiProviderAction` / `deleteAiProviderAction` / `testAiProviderAction`
- Actions: `getAiProviderSummaries ` / `upsert AiProviderAction` / `test AiProviderAction` / `deleteAiProviderAction` ( V3 新增删除能力)
- Actions-password: `changePasswordAction` (✅ P1 已修复:使用 `requirePermission(USER_PROFILE_UPDATE)` + Zod 校验 + DB 操作下沉到 data-access)
- Actions-avatar: `updateUserAvatarAction` / `removeUserAvatarAction` (✅ P2-8 新增:头像上传/删除,复用 `/api/upload` 路由)
- Actions-notifications: `sendTestNotificationAction` (✅ P2-10 新增:发送测试通知,占位实现待接入真实通知服务)
- Actions-system-settings: `getAdminSystemSettingsAction` / `saveAdminSystemSettingsAction` (✅ P0-3 新增:管理员系统设置 CRUD, 4 分类 Zod 校验)
- Actions-security: `getSecurityCenterAction` / `toggleTwoFactorAction` (✅ P2-9 新增: 2FA 状态查询/切换 + 最近登录历史)
- Data-access: `getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange` ( P1 新增,从 actions 下沉)
- Data-access: `getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `deleteAiProvider` ( V3 新增:事务删除 + 自动转移默认)/ ` getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange` ( P1 新增,从 actions 下沉)
- Data-access-system-settings: `getSystemSettingsByCategory` / `getAllSystemSettings` / `getSystemSetting` / `upsertSystemSetting` / `upsertSystemSettings` (✅ P0-3 新增: system_settings 表 CRUD, 键值对存储模式)
- Components: `SettingsView` (统一设置页布局,5 标签页 General/Notifications/Appearance/Security/AI ;角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入; Tab URL 持久化;每个 TabsContent 包裹 `SettingsSectionErrorBoundary` + `Suspense` 骨架屏; AI 标签页条件渲染需 `AI_CONFIGURE` 权限 )、`SettingsServiceProvider` / `useSettingsService` ( Context 注入 `SettingsService` 接口,解耦组件对 users/messaging actions 的直接依赖)、`SettingsSectionErrorBoundary` (分区 Error Boundary, 局部失败不影响整页) 、`QuickLinksCard` ( 快捷链接卡片, i18n 键驱动)、`ProfileStudentOverview` / `ProfileStudentOverviewSkeleton` (学生概览异步 Server Component + 骨架屏)、`ProfileTeacherOverview` / `ProfileTeacherOverviewSkeleton` (教师概览异步 Server Component + 骨架屏)、`AdminSettingsView` (✅ P0-3 已修复:从 mock 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表)、`AvatarUpload` (✅ P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18n) 、`SecurityCenterCard` (✅ P2-9 新增: 2FA 开关 + 最近登录历史卡片)、`ThemePreferencesCard` (✅ P2-11 已增强:集成 `LocaleSwitcher` 语言切换)
- Components: `SettingsView` (统一设置页布局,V3 移除 AI 标签页后为 4 标签页 General/Notifications/Appearance/Security; 角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入; 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 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表)、`AvatarUpload` (✅ P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18n) 、`SecurityCenterCard` (✅ P2-9 新增: 2FA 开关 + 最近登录历史卡片)、`ThemePreferencesCard` (✅ P2-11 已增强:集成 `LocaleSwitcher` 语言切换)、`AiProviderSettingsCard` ( V3 增强:新增删除按钮 + AlertDialog 确认,统一在 `/admin/ai-settings` 页面渲染)
- Config: `ROLE_SETTINGS_CONFIG` / `resolveRoleSettingsConfig` (配置驱动角色 → 设置视图映射,新增角色只需添加条目)
- Lib: `buildStudentOverviewData` / `computeStudentStats` / `sortUpcomingAssignments` / `filterTodaySchedule` / `toWeekday` (纯数据计算函数,与 UI 分离,便于单元测试)
- Types: `AiProviderSummary` / `AiProviderName` / `AiProviderExisting` / `SettingsService` / `ProfileService` / `NotificationPreferenceService` (服务接口定义,用于依赖注入解耦)
@@ -1812,17 +1821,17 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- `FlowEdge` :流程连线(教学节点 → 教学节点)
**导出函数 ** :
- Data-access( `data-access.ts` ) : `getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument` ( v3 规范化,兼容 v1/v2 旧数据)/ `buildDefaultSkeleton` ( v3 默认 10 节点骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById`
- Data-access( `data-access.ts` ) : `getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument` ( v3 规范化,兼容 v1/v2 旧数据)/ `buildDefaultSkeleton` ( v3 默认 10 节点骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById` / `publishLessonPlan` ( V3 新增,设置 status=published) / `unpublishLessonPlan` ( V3 新增,设置 status=draft, 仅 published 课案)
- Lib( `lib/document-migration.ts` ) : `defaultDataForType` / `migrateV1ToV2` / `migrateV2ToV3` / `normalizeDocument` / `buildInitialContent` / `buildDefaultSkeleton` / `isTextbookContentNode` / `isAnchorEdge` / `getAnchorsForNode` / `getActiveAnchorIds` / `getAnchorEdges`
- Lib( `lib/anchor-injector.ts` ) : `markdownToPlainText` / `injectPlaceholders` / `parseAnchoredText` / `toCircledNumber` / `getNextPointIndex` / `relocateAnchors` / `getAnchorColor`
- Lib( `lib/node-summary.ts` ) : `getNodeSummary` / `getTextbookContentSummary` / `getNodeColor` / `NODE_COLORS`
- Lib( `lib/rf-mappers.ts` ) : `toRfNodes` (支持 textbook_content 节点) / `toRfEdges` (区分 anchor/flow 边透明度)/ `fromRfEdges`
- Lib( `lib/rf-mappers.ts` ) : `toRfNodes` (支持 textbook_content 节点; V3: ctx 新增 `anchorableNodes` 和 `onCreateNewNode` 字段)/ `toRfEdges` (区分 anchor/flow 边透明度; V3: 锚点边颜色使用 `getNodeColor(anchor.nodeId)` 替代硬编码, anchorId 存入 edge.data) / `fromRfEdges` ( V3: 从 `e.data.anchorId` 读取 anchorId, 回退到 className 判断)
- 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`
- Actions: `getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction` / `getTextbooksForPickerAction` / `getChaptersForPickerAction` / `publishLessonPlanAction` ( V3 新增, requirePermission(LESSON_PLAN_PUBLISH)) / `unpublishLessonPlanAction` ( V3 新增, requirePermission(LESSON_PLAN_PUBLISH))
**依赖关系 ** :
- 依赖:`shared/*` 、`@/auth` 、`shared/lib/ai` 、`@xyflow/react` (节点图编辑器)、`textbooks` (只读章节/知识点树)、`questions` (创建/查询题目)、`exams` (创建 exam 草稿)、`homework` (创建作业下发)、`classes` (查询教师班级)、`files` (附件)
@@ -1855,6 +1864,18 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
> - **统一错误处理**:所有 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 增强**: `toRfNodes` ctx 新增 `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-guard `resolveDataScope` 中 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)
**文件清单 ** :
| 文件 | 职责 |
|------|------|
@@ -1864,34 +1885,35 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `lib/document-migration.ts` | **纯函数 ** : v1→v2( migrateV1ToV2) / v2→v3( migrateV2ToV3) / 规范化( normalizeDocument, 兼容 v1/v2/v3) / 初始内容( buildInitialContent) / 默认骨架( buildDefaultSkeleton, 10 节点 + 正文节点)/ defaultDataForType / 工具函数( isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges) |
| `lib/anchor-injector.ts` | **纯函数 ** : 锚点注入算法( markdownToPlainText/injectPlaceholders/parseAnchoredText/toCircledNumber/getNextPointIndex/relocateAnchors/getAnchorColor) |
| `lib/node-summary.ts` | **纯函数 ** : getNodeSummary( 支持 11 种节点类型)+ getTextbookContentSummary + NODE_COLORS + getNodeColor |
| `lib/rf-mappers.ts` | **纯函数 ** : toRfNodes( 支持 textbook_content 节点 + 锚点回调) / toRfEdges( 区分 anchor/flow 边透明度)/ fromRfEdges |
| `lib/rf-mappers.ts` | **纯函数 ** : toRfNodes( 支持 textbook_content 节点 + 锚点回调; V3: ctx 新增 anchorableNodes/onCreateNewNode 字段)/ toRfEdges( 区分 anchor/flow 边透明度; V3: 锚点边颜色使用 getNodeColor(anchor.nodeId) 替代硬编码, anchorId 存入 edge.data) / fromRfEdges( V3: 从 e.data.anchorId 读取 anchorId, 回退到 className 判断) |
| `config/block-registry.tsx` | **配置驱动 ** : BLOCK_REGISTRY 注册表 + BlockRenderer( switch 渲染 11 种定制节点 + textbook_content) |
| `providers/lesson-plan-provider.tsx` | **Provider + Context( P1-5/P1-7/P2-4/V2-6) ** : LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置( TEACHER/ADMIN/STUDENT/PARENT) 、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker; hooks: useLessonPlanContextSafe( 返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe( V2-6 新增,返回 noopTracker 不抛错) |
| `services/default-data-service.ts` | **默认数据服务实现 ** : createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现,测试可替换为 mock |
| `data-access.ts` | 课案 CRUD + 模板查询( migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容; buildScopeCondition 按 scope 类型精确过滤 P0-3; V2-1: 抛出 `LessonPlanDataError` 错误码; V2-3: mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫) |
| `data-access.ts` | 课案 CRUD + 模板查询( migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容; buildScopeCondition 按 scope 类型精确过滤 P0-3; V2-1: 抛出 `LessonPlanDataError` 错误码; V2-3: mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫; V3: 新增 publishLessonPlan/unpublishLessonPlan 函数) |
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理; V2-3: mapRowToVersion 显式映射) |
| `data-access-templates.ts` | 个人模板 CRUD( V2-3: mapRowToTemplate 显式映射 + 类型守卫) |
| `data-access-knowledge.ts` | 按知识点/题目反查课案( V2-3: 显式字段映射替代 `as unknown as` ) |
| `actions.ts` | 课案 CRUD/版本/模板 Server Actions( V2-1: getTranslations i18n + 错误码捕获; V2-2: createLessonPlanAction 传入 translateTitle 翻译 SYSTEM_TEMPLATES) |
| `actions-publish.ts` | 发布作业 Server Action( V2-1: getTranslations i18n + PUBLISH_ERROR_KEY_MAP 错误码映射) |
| `actions-publish.ts` | 发布作业 Server Action( V2-1: getTranslations i18n + PUBLISH_ERROR_KEY_MAP 错误码映射; V3: 新增 publishLessonPlanAction/unpublishLessonPlanAction, requirePermission(LESSON_PLAN_PUBLISH)) |
| `actions-ai.ts` | AI 知识点建议 Server Action( V2-1: i18n + 错误码) |
| `actions-kp.ts` | 知识点选项 Server Action( V2-1: i18n + 错误码) |
| `publish-service.ts` | 发布作业服务(编排 homework/exams/classes, 通过对方 data-access 调用, 无直查跨模块表; V2-1: 抛出 `PublishServiceError` 错误码; V2-3: 显式字段映射替代 `as unknown as` ) |
| `ai-suggest.ts` | AI 知识点建议服务 |
| `seed-templates.ts` | 模板种子数据 |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook( 基于 zustand, 支持 nodes/edges/anchors 操作: addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode + 锚点操作 addAnchor/removeAnchor/updateAnchor + 正文节点操作 updateTextbookContent/getTextbookContentNode; 实时拖动) |
| `components/lesson-plan-list.tsx` | 课案列表( i18n 已接入) |
| `components/lesson-plan-card.tsx` | 课案卡片( i18n 已接入; V2-6: duplicate/archive 调用 tracker.track) |
| `components/lesson-plan-list.tsx` | 课案列表( i18n 已接入; V3: 新增 viewMode prop, 支持 teacher/student/parent/admin/gradeHead 多角色视图) |
| `components/lesson-plan-card.tsx` | 课案卡片( i18n 已接入; V2-6: duplicate/archive 调用 tracker.track; V3: 新增 viewMode prop( teacher/student/parent/admin/gradeHead) , 动态跳转链接 + 发布/撤回按钮) |
| `components/lesson-plan-filters.tsx` | 课案筛选器( i18n 已接入; V2-5: 3 个表单元素 label htmlFor 关联) |
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel, i18n 已接入; V2-6: handleManualSave 调用 tracker.track; V3: 顶部工具栏显示教材/章节标题指示器) |
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel, i18n 已接入; V2-6: handleManualSave 调用 tracker.track; V3: 顶部工具栏显示教材/章节标题指示器; V3: 新增 initialStatus prop + 发布/撤回按钮( AlertDialog 确认)) |
| `components/lesson-plan-readonly-view.tsx` | **只读画布组件 ** ( V3 新增):复用 React Flow( nodesDraggable=false, nodesConnectable=false) , 供学生/家长/管理员/教研组长查看已发布课案 |
| `components/node-editor.tsx` | **节点图画布 ** ( React Flow, 使用 lib/rf-mappers + lib/node-summary 纯函数, i18n 已接入; V2-4: MiniMap 复用 getNodeColor; V2-5: role=application + 键盘导航配置; V3: 注册 textbook_content 节点类型 + 锚点回调 + 实时拖动) |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板 ** (配置驱动渲染 Block, 通过 BlockRenderer + LessonPlanErrorBoundary 包裹, i18n 已接入; V3: 处理 textbook_content 节点,教学节点类型收窄) |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板 ** (配置驱动渲染 Block, 通过 BlockRenderer + LessonPlanErrorBoundary 包裹, i18n 已接入; V3: 处理 textbook_content 节点,教学节点类型收窄; V3: 选中 textbook_content 时显示操作提示 + 锚点列表(含删除功能)) |
| `components/nodes/lesson-node.tsx` | **自定义教学节点组件 ** (使用 lib/node-summary 的 getNodeSummary/getNodeColor, i18n 已接入) |
| `components/nodes/textbook-content-node.tsx` | **正文节点组件 ** ( V3 新增) : ReactMarkdown 渲染正文 + 锚点注入 + 文本选择( range 锚定)+ 点击位置( point 锚定)+ 缩放控制 + 锚点浮动菜单 |
| `components/nodes/textbook-content-node.tsx` | **正文节点组件 ** ( V3 新增) : ReactMarkdown 渲染正文 + 锚点注入 + 文本选择( range 锚定)+ 点击位置( point 锚定)+ 缩放控制 + 锚点浮动菜单; V3: 新增 props `anchorableNodes` , `onCreateNewNode` ; AnchorNodeSelector 重写为节点列表+创建新节点选项 |
| `components/lesson-plan-error-boundary.tsx` | **错误边界 ** : LessonPlanErrorBoundary 类组件,支持 fallback 和 onError 回调 |
| `components/lesson-plan-skeleton.tsx` | **骨架屏 ** : VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton |
| `components/block-renderer.tsx` | ⚠️ @deprecated Block 渲染器(已被 NodeEditor 替代,保留向后兼容) |
| `components/template-picker.tsx` | 模板选择器( i18n 已接入; V2-6: create 调用 tracker.track) |
| `components/template-picker.tsx` | 模板选择器( i18n 已接入; V2-6: create 调用 tracker.track; V3: 加载并显示个人模板( 调用 getLessonPlanTemplatesAction) , 分区显示系统/个人模板) |
| `components/version-history-drawer.tsx` | 版本历史抽屉( i18n 已接入; V2-6: revert 调用 tracker.track) |
| `components/knowledge-point-picker.tsx` | 知识点选择器( i18n 已接入) |
| `components/question-bank-picker.tsx` | 题库选择器( i18n 已接入) |
@@ -1949,6 +1971,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- `collectFromExamSubmission` :从考试提交记录中筛选得分 < 满分的题目,去重后批量插入
- `collectFromHomeworkSubmission` :从作业提交记录中筛选错题,去重后批量插入
- 自动关联知识点(通过 `questionsToKnowledgePoints` 表)
- subjectId 来源:`questions` 表无 subjectId 字段,考试错题从 `exams.subjectId` 获取;作业错题从 `homeworkAssignments.sourceExamId` 关联到源试卷的 `exams.subjectId` 获取(独立作业无学科归属则为 null)
**文件清单 ** :
| 文件 | 行数 | 职责 |
@@ -2429,7 +2452,7 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
- `buildScopeClassFilter(scope: DataScope, currentUserId?: string): SQL | null` (新增 `currentUserId` 参数,`class_members` scope 内置 `eq(gradeRecords.studentId, currentUserId)` 过滤; P3-26: 从 `lib/grade-utils.ts` 迁移至 `lib/scope-filter.ts` )
**新增导出 ** :
- `assertClassInScope(scope: DataScope, classId: string): string | null` ( `actions.ts` , 校验 classId 是否在 scope 允许范围内,供 actions.ts 与 actions-analytics.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 模块签名变更