feat: 新增备课模块并修复全模块 P0/P1/P2 缺陷
Some checks failed
Security / deep-security-scan (push) Failing after 20m5s
DR Drill / dr-drill (push) Failing after 1m31s
CI / scheduled-backup (push) Failing after 1m31s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled

主要变更:

- 新增 lesson-preparation 模块: 备课编辑器、节点编辑、AI 建议、知识点选择、版本历史、作业发布

- 新增 shared 通用组件: charts/question-bank-filters/schedule-list/ui (chip-nav/filter-bar/page-header/stat-card/stat-item)

- 新增 student/admin 端 loading.tsx 与 error.tsx, 优化加载与错误态体验

- 新增 teacher/lesson-plans 页面 (列表/新建/编辑)

- 新增 drizzle 迁移 0002_tiny_lionheart 及 snapshot

- 新增 textbooks/schema.ts 与 exams/utils/normalize-structure.ts

- 修复 Tiptap v3 SSR hydration 崩溃 (rich-text-block immediatelyRender: false)

- 重构多模块 data-access/actions/组件, 修复权限校验与类型规范

- 同步架构文档 004/005 反映新增模块、导出、依赖关系

- 归档 bugs/* 测试报告与 e2e 测试脚本 (admin/parent/student/teacher web_test)
This commit is contained in:
SpecialX
2026-06-22 01:06:16 +08:00
parent d8962aba96
commit 978d9a8309
327 changed files with 34070 additions and 5642 deletions

View File

@@ -5,7 +5,7 @@
"generatedAt": "2026-06-17",
"formatVersion": "1.1",
"rule": "每次文件修改后须同步更新本文件",
"lastUpdate": "P2-6/P2-7/P2-8/P2-11/P2-17/P2-18 已修复proxy.ts 改用 Permissions 常量替代硬编码字符串useA11yId 文件已不存在use-aria-live.ts 已在 hooks/ 目录schema.ts 分节编号重新编号为连续 1-24消除 8b/14b/乱序问题announcements 死代码 void wasPublished 已不存在layout/app-sidebar.tsx 改用 hasRole() 判断角色不再用权限反推角色scheduling/actions.ts 移除末尾 re-export data-access4 个页面改为从 data-access 直接导入P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 模块P0-2 已修复shared/lib ↔ auth 循环依赖已解决,新增 shared/lib/session.ts 单一入口P1-6 已修复http-utils.ts 统一 IP/UA 提取P0-1/P0-2/P0-3/P0-4/P0-5/P0-7/P0-8 已修复P1-2 已修复actions 层 DB 操作下沉到 data-accessP2-2/P2-3/P2-12/P2-20 已修复"
"lastUpdate": "P0-b/P1-a/P1-b/P1-c/P2-a/P2-b/P3-a/P3-b/P3-c/P3-d 共享组件抽取重构已同步:新增 shared 层 UI 组件StatCard/StatItem/ChipNav/PageHeader/FilterBar+FilterSearchInput+FilterResetButton、图表组件ChartCardShell/TrendLineChart/SimpleBarChart/ComparisonRadarChart、课表组件ScheduleList+ScheduleListItem、题库组件QuestionBankFilters、工具函数downloadBase64File/downloadBlob/getInitials/formatDateForFile新增 settings 模块 SettingsView 组件;删除 messaging/notification-preferences.tsP0-b 通知模块去重re-export shim 已移除,消费方改为直接从 notifications/preferences 导入);P2-6/P2-7/P2-8/P2-11/P2-17/P2-18 已修复proxy.ts 改用 Permissions 常量替代硬编码字符串useA11yId 文件已不存在use-aria-live.ts 已在 hooks/ 目录schema.ts 分节编号重新编号为连续 1-24消除 8b/14b/乱序问题announcements 死代码 void wasPublished 已不存在layout/app-sidebar.tsx 改用 hasRole() 判断角色不再用权限反推角色scheduling/actions.ts 移除末尾 re-export data-access4 个页面改为从 data-access 直接导入P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 模块P0-2 已修复shared/lib ↔ auth 循环依赖已解决,新增 shared/lib/session.ts 单一入口P1-6 已修复http-utils.ts 统一 IP/UA 提取P0-1/P0-2/P0-3/P0-4/P0-5/P0-7/P0-8 已修复P1-2 已修复actions 层 DB 操作下沉到 data-accessP2-2/P2-3/P2-12/P2-20 已修复"
},
"architectureOverview": {
"layers": [
@@ -231,6 +231,7 @@
"FILE_READ",
"COURSE_PLAN_READ",
"ATTENDANCE_READ",
"MESSAGE_SEND",
"MESSAGE_READ",
"MESSAGE_DELETE",
"DIAGNOSTIC_READ",
@@ -360,6 +361,37 @@
"textbooks"
]
},
{
"name": "getParam",
"file": "lib/search-params.ts",
"signature": "getParam(params: SearchParams, key: string): string | undefined",
"params": {
"params": "Next.js searchParams 对象",
"key": "参数键名"
},
"purpose": "规范化 Next.js 15+ searchParams 访问string | string[] | undefined → string | undefinedre-export 自 utils.ts 的 getSearchParam",
"deps": [
"shared/lib/utils.getSearchParam"
],
"usedBy": [
"teacher/attendance/page.tsx",
"teacher/attendance/sheet/page.tsx",
"teacher/attendance/stats/page.tsx",
"teacher/classes/schedule/page.tsx",
"teacher/classes/students/page.tsx",
"teacher/course-plans/page.tsx",
"teacher/diagnostic/page.tsx",
"teacher/elective/page.tsx",
"teacher/exams/all/page.tsx",
"teacher/grades/page.tsx",
"teacher/grades/analytics/page.tsx",
"teacher/grades/entry/page.tsx",
"teacher/grades/stats/page.tsx",
"teacher/homework/assignments/page.tsx",
"teacher/questions/page.tsx",
"teacher/textbooks/page.tsx"
]
},
{
"name": "parseAiChatPayload",
"file": "lib/ai/payload-parser.ts",
@@ -795,6 +827,55 @@
"usedBy": [
"待扩展"
]
},
{
"name": "getInitials",
"file": "lib/utils.ts",
"signature": "getInitials(name: string | null | undefined): string",
"purpose": "从用户姓名提取首字母缩写最多2字符用于头像 fallback",
"deps": [],
"usedBy": [
"shared/components/ui/avatar.tsx",
"modules/dashboard/components/*-dashboard/*-header.tsx",
"modules/parent/components/child-card.tsx"
]
},
{
"name": "formatDateForFile",
"file": "lib/utils.ts",
"signature": "formatDateForFile(d?: Date): string",
"purpose": "格式化日期为 YYYY-MM-DD 用于文件名P1-c/P2-c 重构:从 grades/export.ts、audit/actions.ts、api/export/route.ts、users/actions.ts 四处重复实现抽取)",
"deps": [],
"usedBy": [
"grades/actions.exportGradesAction",
"audit/actions.exportAuditLogsAction",
"api/export/route",
"users/actions.exportUsersAction"
]
},
{
"name": "downloadBase64File",
"file": "lib/download.ts",
"signature": "downloadBase64File(base64: string, filename: string, mimeType?: string): void",
"purpose": "客户端下载 Base64 编码文件(默认 MIME 为 Excel xlsxP1-c 重构从 grades/export-button、users/user-import-dialog 两处重复实现抽取",
"deps": [
"shared/lib/download.downloadBlob"
],
"usedBy": [
"grades/components/export-button.tsx",
"users/components/user-import-dialog.tsx"
]
},
{
"name": "downloadBlob",
"file": "lib/download.ts",
"signature": "downloadBlob(blob: Blob, filename: string): void",
"purpose": "客户端下载 Blob 对象(创建临时 URL + a 标签点击 + revokeP1-c 重构从 audit/audit-log-export-button 抽取",
"deps": [],
"usedBy": [
"shared/lib/download.downloadBase64File",
"audit/components/audit-log-export-button.tsx"
]
}
],
"hooks": [
@@ -940,6 +1021,261 @@
"usedBy": [
"settings/components/notification-preferences-form.tsx"
]
},
{
"name": "StatCard",
"file": "components/ui/stat-card.tsx",
"props": "{ title, value, icon?, description?, color?, highlight?, href?, isLoading?, valueClassName? }",
"purpose": "统计卡片(标题+数值+图标+描述+跳转+骨架屏P1-a 重构从 8 处重复实现抽取teacher/student/admin dashboard stats、class-overview-stats、grade insights 等)",
"internalDeps": [
"Card",
"CardHeader",
"CardTitle",
"CardContent",
"Link",
"cn",
"Skeleton"
],
"usedBy": [
"dashboard/components/teacher-dashboard/teacher-stats.tsx",
"dashboard/components/student-dashboard/student-stats-grid.tsx",
"dashboard/components/admin-dashboard/admin-dashboard.tsx",
"classes/components/class-detail/class-overview-stats.tsx",
"app/(dashboard)/admin/school/grades/insights/page.tsx",
"app/(dashboard)/management/grade/insights/page.tsx"
]
},
{
"name": "StatItem",
"file": "components/ui/stat-item.tsx",
"props": "{ label, value, icon?, hint?, valueClassName? }",
"purpose": "紧凑统计项label+icon+value+hint用于统计面板网格P1-a 重构从 attendance-stats-card、grade-stats-card 两处重复实现抽取",
"internalDeps": [
"cn"
],
"usedBy": [
"attendance/components/attendance-stats-card.tsx",
"grades/components/grade-stats-card.tsx"
]
},
{
"name": "ChipNav",
"file": "components/ui/chip-nav.tsx",
"props": "{ options: ChipNavOption[], currentId, buildHref: (id) => string, size?: 'sm'|'xs', allOption?, className? }",
"purpose": "芯片导航组(通过 URL search params 切换筛选维度Link 跳转P1-b 重构从 stats-class-selector、attendance-stats-class-selector、analytics-filters 三处重复实现抽取",
"internalDeps": [
"Link",
"cn"
],
"usedBy": [
"grades/components/stats-class-selector.tsx",
"attendance/components/attendance-stats-class-selector.tsx",
"grades/components/analytics-filters.tsx"
]
},
{
"name": "PageHeader",
"file": "components/ui/page-header.tsx",
"props": "{ title, description?, icon?: ComponentType, actions?: ReactNode, className? }",
"purpose": "页面头部(标题+描述+icon+actions响应式布局移动端纵向桌面端横向P2-b 重构从 admin-dashboard、profile/page、settings/security/page 三处内联头部抽取",
"internalDeps": [
"cn"
],
"usedBy": [
"dashboard/components/admin-dashboard/admin-dashboard.tsx",
"app/(dashboard)/profile/page.tsx",
"app/(dashboard)/settings/security/page.tsx",
"modules/settings/components/settings-view.tsx"
]
},
{
"name": "FilterBar",
"file": "components/ui/filter-bar.tsx",
"props": "{ children, hasFilters?, onReset?, layout?: 'default'|'wrap'|'between', gapClassName?, className?, resetClassName? }",
"purpose": "筛选栏容器(统一布局壳 + Reset 按钮P3-b 重构从 exam/textbook/question/audit-log/login-log filters 五处重复布局抽取。URL 状态管理方式nuqs/router/callback由各模块自行处理",
"internalDeps": [
"Button",
"cn"
],
"usedBy": [
"exams/components/exam-filters.tsx",
"textbooks/components/textbook-filters.tsx",
"questions/components/question-filters.tsx",
"audit/components/audit-log-filters.tsx",
"audit/components/login-log-filters.tsx"
]
},
{
"name": "FilterSearchInput",
"file": "components/ui/filter-bar.tsx",
"props": "{ value, onChange, placeholder?, className?, inputClassName? }",
"purpose": "筛选栏搜索框(带 Search 图标的 InputP3-b 重构从 exam/textbook/question filters 三处重复搜索框抽取",
"internalDeps": [
"Input",
"Search (lucide-react)",
"cn"
],
"usedBy": [
"exams/components/exam-filters.tsx",
"textbooks/components/textbook-filters.tsx",
"questions/components/question-filters.tsx"
]
},
{
"name": "FilterResetButton",
"file": "components/ui/filter-bar.tsx",
"props": "{ onClick, className? }",
"purpose": "筛选栏重置按钮Reset + X 图标P3-b 重构从 6 个 filter 文件中重复的 Reset 按钮抽取",
"internalDeps": [
"Button",
"X (lucide-react)",
"cn"
],
"usedBy": [
"FilterBar内部使用"
]
},
{
"name": "ChartCardShell",
"file": "components/charts/chart-card-shell.tsx",
"props": "{ title, description?, icon?, iconClassName?, titleClassName?, isEmpty?, emptyTitle?, emptyDescription?, emptyIcon?, emptyClassName?, children, className?, contentClassName? }",
"purpose": "图表卡片外壳Card + CardHeader + EmptyState + CardContent 统一结构P3-c 重构从 8 个图表文件重复的 Card 包装抽取",
"internalDeps": [
"Card",
"CardHeader",
"CardTitle",
"CardDescription",
"CardContent",
"EmptyState",
"cn"
],
"usedBy": [
"grades/components/grade-trend-chart.tsx",
"grades/components/grade-distribution-chart.tsx",
"grades/components/class-comparison-chart.tsx",
"grades/components/subject-comparison-chart.tsx",
"dashboard/components/teacher-dashboard/teacher-grade-trends.tsx",
"dashboard/components/student-dashboard/student-grades-card.tsx",
"parent/components/child-grade-summary.tsx",
"diagnostic/components/mastery-radar-chart.tsx"
]
},
{
"name": "TrendLineChart",
"file": "components/charts/trend-line-chart.tsx",
"props": "{ data, series: TrendLineSeries[], xKey?, yDomain?, yTickFormatter?, xTickFormatter?, heightClassName?, margin?, yWidth?, tooltipClassName?, tooltipLabelKey?, className? }",
"purpose": "趋势折线图LineChart 统一配置CartesianGrid + XAxis + YAxis + ChartTooltip + LineP3-c 重构从 4 个 LineChart 文件grade-trend-chart、teacher-grade-trends、student-grades-card、child-grade-summary几乎逐行相同的配置抽取",
"internalDeps": [
"ChartContainer",
"ChartTooltip",
"ChartTooltipContent",
"CartesianGrid",
"Line",
"LineChart",
"XAxis",
"YAxis",
"cn"
],
"usedBy": [
"grades/components/grade-trend-chart.tsx",
"dashboard/components/teacher-dashboard/teacher-grade-trends.tsx",
"dashboard/components/student-dashboard/student-grades-card.tsx",
"parent/components/child-grade-summary.tsx"
]
},
{
"name": "SimpleBarChart",
"file": "components/charts/simple-bar-chart.tsx",
"props": "{ data, bars: BarSeries[], xKey, yDomain?, yAllowDecimals?, yTickFormatter?, xTickFormatter?, xTruncateLength?, yWidth?, heightClassName?, margin?, showLegend?, tooltipClassName?, tooltipFormatter?, cellColors?, className? }",
"purpose": "柱状图BarChart 统一配置CartesianGrid + XAxis + YAxis + ChartTooltip + BarP3-c 重构从 grade-distribution-chart单 Bar + Cell 分桶着色)和 class-comparison-chart多 Bar + Legend抽取",
"internalDeps": [
"ChartContainer",
"ChartTooltip",
"ChartTooltipContent",
"Bar",
"BarChart",
"CartesianGrid",
"Legend",
"XAxis",
"YAxis",
"Cell",
"cn"
],
"usedBy": [
"grades/components/grade-distribution-chart.tsx",
"grades/components/class-comparison-chart.tsx"
]
},
{
"name": "ComparisonRadarChart",
"file": "components/charts/comparison-radar-chart.tsx",
"props": "{ data, series: RadarSeries[], angleKey, angleTickFormatter?, angleTickFontSize?, domain?, tickCount?, showLegend?, heightClassName?, tooltipClassName?, className?, gridStrokeDasharray?, gridStrokeOpacity? }",
"purpose": "对比雷达图RadarChart 统一配置PolarGrid + PolarAngleAxis + PolarRadiusAxis + ChartTooltip + RadarP3-c 重构从 subject-comparison-chart双 RadaraverageScore + passRate和 mastery-radar-chart双 Radarstudent + classAverage含条件 Legend抽取",
"internalDeps": [
"ChartContainer",
"ChartTooltip",
"ChartTooltipContent",
"PolarAngleAxis",
"PolarGrid",
"PolarRadiusAxis",
"Radar",
"RadarChart",
"Legend",
"cn"
],
"usedBy": [
"grades/components/subject-comparison-chart.tsx",
"diagnostic/components/mastery-radar-chart.tsx"
]
},
{
"name": "ScheduleList",
"file": "components/schedule/schedule-list.tsx",
"props": "{ items: ScheduleListItemData[], variant?: 'separator'|'card', spacingClassName?, renderTrailing?, className? }",
"purpose": "课表列表(课程+时间+地点+班级徽章P3-a 重构从 student-today-schedule-card、child-schedule-card、student-schedule-view 三处逐行复制的列表项渲染抽取。支持 separator分隔线和 card卡片两种变体",
"internalDeps": [
"ScheduleListItem",
"cn"
],
"usedBy": [
"dashboard/components/student-dashboard/student-today-schedule-card.tsx",
"parent/components/child-schedule-card.tsx",
"student/components/student-schedule-view.tsx"
]
},
{
"name": "ScheduleListItem",
"file": "components/schedule/schedule-list.tsx",
"props": "{ item: ScheduleListItemData, variant?: 'separator'|'card', trailing?, className? }",
"purpose": "课表列表项单条课程渲染course + Clock + MapPin + BadgeP3-a 重构从 3 个课表文件中重复的列表项抽取",
"internalDeps": [
"Badge",
"Clock (lucide-react)",
"MapPin (lucide-react)",
"cn"
],
"usedBy": [
"ScheduleList内部使用"
]
},
{
"name": "QuestionBankFilters",
"file": "components/question/question-bank-filters.tsx",
"props": "{ search, onSearchChange, type, onTypeChange, difficulty, onDifficultyChange, layout?: 'default'|'compact', className? }",
"purpose": "题库筛选栏(搜索+题型+难度P3-d 重构从 exam-assemblycompact 布局)和 question-bank-pickerdefault 布局,同时将原生 HTML input/select 迁移到 shadcn Input/Select两处重复筛选栏抽取。状态管理方式由调用方自行处理",
"internalDeps": [
"Select",
"SelectContent",
"SelectItem",
"SelectTrigger",
"SelectValue",
"FilterSearchInput",
"cn"
],
"usedBy": [
"exams/components/exam-assembly.tsx",
"lesson-preparation/components/question-bank-picker.tsx"
]
}
],
"constants": [
@@ -1029,10 +1365,22 @@
"所有actions"
]
},
{
"name": "Role",
"file": "types/permissions.ts",
"definition": "Role = 'admin' | 'teacher' | 'student' | 'parent' | 'grade_head' | 'teaching_head'",
"usedBy": [
"auth-guard",
"permissions",
"proxy",
"next-auth.d.ts",
"use-permission"
]
},
{
"name": "DataScope",
"file": "types/permissions.ts",
"definition": "DataScope = { type: 'all' } | { type: 'owned'; userId: string } | { type: 'class_taught'; classIds: string[]; subjectIds?: string[] } | { type: 'grade_managed'; gradeIds: string[] } | { type: 'class_members' } | { type: 'children'; childrenIds: string[] }",
"definition": "DataScope = { type: 'all' } | { type: 'owned'; userId: string } | { type: 'class_members'; classIds: string[] } | { type: 'grade_managed'; gradeIds: string[] } | { type: 'class_taught'; classIds: string[]; subjectIds?: string[] } | { type: 'children'; childrenIds: string[] }",
"usedBy": [
"auth-guard",
"exams/data-access",
@@ -1045,7 +1393,7 @@
{
"name": "AuthContext",
"file": "types/permissions.ts",
"definition": "AuthContext = { userId: string; roles: string[]; permissions: Permission[]; dataScope: DataScope }",
"definition": "AuthContext = { userId: string; roles: Role[]; permissions: Permission[]; dataScope: DataScope }",
"usedBy": [
"auth-guard",
"所有调用requirePermission的Server Action"
@@ -2676,6 +3024,22 @@
"exam-form.tsx"
]
}
],
"utils": [
{
"name": "normalizeStructure",
"file": "utils/normalize-structure.ts",
"type": "function",
"signature": "(nodes: unknown) => ExamNode[]",
"purpose": "将持久化的 exam.structureunknown JSON运行时校验并归一化为类型安全的 ExamNode[](类型守卫模式,无 as 断言;递归处理 group children保证 id 唯一)",
"deps": [
"@paralleldrive/cuid2.createId",
"exams/components/assembly/selected-question-list.ExamNode"
],
"usedBy": [
"teacher/exams/[id]/build/page.tsx"
]
}
]
}
},
@@ -4260,6 +4624,14 @@
"usedBy": [
"grades/data-access-analytics"
]
},
{
"name": "getTeacherIdsByClassIds",
"signature": "(classIds: string[]) => Promise<string[]>",
"purpose": "获取多个班级的所有教师 ID班主任 + 任课教师,跨模块接口)",
"usedBy": [
"messaging/data-access.getRecipients"
]
}
],
"schema": [
@@ -5369,8 +5741,8 @@
{
"name": "getAiProviderSummaries",
"permission": "AI_CONFIGURE",
"signature": "() => Promise<AiProviderSummary[]>",
"purpose": "获取AI Provider列表P1 已修复DB 操作下沉到 data-access.getAiProviderSummaries",
"signature": "() => Promise<ActionState<AiProviderSummary[]>>",
"purpose": "获取AI Provider列表P1 已修复DB 操作下沉到 data-access.getAiProviderSummariesv3 已修复:返回值统一为 ActionState",
"deps": [
"data-access.getAiProviderSummaries"
]
@@ -6499,13 +6871,14 @@
"name": "getFileAttachmentsWithFilters",
"signature": "(params: FileAttachmentQueryParams) => Promise<FileAttachment[]>",
"file": "data-access.ts",
"purpose": "按 mimeType精确或前缀匹配与 searchoriginalName/filename 模糊匹配)筛选文件列表,支持 limit/offset 分页",
"purpose": "按 mimeType精确或前缀匹配与 searchoriginalName/filename 模糊匹配)筛选文件列表,支持 limit/offset 分页v3 修复conditions 显式标注 SQL[] 类型,消除隐式 any[]",
"deps": [
"shared.db",
"shared.db.schema.fileAttachments",
"drizzle-orm.like",
"drizzle-orm.or",
"drizzle-orm.and"
"drizzle-orm.and",
"drizzle-orm.SQL"
],
"usedBy": [
"app/(dashboard)/admin/files/page.tsx"
@@ -7234,11 +7607,12 @@
"name": "formatDateForFile",
"signature": "(d?: Date) => string",
"file": "export.ts",
"purpose": "格式化日期为 YYYY-MM-DD 用于文件名",
"deps": [],
"usedBy": [
"actions.exportGradesAction"
]
"purpose": "⚠️ P1-c/P2-c 已迁移:本地实现已删除,改为从 @/shared/lib/utils 导入。此条目保留仅作历史记录",
"deps": [
"shared/lib/utils.formatDateForFile"
],
"usedBy": [],
"migratedTo": "shared/lib/utils.formatDateForFile"
}
],
"components": [
@@ -7321,6 +7695,30 @@
"recharts",
"shared/components/ui/chart"
]
},
{
"name": "AnalyticsFilters",
"file": "components/analytics-filters.tsx",
"purpose": "成绩分析页筛选器(班级、科目、年级 Link 筛选按钮组,含 focus-visible 焦点样式)",
"deps": [
"next/link",
"shared/lib/utils.cn"
],
"usedBy": [
"teacher/grades/analytics/page.tsx"
]
},
{
"name": "StatsClassSelector",
"file": "components/stats-class-selector.tsx",
"purpose": "统计页班级+科目筛选器Link 筛选按钮组,含 focus-visible 焦点样式)",
"deps": [
"next/link",
"shared/lib/utils.cn"
],
"usedBy": [
"teacher/grades/stats/page.tsx"
]
}
]
}
@@ -8232,12 +8630,16 @@
"name": "getRecipients",
"signature": "(ctx: AuthContext) => Promise<RecipientOption[]>",
"file": "data-access.ts",
"purpose": "按 DataScope 过滤可发送对象列表class_taught教师→学生、grade_managed年级管理员→教师/学生、all管理员、class_members学生→自己班级的任课教师/班主任、children家长→孩子的班主任/任课教师)",
"deps": [
"shared.db",
"shared.db.schema.users",
"shared.db.schema.classEnrollments",
"shared.db.schema.classes",
"shared.db.schema.grades"
"shared.db.schema.grades",
"classes.data-access.getTeacherIdsByClassIds",
"classes.data-access.getStudentActiveClassId",
"users.data-access.getUserNamesByIds"
],
"usedBy": [
"getRecipientsAction",
@@ -9307,6 +9709,18 @@
"name": "AttendanceRulesForm",
"file": "components/attendance-rules-form.tsx",
"purpose": "考勤规则配置表单(班级选择器、迟到/早退阈值、自动标记勾选)"
},
{
"name": "AttendanceStatsClassSelector",
"file": "components/attendance-stats-class-selector.tsx",
"purpose": "考勤统计页班级筛选器Link 筛选按钮组,含 focus-visible 焦点样式)",
"deps": [
"next/link",
"shared/lib/utils.cn"
],
"usedBy": [
"teacher/attendance/stats/page.tsx"
]
}
]
}
@@ -9961,7 +10375,7 @@
{
"name": "getStudentProctoringStatuses",
"signature": "(examId: string) => Promise<StudentProctoringStatus[]>",
"purpose": "获取所有学生监考状态",
"purpose": "获取所有学生监考状态v3 优化Promise.all 并行执行 getUserNamesByIds 与事件聚合查询)",
"usedBy": [
"actions.getProctoringDashboardAction",
"teacher/exams/[id]/proctoring/page.tsx"
@@ -10102,7 +10516,7 @@
"name": "updateMasteryFromSubmission",
"signature": "(submissionId: string) => Promise<void>",
"file": "data-access.ts",
"purpose": "从提交答案更新掌握度按知识点聚合正确率onDuplicateKeyUpdate upsert",
"purpose": "从提交答案更新掌握度按知识点聚合正确率onDuplicateKeyUpdate upsertv3 优化Promise.all 并行执行多个知识点 upsert",
"deps": [
"shared.db",
"shared.db.schema.examSubmissions",
@@ -10118,7 +10532,7 @@
"name": "getClassMasterySummary",
"signature": "(classId: string) => Promise<ClassMasterySummary | null>",
"file": "data-access.ts",
"purpose": "获取班级掌握度摘要(学生数、平均掌握度、知识点统计、需重点关注学生)",
"purpose": "获取班级掌握度摘要(学生数、平均掌握度、知识点统计、需重点关注学生v3 优化:两阶段 Promise.all 并行查询班级信息+学生 ID、用户名+掌握度",
"deps": [
"shared.db",
"shared.db.schema.classes",
@@ -10182,7 +10596,7 @@
"name": "getDiagnosticReports",
"signature": "(filters: DiagnosticReportQueryParams) => Promise<DiagnosticReportWithDetails[]>",
"file": "data-access-reports.ts",
"purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名)",
"purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名v3 修复conditions 显式标注 SQL[] 类型,移除 round2 死代码",
"deps": [
"shared.db",
"shared.db.schema.learningDiagnosticReports",
@@ -10787,13 +11201,35 @@
]
},
{
"name": "getSubjectOptions",
"name": "buildCourseSelect",
"file": "data-access.ts",
"signature": "() => Promise<{id, name}[]>",
"purpose": "获取学科选项(按 order, name 排序",
"signature": "() => query builder",
"purpose": "构建 electiveCourses 表查询(仅查询本表字段,不跨表 JOINv3 重构:移除跨模块 LEFT JOIN名称解析改由 resolveCourseDisplayNames 异步聚合",
"usedBy": [
"admin/elective/create/page.tsx",
"admin/elective/[id]/edit/page.tsx"
"data-access.getElectiveCourses",
"data-access.getElectiveCourseById",
"data-access-selections.getAvailableCoursesForStudent"
]
},
{
"name": "mapCourseRow",
"file": "data-access.ts",
"signature": "(row: CourseCoreRow, display: {teacherName?, subjectName?, gradeName?}) => ElectiveCourseWithDetails",
"purpose": "将核心行 + 显示名映射为 ElectiveCourseWithDetailsv3 抽取:消除 data-access 与 data-access-selections 重复代码)",
"usedBy": [
"data-access.getElectiveCourses",
"data-access.getElectiveCourseById",
"data-access-selections.getAvailableCoursesForStudent"
]
},
{
"name": "resolveCourseDisplayNames",
"file": "data-access.ts",
"signature": "(rows: CourseCoreRow[]) => Promise<{teacherName?, subjectName?, gradeName?}[]>",
"purpose": "并行聚合教师名users.getUserNamesByIds、学科school.getSubjectOptions、年级school.getGradeOptions返回每行的显示名映射v3 重构:替代跨模块 LEFT JOIN",
"usedBy": [
"data-access.getElectiveCourses",
"data-access.getElectiveCourseById"
]
},
{
@@ -10838,7 +11274,7 @@
"name": "runLottery",
"file": "data-access-operations.ts",
"signature": "(courseId: string) => Promise<{enrolled: number, waitlist: number}>",
"purpose": "抽签录取(随机打乱 selected 记录,前 capacity 名 enrolled其余 waitlist课程 status=closed",
"purpose": "抽签录取(Fisher-Yates 无偏洗牌 selected 记录,前 capacity 名 enrolled其余 waitlist课程 status=closedv3 修复:替换 sort(Math.random) 有偏洗牌",
"usedBy": [
"actions.runLotteryAction"
]
@@ -10847,7 +11283,7 @@
"name": "selectCourse",
"file": "data-access-operations.ts",
"signature": "(courseId: string, studentId: string, priority?: number) => Promise<{status: CourseSelectionStatus, message: string}>",
"purpose": "学生选课(校验课程状态/时间窗口/重复选课FCFS 模式即时 enrolled/waitlistlottery 模式 selected",
"purpose": "学生选课(校验课程状态/时间窗口/重复选课FCFS 模式即时 enrolled/waitlistlottery 模式 selectedv3 修复db.transaction 包裹 + .for('update') 锁课程行防 FCFS 超卖",
"usedBy": [
"actions.selectCourseAction"
]
@@ -10856,7 +11292,7 @@
"name": "dropCourse",
"file": "data-access-operations.ts",
"signature": "(courseId: string, studentId: string) => Promise<void>",
"purpose": "学生退课status=droppedFCFS 模式自动递补 waitlist 首位)",
"purpose": "学生退课status=droppedFCFS 模式自动递补 waitlist 首位v3 修复db.transaction 包裹 + .for('update') 锁课程行保证递补一致性",
"usedBy": [
"actions.dropCourseAction"
]
@@ -10893,6 +11329,12 @@
"file": "types.ts",
"definition": "ElectiveCourse & { teacherName?, subjectName?, gradeName? }"
},
{
"name": "CourseCoreRow",
"type": "type",
"file": "data-access.ts",
"definition": "buildCourseSelect 返回行的推断类型v3 新增:供 mapCourseRow/resolveCourseDisplayNames 共享)"
},
{
"name": "CourseSelection",
"type": "interface",
@@ -11025,7 +11467,7 @@
},
"lesson_preparation": {
"path": "src/modules/lesson-preparation",
"description": "教师备课模块:基于教材章节创建课案(Block 编辑器),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布",
"description": "教师备课模块:基于教材章节创建课案(节点图编辑器 React Flowv2 nodes+edges 数据结构),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。编辑器从列表式BlockRenderer + @dnd-kit升级为节点图式NodeEditor + @xyflow/react旧 v1 数据通过 migrateV1ToV2() 自动迁移",
"exports": {
"dataAccess": [
{
@@ -11046,7 +11488,7 @@
{
"name": "updateLessonPlanContent",
"file": "data-access.ts",
"purpose": "更新课案内容(Block JSON"
"purpose": "更新课案内容(v2 nodes+edges JSON"
},
{
"name": "softDeleteLessonPlan",
@@ -11066,7 +11508,17 @@
{
"name": "buildInitialContent",
"file": "data-access.ts",
"purpose": "基于模板构建初始课案内容"
"purpose": "基于模板构建初始课案内容v2 nodes+edges"
},
{
"name": "migrateV1ToV2",
"file": "data-access.ts",
"purpose": "v1→v2 迁移:将旧 blocks 数组转换为 nodes + 线性 edges节点按网格布局"
},
{
"name": "normalizeDocument",
"file": "data-access.ts",
"purpose": "规范化:确保 content 为 v2 格式,兼容旧 v1 数据(自动调用 migrateV1ToV2"
},
{
"name": "getLessonPlanVersions",
@@ -11229,7 +11681,8 @@
"homework",
"classes",
"files",
"shared/lib/ai"
"shared/lib/ai",
"@xyflow/react"
],
"files": [
"types.ts",
@@ -11251,6 +11704,9 @@
"components/lesson-plan-card.tsx",
"components/lesson-plan-filters.tsx",
"components/lesson-plan-editor.tsx",
"components/node-editor.tsx",
"components/node-edit-panel.tsx",
"components/nodes/lesson-node.tsx",
"components/block-renderer.tsx",
"components/template-picker.tsx",
"components/version-history-drawer.tsx",
@@ -12028,6 +12484,7 @@
"shared": [
"db",
"auth-guard.requireAuth",
"auth-guard.getAuthContext",
"db.schema.parentStudentRelations",
"types"
],
@@ -12041,14 +12498,13 @@
"classes": [
"data-access.getStudentClasses",
"data-access.getStudentSchedule",
"data-access.getClassNameById",
"data-access.getStudentActiveClassId"
"data-access.getStudentActiveClass"
],
"grades": [
"data-access.getStudentGradeSummary"
],
"school": [
"data-access.getGradeOptions"
"data-access.getGradeNameById"
],
"users": [
"data-access.getUserBasicInfo",
@@ -12060,7 +12516,8 @@
"dependsOn": [
"shared",
"auth",
"notifications"
"notifications",
"classes"
],
"uses": {
"shared": [
@@ -12087,6 +12544,10 @@
"data-access.getUnreadNotificationCount (via re-export)",
"preferences.getNotificationPreferences (via re-export)",
"preferences.upsertNotificationPreferences (via re-export)"
],
"classes": [
"data-access.getTeacherIdsByClassIds",
"data-access.getStudentActiveClassId"
]
}
},
@@ -12332,6 +12793,11 @@
"files": [
"data-access.createFileAttachment",
"data-access.getFileAttachmentsByTarget"
],
"external": [
"@xyflow/reactReact Flow 节点图编辑器ReactFlow/Background/Controls/MiniMap/Handle/applyNodeChanges/applyEdgeChanges",
"@paralleldrive/cuid2节点 ID 生成)",
"zustand编辑器状态管理"
]
}
}
@@ -12449,6 +12915,12 @@
"type": "data-access",
"description": "✅ P0-4 / P1-5 已修复messaging 通过 sendNotification dispatcher 发送通知,通知 CRUD 和偏好通过 re-export 保持向后兼容"
},
{
"from": "messaging",
"to": "classes",
"type": "data-access",
"description": "getRecipients 通过 classes data-access.getTeacherIdsByClassIds / getStudentActiveClassId 获取班级教师 ID支持 class_members学生和 children家长数据范围"
},
{
"from": "classes",
"to": "homework",
@@ -13456,9 +13928,11 @@
"component": "StudentDashboardView",
"type": "server",
"dataAccess": [
"dashboard/data-access (student)",
"homework/data-access.getStudentDashboardGrades",
"classes/data-access.getStudentClasses"
"users/data-access.getCurrentStudentUser",
"classes/data-access.getStudentClasses",
"classes/data-access.getStudentSchedule",
"homework/data-access.getStudentHomeworkAssignments",
"homework/data-access.getStudentDashboardGrades"
],
"permission": "homework:submit"
},
@@ -13467,13 +13941,14 @@
"type": "server",
"module": "homework",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"homework/data-access.getStudentHomeworkAssignments"
],
"permission": "homework:submit"
},
"/student/learning/assignments/[assignmentId]": {
"component": "学生作答/复习",
"type": "client",
"type": "server",
"module": "homework",
"actions": [
"startHomeworkSubmissionAction",
@@ -13481,6 +13956,7 @@
"submitHomeworkAction"
],
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"homework/data-access.getStudentHomeworkTakeData"
],
"permission": "homework:submit"
@@ -13488,6 +13964,10 @@
"/student/learning/courses": {
"component": "StudentCoursesView",
"type": "server",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"classes/data-access.getStudentClasses"
],
"permission": "homework:submit"
},
"/student/learning/textbooks": {
@@ -13495,18 +13975,20 @@
"type": "server",
"module": "textbooks",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"textbooks/data-access.getTextbooks"
],
"permission": "textbook:read"
},
"/student/learning/textbooks/[id]": {
"component": "学生教材阅读(只读)",
"type": "client",
"type": "server",
"module": "textbooks",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"textbooks/data-access.getTextbookById",
"getChaptersByTextbookId",
"getKnowledgePointsByTextbookId"
"textbooks/data-access.getChaptersByTextbookId",
"textbooks/data-access.getKnowledgePointsByTextbookId"
],
"permission": "textbook:read"
},
@@ -13515,6 +13997,8 @@
"type": "server",
"module": "classes",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"classes/data-access.getStudentClasses",
"classes/data-access.getStudentSchedule"
],
"permission": "homework:submit"
@@ -13524,7 +14008,7 @@
"type": "server",
"module": "grades",
"dataAccess": [
"grades/actions.getStudentGradeSummaryAction"
"grades/data-access.getStudentGradeSummary"
],
"permission": "grade_record:read"
},
@@ -13540,7 +14024,7 @@
},
"/student/diagnostic": {
"component": "StudentDiagnosticView",
"type": "client",
"type": "server",
"module": "diagnostic",
"dataAccess": [
"diagnostic/data-access.getStudentMasterySummary (ctx.userId)",