{ "_meta": { "project": "Next_Edu", "description": "K12 智慧教务管理系统", "generatedAt": "2026-06-17", "formatVersion": "1.1", "rule": "每次文件修改后须同步更新本文件", "lastUpdate": "新增 error-book(错题本)模块:(1) DB schema 新增 2 表 errorBookItems(18列/4索引/2外键)+ errorBookReviews(8列/2索引/2外键),含 SM-2 间隔重复字段(nextReviewAt/reviewInterval/reviewCount/correctStreak/masteryLevel)。(2) 新增 3 权限点 ERROR_BOOK_READ/ERROR_BOOK_MANAGE/ERROR_BOOK_ANALYTICS_READ,分配给 6 角色(student: READ+MANAGE, parent: READ, teacher/admin/grade_head/teaching_head: ANALYTICS_READ)。(3) 模块结构:actions.ts(9 个 Server Actions)+ data-access.ts(16 个函数,含 SM-2 算法实现 calculateNewInterval/calculateNewMastery/deriveStatus/calculateNextReviewAt)+ schema.ts(4 个 Zod schema)+ types.ts(6 个类型)+ 9 个组件。(4) 自动采集:collectFromExamSubmission + collectFromHomeworkSubmission 从考试/作业提交自动收录错题(去重)。(5) 跨模块查询:getStudentErrorBookSummaries/getTopWrongQuestionsByStudentIds/getKnowledgePointWeakness/getSubjectErrorDistribution 供教师/家长/管理员视图使用。(6) 4 个角色页面:student(统计/筛选/列表/手动添加/详情复习)、teacher(班级概览/薄弱知识点/学科分布/高频错题)、parent(子女错题统计/薄弱知识点/高频错题)、admin(全校错题分析)。(7) DataScope 行级权限:student=owned, parent=children, teacher=class_taught, admin=all, grade_head/teaching_head=grade_managed。(8) 导航:6 角色均添加错题分析导航项(BookX 图标)。(9) i18n:zh-CN/en 双语翻译文件。前序:exam-homework-audit-v2 全量修复已同步:(1) P0-3 ExamModeConfig 全链路集成:formSchema 扩展 6 字段(examMode/durationMinutes/shuffleQuestions/allowLateStart/lateStartGraceMinutes/antiCheatEnabled)+superRefine 校验,exam-form onSubmit 追加 FormData,actions.ts 新增 parseExamModeConfig 解析,data-access persistExamDraft/persistAiGeneratedExamDraft 写入 DB。(2) P1-6 类型断言清理:exam-form.tsx 用 Resolver 替代 as any;exam-actions.tsx 用 RawStructureNode 类型守卫替代 as unknown as Question;homework-take-view.tsx 用类型收窄替代 as unknown[];homework-grading-view.tsx 用 getOptions+类型守卫替代 as ChoiceOption[];homework/data-access.ts 移除 as unknown。(3) P1-7 ai-pipeline.ts(857行) 拆分为 ai-pipeline/ 目录(parse.ts/request.ts/structure.ts/index.ts)。(4) P1-8 getHomeworkSubmissionDetails 相邻记录查询优化为 LIMIT 1 双查询。(5) P2-9 useDebouncedAutoSave hook 集成到 homework-take-view:3秒debounce+localStorage离线缓存+网络恢复重试+UI状态指示器(idle/saving/saved/error)+提交前flush。(6) P2-12 a11y:exam-columns 难度色条加 role=img+aria-label(i18n);homework-take 题目导航按钮加 aria-pressed+title。(7) P2-13 ExamHomeworkRoleConfig:shared/config/exam-homework-role-config.ts(6角色×11功能特性+并集合并函数)+shared/hooks/use-exam-homework-features.ts。(8) 6.1 ExamHomeworkServicePort:shared/services/exam-homework-port.ts(接口定义+ServiceProvider单例注册器)。(9) 6.5 单测:question-content-utils.test.ts(52测试覆盖14纯函数+applyAutoGrades)+exam-homework-role-config.test.ts(11测试覆盖角色合并)。(10) 6.7 trackExamEvent:track-event.ts 扩展17个exam/homework事件+trackExamEvent便捷函数。前序:teacher_bug_v4 P1-3+P2-1 修复已同步:(1) P1-3 空状态 CTA 优化:empty-state.tsx 默认按钮 variant 从 default 改为 outline,新增 variant prop;button.tsx 导出 ButtonProps 类型;返回路径统一为 ghost+ArrowLeft+文字标签模式(textbooks/[id]、grades/analytics、homework/assignments/[id]、course-plans/[id]、lesson-plans/new);course-plan-detail 中 raw 改为 。(2) P2-1 日期格式本地化+中英文统一:formatLongDate 默认 locale 从 en-US 改为 zh-CN,weekday 从 long 改为 short;teacher 导航项全部中文化(仪表盘/教材/考试/作业/成绩/题库/班级管理/课程计划/我的备课/考勤/调课申请/学情诊断/选修课/年级管理/公告/消息);app-sidebar Collapse 按钮改为「收起」;5 个详情页返回按钮文案中文化;course-plan-detail 组件全量中文化(状态标签/表头/按钮/空状态/删除对话框/toast);grades/analytics 页面标题与描述中文化。前序:Announcements 公告模块修复已同步:(1) getAnnouncements 新增 audience 受众过滤参数(school 全可见 / grade 按年级 / class 按班级),使用 or+and 组合条件;(2) 用户端列表页 /announcements 传入 audience(根据 ctx.dataScope 解析 gradeId/classId,admin 不过滤);(3) 新增用户端公告详情页 /announcements/[id](只读模式 canManage=false,requirePermission ANNOUNCEMENT_READ);(4) 用户端列表页传递 detailHrefBuilder;(5) 管理端列表页 /admin/announcements 增加 getAdminClasses 调用,传递 classes 给 AdminAnnouncementsView;(6) 发布公告触发通知:publishAnnouncementAction/createAnnouncementAction(直接发布)/updateAnnouncementAction(状态变 published) 调用 sendBatchNotifications,根据公告类型查询目标用户(school=全部/grade=按年级/class=学生+教师),新增 users/data-access.getAllUserIds 函数;(7) 新增 loading.tsx 骨架屏(用户端 + 管理端)。前序:Profile/Settings 模块修复已同步:(1) 新增 profile/settings/settings/security 的 loading.tsx + error.tsx(参考 admin 模式);(2) settings/page.tsx 增加 parent 角色分支,新增 ParentSettingsView 组件(backHref 指向 /parent/dashboard);(3) SettingsView 集成 AiProviderSettingsCard(新增 AI 标签页,条件渲染需 AI_CONFIGURE 权限);(4) profile/page.tsx 添加 Avatar 头像展示(从 userProfile.image 获取,无头像显示首字母 fallback);(5) SettingsView Tab URL 持久化(useSearchParams 读取 tab 参数,router.push 更新 URL,Suspense 包装);(6) SettingsView 登出按钮 AlertDialog 二次确认;(7) password-change-form 修复任意值 Tailwind 类([&>div]:bg-red-500 改为 Progress 组件新增 indicatorClassName prop + 标准颜色类);(8) profile/page.tsx 保持 requireAuth(页面仅查看,编辑在 settings 页面有权限校验)。前序:第二轮共享组件抽取重构已同步:P0-1 ConfirmDeleteDialog(5 处 AlertDialog 删除确认块抽取);P0-2 Pagination(3 处审计表格分页块抽取);P0-3 EmptyTableRow(3 处审计表格空行抽取);P1-1 StatusBadge + typeColors 共享(9+ 处状态徽章抽取,修复 StudentHomeworkProgressStatus 在 3 个文件中颜色不一致 bug,统一 audit/grades/homework/questions 状态映射到模块 types.ts);P1-2 TextField/SelectField/TextareaField 表单字段抽取(profile-settings-form 6+1、exam-basic-info-form 4+3、ai-provider-settings-card 4+1、create-question-dialog 2+1 共 26 处 FormField 重复抽取);P1-3 统一 formatDate/formatDateTime/formatLongDate(8 处 toLocaleDateString/toLocaleString 抽取);P1-4 useActionQuery + useActionMutation Hook 抽取(schools-view 3 处 mutation 示范重构,create-question-dialog 1 处 query 重构,潜在影响 50+ 文件)。新增 shared 层 7 个 UI 组件 + 2 个 Hooks + 2 个工具函数。前序: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.ts(P0-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-access,4 个页面改为从 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-access;P2-2/P2-3/P2-12/P2-20 已修复" }, "architectureOverview": { "layers": [ { "name": "app", "description": "路由层,Next.js App Router,按角色分组(admin/teacher/student/parent/management)", "path": "src/app/" }, { "name": "modules", "description": "业务模块层,按业务领域划分(25 个模块)", "path": "src/modules/" }, { "name": "shared", "description": "基础设施层,提供共享能力(db/lib/hooks/components/types)", "path": "src/shared/" } ], "dependencyRule": "app → modules → shared (单向依赖,上层可依赖下层,下层不可依赖上层)", "violations": [ "✅ shared/lib ↔ auth 循环依赖已修复(audit-logger/change-logger/auth-guard 改为通过 shared/lib/session.ts 单一入口获取 session,session.ts 内部 dynamic import @/auth 打破静态循环)", "✅ dashboard 跨模块直查 11 张表已修复(改为并行调用各模块 dashboard stats 函数: getUsersDashboardStats/getClassesDashboardStats/getTextbooksDashboardStats/getQuestionsDashboardStats/getExamsDashboardStats/getHomeworkDashboardStats)", "✅ messaging 绕过 notifications dispatcher 已修复(通知 CRUD 和偏好迁移至 notifications 模块,messaging 通过 dispatcher 发送通知)", "✅ classes/data-access.ts 混入 homework/scheduling/grades 跨模块逻辑已修复(拆分为 5 个文件,classes 通过 homework/data-access-classes 获取作业数据)", "✅ classSchedule 表三处写入口已统一(写函数从 classes 迁移至 scheduling/data-access-class-schedule.ts,classes 仅保留读函数)", "✅ P1-1 跨模块直查已修复(homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 等模块的直查改为通过对方 data-access 接口)" ] }, "techStack": { "framework": "Next.js 16 (App Router)", "language": "TypeScript (strict)", "ui": "React 19 + Tailwind CSS v4 + shadcn/ui", "state": [ "Zustand", "nuqs", "React Hook Form" ], "database": "MySQL + Drizzle ORM 0.45", "auth": "NextAuth v5 (JWT strategy)", "validation": "Zod 4", "ai": "OpenAI SDK (multi-provider)", "testing": [ "Vitest", "Playwright" ] }, "roles": [ "admin", "teacher", "student", "parent", "grade_head", "teaching_head" ], "permissions": { "EXAM_CREATE": "exam:create", "EXAM_READ": "exam:read", "EXAM_UPDATE": "exam:update", "EXAM_DELETE": "exam:delete", "EXAM_DUPLICATE": "exam:duplicate", "EXAM_PUBLISH": "exam:publish", "EXAM_AI_GENERATE": "exam:ai_generate", "EXAM_SUBMIT": "exam:submit", "HOMEWORK_CREATE": "homework:create", "HOMEWORK_GRADE": "homework:grade", "HOMEWORK_SUBMIT": "homework:submit", "QUESTION_CREATE": "question:create", "QUESTION_READ": "question:read", "QUESTION_UPDATE": "question:update", "QUESTION_DELETE": "question:delete", "TEXTBOOK_CREATE": "textbook:create", "TEXTBOOK_READ": "textbook:read", "TEXTBOOK_UPDATE": "textbook:update", "TEXTBOOK_DELETE": "textbook:delete", "CLASS_CREATE": "class:create", "CLASS_READ": "class:read", "CLASS_UPDATE": "class:update", "CLASS_DELETE": "class:delete", "CLASS_ENROLL": "class:enroll", "CLASS_SCHEDULE": "class:schedule", "SCHOOL_MANAGE": "school:manage", "GRADE_MANAGE": "grade:manage", "USER_MANAGE": "user:manage", "AI_CHAT": "ai:chat", "AI_CONFIGURE": "ai:configure", "SETTINGS_ADMIN": "settings:admin", "AUDIT_LOG_READ": "audit_log:read", "ANNOUNCEMENT_MANAGE": "announcement:manage", "ANNOUNCEMENT_READ": "announcement:read", "GRADE_RECORD_MANAGE": "grade_record:manage", "GRADE_RECORD_READ": "grade_record:read", "FILE_UPLOAD": "file:upload", "FILE_READ": "file:read", "FILE_DELETE": "file:delete", "COURSE_PLAN_MANAGE": "course_plan:manage", "COURSE_PLAN_READ": "course_plan:read", "ATTENDANCE_MANAGE": "attendance:manage", "ATTENDANCE_READ": "attendance:read", "MESSAGE_SEND": "message:send", "MESSAGE_READ": "message:read", "MESSAGE_DELETE": "message:delete", "SCHEDULE_AUTO": "schedule:auto", "SCHEDULE_ADJUST": "schedule:adjust", "DIAGNOSTIC_MANAGE": "diagnostic:manage", "DIAGNOSTIC_READ": "diagnostic:read", "ELECTIVE_MANAGE": "elective:manage", "ELECTIVE_READ": "elective:read", "ELECTIVE_SELECT": "elective:select", "EXAM_PROCTOR": "exam:proctor", "EXAM_PROCTOR_READ": "exam:proctor:read", "LESSON_PLAN_CREATE": "lesson_plan:create", "LESSON_PLAN_READ": "lesson_plan:read", "LESSON_PLAN_UPDATE": "lesson_plan:update", "LESSON_PLAN_DELETE": "lesson_plan:delete", "LESSON_PLAN_PUBLISH": "lesson_plan:publish", "DASHBOARD_ADMIN_READ": "dashboard:admin_read", "DASHBOARD_TEACHER_READ": "dashboard:teacher_read", "DASHBOARD_STUDENT_READ": "dashboard:student_read", "DASHBOARD_PARENT_READ": "dashboard:parent_read", "ERROR_BOOK_READ": "error_book:read", "ERROR_BOOK_MANAGE": "error_book:manage", "ERROR_BOOK_ANALYTICS_READ": "error_book:analytics_read" }, "rolePermissions": { "admin": [ "EXAM_CREATE", "EXAM_READ", "EXAM_UPDATE", "EXAM_DELETE", "EXAM_DUPLICATE", "EXAM_PUBLISH", "EXAM_AI_GENERATE", "HOMEWORK_CREATE", "HOMEWORK_GRADE", "QUESTION_CREATE", "QUESTION_READ", "QUESTION_UPDATE", "QUESTION_DELETE", "TEXTBOOK_CREATE", "TEXTBOOK_READ", "TEXTBOOK_UPDATE", "TEXTBOOK_DELETE", "CLASS_CREATE", "CLASS_READ", "CLASS_UPDATE", "CLASS_DELETE", "CLASS_ENROLL", "CLASS_SCHEDULE", "SCHOOL_MANAGE", "GRADE_MANAGE", "USER_MANAGE", "AI_CHAT", "AI_CONFIGURE", "SETTINGS_ADMIN", "AUDIT_LOG_READ", "ANNOUNCEMENT_MANAGE", "ANNOUNCEMENT_READ", "GRADE_RECORD_MANAGE", "GRADE_RECORD_READ", "FILE_UPLOAD", "FILE_READ", "FILE_DELETE", "COURSE_PLAN_MANAGE", "COURSE_PLAN_READ", "ATTENDANCE_MANAGE", "ATTENDANCE_READ", "MESSAGE_SEND", "MESSAGE_READ", "MESSAGE_DELETE", "SCHEDULE_AUTO", "SCHEDULE_ADJUST", "DIAGNOSTIC_MANAGE", "DIAGNOSTIC_READ", "ELECTIVE_MANAGE", "ELECTIVE_READ", "EXAM_PROCTOR", "EXAM_PROCTOR_READ", "DASHBOARD_ADMIN_READ", "ERROR_BOOK_ANALYTICS_READ" ], "teacher": [ "EXAM_CREATE", "EXAM_READ", "EXAM_UPDATE", "EXAM_DELETE", "EXAM_DUPLICATE", "EXAM_PUBLISH", "EXAM_AI_GENERATE", "HOMEWORK_CREATE", "HOMEWORK_GRADE", "QUESTION_CREATE", "QUESTION_READ", "QUESTION_UPDATE", "QUESTION_DELETE", "TEXTBOOK_CREATE", "TEXTBOOK_READ", "TEXTBOOK_UPDATE", "CLASS_READ", "CLASS_ENROLL", "CLASS_SCHEDULE", "AI_CHAT", "ANNOUNCEMENT_READ", "GRADE_RECORD_MANAGE", "GRADE_RECORD_READ", "FILE_UPLOAD", "FILE_READ", "FILE_DELETE", "COURSE_PLAN_READ", "ATTENDANCE_MANAGE", "ATTENDANCE_READ", "MESSAGE_SEND", "MESSAGE_READ", "MESSAGE_DELETE", "DIAGNOSTIC_MANAGE", "DIAGNOSTIC_READ", "ELECTIVE_MANAGE", "ELECTIVE_READ", "EXAM_PROCTOR", "EXAM_PROCTOR_READ", "DASHBOARD_TEACHER_READ", "ERROR_BOOK_ANALYTICS_READ" ], "student": [ "EXAM_READ", "EXAM_SUBMIT", "HOMEWORK_SUBMIT", "QUESTION_READ", "TEXTBOOK_READ", "CLASS_READ", "AI_CHAT", "ANNOUNCEMENT_READ", "GRADE_RECORD_READ", "FILE_READ", "COURSE_PLAN_READ", "ATTENDANCE_READ", "MESSAGE_SEND", "MESSAGE_READ", "MESSAGE_DELETE", "DIAGNOSTIC_READ", "ELECTIVE_SELECT", "ELECTIVE_READ", "DASHBOARD_STUDENT_READ", "ERROR_BOOK_READ", "ERROR_BOOK_MANAGE" ], "parent": [ "EXAM_READ", "TEXTBOOK_READ", "CLASS_READ", "ANNOUNCEMENT_READ", "GRADE_RECORD_READ", "FILE_READ", "ATTENDANCE_READ", "MESSAGE_SEND", "MESSAGE_READ", "MESSAGE_DELETE", "DASHBOARD_PARENT_READ", "ERROR_BOOK_READ" ], "grade_head": [ "EXAM_CREATE", "EXAM_READ", "EXAM_UPDATE", "EXAM_DELETE", "EXAM_DUPLICATE", "EXAM_PUBLISH", "EXAM_AI_GENERATE", "HOMEWORK_CREATE", "HOMEWORK_GRADE", "QUESTION_CREATE", "QUESTION_READ", "QUESTION_UPDATE", "QUESTION_DELETE", "TEXTBOOK_CREATE", "TEXTBOOK_READ", "TEXTBOOK_UPDATE", "CLASS_CREATE", "CLASS_READ", "CLASS_UPDATE", "CLASS_ENROLL", "CLASS_SCHEDULE", "GRADE_MANAGE", "AI_CHAT", "ANNOUNCEMENT_READ", "GRADE_RECORD_READ", "COURSE_PLAN_READ", "ATTENDANCE_READ", "MESSAGE_SEND", "MESSAGE_READ", "MESSAGE_DELETE", "DIAGNOSTIC_MANAGE", "DIAGNOSTIC_READ", "ELECTIVE_READ", "EXAM_PROCTOR_READ", "ERROR_BOOK_ANALYTICS_READ" ], "teaching_head": [ "EXAM_CREATE", "EXAM_READ", "EXAM_UPDATE", "EXAM_DELETE", "EXAM_DUPLICATE", "EXAM_PUBLISH", "EXAM_AI_GENERATE", "HOMEWORK_CREATE", "HOMEWORK_GRADE", "QUESTION_CREATE", "QUESTION_READ", "QUESTION_UPDATE", "QUESTION_DELETE", "TEXTBOOK_CREATE", "TEXTBOOK_READ", "TEXTBOOK_UPDATE", "CLASS_READ", "GRADE_MANAGE", "AI_CHAT", "ANNOUNCEMENT_READ", "GRADE_RECORD_READ", "COURSE_PLAN_READ", "ATTENDANCE_READ", "MESSAGE_SEND", "MESSAGE_READ", "MESSAGE_DELETE", "DIAGNOSTIC_READ", "ELECTIVE_READ", "ERROR_BOOK_ANALYTICS_READ" ] }, "dataScopeTypes": { "all": "管理员:无过滤", "owned": "仅自己创建的资源,含 userId 字段", "class_taught": "教师:所教班级,含 classIds[] 和可选 subjectIds[]", "grade_managed": "年级主任:所管年级,含 gradeIds[]", "class_members": "学生:所在班级的成员数据", "children": "家长:子女数据,含 childrenIds[]" }, "modules": { "shared": { "path": "src/shared", "description": "全项目共享基础设施:数据库、工具函数、权限系统、UI组件、Hooks", "exports": { "functions": [ { "name": "cn", "file": "lib/utils.ts", "signature": "cn(...inputs: ClassValue[]): string", "purpose": "合并CSS类名并解决Tailwind冲突", "deps": [ "clsx", "tailwind-merge" ], "usedBy": [ "*所有模块组件" ] }, { "name": "formatDate", "file": "lib/utils.ts", "signature": "formatDate(date: string | Date, locale?: string): string", "params": { "date": "日期值", "locale": "Intl locale,默认zh-CN" }, "purpose": "国际化日期格式化", "deps": [], "usedBy": [ "exams", "homework", "dashboard", "textbooks" ] }, { "name": "formatDateTime", "file": "lib/utils.ts", "signature": "formatDateTime(date: string | Date, locale?: string): string", "params": { "date": "日期值", "locale": "Intl locale,默认zh-CN" }, "purpose": "国际化日期+时间格式化(含小时、分钟),P1-3 重构从 lesson-plan-card、version-history-drawer、proctoring-dashboard、exam-ai-generator 等处重复的 new Date(x).toLocaleString(...) 抽取", "deps": [], "usedBy": [ "lesson-preparation/components/lesson-plan-card.tsx", "lesson-preparation/components/version-history-drawer.tsx", "proctoring/components/proctoring-dashboard.tsx", "exams/components/exam-ai-generator.tsx" ] }, { "name": "formatLongDate", "file": "lib/utils.ts", "signature": "formatLongDate(date: string | Date, locale?: string): string", "params": { "date": "日期值", "locale": "Intl locale,默认zh-CN" }, "purpose": "国际化长日期格式化(含星期、完整月份名),P1-3 重构从 teacher-dashboard-header 中重复的 new Date().toLocaleDateString('en-US', { weekday, year, month, day }) 抽取;teacher_bug_v4 P2-1 默认 locale 改为 zh-CN,weekday 改为 short,输出形如「2026年6月20日周一」", "deps": [], "usedBy": [ "dashboard/components/teacher-dashboard/teacher-dashboard-header.tsx" ] }, { "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 | undefined),re-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", "signature": "parseAiChatPayload(body: unknown): AiChatRequest", "purpose": "解析并校验AI聊天请求负载", "deps": [ "zod" ], "usedBy": [ "app/api/ai/chat/route.ts" ] }, { "name": "encryptAiApiKey", "file": "lib/ai/api-key-crypto.ts", "signature": "encryptAiApiKey(value: string): string", "purpose": "AES加密AI Provider API Key", "deps": [ "crypto" ], "usedBy": [ "settings/actions.ts" ] }, { "name": "decryptAiApiKey", "file": "lib/ai/api-key-crypto.ts", "signature": "decryptAiApiKey(value: string): string", "purpose": "AES解密AI Provider API Key", "deps": [ "crypto" ], "usedBy": [ "settings/actions.ts", "ai/api-key-crypto.ts内部" ] }, { "name": "testAiProviderConfig", "file": "lib/ai/client.ts", "signature": "testAiProviderConfig(input: { apiKey: string; baseUrl?: string; model: string }): Promise", "purpose": "测试AI Provider连通性(直接配置)", "deps": [ "createAiChatCompletion" ], "usedBy": [ "settings/actions.ts" ] }, { "name": "testAiProviderById", "file": "lib/ai/client.ts", "signature": "testAiProviderById(providerId: string, overrides?: { baseUrl?: string; model?: string }): Promise", "purpose": "测试AI Provider连通性(按ID)", "deps": [ "shared/db", "createAiChatCompletion" ], "usedBy": [ "settings/actions.ts" ] }, { "name": "createAiChatCompletion", "file": "lib/ai/client.ts", "signature": "createAiChatCompletion(input: AiChatRequest): Promise<{ content: string; usage: unknown }>", "purpose": "调用AI模型生成聊天回复", "deps": [ "openai", "shared/db", "ai/provider-config.ts" ], "usedBy": [ "exams/ai-pipeline.ts", "app/api/ai/chat/route.ts" ] }, { "name": "getAiErrorMessage", "file": "lib/ai/errors.ts", "signature": "getAiErrorMessage(v: unknown): string", "purpose": "从AI错误中提取可读消息", "deps": [], "usedBy": [ "exams/ai-pipeline.ts" ] }, { "name": "getAuthContext", "file": "lib/auth-guard.ts", "signature": "getAuthContext(): Promise", "returns": "AuthContext { userId, roles, permissions, dataScope }", "purpose": "获取当前用户完整认证上下文", "deps": [ "shared/lib/session.getSession", "shared/db" ], "usedBy": [ "所有业务模块的Server Actions" ] }, { "name": "requirePermission", "file": "lib/auth-guard.ts", "signature": "requirePermission(permission: Permission): Promise", "throws": "PermissionDeniedError", "purpose": "断言当前用户拥有指定权限", "deps": [ "getAuthContext" ], "usedBy": [ "所有业务模块的Server Actions" ] }, { "name": "checkPermission", "file": "lib/auth-guard.ts", "signature": "checkPermission(permission: Permission): Promise<{ allowed: boolean; ctx: AuthContext }>", "purpose": "非抛出版权限检查", "deps": [ "getAuthContext" ], "usedBy": [] }, { "name": "requireAuth", "file": "lib/auth-guard.ts", "signature": "requireAuth(): Promise", "purpose": "仅断言用户已登录", "deps": [ "getAuthContext" ], "usedBy": [] }, { "name": "resolvePermissions", "file": "lib/permissions.ts", "signature": "resolvePermissions(roleNames: string[]): Permission[]", "purpose": "合并多角色的权限列表(去重)", "deps": [ "ROLE_PERMISSIONS" ], "usedBy": [ "auth.ts (JWT callback)" ] }, { "name": "logAudit", "file": "lib/audit-logger.ts", "signature": "logAudit(params: LogAuditParams): Promise", "purpose": "记录操作日志(静默失败)", "deps": [ "shared/lib/session.getSession", "shared/lib/http-utils.resolveClientIp", "shared/lib/http-utils.getUserAgent", "shared.db", "shared.db.schema.auditLogs", "@paralleldrive/cuid2" ], "usedBy": [ "school/actions.ts", "其他Server Actions" ] }, { "name": "logLoginEvent", "file": "lib/login-logger.ts", "signature": "logLoginEvent(params: LogLoginEventParams): Promise", "purpose": "记录登录日志(signin/signout/signup,静默失败)", "deps": [ "shared/lib/http-utils.resolveClientIp", "shared/lib/http-utils.getUserAgent", "shared.db", "shared.db.schema.loginLogs", "@paralleldrive/cuid2" ], "usedBy": [ "auth.ts (events.signIn, events.signOut)" ] }, { "name": "normalizeRole", "file": "lib/role-utils.ts", "signature": "normalizeRole(value: unknown): NormalizedRole", "purpose": "将角色值规范化为 admin/teacher/student/parent 之一(纯函数,legacy 别名 grade_head/teaching_head→teacher)", "deps": [], "usedBy": [ "auth.ts (jwt/session callbacks)", "lib/role-utils.resolvePrimaryRole" ] }, { "name": "resolvePrimaryRole", "file": "lib/role-utils.ts", "signature": "resolvePrimaryRole(roleNames: string[]): NormalizedRole", "purpose": "从多角色列表解析主角色(优先级 admin>teacher>parent>student,纯函数)", "deps": [ "lib/role-utils.normalizeRole" ], "usedBy": [ "auth.ts (authorize, jwt callback)" ] }, { "name": "normalizeBcryptHash", "file": "lib/bcrypt-utils.ts", "signature": "normalizeBcryptHash(value: string): string", "purpose": "将存储的 bcrypt 哈希规范化为 $2b$ 前缀形式(纯函数,兼容 legacy 无前缀存储)", "deps": [], "usedBy": [ "auth.ts (authorize)" ] }, { "name": "resolveClientIp", "file": "lib/http-utils.ts", "signature": "resolveClientIp(): Promise", "purpose": "从请求头解析客户端 IP(x-forwarded-for 第一段/x-real-ip,best-effort,失败返回 unknown)", "deps": [ "next/headers" ], "usedBy": [ "auth.ts (authorize 速率限制键)", "lib/audit-logger.logAudit", "lib/change-logger.logDataChange", "lib/login-logger.logLoginEvent" ] }, { "name": "getUserAgent", "file": "lib/http-utils.ts", "signature": "getUserAgent(): Promise", "purpose": "从请求头解析 User-Agent(best-effort,失败返回 unknown)", "deps": [ "next/headers" ], "usedBy": [ "lib/audit-logger.logAudit", "lib/login-logger.logLoginEvent" ] }, { "name": "getSession", "file": "lib/session.ts", "signature": "getSession(): Promise", "purpose": "session 获取单一入口(server-only,内部 dynamic import @/auth 打破 shared/lib ↔ auth 静态循环依赖)", "deps": [ "@/auth (dynamic import)" ], "usedBy": [ "lib/auth-guard.getAuthContext", "lib/audit-logger.logAudit", "lib/change-logger.logDataChange" ] }, { "name": "getOrCreatePasswordSecurity", "file": "lib/password-security-service.ts", "signature": "getOrCreatePasswordSecurity(db, passwordSecurity, userId: string): Promise", "purpose": "获取或创建用户的 password_security 行(server-only)", "deps": [ "drizzle-orm.eq", "@paralleldrive/cuid2", "shared.db", "shared.db.schema.passwordSecurity" ], "usedBy": [ "auth.ts (authorize)", "lib/password-security-service.recordFailedLogin", "lib/password-security-service.resetFailedLogin" ] }, { "name": "recordFailedLogin", "file": "lib/password-security-service.ts", "signature": "recordFailedLogin(db, passwordSecurity, userId: string): Promise<{ locked: boolean; lockedUntil: Date | null }>", "purpose": "递增失败登录计数,达到阈值则锁定账户(server-only)", "deps": [ "lib/password-security-service.getOrCreatePasswordSecurity", "lib/password-policy.PASSWORD_RULES", "shared.db", "shared.db.schema.passwordSecurity" ], "usedBy": [ "auth.ts (authorize 密码校验失败分支)" ] }, { "name": "resetFailedLogin", "file": "lib/password-security-service.ts", "signature": "resetFailedLogin(db, passwordSecurity, userId: string): Promise", "purpose": "登录成功后重置失败计数与锁定状态(server-only)", "deps": [ "lib/password-security-service.getOrCreatePasswordSecurity", "shared.db", "shared.db.schema.passwordSecurity" ], "usedBy": [ "auth.ts (authorize 登录成功分支)" ] }, { "name": "isAllowedMimeType", "file": "lib/file-storage.ts", "signature": "isAllowedMimeType(mimeType: string): boolean", "purpose": "判断MIME类型是否在允许上传的白名单中", "deps": [ "ALLOWED_MIME_TYPES 常量" ], "usedBy": [ "app/api/upload/route.ts", "files/components/file-upload.tsx" ] }, { "name": "generateStoragePath", "file": "lib/file-storage.ts", "signature": "generateStoragePath(originalName: string): string", "purpose": "根据原始文件名生成存储路径 uploads/YYYY-MM/cuid.ext(相对于public/)", "deps": [ "@paralleldrive/cuid2" ], "usedBy": [ "app/api/upload/route.ts" ] }, { "name": "getFileExtension", "file": "lib/file-storage.ts", "signature": "getFileExtension(filename: string): string", "purpose": "从文件名中提取小写扩展名(不含点)", "deps": [], "usedBy": [ "shared/lib/file-storage 内部" ] }, { "name": "formatFileSize", "file": "lib/file-storage.ts", "signature": "formatFileSize(bytes: number): string", "purpose": "将字节数格式化为人类可读字符串(如 1.5 MB、800 KB)", "deps": [], "usedBy": [ "files/components/file-upload.tsx", "files/components/file-list.tsx", "files/components/file-preview.tsx" ] }, { "name": "exportToExcel", "file": "lib/excel.ts", "signature": "exportToExcel(params: { sheets: ExcelSheet[] }): Promise", "params": { "sheets": "ExcelSheet[],每个含 name/columns/rows" }, "purpose": "将多 sheet 数据导出为 Excel Buffer(表头加粗+冻结首行+自动筛选)", "deps": [ "exceljs" ], "usedBy": [ "users/import-export.exportUsersToExcel", "grades/export.exportGradeRecordsToExcel", "grades/export.exportClassGradeReportToExcel" ] }, { "name": "parseExcel", "file": "lib/excel.ts", "signature": "parseExcel(buffer: Buffer): Promise", "returns": "ParsedSheet[],每个含 sheetName/rows", "purpose": "从 Buffer 解析 Excel 文件,首行作为表头,返回每 sheet 的行记录数组", "deps": [ "exceljs" ], "usedBy": [ "users/actions.importUsersAction", "app/api/import/route.ts" ] }, { "name": "generateTemplate", "file": "lib/excel.ts", "signature": "generateTemplate(params: { sheets: TemplateSheet[] }): Promise", "params": { "sheets": "TemplateSheet[],每个含 name/columns(含 note)/sampleRows?" }, "purpose": "生成导入模板 Buffer(表头加粗+第二行填写说明+示例行)", "deps": [ "exceljs" ], "usedBy": [ "users/import-export.generateUserImportTemplate" ] }, { "name": "useA11yId", "file": "lib/a11y.ts", "signature": "useA11yId(prefix: string): string", "purpose": "基于React.useId生成SSR安全的唯一ID,用于aria-describedby、aria-labelledby等", "deps": [ "react" ], "usedBy": [ "待扩展(表单组件、a11y组件)" ] }, { "name": "mergeA11yProps", "file": "lib/a11y.ts", "signature": "mergeA11yProps>(...props: (T | undefined | null | false)[]): T", "purpose": "合并多组aria/data属性,普通属性后者覆盖前者,aria-*/data-*字符串属性以空格拼接", "deps": [], "usedBy": [ "待扩展" ] }, { "name": "describeInput", "file": "lib/a11y.ts", "signature": "describeInput(describedBy?: string, error?: string, hint?: string): { ariaDescribedBy?: string; ariaInvalid?: boolean }", "purpose": "计算输入框的aria-describedby(合并多个ID)与aria-invalid(error存在则为true)", "deps": [], "usedBy": [ "待扩展(表单组件)" ] }, { "name": "loadingAria", "file": "lib/a11y.ts", "signature": "loadingAria(isLoading: boolean): { ariaBusy: boolean; ariaLive: 'polite' | 'assertive' }", "purpose": "提供加载状态的aria-busy与aria-live=polite属性", "deps": [], "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 xlsx),P1-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 标签点击 + revoke),P1-c 重构从 audit/audit-log-export-button 抽取", "deps": [], "usedBy": [ "shared/lib/download.downloadBase64File", "audit/components/audit-log-export-button.tsx" ] } ], "hooks": [ { "name": "useActionWithToast", "file": "hooks/use-action-with-toast.ts", "signature": "useActionWithToast(): { isPending: boolean; execute: (action: () => Promise>) => void }", "purpose": "包装Server Action + toast反馈" }, { "name": "useActionMutation", "file": "hooks/use-action-mutation.ts", "signature": "useActionMutation(options?: { successMessage?, errorMessage?, onSuccess?, onError? }): { isWorking: boolean; mutate: (action: () => Promise>) => Promise | undefined> }", "purpose": "通用 Server Action mutation Hook,P1-4 重构从 50+ 个文件中重复的 setIsWorking(true) + try/catch/finally + toast 模式抽取。支持 successMessage/errorMessage/onSuccess/onError 配置", "usedBy": [ "school/components/school-form-dialog.tsx", "school/components/school-delete-dialog.tsx" ] }, { "name": "useActionQuery", "file": "hooks/use-action-query.ts", "signature": "useActionQuery(action: () => Promise>, options?: { deps?, enabled?, errorMessage? }): { data, loading, error, refetch }", "purpose": "通用 Server Action 查询 Hook,P1-4 重构从 11 个文件中重复的 useEffect + useState(loading) + Action().then().catch().finally() 模式抽取。内置竞态防护(cancelled flag)", "usedBy": [ "questions/components/create-question-dialog.tsx" ] }, { "name": "useDebounce", "file": "hooks/use-debounce.ts", "signature": "useDebounce(value: T, delay?: number): T", "purpose": "防抖Hook" }, { "name": "useMediaQuery", "file": "hooks/use-media-query.ts", "signature": "useMediaQuery(query: string): boolean", "purpose": "响应式媒体查询Hook" }, { "name": "useLocalStorage", "file": "hooks/use-local-storage.ts", "signature": "useLocalStorage(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void]", "purpose": "localStorage持久化Hook" }, { "name": "usePermission", "file": "hooks/use-permission.ts", "signature": "usePermission(): { permissions: Permission[]; roles: string[]; hasPermission: (p: Permission) => boolean; hasAnyPermission: (...p: Permission[]) => boolean; hasAllPermissions: (...p: Permission[]) => boolean; hasRole: (r: string) => boolean }", "purpose": "客户端权限检查Hook", "usedBy": [ "layout/app-sidebar.tsx", "exams/components", "homework/components" ] }, { "name": "logDataChange", "file": "lib/change-logger.ts", "signature": "logDataChange(params: LogDataChangeParams): Promise", "purpose": "记录数据变更日志(写入 dataChangeLogs 表,自动获取 changedBy/changedByName/ipAddress;静默失败)", "deps": [ "shared/lib/session.getSession", "shared/lib/http-utils.resolveClientIp", "shared/db (dataChangeLogs)", "@paralleldrive/cuid2" ], "usedBy": [ "待扩展(数据变更场景)" ] }, { "name": "StorageProvider", "file": "lib/storage-provider.ts", "signature": "interface StorageProvider { save(file: Buffer, storagePath: string): Promise; read(storagePath: string): Promise; delete(storagePath: string): Promise; exists(storagePath: string): Promise; getUrl(storagePath: string): string }", "purpose": "文件存储抽象接口,便于未来切换到 OSS/S3", "usedBy": [ "app/api/files/batch-delete/route.ts" ] }, { "name": "LocalStorageProvider", "file": "lib/storage-provider.ts", "signature": "class LocalStorageProvider implements StorageProvider", "purpose": "本地磁盘存储实现,文件持久化到 public/uploads/...,URL 为 /uploads/...", "deps": [ "fs/promises", "path" ], "usedBy": [ "通过 storageProvider 实例使用" ] }, { "name": "storageProvider", "file": "lib/storage-provider.ts", "signature": "const storageProvider: StorageProvider", "purpose": "默认存储 Provider 单例(LocalStorageProvider 实例),替换此实例可迁移到 OSS/S3", "usedBy": [ "app/api/files/batch-delete/route.ts" ] } ], "components": [ { "name": "AuthSessionProvider", "file": "components/auth-session-provider.tsx", "purpose": "NextAuth SessionProvider包装", "usedBy": [ "app/layout.tsx" ] }, { "name": "OnboardingGate", "file": "components/onboarding-gate.tsx", "purpose": "新用户引导流程(已废弃,逻辑迁移至 modules/onboarding/)", "deprecated": true, "usedBy": [] }, { "name": "ThemeProvider", "file": "components/theme-provider.tsx", "purpose": "next-themes主题切换", "usedBy": [ "app/layout.tsx" ] }, { "name": "EmptyState", "file": "components/ui/empty-state.tsx", "purpose": "列表空状态展示", "usedBy": [ "exams", "homework", "questions", "textbooks" ] }, { "name": "GlobalSearch", "file": "components/global-search.tsx", "props": "{ className?, placeholder? }", "purpose": "全局搜索组件(防抖300ms调用 GET /api/search,Cmd/Ctrl+K快捷键聚焦,Escape关闭,↑/↓键盘导航,Enter跳转;下拉展示 question/textbook/exam/announcement 四类结果)", "internalDeps": [ "useDebounce", "Input", "Link", "useRouter" ], "usedBy": [ "layout/components/site-header.tsx" ] }, { "name": "Switch", "file": "components/ui/switch.tsx", "basedOn": "@radix-ui/react-switch", "props": "Radix Switch Root props (checked, onCheckedChange, disabled, id, aria-label)", "purpose": "开关切换UI组件(shadcn风格,checked/unchecked两态)", "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 图标的 Input),P3-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 + Line),P3-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 + Bar),P3-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 + Radar),P3-c 重构从 subject-comparison-chart(双 Radar:averageScore + passRate)和 mastery-radar-chart(双 Radar:student + 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 + Badge),P3-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-assembly(compact 布局)和 question-bank-picker(default 布局,同时将原生 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" ] }, { "name": "ConfirmDeleteDialog", "file": "components/ui/confirm-delete-dialog.tsx", "props": "{ open, onOpenChange, title, description, onConfirm, isWorking?, confirmText?, cancelText?, destructive? }", "purpose": "通用删除确认对话框(AlertDialog 包装),P0-1 重构从 announcement-detail、message-detail、course-plan-detail、grade-classes-view、students-table 五处重复的 AlertDialog 删除确认块抽取", "internalDeps": [ "AlertDialog", "AlertDialogContent", "AlertDialogHeader", "AlertDialogTitle", "AlertDialogDescription", "AlertDialogFooter", "AlertDialogCancel", "AlertDialogAction", "cn" ], "usedBy": [ "announcements/components/announcement-detail.tsx", "messaging/components/message-detail.tsx", "course-plans/components/course-plan-detail.tsx", "classes/components/grade-classes-view.tsx", "classes/components/students-table.tsx" ] }, { "name": "Pagination", "file": "components/ui/pagination.tsx", "props": "{ page, pageSize, total, totalPages, onPageChange, itemLabel? }", "purpose": "通用分页 UI(Showing X-Y of Z + Page X of Y + 上一页/下一页按钮),P0-2 重构从 audit-log-table、login-log-table、data-change-log-table 三处重复的分页块抽取", "internalDeps": [ "Button", "ChevronLeft (lucide-react)", "ChevronRight (lucide-react)", "cn" ], "usedBy": [ "audit/components/audit-log-table.tsx", "audit/components/login-log-table.tsx", "audit/components/data-change-log-table.tsx" ] }, { "name": "EmptyTableRow", "file": "components/ui/empty-table-row.tsx", "props": "{ colSpan, message? }", "purpose": "表格空状态行(TableRow + TableCell 居中显示空状态文案),P0-3 重构从 audit-log-table、login-log-table、data-change-log-table 三处重复的空表格行抽取", "internalDeps": [ "TableRow", "TableCell" ], "usedBy": [ "audit/components/audit-log-table.tsx", "audit/components/login-log-table.tsx", "audit/components/data-change-log-table.tsx" ] }, { "name": "StatusBadge", "file": "components/ui/status-badge.tsx", "props": "{ status, variantMap, labelMap?, classNameMap?, className?, capitalize? }", "purpose": "通用状态徽章(Badge + 状态→variant/label/className 映射表),P1-1 重构从 audit StatusBadge/ActionBadge(2 处 100% 相同)、grades typeColors(2 处 100% 相同)、StudentHomeworkProgressStatus(3 处不一致映射)、QuestionType(switch+label)等 9+ 处重复抽取。同时修复 in_progress 在不同文件中颜色不一致的 bug", "internalDeps": [ "Badge", "cn" ], "usedBy": [ "audit/components/audit-log-table.tsx", "audit/components/login-log-table.tsx", "audit/components/data-change-log-table.tsx", "grades/components/grade-record-list.tsx", "grades/components/student-grade-summary.tsx", "app/(dashboard)/student/learning/assignments/page.tsx", "parent/components/child-homework-summary.tsx", "dashboard/components/student-dashboard/student-upcoming-assignments-card.tsx", "questions/components/question-columns.tsx" ] }, { "name": "TextField", "file": "components/form-fields/text-field.tsx", "props": "{ control, name, label, placeholder?, description?, type?, disabled?, itemClassName?, inputClassName?, inputProps?, toInputValue?, fromInputValue? }", "purpose": "通用文本字段(FormField + FormItem + FormLabel + FormControl + Input + FormMessage 包装),P1-2 重构从 profile-settings-form(6 处)、exam-basic-info-form(4 处)、ai-provider-settings-card(4 处)等 16 处重复的 Input FormField 抽取", "internalDeps": [ "FormField", "FormItem", "FormLabel", "FormControl", "FormDescription", "FormMessage", "Input" ], "usedBy": [ "settings/components/profile-settings-form.tsx", "settings/components/ai-provider-settings-card.tsx", "exams/components/exam-basic-info-form.tsx" ] }, { "name": "SelectField", "file": "components/form-fields/select-field.tsx", "props": "{ control, name, label, placeholder?, description?, options: SelectOption[], disabled?, itemClassName?, labelSlot?, toSelectValue?, fromSelectValue? }", "purpose": "通用选择字段(FormField + FormItem + FormLabel + Select + SelectContent + SelectItem + FormMessage 包装),P1-2 重构从 exam-basic-info-form(3 处)、ai-provider-settings-card(1 处)、create-question-dialog(2 处)、profile-settings-form(1 处)等 8 处重复的 Select FormField 抽取。支持 toSelectValue/fromSelectValue 转换器处理 number↔string", "internalDeps": [ "FormField", "FormItem", "FormLabel", "FormControl", "FormDescription", "FormMessage", "Select", "SelectContent", "SelectItem", "SelectTrigger", "SelectValue" ], "usedBy": [ "settings/components/profile-settings-form.tsx", "settings/components/ai-provider-settings-card.tsx", "exams/components/exam-basic-info-form.tsx", "questions/components/create-question-dialog.tsx" ] }, { "name": "TextareaField", "file": "components/form-fields/textarea-field.tsx", "props": "{ control, name, label, placeholder?, description?, disabled?, itemClassName?, textareaClassName?, textareaProps? }", "purpose": "通用多行文本字段(FormField + FormItem + FormLabel + FormControl + Textarea + FormMessage 包装),P1-2 重构从 exam-ai-generator、create-question-dialog 两处重复的 Textarea FormField 抽取", "internalDeps": [ "FormField", "FormItem", "FormLabel", "FormControl", "FormDescription", "FormMessage", "Textarea" ], "usedBy": [ "questions/components/create-question-dialog.tsx" ] } ], "constants": [ { "name": "ROLE_PERMISSIONS", "file": "lib/permissions.ts", "type": "const", "description": "角色-权限映射表", "usedBy": [ "resolvePermissions", "auth.ts JWT callback" ] }, { "name": "Permissions", "file": "types/permissions.ts", "type": "const", "description": "38个权限点常量对象(含 FILE_UPLOAD/FILE_READ/FILE_DELETE)", "usedBy": [ "auth-guard", "use-permission", "所有actions" ] }, { "name": "db", "file": "db/index.ts", "type": "const", "description": "Drizzle ORM 实例", "usedBy": [ "所有业务模块" ] }, { "name": "questionTypeEnum", "file": "db/schema.ts", "type": "const", "description": "题目类型枚举", "usedBy": [ "questions", "exams" ] }, { "name": "classEnrollmentStatusEnum", "file": "db/schema.ts", "type": "const", "description": "选课状态枚举", "usedBy": [ "classes" ] } ], "files": [ { "path": "db/relations.ts", "description": "20+ 个 Drizzle relations 定义", "usedBy": [ "所有业务模块的关联查询" ] }, { "path": "next-auth.d.ts", "description": "NextAuth Session/JWT 类型扩展声明", "usedBy": [ "auth.ts", "useSession" ] } ], "types": [ { "name": "ActionState", "file": "types/action-state.ts", "definition": "ActionState = { success: boolean; message?: string; errors?: Record; data?: T }", "usedBy": [ "所有模块Server Action" ] }, { "name": "Permission", "file": "types/permissions.ts", "definition": "Permission = (typeof Permissions)[keyof typeof Permissions]", "usedBy": [ "auth-guard", "use-permission", "所有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_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", "homework/data-access", "dashboard/data-access", "grades/data-access", "parent/children/[studentId]/page.tsx" ] }, { "name": "AuthContext", "file": "types/permissions.ts", "definition": "AuthContext = { userId: string; roles: Role[]; permissions: Permission[]; dataScope: DataScope }", "usedBy": [ "auth-guard", "所有调用requirePermission的Server Action" ] }, { "name": "PermissionDeniedError", "file": "lib/auth-guard.ts", "definition": "class PermissionDeniedError extends Error { constructor(permission: string) }", "usedBy": [ "所有Server Action的try/catch" ] } ] }, "dbTables": { "users": { "fields": [ "id", "name", "email", "emailVerified", "image", "password", "phone", "address", "gender", "age", "birthDate", "guardianName", "guardianPhone", "guardianRelation", "consentAcceptedAt", "gradeId", "departmentId", "onboardedAt", "createdAt", "updatedAt" ], "usedBy": [ "auth", "users", "dashboard", "classes" ] }, "accounts": { "fields": [ "userId", "type", "provider", "providerAccountId", "refresh_token", "access_token", "expires_at", "token_type", "scope", "id_token", "session_state" ], "usedBy": [ "auth" ] }, "sessions": { "fields": [ "sessionToken", "userId", "expires" ], "usedBy": [ "auth" ] }, "verificationTokens": { "fields": [ "identifier", "token", "expires" ], "usedBy": [ "auth" ] }, "roles": { "fields": [ "id", "name", "description", "createdAt", "updatedAt" ], "usedBy": [ "auth", "auth-guard" ] }, "usersToRoles": { "fields": [ "userId", "roleId" ], "usedBy": [ "auth", "auth-guard" ] }, "rolePermissions": { "fields": [ "roleId", "permission" ], "usedBy": [ "auth (seed)" ] }, "knowledgePoints": { "fields": [ "id", "name", "description", "anchorText", "parentId", "chapterId", "level", "order", "createdAt", "updatedAt" ], "usedBy": [ "textbooks", "questions" ] }, "knowledgePointPrerequisites": { "fields": [ "knowledgePointId", "prerequisiteKpId", "createdAt" ], "usedBy": [ "textbooks" ], "description": "知识点前置依赖(知识图谱):复合主键 knowledgePointId+prerequisiteKpId,双外键 cascade 删除,无独立 id 字段" }, "questions": { "fields": [ "id", "content", "type", "difficulty", "authorId", "parentId", "createdAt", "updatedAt" ], "usedBy": [ "questions", "exams", "homework" ] }, "questionsToKnowledgePoints": { "fields": [ "questionId", "knowledgePointId" ], "usedBy": [ "questions" ] }, "subjects": { "fields": [ "id", "name", "code", "order", "createdAt", "updatedAt" ], "usedBy": [ "exams", "textbooks" ] }, "textbooks": { "fields": [ "id", "title", "subject", "grade", "publisher", "createdAt", "updatedAt" ], "usedBy": [ "textbooks" ] }, "chapters": { "fields": [ "id", "textbookId", "title", "order", "parentId", "content", "createdAt", "updatedAt" ], "usedBy": [ "textbooks" ] }, "departments": { "fields": [ "id", "name", "description", "createdAt", "updatedAt" ], "usedBy": [ "school" ] }, "classrooms": { "fields": [ "id", "name", "building", "floor", "capacity", "createdAt", "updatedAt" ], "usedBy": [ "school" ] }, "academicYears": { "fields": [ "id", "name", "startDate", "endDate", "isActive", "createdAt", "updatedAt" ], "usedBy": [ "school" ] }, "schools": { "fields": [ "id", "name", "code", "createdAt", "updatedAt" ], "usedBy": [ "school", "classes" ] }, "grades": { "fields": [ "id", "schoolId", "name", "order", "gradeHeadId", "teachingHeadId", "createdAt", "updatedAt" ], "usedBy": [ "school", "classes", "exams", "auth-guard" ] }, "classes": { "fields": [ "id", "schoolId", "gradeId", "teacherId", "name", "homeroom", "room", "invitationCode", "schoolName", "grade", "createdAt", "updatedAt" ], "usedBy": [ "classes", "homework", "auth-guard" ] }, "classSubjectTeachers": { "fields": [ "classId", "teacherId", "subjectId", "createdAt", "updatedAt" ], "usedBy": [ "classes", "auth-guard" ] }, "classEnrollments": { "fields": [ "classId", "studentId", "status", "createdAt" ], "usedBy": [ "classes", "homework" ] }, "classSchedule": { "fields": [ "id", "classId", "weekday", "startTime", "endTime", "course", "location", "createdAt", "updatedAt" ], "usedBy": [ "classes" ] }, "exams": { "fields": [ "id", "creatorId", "title", "description", "subjectId", "gradeId", "status", "structure", "startTime", "endTime", "createdAt", "updatedAt" ], "usedBy": [ "exams", "homework" ] }, "examQuestions": { "fields": [ "examId", "questionId", "score", "order" ], "usedBy": [ "exams" ] }, "examSubmissions": { "fields": [ "id", "examId", "studentId", "score", "submittedAt", "status", "createdAt", "updatedAt" ], "usedBy": [ "exams" ] }, "submissionAnswers": { "fields": [ "id", "submissionId", "questionId", "answerContent", "score", "feedback", "createdAt", "updatedAt" ], "usedBy": [ "exams" ] }, "homeworkAssignments": { "fields": [ "id", "creatorId", "sourceExamId", "title", "description", "status", "availableAt", "dueAt", "allowLate", "lateDueAt", "maxAttempts", "structure", "createdAt", "updatedAt" ], "usedBy": [ "homework" ] }, "homeworkAssignmentQuestions": { "fields": [ "assignmentId", "questionId", "score", "order" ], "usedBy": [ "homework" ] }, "homeworkAssignmentTargets": { "fields": [ "assignmentId", "studentId", "createdAt" ], "usedBy": [ "homework" ] }, "homeworkSubmissions": { "fields": [ "id", "assignmentId", "studentId", "status", "attemptNo", "score", "submittedAt", "startedAt", "isLate", "createdAt", "updatedAt" ], "usedBy": [ "homework" ] }, "homeworkAnswers": { "fields": [ "id", "submissionId", "questionId", "answerContent", "score", "feedback", "createdAt", "updatedAt" ], "usedBy": [ "homework" ] }, "aiProviders": { "fields": [ "id", "provider", "baseUrl", "model", "apiKeyEncrypted", "apiKeyLast4", "isDefault", "createdBy", "updatedBy", "createdAt", "updatedAt" ], "usedBy": [ "settings", "ai" ] }, "auditLogs": { "fields": [ "id", "userId", "userName", "action", "module", "targetId", "targetType", "detail", "ipAddress", "userAgent", "status", "createdAt" ], "usedBy": [ "audit", "shared/lib/audit-logger" ] }, "loginLogs": { "fields": [ "id", "userId", "userEmail", "action", "status", "ipAddress", "userAgent", "errorMessage", "createdAt" ], "usedBy": [ "audit", "shared/lib/login-logger", "auth" ] }, "dataChangeLogs": { "fields": [ "id", "tableName", "recordId", "action", "oldValue", "newValue", "changedBy", "changedByName", "ipAddress", "createdAt" ], "usedBy": [ "audit", "shared/lib/change-logger" ] }, "announcements": { "fields": [ "id", "title", "content", "type", "status", "targetGradeId", "targetClassId", "authorId", "publishedAt", "createdAt", "updatedAt" ], "usedBy": [ "announcements" ] }, "fileAttachments": { "fields": [ "id", "filename", "originalName", "mimeType", "size", "storagePath", "url", "uploaderId", "targetType", "targetId", "createdAt" ], "usedBy": [ "files" ] }, "gradeRecords": { "fields": [ "id", "studentId", "classId", "subjectId", "examId", "academicYearId", "title", "score", "fullScore", "type", "semester", "recordedBy", "remark", "createdAt", "updatedAt" ], "usedBy": [ "grades" ] }, "coursePlans": { "fields": [ "id", "classId", "subjectId", "teacherId", "academicYearId", "semester", "totalHours", "completedHours", "weeklyHours", "startDate", "endDate", "syllabus", "objectives", "status", "createdBy", "createdAt", "updatedAt" ], "usedBy": [ "course-plans" ] }, "coursePlanItems": { "fields": [ "id", "planId", "week", "topic", "content", "hours", "textbookChapter", "notes", "isCompleted", "completedAt", "createdAt", "updatedAt" ], "usedBy": [ "course-plans" ] }, "parentStudentRelations": { "fields": [ "id", "parentId", "studentId", "relation", "createdAt" ], "usedBy": [ "parent", "auth-guard" ] }, "messages": { "fields": [ "id", "senderId", "receiverId", "subject", "content", "isRead", "readAt", "parentMessageId", "senderDeletedAt", "receiverDeletedAt", "createdAt" ], "usedBy": [ "messaging" ] }, "messageNotifications": { "fields": [ "id", "userId", "type", "title", "content", "link", "isRead", "createdAt" ], "usedBy": [ "notifications", "messaging (via re-export)" ] }, "notificationPreferences": { "fields": [ "id", "userId", "emailEnabled", "smsEnabled", "pushEnabled", "homeworkNotifications", "gradeNotifications", "announcementNotifications", "messageNotifications", "attendanceNotifications", "createdAt", "updatedAt" ], "usedBy": [ "notifications", "messaging (via re-export)", "settings" ] }, "attendanceRecords": { "fields": [ "id", "studentId", "classId", "scheduleId", "date", "status", "remark", "recordedBy", "createdAt", "updatedAt" ], "usedBy": [ "attendance" ] }, "attendanceRules": { "fields": [ "id", "classId", "lateThresholdMinutes", "earlyLeaveThresholdMinutes", "enableAutoMark", "createdAt", "updatedAt" ], "usedBy": [ "attendance" ] }, "schedulingRules": { "fields": [ "id", "classId", "maxDailyHours", "maxContinuousHours", "lunchBreakStart", "lunchBreakEnd", "morningStart", "afternoonEnd", "avoidBackToBack", "balancedSubjects", "createdAt", "updatedAt" ], "usedBy": [ "scheduling" ] }, "scheduleChanges": { "fields": [ "id", "originalScheduleId", "classId", "originalTeacherId", "substituteTeacherId", "originalDate", "newDate", "newStartTime", "newEndTime", "reason", "status", "requestedBy", "approvedBy", "createdAt", "updatedAt" ], "usedBy": [ "scheduling" ] }, "passwordSecurity": { "fields": [ "id", "userId", "failedLoginAttempts", "lockedUntil", "passwordChangedAt", "mustChangePassword", "lastPasswordChange", "createdAt", "updatedAt" ], "usedBy": [ "auth", "settings" ] }, "knowledgePointMastery": { "fields": [ "id", "studentId", "knowledgePointId", "masteryLevel", "totalQuestions", "correctQuestions", "lastAssessedAt", "createdAt", "updatedAt" ], "usedBy": [ "diagnostic" ], "description": "知识点掌握度记录(复合主键 studentId+knowledgePointId,onDuplicateKeyUpdate upsert)" }, "learningDiagnosticReports": { "fields": [ "id", "studentId", "generatedBy", "reportType", "period", "summary", "strengths", "weaknesses", "recommendations", "overallScore", "status", "createdAt", "updatedAt" ], "usedBy": [ "diagnostic" ], "description": "学情诊断报告(reportType: individual/class/grade;status: draft/published/archived)" }, "electiveCourses": { "fields": [ "id", "name", "subjectId", "teacherId", "gradeId", "description", "capacity", "enrolledCount", "classroom", "schedule", "startDate", "endDate", "selectionStartAt", "selectionEndAt", "status", "selectionMode", "credit", "createdAt", "updatedAt" ], "usedBy": [ "elective" ], "description": "选修课程(status: draft/open/closed/cancelled;selectionMode: fcfs/lottery)" }, "courseSelections": { "fields": [ "id", "courseId", "studentId", "status", "priority", "selectedAt", "enrolledAt", "droppedAt", "lotteryRank", "createdAt", "updatedAt" ], "usedBy": [ "elective" ], "description": "选课记录(复合主键 courseId+studentId;status: selected/enrolled/waitlist/dropped/rejected)" } } }, "auth": { "path": "src/auth.ts", "description": "用户认证:NextAuth配置(handlers/auth/signIn/signOut)、JWT/Session callbacks、events回调(登录日志)。集成密码安全策略(账户锁定、失败登录追踪)和登录速率限制。P1-3 拆分后,辅助函数已迁移至 shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service}", "imports": [ "shared/lib/permissions", "shared/lib/login-logger", "shared/lib/password-policy", "shared/lib/rate-limit", "shared/lib/role-utils", "shared/lib/bcrypt-utils", "shared/lib/http-utils", "shared/lib/password-security-service", "shared/db", "shared/db/schema" ], "exports": { "functions": [ { "name": "auth", "signature": "auth(): Promise", "purpose": "获取当前用户Session", "usedBy": [ "shared/lib/session.ts (dynamic import,再被 auth-guard/audit-logger/change-logger 间接使用)", "所有Server Component页面", "app/api 路由" ] }, { "name": "handlers", "signature": "{ GET, POST }", "purpose": "NextAuth Route Handler", "usedBy": [ "app/api/auth/[...nextauth]/route.ts" ] }, { "name": "signIn", "signature": "signIn(provider, options?)", "purpose": "登录", "usedBy": [ "login-form.tsx" ] }, { "name": "signOut", "signature": "signOut(options?)", "purpose": "登出", "usedBy": [ "site-header.tsx" ] } ], "events": [ { "name": "signIn", "signature": "async signIn({ user }) => void", "purpose": "用户登录成功后记录登录日志", "deps": [ "shared/lib/login-logger.logLoginEvent" ], "params": "{ userId: user.id, userEmail: user.email, action: 'signin', status: 'success' }" }, { "name": "signOut", "signature": "async signOut(message) => void", "purpose": "用户登出后记录登录日志(处理NextAuth v5不同message形状)", "deps": [ "shared/lib/login-logger.logLoginEvent" ], "params": "{ userId?, userEmail, action: 'signout', status: 'success' }" } ] }, "middleware": { "file": "src/proxy.ts", "signature": "proxy(request: NextRequest): Promise", "purpose": "基于权限点的路由守卫(Next.js 16 将 middleware 重命名为 proxy)", "routePermissions": { "/admin": "school:manage", "/teacher": "exam:create", "/student": "homework:submit", "/parent": "dashboard:parent:read", "/management": "grade:manage" }, "dashboardRoutePermissions": { "/admin/dashboard": "dashboard:admin:read", "/teacher/dashboard": "dashboard:teacher:read", "/student/dashboard": "dashboard:student:read", "/parent/dashboard": "dashboard:parent:read" }, "apiPermissions": { "/api/ai/chat": "ai:chat" }, "securityNote": "P0 修复:原先 /teacher 和 /parent 都使用 EXAM_READ,但 student/parent 也有 EXAM_READ,导致跨角色访问漏洞。改为使用各角色独有的权限点。" } }, "exams": { "path": "src/modules/exams", "description": "考试全生命周期:创建(手动/AI)、编辑、预览、发布、删除、复制", "exports": { "actions": [ { "name": "createExamAction", "permission": "EXAM_CREATE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "手动模式创建考试草稿", "deps": [ "requirePermission", "shared/db", "data-access.persistExamDraft" ], "usedBy": [ "exam-form.tsx" ] }, { "name": "createAiExamAction", "permission": "EXAM_AI_GENERATE", "signature": "同上", "purpose": "AI模式创建考试", "deps": [ "requirePermission", "ai-pipeline.generateAiCreateDraftFromSource", "data-access.persistAiGeneratedExamDraft" ], "usedBy": [ "exam-form.tsx" ] }, { "name": "previewAiExamAction", "permission": "EXAM_AI_GENERATE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "AI预览试卷(不持久化)", "deps": [ "requirePermission", "ai-pipeline.generateAiPreviewData" ], "usedBy": [ "exam-ai-generator.tsx" ] }, { "name": "regenerateAiQuestionAction", "permission": "EXAM_AI_GENERATE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "AI重写单个题目", "deps": [ "requirePermission", "ai-pipeline.regenerateAiQuestionByInstruction" ], "usedBy": [ "exam-preview-question-editor.tsx" ] }, { "name": "updateExamAction", "permission": "EXAM_UPDATE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "更新考试(含资源归属校验)", "deps": [ "requirePermission", "data-access.getExamCreatorId", "data-access.updateExamWithQuestions" ], "usedBy": [ "exam-form.tsx" ] }, { "name": "deleteExamAction", "permission": "EXAM_DELETE", "signature": "同上", "purpose": "删除考试(含资源归属校验)", "deps": [ "requirePermission", "data-access.getExamCreatorId", "data-access.deleteExamById" ], "usedBy": [ "exam-actions.tsx" ] }, { "name": "duplicateExamAction", "permission": "EXAM_DUPLICATE", "signature": "同上", "purpose": "复制考试", "deps": [ "requirePermission", "data-access.getExamCreatorId", "data-access.duplicateExam" ], "usedBy": [ "exam-actions.tsx" ] }, { "name": "getExamPreviewAction", "permission": "EXAM_READ", "signature": "(examId: string) => Promise }>>", "purpose": "获取考试预览数据", "deps": [ "requirePermission", "data-access.getExamPreview" ], "usedBy": [ "exam-viewer.tsx" ] }, { "name": "getSubjectsAction", "permission": "EXAM_READ", "signature": "() => Promise>", "purpose": "获取科目列表", "deps": [ "requirePermission", "data-access.getExamSubjects" ], "usedBy": [ "exam-form.tsx" ] }, { "name": "getGradesAction", "permission": "EXAM_READ", "signature": "同上", "purpose": "获取年级列表", "deps": [ "requirePermission", "data-access.getExamGrades" ], "usedBy": [ "exam-form.tsx" ] } ], "dataAccess": [ { "name": "getExams", "signature": "getExams(params: GetExamsParams & { scope: DataScope }): Promise", "purpose": "查询考试列表(含数据权限过滤)", "usedBy": [ "teacher/exams/all/page.tsx", "homework创建页面" ] }, { "name": "getExamById", "signature": "getExamById(id: string, scope?: DataScope): Promise", "purpose": "按ID获取考试详情", "usedBy": [ "exam详情/编辑页面" ] }, { "name": "persistExamDraft", "signature": "persistExamDraft(input: { examId, title, creatorId, subjectId, gradeId, scheduledAt?, description }): Promise", "purpose": "持久化手动考试草稿", "usedBy": [ "createExamAction" ] }, { "name": "addExamQuestions", "signature": "addExamQuestions(examId: string, items: Array<{ questionId: string; score: number; order: number }>): Promise", "purpose": "批量插入考试-题目关联(跨模块写接口,供 lesson-preparation/publish-service 调用,避免直查 examQuestions 表)", "usedBy": [ "lesson-preparation/publish-service.publishLessonPlanHomework" ] }, { "name": "persistAiGeneratedExamDraft", "signature": "persistAiGeneratedExamDraft(input: { examId, title, creatorId, subjectId, gradeId, scheduledAt?, description, structure, generated }): Promise", "purpose": "持久化AI生成考试草稿(P0-1 已修复:通过 questions/data-access.createQuestionWithRelations 创建题目,不再直查 questions 表)", "usedBy": [ "createAiExamAction" ] }, { "name": "getExamsDashboardStats", "signature": "(scope?: DataScope) => Promise", "purpose": "获取考试仪表盘统计数据(考试总数,支持数据范围过滤)", "usedBy": [ "dashboard/data-access.getAdminDashboardData" ] }, { "name": "omitScheduledAtFromDescription", "signature": "(description: string | null) => string", "purpose": "从描述中移除scheduledAt信息", "usedBy": [ "exams/data-access内部" ] }, { "name": "resolveSubjectGradeNames", "signature": "(input: { subjectId?, gradeId? }) => Promise<{ subjectName?, gradeName? }>", "purpose": "解析科目与年级名称", "usedBy": [ "exams/data-access内部" ] }, { "name": "buildExamDescription", "signature": "(input: { subject, grade, difficulty, totalScore, durationMin, scheduledAt?, questionCount? }) => string", "purpose": "构建考试描述文本", "usedBy": [ "exams/data-access内部" ] }, { "name": "getExamCreatorId", "signature": "(examId: string) => Promise", "purpose": "获取考试创建者ID(用于权限校验)", "usedBy": [ "updateExamAction", "deleteExamAction", "duplicateExamAction" ] }, { "name": "updateExamWithQuestions", "signature": "(input: { examId, title, subjectId, gradeId, difficulty, totalScore, durationMin, scheduledAt?, description?, structure }) => Promise", "purpose": "更新考试及题目关联(P1-2 新增,从 actions 下沉)", "usedBy": [ "updateExamAction" ] }, { "name": "deleteExamById", "signature": "(examId: string) => Promise", "purpose": "按ID删除考试(P1-2 新增,从 actions 下沉)", "usedBy": [ "deleteExamAction" ] }, { "name": "duplicateExam", "signature": "(examId: string, creatorId: string) => Promise<{ newExamId: string }>", "purpose": "复制考试(P1-2 新增,从 actions 下沉)", "usedBy": [ "duplicateExamAction" ] }, { "name": "getExamPreview", "signature": "(examId: string) => Promise<{ structure: unknown; questions: Array<{ id: string }> } | null>", "purpose": "获取考试预览数据(P1-2 新增,从 actions 下沉)", "usedBy": [ "getExamPreviewAction" ] }, { "name": "getExamSubjects", "signature": "() => Promise>", "purpose": "获取科目列表(P1-2 新增,从 actions 下沉)", "usedBy": [ "getSubjectsAction" ] }, { "name": "getExamGrades", "signature": "() => Promise>", "purpose": "获取年级列表(P1-2 新增,从 actions 下沉)", "usedBy": [ "getGradesAction" ] }, { "name": "GetExamsParams", "type": "type", "definition": "{ q?, status?, difficulty?, page?, pageSize? }", "usedBy": [ "getExams", "getExamsAction" ] } ], "aiPipeline": [ { "name": "generateAiPreviewData", "signature": "(input: { title, subject?, grade?, difficulty, totalScore, durationMin, questionCount?, sourceText, aiProviderId? }) => Promise<{ ok, data?, rawOutput?, message? }>", "purpose": "AI预览生成", "deps": [ "shared/lib/ai.createAiChatCompletion" ], "usedBy": [ "previewAiExamAction" ] }, { "name": "generateAiCreateDraftFromSource", "signature": "同上", "purpose": "AI从源文本生成完整考试", "deps": [ "shared/lib/ai.createAiChatCompletion" ], "usedBy": [ "createAiExamAction" ] }, { "name": "regenerateAiQuestionByInstruction", "signature": "(input: { instruction, originalQuestion, sourceText?, aiProviderId? }) => Promise<{ ok, data?, message? }>", "purpose": "AI按指令重写题目", "deps": [ "shared/lib/ai.createAiChatCompletion" ], "usedBy": [ "regenerateAiQuestionAction" ] }, { "name": "AiQuestionSchema", "type": "const", "description": "zod schema 校验AI生成题目", "usedBy": [ "ai-pipeline内部" ] }, { "name": "AiInsertQuestionSchema", "type": "const", "description": "zod schema 校验AI插入题目", "usedBy": [ "ai-pipeline内部" ] }, { "name": "AiGeneratedQuestion", "type": "type", "definition": "{ id, type, difficulty, score, content }", "usedBy": [ "ai-pipeline内部", "exams/components" ] }, { "name": "AiGeneratedStructureNode", "type": "type", "definition": "{ id, type: \"group\"|\"question\", title?, questionId?, score?, children? }", "usedBy": [ "ai-pipeline内部", "exams/components" ] }, { "name": "AiGeneratedStructureNodeSchema", "type": "const", "description": "zod schema 校验AI生成结构节点(递归)", "usedBy": [ "ai-pipeline内部" ] }, { "name": "AiGeneratedStructureSchema", "type": "const", "description": "zod schema 校验AI生成结构", "usedBy": [ "ai-pipeline内部" ] }, { "name": "generateAiExamDraft", "type": "function", "signature": "(input) => Promise", "purpose": "生成AI考试草稿", "deps": [ "shared/lib/ai.createAiChatCompletion" ], "usedBy": [ "ai-pipeline内部" ] } ], "types": [ { "name": "Exam", "definition": "{ id, title, subject, grade, status, difficulty, totalScore, durationMin, questionCount, scheduledAt?, createdAt, updatedAt?, tags? }", "usedBy": [ "exams/components", "homework/types", "dashboard/types" ] }, { "name": "AiPreviewData", "definition": "{ title, rawOutput?, sections?, questions? }", "usedBy": [ "exams/actions", "exams/components" ] }, { "name": "AiRewriteQuestionData", "definition": "{ type, difficulty, score, content }", "usedBy": [ "exams/actions", "exams/components" ] }, { "name": "ExamStatus", "type": "type", "definition": "\"draft\" | \"published\" | \"archived\"", "usedBy": [ "exams/components", "exams/data-access" ] }, { "name": "ExamDifficulty", "type": "type", "definition": "1 | 2 | 3 | 4 | 5", "usedBy": [ "exams/components", "exams/data-access" ] }, { "name": "SubmissionStatus", "type": "type", "definition": "\"pending\" | \"graded\"", "usedBy": [ "exams/components", "exams/data-access" ] }, { "name": "ExamSubmission", "type": "interface", "definition": "{ id, examId, examTitle, studentName, submittedAt, score?, status }", "usedBy": [ "exams/components" ] } ], "components": [ { "name": "ExamPaperPreview", "file": "assembly/exam-paper-preview.tsx", "purpose": "试卷预览" }, { "name": "QuestionBankList", "file": "assembly/question-bank-list.tsx", "purpose": "题库列表(组卷选择)" }, { "name": "SelectedQuestionList", "file": "assembly/selected-question-list.tsx", "purpose": "已选题目列表", "types": [ "ExamNode" ] }, { "name": "StructureEditor", "file": "assembly/structure-editor.tsx", "purpose": "试卷结构编辑器" }, { "name": "ExamActions", "file": "exam-actions.tsx", "purpose": "考试操作按钮(删除/复制等)" }, { "name": "ExamAiGenerator", "file": "exam-ai-generator.tsx", "purpose": "AI生成考试面板" }, { "name": "ExamAssembly", "file": "exam-assembly.tsx", "purpose": "组卷主界面" }, { "name": "ExamBasicInfoForm", "file": "exam-basic-info-form.tsx", "purpose": "考试基本信息表单" }, { "name": "ExamCard", "file": "exam-card.tsx", "purpose": "考试卡片" }, { "name": "examColumns", "file": "exam-columns.tsx", "type": "ColumnDef[]", "purpose": "考试表格列定义" }, { "name": "ExamDataTable", "file": "exam-data-table.tsx", "purpose": "考试数据表格" }, { "name": "ExamFilters", "file": "exam-filters.tsx", "purpose": "考试筛选器" }, { "name": "formSchema", "file": "exam-form-types.ts", "type": "const", "purpose": "表单zod schema" }, { "name": "ExamFormValues", "file": "exam-form-types.ts", "type": "type", "purpose": "表单值类型" }, { "name": "PreviewQuestion", "file": "exam-form-types.ts", "type": "type", "purpose": "预览题目类型" }, { "name": "EditableQuestionContent", "file": "exam-form-types.ts", "type": "type", "purpose": "可编辑题目内容" }, { "name": "PreviewSnapshotMeta", "file": "exam-form-types.ts", "type": "type", "purpose": "预览快照元数据" }, { "name": "PreviewBackgroundTask", "file": "exam-form-types.ts", "type": "type", "purpose": "预览后台任务" }, { "name": "aiProviderLabels", "file": "exam-form-types.ts", "type": "const", "purpose": "AI Provider标签映射" }, { "name": "defaultValues", "file": "exam-form-types.ts", "type": "const", "purpose": "表单默认值" }, { "name": "previewTaskStorageKey", "file": "exam-form-types.ts", "type": "const", "purpose": "预览任务localStorage key" }, { "name": "ExamForm", "file": "exam-form.tsx", "purpose": "考试表单(创建/编辑)" }, { "name": "ExamGrid", "file": "exam-grid.tsx", "purpose": "考试网格视图" }, { "name": "ExamModeSelector", "file": "exam-mode-selector.tsx", "purpose": "考试模式选择(手动/AI)" }, { "name": "ExamPreviewDialog", "file": "exam-preview-dialog.tsx", "purpose": "考试预览对话框" }, { "name": "ExamPreviewQuestionEditor", "file": "exam-preview-question-editor.tsx", "purpose": "预览题目编辑器" }, { "name": "buildPreviewNodes", "file": "exam-preview-utils.ts", "type": "function", "purpose": "构建预览节点" }, { "name": "parseEditableContent", "file": "exam-preview-utils.ts", "type": "function", "purpose": "解析可编辑内容" }, { "name": "flattenPreviewQuestions", "file": "exam-preview-utils.ts", "type": "function", "purpose": "扁平化预览题目" }, { "name": "findPreviewQuestionNode", "file": "exam-preview-utils.ts", "type": "function", "purpose": "查找预览题目节点" }, { "name": "updatePreviewQuestionNodeInList", "file": "exam-preview-utils.ts", "type": "function", "purpose": "更新预览题目节点" }, { "name": "buildPreviewSignature", "file": "exam-preview-utils.ts", "type": "function", "purpose": "构建预览签名" }, { "name": "buildPreviewPayload", "file": "exam-preview-utils.ts", "type": "function", "purpose": "构建预览payload" }, { "name": "buildPreviewRequestData", "file": "exam-preview-utils.ts", "type": "function", "purpose": "构建预览请求数据" }, { "name": "ExamViewer", "file": "exam-viewer.tsx", "purpose": "考试查看器" }, { "name": "QuestionOptionsEditor", "file": "question-options-editor.tsx", "purpose": "题目选项编辑器" }, { "name": "QuestionSubQuestionsEditor", "file": "question-sub-questions-editor.tsx", "purpose": "子题目编辑器" } ], "hooks": [ { "name": "useExamPreview", "file": "use-exam-preview.ts", "signature": "(form: UseFormReturn) => { previewOpen, setPreviewOpen, previewLoading, previewNodes, handlePreview, handleBackgroundPreview, handleOpenPreviewTask, handleRewriteSelectedQuestion }", "purpose": "考试预览Hook", "usedBy": [ "exam-form.tsx" ] } ], "utils": [ { "name": "normalizeStructure", "file": "utils/normalize-structure.ts", "type": "function", "signature": "(nodes: unknown) => ExamNode[]", "purpose": "将持久化的 exam.structure(unknown JSON)运行时校验并归一化为类型安全的 ExamNode[](类型守卫模式,无 as 断言;递归处理 group children;保证 id 唯一)", "deps": [ "@paralleldrive/cuid2.createId", "exams/components/assembly/selected-question-list.ExamNode" ], "usedBy": [ "teacher/exams/[id]/build/page.tsx" ] } ] } }, "homework": { "path": "src/modules/homework", "description": "作业全生命周期:创建(源自考试)、发布、学生作答、教师批改、数据分析", "exports": { "actions": [ { "name": "createHomeworkAssignmentAction", "permission": "HOMEWORK_CREATE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "从已有考试创建作业", "deps": [ "requirePermission", "data-access-write.createHomeworkAssignment", "exams/data-access.getExams" ], "usedBy": [ "homework-assignment-form.tsx" ] }, { "name": "startHomeworkSubmissionAction", "permission": "HOMEWORK_SUBMIT", "signature": "同上", "purpose": "学生开始作答", "deps": [ "requirePermission", "data-access-write.startHomeworkSubmission" ], "usedBy": [ "homework-take-view.tsx" ] }, { "name": "saveHomeworkAnswerAction", "permission": "HOMEWORK_SUBMIT", "signature": "同上", "purpose": "保存单题答案", "deps": [ "requirePermission", "data-access-write.saveHomeworkAnswer" ], "usedBy": [ "homework-take-view.tsx" ] }, { "name": "submitHomeworkAction", "permission": "HOMEWORK_SUBMIT", "signature": "同上", "purpose": "提交作业", "deps": [ "requirePermission", "data-access-write.submitHomework" ], "usedBy": [ "homework-take-view.tsx" ] }, { "name": "gradeHomeworkSubmissionAction", "permission": "HOMEWORK_GRADE", "signature": "同上", "purpose": "教师批改作业", "deps": [ "requirePermission", "data-access-write.gradeHomeworkSubmission" ], "usedBy": [ "homework-grading-view.tsx" ] } ], "dataAccess": [ { "name": "getHomeworkAssignments", "signature": "(params?: { creatorId?, ids?, classId?, scope? }) => Promise", "usedBy": [ "teacher作业列表页", "homework-assignment-form.tsx" ] }, { "name": "getHomeworkAssignmentReviewList", "signature": "(params: { creatorId: string; scope? }) => Promise", "usedBy": [ "teacher批改列表" ] }, { "name": "getHomeworkSubmissions", "signature": "(params?: { assignmentId?, classId?, creatorId?, scope? }) => Promise", "usedBy": [ "teacher提交列表" ] }, { "name": "getStudentHomeworkAssignments", "signature": "(studentId: string) => Promise", "usedBy": [ "student/dashboard" ] }, { "name": "getHomeworkAssignmentById", "signature": "(id: string, scope?: DataScope) => Promise<...>", "purpose": "按ID获取作业详情", "usedBy": [ "homework详情页" ] }, { "name": "getHomeworkSubmissionDetails", "signature": "(submissionId: string) => Promise", "purpose": "获取提交详情(含答案)", "usedBy": [ "homework-grading-view.tsx" ] }, { "name": "getDemoStudentUser", "signature": "() => Promise<{ id: string; name: string } | null>", "purpose": "获取演示学生用户(已迁移至 users 模块 getCurrentStudentUser,此处为 re-export 向后兼容)", "usedBy": [ "homework内部" ] }, { "name": "getStudentHomeworkTakeData", "signature": "(assignmentId: string, studentId: string) => Promise", "purpose": "获取学生作答数据", "usedBy": [ "homework-take-view.tsx" ] } ], "dataAccessClasses": [ { "name": "getAssignmentIdsForStudents", "file": "data-access-classes.ts", "signature": "(studentIds: string[]) => Promise", "purpose": "返回目标学生涉及的作业ID列表(P0-7 新增,供 classes 模块跨模块调用)", "usedBy": [ "classes/data-access-stats.getClassHomeworkInsights", "classes/data-access-stats.getGradeHomeworkInsights", "classes/data-access-students.getStudentsSubjectScores" ] }, { "name": "getHomeworkAssignmentsWithSubject", "file": "data-access-classes.ts", "signature": "(params: { assignmentIds, subjectIdFilter?, limit? }) => Promise", "purpose": "返回作业(含科目信息,via source exam),可选按科目过滤(P0-7 新增)", "usedBy": [ "classes/data-access-stats.getClassHomeworkInsights" ] }, { "name": "getHomeworkAssignmentsByIds", "file": "data-access-classes.ts", "signature": "(params: { assignmentIds, limit? }) => Promise", "purpose": "按 ID 返回作业简要信息(不含科目,P0-7 新增)", "usedBy": [ "classes/data-access-stats.getGradeHomeworkInsights" ] }, { "name": "getAssignmentTargetCounts", "file": "data-access-classes.ts", "signature": "(params: { assignmentIds, studentIds }) => Promise>", "purpose": "返回每个作业在指定学生中的目标计数(P0-7 新增)", "usedBy": [ "classes/data-access-stats.getClassHomeworkInsights", "classes/data-access-stats.getGradeHomeworkInsights" ] }, { "name": "getHomeworkSubmissionsForStudents", "file": "data-access-classes.ts", "signature": "(params: { assignmentIds, studentIds }) => Promise", "purpose": "返回指定作业和学生的提交记录(按 createdAt desc,P0-7 新增)", "usedBy": [ "classes/data-access-stats.getClassHomeworkInsights", "classes/data-access-stats.getGradeHomeworkInsights" ] }, { "name": "getPublishedHomeworkAssignmentsWithSubject", "file": "data-access-classes.ts", "signature": "(params: { assignmentIds }) => Promise", "purpose": "返回已发布作业(含科目信息,via source exam,P0-7 新增)", "usedBy": [ "classes/data-access-students.getStudentsSubjectScores" ] }, { "name": "getHomeworkSubmissionsForAssignments", "file": "data-access-classes.ts", "signature": "(assignmentIds: string[]) => Promise", "purpose": "返回指定作业的提交记录(按 createdAt desc,P0-7 新增)", "usedBy": [ "classes/data-access-students.getStudentsSubjectScores" ] } ], "dataAccessWrite": [ { "name": "createHomeworkAssignment", "file": "data-access-write.ts", "signature": "(input: { examId, classId, title, description, dueAt, allowLate?, lateDueAt?, maxAttempts? }) => Promise<{ assignmentId: string }>", "purpose": "创建作业(P1-2 新增,从 actions 下沉)", "usedBy": [ "createHomeworkAssignmentAction" ] }, { "name": "startHomeworkSubmission", "file": "data-access-write.ts", "signature": "(assignmentId: string, studentId: string) => Promise<{ submissionId: string }>", "purpose": "学生开始作答(P1-2 新增,从 actions 下沉)", "usedBy": [ "startHomeworkSubmissionAction" ] }, { "name": "saveHomeworkAnswer", "file": "data-access-write.ts", "signature": "(submissionId: string, questionId: string, answer: string) => Promise", "purpose": "保存单题答案(P1-2 新增,从 actions 下沉)", "usedBy": [ "saveHomeworkAnswerAction" ] }, { "name": "submitHomework", "file": "data-access-write.ts", "signature": "(submissionId: string, studentId: string) => Promise", "purpose": "提交作业(P1-2 新增,从 actions 下沉)", "usedBy": [ "submitHomeworkAction" ] }, { "name": "gradeHomeworkSubmission", "file": "data-access-write.ts", "signature": "(submissionId: string, grades: Array<{ answerId: string; isCorrect: boolean; score: number; feedback?: string }>) => Promise", "purpose": "教师批改作业(P1-2 新增,从 actions 下沉)", "usedBy": [ "gradeHomeworkSubmissionAction" ] } ], "statsService": [ { "name": "getTeacherGradeTrends", "file": "stats-service.ts", "signature": "(teacherId: string, limit?: number) => Promise", "purpose": "教师仪表盘年级趋势数据", "deps": [ "data-access.getAssignmentMaxScoreById" ], "usedBy": [ "dashboard (教师仪表盘)" ], "reExportedFrom": "data-access.ts (向后兼容)" }, { "name": "getHomeworkAssignmentAnalytics", "file": "stats-service.ts", "signature": "(assignmentId: string) => Promise", "purpose": "作业整体分析(含题目错误率/错答样本)", "deps": [ "data-access.isRecord", "data-access.toQuestionContent" ], "usedBy": [ "homework错误分析组件" ], "reExportedFrom": "data-access.ts (向后兼容)" }, { "name": "getStudentDashboardGrades", "file": "stats-service.ts", "signature": "(studentId: string) => Promise", "purpose": "学生仪表盘成绩(趋势/近期/班级排名)", "deps": [ "data-access.getAssignmentMaxScoreById" ], "usedBy": [ "dashboard/data-access.ts" ], "reExportedFrom": "data-access.ts (向后兼容)" }, { "name": "getHomeworkDashboardStats", "file": "stats-service.ts", "signature": "(scope?: DataScope) => Promise", "purpose": "获取作业仪表盘统计数据(作业数/已发布数/提交数/待批改数,支持数据范围过滤)", "usedBy": [ "dashboard/data-access.getAdminDashboardData" ], "reExportedFrom": "data-access.ts (向后兼容)" } ], "schema": [ { "name": "CreateHomeworkAssignmentSchema", "type": "const", "description": "zod schema 创建作业", "usedBy": [ "createHomeworkAssignmentAction", "homework-assignment-form.tsx" ] }, { "name": "CreateHomeworkAssignmentInput", "type": "type", "definition": "z.infer", "usedBy": [ "createHomeworkAssignmentAction" ] }, { "name": "GradeHomeworkSchema", "type": "const", "description": "zod schema 批改作业", "usedBy": [ "gradeHomeworkSubmissionAction", "homework-grading-view.tsx" ] } ], "types": [ { "name": "StudentDashboardGradeProps", "definition": "{ trend, recent, ranking }", "usedBy": [ "dashboard/types.ts" ] }, { "name": "HomeworkAssignmentListItem", "usedBy": [ "homework列表页" ] }, { "name": "StudentHomeworkTakeData", "usedBy": [ "homework-take-view.tsx" ] }, { "name": "HomeworkAssignmentStatus", "type": "type", "definition": "作业状态枚举", "usedBy": [ "homework/components", "homework/data-access" ] }, { "name": "HomeworkSubmissionStatus", "type": "type", "definition": "提交状态枚举", "usedBy": [ "homework/components", "homework/data-access" ] }, { "name": "TeacherGradeTrendItem", "type": "type", "definition": "教师年级趋势项", "usedBy": [ "dashboard (教师仪表盘)" ] }, { "name": "HomeworkAssignmentReviewListItem", "type": "type", "definition": "批改列表项", "usedBy": [ "teacher批改列表" ] }, { "name": "HomeworkSubmissionListItem", "type": "type", "definition": "提交列表项", "usedBy": [ "teacher提交列表" ] }, { "name": "HomeworkQuestionContent", "type": "type", "definition": "作业题目内容", "usedBy": [ "homework/components" ] }, { "name": "HomeworkSubmissionAnswerDetails", "type": "type", "definition": "提交答案详情", "usedBy": [ "homework-grading-view.tsx" ] }, { "name": "HomeworkSubmissionDetails", "type": "type", "definition": "提交详情(含答案列表)", "usedBy": [ "homework-grading-view.tsx" ] }, { "name": "StudentHomeworkProgressStatus", "type": "type", "definition": "学生作业进度状态", "usedBy": [ "student/dashboard" ] }, { "name": "StudentHomeworkAssignmentListItem", "type": "type", "definition": "学生作业列表项", "usedBy": [ "student/dashboard" ] }, { "name": "StudentHomeworkPerformanceItem", "type": "type", "definition": "学生表现项", "usedBy": [ "student/dashboard" ] }, { "name": "StudentHomeworkPerformanceSummary", "type": "type", "definition": "学生表现汇总", "usedBy": [ "student/dashboard" ] }, { "name": "StudentHomeworkTakeQuestion", "type": "type", "definition": "学生作答题目", "usedBy": [ "homework-take-view.tsx" ] }, { "name": "HomeworkAssignmentQuestionAnalytics", "type": "type", "definition": "作业题目分析", "usedBy": [ "homework错误分析组件" ] }, { "name": "HomeworkAssignmentAnalytics", "type": "type", "definition": "作业整体分析", "usedBy": [ "homework错误分析组件" ] }, { "name": "StudentHomeworkScoreAnalytics", "type": "type", "definition": "学生成绩分析", "usedBy": [ "student/dashboard" ] }, { "name": "StudentRanking", "type": "type", "definition": "学生排名", "usedBy": [ "student/dashboard" ] } ], "components": [ { "name": "HomeworkAssignmentExamContentCard", "file": "homework-assignment-exam-content-card.tsx", "purpose": "作业考试内容卡片" }, { "name": "HomeworkAssignmentExamErrorExplorerLazy", "file": "homework-assignment-exam-error-explorer-lazy.tsx", "purpose": "作业错误分析(懒加载)" }, { "name": "HomeworkAssignmentExamErrorExplorer", "file": "homework-assignment-exam-error-explorer.tsx", "purpose": "作业错误分析探索器" }, { "name": "HomeworkAssignmentExamPreviewPane", "file": "homework-assignment-exam-preview-pane.tsx", "purpose": "作业考试预览面板" }, { "name": "HomeworkAssignmentForm", "file": "homework-assignment-form.tsx", "purpose": "作业创建表单" }, { "name": "HomeworkAssignmentQuestionErrorDetailPanel", "file": "homework-assignment-question-error-detail-panel.tsx", "purpose": "题目错误详情面板" }, { "name": "HomeworkAssignmentQuestionErrorOverviewCard", "file": "homework-assignment-question-error-overview-card.tsx", "purpose": "题目错误概览卡片" }, { "name": "HomeworkGradingView", "file": "homework-grading-view.tsx", "purpose": "作业批改视图" }, { "name": "HomeworkTakeView", "file": "homework-take-view.tsx", "purpose": "学生作答视图" }, { "name": "HomeworkReviewView", "file": "student-homework-review-view.tsx", "purpose": "学生作业复习视图" } ] } }, "questions": { "path": "src/modules/questions", "description": "题库管理:题目CRUD、知识点关联、多题型支持", "exports": { "actions": [ { "name": "createNestedQuestion", "permission": "QUESTION_CREATE", "signature": "(prevState: ActionState | undefined, formData: FormData | CreateQuestionInput) => Promise>", "purpose": "创建题目(含嵌套)", "deps": [ "requirePermission", "data-access.createQuestionWithRelations" ], "usedBy": [ "create-question-dialog.tsx" ] }, { "name": "updateQuestionAction", "permission": "QUESTION_UPDATE", "signature": "同上", "purpose": "更新题目", "deps": [ "requirePermission", "data-access.updateQuestionById" ], "usedBy": [ "question-actions.tsx" ] }, { "name": "deleteQuestionAction", "permission": "QUESTION_DELETE", "signature": "同上", "purpose": "删除题目", "deps": [ "requirePermission", "data-access.deleteQuestionByIdRecursive" ], "usedBy": [ "question-actions.tsx" ] }, { "name": "getQuestionsAction", "permission": "QUESTION_READ", "signature": "(params: GetQuestionsParams) => Promise<...>", "purpose": "查询题目列表", "deps": [ "requirePermission", "data-access.getQuestions" ], "usedBy": [ "teacher/questions/page.tsx" ] }, { "name": "getKnowledgePointOptionsAction", "permission": "QUESTION_READ", "signature": "() => Promise", "purpose": "获取知识点选项", "deps": [ "requirePermission", "data-access.getKnowledgePointOptions" ], "usedBy": [ "create-question-dialog.tsx" ] } ], "dataAccess": [ { "name": "getQuestions", "signature": "(params?: GetQuestionsParams) => Promise<{ data: Question[], meta: { page, pageSize, total, totalPages } }>", "type": "cache function", "purpose": "查询题目列表(缓存)", "usedBy": [ "getQuestionsAction", "teacher/questions/page.tsx" ] }, { "name": "getQuestionsDashboardStats", "signature": "() => Promise", "purpose": "获取题目仪表盘统计数据(题目总数)", "usedBy": [ "dashboard/data-access.getAdminDashboardData" ] }, { "name": "createQuestionWithRelations", "signature": "(input: CreateQuestionInput) => Promise<{ questionId: string }>", "purpose": "创建题目(含嵌套子题目、知识点关联)(P1-2 新增,从 actions 下沉)", "usedBy": [ "createNestedQuestion" ] }, { "name": "updateQuestionById", "signature": "(questionId: string, input: Partial) => Promise", "purpose": "按ID更新题目(P1-2 新增,从 actions 下沉)", "usedBy": [ "updateQuestionAction" ] }, { "name": "deleteQuestionByIdRecursive", "signature": "(questionId: string) => Promise", "purpose": "递归删除题目(含子题目、知识点关联)(P1-2 新增,从 actions 下沉)", "usedBy": [ "deleteQuestionAction" ] }, { "name": "getKnowledgePointOptions", "signature": "() => Promise", "purpose": "获取知识点选项(含章节/教材信息)(P1-2 新增,从 actions 下沉)", "usedBy": [ "getKnowledgePointOptionsAction" ] }, { "name": "GetQuestionsParams", "type": "type", "definition": "{ q?, page?, pageSize?, ids?, knowledgePointId?, type?, difficulty? }", "usedBy": [ "getQuestions", "getQuestionsAction" ] } ], "schema": [ { "name": "QuestionTypeEnum", "type": "const", "description": "zod enum: z.enum([\"single_choice\", \"multiple_choice\", \"text\", \"judgment\", \"composite\"])", "usedBy": [ "CreateQuestionSchema", "questions/components" ] }, { "name": "BaseQuestionSchema", "type": "const", "description": "zod schema 基础题目校验", "usedBy": [ "CreateQuestionSchema" ] }, { "name": "CreateQuestionInput", "type": "type", "definition": "z.infer", "usedBy": [ "createNestedQuestion", "create-question-dialog.tsx" ] }, { "name": "CreateQuestionSchema", "type": "const", "description": "zod schema 创建题目(递归支持嵌套)", "usedBy": [ "createNestedQuestion", "create-question-dialog.tsx" ] } ], "types": [ { "name": "Question", "definition": "{ id, content, type, difficulty, createdAt, updatedAt, author, knowledgePoints, childrenCount? }", "usedBy": [ "exams (题目选择)", "homework (作业题目)" ] }, { "name": "KnowledgePointOption", "definition": "{ id, name, chapterId, chapterTitle, textbookId, textbookTitle, subject, grade }", "usedBy": [ "create-question-dialog.tsx" ] }, { "name": "QuestionType", "type": "type", "definition": "z.infer", "usedBy": [ "questions/components", "exams/components" ] } ], "components": [ { "name": "CreateQuestionButton", "file": "create-question-button.tsx", "purpose": "创建题目按钮" }, { "name": "CreateQuestionDialog", "file": "create-question-dialog.tsx", "purpose": "创建题目对话框" }, { "name": "QuestionActions", "file": "question-actions.tsx", "purpose": "题目操作按钮" }, { "name": "columns", "file": "question-columns.tsx", "type": "ColumnDef[]", "purpose": "题目表格列定义" }, { "name": "QuestionDataTable", "file": "question-data-table.tsx", "purpose": "题目数据表格" }, { "name": "QuestionFilters", "file": "question-filters.tsx", "purpose": "题目筛选器" } ] } }, "textbooks": { "path": "src/modules/textbooks", "description": "教材与知识体系:教材/章节树形结构、知识点CRUD、Markdown内容编辑、知识图谱", "exports": { "actions": [ { "name": "createTextbookAction", "permission": "TEXTBOOK_CREATE", "signature": "(prevState, formData) => Promise", "purpose": "创建教材" }, { "name": "updateTextbookAction", "permission": "TEXTBOOK_UPDATE", "signature": "(textbookId, prevState, formData) => Promise", "purpose": "更新教材元信息" }, { "name": "deleteTextbookAction", "permission": "TEXTBOOK_DELETE", "signature": "(textbookId) => Promise", "purpose": "删除教材" }, { "name": "createChapterAction", "permission": "TEXTBOOK_CREATE", "signature": "(textbookId, parentId?, prevState, formData) => Promise", "purpose": "创建章节" }, { "name": "updateChapterContentAction", "permission": "TEXTBOOK_UPDATE", "signature": "(chapterId, content, textbookId) => Promise", "purpose": "更新章节内容(Markdown)" }, { "name": "deleteChapterAction", "permission": "TEXTBOOK_DELETE", "signature": "(chapterId, textbookId) => Promise", "purpose": "删除章节" }, { "name": "createKnowledgePointAction", "permission": "TEXTBOOK_CREATE", "signature": "(chapterId, textbookId, prevState, formData) => Promise", "purpose": "创建知识点" }, { "name": "updateKnowledgePointAction", "permission": "TEXTBOOK_UPDATE", "signature": "(kpId, textbookId, prevState, formData) => Promise", "purpose": "更新知识点" }, { "name": "deleteKnowledgePointAction", "permission": "TEXTBOOK_DELETE", "signature": "(kpId, textbookId) => Promise", "purpose": "删除知识点" }, { "name": "reorderChaptersAction", "permission": "TEXTBOOK_UPDATE", "signature": "(chapterId, newIndex, parentId, textbookId) => Promise", "purpose": "章节排序" }, { "name": "getKnowledgePointsByChapterAction", "permission": "TEXTBOOK_READ", "signature": "(chapterId, textbookId) => Promise>", "purpose": "获取章节下的知识点列表(含资源归属校验)" }, { "name": "getKnowledgeGraphDataAction", "permission": "TEXTBOOK_READ", "signature": "(textbookId, viewMode: GraphViewMode) => Promise>", "purpose": "Task 7 新增:获取知识图谱数据,支持 structure/student-mastery/class-mastery 三种视图模式" }, { "name": "createPrerequisiteAction", "permission": "TEXTBOOK_UPDATE", "signature": "(formData) => Promise", "purpose": "Task 7 新增:声明前置依赖(含循环检测 + 归属校验)" }, { "name": "deletePrerequisiteAction", "permission": "TEXTBOOK_UPDATE", "signature": "(formData) => Promise", "purpose": "Task 7 新增:删除前置依赖" } ], "dataAccess": [ { "name": "getTextbooks", "signature": "(query?, subject?, grade?) => Promise", "usedBy": [ "teacher/textbooks/page.tsx", "student/learning/textbooks/page.tsx" ] }, { "name": "getTextbookById", "signature": "(id) => Promise", "usedBy": [ "teacher/textbooks/[id]/page.tsx", "student/learning/textbooks/[id]/page.tsx" ] }, { "name": "getChaptersByTextbookId", "signature": "(textbookId) => Promise", "usedBy": [ "textbook-reader.tsx" ] }, { "name": "getKnowledgePointsByChapterId", "signature": "(chapterId) => Promise", "usedBy": [ "textbook-reader.tsx" ] }, { "name": "getKnowledgePointsByTextbookId", "signature": "(textbookId) => Promise", "usedBy": [ "textbook-reader.tsx" ] }, { "name": "createTextbook", "signature": "(input: CreateTextbookInput) => Promise", "purpose": "创建教材", "usedBy": [ "createTextbookAction" ] }, { "name": "updateTextbook", "signature": "(id: string, input: UpdateTextbookInput) => Promise", "purpose": "更新教材", "usedBy": [ "updateTextbookAction" ] }, { "name": "deleteTextbook", "signature": "(id: string) => Promise", "purpose": "删除教材", "usedBy": [ "deleteTextbookAction" ] }, { "name": "createChapter", "signature": "(input: CreateChapterInput) => Promise", "purpose": "创建章节", "usedBy": [ "createChapterAction" ] }, { "name": "updateChapterContent", "signature": "(input: UpdateChapterContentInput) => Promise", "purpose": "更新章节内容", "usedBy": [ "updateChapterContentAction" ] }, { "name": "deleteChapter", "signature": "(chapterId: string, textbookId: string) => Promise", "purpose": "删除章节", "usedBy": [ "deleteChapterAction" ] }, { "name": "createKnowledgePoint", "signature": "(input: CreateKnowledgePointInput) => Promise", "purpose": "创建知识点", "usedBy": [ "createKnowledgePointAction" ] }, { "name": "updateKnowledgePoint", "signature": "(input: UpdateKnowledgePointInput) => Promise", "purpose": "更新知识点", "usedBy": [ "updateKnowledgePointAction" ] }, { "name": "deleteKnowledgePoint", "signature": "(kpId: string, textbookId: string) => Promise", "purpose": "删除知识点", "usedBy": [ "deleteKnowledgePointAction" ] }, { "name": "reorderChapters", "signature": "(chapterId: string, newIndex: number, parentId: string | null, textbookId: string) => Promise", "purpose": "章节排序", "usedBy": [ "reorderChaptersAction" ] }, { "name": "getTextbooksDashboardStats", "signature": "() => Promise", "purpose": "获取教材仪表盘统计数据(教材总数、章节总数)", "usedBy": [ "dashboard/data-access.getAdminDashboardData" ] }, { "name": "getKnowledgePointOptions", "signature": "() => Promise", "purpose": "跨模块接口:获取所有知识点选项(含章节/教材信息),供 questions 模块调用", "usedBy": [ "questions/data-access.getKnowledgePointOptions" ] }, { "name": "getTextbooksWithScope", "signature": "(query?, subject?, grade?, scope?: TextbookQueryScope) => Promise", "purpose": "P1-1 新增:按数据范围获取教材列表,学生端强制按年级过滤", "usedBy": [ "student/learning/textbooks/page.tsx" ] }, { "name": "verifyChapterBelongsToTextbook", "signature": "(chapterId, textbookId) => Promise", "purpose": "P0-4 新增:资源归属校验,防止跨教材越权操作章节", "usedBy": [ "reorderChaptersAction", "updateChapterContentAction", "deleteChapterAction", "createKnowledgePointAction" ] }, { "name": "verifyKnowledgePointBelongsToTextbook", "signature": "(kpId, textbookId) => Promise", "purpose": "P0-4 新增:资源归属校验,防止跨教材越权操作知识点", "usedBy": [ "updateKnowledgePointAction", "deleteKnowledgePointAction", "createPrerequisiteAction" ] }, { "name": "createPrerequisite", "signature": "(input: CreatePrerequisiteInput) => Promise", "purpose": "Task 7 新增:创建知识点前置依赖", "usedBy": [ "createPrerequisiteAction" ] }, { "name": "deletePrerequisite", "signature": "(input: DeletePrerequisiteInput) => Promise", "purpose": "Task 7 新增:删除知识点前置依赖", "usedBy": [ "deletePrerequisiteAction" ] }, { "name": "getPrerequisiteEdgesForTextbook", "signature": "(textbookId) => Promise>", "purpose": "Task 7 新增:获取教材下所有前置依赖边列表,用于循环检测", "usedBy": [ "createPrerequisiteAction" ] }, { "name": "getSubjectLabelKey", "signature": "(subject: string) => string", "purpose": "获取学科的 i18n 标签键", "usedBy": [ "textbooks/components/textbook-filters.tsx", "textbooks/components/textbook-card.tsx" ] }, { "name": "getGradeLabelKey", "signature": "(grade: string) => string", "purpose": "获取年级的 i18n 标签键", "usedBy": [ "textbooks/components/textbook-filters.tsx", "textbooks/components/textbook-card.tsx" ] } ], "hooks": [ { "name": "useTextSelection", "file": "hooks/use-text-selection.ts", "signature": "() => { selectedText, setSelectedText, selectionRef, contentRef, setCreateDialogOpen, setIsCreating, createDialogOpen, isCreating, handleContentPointerDown, handleContextMenuChange }", "purpose": "文本选区Hook(无参数)", "usedBy": [ "textbook-content-panel.tsx" ] }, { "name": "useKnowledgePointActions", "file": "hooks/use-knowledge-point-actions.ts", "signature": "(textbookId, selectedChapterId, selectedChapterTextbookId, highlightedKpId, setHighlightedKpId, onKpCreated?) => { editingKp, setEditingKp, editKpDialogOpen, setEditKpDialogOpen, isUpdatingKp, questionDialogOpen, setQuestionDialogOpen, targetKpForQuestion, setTargetKpForQuestion, deleteConfirmOpen, setDeleteConfirmOpen, handleCreateKnowledgePoint, requestDeleteKnowledgePoint, confirmDeleteKnowledgePoint, handleUpdateKnowledgePoint }", "purpose": "知识点操作Hook(6参数)", "usedBy": [ "textbook-reader.tsx" ] }, { "name": "useGraphData", "file": "hooks/use-graph-data.ts", "signature": "(textbookId: string, viewMode: GraphViewMode) => { data: KnowledgeGraphData | null, isLoading: boolean, error: string | null, reload: () => void }", "purpose": "Task 11 新增:知识图谱数据加载 Hook,按 textbookId + viewMode 加载,使用派生值模式避免 effect 中 setState", "usedBy": [ "components/knowledge-graph.tsx" ] } ], "types": [ { "name": "Chapter", "definition": "{ id, textbookId, title, order, parentId, content?, children? }", "usedBy": [ "textbooks/components", "questions (知识点关联)" ] }, { "name": "ChapterTreeNode", "definition": "Chapter & { children: ChapterTreeNode[] }", "purpose": "P1-5 新增:buildChapterTree 返回类型,强制 children 为非空数组", "usedBy": [ "textbooks/utils.buildChapterTree", "textbooks/components/chapter-sidebar-list.tsx" ] }, { "name": "KnowledgePoint", "definition": "{ id, name, description?, anchorText?, parentId?, chapterId?, level, order }", "usedBy": [ "textbooks/components", "questions/types" ] }, { "name": "Textbook", "type": "type", "definition": "{ id, title, subject, grade, publisher, createdAt, updatedAt }", "usedBy": [ "textbooks/components", "textbooks/data-access" ] }, { "name": "CreateTextbookInput", "type": "type", "definition": "创建教材输入", "usedBy": [ "createTextbook", "createTextbookAction" ] }, { "name": "UpdateTextbookInput", "type": "type", "definition": "更新教材输入", "usedBy": [ "updateTextbook", "updateTextbookAction" ] }, { "name": "CreateChapterInput", "type": "type", "definition": "创建章节输入", "usedBy": [ "createChapter", "createChapterAction" ] }, { "name": "UpdateChapterContentInput", "type": "type", "definition": "更新章节内容输入", "usedBy": [ "updateChapterContent", "updateChapterContentAction" ] }, { "name": "CreateKnowledgePointInput", "type": "type", "definition": "创建知识点输入", "usedBy": [ "createKnowledgePoint", "createKnowledgePointAction" ] }, { "name": "UpdateKnowledgePointInput", "type": "type", "definition": "更新知识点输入", "usedBy": [ "updateKnowledgePoint", "updateKnowledgePointAction" ] }, { "name": "GraphViewMode", "type": "type", "definition": "\"structure\" | \"student-mastery\" | \"class-mastery\"", "purpose": "Task 7 新增:图谱视图模式", "usedBy": [ "getKnowledgeGraphDataAction" ] }, { "name": "MasteryInfo", "type": "interface", "definition": "{ masteryLevel, totalQuestions, correctQuestions, lastAssessedAt }", "purpose": "Task 7 新增:掌握度信息", "usedBy": [ "getKnowledgeGraphDataAction", "data-access-graph.getStudentKpMastery", "data-access-graph.getClassKpMastery" ] }, { "name": "KpWithRelations", "type": "interface", "definition": "{ id, name, description, parentId, chapterId, level, order, questionCount, prerequisiteIds, chapterTitle }", "purpose": "Task 7 新增:带关联关系的知识点(图谱数据)", "usedBy": [ "data-access-graph.getKnowledgePointsWithRelations", "getKnowledgeGraphDataAction" ] }, { "name": "KnowledgeGraphData", "type": "interface", "definition": "{ knowledgePoints: KpWithRelations[], masteryMap: Record, viewMode: GraphViewMode }", "purpose": "Task 7 新增:图谱完整数据(Server Action 返回)", "usedBy": [ "getKnowledgeGraphDataAction" ] }, { "name": "GraphNodeData", "type": "interface", "definition": "{ kp: KpWithRelations, mastery: MasteryInfo | null, viewMode: GraphViewMode, isSelected: boolean, isHighlighted: boolean, chapterColor: string }", "purpose": "Task 9 新增:React Flow 自定义节点数据(传递给 graph-kp-node 组件)", "usedBy": [ "components/graph-kp-node.tsx", "components/knowledge-graph.tsx" ] }, { "name": "GraphEdgeData", "type": "interface", "definition": "{ edgeType: 'parent' | 'prerequisite', isHighlighted: boolean }", "purpose": "Task 9 新增:React Flow 自定义边数据", "usedBy": [ "components/graph-prerequisite-edge.tsx", "components/knowledge-graph.tsx" ] }, { "name": "MasteryLevel", "type": "type", "definition": "'low' | 'medium' | 'high' | 'unassessed'", "purpose": "Task 9 新增:掌握度等级(红/黄/绿/灰)", "usedBy": [ "components/graph-kp-node.tsx" ] }, { "name": "CreatePrerequisiteInput", "type": "type", "definition": "{ knowledgePointId, prerequisiteKpId }", "purpose": "Task 7 新增:创建前置依赖输入", "usedBy": [ "createPrerequisite", "createPrerequisiteAction" ] }, { "name": "DeletePrerequisiteInput", "type": "type", "definition": "{ knowledgePointId, prerequisiteKpId }", "purpose": "Task 7 新增:删除前置依赖输入", "usedBy": [ "deletePrerequisite", "deletePrerequisiteAction" ] } ], "constants": [ { "name": "SUBJECTS", "file": "constants.ts", "purpose": "学科常量数组(含 Chinese/Mathematics/Physics/Chemistry/Biology/English/History/Geography)" }, { "name": "GRADES", "file": "constants.ts", "purpose": "年级常量数组(含 Grade 1/Grade 2/Grade 7-12)" }, { "name": "SUBJECT_COLORS", "file": "constants.ts", "purpose": "学科颜色映射" }, { "name": "getSubjectColor", "file": "constants.ts", "signature": "(subject: string) => string", "purpose": "获取学科对应颜色" }, { "name": "getSubjectLabelKey", "file": "constants.ts", "signature": "(subject: string) => string", "purpose": "获取学科 i18n 标签键" }, { "name": "getGradeLabelKey", "file": "constants.ts", "signature": "(grade: string) => string", "purpose": "获取年级 i18n 标签键" } ], "utils": [ { "name": "sortChapters", "file": "utils.ts", "purpose": "章节排序" }, { "name": "buildChapterTree", "file": "utils.ts", "purpose": "构建章节树(返回 ChapterTreeNode[])" }, { "name": "buildChapterIndex", "file": "utils.ts", "purpose": "构建章节索引 Map" }, { "name": "findChapterParent", "file": "utils.ts", "purpose": "查找章节父节点" }, { "name": "filterKnowledgePointsByChapter", "file": "utils.ts", "purpose": "按章节过滤知识点" }, { "name": "normalizeOptional", "file": "utils.ts", "purpose": "可选字段归一化" }, { "name": "highlightKnowledgePoints", "file": "utils.ts", "purpose": "高亮知识点" }, { "name": "hasCycleAfterAddingEdge", "file": "utils.ts", "purpose": "检测添加有向边后是否形成环(DFS)" } ], "graphLayout": [ { "name": "computeGraphLayout", "file": "graph-layout.ts", "purpose": "知识图谱布局计算纯函数(Task 8:dagre 集成,输入 KpWithRelations[],输出 React Flow Node[]/Edge[] + 画布尺寸)" } ], "analytics": [ { "name": "TextbookAnalytics", "file": "analytics.tsx", "type": "context", "purpose": "教材分析 Context" }, { "name": "TextbookAnalyticsProvider", "file": "analytics.tsx", "type": "component", "purpose": "教材分析 Provider" }, { "name": "useTextbookAnalytics", "file": "analytics.tsx", "type": "hook", "purpose": "教材分析 Hook" } ], "components": [ { "name": "ChapterSidebarList", "purpose": "章节侧边栏列表" }, { "name": "CreateChapterDialog", "purpose": "创建章节对话框" }, { "name": "KnowledgeGraph", "purpose": "知识图谱可视化" }, { "name": "GraphNodeDetailPanel", "purpose": "知识图谱节点详情侧边面板(Task 13 新增:展示知识点名称/描述/掌握度/关联题目/前置后置知识点,支持跳转与前置关系增删)" }, { "name": "KnowledgePointDialogs", "purpose": "知识点对话框集合" }, { "name": "KnowledgePointList", "purpose": "知识点列表" }, { "name": "SectionErrorBoundary", "purpose": "章节内容错误边界(class component + i18n + router.refresh 重试)" }, { "name": "TextbookCard", "purpose": "教材卡片" }, { "name": "TextbookContentPanel", "purpose": "教材内容面板" }, { "name": "TextbookFilters", "purpose": "教材筛选器" }, { "name": "TextbookFormDialog", "purpose": "教材表单对话框" }, { "name": "TextbookReader", "purpose": "教材阅读器" }, { "name": "TeacherTextbookReader", "purpose": "教师端 TextbookReader 客户端包装(v1 测试修复:解决 Server→Client 函数 prop 序列化问题)" }, { "name": "TextbookSettingsDialog", "purpose": "教材设置对话框" } ], "uiDeps": [], "uiDepsNote": "已通过 render prop 解耦,不再直接 import questions 模块组件", "knownIssues": [ "i18n 覆盖率约 95%(chapter-sidebar-list 已接入,actions.ts 已接入,section-error-boundary 默认值已改英文)", "类型断言残留 3 处 as string", "P2 图谱方向键导航未实现", "v1 测试已修复:textbook-reader.tsx SheetTrigger 越界、Server→Client 函数 prop 序列化、seed 数据 i18n key 不匹配" ], "files": { "actions.ts": 502, "data-access.ts": 586, "data-access-graph.ts": 184, "types.ts": 94, "schema.ts": 62, "constants.ts": 99, "utils.ts": 203, "graph-layout.ts": 105, "analytics.tsx": 43, "hooks/use-knowledge-point-actions.ts": 121, "hooks/use-text-selection.ts": 57, "hooks/use-graph-data.ts": 58, "components/teacher-textbook-reader.tsx": 41, "components/knowledge-graph.tsx": 249, "components/graph-kp-node.tsx": 80, "components/graph-prerequisite-edge.tsx": 40, "components/graph-toolbar.tsx": 77, "components/graph-node-detail-panel.tsx": 171 }, "auditReport": "audit/textbooks-audit-report.md" } }, "classes": { "path": "src/modules/classes", "description": "班级管理:班级CRUD、学生注册/退班、邀请码、课表、学科教师分配", "exports": { "actions": [ { "name": "createTeacherClassAction", "permission": "CLASS_CREATE", "signature": "(prevState, formData) => Promise>", "purpose": "教师创建班级" }, { "name": "updateTeacherClassAction", "permission": "CLASS_UPDATE", "signature": "(classId, prevState, formData) => Promise", "purpose": "教师更新班级" }, { "name": "deleteTeacherClassAction", "permission": "CLASS_DELETE", "signature": "(classId) => Promise", "purpose": "教师删除班级" }, { "name": "createGradeClassAction", "permission": "CLASS_CREATE", "signature": "(prevState, formData) => Promise>", "purpose": "年级主任创建班级" }, { "name": "updateGradeClassAction", "permission": "CLASS_UPDATE", "signature": "(classId, prevState, formData) => Promise", "purpose": "年级主任更新班级" }, { "name": "deleteGradeClassAction", "permission": "CLASS_DELETE", "signature": "(classId) => Promise", "purpose": "年级主任删除班级" }, { "name": "enrollStudentByEmailAction", "permission": "CLASS_ENROLL", "signature": "(classId, prevState, formData) => Promise", "purpose": "通过邮箱注册学生" }, { "name": "joinClassByInvitationCodeAction", "permission": "CLASS_ENROLL", "signature": "(prevState, formData) => Promise>", "purpose": "通过邀请码加入" }, { "name": "ensureClassInvitationCodeAction", "permission": "CLASS_ENROLL", "signature": "(classId) => Promise>", "purpose": "确保邀请码存在" }, { "name": "regenerateClassInvitationCodeAction", "permission": "CLASS_ENROLL", "signature": "(classId) => Promise>", "purpose": "重新生成邀请码" }, { "name": "setStudentEnrollmentStatusAction", "permission": "CLASS_ENROLL", "signature": "(classId, studentId, status) => Promise", "purpose": "设置学生状态" }, { "name": "createClassScheduleItemAction", "permission": "CLASS_SCHEDULE", "signature": "(prevState, formData) => Promise>", "purpose": "创建课表项" }, { "name": "updateClassScheduleItemAction", "permission": "CLASS_SCHEDULE", "signature": "(scheduleId, prevState, formData) => Promise", "purpose": "更新课表项" }, { "name": "deleteClassScheduleItemAction", "permission": "CLASS_SCHEDULE", "signature": "(scheduleId) => Promise", "purpose": "删除课表项" }, { "name": "createAdminClassAction", "permission": "CLASS_CREATE", "signature": "(prevState, formData) => Promise>", "purpose": "管理员创建班级" }, { "name": "updateAdminClassAction", "permission": "CLASS_UPDATE", "signature": "(classId, prevState, formData) => Promise", "purpose": "管理员更新班级" }, { "name": "deleteAdminClassAction", "permission": "CLASS_DELETE", "signature": "(classId) => Promise", "purpose": "管理员删除班级" } ], "dataAccess": [ { "name": "getTeacherClasses", "signature": "(params?: { teacherId?: string }) => Promise", "usedBy": [ "teacher/classes/my", "dashboard" ] }, { "name": "getAdminClasses", "signature": "() => Promise", "usedBy": [ "admin班级管理" ] }, { "name": "getGradeManagedClasses", "signature": "(userId) => Promise", "usedBy": [ "grade_head班级管理" ] }, { "name": "getStudentClasses", "signature": "(studentId) => Promise", "usedBy": [ "student/dashboard" ] }, { "name": "getStudentSchedule", "signature": "(studentId) => Promise", "usedBy": [ "student课表" ] }, { "name": "getClassStudents", "signature": "(params?: { classId?, q?, status?, teacherId? }) => Promise", "usedBy": [ "teacher/classes/students" ] }, { "name": "getClassSchedule", "signature": "(params?: { classId?, teacherId? }) => Promise", "usedBy": [ "teacher/classes/schedule" ] }, { "name": "getClassHomeworkInsights", "signature": "(params: { classId, teacherId?, limit? }) => Promise", "usedBy": [ "classes作业洞察" ] }, { "name": "getGradeHomeworkInsights", "signature": "(params: { gradeId, limit? }) => Promise", "usedBy": [ "年级作业洞察" ] }, { "name": "getTeacherIdForMutations", "signature": "() => Promise", "purpose": "获取当前教师ID(用于写操作)", "usedBy": [ "classes写操作内部" ] }, { "name": "getClassSubjects", "signature": "(classId: string) => Promise", "purpose": "获取班级学科列表", "usedBy": [ "class-detail组件" ] }, { "name": "getTeacherOptions", "signature": "() => Promise", "purpose": "获取教师选项列表", "usedBy": [ "class-detail组件" ] }, { "name": "getTeacherTeachingSubjects", "signature": "(teacherId: string) => Promise<...>", "purpose": "获取教师所教学科", "usedBy": [ "classes内部" ] }, { "name": "getManagedGrades", "signature": "(userId: string) => Promise<...>", "purpose": "获取所管年级", "usedBy": [ "grade_head视图" ] }, { "name": "getStudentsSubjectScores", "signature": "(...) => Promise<...>", "purpose": "获取学生学科成绩", "usedBy": [ "classes内部" ] }, { "name": "getClassStudentSubjectScoresV2", "signature": "(...) => Promise<...>", "purpose": "获取班级学生学科成绩V2", "usedBy": [ "classes内部" ] }, { "name": "getClassesDashboardStats", "signature": "() => Promise", "purpose": "获取班级仪表盘统计数据(班级总数)", "usedBy": [ "dashboard/data-access.getAdminDashboardData" ] }, { "name": "createTeacherClass", "signature": "(input) => Promise", "purpose": "教师创建班级", "usedBy": [ "createTeacherClassAction" ] }, { "name": "createAdminClass", "signature": "(input) => Promise", "purpose": "管理员创建班级", "usedBy": [ "createAdminClassAction" ] }, { "name": "ensureClassInvitationCode", "signature": "(classId: string) => Promise<{ code: string }>", "purpose": "确保邀请码存在", "usedBy": [ "ensureClassInvitationCodeAction" ] }, { "name": "regenerateClassInvitationCode", "signature": "(classId: string) => Promise<{ code: string }>", "purpose": "重新生成邀请码", "usedBy": [ "regenerateClassInvitationCodeAction" ] }, { "name": "enrollStudentByInvitationCode", "signature": "(code: string, studentId: string) => Promise<{ classId: string }>", "purpose": "通过邀请码注册学生", "usedBy": [ "joinClassByInvitationCodeAction" ] }, { "name": "enrollTeacherByInvitationCode", "signature": "(code: string, teacherId: string) => Promise<...>", "purpose": "通过邀请码注册教师", "usedBy": [ "classes内部" ] }, { "name": "updateTeacherClass", "signature": "(classId: string, input) => Promise", "purpose": "教师更新班级", "usedBy": [ "updateTeacherClassAction" ] }, { "name": "updateAdminClass", "signature": "(classId: string, input) => Promise", "purpose": "管理员更新班级", "usedBy": [ "updateAdminClassAction" ] }, { "name": "setClassSubjectTeachers", "signature": "(classId: string, assignments: ClassSubjectTeacherAssignment[]) => Promise", "purpose": "设置班级学科教师", "usedBy": [ "classes内部" ] }, { "name": "deleteTeacherClass", "signature": "(classId: string) => Promise", "purpose": "教师删除班级", "usedBy": [ "deleteTeacherClassAction" ] }, { "name": "deleteAdminClass", "signature": "(classId: string) => Promise", "purpose": "管理员删除班级", "usedBy": [ "deleteAdminClassAction" ] }, { "name": "enrollStudentByEmail", "signature": "(classId: string, email: string) => Promise", "purpose": "通过邮箱注册学生", "usedBy": [ "enrollStudentByEmailAction" ] }, { "name": "setStudentEnrollmentStatus", "signature": "(classId: string, studentId: string, status: string) => Promise", "purpose": "设置学生状态", "usedBy": [ "setStudentEnrollmentStatusAction" ] }, { "name": "verifyTeacherOwnsClass", "signature": "(classId: string, teacherId: string) => Promise", "purpose": "校验教师是否拥有班级(P0-5 新增,供 scheduling 模块跨模块校验 classSchedule 写权限)", "usedBy": [ "scheduling/data-access-class-schedule.createClassScheduleItem", "scheduling/data-access-class-schedule.updateClassScheduleItem", "scheduling/data-access-class-schedule.deleteClassScheduleItem" ] }, { "name": "getStudentIdsByClassId", "signature": "(classId: string) => Promise", "purpose": "获取班级所有学生 ID(跨模块接口)", "usedBy": [ "notifications/actions.sendClassNotificationAction" ] }, { "name": "getStudentIdsByClassIds", "signature": "(classIds: string[]) => Promise", "purpose": "获取多个班级的所有学生 ID(跨模块接口)", "usedBy": [ "homework/data-access", "homework/stats-service" ] }, { "name": "getActiveStudentIdsByClassId", "signature": "(classId: string) => Promise", "purpose": "获取班级所有活跃学生 ID(跨模块接口)", "usedBy": [ "homework/data-access", "homework/stats-service", "grades/data-access", "diagnostic/data-access" ] }, { "name": "getTeacherSubjectIdsByClass", "signature": "(classId: string, teacherId: string) => Promise", "purpose": "获取教师在班级所教科目标识(跨模块接口)", "usedBy": [ "homework/data-access-write" ] }, { "name": "getStudentActiveClassId", "signature": "(studentId: string) => Promise", "purpose": "获取学生当前活跃班级 ID(跨模块接口)", "usedBy": [ "homework/stats-service", "grades/data-access-ranking", "parent/data-access" ] }, { "name": "getStudentActiveGradeId", "signature": "(studentId: string) => Promise", "purpose": "获取学生当前活跃班级对应的年级 ID(跨模块接口)", "usedBy": [ "elective/data-access-selections.getStudentGradeId" ] }, { "name": "getClassExists", "signature": "(classId: string) => Promise", "purpose": "校验班级是否存在(跨模块接口)", "usedBy": [ "notifications/actions.sendClassNotificationAction", "grades/data-access", "diagnostic/data-access" ] }, { "name": "getClassNameById", "signature": "(classId: string) => Promise", "purpose": "获取班级名称(跨模块接口)", "usedBy": [ "grades/data-access", "grades/data-access-analytics", "grades/export", "parent/data-access", "diagnostic/data-access" ] }, { "name": "getClassGradeId", "signature": "(classId: string) => Promise", "purpose": "获取班级关联的年级 ID(跨模块接口)", "usedBy": [ "classes/actions.updateGradeClassAction", "classes/actions.deleteGradeClassAction" ] }, { "name": "getGradeIdsByClassIds", "signature": "(classIds: string[]) => Promise", "purpose": "获取多个班级关联的年级 ID 列表(跨模块接口)", "usedBy": [ "homework/stats-service" ] }, { "name": "getClassNamesByIds", "signature": "(classIds: string[]) => Promise>", "purpose": "批量获取班级名称(跨模块接口)", "usedBy": [ "grades/data-access" ] }, { "name": "getClassesByGradeId", "signature": "(gradeId: string) => Promise>", "purpose": "获取指定年级下的所有班级(跨模块接口)", "usedBy": [ "grades/data-access-analytics" ] }, { "name": "getTeacherIdsByClassIds", "signature": "(classIds: string[]) => Promise", "purpose": "获取多个班级的所有教师 ID(班主任 + 任课教师,跨模块接口)", "usedBy": [ "messaging/data-access.getRecipients" ] } ], "schema": [ { "name": "CreateTeacherClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 教师创建班级(name, grade 必填,schoolName/schoolId/gradeId/homeroom/room 可选)", "usedBy": [ "actions.createTeacherClassAction" ] }, { "name": "UpdateTeacherClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 教师更新班级(classId 必填,其余可选)", "usedBy": [ "actions.updateTeacherClassAction" ] }, { "name": "DeleteTeacherClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 教师删除班级(classId)", "usedBy": [ "actions.deleteTeacherClassAction" ] }, { "name": "CreateAdminClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 管理员创建班级(name, grade, teacherId 必填,其余可选)", "usedBy": [ "actions.createAdminClassAction" ] }, { "name": "UpdateAdminClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 管理员更新班级(classId 必填,其余可选)", "usedBy": [ "actions.updateAdminClassAction" ] }, { "name": "DeleteAdminClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 管理员删除班级(classId)", "usedBy": [ "actions.deleteAdminClassAction" ] }, { "name": "CreateGradeClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 年级主任创建班级(name, gradeId, teacherId 必填,其余可选)", "usedBy": [ "actions.createGradeClassAction" ] }, { "name": "UpdateGradeClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 年级主任更新班级(classId 必填,其余可选)", "usedBy": [ "actions.updateGradeClassAction" ] }, { "name": "DeleteGradeClassSchema", "type": "const", "file": "schema.ts", "description": "zod schema 年级主任删除班级(classId)", "usedBy": [ "actions.deleteGradeClassAction" ] }, { "name": "CreateClassScheduleItemSchema", "type": "const", "file": "schema.ts", "description": "zod schema 创建课表项(classId, weekday 1-7, course, startTime, endTime 必填,location 可选)", "usedBy": [ "actions.createClassScheduleItemAction" ] }, { "name": "UpdateClassScheduleItemSchema", "type": "const", "file": "schema.ts", "description": "zod schema 更新课表项(scheduleId 必填,其余可选)", "usedBy": [ "actions.updateClassScheduleItemAction" ] }, { "name": "DeleteClassScheduleItemSchema", "type": "const", "file": "schema.ts", "description": "zod schema 删除课表项(scheduleId)", "usedBy": [ "actions.deleteClassScheduleItemAction" ] }, { "name": "EnrollStudentByEmailSchema", "type": "const", "file": "schema.ts", "description": "zod schema 通过邮箱注册学生(classId, email)", "usedBy": [ "actions.enrollStudentByEmailAction" ] } ], "types": [ { "name": "TeacherClass", "type": "type", "definition": "教师班级对象", "usedBy": [ "getTeacherClasses", "dashboard" ] }, { "name": "AssignmentSummary", "type": "type", "definition": "作业摘要", "usedBy": [ "classes/components" ] }, { "name": "TeacherOption", "type": "type", "definition": "教师选项", "usedBy": [ "getTeacherOptions", "class-detail组件" ] }, { "name": "DEFAULT_CLASS_SUBJECTS", "type": "const", "definition": "默认班级学科列表", "usedBy": [ "classes内部" ] }, { "name": "ClassSubject", "type": "type", "definition": "班级学科", "usedBy": [ "getClassSubjects", "classes/components" ] }, { "name": "ClassSubjectTeacherAssignment", "type": "type", "definition": "班级学科教师分配", "usedBy": [ "setClassSubjectTeachers" ] }, { "name": "AdminClassListItem", "type": "type", "definition": "管理员班级列表项", "usedBy": [ "getAdminClasses", "getGradeManagedClasses" ] }, { "name": "CreateTeacherClassInput", "type": "type", "definition": "教师创建班级输入", "usedBy": [ "createTeacherClass" ] }, { "name": "UpdateTeacherClassInput", "type": "type", "definition": "教师更新班级输入", "usedBy": [ "updateTeacherClass" ] }, { "name": "ClassStudent", "type": "type", "definition": "班级学生", "usedBy": [ "getClassStudents", "classes/components" ] }, { "name": "ClassScheduleItem", "type": "type", "definition": "班级课表项", "usedBy": [ "getClassSchedule", "classes/components" ] }, { "name": "StudentEnrolledClass", "type": "type", "definition": "学生已注册班级", "usedBy": [ "getStudentClasses", "dashboard" ] }, { "name": "StudentScheduleItem", "type": "type", "definition": "学生课表项", "usedBy": [ "getStudentSchedule", "dashboard" ] }, { "name": "ScoreStats", "type": "type", "definition": "成绩统计", "usedBy": [ "classes/components" ] }, { "name": "ClassHomeworkAssignmentStats", "type": "type", "definition": "班级作业统计", "usedBy": [ "classes/components" ] }, { "name": "ClassHomeworkInsights", "type": "type", "definition": "班级作业洞察", "usedBy": [ "getClassHomeworkInsights", "classes/components" ] }, { "name": "GradeHomeworkClassSummary", "type": "type", "definition": "年级作业班级汇总", "usedBy": [ "classes/components" ] }, { "name": "GradeHomeworkInsights", "type": "type", "definition": "年级作业洞察", "usedBy": [ "getGradeHomeworkInsights", "classes/components" ] } ], "components": [ { "name": "StudentsTable", "purpose": "学生表格" }, { "name": "StudentsFilters", "purpose": "学生筛选器" }, { "name": "ScheduleView", "purpose": "课表视图" }, { "name": "MyClassesGrid", "purpose": "我的班级网格" }, { "name": "AdminClassesClient", "purpose": "管理员班级客户端" }, { "name": "ScheduleFilters", "purpose": "课表筛选器" }, { "name": "GradeClassesClient", "purpose": "年级班级客户端" }, { "name": "transformAssignmentsToChartData", "file": "class-detail/transformAssignmentsToChartData", "type": "function", "purpose": "转换作业为图表数据" }, { "name": "ClassSubmissionTrendChart", "file": "class-detail/ClassSubmissionTrendChart", "purpose": "班级提交趋势图表" }, { "name": "ClassTrendsWidget", "file": "class-detail/ClassTrendsWidget", "purpose": "班级趋势组件" }, { "name": "ClassStudentsWidget", "file": "class-detail/ClassStudentsWidget", "purpose": "班级学生组件" }, { "name": "ClassScheduleGrid", "file": "class-detail/ClassScheduleGrid", "purpose": "班级课表网格" }, { "name": "ClassScheduleWidget", "file": "class-detail/ClassScheduleWidget", "purpose": "班级课表组件" }, { "name": "ClassQuickActions", "file": "class-detail/ClassQuickActions", "purpose": "班级快捷操作" }, { "name": "EditClassDialog", "file": "class-detail/EditClassDialog", "purpose": "编辑班级对话框" }, { "name": "ClassOverviewStats", "file": "class-detail/ClassOverviewStats", "purpose": "班级概览统计" }, { "name": "ClassHeader", "file": "class-detail/ClassHeader", "purpose": "班级头部" }, { "name": "ClassAssignmentsWidget", "file": "class-detail/ClassAssignmentsWidget", "purpose": "班级作业组件" } ] }, "files": [ { "path": "data-access.ts", "lines": 548, "description": "核心班级 CRUD + 邀请码 + 教师班级管理(含 re-export 向后兼容)", "exports": [ "getTeacherClasses", "getTeacherOptions", "getTeacherTeachingSubjects", "createTeacherClass", "ensureClassInvitationCode", "regenerateClassInvitationCode", "enrollStudentByInvitationCode", "enrollTeacherByInvitationCode", "updateTeacherClass", "setClassSubjectTeachers", "deleteTeacherClass", "enrollStudentByEmail", "setStudentEnrollmentStatus", "getSessionTeacherId", "getTeacherIdForMutations", "getAccessibleClassIdsForTeacher", "getClassGradeIdsByClassIds", "getTeacherSubjectIdsForClass", "getClassSubjects", "compareClassLike", "isDuplicateInvitationCodeError", "generateUniqueInvitationCode" ] }, { "path": "data-access-stats.ts", "lines": 513, "description": "作业统计查询(班级/年级作业洞察,通过 homework/data-access-classes 获取数据)", "exports": [ "getClassHomeworkInsights", "getGradeHomeworkInsights", "getClassesDashboardStats" ] }, { "path": "data-access-schedule.ts", "lines": 93, "description": "课表查询(学生/班级课表只读,P0-5 已修复:写函数已迁移至 scheduling/data-access-class-schedule.ts)", "exports": [ "getStudentSchedule", "getClassSchedule" ] }, { "path": "data-access-students.ts", "lines": 253, "description": "学生相关查询(科目成绩、学生名单、学生班级,通过 homework/data-access-classes 获取数据)", "exports": [ "getStudentsSubjectScores", "getClassStudentSubjectScoresV2", "getStudentClasses", "getClassStudents" ] }, { "path": "data-access-admin.ts", "lines": 406, "description": "管理员班级管理(管理员班级 CRUD、年级管理班级查询)", "exports": [ "getAdminClasses", "getGradeManagedClasses", "getManagedGrades", "createAdminClass", "updateAdminClass", "deleteAdminClass" ] }, { "path": "actions.ts", "lines": 50, "description": "Barrel re-export(P0-3 修复:从 974 行拆分为 6 个职责文件)", "exports": [ "createTeacherClassAction", "updateTeacherClassAction", "deleteTeacherClassAction", "createAdminClassAction", "updateAdminClassAction", "deleteAdminClassAction", "createGradeClassAction", "updateGradeClassAction", "deleteGradeClassAction", "enrollStudentByEmailAction", "joinClassByInvitationCodeAction", "ensureClassInvitationCodeAction", "regenerateClassInvitationCodeAction", "createClassInvitationCodeAction", "revokeClassInvitationCodeAction", "listClassInvitationCodesAction", "setStudentEnrollmentStatusAction", "createClassScheduleItemAction", "updateClassScheduleItemAction", "deleteClassScheduleItemAction" ] }, { "path": "actions-teacher.ts", "lines": 100, "description": "教师班级 CRUD(3 个 Action,P0-3 修复)" }, { "path": "actions-admin.ts", "lines": 120, "description": "管理员班级 CRUD(3 个 Action,P0-3 修复)" }, { "path": "actions-grade.ts", "lines": 110, "description": "年级组长班级 CRUD(3 个 Action,P0-3 修复)" }, { "path": "actions-invitations.ts", "lines": 280, "description": "邀请码与注册(8 个 Action,P0-3 修复)" }, { "path": "actions-schedule.ts", "lines": 90, "description": "班级课表 CRUD(3 个 Action,P0-3 修复)" }, { "path": "actions-shared.ts", "lines": 60, "description": "共享工具(hasAdminScope/hasTeacherScope/hasStudentScope/parseSubjectTeachers/toWeekday,P1-1 修复)" } ] }, "school": { "path": "src/modules/school", "description": "学校基础数据管理:学校、年级、部门、学年的CRUD。actions 层仅做编排(权限校验+Zod+revalidatePath+after(logAudit)),所有 DB 操作下沉至 data-access。学校CRUD actions 在写操作成功后通过 Next.js after() 异步调用 logAudit() 记录操作日志", "exports": { "actions": [ { "name": "createSchoolAction", "permission": "SCHOOL_MANAGE", "signature": "(prevState, formData) => Promise>", "purpose": "创建学校", "auditLog": "school.create" }, { "name": "updateSchoolAction", "permission": "SCHOOL_MANAGE", "signature": "(schoolId, prevState, formData) => Promise>", "purpose": "更新学校", "auditLog": "school.update" }, { "name": "deleteSchoolAction", "permission": "SCHOOL_MANAGE", "signature": "(schoolId) => Promise>", "purpose": "删除学校", "auditLog": "school.delete" }, { "name": "createGradeAction", "permission": "GRADE_MANAGE", "signature": "(prevState, formData) => Promise>", "purpose": "创建年级" }, { "name": "updateGradeAction", "permission": "GRADE_MANAGE", "signature": "(gradeId, prevState, formData) => Promise>", "purpose": "更新年级" }, { "name": "deleteGradeAction", "permission": "GRADE_MANAGE", "signature": "(gradeId) => Promise>", "purpose": "删除年级" }, { "name": "createDepartmentAction", "permission": "SCHOOL_MANAGE", "signature": "(prevState, formData) => Promise>", "purpose": "创建部门" }, { "name": "updateDepartmentAction", "permission": "SCHOOL_MANAGE", "signature": "(departmentId, prevState, formData) => Promise>", "purpose": "更新部门" }, { "name": "deleteDepartmentAction", "permission": "SCHOOL_MANAGE", "signature": "(departmentId) => Promise>", "purpose": "删除部门" }, { "name": "createAcademicYearAction", "permission": "SCHOOL_MANAGE", "signature": "(prevState, formData) => Promise>", "purpose": "创建学年" }, { "name": "updateAcademicYearAction", "permission": "SCHOOL_MANAGE", "signature": "(academicYearId, prevState, formData) => Promise>", "purpose": "更新学年" }, { "name": "deleteAcademicYearAction", "permission": "SCHOOL_MANAGE", "signature": "(academicYearId) => Promise>", "purpose": "删除学年" } ], "dataAccess": [ { "name": "getSchools", "signature": "() => Promise", "usedBy": [ "admin学校管理", "onboarding" ] }, { "name": "getGrades", "signature": "() => Promise", "usedBy": [ "admin年级管理", "exams", "onboarding" ] }, { "name": "getDepartments", "signature": "() => Promise", "usedBy": [ "admin部门管理" ] }, { "name": "getAcademicYears", "signature": "() => Promise", "usedBy": [ "admin学年管理" ] }, { "name": "getStaffOptions", "signature": "() => Promise", "usedBy": [ "school组件" ] }, { "name": "getGradesForStaff", "signature": "(staffId) => Promise", "usedBy": [ "grade_head视图" ] }, { "name": "getSchoolsForUser", "signature": "(userId) => Promise", "usedBy": [ "非admin角色学校视图" ] }, { "name": "getGradesForUser", "signature": "(userId) => Promise", "usedBy": [ "非admin角色年级视图" ] }, { "name": "createDepartment", "signature": "(data: DepartmentInsertData) => Promise", "usedBy": [ "createDepartmentAction" ] }, { "name": "updateDepartment", "signature": "(id: string, data: DepartmentUpdateData) => Promise", "usedBy": [ "updateDepartmentAction" ] }, { "name": "deleteDepartment", "signature": "(id: string) => Promise", "usedBy": [ "deleteDepartmentAction" ] }, { "name": "createSchool", "signature": "(data: SchoolInsertData) => Promise", "usedBy": [ "createSchoolAction" ] }, { "name": "updateSchool", "signature": "(id: string, data: SchoolUpdateData) => Promise", "usedBy": [ "updateSchoolAction" ] }, { "name": "deleteSchool", "signature": "(id: string) => Promise", "usedBy": [ "deleteSchoolAction" ] }, { "name": "createGrade", "signature": "(data: GradeInsertData) => Promise", "usedBy": [ "createGradeAction" ] }, { "name": "updateGrade", "signature": "(id: string, data: GradeUpdateData) => Promise", "usedBy": [ "updateGradeAction" ] }, { "name": "deleteGrade", "signature": "(id: string) => Promise", "usedBy": [ "deleteGradeAction" ] }, { "name": "createAcademicYear", "signature": "(data: AcademicYearInsertData) => Promise", "usedBy": [ "createAcademicYearAction" ] }, { "name": "updateAcademicYear", "signature": "(id: string, data: AcademicYearUpdateData) => Promise", "usedBy": [ "updateAcademicYearAction" ] }, { "name": "deleteAcademicYear", "signature": "(id: string) => Promise", "usedBy": [ "deleteAcademicYearAction" ] }, { "name": "getSubjectOptions", "signature": "() => Promise", "purpose": "获取科目选项列表(跨模块接口)", "usedBy": [ "homework/data-access", "grades/data-access", "grades/data-access-analytics", "grades/export", "elective/data-access-selections" ] }, { "name": "getGradeOptions", "signature": "() => Promise", "purpose": "获取年级选项列表(跨模块接口)", "usedBy": [ "grades/data-access", "grades/export", "parent/data-access", "elective/data-access-selections" ] }, { "name": "isGradeHead", "signature": "(gradeId: string, userId: string) => Promise", "purpose": "校验用户是否为指定年级的年级主任(跨模块接口)", "usedBy": [ "classes/actions.createTeacherClassAction" ] }, { "name": "isGradeManager", "signature": "(gradeId: string, userId: string) => Promise", "purpose": "校验用户是否为指定年级的年级主任或教学主任(跨模块接口)", "usedBy": [ "classes/actions.createGradeClassAction", "classes/actions.updateGradeClassAction", "classes/actions.deleteGradeClassAction" ] }, { "name": "findGradeIdByHeadAndName", "signature": "(userId: string, gradeName: string) => Promise", "purpose": "根据年级名称查找用户担任年级主任的年级 ID(跨模块接口)", "usedBy": [ "classes/actions.createTeacherClassAction" ] } ], "schema": [ { "name": "UpsertDepartmentSchema", "type": "const", "description": "zod schema 部门upsert", "usedBy": [ "createDepartmentAction", "updateDepartmentAction" ] }, { "name": "UpsertAcademicYearSchema", "type": "const", "description": "zod schema 学年upsert", "usedBy": [ "createAcademicYearAction", "updateAcademicYearAction" ] }, { "name": "UpsertSchoolSchema", "type": "const", "description": "zod schema 学校upsert", "usedBy": [ "createSchoolAction", "updateSchoolAction" ] }, { "name": "UpsertGradeSchema", "type": "const", "description": "zod schema 年级upsert", "usedBy": [ "createGradeAction", "updateGradeAction" ] } ], "types": [ { "name": "DepartmentListItem", "type": "type", "definition": "部门列表项", "usedBy": [ "getDepartments", "school/components" ] }, { "name": "AcademicYearListItem", "type": "type", "definition": "学年列表项", "usedBy": [ "getAcademicYears", "school/components" ] }, { "name": "SchoolListItem", "type": "type", "definition": "学校列表项", "usedBy": [ "getSchools", "school/components" ] }, { "name": "StaffOption", "type": "type", "definition": "员工选项", "usedBy": [ "getStaffOptions", "school/components" ] }, { "name": "GradeListItem", "type": "type", "definition": "年级列表项", "usedBy": [ "getGrades", "school/components", "exams" ] }, { "name": "DepartmentInsertData", "type": "type", "definition": "部门创建入参", "usedBy": [ "createDepartment" ] }, { "name": "DepartmentUpdateData", "type": "type", "definition": "部门更新入参", "usedBy": [ "updateDepartment" ] }, { "name": "SchoolInsertData", "type": "type", "definition": "学校创建入参", "usedBy": [ "createSchool" ] }, { "name": "SchoolUpdateData", "type": "type", "definition": "学校更新入参", "usedBy": [ "updateSchool" ] }, { "name": "GradeInsertData", "type": "type", "definition": "年级创建入参", "usedBy": [ "createGrade" ] }, { "name": "GradeUpdateData", "type": "type", "definition": "年级更新入参", "usedBy": [ "updateGrade" ] }, { "name": "AcademicYearInsertData", "type": "type", "definition": "学年创建入参", "usedBy": [ "createAcademicYear" ] }, { "name": "AcademicYearUpdateData", "type": "type", "definition": "学年更新入参", "usedBy": [ "updateAcademicYear" ] } ], "components": [ { "name": "SchoolsClient", "purpose": "学校管理客户端(组合容器,P1-5/P2-1 重构后仅负责组合子组件与 Table 渲染)" }, { "name": "SchoolFormDialog", "file": "components/school-form-dialog.tsx", "purpose": "学校创建/编辑表单对话框(内部管理 createMutation/updateMutation)", "props": "open, onOpenChange, editItem?, onSuccess" }, { "name": "SchoolDeleteDialog", "file": "components/school-delete-dialog.tsx", "purpose": "学校删除确认对话框(内部管理 deleteMutation)", "props": "deleteItem, onOpenChange, onSuccess" }, { "name": "SchoolListToolbar", "file": "components/school-list-toolbar.tsx", "purpose": "学校列表工具栏(数量 Badge + 新建按钮)", "props": "count, onCreate, isWorking" }, { "name": "GradesClient", "purpose": "年级管理客户端" }, { "name": "DepartmentsClient", "purpose": "部门管理客户端" }, { "name": "AcademicYearClient", "purpose": "学年管理客户端" }, { "name": "SchoolErrorBoundary", "file": "components/school-error-boundary.tsx", "purpose": "school 模块共享 Error Boundary(class component + i18n + router.refresh 重试),4 个页面均包裹", "props": "children, fallback?" }, { "name": "SchoolListSkeleton", "file": "components/school-skeleton.tsx", "purpose": "表格加载骨架屏(animate-pulse)", "props": "rows?" }, { "name": "SchoolCardSkeleton", "file": "components/school-skeleton.tsx", "purpose": "卡片加载骨架屏(animate-pulse)", "props": "" } ], "hooks": [ { "name": "useSchoolData", "file": "hooks/use-school-data.ts", "signature": "useSchoolData(): { createOpen, editItem, deleteItem, setCreateOpen, setEditItem, setDeleteItem, isWorking }", "purpose": "学校管理客户端状态 Hook,集中管理创建/编辑/删除对话框开关状态与当前操作项,isWorking 表示任意对话框处于打开状态", "usedBy": [ "school/components/schools-view.tsx" ] } ] } }, "dashboard": { "path": "src/modules/dashboard", "description": "各角色仪表盘数据聚合与展示(含权限校验 + i18n + 纯逻辑工具函数)", "exports": { "actions": [ { "name": "getAdminDashboardAction", "signature": "() => Promise", "deps": [ "shared/lib/auth-guard.requirePermission", "dashboard/data-access.getAdminDashboardData", "Permissions.DASHBOARD_ADMIN_READ" ], "usedBy": [ "admin/dashboard/page.tsx" ] }, { "name": "getTeacherDashboardAction", "signature": "() => Promise", "deps": [ "shared/lib/auth-guard.requirePermission", "classes/data-access.getTeacherClasses/getClassSchedule/getTeacherIdForMutations", "homework/data-access.getHomeworkAssignments/getHomeworkSubmissions/getTeacherGradeTrends", "users/data-access.getUserBasicInfo", "dashboard/lib/dashboard-utils.computeTeacherMetrics", "Permissions.DASHBOARD_TEACHER_READ" ], "usedBy": [ "teacher/dashboard/page.tsx" ] }, { "name": "getStudentDashboardAction", "signature": "() => Promise<{ student, dashboardProps }>", "deps": [ "shared/lib/auth-guard.requirePermission", "users/data-access.getCurrentStudentUser", "classes/data-access.getStudentClasses/getStudentSchedule", "homework/data-access.getStudentHomeworkAssignments/getStudentDashboardGrades", "dashboard/lib/dashboard-utils.countStudentAssignments/sortUpcomingAssignments/toWeekday/filterTodaySchedule", "Permissions.DASHBOARD_STUDENT_READ" ], "usedBy": [ "student/dashboard/page.tsx" ] }, { "name": "getParentDashboardAction", "signature": "() => Promise<{ data, hasChildren }>", "deps": [ "shared/lib/auth-guard.requirePermission", "parent/data-access.getParentDashboardData", "Permissions.DASHBOARD_PARENT_READ" ], "usedBy": [ "parent/dashboard/page.tsx" ] } ], "lib": [ { "name": "toWeekday", "signature": "(d: Date) => Weekday", "purpose": "Date 转 1-7 周几(周一=1,周日=7)" }, { "name": "countStudentAssignments", "signature": "(assignments, now, dueSoonWindowDays?) => StudentAssignmentStats", "purpose": "单次遍历统计学生作业:即将到期/已逾期/已批改" }, { "name": "sortUpcomingAssignments", "signature": "(assignments, limit?) => StudentHomeworkAssignmentListItem[]", "purpose": "按截止日期升序排序取前 N 条" }, { "name": "filterTodaySchedule", "signature": "(schedule, weekday, classNameById?) => T[]", "purpose": "筛选指定周几课表并按开始时间排序(V3 改为泛型函数,调用方指定返回类型,消除 as 断言)" }, { "name": "computeTeacherMetrics", "signature": "(classes, schedule, assignments, submissions, gradeTrends, now) => TeacherDashboardMetrics", "purpose": "计算教师仪表盘派生指标:待批改数/进行中作业/平均分/提交率/今日课表/待批改列表" }, { "name": "getGreetingKey", "signature": "(now: Date) => 'morning' | 'afternoon' | 'evening'", "purpose": "根据当前小时返回问候语时段 key" } ], "components": [ { "name": "DashboardSection", "path": "components/dashboard-section.tsx", "purpose": "分区 Error Boundary + Suspense + 骨架屏包装器,包裹每个独立数据区块", "variants": ["stats", "card", "chart", "table", "list"], "usedBy": [ "admin-dashboard.tsx", "teacher-dashboard-view.tsx", "student-dashboard-view.tsx" ] }, { "name": "DashboardSectionErrorBoundary", "path": "components/dashboard-section.tsx", "purpose": "仪表盘分区级 Error Boundary(class component),单区块崩溃仅替换该区块" }, { "name": "DashboardSectionSkeleton", "path": "components/dashboard-section.tsx", "purpose": "分区骨架屏,5 种变体匹配不同数据区块布局" } ], "dataAccess": [ { "name": "getAdminDashboardData", "signature": "(scope?: DataScope) => Promise", "deps": [ "users/data-access.getUsersDashboardStats", "classes/data-access.getClassesDashboardStats", "textbooks/data-access.getTextbooksDashboardStats", "questions/data-access.getQuestionsDashboardStats", "exams/data-access.getExamsDashboardStats", "homework/stats-service.getHomeworkDashboardStats", "DataScope" ], "usedBy": [ "dashboard/actions.getAdminDashboardAction" ] } ], "types": [ { "name": "StudentDashboardProps", "definition": "{ studentName, enrolledClassCount, dueSoonCount, overdueCount, gradedCount, todayScheduleItems, upcomingAssignments, grades }", "deps": [ "homework/types.StudentDashboardGradeProps" ], "usedBy": [ "student-dashboard-view.tsx" ] }, { "name": "TeacherDashboardData", "definition": "{ classes, schedule, assignments, submissions, teacherName, gradeTrends }", "deps": [ "homework/data-access.getTeacherGradeTrends", "classes/data-access.getTeacherClasses" ], "usedBy": [ "teacher-dashboard-view.tsx" ] }, { "name": "AdminDashboardData", "definition": "{ activeSessionsCount, userCount, userRoleCounts, classCount, textbookCount, chapterCount, questionCount, examCount, homeworkAssignmentCount, homeworkAssignmentPublishedCount, homeworkSubmissionCount, homeworkSubmissionToGradeCount, recentUsers, userGrowth, homeworkTrend }", "usedBy": [ "admin/dashboard/page.tsx" ] }, { "name": "AdminDashboardUserRoleCount", "type": "type", "definition": "管理员仪表盘用户角色计数", "usedBy": [ "admin-dashboard/AdminDashboardView" ] }, { "name": "AdminDashboardRecentUser", "type": "type", "definition": "管理员仪表盘最近用户", "usedBy": [ "admin-dashboard/AdminDashboardView" ] }, { "name": "StudentTodayScheduleItem", "type": "type", "definition": "学生今日课表项", "usedBy": [ "student-dashboard/StudentTodayScheduleCard" ] }, { "name": "TeacherTodayScheduleItem", "type": "type", "definition": "教师今日课表项", "usedBy": [ "teacher-dashboard/TeacherSchedule" ] } ], "components": [ { "name": "AdminDashboardView", "file": "admin-dashboard/AdminDashboardView", "purpose": "管理员仪表盘视图(V1 新增趋势图表区域:用户增长趋势 + 作业提交趋势)" }, { "name": "UserGrowthChart", "file": "admin-dashboard/user-growth-chart", "purpose": "recharts 折线图组件(V1 新增,复用于用户增长趋势与作业提交趋势两个卡片;V3 新增 labelKey prop 区分图表标签 + 空数据时渲染 EmptyState)" }, { "name": "StudentDashboard", "file": "student-dashboard/StudentDashboard", "purpose": "学生仪表盘(注意:非StudentDashboardView)" }, { "name": "StudentDashboardHeader", "file": "student-dashboard/StudentDashboardHeader", "purpose": "学生仪表盘头部(V3 从客户端组件转为服务端组件)" }, { "name": "StudentGradesCard", "file": "student-dashboard/StudentGradesCard", "purpose": "学生成绩卡片(V3 新增 useLocale 传入 formatDate 实现日期本地化)" }, { "name": "StudentStatsGrid", "file": "student-dashboard/StudentStatsGrid", "purpose": "学生统计网格" }, { "name": "StudentTodayScheduleCard", "file": "student-dashboard/StudentTodayScheduleCard", "purpose": "学生今日课表卡片(V3 空状态新增 CTA 跳转 /student/schedule)" }, { "name": "StudentUpcomingAssignmentsCard", "file": "student-dashboard/StudentUpcomingAssignmentsCard", "purpose": "学生即将到来作业卡片(V3 新增 getLocale 日期本地化 + 空状态 CTA 跳转 /student/learning/assignments)" }, { "name": "TeacherDashboardView", "file": "teacher-dashboard/TeacherDashboardView", "purpose": "教师仪表盘视图" }, { "name": "TeacherClassesCard", "file": "teacher-dashboard/TeacherClassesCard", "purpose": "教师班级卡片" }, { "name": "TeacherDashboardHeader", "file": "teacher-dashboard/TeacherDashboardHeader", "purpose": "教师仪表盘头部(V3 从客户端组件转为服务端组件)" }, { "name": "TeacherGradeTrends", "file": "teacher-dashboard/TeacherGradeTrends", "purpose": "教师年级趋势" }, { "name": "TeacherHomeworkCard", "file": "teacher-dashboard/TeacherHomeworkCard", "purpose": "教师作业卡片(V3 新增 getLocale 日期本地化 + aria-label 无障碍标签)" }, { "name": "TeacherQuickActions", "file": "teacher-dashboard/TeacherQuickActions", "purpose": "教师快捷操作(V3 从客户端组件转为异步服务端组件)" }, { "name": "TeacherSchedule", "file": "teacher-dashboard/TeacherSchedule", "purpose": "教师课表(V3 getStatus 函数新增显式返回类型)" }, { "name": "TeacherStats", "file": "teacher-dashboard/TeacherStats", "purpose": "教师统计(V3 移除未使用的 isLoading prop)" }, { "name": "RecentSubmissions", "file": "teacher-dashboard/RecentSubmissions", "purpose": "最近提交(V3 移除 AvatarImage 死代码 + 新增 getLocale 日期本地化)" }, { "name": "DashboardGreetingHeader", "file": "dashboard-greeting-header", "purpose": "共享问候头部组件(V2 抽象,消除 teacher/student 头部 90% 重复代码,接收 userName 和可选 actions slot;V3 从客户端组件转为异步服务端组件,使用 getTranslations/getLocale)" }, { "name": "DashboardLoadingSkeleton", "file": "dashboard-loading-skeleton", "purpose": "V3 新增:仪表盘共享骨架屏组件,消除 5 个 loading.tsx 路由文件的重复代码" }, { "name": "DashboardErrorFallback", "file": "dashboard-error-fallback", "purpose": "V3 新增:仪表盘共享错误边界组件,含 i18n + reset() 重试,消除 5 个 error.tsx 路由文件的重复代码" } ] } }, "layout": { "path": "src/modules/layout", "description": "应用布局框架:侧边栏、顶栏、导航配置", "exports": { "components": [ { "name": "AppSidebar", "purpose": "根据权限渲染侧边栏导航;N3 新增多角色切换:从 SidebarContext 读取 currentRole(null 时自动检测 admin>student>parent>teacher),用户拥有多个角色时在侧边栏底部显示 Select 下拉切换", "internalDeps": [ "usePermission", "NAV_CONFIG", "useSidebar", "shared/components/ui/select" ] }, { "name": "SiteHeader", "purpose": "顶部导航栏(集成 GlobalSearch 全局搜索:Cmd/Ctrl+K 唤起、300ms 防抖、↑/↓ 导航、Enter 跳转;NotificationDropdown 通知下拉)", "internalDeps": [ "useSession", "signOut", "shared/components/global-search.GlobalSearch", "messaging/components/notification-dropdown.NotificationDropdown" ] }, { "name": "SidebarProvider", "props": "{ children, sidebar }", "purpose": "侧边栏上下文Provider(N3 新增 currentRole/setCurrentRole 状态,null 表示自动检测角色)" }, { "name": "useSidebar", "type": "hook", "purpose": "侧边栏状态Hook(返回 expanded/isMobile/toggleSidebar/currentRole/setCurrentRole)" } ], "types": [ { "name": "Role", "type": "type", "definition": "\"admin\" | \"teacher\" | \"student\" | \"parent\"", "usedBy": [ "NAV_CONFIG", "usePermission" ] }, { "name": "NavItem", "type": "type", "definition": "{ title, href, icon?, permission? }", "usedBy": [ "NAV_CONFIG", "AppSidebar" ] } ], "config": [ { "name": "NAV_CONFIG", "type": "Record", "note": "每个NavItem含permission字段用于权限过滤。admin角色菜单包含Audit Logs项(icon: ScrollText, href: /admin/audit-logs, permission: AUDIT_LOG_READ),含子项Operation Logs与Login Logs。admin角色菜单的School Management子菜单包含Import Users项(href: /admin/users/import, permission: USER_MANAGE)。admin角色菜单包含Scheduling项(icon: CalendarClock, href: /admin/scheduling/rules, permission: SCHEDULE_ADJUST),含子项Rules(/admin/scheduling/rules, permission: SCHEDULE_ADJUST)、Auto Schedule(/admin/scheduling/auto, permission: SCHEDULE_AUTO)、Change Requests(/admin/scheduling/changes, permission: SCHEDULE_ADJUST)。teacher角色菜单包含Grades项(icon: GraduationCap, permission: GRADE_RECORD_READ),含子项All Grades(/teacher/grades)、Batch Entry(/teacher/grades/entry, permission: GRADE_RECORD_MANAGE)、Statistics(/teacher/grades/stats)。teacher角色菜单包含Schedule Changes项(icon: CalendarClock, href: /teacher/schedule-changes, permission: SCHEDULE_ADJUST)。teacher角色菜单包含Diagnostic项(icon: Stethoscope, href: /teacher/diagnostic, permission: DIAGNOSTIC_READ)。student角色菜单包含My Grades项(icon: GraduationCap, href: /student/grades, permission: GRADE_RECORD_READ)。student角色菜单包含Diagnostic项(icon: Stethoscope, href: /student/diagnostic, permission: DIAGNOSTIC_READ)。parent角色菜单包含Dashboard项(icon: LayoutDashboard, href: /parent/dashboard,无permission字段仅需登录)、Grades项(icon: GraduationCap, href: /parent/grades, permission: GRADE_RECORD_READ)、Announcements项(icon: Megaphone, href: /announcements, permission: ANNOUNCEMENT_READ)" } ] } }, "settings": { "path": "src/modules/settings", "description": "系统设置 + AI Provider配置 + 用户偏好 + 密码安全(修改密码、强度校验)。系统设置页 /admin/settings 提供学校信息、安全策略、文件上传、通知配置等运行参数管理", "exports": { "actions": [ { "name": "getAiProviderSummaries", "permission": "AI_CONFIGURE", "signature": "() => Promise>", "purpose": "获取AI Provider列表(P1 已修复:DB 操作下沉到 data-access.getAiProviderSummaries;v3 已修复:返回值统一为 ActionState)", "deps": [ "data-access.getAiProviderSummaries" ] }, { "name": "upsertAiProviderAction", "permission": "AI_CONFIGURE", "signature": "(data) => Promise>", "purpose": "创建/更新AI Provider(P1 已修复:DB 操作下沉到 data-access,并行查询优化)", "deps": [ "shared/lib/ai (encrypt/decrypt)", "data-access.countDefaultAiProviders", "data-access.getAiProviderForUpdate", "data-access.updateAiProvider", "data-access.createAiProvider" ] }, { "name": "testAiProviderAction", "permission": "AI_CONFIGURE", "signature": "(data) => Promise>", "purpose": "测试AI Provider连通性", "deps": [ "shared/lib/ai.testAiProviderConfig" ] }, { "name": "changePasswordAction", "file": "actions-password.ts", "permission": "USER_PROFILE_UPDATE", "signature": "(prevState: ActionState, formData: FormData) => Promise>", "purpose": "修改当前用户密码(Zod 校验 + 校验当前密码 + 新密码策略 + 速率限制 PASSWORD_CHANGE: 5次/分钟 + 并行查询优化)", "deps": [ "requirePermission", "validatePassword", "rateLimit", "bcryptjs (hash/compare)", "data-access.getUserPasswordHash", "data-access.getPasswordSecurityByUserId", "data-access.updateUserPassword", "data-access.upsertPasswordSecurityOnPasswordChange" ], "usedBy": [ "components/password-change-form.tsx" ] }, { "name": "updateUserAvatarAction", "file": "actions-avatar.ts", "permission": "USER_PROFILE_UPDATE", "signature": "(imageUrl: string) => Promise>", "purpose": "更新用户头像 URL(P2-8 新增;v2 已增强:更新成功后清理旧头像文件,包括磁盘文件和 DB 记录)", "deps": [ "requirePermission", "users/data-access.getUserProfile", "users/data-access.updateUserAvatar", "files/data-access.getFileByUrl", "files/data-access.deleteFileAttachment" ], "usedBy": [ "components/avatar-upload.tsx" ] }, { "name": "removeUserAvatarAction", "file": "actions-avatar.ts", "permission": "USER_PROFILE_UPDATE", "signature": "() => Promise>", "purpose": "移除用户头像(P2-8 新增;v2 已增强:同时清理旧头像文件)", "deps": [ "requirePermission", "users/data-access.getUserProfile", "users/data-access.updateUserAvatar", "files/data-access.getFileByUrl", "files/data-access.deleteFileAttachment" ], "usedBy": [ "components/avatar-upload.tsx" ] }, { "name": "sendTestNotificationAction", "file": "actions-notifications.ts", "permission": "USER_PROFILE_UPDATE", "signature": "(input: { channel: 'push' | 'email' | 'sms' }) => Promise>", "purpose": "发送测试通知(P2-10 新增;v2 已增强:接入 notifications/dispatcher.sendNotification 真实发送)", "deps": [ "requirePermission", "notifications/dispatcher.sendNotification" ], "usedBy": [ "components/notification-preferences-form.tsx" ] }, { "name": "getAdminSystemSettingsAction", "file": "actions-system-settings.ts", "permission": "SETTINGS_ADMIN", "signature": "() => Promise>", "purpose": "获取管理员系统设置(P0-3 新增:从 system_settings 表加载 4 个分类配置)", "deps": [ "requirePermission", "data-access-system-settings.getAllSystemSettings" ], "usedBy": [ "components/admin-settings-view.tsx" ] }, { "name": "saveAdminSystemSettingsAction", "file": "actions-system-settings.ts", "permission": "SETTINGS_ADMIN", "signature": "(values: AdminSettingsFormValues) => Promise>", "purpose": "保存管理员系统设置(P0-3 新增:4 分类 Zod 校验 + 批量 upsert)", "deps": [ "requirePermission", "data-access-system-settings.upsertSystemSettings" ], "usedBy": [ "components/admin-settings-view.tsx" ] }, { "name": "getSecurityCenterAction", "file": "actions-security.ts", "permission": "USER_PROFILE_UPDATE", "signature": "() => Promise>", "purpose": "获取安全中心数据(P2-9 新增:2FA 状态 + 最近 10 条登录历史;v2 已优化:使用 getSystemSettingsByCategory 一次查询替代 3 次单独查询)", "deps": [ "requirePermission", "data-access-system-settings.getSystemSettingsByCategory", "shared.db.schema.loginLogs" ], "usedBy": [ "components/security-center-card.tsx" ] }, { "name": "toggleTwoFactorAction", "file": "actions-security.ts", "permission": "USER_PROFILE_UPDATE", "signature": "(enabled: boolean) => Promise>", "purpose": "启用/禁用 2FA(P2-9 新增:占位实现;v2 已禁用开关,显示'即将推出'提示,避免虚假安全感)", "deps": [ "requirePermission", "data-access-system-settings.upsertSystemSetting" ], "usedBy": [ "components/security-center-card.tsx" ] }, { "name": "revokeAllOtherSessionsAction", "file": "actions-security.ts", "permission": "USER_PROFILE_UPDATE", "signature": "() => Promise>", "purpose": "远程登出当前用户的所有其他会话(v2 新增:删除 sessions 表记录 + 记录安全处置日志;注意当前为 JWT 策略,sessions 表可能无活跃记录)", "deps": [ "requirePermission", "users/data-access.getUserProfile", "shared.db.schema.sessions", "shared.lib.login-logger.logLoginEvent" ], "usedBy": [ "components/security-center-card.tsx" ] }, { "name": "updateProfileAction", "file": "actions-service.ts", "permission": "USER_PROFILE_UPDATE", "signature": "(input: UpdateUserProfileInput) => Promise>", "purpose": "设置页个人资料更新 Server Action wrapper(v1.1 新增:委托给 users/actions.updateUserProfile,作为 Server Action 引用传递给 Client Component,避免 Server→Client 函数传递违规)", "deps": [ "users/actions.updateUserProfile" ], "usedBy": [ "app/(dashboard)/settings/page.tsx" ] }, { "name": "updateNotificationPreferencesAction", "file": "actions-service.ts", "permission": "MESSAGE_READ", "signature": "(input: UpdateNotificationPreferencesInput) => Promise>", "purpose": "设置页通知偏好更新 Server Action wrapper(v1.1 新增:直接调用 notifications/preferences.upsertNotificationPreferences,绕过 messaging/actions 的 FormData 签名,适配 SettingsService 接口)", "deps": [ "requirePermission", "notifications/preferences.upsertNotificationPreferences" ], "usedBy": [ "app/(dashboard)/settings/page.tsx" ] } ], "dataAccess": [ { "name": "getAiProviderSummaries", "signature": "() => Promise", "file": "data-access.ts", "purpose": "获取AI Provider列表(P1 新增,从 actions 下沉)", "deps": [ "shared.db", "shared.db.schema.aiProviders" ] }, { "name": "countDefaultAiProviders", "signature": "() => Promise", "file": "data-access.ts", "purpose": "统计默认AI Provider数量(P1 新增)", "deps": [ "shared.db", "shared.db.schema.aiProviders" ] }, { "name": "getAiProviderForUpdate", "signature": "(id: string) => Promise", "file": "data-access.ts", "purpose": "获取AI Provider更新所需字段(P1 新增)", "deps": [ "shared.db", "shared.db.schema.aiProviders" ] }, { "name": "updateAiProvider", "signature": "(id: string, data: UpdateAiProviderInput, resetOtherDefaults: boolean) => Promise", "file": "data-access.ts", "purpose": "更新AI Provider(事务,P1 新增)", "deps": [ "shared.db", "shared.db.schema.aiProviders" ] }, { "name": "createAiProvider", "signature": "(data: CreateAiProviderInput, resetOtherDefaults: boolean) => Promise", "file": "data-access.ts", "purpose": "创建AI Provider(事务,P1 新增)", "deps": [ "shared.db", "shared.db.schema.aiProviders" ] }, { "name": "getUserPasswordHash", "signature": "(userId: string) => Promise<{ password: string | null } | null>", "file": "data-access.ts", "purpose": "获取用户密码哈希(P1 新增,从 actions-password 下沉)", "deps": [ "shared.db", "shared.db.schema.users" ] }, { "name": "getPasswordSecurityByUserId", "signature": "(userId: string) => Promise<{ id: string } | null>", "file": "data-access.ts", "purpose": "获取用户密码安全记录(P1 新增,从 actions-password 下沉)", "deps": [ "shared.db", "shared.db.schema.passwordSecurity" ] }, { "name": "updateUserPassword", "signature": "(userId: string, newHash: string, now: Date) => Promise", "file": "data-access.ts", "purpose": "更新用户密码(P1 新增,从 actions-password 下沉)", "deps": [ "shared.db", "shared.db.schema.users" ] }, { "name": "upsertPasswordSecurityOnPasswordChange", "signature": "(userId: string, now: Date, existing: { id: string } | null) => Promise", "file": "data-access.ts", "purpose": "密码修改后更新或创建密码安全记录(P1 新增,从 actions-password 下沉)", "deps": [ "shared.db", "shared.db.schema.passwordSecurity" ] }, { "name": "getSystemSettingsByCategory", "signature": "(category: SystemSettingCategory) => Promise", "file": "data-access-system-settings.ts", "purpose": "获取指定分类下所有设置项(P0-3 新增)", "deps": [ "shared.db", "shared.db.schema.systemSettings" ] }, { "name": "getAllSystemSettings", "signature": "() => Promise", "file": "data-access-system-settings.ts", "purpose": "获取所有系统设置项(P0-3 新增)", "deps": [ "shared.db", "shared.db.schema.systemSettings" ] }, { "name": "getSystemSetting", "signature": "(category: SystemSettingCategory, key: string) => Promise", "file": "data-access-system-settings.ts", "purpose": "获取单个设置项(P0-3 新增)", "deps": [ "shared.db", "shared.db.schema.systemSettings" ] }, { "name": "upsertSystemSetting", "signature": "(params: { category, key, value, valueType, updatedBy? }) => Promise", "file": "data-access-system-settings.ts", "purpose": "插入或更新设置项(P0-3 新增)", "deps": [ "shared.db", "shared.db.schema.systemSettings" ] }, { "name": "upsertSystemSettings", "signature": "(items: Array<{ category, key, value, valueType }>, updatedBy?: string) => Promise", "file": "data-access-system-settings.ts", "purpose": "批量 upsert 设置项(P0-3 新增)", "deps": [ "shared.db", "shared.db.schema.systemSettings" ] } ], "types": [ { "name": "AiProviderSummary", "file": "types.ts", "type": "interface", "definition": "AI Provider摘要(P1 从 actions.ts 迁至 types.ts)", "usedBy": [ "getAiProviderSummaries", "settings/components", "exams/components" ] }, { "name": "AiProviderName", "file": "types.ts", "type": "type", "definition": "AI Provider名称联合类型(P1 新增)", "usedBy": [ "data-access", "types.AiProviderSummary" ] }, { "name": "AiProviderExisting", "file": "types.ts", "type": "interface", "definition": "AI Provider更新所需字段(P1 新增)", "usedBy": [ "data-access.getAiProviderForUpdate" ] }, { "name": "SettingsService", "file": "types.ts", "type": "interface", "definition": "设置模块统一服务接口(profile + notifications + trackEvent),通过 React Context 注入实现解耦", "usedBy": [ "components/settings-service-context.tsx", "app/(dashboard)/settings/page.tsx" ] }, { "name": "ProfileService", "file": "types.ts", "type": "interface", "definition": "个人资料服务接口(getProfile + updateProfile),解耦组件对 users/actions 的直接依赖", "usedBy": [ "types.SettingsService", "components/profile-settings-form.tsx" ] }, { "name": "NotificationPreferenceService", "file": "types.ts", "type": "interface", "definition": "通知偏好服务接口(getPreferences + updatePreferences),解耦组件对 messaging/actions 的直接依赖", "usedBy": [ "types.SettingsService", "components/notification-preferences-form.tsx" ] } ], "components": [ { "name": "AiProviderSettingsCard", "purpose": "AI Provider设置卡片(i18n:settings.ai.providers)" }, { "name": "AdminSettingsView", "purpose": "系统设置视图(P0-3 已修复:从 mock 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表;4 个 Card:学校信息/安全策略/文件上传/通知配置;i18n:settings.admin)", "deps": [ "getAdminSystemSettingsAction", "saveAdminSystemSettingsAction" ], "usedBy": [ "app/(dashboard)/admin/settings/page.tsx" ] }, { "name": "AvatarUpload", "file": "components/avatar-upload.tsx", "purpose": "头像上传/预览/删除客户端组件(P2-8 新增;v2 已增强:文件名长度校验 ≤255 字符;文件通过 /api/upload 上传,调用 updateUserAvatarAction 更新 users.image;验证 JPEG/PNG/WebP/GIF + 2MB 上限;i18n:settings.profile.avatar)", "deps": [ "updateUserAvatarAction", "removeUserAvatarAction" ], "usedBy": [ "app/(dashboard)/profile/page.tsx" ] }, { "name": "SecurityCenterCard", "file": "components/security-center-card.tsx", "purpose": "安全中心卡片(P2-9 新增;v2 已增强:2FA 开关改为禁用状态显示'即将推出'、新增'登出所有其他会话'按钮、通过 currentDeviceLabel 标记当前会话、纯函数抽取到 lib/security-utils.ts;2FA 状态存储在 system_settings 表;登录历史来自 login_logs 表;i18n:settings.security.center)", "deps": [ "getSecurityCenterAction", "toggleTwoFactorAction", "revokeAllOtherSessionsAction", "lib/security-utils.parseUserAgent", "lib/security-utils.formatRelativeTime" ], "usedBy": [ "components/settings-view.tsx" ] }, { "name": "ProfileSettingsForm", "purpose": "个人资料设置表单(通过 useSettingsService().profile.updateProfile 调用,i18n:settings.profile)", "deps": [ "useSettingsService", "shared/components/form-fields/text-field", "shared/components/form-fields/select-field" ] }, { "name": "ThemePreferencesCard", "purpose": "主题偏好卡片(i18n:settings.appearance;P2-11 已增强:集成 LocaleSwitcher 语言切换到 Appearance 标签页)", "deps": [ "shared/components/locale-switcher" ] }, { "name": "SettingsView", "purpose": "统一设置页布局(5 标签页:General/Notifications/Appearance/Security/AI;角色差异通过 resolveRoleSettingsConfig 配置驱动 + generalExtra props 注入;Tab URL 持久化;每个 TabsContent 包裹 SettingsSectionErrorBoundary + Suspense 骨架屏;AI 标签页条件渲染需 AI_CONFIGURE 权限;登出 AlertDialog 二次确认;i18n:settings 命名空间)", "usedBy": [ "app/(dashboard)/settings/page.tsx" ] }, { "name": "SettingsServiceProvider", "file": "components/settings-service-context.tsx", "purpose": "SettingsService React Context Provider,页面层注入服务实现,组件层通过 useSettingsService() 消费", "usedBy": [ "app/(dashboard)/settings/page.tsx" ] }, { "name": "SettingsSectionErrorBoundary", "file": "components/settings-section-error-boundary.tsx", "purpose": "分区 Error Boundary,包裹每个 TabsContent 和 profile 角色概览区块,局部失败不影响整页", "usedBy": [ "components/settings-view.tsx", "app/(dashboard)/profile/page.tsx" ] }, { "name": "QuickLinksCard", "file": "components/quick-links-card.tsx", "purpose": "快捷链接卡片(客户端组件,i18n 键驱动:settings.quickLinks)", "usedBy": [ "config/role-settings-config.tsx" ] }, { "name": "ProfileStudentOverview", "file": "components/profile-student-overview.tsx", "purpose": "学生概览异步 Server Component,独立获取学生数据并渲染(StatsGrid + UpcomingAssignments + Grades + TodaySchedule),可被 Suspense + ErrorBoundary 包裹实现流式渲染", "deps": [ "classes/data-access.getStudentClasses", "classes/data-access.getStudentSchedule", "homework/data-access.getStudentHomeworkAssignments", "homework/data-access.getStudentDashboardGrades", "lib/student-overview-data.buildStudentOverviewData" ], "usedBy": [ "app/(dashboard)/profile/page.tsx" ] }, { "name": "ProfileTeacherOverview", "file": "components/profile-teacher-overview.tsx", "purpose": "教师概览异步 Server Component,独立获取教师数据并渲染(任教科目 + 任教班级),可被 Suspense + ErrorBoundary 包裹", "deps": [ "classes/data-access.getTeacherClasses", "classes/data-access.getTeacherTeachingSubjects" ], "usedBy": [ "app/(dashboard)/profile/page.tsx" ] }, { "name": "PasswordChangeForm", "purpose": "密码修改表单(当前密码/新密码/确认密码 + 强度指示器 + 需求提示;i18n:settings.security;a11y:aria-label)", "deps": [ "changePasswordAction", "getPasswordStrength", "PASSWORD_REQUIREMENT_HINTS" ] }, { "name": "NotificationPreferencesForm", "file": "components/notification-preferences-form.tsx", "purpose": "通知偏好设置表单(Switch 切换 email/sms/push 通道 + 5 个分类开关;通过 useSettingsService().notifications.updatePreferences 调用,i18n:settings.notifications;P2-10 已增强:每个已启用渠道旁显示测试按钮,调用 sendTestNotificationAction)", "deps": [ "useSettingsService", "shared/components/ui/switch", "shared/components/ui/card" ], "usedBy": [ "SettingsView" ] } ] } }, "users": { "path": "src/modules/users", "description": "用户个人资料管理 + 用户批量导入/导出(Excel)", "exports": { "actions": [ { "name": "updateUserProfile", "signature": "(data: UpdateUserProfileInput) => Promise", "file": "actions.ts", "permission": "requireAuth()", "deps": [ "shared.db", "shared.db.schema.users", "shared.lib.auth-guard.requireAuth" ] }, { "name": "UpdateUserProfileInput", "type": "type", "file": "actions.ts", "definition": "{ name?, phone?, address?, gender?, age? }" }, { "name": "downloadUserTemplateAction", "signature": "() => Promise>", "file": "actions.ts", "permission": "USER_MANAGE", "purpose": "生成用户导入模板(返回 base64 编码的 Excel)", "deps": [ "requirePermission", "import-export.generateUserImportTemplate" ], "usedBy": [ "components/user-import-dialog.tsx" ] }, { "name": "importUsersAction", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "permission": "USER_MANAGE", "purpose": "导入用户:接收文件,解析+验证+批量创建(默认密码 123456)", "deps": [ "requirePermission", "shared.lib.excel.parseExcel", "import-export.parseUserImportData", "import-export.batchImportUsers" ], "usedBy": [ "components/user-import-dialog.tsx" ] }, { "name": "exportUsersAction", "signature": "(role?: string) => Promise>", "file": "actions.ts", "permission": "USER_MANAGE", "purpose": "导出用户列表(返回 base64 编码的 Excel)", "deps": [ "requirePermission", "import-export.exportUsersToExcel" ], "usedBy": [ "待扩展" ] }, { "name": "updateUserRoleAction", "signature": "(prevState: ActionState, formData: FormData) => Promise>", "file": "actions.ts", "permission": "USER_MANAGE", "purpose": "更新用户角色(简化实现,预留扩展)", "deps": [ "requirePermission" ], "usedBy": [ "待扩展" ] }, { "name": "deleteUserAction", "signature": "(prevState: ActionState, formData: FormData) => Promise>", "file": "actions.ts", "permission": "USER_MANAGE", "purpose": "删除用户(按 userId 物理删除 users 表记录,revalidatePath /admin/users)", "deps": [ "requirePermission", "shared.db", "shared.db.schema.users" ], "usedBy": [ "待扩展" ] } ], "dataAccess": [ { "name": "getUserProfile", "signature": "(userId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.users" ] }, { "name": "getUsersDashboardStats", "signature": "() => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.users", "shared.db.schema.sessions", "shared.db.schema.usersToRoles", "shared.db.schema.roles" ], "usedBy": [ "dashboard/data-access.getAdminDashboardData" ] }, { "name": "getCurrentStudentUser", "signature": "() => Promise<{ id: string; name: string } | null>", "file": "data-access.ts", "purpose": "获取当前已认证的学生用户(id + name),通过 session + JOIN users/usersToRoles/roles 校验 student 角色", "deps": [ "auth", "data-access.getUserWithRole" ], "usedBy": [ "student/dashboard", "student/learning/assignments", "student/learning/assignments/[assignmentId]", "student/learning/courses", "student/learning/textbooks", "student/learning/textbooks/[id]", "student/schedule" ] }, { "name": "UserProfile", "type": "type", "file": "data-access.ts", "definition": "{ id, name, email, image, role, phone, address, gender, age, onboardedAt, createdAt, updatedAt }" }, { "name": "UsersDashboardStats", "type": "type", "file": "data-access.ts", "definition": "{ userCount, activeSessionsCount, userRoleCounts, recentUsers }" }, { "name": "getAdminUsers", "signature": "(params: { page?, pageSize?, search?, role? }) => Promise", "file": "data-access.ts", "purpose": "管理员用户列表分页查询(支持按姓名/邮箱搜索,按 createdAt 倒序,关联 usersToRoles/roles 聚合角色名)", "deps": [ "shared.db", "shared.db.schema.users", "shared.db.schema.usersToRoles", "shared.db.schema.roles" ], "usedBy": [ "app/(dashboard)/admin/users/page.tsx" ] }, { "name": "getAdminUserRoles", "signature": "() => Promise", "file": "data-access.ts", "purpose": "返回所有角色名列表(用于用户管理页角色筛选下拉框)", "deps": [ "shared.db", "shared.db.schema.roles" ], "usedBy": [ "app/(dashboard)/admin/users/page.tsx" ] }, { "name": "AdminUserListItem", "type": "type", "file": "data-access.ts", "definition": "{ id, name, email, roles: string[], phone, createdAt }" }, { "name": "AdminUserListResult", "type": "type", "file": "data-access.ts", "definition": "{ items: AdminUserListItem[], total, page, pageSize, totalPages }" } ], "importExport": [ { "name": "generateUserImportTemplate", "signature": "() => Promise", "file": "import-export.ts", "purpose": "生成用户导入模板(列:姓名/邮箱/角色/手机/班级邀请码,含示例行)", "deps": [ "shared.lib.excel.generateTemplate" ], "usedBy": [ "actions.downloadUserTemplateAction" ] }, { "name": "parseUserImportData", "signature": "(rows: Record[]) => UserImportValidation", "file": "import-export.ts", "purpose": "解析并验证导入行(校验姓名/邮箱格式/角色枚举/邀请码仅 student)", "deps": [], "usedBy": [ "actions.importUsersAction" ] }, { "name": "exportUsersToExcel", "signature": "(params: { scope: DataScope; role?: string }) => Promise", "file": "import-export.ts", "purpose": "导出用户列表到 Excel(含姓名/邮箱/手机/性别/年龄/角色/创建时间)", "deps": [ "shared.db", "shared.db.schema.users", "shared.db.schema.roles", "shared.db.schema.usersToRoles", "shared.lib.excel.exportToExcel" ], "usedBy": [ "actions.exportUsersAction", "app/api/export/route.ts" ] } ], "userService": [ { "name": "batchImportUsers", "signature": "(records: UserImportRecord[]) => Promise", "file": "user-service.ts", "purpose": "批量创建用户(默认密码 123456 bcrypt 哈希,自动创建 usersToRoles,student 通过邀请码自动加入班级——委托 class-registration)", "deps": [ "shared.db", "shared.db.schema.users", "shared.db.schema.roles", "shared.db.schema.usersToRoles", "bcryptjs", "@paralleldrive/cuid2", "class-registration.registerStudentByInvitationCode" ], "usedBy": [ "actions.importUsersAction", "import-export.ts (re-export 向后兼容)" ] } ], "classRegistration": [ { "name": "registerStudentByInvitationCode", "signature": "(studentId: string, invitationCode: string) => Promise", "file": "class-registration.ts", "purpose": "通过邀请码将学生注册到班级,委托 classes/data-access.enrollStudentByInvitationCode,返回结构化结果(不抛异常)", "deps": [ "classes/data-access.enrollStudentByInvitationCode" ], "usedBy": [ "user-service.batchImportUsers" ] }, { "name": "ClassRegistrationResult", "type": "type", "file": "class-registration.ts", "definition": "{ success: boolean; error?: string }" } ], "types": [ { "name": "UserImportRecord", "type": "type", "file": "import-export.ts", "definition": "{ name, email, role, phone?, invitationCode? }", "usedBy": [ "parseUserImportData", "batchImportUsers" ] }, { "name": "UserImportValidation", "type": "type", "file": "import-export.ts", "definition": "{ valid: UserImportRecord[], invalid: Array<{ row, record, errors }> }", "usedBy": [ "parseUserImportData" ] }, { "name": "UserImportResult", "type": "type", "file": "user-service.ts", "definition": "{ successCount, failedCount, errors: Array<{ row, email, error }> }", "usedBy": [ "batchImportUsers", "importUsersAction", "import-export.ts (re-export 向后兼容)" ] } ], "components": [ { "name": "UserImportDialog", "file": "components/user-import-dialog.tsx", "purpose": "用户批量导入对话框(4 状态:idle/preview/importing/done;下载模板→上传预览→确认导入→结果展示)", "usedBy": [ "app/(dashboard)/admin/users/import/page.tsx" ] }, { "name": "AdminUsersView", "file": "components/admin-users-view.tsx", "purpose": "管理员用户列表客户端组件(搜索+角色筛选+表格+分页+删除确认对话框,通过 URL searchParams 驱动筛选状态)", "usedBy": [ "app/(dashboard)/admin/users/page.tsx" ] } ] } }, "audit": { "path": "src/modules/audit", "description": "操作日志、登录日志与数据变更日志查询,支持 Excel 导出", "exports": { "dataAccess": [ { "name": "getAuditLogs", "signature": "(params?: AuditLogQueryParams) => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.auditLogs" ], "usedBy": [ "app/(dashboard)/admin/audit-logs/page.tsx" ] }, { "name": "getLoginLogs", "signature": "(params?: LoginLogQueryParams) => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.loginLogs" ], "usedBy": [ "app/(dashboard)/admin/audit-logs/login-logs/page.tsx" ] }, { "name": "getAuditModuleOptions", "signature": "() => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.auditLogs" ], "usedBy": [ "app/(dashboard)/admin/audit-logs/page.tsx" ] }, { "name": "getDataChangeLogs", "signature": "(params?: DataChangeLogQueryParams) => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.dataChangeLogs" ], "usedBy": [ "getDataChangeLogsAction" ] }, { "name": "getDataChangeStats", "signature": "() => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.dataChangeLogs" ], "usedBy": [ "getDataChangeLogsAction" ] }, { "name": "getDataChangeTableOptions", "signature": "() => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.dataChangeLogs" ], "usedBy": [ "getDataChangeLogsAction" ] }, { "name": "getDataChangeLogsForExport", "signature": "(params?: DataChangeLogQueryParams) => Promise", "file": "data-access.ts", "deps": [ "getDataChangeLogs" ], "usedBy": [ "exportDataChangeLogsAction" ] }, { "name": "getAuditLogsForExport", "signature": "(params?: AuditLogQueryParams) => Promise", "file": "data-access.ts", "deps": [ "getAuditLogs" ], "usedBy": [ "exportAuditLogsAction" ] }, { "name": "getLoginLogsForExport", "signature": "(params?: LoginLogQueryParams) => Promise", "file": "data-access.ts", "deps": [ "getLoginLogs" ], "usedBy": [ "exportLoginLogsAction" ] } ], "actions": [ { "name": "getDataChangeLogsAction", "permission": "AUDIT_LOG_READ", "signature": "(params?: DataChangeLogQueryParams) => Promise>", "file": "actions.ts", "purpose": "获取数据变更日志(分页结果 + tableOptions + stats 三者并行加载)", "deps": [ "requirePermission", "data-access.getDataChangeLogs", "data-access.getDataChangeTableOptions", "data-access.getDataChangeStats" ], "usedBy": [ "待扩展" ] }, { "name": "exportAuditLogsAction", "permission": "AUDIT_LOG_READ", "signature": "(params?: AuditLogQueryParams) => Promise>", "file": "actions.ts", "purpose": "导出操作日志为 Excel", "deps": [ "requirePermission", "data-access.getAuditLogsForExport", "shared.lib.excel.exportToExcel" ], "usedBy": [ "待扩展" ] }, { "name": "exportLoginLogsAction", "permission": "AUDIT_LOG_READ", "signature": "(params?: LoginLogQueryParams) => Promise>", "file": "actions.ts", "purpose": "导出登录日志为 Excel", "deps": [ "requirePermission", "data-access.getLoginLogsForExport", "shared.lib.excel.exportToExcel" ], "usedBy": [ "待扩展" ] }, { "name": "exportDataChangeLogsAction", "permission": "AUDIT_LOG_READ", "signature": "(params?: DataChangeLogQueryParams) => Promise>", "file": "actions.ts", "purpose": "导出数据变更日志为 Excel", "deps": [ "requirePermission", "data-access.getDataChangeLogsForExport", "shared.lib.excel.exportToExcel" ], "usedBy": [ "待扩展" ] } ], "types": [ { "name": "AuditLog", "type": "interface", "file": "types.ts", "definition": "{ id, userId, userName, action, module, targetId, targetType, detail, ipAddress, userAgent, status, createdAt }" }, { "name": "LoginLog", "type": "interface", "file": "types.ts", "definition": "{ id, userId, userEmail, action, status, ipAddress, userAgent, errorMessage, createdAt }" }, { "name": "AuditLogQueryParams", "type": "type", "file": "types.ts", "definition": "{ userId?, module?, action?, status?, page?, pageSize?, startDate?, endDate? }" }, { "name": "LoginLogQueryParams", "type": "type", "file": "types.ts", "definition": "{ userId?, action?, status?, page?, pageSize?, startDate?, endDate? }" }, { "name": "PaginatedResult", "type": "interface", "file": "types.ts", "definition": "{ items: T[], total, page, pageSize, totalPages }" }, { "name": "DataChangeAction", "type": "type", "file": "types.ts", "definition": "'create' | 'update' | 'delete'", "usedBy": [ "data-access", "shared/lib/change-logger", "DataChangeLog" ] }, { "name": "DataChangeLog", "type": "interface", "file": "types.ts", "definition": "{ id, tableName, recordId, action, oldValue, newValue, changedBy, changedByName, ipAddress, createdAt }", "usedBy": [ "audit/data-access", "audit/actions" ] }, { "name": "DataChangeStat", "type": "interface", "file": "types.ts", "definition": "{ tableName: string, count: number }", "usedBy": [ "getDataChangeStats", "getDataChangeLogsAction" ] }, { "name": "DataChangeLogQueryParams", "type": "type", "file": "types.ts", "definition": "{ tableName?, recordId?, action?, userId?, page?, pageSize?, startDate?, endDate? }", "usedBy": [ "getDataChangeLogs", "getDataChangeLogsForExport", "getDataChangeLogsAction", "exportDataChangeLogsAction" ] } ], "components": [ { "name": "AuditLogTable", "file": "components/audit-log-table.tsx", "purpose": "操作日志表格(分页)" }, { "name": "AuditLogFilters", "file": "components/audit-log-filters.tsx", "purpose": "操作日志筛选器(模块/操作/状态/日期)" }, { "name": "AuditLogView", "file": "components/audit-log-view.tsx", "purpose": "操作日志视图(筛选+表格+分页)" }, { "name": "LoginLogTable", "file": "components/login-log-table.tsx", "purpose": "登录日志表格(分页)" }, { "name": "LoginLogFilters", "file": "components/login-log-filters.tsx", "purpose": "登录日志筛选器(操作/状态/日期)" }, { "name": "LoginLogView", "file": "components/login-log-view.tsx", "purpose": "登录日志视图(筛选+表格+分页)" } ] } }, "announcements": { "path": "src/modules/announcements", "description": "通知公告系统:创建、编辑、发布、归档、删除公告,所有登录用户可查看已发布公告", "exports": { "actions": [ { "name": "createAnnouncementAction", "permission": "ANNOUNCEMENT_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "创建公告(草稿/已发布);若直接发布则触发通知模块 sendBatchNotifications;V2-P0-2 新增通知标题 i18n 化,通过 getTranslations('announcements') 生成 notification.publishedTitle / publishedContent", "deps": [ "requirePermission", "data-access.insertAnnouncement", "data-access.getAnnouncementById", "notifications.sendBatchNotifications", "users.data-access.getAllUserIds", "users.data-access.getUserIdsByGradeId", "classes.data-access.getStudentIdsByClassId", "classes.data-access.getTeacherIdsByClassIds", "next-intl/server.getTranslations" ], "usedBy": [ "announcement-form.tsx" ] }, { "name": "updateAnnouncementAction", "permission": "ANNOUNCEMENT_MANAGE", "signature": "(id: string, prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "更新公告;若状态从非发布变为发布则触发通知模块 sendBatchNotifications;V2-P0-2 新增通知标题 i18n 化(同 createAnnouncementAction)", "deps": [ "requirePermission", "data-access.updateAnnouncementById", "data-access.getAnnouncementById", "notifications.sendBatchNotifications", "users.data-access.getAllUserIds", "users.data-access.getUserIdsByGradeId", "classes.data-access.getStudentIdsByClassId", "classes.data-access.getTeacherIdsByClassIds", "next-intl/server.getTranslations" ], "usedBy": [ "announcement-form.tsx" ] }, { "name": "deleteAnnouncementAction", "permission": "ANNOUNCEMENT_MANAGE", "signature": "(id: string) => Promise>", "purpose": "删除公告", "deps": [ "requirePermission", "data-access.deleteAnnouncementById" ], "usedBy": [ "announcement-detail.tsx" ] }, { "name": "publishAnnouncementAction", "permission": "ANNOUNCEMENT_MANAGE", "signature": "(id: string) => Promise>", "purpose": "发布公告(发布成功后触发通知模块 sendBatchNotifications);V2-P0-2 新增通知标题 i18n 化(同 createAnnouncementAction)", "deps": [ "requirePermission", "data-access.publishAnnouncementById", "data-access.getAnnouncementById", "notifications.sendBatchNotifications", "users.data-access.getAllUserIds", "users.data-access.getUserIdsByGradeId", "classes.data-access.getStudentIdsByClassId", "classes.data-access.getTeacherIdsByClassIds", "next-intl/server.getTranslations" ], "usedBy": [ "announcement-detail.tsx" ] }, { "name": "archiveAnnouncementAction", "permission": "ANNOUNCEMENT_MANAGE", "signature": "(id: string) => Promise>", "purpose": "归档公告", "deps": [ "requirePermission", "data-access.archiveAnnouncementById" ], "usedBy": [ "announcement-detail.tsx" ] }, { "name": "getAnnouncementsAction", "permission": "ANNOUNCEMENT_READ", "signature": "(params?: GetAnnouncementsParams) => Promise>", "purpose": "获取公告列表(所有登录用户可读)", "deps": [ "requirePermission", "data-access.getAnnouncements" ], "usedBy": [ "待扩展" ] } ], "dataAccess": [ { "name": "getAnnouncements", "signature": "(params?: { status?, type?, page?, pageSize?, audience?: { gradeId?: string; classId?: string } }) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.announcements" ], "usedBy": [ "admin/announcements/page.tsx", "announcements/page.tsx" ] }, { "name": "getAnnouncementById", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.announcements" ], "usedBy": [ "admin/announcements/[id]/page.tsx", "announcements/[id]/page.tsx", "publishAnnouncementAction", "updateAnnouncementAction", "deleteAnnouncementAction", "archiveAnnouncementAction" ] }, { "name": "insertAnnouncement", "signature": "(input: { title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt?, authorId }) => Promise<{ announcementId: string }>", "file": "data-access.ts", "purpose": "插入公告(P1-2 新增,从 actions 下沉)", "deps": [ "shared.db", "shared.db.schema.announcements" ], "usedBy": [ "createAnnouncementAction" ] }, { "name": "updateAnnouncementById", "signature": "(id: string, input: Partial<{ title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt? }>) => Promise", "file": "data-access.ts", "purpose": "按ID更新公告(P1-2 新增,从 actions 下沉)", "deps": [ "shared.db", "shared.db.schema.announcements" ], "usedBy": [ "updateAnnouncementAction" ] }, { "name": "deleteAnnouncementById", "signature": "(id: string) => Promise", "file": "data-access.ts", "purpose": "按ID删除公告(P1-2 新增,从 actions 下沉)", "deps": [ "shared.db", "shared.db.schema.announcements" ], "usedBy": [ "deleteAnnouncementAction" ] }, { "name": "publishAnnouncementById", "signature": "(id: string) => Promise", "file": "data-access.ts", "purpose": "发布公告(P1-2 新增,从 actions 下沉)", "deps": [ "shared.db", "shared.db.schema.announcements" ], "usedBy": [ "publishAnnouncementAction" ] }, { "name": "archiveAnnouncementById", "signature": "(id: string) => Promise", "file": "data-access.ts", "purpose": "归档公告(P1-2 新增,从 actions 下沉)", "deps": [ "shared.db", "shared.db.schema.announcements" ], "usedBy": [ "archiveAnnouncementAction" ] } ], "schemas": [ { "name": "CreateAnnouncementSchema", "type": "zod", "file": "schema.ts", "definition": "{ title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt? }", "usedBy": [ "createAnnouncementAction" ] }, { "name": "UpdateAnnouncementSchema", "type": "zod", "file": "schema.ts", "definition": "{ title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt? }", "usedBy": [ "updateAnnouncementAction" ] } ], "types": [ { "name": "Announcement", "type": "interface", "file": "types.ts", "definition": "{ id, title, content, type, status, targetGradeId, targetClassId, authorId, authorName, publishedAt, createdAt, updatedAt }", "usedBy": [ "announcements/components", "页面" ] }, { "name": "AnnouncementListItem", "type": "type", "file": "types.ts", "definition": "= Announcement", "usedBy": [ "列表页" ] }, { "name": "AnnouncementStatus", "type": "type", "file": "types.ts", "definition": "\"draft\" | \"published\" | \"archived\"", "usedBy": [ "data-access", "components" ] }, { "name": "AnnouncementType", "type": "type", "file": "types.ts", "definition": "\"school\" | \"grade\" | \"class\"", "usedBy": [ "data-access", "components" ] }, { "name": "GetAnnouncementsParams", "type": "interface", "file": "types.ts", "definition": "{ status?, type?, page?, pageSize? }", "usedBy": [ "getAnnouncements", "getAnnouncementsAction" ] } ], "components": [ { "name": "AnnouncementList", "file": "components/announcement-list.tsx", "purpose": "公告列表(支持状态筛选);V2-P1-1 优化:移除客户端 useState/useMemo 过滤,改为纯服务端过滤模式,Select 切换仅更新 URL ?status= 触发 RSC 重新渲染;V3:新增 detailHrefPrefix prop(字符串)替代 detailHrefBuilder 函数 prop,解决 Next.js 16 Server Component 不能向 Client Component 传递函数的序列化限制" }, { "name": "AnnouncementCard", "file": "components/announcement-card.tsx", "purpose": "单条公告卡片" }, { "name": "AnnouncementForm", "file": "components/announcement-form.tsx", "purpose": "创建/编辑表单;V2-P1-4 增强:fieldErrors 状态 + aria-invalid 字段级服务端校验错误展示(title/content/targetGradeId/targetClassId)" }, { "name": "AnnouncementDetail", "file": "components/announcement-detail.tsx", "purpose": "详情查看(含发布/归档/删除操作)" }, { "name": "AdminAnnouncementsView", "file": "components/admin-announcements-view.tsx", "purpose": "管理端公告视图(列表+创建对话框)" } ] } }, "files": { "path": "src/modules/files", "description": "文件上传与管理:通过 API 路由处理文件上传(保存到 public/uploads/YYYY-MM/),记录文件元数据到 DB,支持按关联资源(exam/textbook/question/announcement)多态查询、下载与删除", "exports": { "dataAccess": [ { "name": "createFileAttachment", "signature": "(data: CreateFileAttachmentInput) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.fileAttachments" ], "usedBy": [ "app/api/upload/route.ts" ] }, { "name": "getFileAttachment", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.fileAttachments" ], "usedBy": [ "app/api/files/[id]/route.ts" ] }, { "name": "getFileAttachmentsByTarget", "signature": "(targetType: string, targetId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.fileAttachments" ], "usedBy": [ "按关联资源查询文件列表" ] }, { "name": "getFileAttachmentsByUploader", "signature": "(uploaderId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.fileAttachments" ], "usedBy": [ "按上传者查询文件列表" ] }, { "name": "getAllFileAttachments", "signature": "(limit?: number) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.fileAttachments" ], "usedBy": [ "app/(dashboard)/admin/files/page.tsx" ] }, { "name": "deleteFileAttachment", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.fileAttachments" ], "usedBy": [ "app/api/files/[id]/route.ts" ] }, { "name": "deleteFileAttachments", "signature": "(ids: string[]) => Promise", "file": "data-access.ts", "purpose": "批量删除文件附件记录(仅删 DB 行,磁盘文件由调用方处理;失败时回退到逐条删除)", "deps": [ "shared.db", "shared.db.schema.fileAttachments", "drizzle-orm.inArray" ], "usedBy": [ "app/api/files/batch-delete/route.ts" ] }, { "name": "getFileAttachmentsWithFilters", "signature": "(params: FileAttachmentQueryParams) => Promise", "file": "data-access.ts", "purpose": "按 mimeType(精确或前缀匹配)与 search(originalName/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.SQL" ], "usedBy": [ "app/(dashboard)/admin/files/page.tsx" ] }, { "name": "getFileStats", "signature": "() => Promise", "file": "data-access.ts", "purpose": "获取文件统计(总数、总大小、按 mimeType 分组的 count/size)", "deps": [ "shared.db", "shared.db.schema.fileAttachments", "drizzle-orm.count", "drizzle-orm.sql" ], "usedBy": [ "app/(dashboard)/admin/files/page.tsx" ] }, { "name": "getFileAttachmentsByIds", "signature": "(ids: string[]) => Promise", "file": "data-access.ts", "purpose": "按 ID 列表批量查询文件(用于批量删除前获取磁盘路径)", "deps": [ "shared.db", "shared.db.schema.fileAttachments", "drizzle-orm.inArray" ], "usedBy": [ "app/api/files/batch-delete/route.ts" ] } ], "types": [ { "name": "FileAttachment", "type": "interface", "file": "types.ts", "definition": "{ id, filename, originalName, mimeType, size, storagePath, url, uploaderId, targetType, targetId, createdAt }", "usedBy": [ "files/components", "data-access", "API 路由" ] }, { "name": "FileUploadResult", "type": "interface", "file": "types.ts", "definition": "{ id, url, filename, originalName, size, mimeType }", "usedBy": [ "app/api/upload/route.ts 响应", "file-upload.tsx 回调" ] }, { "name": "FileTargetType", "type": "type", "file": "types.ts", "definition": "\"exam\" | \"textbook\" | \"question\" | \"announcement\"", "usedBy": [ "types.FileAttachment.targetType", "file-upload.tsx" ] }, { "name": "CreateFileAttachmentInput", "type": "interface", "file": "types.ts", "definition": "{ id, filename, originalName, mimeType, size, storagePath, url, uploaderId, targetType?, targetId? }", "usedBy": [ "createFileAttachment" ] }, { "name": "FileAttachmentQueryParams", "type": "interface", "file": "types.ts", "definition": "{ mimeType?, search?, limit?, offset? }", "usedBy": [ "getFileAttachmentsWithFilters" ] }, { "name": "FileStats", "type": "interface", "file": "types.ts", "definition": "{ totalCount, totalSize, byType: Array<{ mimeType, count, size }> }", "usedBy": [ "getFileStats" ] }, { "name": "BatchDeleteResult", "type": "interface", "file": "types.ts", "definition": "{ success, deletedCount, failedIds: string[] }", "usedBy": [ "deleteFileAttachments", "app/api/files/batch-delete/route.ts" ] } ], "components": [ { "name": "FileUpload", "file": "components/file-upload.tsx", "purpose": "文件上传组件(拖拽+点击上传,进度条,文件类型校验,调用 /api/upload)" }, { "name": "FileList", "file": "components/file-list.tsx", "purpose": "文件列表展示(图标、文件名、大小、下载链接、删除按钮)" }, { "name": "FilePreview", "file": "components/file-preview.tsx", "purpose": "文件预览(图片直接预览,PDF iframe,其他下载)" }, { "name": "FileIcon", "file": "components/file-icon.tsx", "purpose": "根据 MIME 类型显示不同图标与颜色" }, { "name": "AdminFilesView", "file": "components/admin-files-view.tsx", "purpose": "管理端文件视图(上传+列表+删除)" } ] } }, "grades": { "path": "src/modules/grades", "description": "成绩分析模块:成绩录入(单条+批量)、查询(按班级/科目/考试/学期过滤)、统计报表(均分、中位数、标准差、及格率、优秀率、排名)、Excel 导出(成绩明细+统计汇总/班级多科目横向对比)、趋势对比分析(成绩趋势、班级对比、科目对比、分数分布、排名趋势)", "exports": { "dataAccess": [ { "name": "getGradeRecords", "signature": "(params: GradeQueryParams & { scope: DataScope; currentUserId?: string; limit?: number; offset?: number }) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.classes", "shared.db.schema.classEnrollments", "shared.db.schema.subjects", "shared.db.schema.users" ], "usedBy": [ "grades/actions.getGradeRecordsAction" ] }, { "name": "getGradeRecordById", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords" ], "usedBy": [ "grades/actions.getGradeRecordByIdAction" ] }, { "name": "createGradeRecord", "signature": "(data: CreateGradeRecordInput, recordedBy: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords" ], "usedBy": [ "grades/actions.createGradeRecordAction" ] }, { "name": "batchCreateGradeRecords", "signature": "(data: BatchCreateGradeRecordInput, recordedBy: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords" ], "usedBy": [ "grades/actions.batchCreateGradeRecordsAction" ] }, { "name": "updateGradeRecord", "signature": "(id: string, data: UpdateGradeRecordInput) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords" ], "usedBy": [ "grades/actions.updateGradeRecordAction" ] }, { "name": "deleteGradeRecord", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords" ], "usedBy": [ "grades/actions.deleteGradeRecordAction" ] }, { "name": "getClassGradeStats", "signature": "(classId: string, subjectId?: string, examId?: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords" ], "usedBy": [ "grades/data-access.getClassGradeStatsWithMeta" ] }, { "name": "getClassGradeStatsWithMeta", "signature": "(classId: string, subjectId?: string, examId?: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.classes", "shared.db.schema.subjects" ], "usedBy": [ "grades/actions.getClassGradeStatsAction" ] }, { "name": "getStudentGradeSummary", "signature": "(studentId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.subjects" ], "usedBy": [ "grades/actions.getStudentGradeSummaryAction" ] }, { "name": "getClassRanking", "signature": "(classId: string, subjectId?: string, examId?: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.users" ], "usedBy": [ "grades/actions.getClassRankingAction" ] }, { "name": "getClassStudentsForEntry", "signature": "(classId: string) => Promise<{ id: string; name: string }[]>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.classEnrollments", "shared.db.schema.users" ], "usedBy": [ "grades/components/batch-grade-entry" ] }, { "name": "getGradeTrend", "signature": "(params: { studentId; subjectId?; semester?; scope: DataScope }) => Promise", "file": "data-access-analytics.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.classEnrollments" ], "usedBy": [ "grades/actions-analytics.getGradeTrendAction", "teacher/grades/analytics" ] }, { "name": "getClassComparison", "signature": "(params: { gradeId; subjectId; examId?; scope: DataScope }) => Promise", "file": "data-access-analytics.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.classes" ], "usedBy": [ "grades/actions-analytics.getClassComparisonAction", "teacher/grades/analytics" ] }, { "name": "getSubjectComparison", "signature": "(params: { classId; examId?; semester?; scope: DataScope }) => Promise", "file": "data-access-analytics.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.subjects" ], "usedBy": [ "grades/actions-analytics.getSubjectComparisonAction", "teacher/grades/analytics" ] }, { "name": "getGradeDistribution", "signature": "(params: { classId; subjectId?; examId?; scope: DataScope }) => Promise", "file": "data-access-analytics.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords" ], "usedBy": [ "grades/actions-analytics.getGradeDistributionAction", "teacher/grades/analytics" ] }, { "name": "getRankingTrend", "signature": "(studentId: string, subjectId?, semester?, scope?: DataScope) => Promise", "file": "data-access-ranking.ts", "deps": [ "shared.db", "shared.db.schema.gradeRecords", "shared.db.schema.classEnrollments" ], "usedBy": [ "grades/actions-analytics.getRankingTrendAction" ] } ], "actions": [ { "name": "createGradeRecordAction", "signature": "(prevState, formData) => Promise>", "file": "actions.ts", "permission": "GRADE_RECORD_MANAGE", "usedBy": [ "grades/components/grade-record-form" ] }, { "name": "batchCreateGradeRecordsAction", "signature": "(prevState, formData) => Promise>", "file": "actions.ts", "permission": "GRADE_RECORD_MANAGE", "usedBy": [ "grades/components/batch-grade-entry" ] }, { "name": "updateGradeRecordAction", "signature": "(prevState, formData) => Promise>", "file": "actions.ts", "permission": "GRADE_RECORD_MANAGE", "usedBy": [ "grades/components/grade-record-list" ] }, { "name": "deleteGradeRecordAction", "signature": "(prevState, formData) => Promise>", "file": "actions.ts", "permission": "GRADE_RECORD_MANAGE", "usedBy": [ "grades/components/grade-record-list" ] }, { "name": "getGradeRecordsAction", "signature": "(params) => Promise", "file": "actions.ts", "permission": "GRADE_RECORD_READ", "usedBy": [ "teacher/grades/page" ] }, { "name": "getClassGradeStatsAction", "signature": "(classId, subjectId?, examId?) => Promise", "file": "actions.ts", "permission": "GRADE_RECORD_READ", "usedBy": [ "teacher/grades/stats/page" ] }, { "name": "getStudentGradeSummaryAction", "signature": "(studentId?) => Promise", "file": "actions.ts", "permission": "GRADE_RECORD_READ", "usedBy": [ "student/grades/page", "parent/grades/page" ] }, { "name": "getClassRankingAction", "signature": "(classId, subjectId?, examId?) => Promise", "file": "actions.ts", "permission": "GRADE_RECORD_READ", "usedBy": [ "teacher/grades/stats/page" ] }, { "name": "getGradeRecordByIdAction", "signature": "(id) => Promise", "file": "actions.ts", "permission": "GRADE_RECORD_READ", "usedBy": [ "grades/components/grade-record-list" ] }, { "name": "exportGradesAction", "signature": "(params: { classId: string; subjectId?: string; examId?: string; reportType?: \"detail\" | \"class\" }) => Promise>", "file": "actions.ts", "permission": "GRADE_RECORD_READ", "purpose": "导出成绩到 Excel(detail=成绩明细+统计汇总,class=班级多科目横向对比总表),返回 base64 buffer", "deps": [ "requirePermission", "export.exportGradeRecordsToExcel", "export.exportClassGradeReportToExcel", "export.formatDateForFile" ], "usedBy": [ "grades/components/export-button.tsx" ] }, { "name": "getGradeTrendAction", "signature": "(params) => Promise", "file": "actions-analytics.ts", "permission": "GRADE_RECORD_READ", "purpose": "获取成绩趋势(按学生/科目/学期,返回归一化分数趋势点)", "usedBy": [ "teacher/grades/analytics" ] }, { "name": "getClassComparisonAction", "signature": "(params) => Promise", "file": "actions-analytics.ts", "permission": "GRADE_RECORD_READ", "purpose": "获取班级对比(同年级各班的均分/及格率/优秀率)", "usedBy": [ "teacher/grades/analytics" ] }, { "name": "getSubjectComparisonAction", "signature": "(params) => Promise", "file": "actions-analytics.ts", "permission": "GRADE_RECORD_READ", "purpose": "获取科目对比(同班级各科目雷达图数据)", "usedBy": [ "teacher/grades/analytics" ] }, { "name": "getGradeDistributionAction", "signature": "(params) => Promise", "file": "actions-analytics.ts", "permission": "GRADE_RECORD_READ", "purpose": "获取分数分布(90-100/80-89/70-79/60-69/<60 各区间人数)", "usedBy": [ "teacher/grades/analytics" ] }, { "name": "getRankingTrendAction", "signature": "(studentId, subjectId?, semester?) => Promise", "file": "actions-analytics.ts", "permission": "GRADE_RECORD_READ", "purpose": "获取排名趋势(学生历次考试排名变化,含 DataScope 二次校验)", "usedBy": [ "待扩展" ] }, { "name": "assertClassInScope", "signature": "(classId: string, ctx: AuthContext) => void", "file": "actions.ts", "permission": "internal", "purpose": "P3 新增:校验 classId 是否在 ctx.dataScope 允许范围内,不在则抛 BusinessError。供 actions.ts 与 actions-analytics.ts 复用", "usedBy": [ "grades/actions.getGradeRecordsAction", "grades/actions.getGradeRecordByIdAction", "grades/actions.exportGradesAction", "grades/actions-analytics.getGradeTrendAction", "grades/actions-analytics.getSubjectComparisonAction", "grades/actions-analytics.getGradeDistributionAction" ] } ], "schemas": [ { "name": "CreateGradeRecordSchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "createGradeRecordAction" ] }, { "name": "BatchCreateGradeRecordSchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "batchCreateGradeRecordsAction" ] }, { "name": "UpdateGradeRecordSchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "updateGradeRecordAction" ] }, { "name": "DeleteGradeRecordSchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "deleteGradeRecordAction" ] }, { "name": "GetGradeRecordByIdSchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getGradeRecordByIdAction" ] }, { "name": "GradeQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getGradeRecordsAction" ] }, { "name": "ClassGradeStatsQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getClassGradeStatsAction" ] }, { "name": "StudentGradeSummaryQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getStudentGradeSummaryAction" ] }, { "name": "ClassRankingQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getClassRankingAction" ] }, { "name": "ExportGradesSchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "exportGradesAction" ] }, { "name": "GradeTrendQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getGradeTrendAction" ] }, { "name": "ClassComparisonQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getClassComparisonAction" ] }, { "name": "SubjectComparisonQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getSubjectComparisonAction" ] }, { "name": "GradeDistributionQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getGradeDistributionAction" ] }, { "name": "RankingTrendQuerySchema", "type": "ZodSchema", "file": "schema.ts", "usedBy": [ "getRankingTrendAction" ] } ], "types": [ { "name": "GradeRecord", "type": "interface", "file": "types.ts", "usedBy": [ "data-access", "actions" ] }, { "name": "GradeRecordListItem", "type": "interface", "file": "types.ts", "usedBy": [ "data-access", "actions", "components/grade-record-list" ] }, { "name": "PaginatedGradeRecords", "type": "interface", "file": "data-access.ts", "definition": "{ records: GradeRecordListItem[]; total: number }", "usedBy": [ "data-access.getGradeRecords", "actions.getGradeRecordsAction", "teacher/grades/page", "export.exportGradeRecordsToExcel" ] }, { "name": "GradeStats", "type": "interface", "file": "types.ts", "definition": "{ count, average, median, stdDev, passRate, excellentRate, maxScore, minScore }", "usedBy": [ "data-access", "components/grade-stats-card" ] }, { "name": "ClassGradeStats", "type": "interface", "file": "types.ts", "usedBy": [ "data-access", "actions", "components/class-grade-report" ] }, { "name": "StudentGradeSummary", "type": "interface", "file": "types.ts", "usedBy": [ "data-access", "actions", "components/student-grade-summary" ] }, { "name": "ClassRankingItem", "type": "interface", "file": "types.ts", "usedBy": [ "data-access", "actions", "components/class-grade-report" ] }, { "name": "GradeRecordType", "type": "type", "file": "types.ts", "definition": "\"exam\" | \"quiz\" | \"assignment\" | \"monthly\" | \"midterm\" | \"final\"", "usedBy": [ "types.GradeRecord.type" ] }, { "name": "GradeRecordSemester", "type": "type", "file": "types.ts", "definition": "\"1\" | \"2\"", "usedBy": [ "types.GradeRecord.semester" ] }, { "name": "GradeQueryParams", "type": "interface", "file": "types.ts", "usedBy": [ "data-access.getGradeRecords" ] }, { "name": "GradeTrendPoint", "type": "interface", "file": "types.ts", "definition": "{ date, title, score, fullScore, normalizedScore, type }", "usedBy": [ "data-access-analytics.getGradeTrend", "grade-trend-chart" ] }, { "name": "GradeTrendResult", "type": "interface", "file": "types.ts", "definition": "{ label, points: GradeTrendPoint[], averageScore }", "usedBy": [ "data-access-analytics.getGradeTrend", "grade-trend-chart" ] }, { "name": "ClassComparisonItem", "type": "interface", "file": "types.ts", "definition": "{ classId, className, averageScore, passRate, excellentRate, studentCount }", "usedBy": [ "data-access-analytics.getClassComparison", "class-comparison-chart" ] }, { "name": "SubjectComparisonItem", "type": "interface", "file": "types.ts", "definition": "{ subjectId, subjectName, averageScore, passRate, excellentRate }", "usedBy": [ "data-access-analytics.getSubjectComparison", "subject-comparison-chart" ] }, { "name": "GradeDistributionBucket", "type": "interface", "file": "types.ts", "definition": "{ label, min, max, count, percentage }", "usedBy": [ "data-access-analytics.getGradeDistribution", "grade-distribution-chart" ] }, { "name": "GradeDistributionResult", "type": "interface", "file": "types.ts", "definition": "{ buckets: GradeDistributionBucket[], totalCount }", "usedBy": [ "data-access-analytics.getGradeDistribution", "grade-distribution-chart" ] }, { "name": "RankingTrendPoint", "type": "interface", "file": "types.ts", "definition": "{ title, date, rank, totalStudents, score }", "usedBy": [ "data-access-ranking.getRankingTrend" ] }, { "name": "RankingTrendResult", "type": "interface", "file": "types.ts", "definition": "{ studentName, points: RankingTrendPoint[] }", "usedBy": [ "data-access-ranking.getRankingTrend" ] } ], "importExport": [ { "name": "exportGradeRecordsToExcel", "signature": "(params: { classId: string; subjectId?: string; examId?: string; scope: DataScope; currentUserId?: string }) => Promise", "file": "export.ts", "purpose": "导出成绩单(Sheet1 成绩明细,Sheet2 统计汇总:均分/中位数/最高分/最低分/标准差/及格率/优秀率/参考人数)(P3 更新:传递 scope/currentUserId 到 data-access)", "deps": [ "shared.lib.excel.exportToExcel", "data-access.getGradeRecords", "data-access.getClassGradeStats" ], "usedBy": [ "actions.exportGradesAction", "app/api/export/route.ts" ] }, { "name": "exportClassGradeReportToExcel", "signature": "(params: { classId: string; scope: DataScope; currentUserId?: string }) => Promise", "file": "export.ts", "purpose": "导出班级成绩总表(多科目横向对比,含总分/平均分/排名列)(P3 更新:适配 PaginatedGradeRecords 结构 + 传递 scope/currentUserId)", "deps": [ "shared.db", "shared.db.schema.classes", "shared.db.schema.subjects", "shared.db.schema.gradeRecords", "shared.db.schema.users", "shared.lib.excel.exportToExcel", "data-access.getGradeRecords" ], "usedBy": [ "actions.exportGradesAction" ] }, { "name": "formatDateForFile", "signature": "(d?: Date) => string", "file": "export.ts", "purpose": "⚠️ P1-c/P2-c 已迁移:本地实现已删除,改为从 @/shared/lib/utils 导入。此条目保留仅作历史记录", "deps": [ "shared/lib/utils.formatDateForFile" ], "usedBy": [], "migratedTo": "shared/lib/utils.formatDateForFile" } ], "lib": [ { "name": "toNumber", "signature": "(v: unknown) => number", "file": "lib/grade-utils.ts", "purpose": "安全将 unknown 值转换为有限数字,非有限值返回 0(P1-2 新增:从 data-access/data-access-analytics/data-access-ranking 抽取)", "usedBy": [ "data-access.serializeRecord", "data-access.getClassGradeStats", "data-access-analytics.getGradeTrend", "data-access-analytics.getClassComparison", "data-access-analytics.getSubjectComparison", "data-access-analytics.getGradeDistribution", "data-access-ranking.getRankingTrend" ] }, { "name": "normalize", "signature": "(score: number, fullScore: number) => number", "file": "lib/grade-utils.ts", "purpose": "将原始分数归一化到 0-100 分制(P1-2 新增:从 data-access-analytics/data-access-ranking 抽取)", "usedBy": [ "data-access-analytics.getGradeTrend", "data-access-analytics.getSubjectComparison", "data-access-analytics.getGradeDistribution", "data-access-ranking.getRankingTrend" ] }, { "name": "buildScopeClassFilter", "signature": "(scope: DataScope, currentUserId?: string) => SQL | null", "file": "lib/grade-utils.ts", "purpose": "根据 DataScope 构建 gradeRecords 表的行级权限过滤条件(P1-2 新增:从 data-access/data-access-analytics 抽取;P3 更新:class_members scope 内置 studentId 过滤,需传入 currentUserId 参数)", "usedBy": [ "data-access.getGradeRecords", "data-access.getClassGradeStats", "data-access.getStudentGradeSummary", "data-access.getClassRanking", "data-access-analytics.getGradeTrend", "data-access-analytics.getClassComparison", "data-access-analytics.getSubjectComparison", "data-access-analytics.getGradeDistribution", "data-access-ranking.getRankingTrend" ] } ], "statsService": [ { "name": "computeGradeStats", "signature": "(rows: RawScoreRow[]) => GradeStats | null", "file": "stats-service.ts", "purpose": "从原始成绩行计算班级统计(均分、中位数、标准差、及格率、优秀率、最高分、最低分、参考人数)(P1-1 新增:从 data-access.getClassGradeStats 抽取为纯函数)", "usedBy": [ "data-access.getClassGradeStats" ] }, { "name": "computeAverageScore", "signature": "(scores: number[]) => number", "file": "stats-service.ts", "purpose": "计算分数平均值(空数组返回 0)(P1-1 新增:从 data-access.getStudentGradeSummary 抽取为纯函数)", "usedBy": [ "data-access.getStudentGradeSummary" ] }, { "name": "buildGradeTrendPoints", "signature": "(rows: RawScoreRow[]) => GradeTrendPoint[]", "file": "stats-service.ts", "purpose": "构建成绩趋势数据点(按考试标题分组,归一化分数 0-100)(P1-1 新增:从 data-access-analytics.getGradeTrend 抽取为纯函数)", "usedBy": [ "data-access-analytics.getGradeTrend" ] }, { "name": "computeTrendAverage", "signature": "(points: GradeTrendPoint[]) => number", "file": "stats-service.ts", "purpose": "计算趋势数据点的平均分(P1-1 新增:从 data-access-analytics.getGradeTrend 抽取为纯函数)", "usedBy": [ "data-access-analytics.getGradeTrend" ] }, { "name": "computeClassComparisonStats", "signature": "(rows: RawScoreRow[]) => Pick", "file": "stats-service.ts", "purpose": "计算班级对比统计(均分、及格率、优秀率、参考人数)(P1-1 新增:从 data-access-analytics.getClassComparison 抽取为纯函数)", "usedBy": [ "data-access-analytics.getClassComparison" ] }, { "name": "computeSubjectComparisonStats", "signature": "(scores: number[]) => Pick", "file": "stats-service.ts", "purpose": "计算科目对比统计(均分、及格率、优秀率、最高分、最低分)(P1-1 新增:从 data-access-analytics.getSubjectComparison 抽取为纯函数)", "usedBy": [ "data-access-analytics.getSubjectComparison" ] }, { "name": "computeGradeDistribution", "signature": "(rows: RawScoreRow[]) => GradeDistributionResult", "file": "stats-service.ts", "purpose": "计算分数分布(90-100/80-89/70-79/60-69/<60 五个区间)(P1-1 新增:从 data-access-analytics.getGradeDistribution 抽取为纯函数)", "usedBy": [ "data-access-analytics.getGradeDistribution" ] }, { "name": "buildRankingTrendPoints", "signature": "(byTitle: Map, targetStudentId: string) => RankingTrendPoint[]", "file": "stats-service.ts", "purpose": "构建排名趋势数据点(按考试标题排序、计算每次考试学生排名)(P1-1 新增:从 data-access-ranking.getRankingTrend 抽取为纯函数)", "usedBy": [ "data-access-ranking.getRankingTrend" ] } ], "components": [ { "name": "GradeRecordForm", "file": "components/grade-record-form.tsx", "purpose": "单条成绩录入表单(选择学生、班级、科目、考试,输入分数、满分、类型、学期、备注)" }, { "name": "BatchGradeEntry", "file": "components/batch-grade-entry.tsx", "purpose": "批量录入界面(选择班级+科目+考试,表格形式录入每个学生分数)" }, { "name": "GradeRecordList", "file": "components/grade-record-list.tsx", "purpose": "成绩列表(含查询筛选、删除对话框)" }, { "name": "GradeStatsCard", "file": "components/grade-stats-card.tsx", "purpose": "统计卡片(均分、中位数、标准差、及格率、优秀率、最高分、最低分)" }, { "name": "GradeQueryFilters", "file": "components/grade-query-filters.tsx", "purpose": "查询筛选器(班级、科目、考试类型、学期)" }, { "name": "StudentGradeSummary", "file": "components/student-grade-summary.tsx", "purpose": "学生成绩汇总视图(按科目分组展示成绩趋势)" }, { "name": "ClassGradeReport", "file": "components/class-grade-report.tsx", "purpose": "班级成绩报表(含统计+排名)" }, { "name": "ExportButton", "file": "components/export-button.tsx", "purpose": "成绩导出按钮(DropdownMenu 选择 detail/class 报表类型,调用 exportGradesAction 并触发浏览器下载)", "usedBy": [ "teacher/grades/page.tsx", "teacher/grades/stats/page.tsx" ] }, { "name": "GradeTrendChart", "file": "components/grade-trend-chart.tsx", "purpose": "成绩趋势折线图(recharts LineChart,归一化分数 0-100)", "deps": [ "recharts", "shared/components/ui/chart" ] }, { "name": "ClassComparisonChart", "file": "components/class-comparison-chart.tsx", "purpose": "班级对比柱状图(recharts BarChart,均分/及格率/优秀率)", "deps": [ "recharts", "shared/components/ui/chart" ] }, { "name": "SubjectComparisonChart", "file": "components/subject-comparison-chart.tsx", "purpose": "科目对比雷达图(recharts RadarChart)", "deps": [ "recharts", "shared/components/ui/chart" ] }, { "name": "GradeDistributionChart", "file": "components/grade-distribution-chart.tsx", "purpose": "分数分布柱状图(recharts BarChart,彩色区间 90-100/80-89/70-79/60-69/<60)", "deps": [ "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" ] }, { "name": "WidgetBoundary", "file": "components/widget-boundary.tsx", "purpose": "通用 Widget 边界组件(Error Boundary + Suspense + Skeleton 组合,含 a11y 属性 role=alert/aria-live/aria-label)(P1-5 新增)", "deps": [ "shared/components/ui/skeleton", "shared/components/ui/button" ] } ] } }, "course-plans": { "path": "src/modules/course-plans", "description": "课程计划管理:创建、编辑、删除课程计划(含周计划条目),管理员可管理全部,教师/学生/年级主任/教务主任可查看", "exports": { "actions": [ { "name": "createCoursePlanAction", "permission": "COURSE_PLAN_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "创建课程计划", "deps": [ "requirePermission", "shared/db", "data-access.createCoursePlan" ], "usedBy": [ "course-plan-form.tsx" ] }, { "name": "updateCoursePlanAction", "permission": "COURSE_PLAN_MANAGE", "signature": "(id: string, prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "更新课程计划", "deps": [ "requirePermission", "shared/db", "data-access.updateCoursePlan" ], "usedBy": [ "course-plan-form.tsx" ] }, { "name": "deleteCoursePlanAction", "permission": "COURSE_PLAN_MANAGE", "signature": "(id: string) => Promise>", "purpose": "删除课程计划", "deps": [ "requirePermission", "shared/db", "data-access.deleteCoursePlan" ], "usedBy": [ "course-plan-detail.tsx" ] }, { "name": "getCoursePlansAction", "permission": "COURSE_PLAN_READ", "signature": "(params?: GetCoursePlansParams) => Promise>", "purpose": "获取课程计划列表", "deps": [ "requirePermission", "data-access.getCoursePlans" ], "usedBy": [ "待扩展" ] }, { "name": "getCoursePlanAction", "permission": "COURSE_PLAN_READ", "signature": "(id: string) => Promise>", "purpose": "获取课程计划详情(含周计划条目)", "deps": [ "requirePermission", "data-access.getCoursePlanById" ], "usedBy": [ "待扩展" ] }, { "name": "createCoursePlanItemAction", "permission": "COURSE_PLAN_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "创建周计划条目", "deps": [ "requirePermission", "shared/db", "data-access.createCoursePlanItem" ], "usedBy": [ "course-plan-item-editor.tsx" ] }, { "name": "updateCoursePlanItemAction", "permission": "COURSE_PLAN_MANAGE", "signature": "(id: string, prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "更新周计划条目", "deps": [ "requirePermission", "shared/db", "data-access.updateCoursePlanItem" ], "usedBy": [ "course-plan-item-editor.tsx" ] }, { "name": "deleteCoursePlanItemAction", "permission": "COURSE_PLAN_MANAGE", "signature": "(id: string) => Promise>", "purpose": "删除周计划条目", "deps": [ "requirePermission", "shared/db", "data-access.deleteCoursePlanItem" ], "usedBy": [ "course-plan-item-editor.tsx" ] }, { "name": "toggleCoursePlanItemCompletedAction", "permission": "COURSE_PLAN_MANAGE", "signature": "(id: string, completed: boolean) => Promise>", "purpose": "切换周计划条目完成状态", "deps": [ "requirePermission", "shared/db", "data-access.updateCoursePlanItem" ], "usedBy": [ "course-plan-detail.tsx" ] } ], "dataAccess": [ { "name": "getCoursePlans", "signature": "(params?: GetCoursePlansParams) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlans", "shared.db.schema.classes", "shared.db.schema.subjects", "shared.db.schema.users" ], "usedBy": [ "admin/course-plans/page.tsx", "teacher/course-plans/page.tsx" ] }, { "name": "getCoursePlanById", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlans", "shared.db.schema.coursePlanItems", "shared.db.schema.classes", "shared.db.schema.subjects", "shared.db.schema.users" ], "usedBy": [ "admin/course-plans/[id]/page.tsx", "teacher/course-plans/[id]/page.tsx" ] }, { "name": "createCoursePlan", "signature": "(data: CreateCoursePlanInput, createdBy: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlans", "@paralleldrive/cuid2" ], "usedBy": [ "createCoursePlanAction" ] }, { "name": "updateCoursePlan", "signature": "(id: string, data: Partial) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlans" ], "usedBy": [ "updateCoursePlanAction" ] }, { "name": "deleteCoursePlan", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlans" ], "usedBy": [ "deleteCoursePlanAction" ] }, { "name": "createCoursePlanItem", "signature": "(data: CreateCoursePlanItemInput) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlanItems", "@paralleldrive/cuid2" ], "usedBy": [ "createCoursePlanItemAction" ] }, { "name": "updateCoursePlanItem", "signature": "(id: string, data: Partial) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlanItems" ], "usedBy": [ "updateCoursePlanItemAction", "toggleCoursePlanItemCompletedAction" ] }, { "name": "deleteCoursePlanItem", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlanItems" ], "usedBy": [ "deleteCoursePlanItemAction" ] }, { "name": "reorderCoursePlanItems", "signature": "(planId: string, items: ReorderCoursePlanItemInput[]) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.coursePlanItems" ], "usedBy": [ "待扩展" ] }, { "name": "getSubjectOptions", "signature": "() => Promise<{id:string;name:string}[]>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.subjects" ], "usedBy": [ "admin/course-plans/create/page.tsx", "admin/course-plans/[id]/edit/page.tsx" ] } ], "schemas": [ { "name": "CreateCoursePlanSchema", "type": "zod", "file": "schema.ts", "definition": "{ classId, subjectId, teacherId, academicYearId?, semester?, totalHours?, weeklyHours?, startDate?, endDate?, syllabus?, objectives?, status? }", "usedBy": [ "createCoursePlanAction" ] }, { "name": "UpdateCoursePlanSchema", "type": "zod", "file": "schema.ts", "definition": "{ classId?, subjectId?, teacherId?, academicYearId?, semester?, totalHours?, completedHours?, weeklyHours?, startDate?, endDate?, syllabus?, objectives?, status? }", "usedBy": [ "updateCoursePlanAction" ] }, { "name": "CreateCoursePlanItemSchema", "type": "zod", "file": "schema.ts", "definition": "{ planId, week, topic, content?, hours?, textbookChapter?, notes? }", "usedBy": [ "createCoursePlanItemAction" ] }, { "name": "UpdateCoursePlanItemSchema", "type": "zod", "file": "schema.ts", "definition": "{ week?, topic?, content?, hours?, textbookChapter?, notes?, isCompleted?, completedAt? }", "usedBy": [ "updateCoursePlanItemAction" ] } ], "types": [ { "name": "CoursePlan", "type": "interface", "file": "types.ts", "definition": "{ id, classId, subjectId, teacherId, academicYearId, semester, totalHours, completedHours, weeklyHours, startDate, endDate, syllabus, objectives, status, createdBy, createdAt, updatedAt }", "usedBy": [ "course-plans/components", "data-access" ] }, { "name": "CoursePlanItem", "type": "interface", "file": "types.ts", "definition": "{ id, planId, week, topic, content, hours, textbookChapter, notes, isCompleted, completedAt, createdAt, updatedAt }", "usedBy": [ "course-plans/components", "data-access" ] }, { "name": "CoursePlanListItem", "type": "interface", "file": "types.ts", "definition": "= CoursePlan & { className, subjectName, teacherName }", "usedBy": [ "列表页", "data-access" ] }, { "name": "CoursePlanWithItems", "type": "interface", "file": "types.ts", "definition": "= CoursePlanListItem & { items: CoursePlanItem[] }", "usedBy": [ "详情页", "data-access" ] }, { "name": "CoursePlanStatus", "type": "type", "file": "types.ts", "definition": "\"planning\" | \"active\" | \"completed\" | \"paused\"", "usedBy": [ "data-access", "components" ] }, { "name": "CoursePlanSemester", "type": "type", "file": "types.ts", "definition": "\"1\" | \"2\"", "usedBy": [ "data-access", "components" ] }, { "name": "GetCoursePlansParams", "type": "interface", "file": "types.ts", "definition": "{ classId?, teacherId?, subjectId?, status? }", "usedBy": [ "getCoursePlans", "getCoursePlansAction" ] }, { "name": "ReorderCoursePlanItemInput", "type": "interface", "file": "types.ts", "definition": "{ id, week }", "usedBy": [ "reorderCoursePlanItems" ] } ], "components": [ { "name": "CoursePlanProgress", "file": "components/course-plan-progress.tsx", "purpose": "进度条组件(completedHours/totalHours 百分比)" }, { "name": "CoursePlanList", "file": "components/course-plan-list.tsx", "purpose": "课程计划列表(支持状态筛选,URL同步)" }, { "name": "CoursePlanForm", "file": "components/course-plan-form.tsx", "purpose": "创建/编辑表单(班级、科目、教师、学年、学期、状态、课时、日期、大纲、目标)" }, { "name": "CoursePlanItemEditor", "file": "components/course-plan-item-editor.tsx", "purpose": "周计划条目编辑器(Dialog,支持创建/编辑/删除/切换完成)" }, { "name": "CoursePlanDetail", "file": "components/course-plan-detail.tsx", "purpose": "详情视图(计划信息、进度、大纲、目标、周计划表格、删除确认)" } ] } }, "parent": { "path": "src/modules/parent", "description": "家长端仪表盘:聚合家长关联子女的学习数据(课表、作业、成绩、班级),支持多子女切换查看。家长通过 parentStudentRelations 表关联子女,DataScope 解析为 children 类型", "exports": { "dataAccess": [ { "name": "getChildren", "signature": "(parentId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.parentStudentRelations", "react.cache" ], "usedBy": [ "getParentDashboardData (内部)", "parent/children/[studentId]/page.tsx" ] }, { "name": "getChildBasicInfo", "signature": "(studentId: string, relation?: string | null) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.users", "shared.db.schema.grades", "shared.db.schema.classEnrollments", "shared.db.schema.classes", "react.cache" ], "usedBy": [ "getChildDashboardData (内部)" ] }, { "name": "getChildDashboardData", "signature": "(studentId: string, relation?: string | null) => Promise", "file": "data-access.ts", "deps": [ "getChildBasicInfo", "classes/data-access.getStudentClasses", "classes/data-access.getStudentSchedule", "homework/data-access.getStudentHomeworkAssignments", "homework/data-access.getStudentDashboardGrades", "grades/data-access.getStudentGradeSummary", "react.cache" ], "usedBy": [ "parent/children/[studentId]/page.tsx", "getParentDashboardData (内部)" ] }, { "name": "getParentDashboardData", "signature": "(parentId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.users", "getChildren", "getChildDashboardData", "react.cache" ], "usedBy": [ "parent/dashboard/page.tsx" ] }, { "name": "verifyParentChildRelation", "signature": "(studentId: string, parentId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.parentStudentRelations", "react.cache" ], "usedBy": [ "parent/children/[studentId]/page.tsx" ] }, { "name": "getChildNameList", "signature": "(parentId: string) => Promise>", "file": "data-access.ts", "deps": [ "getChildren", "users/data-access.getUserNamesByIds", "react.cache" ], "usedBy": [ "parent/children/[studentId]/page.tsx" ] } ], "types": [ { "name": "ParentChildRelation", "type": "type", "file": "types.ts", "definition": "{ id, parentId, studentId, relation: string | null, createdAt: string }", "usedBy": [ "getChildren", "getParentDashboardData" ] }, { "name": "ChildBasicInfo", "type": "type", "file": "types.ts", "definition": "{ id, name: string | null, email, image: string | null, gradeName: string | null, className: string | null, classId: string | null, relation: string | null }", "usedBy": [ "getChildBasicInfo", "ChildDashboardData.basicInfo" ] }, { "name": "ChildScheduleItem", "type": "type", "file": "types.ts", "definition": "{ id, classId, className, course, startTime, endTime, location: string | null }", "usedBy": [ "ChildDashboardData.todaySchedule", "child-schedule-card.tsx" ] }, { "name": "ChildWeeklyScheduleItem", "type": "type", "file": "types.ts", "definition": "ChildScheduleItem & { weekday: 1 | 2 | 3 | 4 | 5 | 6 | 7 }", "usedBy": [ "ChildDashboardData.weeklySchedule", "child-schedule-card.tsx" ] }, { "name": "ChildHomeworkSummary", "type": "type", "file": "types.ts", "definition": "{ pendingCount, submittedCount, gradedCount, overdueCount, recentAssignments: StudentHomeworkAssignmentListItem[] }", "usedBy": [ "ChildDashboardData.homeworkSummary", "child-homework-summary.tsx" ] }, { "name": "ChildDashboardData", "type": "type", "file": "types.ts", "definition": "{ basicInfo: ChildBasicInfo, enrolledClasses: StudentEnrolledClass[], todaySchedule: ChildScheduleItem[], weeklySchedule: ChildWeeklyScheduleItem[], homeworkSummary: ChildHomeworkSummary, gradeTrend: StudentDashboardGradeProps, gradeSummary: StudentGradeSummary | null }", "usedBy": [ "getChildDashboardData", "ParentDashboardData.children", "所有 child-* 组件" ] }, { "name": "ParentDashboardData", "type": "type", "file": "types.ts", "definition": "{ parentName: string | null, children: ChildDashboardData[] }", "usedBy": [ "getParentDashboardData", "parent-dashboard.tsx" ] } ], "components": [ { "name": "ParentDashboard", "file": "components/parent-dashboard.tsx", "purpose": "主容器组件(问候语、待办横幅、4 宫格快捷入口、子女卡片网格、空状态引导)" }, { "name": "ParentAttentionBanner", "file": "components/parent-attention-banner.tsx", "purpose": "v4 新增:仪表盘顶部待办事项横幅,聚合所有子女的逾期作业、待办、考勤、公告数量" }, { "name": "ParentAttendanceWarning", "file": "components/parent-attendance-warning.tsx", "purpose": "v4 新增:考勤页异常预警横幅,聚合缺勤/迟到/低出勤率预警" }, { "name": "ParentAttendanceRateCard", "file": "components/parent-attendance-rate-card.tsx", "purpose": "v4 新增:考勤页出勤率汇总卡片,聚合所有子女的平均出勤率、缺勤、迟到总数" }, { "name": "ParentAttendanceCalendar", "file": "components/parent-attendance-calendar.tsx", "purpose": "v4 新增:考勤月历视图,按状态着色每日出勤状态,支持按月切换(use client)" }, { "name": "ChildGradeDetail", "file": "components/child-grade-detail.tsx", "purpose": "v4 新增:成绩详情视图,按科目分组展示平均分、趋势、最近成绩,提供单科分析" }, { "name": "ChildHomeworkDetail", "file": "components/child-homework-detail.tsx", "purpose": "v4 新增:作业详情视图,展示所有作业的完整信息(状态、截止时间、提交时间、分数、尝试次数)" }, { "name": "ParentExportButton", "file": "components/parent-export-button.tsx", "purpose": "v4 新增:成绩导出按钮(占位,待后端实现 Server Action)" }, { "name": "ChildCard", "file": "components/child-card.tsx", "purpose": "子女卡片(头像、姓名、班级、待完成/逾期/平均分统计、异常红色高亮、趋势图标、点击跳转详情)" }, { "name": "ChildDetailHeader", "file": "components/child-detail-header.tsx", "purpose": "子女详情页头部(面包屑、返回按钮、头像、姓名、班级、年级、关系、邮箱掩码)" }, { "name": "ChildDetailPanel", "file": "components/child-detail-panel.tsx", "purpose": "v4 重写:子女详情面板 Tab 容器(Overview/Homework/Grades/Schedule/Attendance/Diagnostic 六 Tab 切换,支持 ?tab= 参数)" }, { "name": "SiblingSwitcher", "file": "components/child-detail-panel.tsx", "purpose": "v4 新增:详情页头部多子女切换器(Tabs 形式,避免返回仪表盘)" }, { "name": "ChildHomeworkSummary", "file": "components/child-homework-summary.tsx", "purpose": "子女作业概览(pending/submitted/graded/overdue 统计 + 最近作业列表含科目标识)" }, { "name": "ChildGradeSummary", "file": "components/child-grade-summary.tsx", "purpose": "子女成绩概览(Recharts 折线图趋势 + 最新分数 + 趋势图标 + 班级排名 Top X% + 最近成绩列表,use client)" }, { "name": "ChildScheduleCard", "file": "components/child-schedule-card.tsx", "purpose": "子女课表卡片(支持今日课表 + 完整周课表两种视图,周课表按 weekday 分组并高亮今日)" }, { "name": "ParentChildrenDataPage", "file": "components/parent-children-data-page.tsx", "purpose": "多子女数据聚合页共享布局(标题、描述、headerExtra 额外内容、空状态、子女列表)" }, { "name": "ParentNoChildrenPage", "file": "components/parent-children-data-page.tsx", "purpose": "dataScope 为空时的统一空状态页面" } ] } }, "messaging": { "path": "src/modules/messaging", "description": "站内私信系统:用户间私信收发(支持回复链)。通知相关 UI 组件和 CRUD Action 已迁移至 notifications 模块(P1-4 修复)", "exports": { "actions": [ { "name": "sendMessageAction", "permission": "MESSAGE_SEND", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "发送消息(同时通过 notifications dispatcher 为收件人创建多渠道通知;支持 parentMessageId 回复;P2-11 新增 trackEvent 埋点;V2-P0-2 新增通知标题 i18n 化,通过 getTranslations('messages') 生成 notification.messageTitle / messageTitleNoSubject)", "deps": [ "requirePermission", "shared/db", "data-access.createMessage", "notifications.dispatcher.sendNotification", "trackEvent", "revalidatePath", "next-intl/server.getTranslations" ], "usedBy": [ "message-compose.tsx" ] }, { "name": "markMessageAsReadAction", "permission": "MESSAGE_READ", "signature": "(id: string) => Promise>", "purpose": "标记消息已读(设置 readAt;P2-11 新增 trackEvent 埋点)", "deps": [ "requirePermission", "schema.MessageIdSchema", "data-access.markMessageAsRead", "trackEvent", "revalidatePath" ], "usedBy": [ "message-detail.tsx", "messages/[id]/page.tsx" ] }, { "name": "deleteMessageAction", "permission": "MESSAGE_DELETE", "signature": "(id: string) => Promise>", "purpose": "删除消息(仅发送者或接收者可删;P2-11 新增 trackEvent 埋点)", "deps": [ "requirePermission", "schema.MessageIdSchema", "data-access.deleteMessage", "trackEvent", "revalidatePath" ], "usedBy": [ "message-detail.tsx" ] }, { "name": "getMessagesAction", "permission": "MESSAGE_READ", "signature": "(params?: { type?, page?, pageSize?, keyword? }) => Promise>>", "purpose": "获取消息列表(收件箱/已发送,分页,关键词搜索;客户端通过 useMessageSearch hook 调用)", "deps": [ "requirePermission", "data-access.getMessages" ], "usedBy": [ "message-list.tsx (via useMessageSearch hook)" ] }, { "name": "getMessageDetailAction", "permission": "MESSAGE_READ", "signature": "(id: string) => Promise>", "purpose": "获取消息详情(含回复线程)", "deps": [ "requirePermission", "schema.MessageIdSchema", "data-access.getMessageById", "data-access.getMessageThread" ], "usedBy": [ "message-detail.tsx" ] }, { "name": "getRecipientsAction", "permission": "MESSAGE_SEND", "signature": "() => Promise>", "purpose": "获取可发送对象列表(按 DataScope 过滤)", "deps": [ "requirePermission", "data-access.getRecipients" ], "usedBy": [ "messages/compose/page.tsx" ] }, { "name": "getUnreadMessageCountAction", "permission": "MESSAGE_READ", "signature": "() => Promise>", "purpose": "获取当前用户未读私信计数(unread-message-badge 组件每 60 秒轮询)", "deps": [ "requirePermission", "data-access.getUnreadMessageCount" ], "usedBy": [ "unread-message-badge.tsx" ] }, { "name": "getNotificationPreferencesAction", "permission": "MESSAGE_READ", "signature": "() => Promise>", "purpose": "获取当前用户的通知偏好设置(首次访问自动创建默认记录)", "deps": [ "requirePermission", "notifications.preferences.getNotificationPreferences" ], "usedBy": [ "settings/page.tsx", "settings/components/notification-preferences-form.tsx" ] }, { "name": "updateNotificationPreferencesAction", "permission": "MESSAGE_READ", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "更新当前用户的通知偏好设置(upsert 语义,未提供字段保留原值)", "deps": [ "requirePermission", "schema.UpdateNotificationPreferencesSchema", "notifications.preferences.upsertNotificationPreferences", "revalidatePath" ], "usedBy": [ "settings/components/notification-preferences-form.tsx" ] } ], "dataAccess": [ { "name": "getMessages", "signature": "(userId: string, params?: { type?, page?, pageSize? }) => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.messages", "shared.db.schema.users" ], "usedBy": [ "getMessagesAction" ] }, { "name": "getMessageById", "signature": "(id: string, userId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.messages", "shared.db.schema.users" ], "usedBy": [ "getMessageDetailAction", "messages/[id]/page.tsx" ] }, { "name": "getMessageThread", "signature": "(rootId: string, userId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.messages", "shared.db.schema.users" ], "usedBy": [ "getMessageDetailAction" ] }, { "name": "createMessage", "signature": "(input: CreateMessageInput) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.messages", "@paralleldrive/cuid2" ], "usedBy": [ "sendMessageAction" ] }, { "name": "markMessageAsRead", "signature": "(id: string, userId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.messages" ], "usedBy": [ "markMessageAsReadAction", "messages/[id]/page.tsx" ] }, { "name": "deleteMessage", "signature": "(id: string, userId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.messages" ], "usedBy": [ "deleteMessageAction" ] }, { "name": "getUnreadMessageCount", "signature": "(userId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.messages" ], "usedBy": [ "getUnreadMessageCountAction", "unread-message-badge.tsx" ] }, { "name": "getMessagesPageData", "signature": "(userId: string) => Promise<{ messages: PaginatedResult, notifications: PaginatedResult }>", "file": "data-access.ts", "purpose": "P1-5 新增:消息首页编排函数,一次性获取消息列表和通知列表(通知通过动态 import notifications/data-access)", "deps": [ "data-access.getMessages", "notifications.data-access.getNotifications" ], "usedBy": [ "messages/page.tsx" ] }, { "name": "getMessageDetailPageData", "signature": "(id: string, userId: string) => Promise", "file": "data-access.ts", "purpose": "V2-P1-3 新增:消息详情页编排函数,一次性获取消息详情并自动标记已读(仅当 receiverId === userId 且未读时);替代 page.tsx 中 after() + getMessageById + markMessageAsRead 的分散编排", "deps": [ "data-access.getMessageById", "data-access.markMessageAsRead" ], "usedBy": [ "messages/[id]/page.tsx" ] }, { "name": "getRecipients", "signature": "(ctx: AuthContext) => Promise", "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", "classes.data-access.getTeacherIdsByClassIds", "classes.data-access.getStudentActiveClassId", "users.data-access.getUserNamesByIds" ], "usedBy": [ "getRecipientsAction", "messages/compose/page.tsx" ] } ], "notificationPreferences": [ { "name": "getNotificationPreferences", "signature": "(userId: string) => Promise", "file": "notification-preferences.ts", "purpose": "re-export shim(实际逻辑在 notifications/preferences.ts,P0-4 / P1-5 修复后迁移;React cache 包装;若不存在则自动创建默认记录,并发冲突回退到查询)", "deps": [ "notifications.preferences.getNotificationPreferences" ], "usedBy": [ "getNotificationPreferencesAction", "settings/page.tsx" ] }, { "name": "upsertNotificationPreferences", "signature": "(userId: string, input: UpdateNotificationPreferencesInput) => Promise", "file": "notification-preferences.ts", "purpose": "re-export shim(实际逻辑在 notifications/preferences.ts,P0-4 / P1-5 修复后迁移;存在则部分更新,不存在则插入;未提供字段保留原值)", "deps": [ "notifications.preferences.upsertNotificationPreferences" ], "usedBy": [ "updateNotificationPreferencesAction" ] } ], "schemas": [ { "name": "SendMessageSchema", "type": "zod", "file": "schema.ts", "definition": "{ receiverId: string, subject?: string, content: string, parentMessageId?: string }", "usedBy": [ "sendMessageAction" ] } ], "types": [ { "name": "Message", "type": "type", "file": "types.ts", "definition": "{ id, senderId, receiverId, subject: string | null, content, isRead, readAt: string | null, parentMessageId: string | null, createdAt, senderName, receiverName }", "usedBy": [ "messaging/components", "页面" ] }, { "name": "MessageListItem", "type": "type", "file": "types.ts", "definition": "消息列表项类型(同 Message 精简版)", "usedBy": [ "列表页" ] }, { "name": "MessageThread", "type": "type", "file": "types.ts", "definition": "{ root: Message, replies: Message[] }", "usedBy": [ "详情页" ] }, { "name": "Notification", "type": "type", "file": "types.ts", "definition": "{ id, userId, type: NotificationType, title, content: string | null, link: string | null, isRead, createdAt }", "usedBy": [ "notification-dropdown", "notification-list" ] }, { "name": "NotificationListItem", "type": "type", "file": "types.ts", "definition": "通知列表项类型(同 Notification)", "usedBy": [ "列表页" ] }, { "name": "NotificationType", "type": "type", "file": "types.ts", "definition": "'message' | 'announcement' | 'homework' | 'grade'", "usedBy": [ "data-access", "components" ] }, { "name": "MessageType", "type": "type", "file": "types.ts", "definition": "'inbox' | 'sent'", "usedBy": [ "getMessages 参数" ] }, { "name": "CreateMessageInput", "type": "type", "file": "types.ts", "definition": "{ senderId, receiverId, subject?, content, parentMessageId? }", "usedBy": [ "createMessage" ] }, { "name": "CreateNotificationInput", "type": "type", "file": "types.ts", "definition": "{ userId, type: NotificationType, title, content?, link? }", "usedBy": [ "createNotification" ] }, { "name": "RecipientOption", "type": "type", "file": "types.ts", "definition": "{ id: string, name: string }", "usedBy": [ "compose 页面下拉选项" ] }, { "name": "PaginatedResult", "type": "type", "file": "types.ts", "definition": "{ items: T[], total: number, page: number, pageSize: number, totalPages: number }", "usedBy": [ "getMessages", "getNotifications" ] }, { "name": "NotificationPreferences", "type": "interface", "file": "types.ts", "definition": "{ id, userId, emailEnabled, smsEnabled, pushEnabled, homeworkNotifications, gradeNotifications, announcementNotifications, messageNotifications, attendanceNotifications, createdAt, updatedAt }", "usedBy": [ "notification-preferences", "getNotificationPreferencesAction", "updateNotificationPreferencesAction", "settings/components/notification-preferences-form" ] }, { "name": "UpdateNotificationPreferencesInput", "type": "interface", "file": "types.ts", "definition": "{ emailEnabled?, smsEnabled?, pushEnabled?, homeworkNotifications?, gradeNotifications?, announcementNotifications?, messageNotifications?, attendanceNotifications? }", "usedBy": [ "upsertNotificationPreferences", "updateNotificationPreferencesAction" ] } ], "components": [ { "name": "MessageList", "file": "components/message-list.tsx", "purpose": "消息列表(收件箱/已发送 Tab 切换,已读/未读标记,usePermission 控制写消息按钮;V2-P1-2 优化:客户端过滤仅在初始数据 type=all 时执行,搜索结果已由服务端按 tab 过滤)" }, { "name": "MessageDetail", "file": "components/message-detail.tsx", "purpose": "消息详情(含回复线程、回复/删除操作,AlertDialog 删除确认,usePermission 控制按钮可见性)" }, { "name": "MessageCompose", "file": "components/message-compose.tsx", "purpose": "写消息表单(收件人 Select、主题 Input、内容 Textarea,支持回复模式;V2-P1-4 增强:fieldErrors 状态 + aria-invalid 字段级服务端校验错误展示)" }, { "name": "UnreadMessageBadge", "file": "components/unread-message-badge.tsx", "purpose": "未读消息计数徽章(侧边栏 Messages 导航项旁显示,每 60 秒轮询 getUnreadMessageCountAction;V2-P2-1 优化:轮询间隔提取为 POLL_INTERVAL_MS 常量)" } ], "hooks": [ { "name": "useMessageSearch", "file": "hooks/use-message-search.ts", "purpose": "P1-7 新增:消息搜索 hook(400ms 防抖 + 请求竞态取消,通过 requestIdRef 匹配最新请求)", "usedBy": [ "message-list.tsx" ] } ] } }, "notifications": { "path": "src/modules/notifications", "description": "通知渠道集成层:基于用户通知偏好(notification_preferences)将通知分发到站内消息/SMS/微信公众号/邮件多渠道。所有渠道实现统一 NotificationChannelSender 接口,dispatcher 按偏好并行发送。支持 Mock 模式(开发环境无需外部服务)。P1-4 修复后新增通知 UI 组件和通知 CRUD Server Action。", "exports": { "actions": [ { "name": "sendNotificationAction", "permission": "MESSAGE_SEND", "signature": "(payload: NotificationPayload) => Promise>", "purpose": "发送通知给指定用户(按偏好多渠道分发)", "deps": [ "requirePermission", "dispatcher.sendNotification" ], "usedBy": [ "待扩展" ] }, { "name": "sendClassNotificationAction", "permission": "MESSAGE_SEND", "signature": "(classId: string, payload: Omit) => Promise>", "purpose": "发送班级通知(批量发送给班级所有学生;教师只能给自己所教班级发送)", "deps": [ "requirePermission", "db.schema.classEnrollments", "db.schema.classes", "dispatcher.sendBatchNotifications" ], "usedBy": [ "待扩展" ] }, { "name": "getNotificationsAction", "permission": "MESSAGE_READ", "signature": "(params?: { page?, pageSize?, unreadOnly? }) => Promise>>", "purpose": "P1-4 新增(从 messaging 迁移):获取当前用户通知列表(分页)", "deps": [ "requirePermission", "data-access.getNotifications" ], "usedBy": [ "notification-dropdown.tsx", "notification-list.tsx" ] }, { "name": "getUnreadNotificationCountAction", "permission": "MESSAGE_READ", "signature": "() => Promise>", "purpose": "P1-4 新增(从 messaging 迁移):获取当前用户未读通知计数", "deps": [ "requirePermission", "data-access.getUnreadNotificationCount" ], "usedBy": [ "notification-dropdown.tsx" ] }, { "name": "markNotificationAsReadAction", "permission": "MESSAGE_READ", "signature": "(notificationId: string) => Promise>", "purpose": "P1-4 新增(从 messaging 迁移):标记单条通知已读;P2-11 新增 trackEvent 埋点", "deps": [ "requirePermission", "schema.NotificationIdSchema", "data-access.markNotificationAsRead", "trackEvent", "revalidatePath" ], "usedBy": [ "notification-dropdown.tsx", "notification-list.tsx" ] }, { "name": "markAllNotificationsAsReadAction", "permission": "MESSAGE_READ", "signature": "() => Promise>", "purpose": "P1-4 新增(从 messaging 迁移):标记所有通知已读;P2-11 新增 trackEvent 埋点", "deps": [ "requirePermission", "data-access.markAllNotificationsAsRead", "trackEvent", "revalidatePath" ], "usedBy": [ "notification-dropdown.tsx", "notification-list.tsx" ] } ], "dispatcher": [ { "name": "sendNotification", "signature": "(payload: NotificationPayload) => Promise", "file": "dispatcher.ts", "purpose": "发送单条通知:读取用户偏好+联系方式,按偏好选择渠道并行发送,记录日志", "deps": [ "preferences.getNotificationPreferences", "data-access.getUserContactInfo", "data-access.logNotificationSendBatch", "channels.sms-channel.createSmsSender", "channels.wechat-channel.createWechatSender", "channels.email-channel.createEmailSender", "channels.in-app-channel.createInAppSender" ], "usedBy": [ "sendNotificationAction", "sendClassNotificationAction", "messaging.sendMessageAction" ] }, { "name": "sendBatchNotifications", "signature": "(payloads: NotificationPayload[]) => Promise", "file": "dispatcher.ts", "purpose": "批量发送通知(每个用户独立选择渠道,并行发送)", "deps": [ "sendNotification" ], "usedBy": [ "sendClassNotificationAction" ] } ], "dataAccess": [ { "name": "getNotifications", "signature": "(userId: string, params?: { page?, pageSize?, unreadOnly? }) => Promise>", "file": "data-access.ts", "purpose": "获取用户站内通知列表(分页,支持 unreadOnly 过滤;P0-4 / P1-5 修复后从 messaging 迁移)", "deps": [ "shared.db", "shared.db.schema.messageNotifications", "react.cache" ], "usedBy": [ "messaging.getNotificationsAction (via re-export)", "messages/page.tsx (via re-export)" ] }, { "name": "createNotification", "signature": "(input: CreateNotificationInput) => Promise", "file": "data-access.ts", "purpose": "创建站内通知记录(写入 message_notifications 表;P0-4 / P1-5 修复后从 messaging 迁移)", "deps": [ "shared.db", "shared.db.schema.messageNotifications", "@paralleldrive/cuid2" ], "usedBy": [ "channels.in-app-channel.InAppChannelSender.send" ] }, { "name": "markNotificationAsRead", "signature": "(id: string, userId: string) => Promise", "file": "data-access.ts", "purpose": "标记单条通知已读(P0-4 / P1-5 修复后从 messaging 迁移)", "deps": [ "shared.db", "shared.db.schema.messageNotifications" ], "usedBy": [ "messaging.markNotificationAsReadAction (via re-export)" ] }, { "name": "markAllNotificationsAsRead", "signature": "(userId: string) => Promise", "file": "data-access.ts", "purpose": "标记用户所有通知已读(P0-4 / P1-5 修复后从 messaging 迁移)", "deps": [ "shared.db", "shared.db.schema.messageNotifications" ], "usedBy": [ "messaging.markAllNotificationsAsReadAction (via re-export)" ] }, { "name": "getUnreadNotificationCount", "signature": "(userId: string) => Promise", "file": "data-access.ts", "purpose": "获取用户未读通知数(P0-4 / P1-5 修复后从 messaging 迁移)", "deps": [ "shared.db", "shared.db.schema.messageNotifications", "react.cache" ], "usedBy": [ "待扩展" ] }, { "name": "getUserContactInfo", "signature": "(userId: string) => Promise", "file": "data-access.ts", "purpose": "获取用户联系方式(phone/email;wechatOpenId 暂不支持,users 表无此字段)", "deps": [ "shared.db", "shared.db.schema.users", "react.cache" ], "usedBy": [ "dispatcher.sendNotification" ] }, { "name": "logNotificationSend", "signature": "(result: ChannelSendResult) => void", "file": "data-access.ts", "purpose": "记录单条发送日志(当前使用 console.info;未来可扩展 notification_logs 表)", "deps": [], "usedBy": [ "logNotificationSendBatch" ] }, { "name": "logNotificationSendBatch", "signature": "(results: ChannelSendResult[]) => void", "file": "data-access.ts", "purpose": "批量记录发送日志", "deps": [ "logNotificationSend" ], "usedBy": [ "dispatcher.sendNotification", "dispatcher.sendBatchNotifications" ] } ], "preferences": [ { "name": "getNotificationPreferences", "signature": "(userId: string) => Promise", "file": "preferences.ts", "purpose": "获取用户通知偏好(React cache 包装;若不存在则自动创建默认记录,并发冲突回退到查询;P0-4 / P1-5 修复后从 messaging 迁移)", "deps": [ "shared.db", "shared.db.schema.notificationPreferences", "react.cache", "@paralleldrive/cuid2" ], "usedBy": [ "dispatcher.sendNotification", "messaging.getNotificationPreferencesAction (via re-export)", "settings/page.tsx (via re-export)" ] }, { "name": "upsertNotificationPreferences", "signature": "(userId: string, input: UpdateNotificationPreferencesInput) => Promise", "file": "preferences.ts", "purpose": "更新或插入用户通知偏好(存在则部分更新,不存在则插入;未提供字段保留原值;P0-4 / P1-5 修复后从 messaging 迁移)", "deps": [ "shared.db", "shared.db.schema.notificationPreferences", "@paralleldrive/cuid2" ], "usedBy": [ "messaging.updateNotificationPreferencesAction (via re-export)" ] } ], "channels": [ { "name": "createSmsSender", "file": "channels/sms-channel.ts", "purpose": "创建 SMS 渠道发送器(aliyun/tencent/mock,根据 SMS_PROVIDER 环境变量选择;SDK 动态 import)", "deps": [ "环境变量: SMS_PROVIDER, SMS_ACCESS_KEY_ID, SMS_ACCESS_KEY_SECRET, SMS_SIGN_NAME, SMS_TEMPLATE_CODE" ] }, { "name": "createWechatSender", "file": "channels/wechat-channel.ts", "purpose": "创建微信渠道发送器(配置完整用真实发送器,否则 Mock;access_token 带缓存)", "deps": [ "环境变量: WECHAT_APP_ID, WECHAT_APP_SECRET, WECHAT_TEMPLATE_ID" ] }, { "name": "createEmailSender", "file": "channels/email-channel.ts", "purpose": "创建邮件渠道发送器(配置 EMAIL_HOST 用 Nodemailer SMTP,否则 Mock;HTML 模板按 type 着色)", "deps": [ "环境变量: EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_FROM" ] }, { "name": "createInAppSender", "file": "channels/in-app-channel.ts", "purpose": "创建站内消息渠道发送器(调用 notifications.data-access.createNotification 写入 message_notifications 表;总是启用;P0-4 / P1-5 修复后改为静态导入本地 createNotification,不再动态 import messaging)", "deps": [ "data-access.createNotification" ] } ], "types": [ { "name": "NotificationChannel", "type": "type", "file": "types.ts", "definition": "'in_app' | 'email' | 'sms' | 'wechat'", "usedBy": [ "所有渠道文件", "dispatcher" ] }, { "name": "NotificationPayload", "type": "interface", "file": "types.ts", "definition": "{ userId, title, content, type: 'info'|'warning'|'error'|'success', metadata?, actionUrl? }", "usedBy": [ "dispatcher", "actions", "所有渠道" ] }, { "name": "ChannelSendResult", "type": "interface", "file": "types.ts", "definition": "{ channel, success, messageId?, error?, sentAt }", "usedBy": [ "dispatcher", "actions", "所有渠道" ] }, { "name": "NotificationChannelConfig", "type": "interface", "file": "types.ts", "definition": "{ enabled, sms?, wechat?, email? }", "usedBy": [ "类型定义" ] }, { "name": "NotificationChannelSender", "type": "interface", "file": "channels/types.ts", "definition": "{ channel: NotificationChannel, send(payload, recipient), sendBatch(items) }", "usedBy": [ "所有渠道实现", "dispatcher" ] }, { "name": "ChannelRecipient", "type": "interface", "file": "channels/types.ts", "definition": "{ userId, phone?, email?, wechatOpenId? }", "usedBy": [ "所有渠道", "data-access.getUserContactInfo" ] }, { "name": "NotificationType", "type": "type", "file": "types.ts", "definition": "'message' | 'announcement' | 'homework' | 'grade'", "usedBy": [ "data-access", "channels.in-app-channel", "messaging (via re-export)" ] }, { "name": "Notification", "type": "interface", "file": "types.ts", "definition": "{ id, userId, type: NotificationType, title, content: string | null, link: string | null, isRead, createdAt }", "usedBy": [ "data-access.getNotifications", "messaging (via re-export)" ] }, { "name": "NotificationListItem", "type": "type", "file": "types.ts", "definition": "通知列表项类型(同 Notification)", "usedBy": [ "messaging (via re-export)" ] }, { "name": "PaginatedResult", "type": "interface", "file": "types.ts", "definition": "{ items: T[], total: number, page: number, pageSize: number, totalPages: number }", "usedBy": [ "data-access.getNotifications", "messaging (via re-export)" ] }, { "name": "GetNotificationsParams", "type": "interface", "file": "types.ts", "definition": "{ page?, pageSize?, unreadOnly? }", "usedBy": [ "data-access.getNotifications", "messaging (via re-export)" ] }, { "name": "CreateNotificationInput", "type": "interface", "file": "types.ts", "definition": "{ userId, type: NotificationType, title, content?, link? }", "usedBy": [ "data-access.createNotification", "channels.in-app-channel", "messaging (via re-export)" ] }, { "name": "NotificationPreferences", "type": "interface", "file": "types.ts", "definition": "{ id, userId, emailEnabled, smsEnabled, pushEnabled, homeworkNotifications, gradeNotifications, announcementNotifications, messageNotifications, attendanceNotifications, createdAt, updatedAt }", "usedBy": [ "preferences.getNotificationPreferences", "preferences.upsertNotificationPreferences", "dispatcher.sendNotification", "messaging (via re-export)" ] }, { "name": "UpdateNotificationPreferencesInput", "type": "interface", "file": "types.ts", "definition": "{ emailEnabled?, smsEnabled?, pushEnabled?, homeworkNotifications?, gradeNotifications?, announcementNotifications?, messageNotifications?, attendanceNotifications? }", "usedBy": [ "preferences.upsertNotificationPreferences", "messaging (via re-export)" ] } ], "components": [ { "name": "NotificationList", "file": "components/notification-list.tsx", "purpose": "P1-4 新增(从 messaging 迁移):通知完整列表(全部标记已读、单条标记已读、查看链接);V2-P0-1 优化:useTranslations 命名空间从 'messages' 切换到独立的 'notifications'" }, { "name": "NotificationDropdown", "file": "components/notification-dropdown.tsx", "purpose": "P1-4 新增(从 messaging 迁移):SiteHeader 通知下拉菜单(Bell 图标 + 未读数 Badge,每 30 秒轮询,滚动列表,标记已读,查看全部链接);V2-P0-1 优化:useTranslations 命名空间从 'messages' 切换到独立的 'notifications';V2-P2-1 优化:轮询间隔提取为 POLL_INTERVAL_MS 常量" } ] } }, "attendance": { "path": "src/modules/attendance", "description": "学生考勤管理:教师按班级/日期点名(单条/批量)、查询考勤记录、统计出勤率/迟到率,学生/家长查看本人/子女考勤汇总,管理员查看全校考勤记录。支持班级考勤规则配置。", "exports": { "actions": [ { "name": "recordAttendanceAction", "permission": "ATTENDANCE_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "创建单条考勤记录", "deps": [ "requirePermission", "data-access.createAttendanceRecord", "revalidatePath" ], "usedBy": [ "attendance-record-list.tsx" ] }, { "name": "batchRecordAttendanceAction", "permission": "ATTENDANCE_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "批量点名(班级+日期,表格形式录入每个学生状态)", "deps": [ "requirePermission", "data-access.batchCreateAttendanceRecords", "revalidatePath" ], "usedBy": [ "attendance-sheet.tsx" ] }, { "name": "updateAttendanceAction", "permission": "ATTENDANCE_MANAGE", "signature": "(id: string, prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "更新考勤记录(状态、备注)", "deps": [ "requirePermission", "data-access.updateAttendanceRecord", "revalidatePath" ], "usedBy": [ "attendance-record-list.tsx" ] }, { "name": "deleteAttendanceAction", "permission": "ATTENDANCE_MANAGE", "signature": "(id: string) => Promise>", "purpose": "删除考勤记录", "deps": [ "requirePermission", "data-access.deleteAttendanceRecord", "revalidatePath" ], "usedBy": [ "attendance-record-list.tsx" ] }, { "name": "getAttendanceAction", "permission": "ATTENDANCE_READ", "signature": "(params?: AttendanceQueryParams) => Promise>", "purpose": "分页查询考勤记录(按 scope 过滤)", "deps": [ "requirePermission", "data-access.getAttendanceRecords" ], "usedBy": [], "issues": [ "P0: 无调用方——admin/teacher 页面绕过 Action 直接调用 data-access.getAttendanceRecords,违反三层架构" ] }, { "name": "getStudentAttendanceAction", "permission": "ATTENDANCE_READ", "signature": "(studentId: string) => Promise>", "purpose": "获取学生考勤汇总(含 DataScope 二次校验:class_members 仅查自己,children 仅查子女)", "deps": [ "requirePermission", "data-access-stats.getStudentAttendanceSummary" ], "usedBy": [], "issues": [ "P0: 无调用方——student/parent 页面绕过 Action 直接调用 data-access-stats.getStudentAttendanceSummary" ] }, { "name": "getClassAttendanceStatsAction", "permission": "ATTENDANCE_READ", "signature": "(classId: string, startDate?: string, endDate?: string) => Promise>", "purpose": "获取班级考勤统计", "deps": [ "requirePermission", "data-access-stats.getClassAttendanceStats" ], "usedBy": [], "issues": [ "P0: 无调用方——teacher/attendance/stats 页面绕过 Action 直接调用 data-access-stats.getClassAttendanceStats" ] }, { "name": "getClassAttendanceForDateAction", "permission": "ATTENDANCE_READ", "signature": "(classId: string, date: string) => Promise>", "purpose": "获取班级指定日期考勤(用于点名页加载已有记录)", "deps": [ "requirePermission", "data-access.getClassAttendanceForDate" ], "usedBy": [ "attendance-sheet.tsx" ] }, { "name": "saveAttendanceRulesAction", "permission": "ATTENDANCE_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "保存班级考勤规则(upsert)", "deps": [ "requirePermission", "data-access.upsertAttendanceRules", "revalidatePath" ], "usedBy": [ "attendance-rules-form.tsx" ] }, { "name": "getAttendanceRulesAction", "permission": "ATTENDANCE_READ", "signature": "(classId?: string) => Promise>", "purpose": "获取班级考勤规则", "deps": [ "requirePermission", "data-access.getAttendanceRules" ], "usedBy": [ "attendance-rules-form.tsx" ] } ], "dataAccess": [ { "name": "getAttendanceRecords", "signature": "(params: AttendanceQueryParams & { scope: DataScope; currentUserId?: string }) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords", "shared.db.schema.users", "shared.db.schema.classes", "types.DataScope" ], "usedBy": [ "getAttendanceAction" ] }, { "name": "getClassAttendanceForDate", "signature": "(classId: string, date: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords", "shared.db.schema.users", "shared.db.schema.classes" ], "usedBy": [ "getClassAttendanceForDateAction" ] }, { "name": "createAttendanceRecord", "signature": "(data: RecordAttendanceInput, recordedBy: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords", "@paralleldrive/cuid2" ], "usedBy": [ "recordAttendanceAction" ] }, { "name": "batchCreateAttendanceRecords", "signature": "(data: BatchRecordAttendanceInput, recordedBy: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords", "@paralleldrive/cuid2" ], "usedBy": [ "batchRecordAttendanceAction" ] }, { "name": "updateAttendanceRecord", "signature": "(id: string, data: UpdateAttendanceInput) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords" ], "usedBy": [ "updateAttendanceAction" ] }, { "name": "deleteAttendanceRecord", "signature": "(id: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords" ], "usedBy": [ "deleteAttendanceAction" ] }, { "name": "getClassStudentsForAttendance", "signature": "(classId: string) => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.classEnrollments", "shared.db.schema.users" ], "usedBy": [ "attendance-sheet.tsx" ], "issues": [ "P0: 跨模块直查 classEnrollments 表,违反模块间只能通过对方 data-access 通信的规则(应改为调用 classes data-access)" ] }, { "name": "getAttendanceRules", "signature": "(classId?: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRules" ], "usedBy": [ "getAttendanceRulesAction" ] }, { "name": "upsertAttendanceRules", "signature": "(data: AttendanceRuleInput) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRules", "@paralleldrive/cuid2" ], "usedBy": [ "saveAttendanceRulesAction" ] }, { "name": "getStudentAttendanceSummary", "signature": "(studentId: string, startDate?: string, endDate?: string) => Promise", "file": "data-access-stats.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords", "shared.db.schema.classes", "shared.db.schema.users" ], "usedBy": [ "getStudentAttendanceAction", "student/attendance/page.tsx", "parent/attendance/page.tsx" ] }, { "name": "getClassAttendanceStats", "signature": "(classId: string, startDate?: string, endDate?: string) => Promise", "file": "data-access-stats.ts", "deps": [ "shared.db", "shared.db.schema.attendanceRecords", "shared.db.schema.classes", "shared.db.schema.users" ], "usedBy": [ "getClassAttendanceStatsAction", "teacher/attendance/stats/page.tsx" ] }, { "name": "getAttendanceStats", "signature": "(params: { scope: DataScope; currentUserId: string; classId?: string; date?: string }) => Promise", "file": "data-access.ts", "deps": [ "data-access.getAttendanceRecords" ], "usedBy": [ "admin/attendance/page.tsx" ], "issues": [ "P0: 统计失真——调用 getAttendanceRecords(默认 pageSize=20)后对 items 聚合,仅基于前 20 条记录计算总览数据,班级/状态/日期筛选后仍只统计前 20 条" ] } ], "schemas": [ { "name": "AttendanceStatusEnum", "type": "zod", "file": "schema.ts", "definition": "enum('present','absent','late','early_leave','excused')", "usedBy": [ "RecordAttendanceSchema", "BatchRecordAttendanceSchema", "UpdateAttendanceSchema" ] }, { "name": "RecordAttendanceSchema", "type": "zod", "file": "schema.ts", "definition": "{ studentId, classId, scheduleId?, date, status, remark? }", "usedBy": [ "recordAttendanceAction" ] }, { "name": "BatchRecordAttendanceSchema", "type": "zod", "file": "schema.ts", "definition": "{ records: [{ studentId, classId, scheduleId?, date, status, remark? }] }", "usedBy": [ "batchRecordAttendanceAction" ] }, { "name": "UpdateAttendanceSchema", "type": "zod", "file": "schema.ts", "definition": "{ status?, remark?, scheduleId? }", "usedBy": [ "updateAttendanceAction" ] }, { "name": "AttendanceRuleSchema", "type": "zod", "file": "schema.ts", "definition": "{ classId, lateThresholdMinutes?, earlyLeaveThresholdMinutes?, enableAutoMark? }", "usedBy": [ "saveAttendanceRulesAction" ] } ], "types": [ { "name": "AttendanceStatus", "type": "type", "file": "types.ts", "definition": "'present' | 'absent' | 'late' | 'early_leave' | 'excused'", "usedBy": [ "attendance/data-access", "attendance/components" ] }, { "name": "AttendanceRecord", "type": "type", "file": "types.ts", "definition": "考勤记录完整类型", "usedBy": [ "attendance/data-access" ] }, { "name": "AttendanceListItem", "type": "type", "file": "types.ts", "definition": "{ id, studentId, studentName, classId, className, scheduleId, date, status, remark, recordedBy, recorderName, createdAt }", "usedBy": [ "attendance/components", "页面" ] }, { "name": "AttendanceStats", "type": "type", "file": "types.ts", "definition": "{ total, present, absent, late, earlyLeave, excused, presentRate, lateRate }", "usedBy": [ "attendance-stats-card.tsx" ] }, { "name": "StudentAttendanceSummary", "type": "type", "file": "types.ts", "definition": "{ studentId, studentName, stats: AttendanceStats, recentRecords: AttendanceListItem[] }", "usedBy": [ "student-attendance-view.tsx" ] }, { "name": "ClassAttendanceSummary", "type": "type", "file": "types.ts", "definition": "{ classId, className, date, stats: AttendanceStats, studentRecords: AttendanceListItem[] }", "usedBy": [ "teacher/attendance/stats" ] }, { "name": "AttendanceRule", "type": "type", "file": "types.ts", "definition": "{ id, classId, lateThresholdMinutes, earlyLeaveThresholdMinutes, enableAutoMark, createdAt, updatedAt }", "usedBy": [ "attendance-rules-form.tsx" ] }, { "name": "AttendanceQueryParams", "type": "type", "file": "types.ts", "definition": "{ classId?, studentId?, date?, startDate?, endDate?, status?, page?, pageSize? }", "usedBy": [ "getAttendanceRecords", "getAttendanceAction" ] }, { "name": "PaginatedAttendanceResult", "type": "type", "file": "types.ts", "definition": "{ items: AttendanceListItem[], total, page, pageSize, totalPages }", "usedBy": [ "getAttendanceRecords", "getAttendanceAction" ] }, { "name": "ATTENDANCE_STATUS_LABELS", "type": "const", "file": "types.ts", "definition": "状态中文标签常量", "usedBy": [ "attendance/components" ] }, { "name": "ATTENDANCE_STATUS_COLORS", "type": "const", "file": "types.ts", "definition": "状态颜色常量(用于 Badge)", "usedBy": [ "attendance/components" ] } ], "components": [ { "name": "AttendanceSheet", "file": "components/attendance-sheet.tsx", "purpose": "批量点名表单(班级/日期选择器 + 学生表格 + 每行状态 Select + 全部标记到场按钮)" }, { "name": "AttendanceRecordList", "file": "components/attendance-record-list.tsx", "purpose": "考勤记录列表表格(含删除确认对话框)" }, { "name": "AttendanceStatsCard", "file": "components/attendance-stats-card.tsx", "purpose": "统计卡片(总数、到场、缺勤、迟到、早退、请假、出勤率、迟到率)" }, { "name": "AttendanceStatsCards", "file": "components/attendance-stats-cards.tsx", "purpose": "管理员考勤总览页统计概览卡片组(总记录数、出勤、缺勤、迟到、早退、出勤率,6 卡片网格布局)" }, { "name": "AttendanceFilters", "file": "components/attendance-filters.tsx", "purpose": "URL 同步筛选器(班级、状态、日期)" }, { "name": "StudentAttendanceView", "file": "components/student-attendance-view.tsx", "purpose": "学生/家长视图(统计卡片 + 最近记录表格)" }, { "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" ] } ] } }, "scheduling": { "path": "src/modules/scheduling", "description": "排课与调课:管理员配置班级排课规则(每日课时、连续课时、午休、上下学时间、避免背靠背、科目均衡),自动排课引擎按规则生成周课表,调课/代课申请与审批流程,课表冲突检测。", "exports": { "actions": [ { "name": "saveSchedulingRulesAction", "permission": "SCHEDULE_ADJUST", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "保存班级排课规则(upsert,classId 为空时为全局规则)", "deps": [ "requirePermission", "data-access.upsertSchedulingRules", "revalidatePath" ], "usedBy": [ "scheduling-rules-form.tsx" ] }, { "name": "autoScheduleAction", "permission": "SCHEDULE_AUTO", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "根据规则与科目分配生成预览课表(不落库)", "deps": [ "requirePermission", "data-access.getSchedulingRules", "data-access.getClassSubjectsForScheduling", "data-access.getClassroomsForScheduling", "auto-scheduler.autoSchedule", "auto-scheduler.buildDefaultTimeSlots", "revalidatePath" ], "usedBy": [ "auto-schedule-panel.tsx" ] }, { "name": "applyAutoScheduleAction", "permission": "SCHEDULE_AUTO", "signature": "(classId: string, schedules: Array<{ weekday, startTime, endTime, course, location }>) => Promise>", "purpose": "将生成的课表写入 classSchedule 表(先删除该班旧课表再插入新课表,事务)", "deps": [ "requirePermission", "shared.db", "shared.db.schema.classSchedule", "@paralleldrive/cuid2", "revalidatePath" ], "usedBy": [ "auto-schedule-panel.tsx" ] }, { "name": "requestScheduleChangeAction", "permission": "SCHEDULE_ADJUST", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "purpose": "提交调课/代课申请(status=pending)", "deps": [ "requirePermission", "data-access.createScheduleChange", "revalidatePath" ], "usedBy": [ "schedule-change-form.tsx" ] }, { "name": "approveScheduleChangeAction", "permission": "SCHEDULE_AUTO", "signature": "(changeId: string) => Promise", "purpose": "审批通过调课申请(status=approved)", "deps": [ "requirePermission", "data-access.updateScheduleChangeStatus", "revalidatePath" ], "usedBy": [ "schedule-change-list.tsx" ] }, { "name": "rejectScheduleChangeAction", "permission": "SCHEDULE_AUTO", "signature": "(changeId: string, reason?: string) => Promise", "purpose": "驳回调课申请(status=rejected)", "deps": [ "requirePermission", "data-access.updateScheduleChangeStatus", "revalidatePath" ], "usedBy": [ "schedule-change-list.tsx" ] }, { "name": "getScheduleChangesAction", "permission": "SCHEDULE_ADJUST", "signature": "(params: ScheduleChangeQueryParams) => Promise>", "purpose": "查询调课申请列表(可按 classId/status/requesterId 过滤)", "deps": [ "requirePermission", "data-access.getScheduleChanges" ], "usedBy": [ "admin/scheduling/changes/page.tsx", "teacher/schedule-changes/page.tsx" ] }, { "name": "getClassConflictsAction", "permission": "SCHEDULE_ADJUST", "signature": "(classId: string) => Promise>", "purpose": "检测班级课表时间重叠冲突", "deps": [ "requirePermission", "data-access.getClassConflicts" ], "usedBy": [ "schedule-conflicts-view.tsx" ] } ], "dataAccess": [ { "name": "getSchedulingRules", "signature": "(classId?: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.schedulingRules" ], "usedBy": [ "saveSchedulingRulesAction", "autoScheduleAction", "admin/scheduling/rules/page.tsx" ] }, { "name": "upsertSchedulingRules", "signature": "(data: SchedulingRuleInput) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.schedulingRules", "@paralleldrive/cuid2" ], "usedBy": [ "saveSchedulingRulesAction" ] }, { "name": "getScheduleChanges", "signature": "(params: ScheduleChangeQueryParams) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.scheduleChanges", "shared.db.schema.classes", "shared.db.schema.users" ], "usedBy": [ "getScheduleChangesAction", "admin/scheduling/changes/page.tsx", "teacher/schedule-changes/page.tsx" ] }, { "name": "createScheduleChange", "signature": "(data: ScheduleChangeInput, requestedBy: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.scheduleChanges", "@paralleldrive/cuid2" ], "usedBy": [ "requestScheduleChangeAction" ] }, { "name": "updateScheduleChangeStatus", "signature": "(id: string, status: 'approved'|'rejected'|'completed', approverId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.scheduleChanges" ], "usedBy": [ "approveScheduleChangeAction", "rejectScheduleChangeAction" ] }, { "name": "getClassConflicts", "signature": "(classId: string) => Promise", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.classSchedule" ], "usedBy": [ "getClassConflictsAction" ] }, { "name": "getAdminClassesForScheduling", "signature": "() => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.classes" ], "usedBy": [ "admin/scheduling/rules/page.tsx", "admin/scheduling/auto/page.tsx", "admin/scheduling/changes/page.tsx", "teacher/schedule-changes/page.tsx" ] }, { "name": "getTeachersForScheduling", "signature": "() => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.users", "shared.db.schema.classSubjectTeachers" ], "usedBy": [ "teacher/schedule-changes/page.tsx" ] }, { "name": "getClassroomsForScheduling", "signature": "() => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.classrooms" ], "usedBy": [ "autoScheduleAction" ] }, { "name": "getClassSubjectsForScheduling", "signature": "(classId: string) => Promise>", "file": "data-access.ts", "deps": [ "shared.db", "shared.db.schema.classSubjectTeachers", "shared.db.schema.subjects" ], "usedBy": [ "autoScheduleAction" ] }, { "name": "createClassScheduleItem", "signature": "(data: CreateClassScheduleItemInput) => Promise", "file": "data-access-class-schedule.ts", "purpose": "创建课表项(P0-5 从 classes 模块迁移,含教师班级归属校验)", "deps": [ "classes/data-access.getTeacherIdForMutations", "classes/data-access.verifyTeacherOwnsClass", "data-access.insertClassScheduleItem" ], "usedBy": [ "classes/actions.createClassScheduleItemAction" ] }, { "name": "updateClassScheduleItem", "signature": "(scheduleId: string, data: UpdateClassScheduleItemInput) => Promise", "file": "data-access-class-schedule.ts", "purpose": "更新课表项(P0-5 从 classes 模块迁移,含教师班级归属校验)", "deps": [ "classes/data-access.getTeacherIdForMutations", "classes/data-access.verifyTeacherOwnsClass", "data-access.updateClassScheduleItemById" ], "usedBy": [ "classes/actions.updateClassScheduleItemAction" ] }, { "name": "deleteClassScheduleItem", "signature": "(scheduleId: string) => Promise", "file": "data-access-class-schedule.ts", "purpose": "删除课表项(P0-5 从 classes 模块迁移,含教师班级归属校验)", "deps": [ "classes/data-access.getTeacherIdForMutations", "classes/data-access.verifyTeacherOwnsClass", "data-access.deleteClassScheduleItemById" ], "usedBy": [ "classes/actions.deleteClassScheduleItemAction" ] } ], "autoScheduler": [ { "name": "autoSchedule", "signature": "(params: AutoScheduleParams) => AutoScheduleResult", "file": "auto-scheduler.ts", "purpose": "贪心+冲突检测排课算法:按科目每周课时降序,为每节课选择第一个满足约束的时段(午休、每日窗口、班级/教师/教室冲突、每日最大课时、避免背靠背)", "deps": [ "findOptimalSlot", "validateSchedule" ], "usedBy": [ "autoScheduleAction" ] }, { "name": "findOptimalSlot", "signature": "(args) => TimeSlot | null", "file": "auto-scheduler.ts", "purpose": "在候选时段中找到第一个满足所有约束的时段", "usedBy": [ "autoSchedule" ] }, { "name": "validateSchedule", "signature": "(schedules: GeneratedSchedule[], rules: SchedulingRule) => ScheduleConflict[]", "file": "auto-scheduler.ts", "purpose": "校验生成的课表是否违反规则,返回冲突列表", "usedBy": [ "autoSchedule" ] }, { "name": "buildDefaultTimeSlots", "signature": "(morningStart, afternoonEnd, lunchBreakStart, lunchBreakEnd) => TimeSlot[]", "file": "auto-scheduler.ts", "purpose": "根据上下学时间和午休时间构建默认时段(周一至周五,上午4节+下午4节)", "usedBy": [ "autoScheduleAction" ] } ], "schemas": [ { "name": "SchedulingRuleSchema", "type": "zod", "file": "schema.ts", "definition": "{ classId, maxDailyHours?, maxContinuousHours?, lunchBreakStart?, lunchBreakEnd?, morningStart?, afternoonEnd?, avoidBackToBack?, balancedSubjects? }", "usedBy": [ "saveSchedulingRulesAction" ] }, { "name": "ScheduleChangeSchema", "type": "zod", "file": "schema.ts", "definition": "{ classId, originalScheduleId?, originalTeacherId?, substituteTeacherId?, originalDate?, newDate?, newStartTime?, newEndTime?, reason }", "usedBy": [ "requestScheduleChangeAction" ] }, { "name": "AutoScheduleParamsSchema", "type": "zod", "file": "schema.ts", "definition": "{ classId, rules: SchedulingRuleSchema, subjects: [{ subjectId, subjectName, weeklyHours, teacherId? }], teachers: [{ id, name }], classrooms: [{ id, name }], timeSlots: [{ weekday, startTime, endTime }] }", "usedBy": [ "autoScheduleAction" ] }, { "name": "ScheduleChangeStatusEnum", "type": "zod", "file": "schema.ts", "definition": "enum('pending','approved','rejected','completed')", "usedBy": [ "ScheduleChangeSchema" ] }, { "name": "ApproveScheduleChangeSchema", "type": "zod", "file": "schema.ts", "definition": "{ changeId, reason? }", "usedBy": [ "approveScheduleChangeAction" ] } ], "types": [ { "name": "ScheduleChangeStatus", "type": "type", "file": "types.ts", "definition": "'pending' | 'approved' | 'rejected' | 'completed'", "usedBy": [ "scheduling/data-access", "scheduling/components" ] }, { "name": "Weekday", "type": "type", "file": "types.ts", "definition": "1 | 2 | 3 | 4 | 5 | 6 | 7", "usedBy": [ "CreateClassScheduleItemInput", "UpdateClassScheduleItemInput" ] }, { "name": "CreateClassScheduleItemInput", "type": "type", "file": "types.ts", "definition": "{ classId, weekday: Weekday, startTime, endTime, course, location? }(P0-5 从 classes/types.ts 迁移)", "usedBy": [ "scheduling/data-access-class-schedule.createClassScheduleItem" ] }, { "name": "UpdateClassScheduleItemInput", "type": "type", "file": "types.ts", "definition": "{ classId?, weekday?: Weekday, startTime?, endTime?, course?, location? }(P0-5 从 classes/types.ts 迁移)", "usedBy": [ "scheduling/data-access-class-schedule.updateClassScheduleItem" ] }, { "name": "SchedulingRule", "type": "type", "file": "types.ts", "definition": "{ id, classId, maxDailyHours, maxContinuousHours, lunchBreakStart, lunchBreakEnd, morningStart, afternoonEnd, avoidBackToBack, balancedSubjects, createdAt, updatedAt }", "usedBy": [ "scheduling-rules-form.tsx", "auto-scheduler.ts" ] }, { "name": "ScheduleChange", "type": "type", "file": "types.ts", "definition": "调课申请完整类型", "usedBy": [ "scheduling/data-access" ] }, { "name": "ScheduleChangeListItem", "type": "type", "file": "types.ts", "definition": "{ ...ScheduleChange, className, originalTeacherName, substituteTeacherName, requesterName, approverName }", "usedBy": [ "schedule-change-list.tsx", "页面" ] }, { "name": "TimeSlot", "type": "type", "file": "types.ts", "definition": "{ weekday, startTime, endTime }", "usedBy": [ "auto-scheduler.ts" ] }, { "name": "ScheduleConflict", "type": "type", "file": "types.ts", "definition": "{ type: 'teacher_overlap'|'classroom_overlap'|'class_overlap'|'rule_violation', description, scheduleIds }", "usedBy": [ "auto-schedule-result.tsx", "schedule-conflicts-view.tsx" ] }, { "name": "AutoScheduleResult", "type": "type", "file": "types.ts", "definition": "{ success, scheduledCount, conflictCount, conflicts, schedules: GeneratedSchedule[] }", "usedBy": [ "auto-schedule-panel.tsx", "auto-schedule-result.tsx" ] }, { "name": "GeneratedSchedule", "type": "type", "file": "types.ts", "definition": "{ classId, weekday, startTime, endTime, course, location, teacherId, subjectId }", "usedBy": [ "auto-scheduler.ts", "auto-schedule-result.tsx" ] }, { "name": "AutoScheduleParams", "type": "type", "file": "types.ts", "definition": "{ classId, rules, subjects, teachers, classrooms, timeSlots }", "usedBy": [ "auto-scheduler.ts", "autoScheduleAction" ] }, { "name": "ScheduleChangeQueryParams", "type": "type", "file": "types.ts", "definition": "{ classId?, status?, requesterId? }", "usedBy": [ "getScheduleChanges", "getScheduleChangesAction" ] }, { "name": "SCHEDULE_CHANGE_STATUS_LABELS", "type": "const", "file": "types.ts", "definition": "状态英文标签常量", "usedBy": [ "schedule-change-list.tsx" ] }, { "name": "SCHEDULE_CHANGE_STATUS_COLORS", "type": "const", "file": "types.ts", "definition": "状态颜色常量(用于 Badge)", "usedBy": [ "schedule-change-list.tsx" ] } ], "components": [ { "name": "SchedulingRulesForm", "file": "components/scheduling-rules-form.tsx", "purpose": "排课规则配置表单(班级选择器、每日最大课时、连续课时、午休时间、上下学时间、避免背靠背、科目均衡)" }, { "name": "AutoSchedulePanel", "file": "components/auto-schedule-panel.tsx", "purpose": "自动排课面板(班级选择→预览→应用流程,调用 autoScheduleAction 和 applyAutoScheduleAction)" }, { "name": "AutoScheduleResultView", "file": "components/auto-schedule-result.tsx", "purpose": "排课结果预览(课表表格 + 冲突/警告列表)" }, { "name": "ScheduleChangeForm", "file": "components/schedule-change-form.tsx", "purpose": "调课/代课申请表单(班级、原任课教师、代课教师、原日期、新日期、新时间、原因)" }, { "name": "ScheduleChangeList", "file": "components/schedule-change-list.tsx", "purpose": "调课申请列表表格(含审批/驳回对话框,canApprove 控制是否显示审批按钮)" }, { "name": "ScheduleConflictsView", "file": "components/schedule-conflicts-view.tsx", "purpose": "冲突检测视图(班级选择器 + 检测按钮 + 冲突结果列表)" } ] } }, "proctoring": { "path": "src/modules/proctoring", "description": "考试监考模块:监考模式考试实时监控、防作弊事件采集、教师监考面板、学生端防作弊监控、考试模式配置", "exports": { "actions": [ { "name": "recordProctoringEventAction", "permission": "EXAM_SUBMIT", "signature": "(prevState: ActionState<{id:string}> | null, formData: FormData) => Promise>", "purpose": "学生端上报监考事件(含 submission 归属校验,刷新监考面板缓存)", "deps": [ "requirePermission(EXAM_SUBMIT)", "data-access.getExamSubmissionForProctoring", "data-access.recordProctoringEvent", "revalidatePath" ], "usedBy": [ "anti-cheat-monitor.tsx" ] }, { "name": "getProctoringDashboardAction", "permission": "EXAM_PROCTOR", "signature": "(examId: string) => Promise>", "purpose": "获取监考面板数据(摘要+学生状态+最近事件)", "deps": [ "requirePermission(EXAM_PROCTOR)", "data-access.getExamForProctoring,getExamProctoringSummary,getStudentProctoringStatuses,getRecentProctoringEvents" ], "usedBy": [ "proctoring-dashboard.tsx" ] } ], "dataAccess": [ { "name": "getExamSubmissionForProctoring", "signature": "(submissionId: string, studentId: string) => Promise<{id,examId,studentId} | null>", "purpose": "校验提交记录归属(监考事件上报前的安全校验)", "usedBy": [ "actions.recordProctoringEventAction" ] }, { "name": "recordProctoringEvent", "signature": "(input: RecordProctoringEventInput) => Promise", "purpose": "记录一条监考事件", "usedBy": [ "actions.recordProctoringEventAction", "api/proctoring/event/route.ts" ] }, { "name": "getProctoringEvents", "signature": "(examId: string, filters?: GetProctoringEventsFilters) => Promise", "purpose": "查询考试监考事件(含学生姓名、考试标题)", "usedBy": [ "待扩展" ] }, { "name": "getProctoringEventsBySubmission", "signature": "(submissionId: string) => Promise", "purpose": "查询提交的监考事件", "usedBy": [ "待扩展" ] }, { "name": "getExamProctoringSummary", "signature": "(examId: string) => Promise", "purpose": "获取考试监考摘要", "usedBy": [ "actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx" ] }, { "name": "getStudentProctoringStatuses", "signature": "(examId: string) => Promise", "purpose": "获取所有学生监考状态(v3 优化:Promise.all 并行执行 getUserNamesByIds 与事件聚合查询)", "usedBy": [ "actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx" ] }, { "name": "getExamForProctoring", "signature": "(examId: string) => Promise<{id,title,examMode,config} | null>", "purpose": "获取考试信息(含 examMode 设置)", "usedBy": [ "actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx" ] }, { "name": "getRecentProctoringEvents", "signature": "(examId: string, limit?: number) => Promise", "purpose": "获取最近 N 条监考事件", "usedBy": [ "actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx" ] } ], "types": [ { "name": "ProctoringEventType", "type": "type", "definition": "\"tab_switch\" | \"window_blur\" | \"copy_attempt\" | \"paste_attempt\" | \"right_click\" | \"devtools_open\" | \"fullscreen_exit\" | \"idle_timeout\"" }, { "name": "ExamMode", "type": "type", "definition": "\"homework\" | \"timed\" | \"proctored\"" }, { "name": "ProctoringEvent", "type": "interface", "definition": "{ id, submissionId, studentId, examId, eventType, eventDetail?, occurredAt, createdAt }" }, { "name": "ProctoringEventWithDetails", "type": "interface", "definition": "ProctoringEvent & { studentName, examTitle }" }, { "name": "ExamProctoringSummary", "type": "interface", "definition": "{ examId, examTitle, examMode, totalStudents, startedStudents, submittedStudents, totalEvents, abnormalStudents, eventsByType }" }, { "name": "StudentProctoringStatus", "type": "interface", "definition": "{ studentId, studentName, submissionId, submissionStatus, eventCount, lastEventAt, isAbnormal, eventsByType }" }, { "name": "ProctoringDashboardData", "type": "interface", "definition": "{ summary, students, recentEvents }" }, { "name": "ExamModeConfig", "type": "interface", "definition": "{ examMode, durationMinutes, shuffleQuestions, allowLateStart, lateStartGraceMinutes, antiCheatEnabled }" }, { "name": "PROCTORING_EVENT_LABELS", "type": "const", "description": "事件类型中文标签常量" }, { "name": "EXAM_MODE_LABELS", "type": "const", "description": "考试模式中文标签常量" }, { "name": "ABNORMAL_EVENT_THRESHOLD", "type": "const", "description": "异常学生事件数阈值(3)" } ], "components": [ { "name": "ProctoringDashboard", "file": "components/proctoring-dashboard.tsx", "purpose": "教师监考面板(实时学生状态、异常事件统计、异常学生高亮、10 秒轮询、usePermission 权限控制)" }, { "name": "AntiCheatMonitor", "file": "components/anti-cheat-monitor.tsx", "purpose": "学生端防作弊监控(visibilitychange/blur/copy/paste/contextmenu/keydown/fullscreenchange 监听、空闲超时检测、强制全屏、警告提示、事件上报)" }, { "name": "ExamModeConfig", "file": "components/exam-mode-config.tsx", "purpose": "考试模式配置(react-hook-form Controller,作业/限时/监考模式选择,限时设置时长,监考设置防作弊选项)" } ] } }, "diagnostic": { "path": "src/modules/diagnostic", "description": "学情诊断报告模块:基于知识点掌握度(knowledgePointMastery)生成个人/班级诊断报告,掌握度雷达图(学生 vs 班级平均),强项/弱项分析,知识点掌握度热力图,需重点关注学生列表,报告发布/删除管理", "exports": { "dataAccess": [ { "name": "getStudentMastery", "signature": "(studentId: string) => Promise", "file": "data-access.ts", "purpose": "获取学生在所有知识点的掌握度(含知识点名称,按掌握度降序)", "deps": [ "shared.db", "shared.db.schema.knowledgePointMastery", "shared.db.schema.knowledgePoints" ], "usedBy": [ "data-access.getStudentMasterySummary", "teacher/diagnostic/student/[studentId]/page.tsx" ] }, { "name": "getStudentMasterySummary", "signature": "(studentId: string) => Promise", "file": "data-access.ts", "purpose": "获取学生掌握度摘要(平均掌握度、强项≥80%、弱项<60%)", "deps": [ "shared.db", "shared.db.schema.users", "data-access.getStudentMastery" ], "usedBy": [ "data-access-reports.generateDiagnosticReport", "teacher/diagnostic/student/[studentId]/page.tsx", "student/diagnostic/page.tsx" ] }, { "name": "updateMasteryFromSubmission", "signature": "(submissionId: string) => Promise", "file": "data-access.ts", "purpose": "从提交答案更新掌握度(按知识点聚合正确率,onDuplicateKeyUpdate upsert;v3 优化:Promise.all 并行执行多个知识点 upsert)", "deps": [ "shared.db", "shared.db.schema.examSubmissions", "shared.db.schema.submissionAnswers", "shared.db.schema.questionsToKnowledgePoints", "shared.db.schema.knowledgePointMastery" ], "usedBy": [ "待扩展(作业/考试提交后触发)" ] }, { "name": "getClassMasterySummary", "signature": "(classId: string) => Promise", "file": "data-access.ts", "purpose": "获取班级掌握度摘要(学生数、平均掌握度、知识点统计、需重点关注学生;v3 优化:两阶段 Promise.all 并行查询班级信息+学生 ID、用户名+掌握度)", "deps": [ "shared.db", "shared.db.schema.classes", "shared.db.schema.classEnrollments", "shared.db.schema.users", "shared.db.schema.knowledgePointMastery", "shared.db.schema.knowledgePoints" ], "usedBy": [ "data-access-reports.generateClassDiagnosticReport", "teacher/diagnostic/class/[classId]/page.tsx" ] }, { "name": "getKnowledgePointStats", "signature": "(classId?: string, gradeId?: string) => Promise", "file": "data-access.ts", "purpose": "获取知识点统计(按班级或年级聚合平均掌握度、掌握人数、未掌握人数)", "deps": [ "shared.db", "shared.db.schema.classEnrollments", "shared.db.schema.users", "shared.db.schema.knowledgePointMastery", "shared.db.schema.knowledgePoints" ], "usedBy": [ "teacher/diagnostic/student/[studentId]/page.tsx (班级平均对比)" ] }, { "name": "generateDiagnosticReport", "signature": "(studentId: string, period: string, generatedBy: string) => Promise", "file": "data-access-reports.ts", "purpose": "生成个人诊断报告(计算 overallScore、强项/弱项列表、复习建议,status=draft)", "deps": [ "shared.db", "shared.db.schema.learningDiagnosticReports", "data-access.getStudentMasterySummary", "@paralleldrive/cuid2" ], "usedBy": [ "actions.generateStudentReportAction" ] }, { "name": "generateClassDiagnosticReport", "signature": "(classId: string, period: string, generatedBy: string) => Promise", "file": "data-access-reports.ts", "purpose": "生成班级诊断报告(聚合班级掌握度,识别薄弱知识点,status=draft,studentId 存生成者 ID)", "deps": [ "shared.db", "shared.db.schema.learningDiagnosticReports", "data-access.getClassMasterySummary", "@paralleldrive/cuid2" ], "usedBy": [ "actions.generateClassReportAction" ] }, { "name": "getDiagnosticReports", "signature": "(filters: DiagnosticReportQueryParams) => Promise", "file": "data-access-reports.ts", "purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名;v3 修复:conditions 显式标注 SQL[] 类型,移除 round2 死代码)", "deps": [ "shared.db", "shared.db.schema.learningDiagnosticReports", "shared.db.schema.users" ], "usedBy": [ "actions.getDiagnosticReportsAction", "teacher/diagnostic/page.tsx", "teacher/diagnostic/student/[studentId]/page.tsx", "student/diagnostic/page.tsx" ] }, { "name": "getDiagnosticReportById", "signature": "(id: string) => Promise", "file": "data-access-reports.ts", "purpose": "获取报告详情(含学生名和生成者名)", "deps": [ "shared.db", "shared.db.schema.learningDiagnosticReports", "shared.db.schema.users" ], "usedBy": [ "actions.getDiagnosticReportByIdAction" ] }, { "name": "publishDiagnosticReport", "signature": "(id: string) => Promise", "file": "data-access-reports.ts", "purpose": "发布诊断报告(status=published)", "deps": [ "shared.db", "shared.db.schema.learningDiagnosticReports" ], "usedBy": [ "actions.publishReportAction" ] }, { "name": "deleteDiagnosticReport", "signature": "(id: string) => Promise", "file": "data-access-reports.ts", "purpose": "删除诊断报告", "deps": [ "shared.db", "shared.db.schema.learningDiagnosticReports" ], "usedBy": [ "actions.deleteReportAction" ] } ], "actions": [ { "name": "generateStudentReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "生成学生个人诊断报告(formData: studentId, period)", "deps": [ "requirePermission", "data-access-reports.generateDiagnosticReport", "revalidatePath" ], "usedBy": [ "components/student-diagnostic-view.tsx" ] }, { "name": "generateClassReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "生成班级诊断报告(formData: classId, period)", "deps": [ "requirePermission", "data-access-reports.generateClassDiagnosticReport", "revalidatePath" ], "usedBy": [ "components/class-diagnostic-view.tsx" ] }, { "name": "publishReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "发布诊断报告(formData: id)", "deps": [ "requirePermission", "data-access-reports.publishDiagnosticReport", "revalidatePath" ], "usedBy": [ "components/report-list.tsx" ] }, { "name": "deleteReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "删除诊断报告(formData: id)", "deps": [ "requirePermission", "data-access-reports.deleteDiagnosticReport", "revalidatePath" ], "usedBy": [ "components/report-list.tsx" ] }, { "name": "getDiagnosticReportsAction", "permission": "DIAGNOSTIC_READ", "signature": "(params: DiagnosticReportQueryParams) => Promise>", "file": "actions.ts", "purpose": "查询诊断报告列表(读权限)", "deps": [ "requirePermission", "data-access-reports.getDiagnosticReports" ], "usedBy": [ "待扩展" ] }, { "name": "getDiagnosticReportByIdAction", "permission": "DIAGNOSTIC_READ", "signature": "(id: string) => Promise>", "file": "actions.ts", "purpose": "获取诊断报告详情(读权限)", "deps": [ "requirePermission", "data-access-reports.getDiagnosticReportById" ], "usedBy": [ "待扩展" ] } ], "schema": [ { "name": "GenerateStudentReportSchema", "type": "const", "file": "schema.ts", "description": "zod schema 生成学生个人诊断报告(studentId, period)", "usedBy": [ "actions.generateStudentReportAction" ] }, { "name": "GenerateClassReportSchema", "type": "const", "file": "schema.ts", "description": "zod schema 生成班级诊断报告(classId, period)", "usedBy": [ "actions.generateClassReportAction" ] }, { "name": "PublishReportSchema", "type": "const", "file": "schema.ts", "description": "zod schema 发布诊断报告(id)", "usedBy": [ "actions.publishReportAction" ] }, { "name": "DeleteReportSchema", "type": "const", "file": "schema.ts", "description": "zod schema 删除诊断报告(id)", "usedBy": [ "actions.deleteReportAction" ] }, { "name": "GetDiagnosticReportsSchema", "type": "const", "file": "schema.ts", "description": "zod schema 查询诊断报告列表(studentId?, reportType?, status?, period?)", "usedBy": [ "actions.getDiagnosticReportsAction" ] }, { "name": "GetDiagnosticReportByIdSchema", "type": "const", "file": "schema.ts", "description": "zod schema 获取诊断报告详情(id)", "usedBy": [ "actions.getDiagnosticReportByIdAction" ] } ], "types": [ { "name": "DiagnosticReportType", "type": "type", "file": "types.ts", "definition": "\"individual\" | \"class\" | \"grade\"", "usedBy": [ "types.DiagnosticReport.reportType", "actions", "components/report-list.tsx" ] }, { "name": "DiagnosticReportStatus", "type": "type", "file": "types.ts", "definition": "\"draft\" | \"published\" | \"archived\"", "usedBy": [ "types.DiagnosticReport.status", "actions", "components/report-list.tsx" ] }, { "name": "KnowledgePointMastery", "type": "interface", "file": "types.ts", "definition": "{ id, studentId, knowledgePointId, masteryLevel(0-100), totalQuestions, correctQuestions, lastAssessedAt, createdAt, updatedAt }", "usedBy": [ "data-access", "types.MasteryWithKnowledgePoint" ] }, { "name": "MasteryWithKnowledgePoint", "type": "interface", "file": "types.ts", "definition": "KnowledgePointMastery & { knowledgePointName, knowledgePointDescription }", "usedBy": [ "data-access.getStudentMastery", "types.StudentMasterySummary" ] }, { "name": "StudentMasterySummary", "type": "interface", "file": "types.ts", "definition": "{ studentId, studentName, averageMastery, totalKnowledgePoints, strengths(≥80), weaknesses(<60), allMastery }", "usedBy": [ "data-access.getStudentMasterySummary", "data-access-reports.generateDiagnosticReport", "components/student-diagnostic-view.tsx" ] }, { "name": "DiagnosticReport", "type": "interface", "file": "types.ts", "definition": "{ id, studentId, generatedBy, reportType, period, summary, strengths[], weaknesses[], recommendations[], overallScore, status, createdAt, updatedAt }", "usedBy": [ "data-access-reports", "types.DiagnosticReportWithDetails" ] }, { "name": "DiagnosticReportWithDetails", "type": "interface", "file": "types.ts", "definition": "DiagnosticReport & { studentName, generatedByName }", "usedBy": [ "data-access-reports.getDiagnosticReports", "actions", "components/report-list.tsx", "components/student-diagnostic-view.tsx" ] }, { "name": "ClassMasterySummary", "type": "interface", "file": "types.ts", "definition": "{ classId, className, studentCount, averageMastery, knowledgePointStats[], studentsNeedingAttention[] }", "usedBy": [ "data-access.getClassMasterySummary", "data-access-reports.generateClassDiagnosticReport", "components/class-diagnostic-view.tsx" ] }, { "name": "KnowledgePointStat", "type": "interface", "file": "types.ts", "definition": "{ knowledgePointId, knowledgePointName, averageMastery, masteredCount(≥80), notMasteredCount(<60), totalStudents }", "usedBy": [ "data-access.getKnowledgePointStats", "types.ClassMasterySummary", "components/class-diagnostic-view.tsx" ] }, { "name": "DiagnosticReportQueryParams", "type": "interface", "file": "types.ts", "definition": "{ studentId?, reportType?, status?, period? }", "usedBy": [ "data-access-reports.getDiagnosticReports", "actions.getDiagnosticReportsAction" ] }, { "name": "MasteryRadarPoint", "type": "interface", "file": "types.ts", "definition": "{ knowledgePoint, student(0-100), classAverage?(0-100) }", "usedBy": [ "components/mastery-radar-chart.tsx", "components/student-diagnostic-view.tsx", "teacher/diagnostic/student/[studentId]/page.tsx" ] } ], "components": [ { "name": "MasteryRadarChart", "file": "components/mastery-radar-chart.tsx", "purpose": "知识点掌握度雷达图(recharts RadarChart,学生 vs 班级平均对比,无数据时显示 EmptyState)", "deps": [ "recharts", "shared/components/ui/card", "shared/components/ui/chart", "shared/components/ui/empty-state" ] }, { "name": "StudentDiagnosticView", "file": "components/student-diagnostic-view.tsx", "purpose": "学生诊断视图(概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示)", "deps": [ "usePermission", "actions.generateStudentReportAction", "components/mastery-radar-chart", "shared/components/ui/*" ] }, { "name": "ClassDiagnosticView", "file": "components/class-diagnostic-view.tsx", "purpose": "班级诊断视图(概览卡片、知识点掌握度热力图[绿/黄/橙/红]、知识点排名表、需重点关注学生表[链接到学生视图]、生成班级报告表单[DIAGNOSTIC_MANAGE])", "deps": [ "usePermission", "actions.generateClassReportAction", "shared/components/ui/*" ] }, { "name": "ReportList", "file": "components/report-list.tsx", "purpose": "诊断报告列表(reportType/status 过滤器[URL searchParams]、报告表格、发布/删除操作[DIAGNOSTIC_MANAGE]、确认对话框)", "deps": [ "usePermission", "actions.publishReportAction", "actions.deleteReportAction", "shared/components/ui/*" ] } ] } }, "elective": { "path": "src/modules/elective", "description": "选课管理模块:选修课程 CRUD、选课开放/关闭、学生选课/退课、抽签模式批量录取、FCFS 即时录取、DataScope 行级过滤(admin 全部、teacher 所教、grade_head 所管年级、student 可选课程)", "exports": { "actions": [ { "name": "createElectiveCourseAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "创建选修课程(formData: name, subjectId?, teacherId, gradeId?, description?, capacity?, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, selectionMode?, credit?)", "deps": [ "requirePermission(ELECTIVE_MANAGE)", "data-access.createElectiveCourse", "revalidatePath" ], "usedBy": [ "admin/elective/create/page.tsx" ] }, { "name": "updateElectiveCourseAction", "permission": "ELECTIVE_MANAGE", "signature": "(id: string, prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "更新选修课程", "deps": [ "requirePermission(ELECTIVE_MANAGE)", "data-access.getElectiveCourseById", "data-access.updateElectiveCourse", "revalidatePath" ], "usedBy": [ "admin/elective/[id]/edit/page.tsx" ] }, { "name": "deleteElectiveCourseAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "删除选修课程(formData: courseId)", "deps": [ "requirePermission(ELECTIVE_MANAGE)", "data-access.deleteElectiveCourse", "revalidatePath" ], "usedBy": [ "components/elective-course-list.tsx" ] }, { "name": "openSelectionAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "开放选课(formData: courseId)", "deps": [ "requirePermission(ELECTIVE_MANAGE)", "data-access.openSelection", "revalidatePath" ], "usedBy": [ "components/elective-course-list.tsx" ] }, { "name": "closeSelectionAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "关闭选课(formData: courseId)", "deps": [ "requirePermission(ELECTIVE_MANAGE)", "data-access.closeSelection", "revalidatePath" ], "usedBy": [ "components/elective-course-list.tsx" ] }, { "name": "runLotteryAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState<{enrolled:number,waitlist:number}> | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "执行抽签录取(formData: courseId)", "deps": [ "requirePermission(ELECTIVE_MANAGE)", "data-access-operations.runLottery", "revalidatePath" ], "usedBy": [ "components/elective-course-list.tsx" ] }, { "name": "selectCourseAction", "permission": "ELECTIVE_SELECT", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "学生选课(formData: courseId, priority?)", "deps": [ "requirePermission(ELECTIVE_SELECT)", "data-access-operations.selectCourse", "revalidatePath" ], "usedBy": [ "components/student-selection-view.tsx" ] }, { "name": "dropCourseAction", "permission": "ELECTIVE_SELECT", "signature": "(prevState: ActionState | null, formData: FormData) => Promise>", "file": "actions.ts", "purpose": "学生退课(formData: courseId)", "deps": [ "requirePermission(ELECTIVE_SELECT)", "data-access-operations.dropCourse", "revalidatePath" ], "usedBy": [ "components/student-selection-view.tsx" ] }, { "name": "getElectiveCoursesAction", "permission": "ELECTIVE_READ", "signature": "(params?: GetElectiveCoursesParams) => Promise>", "file": "actions.ts", "purpose": "查询选修课程列表(按 DataScope 过滤)", "deps": [ "requirePermission(ELECTIVE_READ)", "data-access.getElectiveCourses (scope, currentUserId)" ], "usedBy": [], "issues": [ "P0: 无调用方——admin/teacher 页面绕过 Action 直接调用 data-access.getElectiveCourses,违反三层架构" ] }, { "name": "getStudentSelectionsAction", "permission": "ELECTIVE_READ", "signature": "(studentId: string) => Promise>", "file": "actions.ts", "purpose": "查询学生选课记录(含 DataScope 二次校验:class_members 仅自己,children 仅子女)", "deps": [ "requirePermission(ELECTIVE_READ)", "data-access-selections.getStudentSelections" ], "usedBy": [], "issues": [ "P0: 无调用方——页面绕过 Action 直接调用 data-access-selections.getStudentSelections" ] }, { "name": "getAvailableCoursesAction", "permission": "ELECTIVE_SELECT", "signature": "() => Promise>", "file": "actions.ts", "purpose": "获取学生可选课程(status=open 且匹配年级)", "deps": [ "requirePermission(ELECTIVE_SELECT)", "data-access-selections.getAvailableCoursesForStudent" ], "usedBy": [], "issues": [ "P0: 无调用方——student/elective 页面绕过 Action 直接调用 data-access-selections.getAvailableCoursesForStudent" ] } ], "dataAccess": [ { "name": "getElectiveCourses", "file": "data-access.ts", "signature": "(params?: GetElectiveCoursesParams & { scope?: DataScope; currentUserId?: string }) => Promise", "purpose": "查询选修课程列表(按 scope 行级过滤:owned/class_taught 按 teacherId,grade_managed 按 gradeIds)", "usedBy": [ "actions.getElectiveCoursesAction", "admin/elective/page.tsx", "teacher/elective/page.tsx" ] }, { "name": "getElectiveCourseById", "file": "data-access.ts", "signature": "(id: string) => Promise", "purpose": "获取课程详情", "usedBy": [ "actions.updateElectiveCourseAction", "admin/elective/[id]/edit/page.tsx" ] }, { "name": "createElectiveCourse", "file": "data-access.ts", "signature": "(data: CreateElectiveCourseInput, teacherId: string) => Promise", "purpose": "创建选修课程(status=draft, enrolledCount=0)", "usedBy": [ "actions.createElectiveCourseAction" ] }, { "name": "updateElectiveCourse", "file": "data-access.ts", "signature": "(id: string, data: Partial) => Promise", "purpose": "更新选修课程字段", "usedBy": [ "actions.updateElectiveCourseAction" ] }, { "name": "deleteElectiveCourse", "file": "data-access.ts", "signature": "(id: string) => Promise", "purpose": "删除选修课程", "usedBy": [ "actions.deleteElectiveCourseAction" ] }, { "name": "openSelection", "file": "data-access.ts", "signature": "(courseId: string) => Promise", "purpose": "开放选课(status=open)", "usedBy": [ "actions.openSelectionAction" ] }, { "name": "closeSelection", "file": "data-access.ts", "signature": "(courseId: string) => Promise", "purpose": "关闭选课(status=closed)", "usedBy": [ "actions.closeSelectionAction" ] }, { "name": "buildCourseSelect", "file": "data-access.ts", "signature": "() => query builder", "purpose": "构建 electiveCourses 表查询(仅查询本表字段,不跨表 JOIN;v3 重构:移除跨模块 LEFT JOIN,名称解析改由 resolveCourseDisplayNames 异步聚合)", "usedBy": [ "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": "将核心行 + 显示名映射为 ElectiveCourseWithDetails(v3 抽取:消除 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" ] }, { "name": "getCourseSelections", "file": "data-access-selections.ts", "signature": "(courseId: string) => Promise", "purpose": "查询课程所有选课记录(按 priority, selectedAt 排序)", "usedBy": [ "待扩展" ] }, { "name": "getStudentSelections", "file": "data-access-selections.ts", "signature": "(studentId: string) => Promise", "purpose": "查询学生选课记录(按 selectedAt 降序)", "usedBy": [ "actions.getStudentSelectionsAction", "student/elective/page.tsx" ] }, { "name": "getStudentGradeId", "file": "data-access-selections.ts", "signature": "(studentId: string) => Promise", "purpose": "获取学生所在年级 ID(通过 classEnrollments active 记录)", "usedBy": [ "data-access-selections.getAvailableCoursesForStudent" ] }, { "name": "getAvailableCoursesForStudent", "file": "data-access-selections.ts", "signature": "(studentId: string, gradeId?: string | null) => Promise", "purpose": "获取学生可选课程(status=open 且 gradeId 匹配或为空)", "usedBy": [ "actions.getAvailableCoursesAction", "student/elective/page.tsx" ] }, { "name": "runLottery", "file": "data-access-operations.ts", "signature": "(courseId: string) => Promise<{enrolled: number, waitlist: number}>", "purpose": "抽签录取(Fisher-Yates 无偏洗牌 selected 记录,前 capacity 名 enrolled,其余 waitlist,课程 status=closed;v3 修复:替换 sort(Math.random) 有偏洗牌)", "usedBy": [ "actions.runLotteryAction" ] }, { "name": "selectCourse", "file": "data-access-operations.ts", "signature": "(courseId: string, studentId: string, priority?: number) => Promise<{status: CourseSelectionStatus, message: string}>", "purpose": "学生选课(校验课程状态/时间窗口/重复选课;FCFS 模式即时 enrolled/waitlist,lottery 模式 selected;v3 修复:db.transaction 包裹 + .for('update') 锁课程行防 FCFS 超卖)", "usedBy": [ "actions.selectCourseAction" ] }, { "name": "dropCourse", "file": "data-access-operations.ts", "signature": "(courseId: string, studentId: string) => Promise", "purpose": "学生退课(status=dropped;FCFS 模式自动递补 waitlist 首位;v3 修复:db.transaction 包裹 + .for('update') 锁课程行保证递补一致性)", "usedBy": [ "actions.dropCourseAction" ] } ], "types": [ { "name": "ElectiveCourseStatus", "type": "type", "file": "types.ts", "definition": "\"draft\" | \"open\" | \"closed\" | \"cancelled\"" }, { "name": "ElectiveSelectionMode", "type": "type", "file": "types.ts", "definition": "\"fcfs\" | \"lottery\"" }, { "name": "CourseSelectionStatus", "type": "type", "file": "types.ts", "definition": "\"selected\" | \"enrolled\" | \"waitlist\" | \"dropped\" | \"rejected\"" }, { "name": "ElectiveCourse", "type": "interface", "file": "types.ts", "definition": "{ id, name, subjectId?, teacherId, gradeId?, description?, capacity, enrolledCount, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, status, selectionMode, credit, createdAt, updatedAt }" }, { "name": "ElectiveCourseWithDetails", "type": "interface", "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", "file": "types.ts", "definition": "{ id, courseId, studentId, status, priority?, selectedAt, enrolledAt?, droppedAt?, lotteryRank?, createdAt, updatedAt }" }, { "name": "CourseSelectionWithDetails", "type": "interface", "file": "types.ts", "definition": "CourseSelection & { courseName?, studentName?, courseCapacity?, courseEnrolledCount?, courseStatus? }" }, { "name": "GetElectiveCoursesParams", "type": "interface", "file": "types.ts", "definition": "{ status?, gradeId?, subjectId?, teacherId? }" }, { "name": "ELECTIVE_STATUS_LABELS", "type": "const", "file": "types.ts", "description": "课程状态标签常量" }, { "name": "ELECTIVE_STATUS_COLORS", "type": "const", "file": "types.ts", "description": "课程状态颜色常量(Badge variant)" }, { "name": "SELECTION_MODE_LABELS", "type": "const", "file": "types.ts", "description": "选课模式标签常量" }, { "name": "COURSE_SELECTION_STATUS_LABELS", "type": "const", "file": "types.ts", "description": "选课状态标签常量" }, { "name": "COURSE_SELECTION_STATUS_COLORS", "type": "const", "file": "types.ts", "description": "选课状态颜色常量(Badge variant)" } ], "schemas": [ { "name": "ElectiveCourseStatusEnum", "file": "schema.ts", "definition": "z.enum([\"draft\",\"open\",\"closed\",\"cancelled\"])" }, { "name": "ElectiveSelectionModeEnum", "file": "schema.ts", "definition": "z.enum([\"fcfs\",\"lottery\"])" }, { "name": "CourseSelectionStatusEnum", "file": "schema.ts", "definition": "z.enum([\"selected\",\"enrolled\",\"waitlist\",\"dropped\",\"rejected\"])" }, { "name": "CreateElectiveCourseSchema", "file": "schema.ts", "purpose": "创建课程校验(name 必填,teacherId 必填,capacity 1-500 默认 30,selectionMode 默认 fcfs,credit 默认 1.0)" }, { "name": "UpdateElectiveCourseSchema", "file": "schema.ts", "purpose": "更新课程校验(所有字段可选,含 status)" }, { "name": "SelectCourseSchema", "file": "schema.ts", "purpose": "选课校验(courseId 必填,priority 1-10 可选)" }, { "name": "DropCourseSchema", "file": "schema.ts", "purpose": "退课校验(courseId 必填)" }, { "name": "RunLotterySchema", "file": "schema.ts", "purpose": "抽签校验(courseId 必填)" } ], "components": [ { "name": "ElectiveCourseList", "file": "components/elective-course-list.tsx", "purpose": "课程卡片列表(管理员/教师视图,含编辑/开放/关闭/抽签/删除操作按钮,usePermission 控制权限)", "deps": [ "usePermission", "actions.deleteElectiveCourseAction", "actions.openSelectionAction", "actions.closeSelectionAction", "actions.runLotteryAction", "shared/components/ui/*" ] }, { "name": "ElectiveCourseForm", "file": "components/elective-course-form.tsx", "purpose": "课程创建/编辑表单(name, subjectId, teacherId, gradeId, description, capacity, classroom, schedule, dates, selectionMode, credit)", "deps": [ "react-hook-form", "actions.createElectiveCourseAction", "actions.updateElectiveCourseAction", "shared/components/ui/*" ] }, { "name": "StudentSelectionView", "file": "components/student-selection-view.tsx", "purpose": "学生选课视图(可选课程列表 + 我的选课记录,含选课/退课按钮)", "deps": [ "usePermission", "actions.selectCourseAction", "actions.dropCourseAction", "shared/components/ui/*" ] } ] } }, "lesson_preparation": { "path": "src/modules/lesson-preparation", "description": "教师备课模块:基于教材章节创建课案(节点图编辑器 React Flow,v2 nodes+edges 数据结构),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。编辑器从列表式(BlockRenderer + @dnd-kit)升级为节点图式(NodeEditor + @xyflow/react),旧 v1 数据通过 migrateV1ToV2() 自动迁移。V2 审计修复:Server Actions i18n + 错误码模式、SYSTEM_TEMPLATES i18n 化、as unknown as 类型断言清零、a11y 深度修复、Tracker 埋点接入", "exports": { "dataAccess": [ { "name": "getLessonPlans", "file": "data-access.ts", "purpose": "查询课案列表(按教师/教材/章节过滤)" }, { "name": "getLessonPlanById", "file": "data-access.ts", "purpose": "按 ID 获取课案详情" }, { "name": "createLessonPlan", "file": "data-access.ts", "purpose": "创建课案(V2-2:接受 translateTitle 函数翻译 SYSTEM_TEMPLATES 的 i18n 键后存储到 DB)" }, { "name": "updateLessonPlanContent", "file": "data-access.ts", "purpose": "更新课案内容(v2 nodes+edges JSON)" }, { "name": "softDeleteLessonPlan", "file": "data-access.ts", "purpose": "软删除课案" }, { "name": "duplicateLessonPlan", "file": "data-access.ts", "purpose": "复制课案" }, { "name": "getTemplateById", "file": "data-access.ts", "purpose": "按 ID 获取模板" }, { "name": "buildInitialContent", "file": "lib/document-migration.ts(data-access.ts re-export)", "purpose": "基于模板构建初始课案内容(v2 nodes+edges)" }, { "name": "migrateV1ToV2", "file": "lib/document-migration.ts(data-access.ts re-export)", "purpose": "v1→v2 迁移:将旧 blocks 数组转换为 nodes + 线性 edges(节点按网格布局),使用类型守卫 isV1Document/isV2Document 替代 as 断言" }, { "name": "normalizeDocument", "file": "lib/document-migration.ts(data-access.ts re-export)", "purpose": "规范化:确保 content 为 v2 格式,兼容旧 v1 数据(自动调用 migrateV1ToV2)" }, { "name": "getLessonPlanVersions", "file": "data-access-versions.ts", "purpose": "查询课案版本列表" }, { "name": "createLessonPlanVersion", "file": "data-access-versions.ts", "purpose": "创建课案版本(手动/自动)" }, { "name": "getVersionContent", "file": "data-access-versions.ts", "purpose": "获取指定版本内容" }, { "name": "revertToVersion", "file": "data-access-versions.ts", "purpose": "回滚到指定版本" }, { "name": "pruneAutoVersions", "file": "data-access-versions.ts", "purpose": "清理自动版本(保留最近 N 个)" }, { "name": "getLessonPlanTemplates", "file": "data-access-templates.ts", "purpose": "查询模板列表(系统/个人)" }, { "name": "saveAsTemplate", "file": "data-access-templates.ts", "purpose": "将课案保存为个人模板" }, { "name": "deletePersonalTemplate", "file": "data-access-templates.ts", "purpose": "删除个人模板" }, { "name": "getLessonPlansByKnowledgePoint", "file": "data-access-knowledge.ts", "purpose": "按知识点反查课案" }, { "name": "getLessonPlansByQuestion", "file": "data-access-knowledge.ts", "purpose": "按题目反查课案" }, { "name": "publishLessonPlanHomework", "file": "publish-service.ts", "purpose": "发布课案为作业(编排 homework/exams/classes,通过对方 data-access 调用 addExamQuestions/getStudentIdsByClassIds,无直查跨模块表;V2-1:抛出 PublishServiceError 错误码;V2-3:显式字段映射替代 as unknown as)" }, { "name": "suggestKnowledgePoints", "file": "ai-suggest.ts", "purpose": "AI 建议知识点(基于课案内容)" } ], "actions": [ { "name": "getLessonPlansAction", "permission": "LESSON_PLAN_READ", "file": "actions.ts", "purpose": "查询课案列表" }, { "name": "getLessonPlanByIdAction", "permission": "LESSON_PLAN_READ", "file": "actions.ts", "purpose": "获取课案详情" }, { "name": "createLessonPlanAction", "permission": "LESSON_PLAN_CREATE", "file": "actions.ts", "purpose": "创建课案" }, { "name": "updateLessonPlanAction", "permission": "LESSON_PLAN_UPDATE", "file": "actions.ts", "purpose": "更新课案内容" }, { "name": "saveLessonPlanVersionAction", "permission": "LESSON_PLAN_UPDATE", "file": "actions.ts", "purpose": "保存课案版本" }, { "name": "getLessonPlanVersionsAction", "permission": "LESSON_PLAN_READ", "file": "actions.ts", "purpose": "查询课案版本列表" }, { "name": "revertLessonPlanVersionAction", "permission": "LESSON_PLAN_UPDATE", "file": "actions.ts", "purpose": "回滚到指定版本" }, { "name": "deleteLessonPlanAction", "permission": "LESSON_PLAN_DELETE", "file": "actions.ts", "purpose": "删除课案" }, { "name": "duplicateLessonPlanAction", "permission": "LESSON_PLAN_CREATE", "file": "actions.ts", "purpose": "复制课案" }, { "name": "getLessonPlanTemplatesAction", "permission": "LESSON_PLAN_READ", "file": "actions.ts", "purpose": "查询模板列表" }, { "name": "saveAsTemplateAction", "permission": "LESSON_PLAN_UPDATE", "file": "actions.ts", "purpose": "保存为个人模板" }, { "name": "deleteTemplateAction", "permission": "LESSON_PLAN_DELETE", "file": "actions.ts", "purpose": "删除个人模板" }, { "name": "suggestKnowledgePointsAction", "permission": "LESSON_PLAN_UPDATE", "file": "actions-ai.ts", "purpose": "AI 建议知识点" }, { "name": "publishLessonPlanHomeworkAction", "permission": "LESSON_PLAN_PUBLISH", "file": "actions-publish.ts", "purpose": "发布课案为作业" }, { "name": "getKnowledgePointOptionsAction", "permission": "LESSON_PLAN_READ", "file": "actions-kp.ts", "purpose": "获取知识点选项(委托 textbooks data-access)" } ] }, "dependencies": [ "textbooks", "questions", "exams", "homework", "classes", "files", "shared/lib/ai", "@xyflow/react" ], "files": [ "types.ts", "constants.ts", "schema.ts", "lib/document-migration.ts", "lib/node-summary.ts", "lib/rf-mappers.ts", "config/block-registry.tsx", "providers/lesson-plan-provider.tsx", "services/default-data-service.ts", "data-access.ts", "data-access-versions.ts", "data-access-templates.ts", "data-access-knowledge.ts", "actions.ts", "actions-publish.ts", "actions-ai.ts", "actions-kp.ts", "publish-service.ts", "ai-suggest.ts", "seed-templates.ts", "hooks/use-lesson-plan-editor.ts", "components/lesson-plan-list.tsx", "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/lesson-plan-error-boundary.tsx", "components/lesson-plan-skeleton.tsx", "components/block-renderer.tsx", "components/template-picker.tsx", "components/version-history-drawer.tsx", "components/knowledge-point-picker.tsx", "components/question-bank-picker.tsx", "components/inline-question-editor.tsx", "components/publish-homework-dialog.tsx", "components/blocks/rich-text-block.tsx", "components/blocks/text-study-block.tsx", "components/blocks/exercise-block.tsx", "components/blocks/reflection-block.tsx" ], "i18n": { "namespace": "lessonPreparation", "status": "implemented", "messageFiles": [ "shared/i18n/messages/zh-CN/lesson-preparation.json", "shared/i18n/messages/en/lesson-preparation.json" ] }, "auditFixes": { "P0-1": "publish-service.ts 跨模块直查修复:改用 exams/classes data-access", "P0-2": "i18n 接入:17 个组件改造为 useTranslations/getTranslations", "P0-3": "buildScopeCondition 按 scope 类型精确过滤(class_taught/grade_managed/class_members/children)", "P1-1": "as never 断言替换为类型守卫函数;BLOCK_TYPE_LABELS/LESSON_PLAN_STATUS_LABELS 改为 i18n 键", "P1-2": "新增 LessonPlanErrorBoundary 错误边界", "P1-3": "新增 4 个 Skeleton 骨架屏组件", "P1-4": "alert/confirm/window.location.reload 替换为 toast/AlertDialog/router.refresh", "P1-5": "新增 LessonPlanProvider + Context 注入数据服务,支持多实例", "P1-7": "新增 4 个角色配置(TEACHER/ADMIN/STUDENT/PARENT)+ ROLE_CONFIGS 注册表", "P1-8": "新增 BLOCK_REGISTRY 注册表,node-edit-panel 配置驱动渲染", "P2-1": "5 个组件添加 role=dialog/aria-modal/aria-label", "P2-4": "预留 LessonPlanTracker 接口 + noopTracker 默认实现", "V2-1": "Server Actions i18n + 错误码模式:12 个 Action 通过 getTranslations 翻译错误消息;Service/DataAccess 层抛出 PublishServiceError/LessonPlanDataError 错误码,Actions 层通过 PUBLISH_ERROR_KEY_MAP 翻译", "V2-2": "SYSTEM_TEMPLATES i18n 化:name/title 改为 i18n 键,createLessonPlan 接受 translateTitle 函数在服务端翻译后存储到 DB", "V2-3": "as unknown as 类型断言清零:8 处替换为显式类型映射函数(mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate/mapRowToVersion)+ 类型守卫(isLessonPlanStatus/isTemplateType/isTemplateScope)", "V2-4": "MiniMap nodeColor 复用 lib/node-summary.ts 的 getNodeColor", "V2-5": "a11y 深度修复:lesson-plan-filters/exercise-block/inline-question-editor 的 select 添加 label htmlFor 关联;exercise-block 题目列表改为 ul/li;node-editor 画布添加 role=application + 键盘导航配置", "V2-6": "Tracker 埋点接入:新增 useLessonPlanTrackerSafe hook,在 create/save/publish/revert/duplicate/archive 6 处调用 tracker.track" } }, "error-book": { "path": "src/modules/error-book", "description": "错题本模块:自动采集考试/作业错题,SM-2 间隔重复算法复习,知识点薄弱度分析,支持学生/教师/家长/管理员多角色视图", "exports": { "actions": [ { "name": "getErrorBookItemsAction", "permission": "ERROR_BOOK_READ", "signature": "(params: GetErrorBookItemsParams) => Promise>", "purpose": "查询错题本列表", "deps": ["requirePermission", "data-access.getErrorBookItems"], "usedBy": ["student/error-book/page.tsx"] }, { "name": "getErrorBookItemDetailAction", "permission": "ERROR_BOOK_READ", "signature": "(itemId: string) => Promise>", "purpose": "查询错题详情(含复习历史)", "deps": ["requirePermission", "data-access.getErrorBookItemById"], "usedBy": ["error-book-detail-dialog.tsx"] }, { "name": "getErrorBookStatsAction", "permission": "ERROR_BOOK_READ", "signature": "() => Promise>", "purpose": "查询错题本统计", "deps": ["requirePermission", "data-access.getErrorBookStats"], "usedBy": ["student/error-book/page.tsx", "parent/error-book/page.tsx"] }, { "name": "createErrorBookItemAction", "permission": "ERROR_BOOK_MANAGE", "signature": "(prevState: ActionState | undefined, formData: FormData) => Promise>", "purpose": "手动添加错题", "deps": ["requirePermission", "data-access.createErrorBookItem"], "usedBy": ["add-error-book-dialog.tsx"] }, { "name": "updateErrorBookNoteAction", "permission": "ERROR_BOOK_MANAGE", "signature": "(prevState: ActionState | undefined, formData: FormData) => Promise>", "purpose": "更新错题笔记/标签", "deps": ["requirePermission", "data-access.updateErrorBookNote"], "usedBy": ["error-book-detail-dialog.tsx"] }, { "name": "reviewErrorBookItemAction", "permission": "ERROR_BOOK_MANAGE", "signature": "(prevState: ActionState | undefined, formData: FormData) => Promise>", "purpose": "记录复习结果(SM-2 算法)", "deps": ["requirePermission", "data-access.recordReview"], "usedBy": ["review-buttons.tsx"] }, { "name": "deleteErrorBookItemAction", "permission": "ERROR_BOOK_MANAGE", "signature": "(prevState: ActionState | undefined, formData: FormData) => Promise>", "purpose": "删除错题", "deps": ["requirePermission", "data-access.deleteErrorBookItem"], "usedBy": ["error-book-item-card.tsx"] }, { "name": "archiveErrorBookItemAction", "permission": "ERROR_BOOK_MANAGE", "signature": "(prevState: ActionState | undefined, formData: FormData) => Promise>", "purpose": "归档错题", "deps": ["requirePermission", "data-access.archiveErrorBookItem"], "usedBy": ["error-book-item-card.tsx"] }, { "name": "collectFromSubmissionAction", "permission": "ERROR_BOOK_MANAGE", "signature": "(prevState: ActionState | undefined, formData: FormData) => Promise>", "purpose": "从考试/作业提交自动采集错题", "deps": ["requirePermission", "data-access.collectFromExamSubmission", "data-access.collectFromHomeworkSubmission"], "usedBy": ["exam-homework 提交后调用"] } ], "dataAccess": [ { "name": "getErrorBookItems", "signature": "(params: GetErrorBookItemsParams) => Promise", "type": "cache function", "purpose": "查询错题本列表(缓存)", "usedBy": ["getErrorBookItemsAction", "student/error-book/page.tsx"] }, { "name": "getErrorBookItemById", "signature": "(itemId: string, studentId: string) => Promise", "type": "cache function", "purpose": "查询错题详情(含复习历史)", "usedBy": ["getErrorBookItemDetailAction"] }, { "name": "getErrorBookStats", "signature": "(studentId: string) => Promise", "type": "cache function", "purpose": "查询错题本统计(总数/待复习/已掌握等)", "usedBy": ["getErrorBookStatsAction", "student/error-book/page.tsx", "parent/error-book/page.tsx"] }, { "name": "createErrorBookItem", "signature": "(studentId: string, input: CreateErrorBookItemInput) => Promise", "type": "async function", "purpose": "手动添加错题", "usedBy": ["createErrorBookItemAction"] }, { "name": "updateErrorBookNote", "signature": "(itemId: string, studentId: string, input: { note?: string; errorTags?: string[] }) => Promise", "type": "async function", "purpose": "更新错题笔记/标签", "usedBy": ["updateErrorBookNoteAction"] }, { "name": "recordReview", "signature": "(itemId: string, studentId: string, result: ErrorBookReviewResult) => Promise", "type": "async function", "purpose": "记录复习结果并应用 SM-2 算法更新间隔/掌握度", "usedBy": ["reviewErrorBookItemAction"] }, { "name": "deleteErrorBookItem", "signature": "(itemId: string, studentId: string) => Promise", "type": "async function", "purpose": "删除错题", "usedBy": ["deleteErrorBookItemAction"] }, { "name": "archiveErrorBookItem", "signature": "(itemId: string, studentId: string) => Promise", "type": "async function", "purpose": "归档错题", "usedBy": ["archiveErrorBookItemAction"] }, { "name": "collectFromExamSubmission", "signature": "(submissionId: string, studentId: string) => Promise", "type": "async function", "purpose": "从考试提交自动采集错题(去重)", "usedBy": ["collectFromSubmissionAction"] }, { "name": "collectFromHomeworkSubmission", "signature": "(submissionId: string, studentId: string) => Promise", "type": "async function", "purpose": "从作业提交自动采集错题(去重)", "usedBy": ["collectFromSubmissionAction"] }, { "name": "getStudentErrorBookSummaries", "signature": "(studentIds: string[]) => Promise", "type": "async function", "purpose": "查询多个学生的错题统计(教师/管理员视图)", "usedBy": ["teacher/error-book/page.tsx", "admin/error-book/page.tsx"] }, { "name": "getTopWrongQuestionsByStudentIds", "signature": "(studentIds: string[], limit?: number) => Promise", "type": "async function", "purpose": "查询高频错题(教师/家长视图)", "usedBy": ["teacher/error-book/page.tsx", "parent/error-book/page.tsx"] }, { "name": "getKnowledgePointWeakness", "signature": "(studentIds: string[], limit?: number) => Promise", "type": "async function", "purpose": "查询知识点薄弱度统计", "usedBy": ["teacher/error-book/page.tsx", "parent/error-book/page.tsx", "admin/error-book/page.tsx"] }, { "name": "getSubjectErrorDistribution", "signature": "(studentIds: string[]) => Promise", "type": "async function", "purpose": "查询学科错题分布", "usedBy": ["teacher/error-book/page.tsx", "admin/error-book/page.tsx"] }, { "name": "getStudentNameMap", "signature": "(studentIds: string[]) => Promise>", "type": "async function", "purpose": "查询学生姓名映射", "usedBy": ["teacher/error-book/page.tsx", "parent/error-book/page.tsx"] }, { "name": "getStudentIdsByClassIdList", "signature": "(classIds: string[]) => Promise", "type": "async function", "purpose": "按班级 ID 查询学生 ID 列表(委托给 classes 模块)", "usedBy": ["teacher/error-book/page.tsx", "admin/error-book/page.tsx"] } ], "schema": [ { "name": "CreateErrorBookItemSchema", "type": "const", "description": "zod schema 手动添加错题", "usedBy": ["createErrorBookItemAction"] }, { "name": "UpdateErrorBookNoteSchema", "type": "const", "description": "zod schema 更新笔记/标签", "usedBy": ["updateErrorBookNoteAction"] }, { "name": "ReviewErrorBookItemSchema", "type": "const", "description": "zod schema 复习结果", "usedBy": ["reviewErrorBookItemAction"] }, { "name": "CollectFromSubmissionSchema", "type": "const", "description": "zod schema 自动采集", "usedBy": ["collectFromSubmissionAction"] } ], "types": [ { "name": "ErrorBookItem", "definition": "{ id, studentId, questionId, sourceType, sourceId, studentAnswer, correctAnswer, subjectId, knowledgePointIds, status, masteryLevel, nextReviewAt, reviewInterval, reviewCount, correctStreak, note, errorTags, createdAt, updatedAt, question?, subjectName? }", "usedBy": ["error-book-list", "error-book-item-card"] }, { "name": "ErrorBookItemDetail", "definition": "ErrorBookItem & { reviews: ErrorBookReviewRecord[] }", "usedBy": ["error-book-detail-dialog"] }, { "name": "ErrorBookStats", "definition": "{ totalCount, newCount, learningCount, masteredCount, archivedCount, dueReviewCount, masteredRate }", "usedBy": ["error-book-stats-cards"] }, { "name": "StudentErrorBookSummary", "definition": "{ studentId, totalCount, newCount, learningCount, masteredCount, dueReviewCount, masteredRate, lastActivityAt }", "usedBy": ["class-error-overview"] }, { "name": "KnowledgePointWeakness", "definition": "{ knowledgePointId, knowledgePointName, errorCount, masteredCount, totalCount, masteryRate }", "usedBy": ["class-error-overview"] }, { "name": "SubjectErrorDistribution", "definition": "{ subjectId, subjectName, errorCount, masteredCount, masteryRate }", "usedBy": ["class-error-overview"] } ], "components": [ { "name": "ErrorBookStatsCards", "file": "error-book-stats-cards.tsx", "purpose": "错题统计卡片(5 个指标)" }, { "name": "ErrorBookFilters", "file": "error-book-filters.tsx", "purpose": "筛选栏(搜索/状态/来源/待复习)" }, { "name": "ErrorBookItemCard", "file": "error-book-item-card.tsx", "purpose": "错题卡片(预览/标签/笔记/掌握度)" }, { "name": "ReviewButtons", "file": "review-buttons.tsx", "purpose": "复习按钮组(again/hard/good/easy)" }, { "name": "ErrorBookDetailDialog", "file": "error-book-detail-dialog.tsx", "purpose": "错题详情对话框(题目/答案/复习/笔记/历史)" }, { "name": "ErrorBookList", "file": "error-book-list.tsx", "purpose": "错题列表(网格布局)" }, { "name": "AddErrorBookDialog", "file": "add-error-book-dialog.tsx", "purpose": "手动添加错题对话框(题库选择)" }, { "name": "ClassErrorOverview", "file": "class-error-overview.tsx", "purpose": "班级错题概览(教师/管理员视图)" }, { "name": "TopWrongQuestions", "file": "top-wrong-questions.tsx", "purpose": "高频错题列表(Top 10)" } ] }, "dependencies": { "dependsOn": ["shared/db", "shared/lib/auth-guard", "shared/types/permissions", "modules/classes", "modules/questions"], "usedBy": ["app/(dashboard)/student/error-book", "app/(dashboard)/teacher/error-book", "app/(dashboard)/parent/error-book", "app/(dashboard)/admin/error-book"] }, "algorithms": { "sm2": { "name": "SM-2 间隔重复算法(简化版)", "file": "sm2-algorithm.ts", "description": "独立纯函数模块,4 级评级(again/hard/good/easy),间隔 1/2/4/7 天起,指数增长(×1.2/×1.5/×2),连续 3 次答对标记为已掌握。支持时间注入便于测试。", "functions": ["calculateNewInterval", "calculateNewMastery", "deriveStatus", "calculateNextReviewAt", "calculateNewCorrectStreak", "calculateSm2Result"], "constants": ["REVIEW_INTERVALS", "INTERVAL_MULTIPLIERS", "MAX_MASTERY_LEVEL", "MIN_MASTERY_LEVEL", "MASTERED_REQUIRED_STREAK", "MASTERED_REQUIRED_MASTERY"], "testFile": "sm2-algorithm.test.ts", "testCount": 39 } }, "dataScope": { "student": "owned(仅自己的错题)", "parent": "children(子女的错题)", "teacher": "class_taught(所教班级学生的错题)", "admin": "all(所有错题)", "grade_head": "grade_managed(所管年级学生的错题)", "teaching_head": "grade_managed(所管年级学生的错题)" } } }, "dbTables": { "_meta": { "total": 57, "orm": "Drizzle ORM 0.45", "database": "MySQL", "idStrategy": "CUID2 (varchar length 128)", "source": "src/shared/db/schema.ts" }, "usersAndAuth": { "description": "用户与认证 (Auth.js v5 + RBAC)", "tables": { "users": { "owner": "users", "description": "用户主表(含未成年人信息保护字段)" }, "accounts": { "owner": "auth", "description": "OAuth 账户" }, "sessions": { "owner": "auth", "description": "Auth.js 会话" }, "verificationTokens": { "owner": "auth", "description": "验证令牌" }, "roles": { "owner": "auth", "description": "角色(admin/teacher/student/parent/grade_head/teaching_head)" }, "usersToRoles": { "owner": "auth", "description": "用户-角色多对多" }, "rolePermissions": { "owner": "auth", "description": "角色-权限(细粒度 RBAC)" }, "passwordSecurity": { "owner": "auth", "description": "密码安全策略(失败次数/锁定/必须改密)" } } }, "knowledgeStructure": { "description": "知识点树结构", "tables": { "knowledgePoints": { "owner": "textbooks", "description": "知识点(树结构,parentId 自引用)" }, "knowledgePointPrerequisites": { "owner": "textbooks", "description": "知识点前置依赖(复合主键 knowledgePointId+prerequisiteKpId,双外键 cascade 删除)" } } }, "questionBank": { "description": "题库核心", "tables": { "questions": { "owner": "questions", "description": "题目(content JSON,支持复合题 parentId 自引用)" }, "questionsToKnowledgePoints": { "owner": "questions", "description": "题目-知识点多对多" } } }, "academicStructure": { "description": "教学结构", "tables": { "subjects": { "owner": "school", "description": "科目" }, "textbooks": { "owner": "textbooks", "description": "教材" }, "chapters": { "owner": "textbooks", "description": "章节(支持嵌套 parentId)" } } }, "schoolManagement": { "description": "学校管理", "tables": { "departments": { "owner": "school", "description": "部门" }, "classrooms": { "owner": "school", "description": "教室" }, "academicYears": { "owner": "school", "description": "学年" }, "schools": { "owner": "school", "description": "学校" }, "grades": { "owner": "school", "description": "年级(含 gradeHeadId/teachingHeadId)" } } }, "classes": { "description": "班级/选课/课表/邀请码(v3 新增邀请码体系,对标 Google Classroom / 钉钉教育)", "tables": { "classes": { "owner": "classes", "description": "班级(invitationCode 字段保留作为 fallback,v3 迁移至 classInvitationCodes 表)" }, "classSubjectTeachers": { "owner": "classes", "description": "班级-科目-教师" }, "classEnrollments": { "owner": "classes", "description": "班级选课(active/inactive)" }, "classSchedule": { "owner": "scheduling", "description": "课表(注意:三处写入口,见 knownIssues P0-6)" }, "classInvitationCodes": { "owner": "classes", "description": "v3 新增:班级邀请码(独立表,支持有效期/次数限制/审计/多码并存,6 位字母数字剔除歧义字符)" } } }, "exams": { "description": "考试与提交", "tables": { "exams": { "owner": "exams", "description": "考试(含 examMode: homework/timed/proctored)" }, "examQuestions": { "owner": "exams", "description": "考试-题目多对多" }, "examSubmissions": { "owner": "exams", "description": "考试提交(started/submitted/graded)" }, "submissionAnswers": { "owner": "exams", "description": "提交答案" } } }, "homework": { "description": "作业", "tables": { "homeworkAssignments": { "owner": "homework", "description": "作业(关联 sourceExamId)" }, "homeworkAssignmentQuestions": { "owner": "homework", "description": "作业-题目" }, "homeworkAssignmentTargets": { "owner": "homework", "description": "作业目标学生" }, "homeworkSubmissions": { "owner": "homework", "description": "作业提交(含 attemptNo/isLate)" }, "homeworkAnswers": { "owner": "homework", "description": "作业答案" } } }, "ai": { "description": "AI 配置", "tables": { "aiProviders": { "owner": "settings", "description": "AI Provider(zhipu/openai/gemini/custom,apiKey 加密)" } } }, "announcements": { "description": "公告", "tables": { "announcements": { "owner": "announcements", "description": "公告(school/grade/class,draft/published/archived)" } } }, "auditLogs": { "description": "审计与登录日志", "tables": { "auditLogs": { "owner": "audit", "description": "审计日志(success/failure)" }, "loginLogs": { "owner": "audit", "description": "登录日志(signin/signout/signup)" }, "dataChangeLogs": { "owner": "audit", "description": "数据变更日志(create/update/delete)" } } }, "gradeRecords": { "description": "成绩录入", "tables": { "gradeRecords": { "owner": "grades", "description": "成绩记录(exam/quiz/homework/other)" } } }, "files": { "description": "文件附件", "tables": { "fileAttachments": { "owner": "files", "description": "文件附件(含 storagePath/url)" } } }, "coursePlans": { "description": "课程计划", "tables": { "coursePlans": { "owner": "course-plans", "description": "课程计划(planning/active/completed/paused)" }, "coursePlanItems": { "owner": "course-plans", "description": "课程计划项(按周)" } } }, "messaging": { "description": "消息与通知", "tables": { "messages": { "owner": "messaging", "description": "站内消息(含回复链 parentMessageId;软删除 senderDeletedAt/receiverDeletedAt)" }, "messageNotifications": { "owner": "notifications", "description": "消息通知" }, "notificationPreferences": { "owner": "notifications", "description": "通知偏好(email/sms/push + 分类开关)" } } }, "parentStudent": { "description": "家长-子女关联", "tables": { "parentStudentRelations": { "owner": "parent", "description": "家长-子女关系" } } }, "attendance": { "description": "考勤", "tables": { "attendanceRecords": { "owner": "attendance", "description": "考勤记录(present/absent/late/early_leave/excused)" }, "attendanceRules": { "owner": "attendance", "description": "考勤规则(迟到/早退阈值)" } } }, "scheduling": { "description": "排课", "tables": { "schedulingRules": { "owner": "scheduling", "description": "排课规则(每日最大课时/连续课时/午休等)" }, "scheduleChanges": { "owner": "scheduling", "description": "调课/代课申请(pending/approved/rejected/completed)" } } }, "elective": { "description": "选课管理 (P2)", "tables": { "electiveCourses": { "owner": "elective", "description": "选修课程(draft/open/closed/cancelled,fcfs/lottery)" }, "courseSelections": { "owner": "elective", "description": "选课记录(selected/enrolled/waitlist/dropped/rejected)" } } }, "proctoring": { "description": "考试监考 (P2)", "tables": { "examProctoringEvents": { "owner": "proctoring", "description": "监考事件(tab_switch/window_blur/copy_attempt 等)" } } }, "diagnostic": { "description": "学情诊断 (P2)", "tables": { "knowledgePointMastery": { "owner": "diagnostic", "description": "知识点掌握度(学生-知识点)" }, "learningDiagnosticReports": { "owner": "diagnostic", "description": "学情诊断报告(individual/class/grade)" } } }, "lessonPreparation": { "description": "教师备课", "tables": { "lessonPlans": { "owner": "lesson-preparation", "description": "课案主表(Block JSON 内容,15 列/4 索引/5 外键)", "columns": 15, "indexes": 4, "foreignKeys": 5 }, "lessonPlanVersions": { "owner": "lesson-preparation", "description": "课案版本(手动/自动快照,8 列/2 索引/2 外键)", "columns": 8, "indexes": 2, "foreignKeys": 2 }, "lessonPlanTemplates": { "owner": "lesson-preparation", "description": "课案模板(系统/个人,8 列/1 索引/1 外键)", "columns": 8, "indexes": 1, "foreignKeys": 1 }, "systemSettings": { "owner": "settings", "description": "系统设置(键值对存储:category + key + value + valueType,P0-3 新增)", "columns": 7, "indexes": 2, "foreignKeys": 0 } } }, "errorBook": { "description": "错题本(SM-2 间隔重复算法)", "tables": { "errorBookItems": { "owner": "error-book", "description": "错题条目主表(学生-题目-来源-状态-掌握度-SM2 字段-笔记-标签)", "columns": 18, "indexes": 4, "foreignKeys": 2 }, "errorBookReviews": { "owner": "error-book", "description": "复习记录(错题条目-学生-评级-新间隔-新掌握度-复习时间)", "columns": 8, "indexes": 2, "foreignKeys": 2 } } }, "onboarding": { "description": "首次登录引导(独立路由 /onboarding + Server Action + middleware 重定向,v3 对标 PowerSchool/Veracross/Auth0)", "tables": { "users": { "owner": "shared", "description": "读写 users.onboardedAt / name / phone / address / birthDate / phone(v3 家长绑定三因子验证)", "columns": "subset" }, "parentStudentRelations": { "owner": "shared", "description": "家长绑定子女(onboarding 中写入,支持多子女循环绑定)", "columns": "subset" }, "auditLogs": { "owner": "shared", "description": "v3 新增:onboarding 完成后写审计日志(含失败项明细)", "columns": "subset" } }, "exports": { "actions": [ { "name": "getOnboardingStatusAction", "file": "actions.ts", "purpose": "查询当前用户 onboarding 状态(Server Action)" }, { "name": "completeOnboardingAction", "file": "actions.ts", "purpose": "完成 onboarding(Server Action + requireAuth + Zod + db.transaction + logAudit,v3 新增幂等检查/局部错误收集/教师多科目循环绑定)" } ], "dataAccess": [ { "name": "getOnboardingStatus", "file": "data-access.ts", "purpose": "读取 users.onboardedAt + usersToRoles" }, { "name": "updateUserProfile", "file": "data-access.ts", "purpose": "更新 users.name/phone/address" }, { "name": "bindParentToChild", "file": "data-access.ts", "purpose": "家长绑定子女(v3 三因子验证:邮箱+生日+手机号后4位,对标 PowerSchool Access ID+Password)" }, { "name": "resolveDefaultPathByRoles", "file": "data-access.ts", "purpose": "按角色解析默认跳转路径" } ], "schema": [ { "name": "OnboardingSchema", "file": "schema.ts", "purpose": "Zod 校验:name/phone/address/classCodes/teacherSubjects/children[](v3 重构:children 数组替代单个 childEmail/childBindingCode,支持多子女)" } ], "types": [ { "name": "OnboardingRoleInfo / OnboardingStatus / OnboardingCompleteData", "file": "types.ts", "purpose": "类型定义" }, { "name": "OnboardingFailureItem", "file": "types.ts", "purpose": "v3 新增:局部失败项类型(班级码/子女绑定失败不回滚整个事务)" }, { "name": "BindParentToChildParams", "file": "types.ts", "purpose": "v3 新增:家长绑定子女输入参数(三因子验证)" } ], "components": [ { "name": "OnboardingStepper", "file": "components/onboarding-stepper.tsx", "purpose": "客户端 stepper 容器(4 步,v3 新增:URL query 持久化步骤/家长多子女动态行/跳过机制明确化)" } ] }, "routes": { "/onboarding": { "methods": ["GET"], "handler": "OnboardingPage(服务端组件,读 session.onboarded 决定渲染,v3 新增 Suspense 边界 + 骨架屏)", "auth": "required", "validation": "server-side redirect" } }, "securityNotes": [ "角色只读:不写 usersToRoles,角色由管理员预分配", "班级绑定:调用 modules/classes data-access 的 enrollStudentByInvitationCode / enrollTeacherByInvitationCode(含校验)", "事务化:completeOnboardingAction 用 db.transaction 包裹全部写入", "Zod 校验:OnboardingSchema 校验所有输入", "middleware 拦截:proxy.ts 读取 token.onboarded,未完成则重定向 /onboarding", "v3 P0-2 家长绑定三因子验证:邮箱+生日+手机号后4位(组合空间 3.65M,对标 PowerSchool)", "v3 P0-3 教师多科目循环绑定:修复 UI 多选但服务端只取第一个的 bug", "v3 P0-4 审计日志:onboarding 完成后写 audit_logs(含失败项明细)", "v3 P0-5 服务端幂等:开始时检查 users.onboardedAt,已完成直接返回成功", "v3 P1-1 URL query 持久化步骤:?step=N,刷新不丢步,支持浏览器前进后退", "v3 P1-2 局部错误收集:班级码/子女绑定失败不回滚整个事务,收集失败列表返回前端", "v3 P1-4 家长多子女绑定:动态多行 UI,支持一次绑定多个子女", "v3 P1-5 跳过机制明确化:parent 不可跳过子女绑定(核心功能)" ] }, "i18n": { "description": "v3 新增:项目国际化体系(next-intl 4.x,without i18n routing 模式,cookie 驱动)", "tables": {}, "exports": { "config": [ { "name": "getRequestConfig", "file": "src/i18n/request.ts", "purpose": "next-intl 请求配置:从 cookie 读取 locale,按命名空间加载字典" }, { "name": "setLocaleAction", "file": "src/i18n/actions.ts", "purpose": "Server Action:切换语言(写 cookie + revalidatePath)" } ], "shared": [ { "name": "LOCALES / Locale / DEFAULT_LOCALE / LOCALE_COOKIE", "file": "src/shared/i18n/locale.ts", "purpose": "locale 常量与工具函数" }, { "name": "LocaleSwitcher", "file": "src/shared/components/locale-switcher.tsx", "purpose": "语言切换组件(DropdownMenu + cookie 持久化)" } ], "messages": [ "src/shared/i18n/messages/zh-CN/{common,auth,onboarding,classes,errors,dashboard,examHomework,announcements,messages,notifications}.json", "src/shared/i18n/messages/en/{common,auth,onboarding,classes,errors,dashboard,examHomework,announcements,messages,notifications}.json" ] }, "routes": {}, "securityNotes": [ "不使用 URL 路由段,避免破坏现有 (auth)/(dashboard)/(onboarding) 路由组结构", "locale 通过 cookie 持久化,SSR 时由 getRequestConfig 读取", "不使用 Accept-Language 自动协商,避免 SSR 与客户端 hydration 不一致", "字典放在 shared/i18n/messages/,符合三层架构约束(shared 为被依赖方)", "NextIntlClientProvider 在根 layout 注入,服务端/客户端组件均可使用 useTranslations" ] } }, "dependencyMatrix": { "shared": { "dependsOn": [] }, "auth": { "dependsOn": [ "shared" ], "uses": { "shared": [ "db", "schema", "permissions" ] } }, "exams": { "dependsOn": [ "shared", "auth", "questions", "classes" ], "uses": { "shared": [ "db", "auth-guard", "types", "ai" ], "auth": [ "auth" ], "questions": [ "data-access.createQuestionWithRelations" ], "classes": [ "data-access.getClassGradeIdsByClassIds" ] } }, "homework": { "dependsOn": [ "shared", "auth", "exams", "classes", "school", "users" ], "uses": { "shared": [ "db", "auth-guard", "types" ], "auth": [ "auth" ], "exams": [ "data-access.getExamIdsByGradeIds", "data-access.getExamSubjectIdMap", "data-access.getExamWithQuestionsForHomework" ], "classes": [ "data-access.getStudentIdsByClassId", "data-access.getStudentIdsByClassIds", "data-access.getActiveStudentIdsByClassId", "data-access.getTeacherSubjectIdsByClass", "data-access.getStudentActiveClassId", "data-access.getGradeIdsByClassIds", "data-access.getClassTeacherById" ], "school": [ "data-access.getSubjectOptions" ], "users": [ "data-access.getUserWithRole", "data-access.getUserNamesByIds" ] } }, "questions": { "dependsOn": [ "shared", "auth" ], "uses": { "shared": [ "db", "auth-guard", "types" ], "auth": [ "auth" ] } }, "textbooks": { "dependsOn": [ "shared", "auth" ], "uses": { "shared": [ "db", "auth-guard", "types" ], "auth": [ "auth" ] } }, "classes": { "dependsOn": [ "shared", "auth", "homework", "scheduling", "school" ], "uses": { "shared": [ "db", "auth-guard", "types" ], "auth": [ "auth" ], "homework": [ "data-access-classes.getAssignmentIdsForStudents", "data-access-classes.getHomeworkAssignmentsWithSubject", "data-access-classes.getHomeworkAssignmentsByIds", "data-access-classes.getAssignmentMaxScoreById", "data-access-classes.getAssignmentTargetCounts", "data-access-classes.getHomeworkSubmissionsForStudents", "data-access-classes.getPublishedHomeworkAssignmentsWithSubject", "data-access-classes.getHomeworkSubmissionsForAssignments" ], "scheduling": [ "data-access-class-schedule.createClassScheduleItem", "data-access-class-schedule.updateClassScheduleItem", "data-access-class-schedule.deleteClassScheduleItem" ], "school": [ "data-access.isGradeHead", "data-access.isGradeManager", "data-access.findGradeIdByHeadAndName" ] } }, "school": { "dependsOn": [ "shared", "auth" ], "uses": { "shared": [ "db", "auth-guard", "types" ], "auth": [ "auth" ] } }, "dashboard": { "dependsOn": [ "shared", "auth", "homework", "classes" ], "uses": { "shared": [ "db", "types" ], "auth": [ "auth" ], "homework": [ "data-access.getTeacherGradeTrends", "data-access.getStudentDashboardGrades" ], "classes": [ "data-access.getTeacherClasses", "data-access.getStudentClasses", "data-access.getStudentSchedule" ] } }, "layout": { "dependsOn": [ "shared", "auth", "messaging" ], "uses": { "shared": [ "hooks.usePermission", "components.global-search.GlobalSearch" ], "auth": [ "useSession" ], "messaging": [ "components.notification-dropdown" ] } }, "settings": { "dependsOn": [ "shared", "auth", "classes", "homework", "dashboard", "users", "notifications", "files" ], "uses": { "shared": [ "db", "auth-guard", "ai", "types", "components.ui.switch", "components.ui.card", "components.ui.tabs", "components.ui.alert-dialog", "components.ui.badge", "components.form-fields", "components.locale-switcher" ], "auth": [ "auth" ], "classes": [ "data-access.getStudentClasses", "data-access.getStudentSchedule", "data-access.getTeacherClasses", "data-access.getTeacherTeachingSubjects" ], "homework": [ "data-access.getStudentHomeworkAssignments", "data-access.getStudentDashboardGrades" ], "dashboard": [ "components.student-dashboard.student-grades-card", "components.student-dashboard.student-stats-grid", "components.student-dashboard.student-today-schedule-card", "components.student-dashboard.student-upcoming-assignments-card" ], "users": [ "data-access.UserProfile", "data-access.UpdateUserProfileInput", "data-access.updateUserAvatar" ], "notifications": [ "types.NotificationPreferences", "types.UpdateNotificationPreferencesInput" ], "files": [ "data-access.getFileByUrl", "data-access.deleteFileAttachment" ] }, "note": "组件层通过 SettingsService 接口注入解耦,不直接 import messaging/actions;页面层 app/(dashboard)/settings/page.tsx 负责注入 users/actions + messaging/actions 实现。P0-3/P2-8/P2-9/P2-10/P2-11 已修复:AdminSettingsView 接入真实数据层(system_settings 表)、头像上传、2FA/登录历史、通知测试按钮、语言切换集成。v2 已增强:2FA 开关改为禁用状态避免虚假安全感、通知测试接入真实发送、头像上传清理旧文件、会话远程登出、AdminSettingsView/通知偏好表单 dirty 检测、currentDeviceLabel 标记当前会话、文件名长度校验、2FA 查询 N+1 优化、新增 30 个单元测试。" }, "users": { "dependsOn": [ "shared", "auth", "classes" ], "uses": { "shared": [ "db", "auth-guard.requireAuth", "auth-guard.requirePermission", "db.schema.users", "db.schema.roles", "db.schema.usersToRoles", "types.permissions", "types.action-state", "lib.excel" ], "auth": [ "auth" ], "classes": [ "data-access.enrollStudentByInvitationCode" ] } }, "audit": { "dependsOn": [ "shared", "auth" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.auditLogs", "db.schema.loginLogs", "db.schema.dataChangeLogs", "types.permissions", "lib.excel" ], "auth": [ "auth" ] } }, "announcements": { "dependsOn": [ "shared", "auth", "school" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "auth-guard.requireAuth", "db.schema.announcements", "types.permissions" ], "auth": [ "auth" ], "school": [ "data-access.getGrades" ] } }, "files": { "dependsOn": [ "shared", "auth" ], "uses": { "shared": [ "db", "auth-guard.requireAuth", "auth-guard.requirePermission", "types.permissions", "lib.file-storage", "lib.storage-provider" ], "auth": [ "auth" ] } }, "course-plans": { "dependsOn": [ "shared", "auth", "school", "classes" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.coursePlans", "db.schema.coursePlanItems", "db.schema.classes", "db.schema.subjects", "db.schema.users", "types.permissions", "types.action-state" ], "auth": [ "auth" ], "school": [ "data-access.getAcademicYears" ], "classes": [ "data-access.getAdminClasses", "data-access.getStaffOptions" ] } }, "grades": { "dependsOn": [ "shared", "auth", "classes", "school", "users" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "types.permissions", "types.action-state", "db.schema.gradeRecords", "lib.excel" ], "auth": [ "auth" ], "classes": [ "data-access.getClassExists", "data-access.getClassNameById", "data-access.getClassNamesByIds", "data-access.getActiveStudentIdsByClassId", "data-access.getStudentActiveClassId", "data-access.getClassesByGradeId" ], "school": [ "data-access.getSubjectOptions", "data-access.getGradeOptions" ], "users": [ "data-access.getUserNamesByIds" ] } }, "parent": { "dependsOn": [ "shared", "auth", "homework", "classes", "grades", "school", "users", "attendance" ], "uses": { "shared": [ "db", "auth-guard.requireAuth", "auth-guard.getAuthContext", "db.schema.parentStudentRelations", "types", "lib.utils.getSearchParam" ], "auth": [ "auth" ], "homework": [ "data-access.getStudentHomeworkAssignments", "data-access.getStudentDashboardGrades" ], "classes": [ "data-access.getStudentClasses", "data-access.getStudentSchedule", "data-access.getStudentActiveClass" ], "grades": [ "data-access.getStudentGradeSummary" ], "school": [ "data-access.getGradeNameById" ], "users": [ "data-access.getUserBasicInfo", "data-access.getUserNamesByIds" ], "attendance": [ "data-access-stats.getStudentAttendanceSummary", "components.student-attendance-view", "types.StudentAttendanceSummary (⚠️ 跨模块 UI 类型依赖:parent-attendance-warning.tsx / parent-attendance-rate-card.tsx / parent-attendance-calendar.tsx 直接 import)" ] } }, "messaging": { "dependsOn": [ "shared", "auth", "notifications", "classes" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "auth-guard.requireAuth", "db.schema.messages", "db.schema.users", "db.schema.classEnrollments", "db.schema.classes", "db.schema.grades", "types.permissions", "types.action-state" ], "auth": [ "auth" ], "notifications": [ "dispatcher.sendNotification", "data-access.createNotification (via re-export)", "data-access.getNotifications (via re-export)", "data-access.markNotificationAsRead (via re-export)", "data-access.markAllNotificationsAsRead (via re-export)", "data-access.getUnreadNotificationCount (via re-export)", "preferences.getNotificationPreferences (via re-export)", "preferences.upsertNotificationPreferences (via re-export)" ], "classes": [ "data-access.getTeacherIdsByClassIds", "data-access.getStudentActiveClassId" ] } }, "notifications": { "dependsOn": [ "shared", "auth", "classes" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.messageNotifications", "db.schema.notificationPreferences", "types.permissions", "types.action-state" ], "auth": [ "auth" ], "classes": [ "data-access.getClassExists", "data-access.getStudentIdsByClassId" ] } }, "attendance": { "dependsOn": [ "shared", "auth", "classes" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.attendanceRecords", "db.schema.attendanceRules", "db.schema.classEnrollments", "db.schema.users", "db.schema.classes", "types.permissions", "types.action-state", "types.DataScope" ], "auth": [ "auth" ], "classes": [ "data-access.getTeacherClasses", "data-access.getAdminClasses" ] } }, "scheduling": { "dependsOn": [ "shared", "auth", "classes", "users" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "auth-guard.getAuthContext", "db.schema.schedulingRules", "db.schema.scheduleChanges", "db.schema.classSchedule", "db.schema.classSubjectTeachers", "db.schema.subjects", "db.schema.classrooms", "types.permissions", "types.action-state" ], "auth": [ "auth" ], "classes": [ "data-access.verifyTeacherOwnsClass", "data-access.getTeacherIdForMutations" ], "users": [ "data-access.getUserNamesByIds" ] } }, "diagnostic": { "dependsOn": [ "shared", "auth", "classes", "exams", "questions", "users" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "auth-guard.getAuthContext", "db.schema.knowledgePointMastery", "db.schema.learningDiagnosticReports", "db.schema.knowledgePoints", "types.permissions", "types.action-state", "hooks.usePermission", "components.ui.*" ], "auth": [ "auth" ], "classes": [ "data-access.getClassExists", "data-access.getClassNameById", "data-access.getActiveStudentIdsByClassId" ], "exams": [ "data-access.getExamSubmissionWithAnswers" ], "questions": [ "data-access.getKnowledgePointsForQuestions" ], "users": [ "data-access.getUserNamesByIds", "data-access.getUserIdsByGradeId" ] } }, "elective": { "dependsOn": [ "shared", "auth", "classes", "school", "users" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.electiveCourses", "db.schema.courseSelections", "types.permissions", "types.action-state", "types.DataScope", "hooks.usePermission", "components.ui.*" ], "auth": [ "auth" ], "classes": [ "data-access.getStudentActiveGradeId" ], "school": [ "data-access.getSubjectOptions", "data-access.getGradeOptions" ], "users": [ "data-access.getUserNamesByIds" ] } }, "proctoring": { "dependsOn": [ "shared", "auth", "exams", "users" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.examProctoringEvents", "types.permissions", "types.action-state", "hooks.usePermission", "components.ui.*", "next/cache.revalidatePath" ], "auth": [ "auth" ], "exams": [ "data-access.getExamForProctoringCrossModule", "data-access.getExamSubmissionForProctoringCrossModule", "data-access.getExamSubmissionsForExam", "data-access.getExamTitleById" ], "users": [ "data-access.getUserNamesByIds" ] } }, "lesson_preparation": { "dependsOn": [ "shared", "auth", "textbooks", "questions", "exams", "homework", "classes", "files" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.lessonPlans", "db.schema.lessonPlanVersions", "db.schema.lessonPlanTemplates", "lib.ai.createAiChatCompletion", "types.permissions", "types.action-state", "hooks.usePermission", "components.ui.*", "next/cache.revalidatePath" ], "auth": [ "auth" ], "textbooks": [ "data-access.getChapters", "data-access.getKnowledgePoints" ], "questions": [ "data-access.getQuestions", "data-access.createQuestionWithRelations" ], "exams": [ "data-access.persistExamDraft" ], "homework": [ "data-access-write.createHomeworkAssignment" ], "classes": [ "data-access.getTeacherClasses" ], "files": [ "data-access.createFileAttachment", "data-access.getFileAttachmentsByTarget" ], "external": [ "@xyflow/react(React Flow 节点图编辑器:ReactFlow/Background/Controls/MiniMap/Handle/applyNodeChanges/applyEdgeChanges)", "@paralleldrive/cuid2(节点 ID 生成)", "zustand(编辑器状态管理)" ] } }, "onboarding": { "dependsOn": [ "shared", "auth", "classes" ], "uses": { "shared": [ "db", "auth-guard.requireAuth", "db.schema.users", "db.schema.usersToRoles", "db.schema.roles", "db.schema.parentStudentRelations", "lib.role-utils", "types.permissions", "types.action-state" ], "auth": [ "auth" ], "classes": [ "data-access.enrollStudentByInvitationCode", "data-access.enrollTeacherByInvitationCode" ] } }, "error-book": { "dependsOn": [ "shared", "classes", "questions" ], "uses": { "shared": [ "db", "auth-guard.requirePermission", "db.schema.errorBookItems", "db.schema.errorBookReviews", "db.schema.examSubmissions", "db.schema.submissionAnswers", "db.schema.homeworkSubmissions", "db.schema.homeworkAnswers", "db.schema.questions", "db.schema.questionsToKnowledgePoints", "db.schema.knowledgePoints", "db.schema.subjects", "db.schema.examQuestions", "db.schema.homeworkAssignmentQuestions", "db.schema.users", "types.permissions", "types.action-state", "lib.utils" ], "classes": [ "data-access.getStudentIdsByClassIds" ], "questions": [ "actions.getQuestionsAction" ] } } }, "moduleDependencyGraph": { "nodes": [ "shared", "auth", "exams", "homework", "questions", "textbooks", "classes", "school", "dashboard", "layout", "settings", "users", "audit", "announcements", "files", "course-plans", "grades", "parent", "messaging", "notifications", "attendance", "scheduling", "proctoring", "diagnostic", "elective", "onboarding", "error-book" ], "edges": [ { "from": "exams", "to": "questions", "type": "data-access", "description": "引用题目(examQuestions 关联)" }, { "from": "exams", "to": "shared", "type": "normal", "description": "使用 db/auth-guard/ai" }, { "from": "homework", "to": "exams", "type": "data-access", "description": "引用试卷结构(sourceExamId)" }, { "from": "homework", "to": "questions", "type": "data-access", "description": "引用题目" }, { "from": "grades", "to": "classes", "type": "data-access", "description": "查询班级" }, { "from": "grades", "to": "exams", "type": "data-access", "description": "关联考试(examId)" }, { "from": "grades", "to": "subjects", "type": "data-access", "description": "查询科目" }, { "from": "dashboard", "to": "exams", "type": "data-access", "description": "调用 getExamsDashboardStats 获取考试统计(P0-4 已修复)" }, { "from": "dashboard", "to": "homework", "type": "data-access", "description": "调用 getHomeworkDashboardStats 获取作业统计(P0-4 已修复)" }, { "from": "dashboard", "to": "classes", "type": "data-access", "description": "调用 getClassesDashboardStats 获取班级统计(P0-4 已修复)" }, { "from": "dashboard", "to": "users", "type": "data-access", "description": "调用 getUsersDashboardStats 获取用户/会话/角色统计(P0-4 已修复)" }, { "from": "dashboard", "to": "textbooks", "type": "data-access", "description": "调用 getTextbooksDashboardStats 获取教材/章节统计(P0-4 已修复)" }, { "from": "dashboard", "to": "questions", "type": "data-access", "description": "调用 getQuestionsDashboardStats 获取题目统计(P0-4 已修复)" }, { "from": "messaging", "to": "notifications", "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", "type": "data-access", "description": "通过 homework/data-access-classes 获取作业数据(P0-7 已修复,原为 violation)" }, { "from": "classes", "to": "scheduling", "type": "resolved", "description": "✅ P0-5 已修复:classSchedule 写函数从 classes 迁移至 scheduling/data-access-class-schedule.ts,classes 仅保留读函数" }, { "from": "classes", "to": "grades", "type": "resolved", "description": "✅ P0-1 已修复:classes/data-access.ts 已拆分为 5 个文件,不再混入 grades 查询逻辑" }, { "from": "proctoring", "to": "exams", "type": "data-access", "description": "关联考试与提交(examSubmissions)" }, { "from": "diagnostic", "to": "questions", "type": "data-access", "description": "关联题目知识点(questionsToKnowledgePoints)" }, { "from": "diagnostic", "to": "exams", "type": "data-access", "description": "关联考试提交(examSubmissions/submissionAnswers)" }, { "from": "elective", "to": "school", "type": "data-access", "description": "关联年级(grades)/科目(subjects)" }, { "from": "attendance", "to": "classes", "type": "data-access", "description": "查询班级与课表" }, { "from": "parent", "to": "grades", "type": "data-access", "description": "查询子女成绩" }, { "from": "parent", "to": "attendance", "type": "data-access", "description": "查询子女考勤" }, { "from": "parent", "to": "homework", "type": "data-access", "description": "查询子女作业" }, { "from": "scheduling", "to": "classes", "type": "data-access", "description": "排课关联班级" }, { "from": "course-plans", "to": "classes", "type": "data-access", "description": "课程计划关联班级" }, { "from": "course-plans", "to": "school", "type": "data-access", "description": "关联科目" }, { "from": "announcements", "to": "school", "type": "data-access", "description": "关联年级/班级" }, { "from": "shared", "to": "auth", "type": "resolved", "description": "✅ 已修复:shared/lib 通过 session.ts 单一入口获取 session(dynamic import 打破静态循环)" }, { "from": "auth", "to": "shared", "type": "normal", "description": "auth.ts 依赖 shared/lib(合理,单向)" }, { "from": "layout", "to": "shared", "type": "normal", "description": "使用 shared 组件与配置" }, { "from": "settings", "to": "shared", "type": "normal", "description": "使用 shared/lib/ai" }, { "from": "settings", "to": "messaging", "type": "normal", "description": "通知偏好设置(v1.1 已重构:settings/actions-service.ts 直接调用 notifications/preferences.upsertNotificationPreferences,不再依赖 messaging/actions.updateNotificationPreferencesAction)" }, { "from": "settings", "to": "users", "type": "normal", "description": "v1.1 新增:settings/actions-service.ts.updateProfileAction 委托 users/actions.updateUserProfile" }, { "from": "settings", "to": "notifications", "type": "normal", "description": "v1.1 新增:settings/actions-service.ts.updateNotificationPreferencesAction 调用 notifications/preferences.upsertNotificationPreferences" }, { "from": "audit", "to": "shared", "type": "normal", "description": "使用 shared/lib/audit-logger" }, { "from": "files", "to": "shared", "type": "normal", "description": "使用 shared/db" }, { "from": "users", "to": "classes", "type": "data-access", "description": "✅ P1-4 已修复:通过 class-registration.ts 调用 classes/data-access.enrollStudentByInvitationCode,不再直写 classEnrollments" }, { "from": "error-book", "to": "shared", "type": "normal", "description": "使用 shared/db、auth-guard、types" }, { "from": "error-book", "to": "classes", "type": "data-access", "description": "通过 classes/data-access.getStudentIdsByClassIds 查询班级学生" }, { "from": "error-book", "to": "questions", "type": "data-access", "description": "通过 questions/actions.getQuestionsAction 查询题库(手动添加错题时)" } ] }, "knownIssues": [ { "id": "P0-1", "severity": "P0", "title": "classes/data-access.ts 超过 1000 行", "file": "src/modules/classes/data-access.ts", "lines": 2104, "problem": "混入 homework/scheduling/grades 逻辑,严重违反模块职责单一原则", "suggestion": "按职责拆分为 class-query/schedule/homework-insights/grade-query", "status": "resolved", "resolvedAt": "2026-06-17", "resolution": "拆分为 5 个文件:data-access.ts(548行,核心CRUD+邀请码+教师班级管理) + data-access-stats.ts(531行,作业统计) + data-access-schedule.ts(194行,课表) + data-access-students.ts(244行,学生查询) + data-access-admin.ts(406行,管理员班级管理),所有文件均 ≤800 行,data-access.ts 通过 re-export 保持向后兼容" }, { "id": "P0-2", "severity": "P0", "title": "homework/data-access.ts 超过 1000 行", "file": "src/modules/homework/data-access.ts", "lines": 1038, "problem": "混入排名计算业务逻辑", "suggestion": "分离排名逻辑到独立文件(如 data-access-ranking.ts)", "status": "resolved", "resolvedAt": "2026-06-17", "resolution": "拆分为 data-access.ts(598行) + stats-service.ts(425行),统计函数(getTeacherGradeTrends/getHomeworkAssignmentAnalytics/getStudentDashboardGrades)迁移至 stats-service.ts,data-access.ts 通过 re-export 保持向后兼容" }, { "id": "P0-3", "severity": "P0", "title": "shared/lib ↔ auth 循环依赖", "file": "src/shared/lib/{audit-logger,change-logger,auth-guard}.ts ↔ src/auth.ts", "problem": "shared/lib 依赖 @/auth(src/auth.ts),auth.ts 又依赖 shared/lib,形成循环依赖", "suggestion": "拆分 auth.ts,将 shared 依赖部分抽出为独立模块", "status": "resolved", "resolvedAt": "2026-06-18", "resolution": "新增 shared/lib/session.ts 单一入口封装 getSession()(server-only,内部 dynamic import @/auth 打破模块级静态循环)。audit-logger.ts/change-logger.ts/auth-guard.ts 改为 import { getSession } from '@/shared/lib/session',不再直接依赖 @/auth。运行时调用链保持不变,模块加载图无环。" }, { "id": "P0-4", "severity": "P0", "title": "dashboard 跨模块直接查询 11 张表", "file": "src/modules/dashboard/data-access.ts", "problem": "getAdminDashboardData 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles,严重违反模块封装", "suggestion": "改为通过各模块 data-access 获取数据", "status": "fixed", "fixedBy": "新增 getUsersDashboardStats/getClassesDashboardStats/getTextbooksDashboardStats/getQuestionsDashboardStats/getExamsDashboardStats/getHomeworkDashboardStats,dashboard 改为并行调用各模块 stats 函数" }, { "id": "P0-5", "severity": "P0", "title": "messaging 绕过 notifications 直接写通知", "file": "src/modules/messaging/actions.ts", "lines": "66-72", "problem": "直接调用 createNotification,导致用户通知偏好失效、多渠道通知无效", "suggestion": "改为通过 notifications dispatcher 统一分发", "status": "resolved", "resolvedAt": "2026-06-17", "resolution": "将 messageNotifications 和 notificationPreferences 表的 CRUD 函数从 messaging 迁移到 notifications 模块;notifications/channels/in-app-channel.ts 改为静态导入本地 createNotification(不再动态 import messaging);notifications/dispatcher.ts 改为从本地 preferences.ts 导入偏好函数;messaging 模块通过 re-export 保持向后兼容;依赖方向变为单向:messaging → notifications(messaging 调用 sendNotification dispatcher 发送通知)" }, { "id": "P0-6", "severity": "P0", "title": "classSchedule 表三处写入口", "file": "src/modules/classes/data-access.ts, src/modules/scheduling/actions.ts, src/modules/scheduling/data-access.ts", "problem": "三处独立写入 classSchedule 表,数据完整性高风险", "suggestion": "统一写入口到 scheduling 模块", "status": "fixed", "fixedBy": "将 classSchedule 写函数(createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem)从 classes/data-access-schedule.ts 迁移到新文件 scheduling/data-access-class-schedule.ts;classes 模块仅保留 READ 函数(getStudentSchedule/getClassSchedule);新增 classes/data-access.verifyTeacherOwnsClass 供 scheduling 模块跨模块校验教师班级归属;classes/actions.ts 改为从 @/modules/scheduling/data-access-class-schedule 导入写函数;类型 CreateClassScheduleItemInput/UpdateClassScheduleItemInput 从 classes/types.ts 迁移到 scheduling/types.ts;所有 classSchedule DB 写入统一由 scheduling 模块管理" }, { "id": "P0-7", "severity": "P0", "title": "classes 模块直查 homework/exams 表违反三层架构", "file": "src/modules/classes/data-access-stats.ts, src/modules/classes/data-access-students.ts", "problem": "data-access-stats.ts 和 data-access-students.ts 直接导入并查询 homeworkAssignmentQuestions/homeworkAssignmentTargets/homeworkAssignments/homeworkSubmissions/exams 表,违反 modules/ 不直接查询其他模块 DB 表的规则", "suggestion": "在 homework 模块新增 data-access-classes.ts 暴露所需数据,classes 模块改为调用这些函数", "status": "resolved", "resolvedAt": "2026-06-18", "resolution": "新增 src/modules/homework/data-access-classes.ts(232行,7个函数:getAssignmentIdsForStudents/getHomeworkAssignmentsWithSubject/getHomeworkAssignmentsByIds/getAssignmentTargetCounts/getHomeworkSubmissionsForStudents/getPublishedHomeworkAssignmentsWithSubject/getHomeworkSubmissionsForAssignments,re-export getAssignmentMaxScoreById);classes/data-access-stats.ts 和 data-access-students.ts 改为调用这些函数,移除对 homework/exams 表的直接导入;同时修复 P1 问题:移除 4 处 `as string` 断言(data-access-stats.ts),将 `studentScores.get(s.studentId)!` 非空断言替换为显式 null 检查(data-access-students.ts)" }, { "id": "P1-1", "severity": "P1", "title": "跨模块直接 DB 查询普遍存在", "problem": "classes(8+)/classEnrollments(6+)/users(6+)/subjects(6+)/exams(5+) 表被多个模块直接查询", "suggestion": "建立模块间数据访问规范,通过对方 data-access 或导出查询函数", "status": "resolved", "resolvedAt": "2026-06-18", "resolution": "所有跨模块直查已改为通过对方 data-access 接口:exams 通过 school data-access.getSubjectOptions/getGradeOptions;questions 通过 textbooks data-access.getKnowledgePointOptions;homework 通过 exams/classes/school/users data-access;grades 通过 classes/school/users data-access;classes 通过 school/homework data-access;attendance 通过 classes data-access;scheduling 通过 users data-access;notifications 通过 classes data-access;proctoring 通过 exams/users data-access;diagnostic 通过 exams/questions/classes/users data-access;dashboard 通过各模块 data-access;users updateUserProfile 已下沉到 data-access。各模块 data-access 暴露的查询接口:classes(getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId/verifyTeacherOwnsClass/getTeacherIdForMutations 等)、exams(getExamIdsByGradeIds/getExamSubjectIdMap/getExamWithQuestionsForHomework/getExamSubmissionWithAnswers/getExamForProctoringCrossModule 等)、school(getSubjectOptions/getGradeOptions)、users(getUserNamesByIds/getUserWithRole/getUserBasicInfo/getUserIdsByGradeId/getCurrentStudentUser)、textbooks(getKnowledgePointOptions)、questions(createQuestionWithRelations/getKnowledgePointsForQuestions)、homework/data-access-classes(7 个函数供 classes 模块跨模块调用)" }, { "id": "P1-2", "severity": "P1", "title": "actions 层混入数据访问逻辑", "file": "src/modules/{exams,homework,questions,announcements}/actions.ts", "problem": "actions.ts 中存在直接 db.insert/update/delete,应通过 data-access 层", "suggestion": "将 DB 操作下沉到 data-access 层", "status": "resolved", "resolvedAt": "2026-06-17", "resolution": "4 个模块的 actions 层 DB 操作全部下沉到 data-access:exams 新增 7 个 data-access 函数(actions.ts 831→691 行,data-access.ts 374→471 行);homework 新建 data-access-write.ts(285 行,10 个写函数,actions.ts 387→239 行);questions 新增 4 个 data-access 函数(actions.ts 294→149 行,data-access.ts 138→260 行);announcements 新增 5 个 data-access 函数(actions.ts 242→197 行,data-access.ts 120→171 行)。users/scheduling 待后续处理" }, { "id": "P1-3", "severity": "P1", "title": "auth.ts 混合 5 类职责", "file": "src/auth.ts", "problem": "NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数混合", "suggestion": "拆分为 auth-config/password-security/role-normalizer/ip-utils 等多文件", "status": "resolved", "resolution": "已拆分:密码安全DB操作→shared/lib/password-security-service.ts,角色规范化→shared/lib/role-utils.ts,bcrypt哈希规范化→shared/lib/bcrypt-utils.ts,IP解析→shared/lib/http-utils.ts。auth.ts 现 193 行仅保留 NextAuth 配置" }, { "id": "P1-4", "severity": "P1", "title": "users/import-export.ts 四重职责", "file": "src/modules/users/import-export.ts", "problem": "导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments)", "suggestion": "按职责拆分为 import-parser/exporter/user-creator/enrollment", "status": "resolved", "resolvedAt": "2026-06-17", "resolution": "已拆分:import-export.ts(157行,仅保留文件解析/生成) + user-service.ts(82行,batchImportUsers 用户创建+密码哈希+角色分配) + class-registration.ts(21行,registerStudentByInvitationCode 委托 classes/data-access.enrollStudentByInvitationCode)。batchImportUsers 不再直写 classEnrollments,改为调用 classes/data-access.enrollStudentByInvitationCode。import-export.ts 通过 re-export batchImportUsers/UserImportResult 保持向后兼容" }, { "id": "P1-5", "severity": "P1", "title": "proctoring 死代码", "file": "src/modules/proctoring/components/exam-mode-config.tsx", "problem": "组件已创建但未集成到考试表单,DB schema 有 examMode 字段但表单不收集;事件上报存在 Server Action 与 REST API 双通道重复", "suggestion": "集成 proctoring/exam-mode-config 到考试表单;删除重复的 REST API 路由", "status": "partially_fixed", "fixedBy": "删除 /api/proctoring/event REST 路由(移至 deletes/),Server Action recordProctoringEventAction 为唯一规范路径;exam-mode-config.tsx 集成属于功能新增,暂保留原位" }, { "id": "P2-1", "severity": "P2", "title": "schema.ts 54 张表混合(1111 行)", "file": "src/shared/db/schema.ts", "lines": 1111, "problem": "所有表定义混合在单文件,虽可接受但需分节", "suggestion": "按业务域分节(加注释分隔)或拆分为多文件" }, { "id": "P2-2", "severity": "P2", "title": "ai.ts 混合 5 类职责(218 行)", "file": "src/shared/lib/ai.ts", "lines": 218, "problem": "AI 调用 + Provider 配置 + API Key 加密 + 错误格式化 + 负载解析混合在单文件", "suggestion": "按职责拆分为 ai/ 目录多文件", "status": "resolved", "resolvedAt": "2026-06-17", "resolution": "已拆分为 src/shared/lib/ai/ 目录:payload-parser.ts(78 行,请求负载解析) + api-key-crypto.ts(28 行,API Key 加密/解密) + provider-config.ts(61 行,Provider 配置查询) + client.ts(58 行,AI 客户端创建与调用) + errors.ts(8 行,错误格式化) + index.ts(5 行,聚合导出)。原 ai.ts 保留为向后兼容的重导出文件(9 行)" }, { "id": "P1-6", "severity": "P1", "title": "三个 logger 重复实现 IP/Header 提取", "file": "src/shared/lib/{audit-logger,change-logger,login-logger}.ts, src/auth.ts", "problem": "audit-logger.ts / change-logger.ts / login-logger.ts / auth.ts 四处重复实现 IP/User-Agent 提取逻辑,且实现略有差异", "suggestion": "提取 shared/lib/http-utils.ts 统一 IP/UA 提取", "status": "resolved", "resolvedAt": "2026-06-18", "resolution": "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 第一段,更准确)" }, { "id": "P2-20", "severity": "P2", "title": "homework/data-access.getDemoStudentUser 使用 auth() 而非 auth-guard", "file": "src/modules/homework/data-access.ts", "problem": "getDemoStudentUser 直接调用 auth() 获取学生身份,绕过 shared/lib/auth-guard 的统一认证上下文,且导致 student 页面虚假依赖 homework 模块", "suggestion": "迁移到 users 模块 getCurrentStudentUser,使用 auth-guard.getAuthContext", "status": "resolved", "resolvedAt": "2026-06-18", "resolution": "getDemoStudentUser 从 homework 模块迁移至 users 模块 getCurrentStudentUser(通过 session + JOIN users/usersToRoles/roles 校验 student 角色);6 个 student 页面(dashboard/assignments/assignments-[assignmentId]/courses/textbooks/textbooks-[id]/schedule)改用 users 模块,消除对 homework 的虚假依赖;student/elective 改用 getAuthContext();homework 保留 re-export 向后兼容" } ], "routes": { "auth": { "/login": { "component": "LoginForm", "type": "client", "module": "auth" }, "/register": { "component": "RegisterForm + registerAction", "type": "server", "module": "auth", "description": "注册页面(含未成年人信息保护、隐私政策/用户协议同意勾选)" }, "/privacy": { "component": "PrivacyPage", "type": "server", "module": "auth", "description": "隐私政策页面(信息收集/使用/保护、用户权利、Cookie、未成年人保护条款、联系方式)" }, "/terms": { "component": "TermsPage", "type": "server", "module": "auth", "description": "用户协议页面(服务说明、注册、行为规范、知识产权、免责、变更终止、法律适用)" } }, "onboarding": { "/onboarding": { "component": "OnboardingPage + OnboardingStepper", "type": "server", "module": "onboarding", "method": "GET", "actions": [ "getOnboardingStatusAction", "completeOnboardingAction" ], "dataAccess": [ "onboarding/data-access.getOnboardingStatus", "onboarding/data-access.updateUserProfile", "onboarding/data-access.bindParentToChild", "onboarding/data-access.resolveDefaultPathByRoles", "classes/data-access.enrollStudentByInvitationCode", "classes/data-access.enrollTeacherByInvitationCode" ], "auth": "required", "description": "首次登录引导(独立路由 + middleware 重定向 + Server Action + Zod + 事务)" } }, "admin": { "_layout": { "file": "app/(dashboard)/admin/layout.tsx", "type": "server", "purpose": "admin 路由组统一权限守卫:调用 getAuthContext() 校验已登录,未登录抛 PermissionDeniedError;细粒度权限由各页面 requirePermission() 自行检查", "authGuard": "shared/lib/auth-guard.getAuthContext", "auth": "required" }, "/admin/dashboard": { "component": "AdminDashboardView", "type": "server", "dataAccess": [ "dashboard/data-access.getAdminDashboardData" ], "permission": "school:manage" }, "/admin/school": { "component": "重定向", "type": "server", "redirect": "/admin/school/classes", "permission": "school:manage" }, "/admin/school/schools": { "component": "SchoolsClient", "type": "client", "module": "school", "permission": "school:manage" }, "/admin/school/grades": { "component": "GradesClient", "type": "client", "module": "school", "permission": "school:manage" }, "/admin/school/grades/insights": { "component": "年级作业洞察", "type": "server", "dataAccess": [ "classes/data-access.getGradeHomeworkInsights" ], "permission": "school:manage" }, "/admin/school/departments": { "component": "DepartmentsClient", "type": "client", "module": "school", "permission": "school:manage" }, "/admin/school/classes": { "component": "AdminClassesClient", "type": "client", "module": "classes", "permission": "school:manage" }, "/admin/school/academic-year": { "component": "AcademicYearClient", "type": "client", "module": "school", "permission": "school:manage" }, "/admin/audit-logs": { "component": "AuditLogView", "type": "server", "module": "audit", "dataAccess": [ "audit/data-access.getAuditLogs", "audit/data-access.getAuditModuleOptions" ], "permission": "audit_log:read" }, "/admin/audit-logs/login-logs": { "component": "LoginLogView", "type": "server", "module": "audit", "dataAccess": [ "audit/data-access.getLoginLogs" ], "permission": "audit_log:read" }, "/admin/announcements": { "component": "AdminAnnouncementsView", "type": "server", "module": "announcements", "dataAccess": [ "announcements/data-access.getAnnouncements", "school/data-access.getGrades", "classes/data-access.getAdminClasses" ], "actions": [ "createAnnouncementAction" ], "permission": "announcement:manage" }, "/admin/announcements/[id]": { "component": "AnnouncementForm (edit)", "type": "server", "module": "announcements", "dataAccess": [ "announcements/data-access.getAnnouncementById", "school/data-access.getGrades" ], "actions": [ "updateAnnouncementAction" ], "permission": "announcement:manage" }, "/admin/files": { "component": "AdminFilesView", "type": "server", "module": "files", "dataAccess": [ "files/data-access.getAllFileAttachments" ], "permission": "file:read" }, "/admin/course-plans": { "component": "CoursePlanList", "type": "client", "module": "course-plans", "dataAccess": [ "course-plans/data-access.getCoursePlans" ], "permission": "course_plan:manage" }, "/admin/course-plans/create": { "component": "CoursePlanForm (create)", "type": "client", "module": "course-plans", "actions": [ "createCoursePlanAction" ], "dataAccess": [ "classes/data-access.getAdminClasses", "course-plans/data-access.getSubjectOptions", "classes/data-access.getStaffOptions", "school/data-access.getAcademicYears" ], "permission": "course_plan:manage" }, "/admin/course-plans/[id]": { "component": "CoursePlanDetail", "type": "client", "module": "course-plans", "dataAccess": [ "course-plans/data-access.getCoursePlanById" ], "actions": [ "deleteCoursePlanAction", "createCoursePlanItemAction", "updateCoursePlanItemAction", "deleteCoursePlanItemAction", "toggleCoursePlanItemCompletedAction" ], "permission": "course_plan:manage" }, "/admin/course-plans/[id]/edit": { "component": "CoursePlanForm (edit)", "type": "client", "module": "course-plans", "actions": [ "updateCoursePlanAction" ], "dataAccess": [ "course-plans/data-access.getCoursePlanById", "classes/data-access.getAdminClasses", "course-plans/data-access.getSubjectOptions", "classes/data-access.getStaffOptions", "school/data-access.getAcademicYears" ], "permission": "course_plan:manage" }, "/admin/attendance": { "component": "AttendanceRecordList", "type": "server", "module": "attendance", "dataAccess": [ "attendance/data-access.getAttendanceRecords (scope=all)", "classes/data-access.getAdminClasses" ], "permission": "attendance:manage", "description": "管理员考勤总览(权限:requirePermission(ATTENDANCE_MANAGE))" }, "/admin/users/import": { "component": "UserImportPage (含 UserImportDialog)", "type": "server", "module": "users", "actions": [ "users/actions.downloadUserTemplateAction", "users/actions.importUsersAction" ], "permission": "user:manage", "description": "用户批量导入页面(说明卡片+字段文档表+导入对话框;权限:requirePermission(USER_MANAGE))" }, "/admin/users": { "component": "AdminUsersPage (含 AdminUsersView)", "type": "server", "module": "users", "dataAccess": [ "users/data-access.getAdminUsers", "users/data-access.getAdminUserRoles" ], "actions": [ "users/actions.updateUserRoleAction", "users/actions.deleteUserAction" ], "permission": "user:manage", "description": "管理员用户列表页面(搜索+角色筛选+分页表格+删除;权限:requirePermission(USER_MANAGE))" }, "/admin/scheduling/rules": { "component": "SchedulingRulesForm", "type": "server", "module": "scheduling", "dataAccess": [ "scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getSchedulingRules" ], "actions": [ "saveSchedulingRulesAction" ], "permission": "schedule:adjust", "description": "排课规则配置页面(权限:requirePermission(SCHEDULE_ADJUST))" }, "/admin/scheduling/auto": { "component": "AutoSchedulePanel + AutoScheduleResultView", "type": "server", "module": "scheduling", "dataAccess": [ "scheduling/actions.getAdminClassesForScheduling" ], "actions": [ "autoScheduleAction", "applyAutoScheduleAction" ], "permission": "schedule:auto", "description": "自动排课页面(预览+应用;权限:requirePermission(SCHEDULE_AUTO))" }, "/admin/scheduling/changes": { "component": "ScheduleChangeList + ScheduleConflictsView", "type": "server", "module": "scheduling", "dataAccess": [ "scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getScheduleChanges" ], "actions": [ "approveScheduleChangeAction", "rejectScheduleChangeAction", "getClassConflictsAction" ], "permission": "schedule:adjust", "description": "调课申请审批+冲突检测页面(权限:requirePermission(SCHEDULE_ADJUST);审批操作需 SCHEDULE_AUTO)" }, "/admin/elective": { "component": "ElectiveCourseList", "type": "server", "module": "elective", "dataAccess": [ "elective/data-access.getElectiveCourses (scope=all)" ], "actions": [ "deleteElectiveCourseAction", "openSelectionAction", "closeSelectionAction", "runLotteryAction" ], "permission": "elective:manage", "description": "管理员选修课程列表(权限:requirePermission(ELECTIVE_MANAGE))" }, "/admin/elective/create": { "component": "ElectiveCourseForm", "type": "client", "module": "elective", "actions": [ "createElectiveCourseAction" ], "dataAccess": [ "elective/data-access.getSubjectOptions" ], "permission": "elective:manage", "description": "创建选修课程(权限:requirePermission(ELECTIVE_MANAGE))" }, "/admin/elective/[id]/edit": { "component": "ElectiveCourseForm (edit)", "type": "client", "module": "elective", "actions": [ "updateElectiveCourseAction" ], "dataAccess": [ "elective/data-access.getElectiveCourseById", "elective/data-access.getSubjectOptions" ], "permission": "elective:manage", "description": "编辑选修课程(权限:requirePermission(ELECTIVE_MANAGE))" }, "/admin/settings": { "component": "AdminSettingsView", "type": "client", "module": "settings", "permission": "settings:admin", "description": "系统设置页面(学校信息/安全策略/文件上传/通知配置;权限:requirePermission(SETTINGS_ADMIN))" }, "/admin/error-book": { "component": "AdminErrorBookPage + ClassErrorOverview", "type": "server", "module": "error-book", "permission": "error_book:analytics_read", "description": "管理员错题分析页面(全校错题统计/薄弱知识点/学科分布/高频错题;权限:requirePermission(ERROR_BOOK_ANALYTICS_READ))" } }, "teacher": { "/teacher/dashboard": { "component": "TeacherDashboardView", "type": "server", "dataAccess": [ "dashboard/data-access (teacher)", "homework/data-access.getTeacherGradeTrends", "classes/data-access.getTeacherClasses" ], "permission": "exam:read" }, "/teacher/exams/all": { "component": "ExamDataTable", "type": "server", "dataAccess": [ "exams/data-access.getExams" ], "permission": "exam:read" }, "/teacher/exams/create": { "component": "ExamForm", "type": "client", "actions": [ "createExamAction", "createAiExamAction", "previewAiExamAction" ], "permission": "exam:create" }, "/teacher/questions": { "component": "QuestionDataTable", "type": "server", "dataAccess": [ "questions/data-access.getQuestions" ], "permission": "question:read" }, "/teacher/textbooks": { "component": "TextbookList", "type": "server", "dataAccess": [ "textbooks/data-access.getTextbooks" ], "permission": "textbook:read" }, "/teacher/textbooks/[id]": { "component": "TextbookReader", "type": "client", "dataAccess": [ "textbooks/data-access.getTextbookById", "getChaptersByTextbookId", "getKnowledgePointsByTextbookId" ], "permission": "textbook:read" }, "/teacher/classes/my": { "component": "ClassList", "type": "server", "dataAccess": [ "classes/data-access.getTeacherClasses" ], "permission": "class:read" }, "/teacher/classes/schedule": { "component": "ClassSchedule", "type": "server", "dataAccess": [ "classes/data-access.getClassSchedule" ], "permission": "class:read" }, "/teacher/classes/students": { "component": "ClassStudents", "type": "server", "dataAccess": [ "classes/data-access.getClassStudents" ], "permission": "class:read" }, "/teacher/classes": { "component": "重定向", "type": "server", "redirect": "/teacher/classes/my", "permission": "class:read" }, "/teacher/classes/my/[id]": { "component": "班级详情", "type": "client", "module": "classes", "dataAccess": [ "classes/data-access.getClassStudents", "classes/data-access.getClassSchedule", "classes/data-access.getClassHomeworkInsights" ], "permission": "class:read" }, "/teacher/homework": { "component": "重定向", "type": "server", "redirect": "/teacher/homework/assignments", "permission": "homework:create" }, "/teacher/homework/assignments": { "component": "作业列表", "type": "server", "module": "homework", "dataAccess": [ "homework/data-access.getHomeworkAssignments" ], "permission": "homework:create" }, "/teacher/homework/assignments/create": { "component": "HomeworkAssignmentForm", "type": "client", "module": "homework", "actions": [ "createHomeworkAssignmentAction" ], "permission": "homework:create" }, "/teacher/homework/assignments/[id]": { "component": "作业详情+错误分析", "type": "client", "module": "homework", "dataAccess": [ "homework/data-access.getHomeworkAssignmentById", "homework/data-access.getHomeworkAssignmentAnalytics" ], "permission": "homework:create" }, "/teacher/homework/assignments/[id]/submissions": { "component": "作业提交列表", "type": "server", "module": "homework", "dataAccess": [ "homework/data-access.getHomeworkSubmissions" ], "permission": "homework:create" }, "/teacher/homework/submissions": { "component": "批改列表", "type": "server", "module": "homework", "dataAccess": [ "homework/data-access.getHomeworkAssignmentReviewList" ], "permission": "homework:grade" }, "/teacher/homework/submissions/[submissionId]": { "component": "HomeworkGradingView", "type": "client", "module": "homework", "actions": [ "gradeHomeworkSubmissionAction" ], "dataAccess": [ "homework/data-access.getHomeworkSubmissionDetails" ], "permission": "homework:grade" }, "/teacher/exams": { "component": "重定向", "type": "server", "redirect": "/teacher/exams/all", "permission": "exam:read" }, "/teacher/exams/[id]/build": { "component": "ExamAssembly", "type": "client", "module": "exams", "permission": "exam:update" }, "/teacher/exams/grading": { "component": "重定向", "type": "server", "redirect": "/teacher/homework/submissions", "permission": "homework:grade" }, "/teacher/exams/grading/[submissionId]": { "component": "重定向", "type": "server", "redirect": "/teacher/homework/submissions", "permission": "homework:grade" }, "/teacher/grades": { "component": "成绩管理首页", "type": "server", "module": "grades", "dataAccess": [ "grades/actions.getGradeRecordsAction" ], "permission": "grade_record:read" }, "/teacher/grades/entry": { "component": "批量成绩录入", "type": "server", "module": "grades", "actions": [ "grades/actions.batchCreateGradeRecordsAction", "grades/actions.createGradeRecordAction" ], "permission": "grade_record:manage" }, "/teacher/grades/stats": { "component": "成绩统计报表", "type": "server", "module": "grades", "dataAccess": [ "grades/actions.getClassGradeStatsAction", "grades/actions.getClassRankingAction" ], "permission": "grade_record:read" }, "/teacher/grades/analytics": { "component": "成绩分析", "type": "server", "module": "grades", "dataAccess": [ "grades/data-access-analytics.getGradeTrend", "grades/data-access-analytics.getClassComparison", "grades/data-access-analytics.getSubjectComparison", "grades/data-access-analytics.getGradeDistribution" ], "permission": "grade_record:read" }, "/teacher/course-plans": { "component": "CoursePlanList (teacher)", "type": "client", "module": "course-plans", "dataAccess": [ "course-plans/data-access.getCoursePlans (filtered by teacherId)" ], "permission": "course_plan:read" }, "/teacher/course-plans/[id]": { "component": "CoursePlanDetail (teacher, read-only)", "type": "client", "module": "course-plans", "dataAccess": [ "course-plans/data-access.getCoursePlanById" ], "permission": "course_plan:read" }, "/teacher/attendance": { "component": "AttendanceRecordList + AttendanceFilters", "type": "server", "module": "attendance", "dataAccess": [ "attendance/data-access.getAttendanceRecords", "classes/data-access.getTeacherClasses" ], "permission": "attendance:manage", "description": "教师考勤记录列表(权限:requirePermission(ATTENDANCE_MANAGE))" }, "/teacher/attendance/sheet": { "component": "AttendanceSheet", "type": "client", "module": "attendance", "actions": [ "batchRecordAttendanceAction", "getClassAttendanceForDateAction" ], "dataAccess": [ "attendance/data-access.getClassStudentsForAttendance" ], "permission": "attendance:manage", "description": "批量点名页面(权限:requirePermission(ATTENDANCE_MANAGE))" }, "/teacher/attendance/stats": { "component": "AttendanceStatsCard", "type": "server", "module": "attendance", "dataAccess": [ "attendance/data-access-stats.getClassAttendanceStats", "classes/data-access.getTeacherClasses" ], "permission": "attendance:read", "description": "班级考勤统计(权限:requirePermission(ATTENDANCE_READ))" }, "/teacher/schedule-changes": { "component": "ScheduleChangeForm + ScheduleChangeList", "type": "server", "module": "scheduling", "dataAccess": [ "scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getTeachersForScheduling", "scheduling/actions.getScheduleChanges (requesterId=ctx.userId)" ], "actions": [ "requestScheduleChangeAction" ], "permission": "schedule:adjust", "description": "教师调课/代课申请页面(提交申请+查看本人申请列表;权限:requirePermission(SCHEDULE_ADJUST);admin 角色查看全部申请)" }, "/teacher/diagnostic": { "component": "ReportList", "type": "client", "module": "diagnostic", "dataAccess": [ "diagnostic/data-access-reports.getDiagnosticReports" ], "actions": [ "publishReportAction", "deleteReportAction" ], "permission": "diagnostic:read", "description": "学情诊断报告列表(reportType/status 过滤器;权限:requirePermission(DIAGNOSTIC_READ);DataScope.class_members 仅查看自己报告;发布/删除操作需 DIAGNOSTIC_MANAGE)" }, "/teacher/diagnostic/student/[studentId]": { "component": "StudentDiagnosticView", "type": "client", "module": "diagnostic", "dataAccess": [ "diagnostic/data-access.getStudentMasterySummary", "diagnostic/data-access.getKnowledgePointStats (班级平均对比)", "diagnostic/data-access-reports.getDiagnosticReports" ], "actions": [ "generateStudentReportAction" ], "permission": "diagnostic:read", "description": "学生学情诊断视图(概览卡片+雷达图+强项/弱项+生成报告[DIAGNOSTIC_MANAGE]+最新报告;权限:getAuthContext + DataScope 二次校验,class_members 仅自己,children 仅子女)" }, "/teacher/diagnostic/class/[classId]": { "component": "ClassDiagnosticView", "type": "client", "module": "diagnostic", "dataAccess": [ "diagnostic/data-access.getClassMasterySummary" ], "actions": [ "generateClassReportAction" ], "permission": "diagnostic:read", "description": "班级学情诊断视图(概览+知识点热力图+排名表+需重点关注学生+生成班级报告[DIAGNOSTIC_MANAGE];权限:getAuthContext + DataScope 校验,class_taught 必须包含 classId,class_members/children notFound)" }, "/teacher/elective": { "component": "ElectiveCourseList (teacher)", "type": "server", "module": "elective", "dataAccess": [ "elective/data-access.getElectiveCourses (scope=class_taught/owned, currentUserId)" ], "actions": [ "deleteElectiveCourseAction", "openSelectionAction", "closeSelectionAction", "runLotteryAction" ], "permission": "elective:manage", "description": "教师选修课程列表(权限:requirePermission(ELECTIVE_MANAGE);DataScope.class_taught/owned 按 teacherId 过滤)" }, "/teacher/lesson-plans": { "component": "LessonPlanList", "type": "server", "module": "lesson-preparation", "method": "GET", "dataAccess": [ "lesson-preparation/data-access.getLessonPlans" ], "permission": "lesson_plan:read", "description": "教师课案列表(权限:requirePermission(LESSON_PLAN_READ))" }, "/teacher/lesson-plans/new": { "component": "LessonPlanEditor", "type": "client", "module": "lesson-preparation", "method": "GET", "actions": [ "createLessonPlanAction", "getLessonPlanTemplatesAction", "getKnowledgePointOptionsAction" ], "permission": "lesson_plan:create", "description": "新建课案页面(权限:requirePermission(LESSON_PLAN_CREATE))" }, "/teacher/lesson-plans/[planId]/edit": { "component": "LessonPlanEditor", "type": "client", "module": "lesson-preparation", "method": "GET", "dataAccess": [ "lesson-preparation/data-access.getLessonPlanById", "lesson-preparation/data-access-versions.getLessonPlanVersions" ], "actions": [ "updateLessonPlanAction", "saveLessonPlanVersionAction", "revertLessonPlanVersionAction", "deleteLessonPlanAction", "duplicateLessonPlanAction", "saveAsTemplateAction", "suggestKnowledgePointsAction", "publishLessonPlanHomeworkAction" ], "permission": "lesson_plan:update", "permissionRead": "lesson_plan:read", "description": "编辑课案页面(权限:requirePermission(LESSON_PLAN_UPDATE);只读访问需 LESSON_PLAN_READ)" }, "/teacher/error-book": { "component": "TeacherErrorBookPage + ClassErrorOverview", "type": "server", "module": "error-book", "dataAccess": [ "error-book/data-access.getStudentErrorBookSummaries", "error-book/data-access.getKnowledgePointWeakness", "error-book/data-access.getSubjectErrorDistribution", "error-book/data-access.getTopWrongQuestionsByStudentIds", "error-book/data-access.getStudentNameMap", "classes/data-access.getStudentIdsByClassIds" ], "permission": "error_book:analytics_read", "description": "教师错题分析页面(所教班级学生的错题统计/薄弱知识点/学科分布/高频错题;权限:requirePermission(ERROR_BOOK_ANALYTICS_READ);DataScope.class_taught)" } }, "student": { "/student/dashboard": { "component": "StudentDashboardView", "type": "server", "dataAccess": [ "users/data-access.getCurrentStudentUser", "classes/data-access.getStudentClasses", "classes/data-access.getStudentSchedule", "homework/data-access.getStudentHomeworkAssignments", "homework/data-access.getStudentDashboardGrades" ], "permission": "homework:submit" }, "/student/learning/assignments": { "component": "学生作业列表", "type": "server", "module": "homework", "dataAccess": [ "users/data-access.getCurrentStudentUser", "homework/data-access.getStudentHomeworkAssignments" ], "permission": "homework:submit" }, "/student/learning/assignments/[assignmentId]": { "component": "学生作答/复习", "type": "server", "module": "homework", "actions": [ "startHomeworkSubmissionAction", "saveHomeworkAnswerAction", "submitHomeworkAction" ], "dataAccess": [ "users/data-access.getCurrentStudentUser", "homework/data-access.getStudentHomeworkTakeData" ], "permission": "homework:submit" }, "/student/learning/courses": { "component": "StudentCoursesView", "type": "server", "dataAccess": [ "users/data-access.getCurrentStudentUser", "classes/data-access.getStudentClasses" ], "permission": "homework:submit" }, "/student/learning/textbooks": { "component": "学生教材列表(只读)", "type": "server", "module": "textbooks", "dataAccess": [ "users/data-access.getCurrentStudentUser", "textbooks/data-access.getTextbooks" ], "permission": "textbook:read" }, "/student/learning/textbooks/[id]": { "component": "学生教材阅读(只读)", "type": "server", "module": "textbooks", "dataAccess": [ "users/data-access.getCurrentStudentUser", "textbooks/data-access.getTextbookById", "textbooks/data-access.getChaptersByTextbookId", "textbooks/data-access.getKnowledgePointsByTextbookId" ], "permission": "textbook:read" }, "/student/schedule": { "component": "学生课表", "type": "server", "module": "classes", "dataAccess": [ "users/data-access.getCurrentStudentUser", "classes/data-access.getStudentClasses", "classes/data-access.getStudentSchedule" ], "permission": "homework:submit" }, "/student/grades": { "component": "我的成绩", "type": "server", "module": "grades", "dataAccess": [ "grades/data-access.getStudentGradeSummary" ], "permission": "grade_record:read" }, "/student/attendance": { "component": "StudentAttendanceView", "type": "server", "module": "attendance", "dataAccess": [ "attendance/data-access-stats.getStudentAttendanceSummary" ], "permission": "attendance:read", "description": "学生考勤视图(统计卡片 + 最近记录;权限:requirePermission(ATTENDANCE_READ),DataScope.class_members 仅查自己)" }, "/student/diagnostic": { "component": "StudentDiagnosticView", "type": "server", "module": "diagnostic", "dataAccess": [ "diagnostic/data-access.getStudentMasterySummary (ctx.userId)", "diagnostic/data-access-reports.getDiagnosticReports (studentId=ctx.userId)" ], "permission": "diagnostic:read", "description": "学生本人学情诊断视图(概览+雷达图+强项/弱项+最新报告;权限:requirePermission(DIAGNOSTIC_READ),DataScope.class_members 仅查自己)" }, "/student/elective": { "component": "StudentSelectionView", "type": "server", "module": "elective", "dataAccess": [ "elective/data-access-selections.getAvailableCoursesForStudent", "elective/data-access-selections.getStudentSelections" ], "actions": [ "selectCourseAction", "dropCourseAction" ], "permission": "elective:select", "description": "学生选课页面(可选课程列表 + 我的选课记录;权限:requirePermission(ELECTIVE_SELECT))" }, "/student/error-book": { "component": "StudentErrorBookPage + ErrorBookStatsCards + ErrorBookFilters + ErrorBookList + AddErrorBookDialog", "type": "server", "module": "error-book", "dataAccess": [ "error-book/data-access.getErrorBookStats (ctx.userId)", "error-book/data-access.getErrorBookItems (ctx.userId)" ], "actions": [ "createErrorBookItemAction", "updateErrorBookNoteAction", "reviewErrorBookItemAction", "deleteErrorBookItemAction", "archiveErrorBookItemAction", "getErrorBookItemDetailAction" ], "permission": "error_book:read", "description": "学生错题本页面(统计卡片/筛选/列表/手动添加/详情复习;权限:requirePermission(ERROR_BOOK_READ);DataScope.owned 仅查自己)" } }, "management": { "/management/grade": { "component": "GradeManagementRedirect", "type": "server", "module": "grades", "redirect": "/management/grade/classes", "permission": "grade:manage" }, "/management/grade/classes": { "component": "GradeClassesClient", "type": "client", "module": "classes", "permission": "grade:manage" }, "/management/grade/insights": { "component": "年级作业洞察", "type": "server", "dataAccess": [ "classes/data-access.getGradeHomeworkInsights" ], "permission": "grade:manage" } }, "parent": { "/parent/dashboard": { "component": "ParentDashboard", "type": "server", "module": "parent", "dataAccess": [ "parent/data-access.getParentDashboardData" ], "permission": "auth_required", "description": "家长仪表盘首页(问候语 + 子女卡片网格;权限:requireAuth())" }, "/parent/children/[studentId]": { "component": "ChildDetailHeader + ChildDetailPanel (Tab 布局 + SiblingSwitcher)", "type": "server", "module": "parent", "dataAccess": [ "parent/data-access.getChildDashboardData", "parent/data-access.getChildNameList" ], "permission": "auth_required", "description": "v4 升级:子女详情页改为 6-Tab 布局(overview/homework/grades/schedule/attendance/diagnostic),支持 ?tab= URL 参数与多子女切换;权限:requireAuth() + 二次校验 ctx.dataScope.childrenIds 包含 studentId" }, "/parent/grades": { "component": "子女成绩", "type": "server", "module": "grades", "dataAccess": [ "grades/data-access.getStudentGradeSummary" ], "permission": "grade_record:read", "description": "家长成绩视图(按 DataScope.children 过滤;v4 新增 ParentExportButton 占位)" }, "/parent/diagnostic": { "component": "子女学情诊断", "type": "server", "module": "diagnostic", "dataAccess": [ "diagnostic/data-access.getStudentMasterySummary", "diagnostic/data-access-reports.getDiagnosticReports" ], "permission": "diagnostic:read", "description": "P2-5 新增:家长查看多子女学情诊断(按 DataScope.children 遍历,复用 StudentDiagnosticView 组件;含 loading.tsx + error.tsx)" }, "/parent/attendance": { "component": "StudentAttendanceView (per child) + ParentAttendanceWarning", "type": "server", "module": "attendance", "dataAccess": [ "parent/data-access.getChildren", "attendance/data-access-stats.getStudentAttendanceSummary" ], "permission": "attendance:read", "description": "v4 升级:家长考勤视图新增 ParentAttendanceWarning 异常预警横幅(absent>=3 high、absent>=1 medium、late>=3 medium、presentRate<90 high);权限:requirePermission(ATTENDANCE_READ),DataScope.children 仅查子女" }, "/parent/leave": { "component": "LeaveRequestPage", "type": "server", "module": "parent", "permission": "auth_required", "description": "v4 新增:请假申请占位页(功能开发中,提供线下联系方式入口)" }, "/parent/error-book": { "component": "ParentErrorBookPage + ErrorBookStatsCards + TopWrongQuestions", "type": "server", "module": "error-book", "dataAccess": [ "error-book/data-access.getErrorBookStats (per child)", "error-book/data-access.getTopWrongQuestionsByStudentIds", "error-book/data-access.getKnowledgePointWeakness", "error-book/data-access.getStudentNameMap" ], "permission": "error_book:read", "description": "家长错题本页面(子女错题统计/薄弱知识点/高频错题;权限:requirePermission(ERROR_BOOK_READ);DataScope.children 仅查子女)" } }, "root": { "/": { "component": "重定向", "type": "server", "redirect": "/dashboard" } }, "shared": { "/dashboard": { "component": "角色路由分发", "type": "server", "redirect": "按permissions判断→/admin|/teacher|/student|/parent" }, "/profile": { "component": "ProfilePage", "type": "server", "permission": "auth_required", "description": "个人资料页面(展示头像 Avatar + Personal Information + Account Information + 学生/教师概览;头像从 userProfile.image 获取,无头像时显示用户名首字母 fallback)" }, "/settings": { "component": "SettingsPage", "type": "server", "permission": "auth_required", "dataAccess": [ "messaging/notification-preferences.getNotificationPreferences (re-export from notifications/preferences.ts)" ], "description": "设置页面(按角色分发 AdminSettingsView/TeacherSettingsView/StudentSettingsView/ParentSettingsView;含 General/Notifications/Appearance/Security/AI tab,Tab 通过 URL ?tab= 参数持久化,AI tab 需 AI_CONFIGURE 权限,Notifications 渲染 NotificationPreferencesForm,登出按钮 AlertDialog 二次确认)" }, "/announcements": { "component": "AnnouncementList (published only, audience-filtered)", "type": "server", "module": "announcements", "dataAccess": [ "announcements/data-access.getAnnouncements (status=published, audience={gradeId, classId})", "classes/data-access.getStudentActiveClassId", "classes/data-access.getStudentActiveGradeId", "classes/data-access.getClassGradeId" ], "permission": "announcement:read", "description": "用户端公告列表(根据用户身份过滤受众:school 全可见 / grade 按年级 / class 按班级)" }, "/announcements/[id]": { "component": "AnnouncementDetail (read-only, canManage=false)", "type": "server", "module": "announcements", "dataAccess": [ "announcements/data-access.getAnnouncementById" ], "permission": "announcement:read", "description": "用户端公告详情页(只读模式,无编辑/删除按钮)" } }, "messages": { "/messages": { "component": "MessageList + NotificationList", "type": "server", "module": "messaging", "dataAccess": [ "messaging/data-access.getMessages", "messaging/data-access.getNotifications" ], "permission": "message:read", "description": "消息首页(收件箱/已发送列表 + 通知列表;权限:requirePermission(MESSAGE_READ))" }, "/messages/[id]": { "component": "MessageDetail", "type": "server", "module": "messaging", "dataAccess": [ "messaging/data-access.getMessageById", "messaging/data-access.getMessageThread" ], "actions": [ "markMessageAsReadAction (自动已读)" ], "permission": "message:read", "description": "消息详情(含回复线程;权限:requirePermission(MESSAGE_READ))" }, "/messages/compose": { "component": "MessageCompose", "type": "server", "module": "messaging", "dataAccess": [ "messaging/data-access.getRecipients" ], "permission": "message:send", "description": "写消息页面(支持 reply 模式 via searchParams: receiverId, subject, parentMessageId;权限:requirePermission(MESSAGE_SEND))" } } }, "apiRoutes": { "/api/auth/[...nextauth]": { "methods": [ "GET", "POST" ], "handler": "auth.handlers", "auth": "public" }, "/api/ai/chat": { "methods": [ "POST" ], "handler": "createAiChatCompletion", "auth": "AI_CHAT", "validation": "parseAiChatPayload (Zod)" }, "/api/onboarding/complete": { "methods": [ "POST" ], "handler": "onboarding complete(已废弃,迁移至 completeOnboardingAction Server Action)", "auth": "required", "validation": "Zod schema", "deprecated": true, "deprecatedReason": "存在角色自选越权、教师覆盖任课、无事务等漏洞,已迁移至 modules/onboarding/actions.ts" }, "/api/onboarding/status": { "methods": [ "GET" ], "handler": "onboarding status(已废弃,迁移至 getOnboardingStatusAction Server Action)", "auth": "required", "deprecated": true, "deprecatedReason": "引导流程改为独立路由 /onboarding + middleware 重定向,不再需要客户端拉取状态" }, "/api/upload": { "methods": [ "POST" ], "handler": "文件上传 (multipart/form-data)", "auth": "requireAuth", "module": "files", "validation": "isAllowedMimeType + MAX_FILE_SIZE (10MB)", "description": "保存文件到 public/uploads/YYYY-MM/cuid.ext,写入 fileAttachments 表,返回 FileUploadResult" }, "/api/files/[id]": { "methods": [ "GET", "DELETE" ], "handler": "文件元数据查询/删除", "auth": "GET: requireAuth, DELETE: requirePermission(FILE_DELETE)", "module": "files", "description": "GET 返回文件元数据;DELETE 删除 DB 记录并 unlink 磁盘文件(静默失败)" }, "/api/files/batch-delete": { "methods": [ "POST" ], "handler": "批量删除文件", "auth": "requirePermission(FILE_DELETE)", "module": "files", "validation": "JSON body { ids: string[] },空数组返回 400", "description": "先通过 getFileAttachmentsByIds 查出文件记录,并行调用 storageProvider.delete 删除磁盘文件(静默失败),再调用 deleteFileAttachments 删除 DB 记录(失败时回退到逐条删除);响应 { success, message, deletedCount, failedIds }" }, "/api/search": { "methods": [ "GET" ], "handler": "全局全文检索", "auth": "requireAuth", "module": "shared.db (questions/textbooks/exams/announcements)", "validation": "query params: q (关键词), type=all|question|textbook|exam|announcement, page=1, pageSize=10 (上限 50)", "description": "并行查询 questions/textbooks/exams/announcements(公告仅 status=published),按 createdAt 降序排序后分页;question content 字段 CAST AS CHAR 模糊匹配;返回 { success, query, type, results: [{ id, title, snippet, type, href, createdAt }], total, page, pageSize }" }, "/api/export": { "methods": [ "POST" ], "handler": "Excel 导出(grades/users/attendance)", "auth": "requireAuth", "module": "shared.lib.excel + users/grades", "validation": "JSON body { type, params }", "description": "按 type 分发到 exportGradeRecordsToExcel/exportUsersToExcel,返回 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 二进制流" }, "/api/import": { "methods": [ "POST" ], "handler": "Excel 解析预览(不写 DB)", "auth": "requirePermission(USER_MANAGE)", "module": "shared.lib.excel", "validation": "multipart/form-data file,限 .xlsx/.xls,10MB 上限", "description": "接收 Excel 文件,调用 parseExcel 返回 sheets 预览数据(实际导入由 users/actions.importUsersAction 完成)" } }, "devops": { "ci": { "configFile": ".gitea/workflows/ci.yml", "triggers": [ "push to main", "pull_request to main", "schedule cron 0 2 * * *" ], "jobs": { "build-deploy": { "runsOn": "CDCD", "container": "dockerreg.eazygame.cn/node-with-docker:22", "trigger": "push/PR to main", "steps": [ "checkout", "cache npm", "configure npm proxy", "npm ci", "lint", "typecheck", "install playwright chromium", "integration tests", "e2e tests", "cache next.js build", "build", "prepare standalone", "deploy to docker" ] }, "security-scan": { "runsOn": "ubuntu-latest", "trigger": "push/PR to main", "needs": "build-deploy", "continueOnError": true, "steps": [ "checkout", "setup node 20", "npm ci", "npm audit --audit-level=moderate + 生成 audit-report.json (continue-on-error)", "Snyk scan --severity-threshold=high --sarif-file-output=snyk.sarif (env SNYK_TOKEN, continue-on-error)", "Trivy fs scan json+table (continue-on-error)", "OWASP ZAP baseline scan target=NEXTAUTH_URL||localhost:8015 cmd_options='-a -j' (continue-on-error)", "upload security-reports artifact (audit-report.json, trivy-fs-report.json, snyk.sarif)" ] }, "scheduled-backup": { "runsOn": "ubuntu-latest", "trigger": "schedule cron 0 2 * * *", "condition": "github.event_name == 'schedule'", "steps": [ "checkout", "run scripts/backup-db.sh (env DATABASE_URL, BACKUP_DIR)", "run scripts/backup-verify.sh (校验备份完整性)", "run scripts/backup-offsite-sync.sh (异地同步, env BACKUP_OFFSITE_*, 失败不阻塞)", "upload backups/ artifact (retention 30 days)" ] }, "backup-verify": { "runsOn": "ubuntu-latest", "trigger": "schedule", "condition": "github.event_name == 'schedule'", "needs": "scheduled-backup", "steps": [ "checkout", "download db-backup artifact", "run scripts/backup-verify.sh (独立校验)", "run scripts/health-check.sh > health-report.json", "upload backup-verify-report artifact (backups/, health-report.json, retention 7 days)" ] }, "weekly-dr-drill": { "runsOn": "ubuntu-latest", "trigger": "schedule (每周触发, github.run_attempt % 7 == 0)", "condition": "github.event_name == 'schedule' && github.run_attempt % 7 == 0", "needs": "backup-verify", "steps": [ "checkout", "run scripts/dr-drill.sh (env DATABASE_URL, DR_DRILL_TEST_DB=next_edu_dr_drill)", "upload dr-drill-report artifact (docs/dr/reports/, retention 90 days)" ] } } }, "drDrillWorkflow": { "configFile": ".gitea/workflows/dr-drill.yml", "triggers": [ "schedule cron 0 4 * * 1 (每周一凌晨 4 点)", "workflow_dispatch (inputs: backup_file, no_cleanup)" ], "job": "dr-drill", "runsOn": "ubuntu-latest", "timeoutMinutes": 30, "steps": [ "checkout", "install mysql-client", "prepare backup directory (mkdir backups docs/dr/reports)", "download db-backup artifact (continue-on-error) 或现场执行 backup-db.sh", "run scripts/dr-drill.sh (支持 --backup/--no-cleanup 参数)", "upload dr-drill-report-${{ github.run_id }} artifact (docs/dr/reports/, retention 90 days)", "on failure: webhook 通知运维团队 (DR_NOTIFICATION_WEBHOOK)" ] }, "securityWorkflow": { "configFile": ".gitea/workflows/security.yml", "triggers": [ "schedule cron 0 3 * * 1 (每周一凌晨 3 点)", "workflow_dispatch (inputs: target_url, skip_dast)" ], "job": "deep-security-scan", "runsOn": "ubuntu-latest", "continueOnError": true, "steps": [ "checkout", "setup node 20", "npm ci", "npm audit + 生成 audit-report.json (依赖扫描)", "Snyk scan --severity-threshold=medium --sarif-file-output=snyk.sarif (env SNYK_TOKEN, 深度依赖+静态分析)", "Trivy fs scan json+table (文件系统扫描, trivy-fs-report.json)", "Build Next.js standalone + docker build nextjs-app:scan + Trivy image scan (容器镜像扫描, trivy-image-report.json)", "OWASP ZAP baseline scan (DAST, target=inputs.target_url||NEXTAUTH_URL||localhost:8015, 可通过 skip_dast 跳过)", "Generate security-summary.md (jq 汇总各报告漏洞计数)", "upload security-reports-full artifact (audit-report.json, trivy-fs-report.json, trivy-image-report.json, snyk.sarif, security-summary.md)" ], "configFiles": { "suppressions": ".gitea/suppressions.json (Snyk 漏洞抑制, 每条含 id/package/severity/reason/expires/owner)", "trivyignore": ".trivyignore (Trivy CVE 忽略列表, 每行一个 CVE 带注释)" } }, "scripts": { "scripts/audit.sh": { "type": "bash", "purpose": "依赖安全审计,运行 npm audit --audit-level=moderate,失败时生成 audit-report.json", "platform": "Linux/macOS" }, "scripts/audit.ps1": { "type": "powershell", "purpose": "依赖安全审计(Windows 版本)", "platform": "Windows" }, "scripts/backup-db.sh": { "type": "bash", "purpose": "MySQL 数据库备份,从 DATABASE_URL 解析连接信息,gzip 压缩,保留 RETENTION_DAYS 天(默认 30)", "env": [ "DATABASE_URL", "BACKUP_DIR", "RETENTION_DAYS" ], "output": "${BACKUP_DIR}/db_backup_${TIMESTAMP}.sql.gz" }, "scripts/restore-db.sh": { "type": "bash", "purpose": "MySQL 数据库恢复,从指定备份文件恢复", "env": [ "DATABASE_URL" ], "usage": "./restore-db.sh " }, "scripts/test-backup.sh": { "type": "bash", "purpose": "备份流程测试,执行一次备份并验证最新备份文件" }, "scripts/backup-verify.sh": { "type": "bash", "purpose": "备份完整性校验:检查文件存在/大小/gzip 完整性/SQL 内容结构/SQL 语法(可选,需 DATABASE_URL)", "env": [ "BACKUP_DIR", "DATABASE_URL", "BACKUP_VERIFY_MIN_SIZE" ], "exitCodes": { "0": "校验通过", "1": "校验失败" }, "options": [ "--min-size BYTES", "--no-sql-check", "--help" ] }, "scripts/backup-offsite-sync.sh": { "type": "bash", "purpose": "异地备份同步:支持 S3/OSS/NFS 后端,同步后校验文件数量,清理远程过期备份(保留 90 天)", "env": [ "BACKUP_DIR", "BACKUP_OFFSITE_BACKEND", "BACKUP_OFFSITE_REMOTE", "BACKUP_OFFSITE_BUCKET", "BACKUP_OFFSITE_ACCESS_KEY", "BACKUP_OFFSITE_SECRET_KEY", "BACKUP_OFFSITE_REGION", "BACKUP_OFFSITE_RETENTION_DAYS" ], "tools": [ "aws-cli (s3)", "rclone (s3/oss)", "ossutil (oss)", "rsync (nfs)" ], "exitCodes": { "0": "同步成功", "1": "同步失败" }, "options": [ "--backend TYPE", "--no-cleanup", "--no-verify", "--help" ] }, "scripts/dr-drill.sh": { "type": "bash", "purpose": "灾备演练:创建测试库→从备份恢复→数据完整性检查→冒烟测试→清理→生成报告到 docs/dr/reports/", "env": [ "DATABASE_URL", "BACKUP_DIR", "DR_DRILL_TEST_DB", "DR_DRILL_REPORT_DIR" ], "exitCodes": { "0": "演练成功", "1": "演练失败" }, "options": [ "--backup FILE", "--test-db NAME", "--no-cleanup", "--report-dir DIR", "--help" ] }, "scripts/dr-drill.ps1": { "type": "powershell", "purpose": "灾备演练(Windows PowerShell 5.1+ 版本),功能同 Bash 版本", "env": [ "DATABASE_URL", "BACKUP_DIR", "DR_DRILL_TEST_DB", "DR_DRILL_REPORT_DIR" ], "platform": "Windows", "options": [ "-BackupFile FILE", "-TestDb NAME", "-NoCleanup", "-ReportDir DIR", "-Help" ] }, "scripts/failover.sh": { "type": "bash", "purpose": "故障切换:检测主库健康→提升备库→更新应用配置→重启应用→验证切换", "env": [ "DATABASE_URL", "DATABASE_URL_STANDBY", "FAILOVER_APP_URL", "FAILOVER_APP_NAME", "FAILOVER_CONFIG_FILE", "FAILOVER_LOG_FILE" ], "exitCodes": { "0": "切换成功", "1": "切换失败" }, "options": [ "--auto", "--primary URL", "--standby URL", "--app-url URL", "--no-restart", "--dry-run", "--help" ] }, "scripts/health-check.sh": { "type": "bash", "purpose": "健康检查:检查应用 HTTP/数据库连接/磁盘空间/备份新鲜度,输出 JSON 报告", "env": [ "DATABASE_URL", "HEALTH_CHECK_URL", "BACKUP_DIR", "HEALTH_CHECK_DISK_THRESHOLD", "HEALTH_CHECK_BACKUP_MAX_AGE" ], "exitCodes": { "0": "健康", "1": "异常" }, "options": [ "--app-url URL", "--no-app", "--no-db", "--no-disk", "--no-backup", "--disk-threshold PCT", "--backup-max-age HRS", "--help" ] } }, "packageJsonScripts": { "audit": "npm audit --audit-level=moderate", "audit:report": "npm audit --json > audit-report.json", "security:audit": "npm audit --audit-level=moderate", "security:scan": "bash scripts/security-scan.sh", "backup": "bash scripts/backup-db.sh", "restore": "bash scripts/restore-db.sh", "dr:backup-verify": "bash scripts/backup-verify.sh", "dr:offsite-sync": "bash scripts/backup-offsite-sync.sh", "dr:drill": "bash scripts/dr-drill.sh", "dr:drill:ps1": "powershell -ExecutionPolicy Bypass -File scripts/dr-drill.ps1", "dr:health-check": "bash scripts/health-check.sh", "dr:failover": "bash scripts/failover.sh" }, "drDocs": { "docs/dr/dr-plan.md": "灾备计划文档:RTO/RPO 定义(4h/24h)、备份策略、故障切换流程、联系人列表、恢复步骤", "docs/dr/dr-runbook.md": "灾备操作手册:数据库故障/应用故障/备份失败/异地同步失败/演练失败/磁盘不足场景的诊断与处理", "docs/dr/reports/": "灾备演练报告存档目录(Markdown 格式,由 dr-drill.sh 生成)", "docs/dr/logs/": "故障切换日志目录(由 failover.sh 生成)" }, "drEnvVars": { "BACKUP_OFFSITE_BACKEND": "异地备份后端类型: s3|oss|nfs|none", "BACKUP_OFFSITE_REMOTE": "远程存储路径", "BACKUP_OFFSITE_BUCKET": "存储桶名称(仅 s3/oss)", "BACKUP_OFFSITE_ACCESS_KEY": "访问密钥", "BACKUP_OFFSITE_SECRET_KEY": "秘密密钥", "BACKUP_OFFSITE_REGION": "区域(默认 us-east-1)", "BACKUP_OFFSITE_RETENTION_DAYS": "远程备份保留天数(默认 90)", "DR_DRILL_TEST_DB": "演练测试数据库名(默认 next_edu_dr_drill)", "HEALTH_CHECK_URL": "应用健康检查 URL(默认 http://localhost:8015)", "DATABASE_URL_STANDBY": "备库连接 URL(故障切换时使用)", "FAILOVER_APP_NAME": "应用容器名(默认 nextjs-app)" }, "gitignore": { "added": [ "/backups/", "/audit-report.json", "/trivy-fs-report.json", "/trivy-image-report.json", "/snyk.sarif", "/security-summary.md", "/playwright-report/", "/test-results/", "/tests/visual/.auth/" ], "exceptions": [ ".env.example (灾备环境变量示例,允许提交)" ] } }, "testing": { "e2e": { "configFile": "playwright.config.ts", "testDir": "./tests", "baseURL": "http://127.0.0.1:3000", "webServer": { "command": "npm run dev", "port": 3000, "timeout": 180000, "reuseExistingServer": "!process.env.CI", "env": { "SKIP_ENV_VALIDATION": "1", "NEXTAUTH_SECRET": "test-nextauth-secret", "NEXTAUTH_URL": "http://127.0.0.1:3000", "DATABASE_URL": "mysql://test:test@127.0.0.1:3306/test_db" } }, "projects": [ { "name": "chromium", "testDir": "./tests/e2e", "channel": "CI: undefined, local: chrome" }, { "name": "visual-chromium", "testDir": "./tests/visual", "channel": "CI: undefined, local: chrome" } ], "snapshotPathTemplate": "{testDir}/__screenshots__/{testFilePath}/{arg}{ext}", "expect": { "toHaveScreenshot": { "maxDiffPixelRatio": 0.01, "animations": "disabled", "caret": "hide" }, "toMatchSnapshot": { "maxDiffPixelRatio": 0.01 } }, "retries": "CI: 2, local: 0", "workers": "CI: 2, local: default", "testFiles": { "smoke-auth.spec.ts": { "coverage": "登录/注册页面控件渲染冒烟测试", "requiresDb": false }, "auth-business-flow.spec.ts": { "coverage": "注册→登录→访问受保护区域完整流程", "requiresDb": true }, "full-route-regression.spec.ts": { "coverage": "全路由清单完整性 + 公开/受保护路由守卫", "requiresDb": false }, "auth.spec.ts": { "coverage": "认证页面(登录/注册/隐私/协议)渲染 + 未认证重定向", "requiresDb": false }, "navigation.spec.ts": { "coverage": "admin/teacher/student 导航链接无 404", "requiresDb": true, "envVars": [ "E2E_ADMIN_EMAIL", "E2E_TEACHER_EMAIL", "E2E_STUDENT_EMAIL" ] }, "announcements.spec.ts": { "coverage": "公告页面未认证重定向 + 登录后渲染", "requiresDb": "partial" }, "grades.spec.ts": { "coverage": "成绩页面未认证重定向 + 登录后渲染", "requiresDb": "partial" } } }, "visual": { "configFile": "tests/visual/visual.config.ts", "snapshotDir": "tests/visual/__screenshots__", "storageStateDir": "tests/visual/.auth/", "viewports": { "desktop": { "width": 1920, "height": 1080 }, "tablet": { "width": 768, "height": 1024 }, "mobile": { "width": 375, "height": 812 } }, "themes": [ "light", "dark" ], "defaultMaxDiffPixelRatio": 0.01, "testAccounts": { "admin": { "email": "admin@xiaoxue.edu.cn", "envVars": [ "VISUAL_ADMIN_EMAIL", "VISUAL_ADMIN_PASSWORD" ] }, "teacher": { "email": "admin@xiaoxue.edu.cn", "envVars": [ "VISUAL_TEACHER_EMAIL", "VISUAL_TEACHER_PASSWORD" ] }, "student": { "email": "admin@xiaoxue.edu.cn", "envVars": [ "VISUAL_STUDENT_EMAIL", "VISUAL_STUDENT_PASSWORD" ] } }, "testFiles": { "homepage.spec.ts": { "coverage": "登录页在 desktop/tablet/mobile × light/dark 下的快照", "requiresDb": false }, "admin-dashboard.spec.ts": { "coverage": "管理员仪表盘整页 + 侧边栏/主内容区组件快照", "requiresDb": true, "role": "admin" }, "teacher-dashboard.spec.ts": { "coverage": "教师仪表盘整页 + 侧边栏/主内容区组件快照", "requiresDb": true, "role": "teacher" }, "student-dashboard.spec.ts": { "coverage": "学生仪表盘整页 + 侧边栏/主内容区组件快照", "requiresDb": true, "role": "student" } }, "helpers": { "auth.ts": [ "setupAuthState(role)", "loginByUI(page, role)", "storageStatePath(role)" ], "visual-helpers.ts": [ "setViewport(page, size)", "setTheme(page, theme)", "waitForPageReady(page)", "maskDynamicElements(page, selectors)", "buildMaskOption(masks)" ] }, "scripts": { "test:visual": "playwright test --project=visual-chromium", "test:visual:update": "playwright test --project=visual-chromium --update-snapshots" } } } }