# Next_Edu 架构影响地图 > 全模块·全函数·全参数级别 > 生成日期:2026-06-16 > 规则:每次文件修改后须同步更新本文档 --- ## 模块:shared ### 模块职责 提供全项目共享的基础设施:数据库连接、Schema、工具函数、权限系统、UI 基础组件、通用 Hooks。 ### 导出函数 #### `cn` - 签名:`cn(...inputs: ClassValue[]): string` - 参数说明: - `inputs`: ClassValue[],来自 `clsx` + `tailwind-merge`,CSS 类名列表 - 功能:合并 CSS 类名并解决 Tailwind 冲突 - 依赖:`clsx`, `tailwind-merge` - 被以下模块使用:**所有模块**的组件(50+ 文件) #### `formatDate` - 签名:`formatDate(date: string | Date, locale?: string): string` - 参数说明: - `date`: string | Date,日期值 - `locale`: string,默认 `"zh-CN"`,`Intl.DateTimeFormat` 的 locale - 功能:国际化日期格式化 - 依赖:无 - 被以下模块使用:exams, homework, dashboard, textbooks #### `parseAiChatPayload` - 签名:`parseAiChatPayload(body: unknown): AiChatRequest` - 参数说明:`body`: unknown,HTTP 请求体 - 功能:解析并校验 AI 聊天请求负载 - 依赖:Zod schema - 被以下模块使用:`app/api/ai/chat/route.ts` #### `encryptAiApiKey` / `decryptAiApiKey` - 签名:`(value: string) => string` - 功能:AES 加密/解密 AI Provider API Key - 依赖:`crypto` (Node.js 内置) - 被以下模块使用:settings (存储/读取 API Key) #### `testAiProviderConfig` / `testAiProviderById` - 签名:`(input: { apiKey, baseUrl?, model }) => Promise` / `(providerId, overrides?) => Promise` - 功能:测试 AI Provider 连通性 - 依赖:`createAiChatCompletion`, `shared/db` - 被以下模块使用:settings/actions.ts #### `createAiChatCompletion` - 签名:`(input: AiChatRequest) => Promise<{ content, usage }>` - 功能:调用 AI 模型生成聊天回复 - 依赖:OpenAI SDK, `shared/db` (读取 provider 配置) - 被以下模块使用:exams/ai-pipeline.ts, `app/api/ai/chat/route.ts` #### `getAuthContext` - 签名:`getAuthContext(): Promise` - 功能:获取当前用户的完整认证上下文(userId, roles, permissions, dataScope) - 依赖:`auth` (NextAuth), `shared/db` (查询角色/班级/年级关系) - 被以下模块使用:**所有业务模块**的 Server Actions #### `requirePermission` - 签名:`requirePermission(permission: Permission): Promise` - 参数说明:`permission`: Permission,来自 `shared/types/permissions` 的权限常量 - 功能:断言当前用户拥有指定权限,否则抛出 PermissionDeniedError - 依赖:`getAuthContext` - 被以下模块使用:**所有业务模块**的 Server Actions(exams, homework, questions, textbooks, classes, school, settings, audit, announcements, files, grades, attendance) #### `checkPermission` - 签名:`checkPermission(permission: Permission): Promise<{ allowed: boolean; ctx: AuthContext }>` - 功能:非抛出版权限检查 - 依赖:`getAuthContext` - 被以下模块使用:待扩展 #### `requireAuth` - 签名:`requireAuth(): Promise` - 功能:仅断言用户已登录 - 依赖:`getAuthContext` - 被以下模块使用:待扩展 #### `resolvePermissions` - 签名:`resolvePermissions(roleNames: string[]): Permission[]` - 参数说明:`roleNames`: string[],用户的角色名称列表 - 功能:合并多角色的权限列表(去重) - 依赖:`ROLE_PERMISSIONS` 常量 - 被以下模块使用:auth.ts (JWT callback) #### `logAudit` - 签名:`logAudit(params: LogAuditParams): Promise` - 参数说明:`params`: LogAuditParams,含 userId, userName, action, module, targetId?, targetType?, detail?, status? - 功能:记录操作日志(静默失败,不影响主流程) - 依赖:`auth` (NextAuth), `shared/db` (auditLogs 表), `next/headers` - 被以下模块使用:school/actions.ts,其他 Server Actions #### `logLoginEvent` - 签名:`logLoginEvent(params: LogLoginEventParams): Promise` - 参数说明:`params`: LogLoginEventParams,含 userId?, userEmail, action (signin/signout/signup), status, errorMessage? - 功能:记录登录日志(不依赖 auth 上下文,可在 NextAuth events 中调用,静默失败) - 依赖:`shared/db` (loginLogs 表), `next/headers` - 被以下模块使用:auth.ts (events.signIn, events.signOut) #### `isAllowedMimeType` - 签名:`isAllowedMimeType(mimeType: string): boolean` - 功能:判断 MIME 类型是否在允许上传的白名单中 - 依赖:`ALLOWED_MIME_TYPES` 常量 - 被以下模块使用:`app/api/upload/route.ts`, files/components/file-upload.tsx #### `generateStoragePath` - 签名:`generateStoragePath(originalName: string): string` - 功能:根据原始文件名生成存储路径 `uploads/YYYY-MM/cuid.ext`(相对于 public/) - 依赖:`@paralleldrive/cuid2` - 被以下模块使用:`app/api/upload/route.ts` #### `getFileExtension` - 签名:`getFileExtension(filename: string): string` - 功能:从文件名中提取小写扩展名(不含点) - 被以下模块使用:`shared/lib/file-storage` 内部 #### `formatFileSize` - 签名:`formatFileSize(bytes: number): string` - 功能:将字节数格式化为人类可读字符串(如 `1.5 MB`、`800 KB`) - 被以下模块使用:files/components/file-upload.tsx, file-list.tsx, file-preview.tsx #### `exportToExcel` - 签名:`exportToExcel(params: { sheets: ExcelSheet[] }): Promise` - 参数说明:`sheets`: ExcelSheet[],每个含 `name`/`columns`/`rows` - 功能:将多 sheet 数据导出为 Excel Buffer(表头加粗+冻结首行+自动筛选) - 依赖:`exceljs` - 被以下模块使用:users/import-export.exportUsersToExcel, grades/export.exportGradeRecordsToExcel, grades/export.exportClassGradeReportToExcel #### `parseExcel` - 签名:`parseExcel(buffer: Buffer): Promise` - 返回:ParsedSheet[],每个含 `sheetName`/`rows` - 功能:从 Buffer 解析 Excel 文件,首行作为表头,返回每 sheet 的行记录数组 - 依赖:`exceljs` - 被以下模块使用:users/actions.importUsersAction, `app/api/import/route.ts` #### `generateTemplate` - 签名:`(params: { sheets: TemplateSheet[] }): Promise` - 参数说明:`sheets`: TemplateSheet[],每个含 `name`/`columns`(含 `note`)/`sampleRows?` - 功能:生成导入模板 Buffer(表头加粗+第二行填写说明+示例行) - 依赖:`exceljs` - 被以下模块使用:users/import-export.generateUserImportTemplate #### `validatePassword` - 签名:`(password: string) => { valid: boolean; errors: string[] }` - 功能:校验密码是否符合策略(最小长度、大小写、数字等) - 依赖:`PASSWORD_RULES` 常量 - 被以下模块使用:settings/actions-password.changePasswordAction, auth.ts #### `getPasswordStrength` - 签名:`(password: string) => "weak" | "medium" | "strong"` - 功能:基于长度和字符多样性计算密码强度等级(纯函数,可在客户端使用) - 依赖:无 - 被以下模块使用:settings/components/password-change-form.tsx #### `isAccountLocked` - 签名:`(failedAttempts: number, lastFailedAt: Date | null) => boolean` - 功能:判断账户是否应被锁定(失败次数达阈值且锁定时间未过期) - 依赖:`PASSWORD_RULES.maxLoginAttempts`, `PASSWORD_RULES.lockoutDurationMinutes` - 被以下模块使用:auth.ts (authorize callback) #### `getRemainingLockoutMs` - 签名:`(failedAttempts: number, lastFailedAt: Date | null) => number` - 功能:计算剩余锁定时间(毫秒,0 表示已解锁) - 依赖:`isAccountLocked` - 被以下模块使用:待扩展 #### `rateLimit` - 签名:`(params: { key: string; limit: number; windowMs: number }) => { success: boolean; remaining: number; resetTime: number; retryAfterMs: number }` - 功能:内存滑动窗口限流检查(单实例,多实例需替换为 @upstash/ratelimit) - 依赖:无(Map 存储) - 被以下模块使用:auth.ts (LOGIN), settings/actions-password (PASSWORD_CHANGE), app/api/ai/chat (AI_CHAT), app/api/upload (UPLOAD), app/api/rate-limit-test #### `resetRateLimit` - 签名:`(key: string) => void` - 功能:重置指定 key 的限流计数(如登录成功后清除失败计数) - 被以下模块使用:auth.ts (成功登录后) #### `rateLimitKey` - 签名:`(prefix: string, identifier: string) => string` - 功能:构建限流 key(如 `login:ip:email`) - 被以下模块使用:auth.ts, settings/actions-password, app/api/ai/chat, app/api/upload #### `rateLimitHeaders` - 签名:`(result: RateLimitResult) => Record` - 功能:将限流结果转为 HTTP 响应头(X-RateLimit-Limit/Remaining/Reset, Retry-After) - 被以下模块使用:app/api/ai/chat, app/api/upload, app/api/rate-limit-test #### `logDataChange` - 签名:`logDataChange(params: LogDataChangeParams): Promise` - 参数说明:`params`: LogDataChangeParams,含 tableName, recordId, action ("create" | "update" | "delete"), oldValue?, newValue? - 功能:记录数据变更日志(写入 dataChangeLogs 表,自动从 NextAuth session 获取 changedBy/changedByName,从 headers 获取 ipAddress;静默失败,不阻塞主流程) - 依赖:`auth` (NextAuth), `shared/db` (dataChangeLogs 表), `next/headers`, `@paralleldrive/cuid2` - 被以下模块使用:待扩展(数据变更场景) #### `StorageProvider` (接口) - 文件:`lib/storage-provider.ts` - 定义:文件存储抽象接口,方法 `save(file, storagePath) => Promise`、`read(storagePath) => Promise`、`delete(storagePath) => Promise`、`exists(storagePath) => Promise`、`getUrl(storagePath) => string` - 功能:抽象文件持久化层,便于未来切换到 OSS/S3 而不修改调用方 - 被以下模块使用:`app/api/files/batch-delete/route.ts` #### `LocalStorageProvider` (类) - 文件:`lib/storage-provider.ts` - 实现:`StorageProvider` 接口,文件持久化到 `public/uploads/...`,URL 为 `/uploads/...` - 依赖:`fs/promises` (mkdir/readFile/writeFile/unlink/access), `path` - 被以下模块使用:通过 `storageProvider` 实例使用 #### `storageProvider` (实例) - 文件:`lib/storage-provider.ts` - 类型:`StorageProvider`(默认为 `LocalStorageProvider` 实例) - 功能:默认存储 Provider 单例,替换此实例可迁移到 OSS/S3 - 被以下模块使用:`app/api/files/batch-delete/route.ts` #### `useA11yId` - 签名:`useA11yId(prefix: string): string` - 功能:基于 `React.useId` 生成 SSR 安全的唯一 ID,用于 `aria-describedby`、`aria-labelledby` 等 - 依赖:`react` - 被以下模块使用:待扩展(表单组件、a11y 组件) #### `mergeA11yProps` - 签名:`mergeA11yProps>(...props: (T | undefined | null | false)[]): T` - 功能:合并多组 aria/data 属性,普通属性后者覆盖前者,`aria-*`/`data-*` 字符串属性以空格拼接 - 依赖:无 - 被以下模块使用:待扩展 #### `describeInput` - 签名:`describeInput(describedBy?: string, error?: string, hint?: string): { ariaDescribedBy?: string; ariaInvalid?: boolean }` - 功能:计算输入框的 `aria-describedby`(合并多个 ID)与 `aria-invalid`(error 存在则为 true) - 依赖:无 - 被以下模块使用:待扩展(表单组件) #### `loadingAria` - 签名:`loadingAria(isLoading: boolean): { ariaBusy: boolean; ariaLive: "polite" | "assertive" }` - 功能:提供加载状态的 `aria-busy` 与 `aria-live=polite` 属性 - 依赖:无 - 被以下模块使用:待扩展 ### 导出常量与实例 #### `Permissions` (常量对象) - 文件:`types/permissions.ts` - 定义:54 个权限常量(`exam:create`, `homework:grade`, `audit_log:read`, `announcement:manage`, `file:upload`, `file:read`, `file:delete`, `grade_record:manage`, `grade_record:read`, `course_plan:manage`, `course_plan:read`, `attendance:manage`, `attendance:read`, `message:send`, `message:read`, `message:delete`, `schedule:auto`, `schedule:adjust`, `elective:manage`, `elective:read`, `elective:select`, `exam:proctor`, `exam:proctor_read`, `diagnostic:manage`, `diagnostic:read` 等) - 被使用:auth-guard.ts, 所有模块的 actions.ts, 前端组件 #### `ROLE_PERMISSIONS` (常量对象) - 文件:`lib/permissions.ts` - 定义:角色到权限列表的映射(admin/teacher/student/parent/grade_head/teaching_head) - 课程计划权限:admin 含 `COURSE_PLAN_MANAGE`+`COURSE_PLAN_READ`;teacher/student/grade_head/teaching_head 含 `COURSE_PLAN_READ` - 考勤权限:admin/teacher 含 `ATTENDANCE_MANAGE`+`ATTENDANCE_READ`;student/parent/grade_head/teaching_head 含 `ATTENDANCE_READ` - 消息权限:admin/teacher/grade_head/teaching_head 含 `MESSAGE_SEND`+`MESSAGE_READ`+`MESSAGE_DELETE`;student/parent 含 `MESSAGE_SEND`+`MESSAGE_READ` - 排课权限:admin 含 `SCHEDULE_AUTO`+`SCHEDULE_ADJUST`;teacher/student/parent/grade_head/teaching_head 无排课权限 - 学情诊断权限:admin/teacher/grade_head 含 `DIAGNOSTIC_MANAGE`+`DIAGNOSTIC_READ`;teaching_head/student 含 `DIAGNOSTIC_READ` - 被使用:`resolvePermissions`, auth.ts (JWT callback) #### `db` (Drizzle 实例) - 文件:`db/index.ts` - 定义:Drizzle ORM 客户端实例(MySQL) - 被使用:**所有业务模块**的 data-access.ts 和 actions.ts #### `questionTypeEnum` - 文件:`db/schema.ts` (或 schema 枚举导出) - 定义:题目类型枚举(选择/填空/判断/复合等) - 被使用:questions, exams, homework #### `classEnrollmentStatusEnum` - 文件:`db/schema.ts` (或 schema 枚举导出) - 定义:班级注册状态枚举(active/inactive 等) - 被使用:classes, homework #### `PASSWORD_RULES` (常量对象) - 文件:`lib/password-policy.ts` - 定义:密码策略配置(minLength: 8, requireUppercase/Lowercase/Number: true, maxLoginAttempts: 5, lockoutDurationMinutes: 30) - 被使用:auth.ts, settings/actions-password.ts, password-change-form.tsx (via PASSWORD_REQUIREMENT_HINTS) #### `RATE_LIMIT_RULES` (常量对象) - 文件:`lib/rate-limit.ts` - 定义:预定义限流规则(LOGIN: 5/15min, API: 100/min, UPLOAD: 10/min, AI_CHAT: 20/min, PASSWORD_CHANGE: 5/min) - 被使用:auth.ts (LOGIN), settings/actions-password (PASSWORD_CHANGE), app/api/ai/chat (AI_CHAT), app/api/upload (UPLOAD) ### 文件记录 #### `db/relations.ts` - 内容:24+ Drizzle relations(表间关系定义,含 coursePlansRelations、coursePlanItemsRelations、attendanceRecordsRelations、attendanceRulesRelations) - 被使用:Drizzle ORM 关联查询(所有 data-access.ts) #### `next-auth.d.ts` - 内容:NextAuth 类型扩展(Session.user 增加 id/roles/permissions 等字段) - 被使用:auth.ts, 所有使用 `useSession`/`auth()` 的代码 ### 导出组件 #### `AuthSessionProvider` - Props: `{ children: React.ReactNode }` - 内部使用:`next-auth/react` 的 `SessionProvider` - 被使用:`app/layout.tsx` #### `OnboardingGate` - Props: 无(内部使用 `useSession`) - 内部使用:`useSession`, `Permissions`, 多个 Server Action - 功能:新用户引导流程(角色选择、学校/班级配置) - 被使用:`app/layout.tsx` #### `ThemeProvider` - Props: `next-themes` 的 `ThemeProviderProps` - 被使用:`app/layout.tsx` #### `EmptyState` - 文件:`components/ui/empty-state.tsx` - Props: `{ icon?, title, description, action? }` - 被使用:exams, homework, questions, textbooks 等模块的列表空状态 #### `GlobalSearch` - 文件:`components/global-search.tsx` - Props: `{ className?, placeholder? }` - 功能:全局搜索组件(防抖 300ms 调用 `GET /api/search`,Cmd/Ctrl+K 快捷键聚焦,Escape 关闭,↑/↓ 键盘导航,Enter 跳转;下拉展示 question/textbook/exam/announcement 四类结果,点击外部自动关闭) - 内部使用:`useDebounce`, `Input`, `Link`, `useRouter` - 被使用:`layout/components/site-header.tsx` #### `Switch` - 文件:`components/ui/switch.tsx` - 基于:`@radix-ui/react-switch` - Props: Radix Switch Root props(含 `checked`, `onCheckedChange`, `disabled`, `id`, `aria-label` 等) - 功能:开关切换 UI 组件(shadcn 风格,checked/unchecked 两态) - 被使用:settings/components/notification-preferences-form.tsx #### `SkipLink` - 文件:`components/a11y/skip-link.tsx` - Props: `{ href?, children?, ...AnchorHTMLAttributes }`,默认 href=`#main-content`,默认文字"跳转到主内容" - 功能:跳转链接组件,视觉隐藏,获得焦点时高对比度显示,供键盘用户跳过导航直达主内容 - 被使用:待替换 `app/(dashboard)/layout.tsx` 中的内联 skip-link #### `VisuallyHidden` - 文件:`components/a11y/visually-hidden.tsx` - Props: `{ children?, ...HTMLAttributes }` - 功能:视觉隐藏但屏幕阅读器可读,用于图标按钮文字描述、表单辅助说明 - 被使用:待扩展 #### `FocusTrap` - 文件:`components/a11y/focus-trap.tsx` - Props: `{ children, active?, initialFocusRef?, restoreFocus?, className? }` - 功能:焦点陷阱组件,捕获 Tab/Shift+Tab 在容器内循环,支持初始焦点与焦点恢复,用于模态框/对话框 - 被使用:待扩展(Dialog/Sheet 自定义场景) #### `AriaStatus` - 文件:`components/a11y/aria-status.tsx` - Props: `{ children?, politeness?, atomic?, ...HTMLAttributes }`,默认 politeness=`polite`,atomic=`true` - 功能:ARIA 状态通知区域,渲染 `aria-live` 区域(role=status),用于页面级状态通知(如"加载中"、"已保存") - 被使用:待扩展 ### 导出 Hooks #### `useActionWithToast` - 签名:`useActionWithToast(): { isPending, execute }` - 功能:包装 Server Action + toast 反馈 - 被使用:待扩展 #### `useDebounce` - 签名:`useDebounce(value: T, delay?: number): T` - 被使用:搜索输入框等 #### `useMediaQuery` - 签名:`useMediaQuery(query: string): boolean` - 被使用:响应式布局判断 #### `useLocalStorage` - 签名:`useLocalStorage(key: string, initialValue: T): [T, setter]` - 被使用:exam-form (previewTaskStorageKey) #### `usePermission` - 签名:`usePermission(): { permissions, roles, hasPermission, hasAnyPermission, hasAllPermissions, hasRole }` - 功能:客户端权限检查 Hook - 被使用:layout/app-sidebar.tsx, exams/components, homework/components ### 类型/接口 #### `ActionState` - 定义:`{ success: boolean; message?: string; errors?: Record; data?: T }` - 被使用:**所有模块**的 Server Action 返回类型 #### `Permission` (类型) - 定义:`Permissions` 值的联合类型 - 被使用:auth-guard.ts, use-permission.ts #### `DataScope` (联合类型) - 定义:`{ type: "all" } | { type: "owned"; userId: string } | { type: "class_taught"; classIds: string[]; subjectIds?: string[] } | { type: "grade_managed"; gradeIds: string[] } | { type: "class_members" } | { type: "children"; childrenIds: string[] }` - 被使用:auth-guard.ts, exams/data-access.ts, homework/data-access.ts, dashboard/data-access.ts #### `AuthContext` (接口) - 定义:`{ userId: string; roles: string[]; permissions: Permission[]; dataScope: DataScope }` - 被使用:auth-guard.ts, 所有调用 `requirePermission` 的 Server Action #### `PermissionDeniedError` (类) - 被使用:所有 Server Action 的 try/catch ### 数据库表 (shared/db/schema.ts) | 表名 | 核心字段 | 被哪些模块使用 | |------|---------|--------------| | `users` | id, name, email, emailVerified, image, password, phone, address, gender, age, birthDate, guardianName, guardianPhone, guardianRelation, consentAcceptedAt, gradeId, departmentId, onboardedAt, createdAt, updatedAt | auth, users, dashboard, classes | | `accounts` | userId, type, provider, providerAccountId, refresh_token, access_token, expires_at, token_type, scope, id_token, session_state | auth | | `sessions` | sessionToken, userId, expires | auth | | `verificationTokens` | identifier, token, expires | auth | | `roles` | id, name, description, createdAt, updatedAt | auth, auth-guard | | `usersToRoles` | userId, roleId | auth, auth-guard | | `rolePermissions` | roleId, permission | auth (seed) | | `knowledgePoints` | id, name, description, anchorText, parentId, chapterId, level, order, createdAt, updatedAt | textbooks, questions | | `questions` | id, content, type, difficulty, authorId, parentId, createdAt, updatedAt | questions, exams, homework | | `questionsToKnowledgePoints` | questionId, knowledgePointId | questions | | `subjects` | id, name, order, code, createdAt, updatedAt | exams, textbooks | | `textbooks` | id, title, subject, grade, publisher, createdAt, updatedAt | textbooks | | `chapters` | id, textbookId, title, order, parentId, content, createdAt, updatedAt | textbooks | | `departments` | id, name, description, createdAt, updatedAt | school | | `classrooms` | id, name, building, floor, capacity, createdAt, updatedAt | school | | `academicYears` | id, name, startDate, endDate, isActive, createdAt, updatedAt | school | | `schools` | id, name, code, createdAt, updatedAt | school, classes | | `grades` | id, schoolId, name, order, gradeHeadId, teachingHeadId, createdAt, updatedAt | school, classes, exams, auth-guard | | `classes` | id, schoolId, gradeId, teacherId, name, homeroom, room, invitationCode, schoolName, grade, createdAt, updatedAt | classes, homework, auth-guard | | `classSubjectTeachers` | classId, teacherId, subjectId, createdAt, updatedAt | classes, auth-guard | | `classEnrollments` | classId, studentId, status, createdAt | classes, homework | | `classSchedule` | id, classId, weekday, startTime, endTime, course, location, createdAt, updatedAt | classes | | `exams` | id, creatorId, title, description, subjectId, gradeId, status, structure, startTime, endTime, createdAt, updatedAt | exams, homework | | `examQuestions` | examId, questionId, score, order | exams | | `examSubmissions` | id, examId, studentId, score, status, submittedAt, createdAt, updatedAt | exams | | `submissionAnswers` | id, submissionId, questionId, answerContent, score, feedback, createdAt, updatedAt | exams | | `homeworkAssignments` | id, creatorId, sourceExamId, title, description, status, structure, availableAt, dueAt, allowLate, lateDueAt, maxAttempts, createdAt, updatedAt | homework | | `homeworkAssignmentQuestions` | assignmentId, questionId, score, order | homework | | `homeworkAssignmentTargets` | assignmentId, studentId, createdAt | homework | | `homeworkSubmissions` | id, assignmentId, studentId, status, attemptNo, score, submittedAt, startedAt, isLate, createdAt, updatedAt | homework | | `homeworkAnswers` | id, submissionId, questionId, answerContent, score, feedback, createdAt, updatedAt | homework | | `aiProviders` | id, provider, baseUrl, model, apiKeyEncrypted, apiKeyLast4, isDefault, createdBy, updatedBy, createdAt, updatedAt | settings, ai | | `announcements` | id, title, content, type, status, targetGradeId, targetClassId, authorId, publishedAt, createdAt, updatedAt | announcements | | `auditLogs` | id, userId, userName, action, module, targetId, targetType, detail, ipAddress, userAgent, status, createdAt | audit, shared/lib/audit-logger | | `loginLogs` | id, userId, userEmail, action, status, ipAddress, userAgent, errorMessage, createdAt | audit, shared/lib/login-logger, auth | | `dataChangeLogs` | id, tableName, recordId, action (create/update/delete), oldValue, newValue, changedBy, changedByName, ipAddress, createdAt | audit, shared/lib/change-logger | | `fileAttachments` | id, filename, originalName, mimeType, size, storagePath, url, uploaderId, targetType, targetId, createdAt | files | | `gradeRecords` | id, studentId, classId, subjectId, examId, academicYearId, title, score, fullScore, type, semester, recordedBy, remark, createdAt, updatedAt | grades | | `coursePlans` | id, classId, subjectId, teacherId, academicYearId, semester, totalHours, completedHours, weeklyHours, startDate, endDate, syllabus, objectives, status, createdBy, createdAt, updatedAt | course-plans | | `coursePlanItems` | id, planId, week, topic, content, hours, textbookChapter, notes, isCompleted, completedAt, createdAt, updatedAt | course-plans | | `parentStudentRelations` | id, parentId, studentId, relation, createdAt | parent, auth-guard | | `messages` | id, senderId, receiverId, subject, content, isRead, readAt, parentMessageId, createdAt | messaging | | `messageNotifications` | id, userId, type, title, content, link, isRead, createdAt | messaging | | `notificationPreferences` | id, userId (unique FK→users), emailEnabled, smsEnabled, pushEnabled, homeworkNotifications, gradeNotifications, announcementNotifications, messageNotifications, attendanceNotifications, createdAt, updatedAt | messaging, settings | | `attendanceRecords` | id, studentId, classId, scheduleId, date, status, remark, recordedBy, createdAt, updatedAt | attendance | | `attendanceRules` | id, classId, lateThresholdMinutes, earlyLeaveThresholdMinutes, enableAutoMark, createdAt, updatedAt | attendance | | `schedulingRules` | id, classId, maxDailyHours, maxContinuousHours, lunchBreakStart, lunchBreakEnd, morningStart, afternoonEnd, avoidBackToBack, balancedSubjects, createdAt, updatedAt | scheduling | | `scheduleChanges` | id, originalScheduleId, classId, originalTeacherId, substituteTeacherId, originalDate, newDate, newStartTime, newEndTime, reason, status, requestedBy, approvedBy, createdAt, updatedAt | scheduling | | `passwordSecurity` | id, userId, failedLoginAttempts, lockedUntil, passwordChangedAt, mustChangePassword, lastPasswordChange, createdAt, updatedAt | auth, settings | | `knowledgePointMastery` | id, studentId, knowledgePointId, masteryLevel, totalQuestions, correctQuestions, lastAssessedAt, createdAt, updatedAt | diagnostic | | `learningDiagnosticReports` | id, studentId, generatedBy, reportType, period, summary, strengths, weaknesses, recommendations, overallScore, status, createdAt, updatedAt | diagnostic | --- ## 模块:auth ### 模块职责 处理用户认证(登录/注册/JWT/Session),提供 NextAuth 实例和中间件。通过 events 回调记录登录日志。 集成密码安全策略(账户锁定、失败登录追踪)和登录速率限制。 ### 导出函数 #### `auth` - 签名:`auth(): Promise` (NextAuth 导出) - 功能:获取当前用户 Session - 被使用:auth-guard.ts, 所有 Server Component 页面, audit-logger.ts #### `handlers` - 签名:`{ GET, POST }` (NextAuth Route Handler) - 被使用:`app/api/auth/[...nextauth]/route.ts` #### `signIn` / `signOut` - 被使用:login-form.tsx, site-header.tsx ### authorize 回调(Credentials Provider) > 登录流程集成密码安全策略和速率限制: 1. **速率限制**:按 `IP:email` 维度限流(RATE_LIMIT_RULES.LOGIN: 5次/15分钟),超限返回 null 2. **账户锁定检查**:通过 `isAccountLocked(failedLoginAttempts, lastFailedAt)` 判断,锁定则返回 null 3. **密码验证失败**:调用 `recordFailedLogin` 递增失败次数,达阈值自动锁定 4. **登录成功**:调用 `resetFailedLogin` 清零失败次数,`resetRateLimit` 清除限流计数 ### Events 回调 #### `events.signIn` - 签名:`async signIn({ user }) => void` - 功能:用户登录成功后记录登录日志 - 依赖:`shared/lib/login-logger.logLoginEvent` - 调用参数:`{ userId: user.id, userEmail: user.email, action: "signin", status: "success" }` #### `events.signOut` - 签名:`async signOut(message) => void` - 功能:用户登出后记录登录日志(处理 NextAuth v5 不同 message 形状) - 依赖:`shared/lib/login-logger.logLoginEvent` - 调用参数:`{ userId?, userEmail, action: "signout", status: "success" }` #### `middleware` (proxy.ts) - 签名:`middleware(request: NextRequest) => Promise` - 功能:基于权限点的路由守卫,未登录重定向 /login,无权限重定向角色首页 - 依赖:`getToken` (next-auth/jwt), `Permissions` - 被使用:Next.js middleware 层 --- ## 模块:exams ### 模块职责 考试全生命周期管理:创建(手动/AI)、编辑、预览、发布、删除、复制。 ### 导出函数 (actions.ts) #### `createExamAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 参数说明:`formData` 包含 mode, title, subject, grade, difficulty, totalScore, durationMin, scheduledAt - 功能:手动模式创建考试草稿 - 依赖:`requirePermission(EXAM_CREATE)`, `shared/db`, `data-access.persistExamDraft` - 被使用:exam-form.tsx #### `createAiExamAction` - 签名:同上 - 功能:AI 模式创建考试(调用 AI pipeline 生成题目) - 依赖:`requirePermission(EXAM_AI_GENERATE)`, `ai-pipeline.generateAiCreateDraftFromSource`, `data-access.persistAiGeneratedExamDraft` - 被使用:exam-form.tsx #### `previewAiExamAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 功能:AI 预览试卷(不持久化) - 依赖:`requirePermission(EXAM_AI_GENERATE)`, `ai-pipeline.generateAiPreviewData` - 被使用:exam-ai-generator.tsx (via useExamPreview) #### `regenerateAiQuestionAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 功能:AI 重写单个题目 - 依赖:`requirePermission(EXAM_AI_GENERATE)`, `ai-pipeline.regenerateAiQuestionByInstruction` - 被使用:exam-preview-question-editor.tsx #### `updateExamAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 功能:更新考试信息,含资源归属校验(非 admin 只能改自己的) - 依赖:`requirePermission(EXAM_UPDATE)`, `shared/db` - 被使用:exam-form.tsx #### `deleteExamAction` - 签名:同上 - 功能:删除考试,含资源归属校验 - 依赖:`requirePermission(EXAM_DELETE)`, `shared/db` - 被使用:exam-actions.tsx #### `duplicateExamAction` - 签名:同上 - 功能:复制考试 - 依赖:`requirePermission(EXAM_DUPLICATE)`, `shared/db` - 被使用:exam-actions.tsx #### `getExamPreviewAction` - 签名:`(examId: string) => Promise>` - 功能:获取考试预览数据 - 依赖:`requirePermission(EXAM_READ)`, `shared/db` - 被使用:exam-viewer.tsx #### `getSubjectsAction` - 签名:`() => Promise>` - 依赖:`requirePermission(EXAM_READ)`, `shared/db` - 被使用:exam-form.tsx #### `getGradesAction` - 签名:同上 - 依赖:`requirePermission(EXAM_READ)`, `shared/db` - 被使用:exam-form.tsx ### 导出函数 (data-access.ts) #### `getExams` - 签名:`getExams(params: GetExamsParams & { scope: DataScope }): Promise` - 参数说明:`scope` 来自 `auth-guard.getAuthContext().dataScope` - 功能:查询考试列表,含数据权限过滤 - 依赖:`shared/db`, `DataScope` - 被使用:teacher/exams/all/page.tsx, homework 创建页面 #### `getExamById` - 签名:`getExamById(id: string, scope?: DataScope): Promise` - 被使用:exam 详情/编辑页面 #### `persistExamDraft` / `persistAiGeneratedExamDraft` - 被使用:createExamAction, createAiExamAction #### `omitScheduledAtFromDescription` - 功能:从考试描述中移除 scheduledAt 字段(用于编辑场景) - 被使用:exams/data-access.ts 内部 #### `resolveSubjectGradeNames` - 功能:将 subjectId/gradeId 解析为可读名称 - 被使用:exams/data-access.ts 内部, buildExamDescription #### `buildExamDescription` - 功能:构建考试描述文本(含科目、年级、时间等) - 依赖:`resolveSubjectGradeNames`, `omitScheduledAtFromDescription` - 被使用:createExamAction, createAiExamAction #### `GetExamsParams` (类型) - 定义:查询参数类型(含 subjectId?, gradeId?, status?, keyword? 等过滤条件) - 被使用:`getExams`, `getQuestionsAction` ### 导出函数 (ai-pipeline.ts) #### `generateAiPreviewData` - 签名:`(input: { title, subject?, grade?, difficulty, totalScore, durationMin, questionCount?, sourceText, aiProviderId? }) => Promise<{ ok, data?, rawOutput?, message? }>` - 依赖:`shared/lib/ai.createAiChatCompletion` - 被使用:previewAiExamAction #### `generateAiCreateDraftFromSource` - 被使用:createAiExamAction #### `regenerateAiQuestionByInstruction` - 被使用:regenerateAiQuestionAction #### `generateAiExamDraft` - 功能:生成 AI 考试草稿(含结构与题目) - 依赖:`shared/lib/ai.createAiChatCompletion`, `AiQuestionSchema`, `AiGeneratedStructureSchema` - 被使用:createAiExamAction ### AI Schema 与类型 (ai-pipeline.ts) #### `AiQuestionSchema` - 类型:Zod schema - 定义:AI 生成题目的校验 schema(type, content, difficulty, score, options? 等) - 被使用:`generateAiExamDraft`, `AiGeneratedQuestion` #### `AiInsertQuestionSchema` - 类型:Zod schema - 定义:插入题目到 DB 的校验 schema(含 authorId, parentId 等 DB 字段) - 被使用:`persistAiGeneratedExamDraft` #### `AiGeneratedQuestion` - 类型:TypeScript 类型(基于 `AiQuestionSchema` 推断) - 被使用:`AiPreviewData`, exams/components #### `AiGeneratedStructureNode` - 类型:TypeScript 类型 - 定义:AI 生成的试卷结构节点(section 标题、题目列表) - 被使用:`AiGeneratedStructureSchema` #### `AiGeneratedStructureNodeSchema` - 类型:Zod schema - 定义:`AiGeneratedStructureNode` 的校验 schema - 被使用:`AiGeneratedStructureSchema` #### `AiGeneratedStructureSchema` - 类型:Zod schema - 定义:AI 生成的完整试卷结构校验 schema(含 `AiGeneratedStructureNode[]`) - 被使用:`generateAiExamDraft` ### 类型/接口 #### `Exam` - 被使用:exams/components, homework/types (sourceExam), dashboard/types #### `AiPreviewData` / `AiRewriteQuestionData` - 被使用:exams/actions.ts, exams/components #### `ExamStatus` - 定义:考试状态枚举类型(draft/published 等) - 被使用:exams/data-access.ts, exams/components #### `ExamDifficulty` - 定义:考试难度枚举类型 - 被使用:exams/components, ai-pipeline.ts #### `SubmissionStatus` - 定义:提交状态枚举类型(in_progress/submitted/graded 等) - 被使用:examSubmissions 相关逻辑 #### `ExamSubmission` - 定义:考试提交记录类型(含 studentId, score, status 等) - 被使用:exams/data-access.ts, exams/components ### 导出 Hooks #### `useExamPreview` - 签名:`useExamPreview(): { isPending, execute, previewData, error }` - 功能:包装 `previewAiExamAction`,管理 AI 预览状态 - 被使用:exam-ai-generator.tsx ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `exam-form.tsx` | 考试创建/编辑表单(手动 + AI 模式) | | `exam-ai-generator.tsx` | AI 生成考试配置面板 | | `exam-viewer.tsx` | 考试预览展示 | | `exam-actions.tsx` | 考试操作菜单(复制/删除) | | `exam-data-table.tsx` | 考试列表数据表格 | | `exam-preview-question-editor.tsx` | AI 预览题目编辑器 | | `exam-status-badge.tsx` | 考试状态徽章 | | `exam-card.tsx` | 考试卡片 | | `exam-list.tsx` | 考试列表 | | `exam-detail.tsx` | 考试详情 | | `exam-header.tsx` | 考试头部信息 | | `exam-info.tsx` | 考试基本信息展示 | | `exam-question-list.tsx` | 考试题目列表 | | `exam-question-item.tsx` | 考试题目项 | | `exam-question-picker.tsx` | 题目选择器 | | `exam-score-summary.tsx` | 分值汇总 | | `exam-schedule-picker.tsx` | 考试时间选择器 | | `exam-subject-grade-select.tsx` | 科目年级选择器 | | `exam-empty.tsx` | 考试空状态 | | `assembly/exam-assembly-panel.tsx` | 组卷面板 | | `assembly/question-pool.tsx` | 题库选择池 | | `assembly/assembly-cart.tsx` | 已选题目购物车 | | `assembly/assembly-summary.tsx` | 组卷汇总 | --- ## 模块:homework ### 模块职责 作业全生命周期:创建(源自考试)、发布、学生作答、教师批改、数据分析。 ### 导出函数 (actions.ts) #### `createHomeworkAssignmentAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 功能:从已有考试创建作业 - 依赖:`requirePermission(HOMEWORK_CREATE)`, `shared/db`, `exams/data-access.getExams` - 被使用:homework-assignment-form.tsx #### `startHomeworkSubmissionAction` - 签名:同上 - 功能:学生开始作答 - 依赖:`requirePermission(HOMEWORK_SUBMIT)`, `shared/db` - 被使用:homework-take-view.tsx #### `saveHomeworkAnswerAction` - 签名:同上 - 功能:保存单题答案 - 依赖:`requirePermission(HOMEWORK_SUBMIT)`, `shared/db` - 被使用:homework-take-view.tsx #### `submitHomeworkAction` - 签名:同上 - 功能:提交作业 - 依赖:`requirePermission(HOMEWORK_SUBMIT)`, `shared/db` - 被使用:homework-take-view.tsx #### `gradeHomeworkSubmissionAction` - 签名:同上 - 功能:教师批改作业 - 依赖:`requirePermission(HOMEWORK_GRADE)`, `shared/db` - 被使用:homework-grading-view.tsx ### 导出函数 (data-access.ts) #### `getHomeworkAssignments` - 签名:`(params?: { creatorId?, ids?, classId?, scope? }) => Promise` - 依赖:`shared/db`, `DataScope` - 被使用:teacher 作业列表页, homework-assignment-form.tsx #### `getStudentHomeworkAssignments` - 签名:`(studentId: string) => Promise` - 被使用:student/dashboard #### `getStudentDashboardGrades` - 签名:`(studentId: string) => Promise` - 被使用:dashboard/data-access.ts (学生仪表盘) #### `getHomeworkAssignmentAnalytics` - 签名:`(assignmentId: string) => Promise` - 被使用:homework 错误分析组件 #### `getHomeworkAssignmentById` - 签名:`(assignmentId: string, scope?: DataScope) => Promise` - 功能:按 ID 获取作业详情(含数据权限过滤) - 被使用:homework 详情/编辑页面, homework-take-view.tsx #### `getHomeworkSubmissionDetails` - 签名:`(submissionId: string, scope?: DataScope) => Promise` - 功能:获取作业提交详情(含答案列表、学生信息) - 被使用:homework-grading-view.tsx #### `getDemoStudentUser` - 签名:`() => Promise<{ id: string; name: string } | null>` - 功能:获取演示学生用户(用于 demo/预览场景) - 被使用:homework demo 相关页面 #### `getStudentHomeworkTakeData` - 签名:`(studentId: string, assignmentId: string) => Promise` - 功能:获取学生作答作业所需完整数据(作业、题目、已答内容、提交状态) - 被使用:homework-take-view.tsx ### Schema (schema.ts) #### `CreateHomeworkAssignmentSchema` - 类型:Zod schema - 定义:创建作业的校验 schema(sourceExamId, title, classIds, dueAt, allowLate 等) - 被使用:`createHomeworkAssignmentAction` #### `CreateHomeworkAssignmentInput` - 类型:TypeScript 类型(基于 `CreateHomeworkAssignmentSchema` 推断) - 被使用:`createHomeworkAssignmentAction`, homework-assignment-form.tsx #### `GradeHomeworkSchema` - 类型:Zod schema - 定义:批改作业的校验 schema(submissionId, answers[{ questionId, score, feedback }]) - 被使用:`gradeHomeworkSubmissionAction` ### 类型/接口 #### `StudentDashboardGradeProps` - 被使用:dashboard/types.ts (StudentDashboardProps.grades) #### `HomeworkAssignmentListItem` - 被使用:homework 列表页, homework-assignment-form.tsx #### `HomeworkAssignment` - 定义:作业完整类型(含 sourceExam, targets, questions 等关联) - 被使用:homework 详情/编辑页面 #### `HomeworkAssignmentReviewListItem` - 被使用:teacher 批改列表 #### `HomeworkSubmissionListItem` - 被使用:teacher 提交列表 #### `HomeworkSubmission` - 定义:作业提交记录类型 - 被使用:homework-grading-view.tsx #### `HomeworkSubmissionDetails` - 定义:提交详情类型(含学生、答案列表) - 被使用:homework-grading-view.tsx #### `HomeworkAnswer` - 定义:作业答案类型 - 被使用:homework-take-view.tsx, homework-grading-view.tsx #### `HomeworkAssignmentAnalytics` - 定义:作业分析数据类型(含错误率、平均分等) - 被使用:homework 错误分析组件 #### `StudentHomeworkAssignmentListItem` - 被使用:student/dashboard #### `StudentHomeworkTakeData` - 定义:学生作答数据类型(含 assignment, questions, currentAnswers, submission) - 被使用:homework-take-view.tsx #### `TeacherGradeTrendItem` - 被使用:dashboard (教师仪表盘) #### `HomeworkAssignmentStatus` - 定义:作业状态枚举类型(draft/published/closed 等) - 被使用:homework/data-access.ts, homework/components #### `HomeworkSubmissionStatus` - 定义:提交状态枚举类型(in_progress/submitted/graded 等) - 被使用:homework/data-access.ts, homework/components #### `HomeworkAssignmentTarget` - 定义:作业目标学生类型 - 被使用:homework/data-access.ts #### `HomeworkQuestion` - 定义:作业题目类型(含 score, order) - 被使用:homework-take-view.tsx #### `HomeworkGradingInput` - 定义:批改输入类型 - 被使用:homework-grading-view.tsx ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `homework-assignment-form.tsx` | 作业创建表单(源自考试) | | `homework-take-view.tsx` | 学生作答视图 | | `homework-grading-view.tsx` | 教师批改视图 | | `homework-assignment-list.tsx` | 作业列表 | | `homework-assignment-card.tsx` | 作业卡片 | | `homework-assignment-detail.tsx` | 作业详情 | | `homework-submission-list.tsx` | 提交列表 | | `homework-submission-card.tsx` | 提交卡片 | | `homework-analytics.tsx` | 作业分析(错误率/平均分) | | `homework-status-badge.tsx` | 作业状态徽章 | --- ## 模块:questions ### 模块职责 题库管理:题目 CRUD、知识点关联、题型支持(选择/填空/判断/复合)。 ### 导出函数 (actions.ts) #### `createNestedQuestion` - 签名:`(prevState: ActionState | undefined, formData: FormData | CreateQuestionInput) => Promise>` - 依赖:`requirePermission(QUESTION_CREATE)`, `shared/db` - 被使用:create-question-dialog.tsx #### `updateQuestionAction` - 签名:同上 - 依赖:`requirePermission(QUESTION_UPDATE)`, `shared/db` - 被使用:question-actions.tsx #### `deleteQuestionAction` - 签名:同上 - 依赖:`requirePermission(QUESTION_DELETE)`, `shared/db` - 被使用:question-actions.tsx #### `getQuestionsAction` - 签名:`(params: GetQuestionsParams) => Promise<...>` - 依赖:`requirePermission(QUESTION_READ)`, `data-access.getQuestions` - 被使用:teacher/questions/page.tsx #### `getKnowledgePointOptionsAction` - 签名:`() => Promise` - 依赖:`requirePermission(QUESTION_READ)`, `shared/db` - 被使用:create-question-dialog.tsx ### 导出函数 (data-access.ts) #### `getQuestions` - 签名:`(params: GetQuestionsParams & { scope?: DataScope }) => Promise` - 功能:查询题目列表(含知识点关联、数据权限过滤) - 依赖:`shared/db`, `DataScope` - 被使用:`getQuestionsAction`, teacher/questions/page.tsx #### `GetQuestionsParams` (类型) - 定义:查询参数类型(含 keyword?, type?, difficulty?, knowledgePointId?, parentId? 等过滤条件) - 被使用:`getQuestions`, `getQuestionsAction` ### Schema (schema.ts) #### `QuestionTypeEnum` - 类型:Zod enum - 定义:题目类型枚举(choice/fill/judge/composite 等) - 被使用:`BaseQuestionSchema`, `CreateQuestionSchema` #### `BaseQuestionSchema` - 类型:Zod schema - 定义:题目基础校验 schema(type, content, difficulty, knowledgePointIds? 等) - 被使用:`CreateQuestionSchema` #### `CreateQuestionInput` - 类型:TypeScript 类型(基于 `CreateQuestionSchema` 推断) - 被使用:`createNestedQuestion`, create-question-dialog.tsx #### `CreateQuestionSchema` - 类型:Zod schema - 定义:创建题目的完整校验 schema(含 `BaseQuestionSchema` + 选项/答案/子题目) - 被使用:`createNestedQuestion` ### 类型/接口 #### `Question` - 被使用:exams (题目选择), homework (作业题目) #### `KnowledgePointOption` - 被使用:create-question-dialog.tsx #### `QuestionType` - 定义:题目类型联合类型(基于 `QuestionTypeEnum`) - 被使用:questions/components, exams/components, homework/components ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `create-question-dialog.tsx` | 创建题目对话框(含嵌套题目) | | `question-actions.tsx` | 题目操作菜单(编辑/删除) | | `question-data-table.tsx` | 题目列表数据表格 | | `question-card.tsx` | 题目卡片 | | `question-detail.tsx` | 题目详情展示 | | `question-type-badge.tsx` | 题目类型徽章 | --- ## 模块:textbooks ### 模块职责 教材与知识体系管理:教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱。 ### 导出函数 (actions.ts) | 函数 | 权限 | 核心功能 | |------|------|---------| | `createTextbookAction` | TEXTBOOK_CREATE | 创建教材 | | `updateTextbookAction` | TEXTBOOK_UPDATE | 更新教材元信息 | | `deleteTextbookAction` | TEXTBOOK_DELETE | 删除教材 | | `createChapterAction` | TEXTBOOK_CREATE | 创建章节 | | `updateChapterContentAction` | TEXTBOOK_UPDATE | 更新章节内容(Markdown) | | `deleteChapterAction` | TEXTBOOK_DELETE | 删除章节 | | `createKnowledgePointAction` | TEXTBOOK_CREATE | 创建知识点 | | `updateKnowledgePointAction` | TEXTBOOK_UPDATE | 更新知识点 | | `deleteKnowledgePointAction` | TEXTBOOK_DELETE | 删除知识点 | | `reorderChaptersAction` | TEXTBOOK_UPDATE | 章节排序 | ### 导出函数 (data-access.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `getTextbooks` | `(query?, subject?, grade?) => Promise` | teacher/textbooks/page.tsx | | `getTextbookById` | `(id: string) => Promise` | teacher/textbooks/[id]/page.tsx | | `getChaptersByTextbookId` | `(textbookId: string) => Promise` | textbook-reader.tsx | | `getKnowledgePointsByChapterId` | `(chapterId: string) => Promise` | textbook-reader.tsx | | `getKnowledgePointsByTextbookId` | `(textbookId: string) => Promise` | textbook-reader.tsx | #### 写操作函数 (data-access.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `persistTextbook` | `(input: { textbookId, title, subject, grade, publisher, creatorId }) => Promise` | createTextbookAction | | `updateTextbookMeta` | `(textbookId, input: { title?, subject?, grade?, publisher? }) => Promise` | updateTextbookAction | | `deleteTextbookRecord` | `(textbookId) => Promise` | deleteTextbookAction | | `persistChapter` | `(input: { chapterId, textbookId, parentId?, title, order }) => Promise` | createChapterAction | | `updateChapterContent` | `(chapterId, content, textbookId) => Promise` | updateChapterContentAction | | `deleteChapterRecord` | `(chapterId, textbookId) => Promise` | deleteChapterAction | | `reorderChapters` | `(chapterId, newIndex, parentId, textbookId) => Promise` | reorderChaptersAction | | `persistKnowledgePoint` | `(input: { kpId, chapterId, textbookId, name, description?, anchorText? }) => Promise` | createKnowledgePointAction | | `updateKnowledgePointRecord` | `(kpId, textbookId, input: { name?, description?, anchorText? }) => Promise` | updateKnowledgePointAction | | `deleteKnowledgePointRecord` | `(kpId, textbookId) => Promise` | deleteKnowledgePointAction | ### 导出 Hooks #### `useTextSelection` - 签名:`useTextSelection() => { selectedText, createDialogOpen, isCreating, handleContentPointerDown, handleContextMenuChange }` - 功能:管理教材内容文本选择与知识点创建对话框状态(无参数,内部使用 ref 与状态) - 被使用:textbook-content-panel.tsx #### `useKnowledgePointActions` - 签名:`useKnowledgePointActions(textbookId, selectedChapterId, highlightedKpId, setHighlightedKpId, onCreateKP, onEditKP) => { editingKp, editKpDialogOpen, ..., requestDeleteKnowledgePoint, confirmDeleteKnowledgePoint, handleUpdateKnowledgePoint }` - 功能:知识点 CRUD 操作集合(6 参数:textbookId, selectedChapterId, highlightedKpId, setHighlightedKpId, onCreateKP, onEditKP) - 被使用:textbook-reader.tsx ### 类型/接口 #### `Chapter` - 被使用:textbooks/components, questions (知识点关联) #### `KnowledgePoint` - 被使用:textbooks/components, questions/types (KnowledgePointOption) #### `Textbook` - 定义:教材类型(含 id, title, subject, grade, publisher 等) - 被使用:textbooks/components, teacher/textbooks/page.tsx #### `TextbookListItem` - 定义:教材列表项类型 - 被使用:teacher/textbooks/page.tsx #### `ChapterTreeNode` - 定义:章节树节点类型(含 children[]) - 被使用:textbook-reader.tsx, chapter-tree.tsx #### `KnowledgePointInput` - 定义:知识点创建/更新输入类型 - 被使用:useKnowledgePointActions, createKnowledgePointAction #### `ChapterInput` - 定义:章节创建输入类型 - 被使用:createChapterAction #### `ReorderChaptersInput` - 定义:章节排序输入类型 - 被使用:reorderChaptersAction ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `textbook-reader.tsx` | 教材阅读器(章节树 + 内容面板 + 知识点) | | `textbook-list.tsx` | 教材列表 | | `textbook-card.tsx` | 教材卡片 | | `textbook-form.tsx` | 教材创建/编辑表单 | | `textbook-content-panel.tsx` | 教材内容面板(Markdown 渲染 + 文本选择) | | `chapter-tree.tsx` | 章节树(可拖拽排序) | | `chapter-node.tsx` | 章节树节点 | | `chapter-form.tsx` | 章节创建/编辑表单 | | `chapter-content-editor.tsx` | 章节 Markdown 内容编辑器 | | `knowledge-point-list.tsx` | 知识点列表 | | `knowledge-point-item.tsx` | 知识点项 | | `knowledge-point-form.tsx` | 知识点创建/编辑表单 | | `knowledge-graph.tsx` | 知识图谱可视化 | --- ## 模块:classes ### 模块职责 班级管理:班级 CRUD、学生注册/退班、邀请码、课表、学科教师分配。 ### 导出函数 (actions.ts) | 函数 | 权限 | 核心功能 | |------|------|---------| | `createTeacherClassAction` | CLASS_CREATE | 教师创建班级 | | `updateTeacherClassAction` | CLASS_UPDATE | 教师更新班级 | | `deleteTeacherClassAction` | CLASS_DELETE | 教师删除班级 | | `createGradeClassAction` | CLASS_CREATE | 年级主任创建班级 | | `updateGradeClassAction` | CLASS_UPDATE | 年级主任更新班级 | | `deleteGradeClassAction` | CLASS_DELETE | 年级主任删除班级 | | `enrollStudentByEmailAction` | CLASS_ENROLL | 通过邮箱注册学生 | | `joinClassByInvitationCodeAction` | CLASS_ENROLL | 通过邀请码加入 | | `ensureClassInvitationCodeAction` | CLASS_ENROLL | 确保邀请码存在 | | `regenerateClassInvitationCodeAction` | CLASS_ENROLL | 重新生成邀请码 | | `setStudentEnrollmentStatusAction` | CLASS_ENROLL | 设置学生状态 | | `createClassScheduleItemAction` | CLASS_SCHEDULE | 创建课表项 | | `updateClassScheduleItemAction` | CLASS_SCHEDULE | 更新课表项 | | `deleteClassScheduleItemAction` | CLASS_SCHEDULE | 删除课表项 | | `createAdminClassAction` | CLASS_CREATE | 管理员创建班级 | | `updateAdminClassAction` | CLASS_UPDATE | 管理员更新班级 | | `deleteAdminClassAction` | CLASS_DELETE | 管理员删除班级 | ### 导出函数 (data-access.ts) #### 读操作函数 | 函数 | 签名 | 被使用 | |------|------|--------| | `getTeacherClasses` | `(params?: { teacherId? }) => Promise` | teacher/classes/my, dashboard | | `getAdminClasses` | `() => Promise` | admin 班级管理 | | `getGradeManagedClasses` | `(userId) => Promise` | grade_head 班级管理 | | `getStudentClasses` | `(studentId) => Promise` | student/dashboard | | `getStudentSchedule` | `(studentId) => Promise` | student 课表 | | `getClassStudents` | `(classId, scope?) => Promise` | teacher/classes/students | | `getClassSchedule` | `(classId) => Promise` | teacher/classes/schedule | | `getClassHomeworkInsights` | `(classId) => Promise` | classes 作业洞察 | | `getGradeHomeworkInsights` | `(gradeId) => Promise` | 年级作业洞察 | | `getClassById` | `(classId, scope?) => Promise` | 班级详情页 | | `getClassDetails` | `(classId, scope?) => Promise` | 班级详情(含学生数、教师数) | | `getClassSubjectTeachers` | `(classId) => Promise` | 班级学科教师分配 | | `getStudentEnrollmentStatus` | `(classId, studentId) => Promise` | 注册状态查询 | | `validateInvitationCode` | `(code) => Promise<{ valid: boolean; classId?: string }>` | 邀请码校验 | | `getClassInsights` | `(classId) => Promise` | 班级洞察数据 | #### 写操作函数 | 函数 | 签名 | 被使用 | |------|------|--------| | `persistTeacherClass` | `(input: { classId, teacherId, name, gradeId, ... }) => Promise` | createTeacherClassAction | | `updateTeacherClassRecord` | `(classId, input) => Promise` | updateTeacherClassAction | | `deleteTeacherClassRecord` | `(classId) => Promise` | deleteTeacherClassAction | | `persistGradeClass` | `(input: { classId, gradeId, ... }) => Promise` | createGradeClassAction | | `updateGradeClassRecord` | `(classId, input) => Promise` | updateGradeClassAction | | `deleteGradeClassRecord` | `(classId) => Promise` | deleteGradeClassAction | | `enrollStudentByEmail` | `(classId, email) => Promise<{ studentId }>` | enrollStudentByEmailAction | | `joinClassByInvitationCode` | `(code, studentId) => Promise<{ classId }>` | joinClassByInvitationCodeAction | | `ensureInvitationCode` | `(classId) => Promise<{ code }>` | ensureClassInvitationCodeAction | | `regenerateInvitationCode` | `(classId) => Promise<{ code }>` | regenerateClassInvitationCodeAction | | `setStudentEnrollmentStatus` | `(classId, studentId, status) => Promise` | setStudentEnrollmentStatusAction | | `persistClassScheduleItem` | `(input: { scheduleId, classId, ... }) => Promise` | createClassScheduleItemAction | | `updateClassScheduleItem` | `(scheduleId, input) => Promise` | updateClassScheduleItemAction | | `deleteClassScheduleItem` | `(scheduleId) => Promise` | deleteClassScheduleItemAction | | `persistAdminClass` | `(input: { classId, ... }) => Promise` | createAdminClassAction | | `updateAdminClassRecord` | `(classId, input) => Promise` | updateAdminClassAction | | `deleteAdminClassRecord` | `(classId) => Promise` | deleteAdminClassAction | | `assignSubjectTeacher` | `(classId, teacherId, subjectId) => Promise` | 学科教师分配 | | `removeClassSubjectTeacher` | `(classId, teacherId, subjectId) => Promise` | 移除学科教师 | | `bulkEnrollStudents` | `(classId, studentIds[]) => Promise` | 批量注册学生 | | `transferStudent` | `(fromClassId, toClassId, studentId) => Promise` | 学生转班 | | `getClassInvitationInfo` | `(classId) => Promise<{ code, expiresAt? }>` | 邀请信息查询 | | `countClassStudents` | `(classId) => Promise` | 学生计数 | ### 类型/接口 #### `TeacherClass` - 定义:教师班级类型(含 grade, studentCount 等) - 被使用:teacher/classes/my, dashboard #### `AdminClassListItem` - 定义:管理员班级列表项类型 - 被使用:admin 班级管理 #### `StudentEnrolledClass` - 定义:学生已加入班级类型 - 被使用:student/dashboard #### `StudentScheduleItem` - 定义:学生课表项类型 - 被使用:student 课表 #### `ClassStudent` - 定义:班级学生类型(含 enrollmentStatus) - 被使用:teacher/classes/students #### `ClassScheduleItem` - 定义:班级课表项类型 - 被使用:teacher/classes/schedule #### `ClassHomeworkInsights` - 定义:班级作业洞察类型 - 被使用:classes 作业洞察 #### `GradeHomeworkInsights` - 定义:年级作业洞察类型 - 被使用:年级作业洞察 #### `Class` - 定义:班级基础类型 - 被使用:classes/components #### `ClassDetails` - 定义:班级详情类型(含学生数、教师数、课表等) - 被使用:班级详情页 #### `ClassSubjectTeacher` - 定义:班级学科教师类型 - 被使用:班级学科教师分配 #### `EnrollmentStatus` - 定义:注册状态枚举类型(active/inactive/pending) - 被使用:classes/data-access.ts #### `ClassInsights` - 定义:班级洞察数据类型 - 被使用:班级洞察页面 #### `CreateClassInput` - 定义:创建班级输入类型 - 被使用:createTeacherClassAction, createAdminClassAction #### `UpdateClassInput` - 定义:更新班级输入类型 - 被使用:updateTeacherClassAction, updateAdminClassAction #### `CreateScheduleItemInput` - 定义:创建课表项输入类型 - 被使用:createClassScheduleItemAction #### `EnrollmentInput` - 定义:注册输入类型 - 被使用:enrollStudentByEmailAction #### `InvitationCodeResult` - 定义:邀请码结果类型 - 被使用:ensureClassInvitationCodeAction #### `ClassListItem` - 定义:班级列表项类型 - 被使用:班级列表页 #### `ClassFormValues` - 定义:班级表单值类型 - 被使用:class-form.tsx ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `class-list.tsx` | 班级列表 | | `class-card.tsx` | 班级卡片 | | `class-form.tsx` | 班级创建/编辑表单 | | `class-detail.tsx` | 班级详情 | | `class-actions.tsx` | 班级操作菜单 | | `class-students.tsx` | 班级学生列表 | | `class-student-card.tsx` | 班级学生卡片 | | `class-schedule.tsx` | 班级课表 | | `class-schedule-form.tsx` | 课表项创建/编辑表单 | | `class-schedule-item.tsx` | 课表项展示 | | `class-invitation.tsx` | 邀请码组件 | | `class-invitation-dialog.tsx` | 邀请码对话框 | | `class-enroll-dialog.tsx` | 学生注册对话框 | | `class-subject-teachers.tsx` | 学科教师分配 | | `class-subject-teacher-form.tsx` | 学科教师表单 | | `class-insights.tsx` | 班级洞察 | | `class-homework-insights.tsx` | 班级作业洞察 | | `class-status-badge.tsx` | 班级状态徽章 | --- ## 模块:school ### 模块职责 学校基础数据管理:学校、年级、部门、学年的 CRUD。 ### 导出函数 (actions.ts) > 所有 12 个 actions 均使用 `requirePermission()` 进行权限校验。 > 学校 CRUD actions(createSchoolAction/updateSchoolAction/deleteSchoolAction)在写操作成功后调用 `logAudit()` 记录操作日志。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `createSchoolAction` | SCHOOL_MANAGE | 创建学校(成功后记录 audit log: school.create) | | `updateSchoolAction` | SCHOOL_MANAGE | 更新学校(成功后记录 audit log: school.update) | | `deleteSchoolAction` | SCHOOL_MANAGE | 删除学校(成功后记录 audit log: school.delete) | | `createGradeAction` | GRADE_MANAGE | 创建年级 | | `updateGradeAction` | GRADE_MANAGE | 更新年级 | | `deleteGradeAction` | GRADE_MANAGE | 删除年级 | | `createDepartmentAction` | SCHOOL_MANAGE | 创建部门 | | `updateDepartmentAction` | SCHOOL_MANAGE | 更新部门 | | `deleteDepartmentAction` | SCHOOL_MANAGE | 删除部门 | | `createAcademicYearAction` | SCHOOL_MANAGE | 创建学年 | | `updateAcademicYearAction` | SCHOOL_MANAGE | 更新学年 | | `deleteAcademicYearAction` | SCHOOL_MANAGE | 删除学年 | ### 导出函数 (data-access.ts) | 函数 | 被使用 | |------|--------| | `getSchools()` | admin 学校管理, onboarding | | `getGrades()` | admin 年级管理, exams, onboarding | | `getDepartments()` | admin 部门管理 | | `getAcademicYears()` | admin 学年管理 | | `getStaffOptions()` | school 组件 (年级主任选择) | | `getGradesForStaff(staffId)` | grade_head 视图 | ### Schema (schema.ts) #### `CreateSchoolSchema` - 类型:Zod schema - 定义:创建学校的校验 schema(name, code) - 被使用:`createSchoolAction` #### `CreateGradeSchema` - 类型:Zod schema - 定义:创建年级的校验 schema(schoolId, name, order, gradeHeadId?, teachingHeadId?) - 被使用:`createGradeAction` #### `CreateDepartmentSchema` - 类型:Zod schema - 定义:创建部门的校验 schema(name, description?) - 被使用:`createDepartmentAction` #### `CreateAcademicYearSchema` - 类型:Zod schema - 定义:创建学年的校验 schema(name, startDate, endDate, isActive?) - 被使用:`createAcademicYearAction` ### 类型/接口 #### `SchoolListItem` - 定义:学校列表项类型(含 id, name, code) - 被使用:admin 学校管理 #### `GradeListItem` - 定义:年级列表项类型(含 schoolId, name, gradeHeadId?) - 被使用:admin 年级管理, exams #### `DepartmentListItem` - 定义:部门列表项类型 - 被使用:admin 部门管理 #### `AcademicYearListItem` - 定义:学年列表项类型 - 被使用:admin 学年管理 #### `StaffOption` - 定义:员工选项类型(含 id, name) - 被使用:school 组件 (年级主任选择) ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `school-form.tsx` | 学校创建/编辑表单 | | `grade-form.tsx` | 年级创建/编辑表单 | | `department-form.tsx` | 部门创建/编辑表单 | | `academic-year-form.tsx` | 学年创建/编辑表单 | --- ## 模块:dashboard ### 模块职责 各角色仪表盘数据聚合与展示。 ### 导出函数 (data-access.ts) #### `getAdminDashboardData` - 签名:`getAdminDashboardData(scope?: DataScope): Promise` - 依赖:`shared/db`, `DataScope` - 被使用:admin/dashboard/page.tsx ### 类型/接口 #### `StudentDashboardProps` - 被使用:student-dashboard.tsx - 依赖:`homework/types.StudentDashboardGradeProps` #### `StudentDashboard` - 定义:学生仪表盘组件(原 `StudentDashboardView`,已重命名) - 被使用:student/dashboard/page.tsx #### `TeacherDashboardData` - 被使用:teacher-dashboard-view.tsx - 依赖:`homework/data-access.getTeacherGradeTrends`, `classes/data-access.getTeacherClasses` #### `TeacherDashboardProps` - 定义:教师仪表盘组件 Props 类型 - 被使用:teacher-dashboard-view.tsx #### `AdminDashboardData` - 定义:管理员仪表盘数据类型(含 activeSessionsCount, userCount, userRoleCounts, classCount 等) - 被使用:admin/dashboard/page.tsx #### `DashboardWidget` - 定义:仪表盘通用小组件类型(含 title, value, trend? 等) - 被使用:dashboard/components ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `admin-dashboard-view.tsx` | 管理员仪表盘视图 | | `teacher-dashboard-view.tsx` | 教师仪表盘视图 | | `student-dashboard.tsx` | 学生仪表盘视图(原 StudentDashboardView) | | `parent-dashboard-view.tsx` | 家长仪表盘视图 | | `dashboard-card.tsx` | 仪表盘卡片 | | `dashboard-widget.tsx` | 仪表盘小组件 | | `dashboard-stat.tsx` | 统计数据展示 | | `dashboard-chart.tsx` | 图表组件 | | `dashboard-schedule.tsx` | 课表展示 | | `dashboard-assignment-list.tsx` | 作业列表 | | `dashboard-assignment-item.tsx` | 作业项 | | `dashboard-grade-trend.tsx` | 成绩趋势 | | `dashboard-class-list.tsx` | 班级列表 | | `dashboard-submission-list.tsx` | 提交列表 | | `dashboard-empty.tsx` | 仪表盘空状态 | | `dashboard-header.tsx` | 仪表盘头部 | --- ## 模块:layout ### 模块职责 应用布局框架:侧边栏、顶栏、导航配置。 ### 导出组件 #### `AppSidebar` - 内部使用:`usePermission`, `NAV_CONFIG` - 功能:根据权限渲染侧边栏导航 #### `SiteHeader` - 内部使用:`useSession`, `signOut`, `NotificationDropdown`(来自 messaging 模块), `GlobalSearch`(来自 shared/components), `Breadcrumb` - 功能:顶部导航栏(含全局搜索 GlobalSearch、通知下拉菜单展示未读通知数、面包屑导航、用户下拉菜单) #### `SidebarProvider` - Props: `{ children: React.ReactNode, defaultOpen?: boolean }` - 功能:侧边栏状态上下文 Provider(管理展开/折叠状态) - 被使用:app/layout.tsx ### 导出 Hooks #### `useSidebar` - 签名:`useSidebar(): { isOpen, toggle, setOpen, isMobile, openMobile, setOpenMobile }` - 功能:访问侧边栏状态(需在 `SidebarProvider` 内使用) - 被使用:app-sidebar.tsx, site-header.tsx ### 类型/接口 #### `Role` - 定义:`type Role = "admin" | "teacher" | "student" | "parent" | "grade_head" | "teaching_head"` - 被使用:NAV_CONFIG, usePermission #### `NavItem` - 定义:`{ title: string; href: string; icon?: string; permission?: Permission; children?: NavItem[] }` - 被使用:NAV_CONFIG, app-sidebar.tsx ### 导出配置 #### `NAV_CONFIG` - 类型:`Record` - 每个NavItem含 `permission?: Permission` 字段 - admin 角色菜单包含 "Audit Logs" 项(icon: ScrollText, href: /admin/audit-logs, permission: Permissions.AUDIT_LOG_READ),含子项 Operation Logs 与 Login Logs - admin 角色菜单的 "School Management" 子菜单包含 "Course Plans" 项(href: /admin/course-plans, permission: Permissions.COURSE_PLAN_MANAGE) - admin 角色菜单的 "School Management" 子菜单包含 "Import Users" 项(href: /admin/users/import, permission: Permissions.USER_MANAGE) - admin 角色菜单包含 "Scheduling" 项(icon: CalendarClock, href: /admin/scheduling/rules, permission: Permissions.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: Permissions.GRADE_RECORD_READ),含子项 All Grades (/teacher/grades)、Batch Entry (/teacher/grades/entry, permission: GRADE_RECORD_MANAGE)、Statistics (/teacher/grades/stats)、Analytics (/teacher/grades/analytics, permission: GRADE_RECORD_READ) - teacher 角色菜单包含 "Course Plans" 项(icon: CalendarRange, href: /teacher/course-plans, permission: Permissions.COURSE_PLAN_READ) - teacher 角色菜单包含 "Attendance" 项(icon: CalendarCheck, href: /teacher/attendance, permission: Permissions.ATTENDANCE_MANAGE),含子项 Records (/teacher/attendance)、Take Attendance (/teacher/attendance/sheet, permission: ATTENDANCE_MANAGE)、Statistics (/teacher/attendance/stats, permission: ATTENDANCE_READ) - teacher 角色菜单包含 "Schedule Changes" 项(icon: CalendarClock, href: /teacher/schedule-changes, permission: Permissions.SCHEDULE_ADJUST) - teacher 角色菜单包含 "Diagnostic" 项(icon: Stethoscope, href: /teacher/diagnostic, permission: Permissions.DIAGNOSTIC_READ) - student 角色菜单包含 "My Grades" 项(icon: GraduationCap, href: /student/grades, permission: Permissions.GRADE_RECORD_READ) - student 角色菜单包含 "Attendance" 项(icon: CalendarCheck, href: /student/attendance, permission: Permissions.ATTENDANCE_READ) - student 角色菜单包含 "Diagnostic" 项(icon: Stethoscope, href: /student/diagnostic, permission: Permissions.DIAGNOSTIC_READ) - parent 角色菜单包含 "Dashboard" 项(icon: LayoutDashboard, href: /parent/dashboard,无 permission 字段,仅需登录) - parent 角色菜单包含 "Grades" 项(icon: GraduationCap, href: /parent/grades, permission: Permissions.GRADE_RECORD_READ) - parent 角色菜单包含 "Attendance" 项(icon: CalendarCheck, href: /parent/attendance, permission: Permissions.ATTENDANCE_READ) - parent 角色菜单包含 "Announcements" 项(icon: Megaphone, href: /announcements, permission: Permissions.ANNOUNCEMENT_READ) - 所有角色(admin/teacher/student/parent)菜单均包含 "Messages" 项(icon: Mail, href: /messages, permission: Permissions.MESSAGE_READ) - 被使用:app-sidebar.tsx --- ## 模块:settings ### 模块职责 系统设置:AI Provider 配置、用户偏好、密码安全(修改密码、强度校验)。 ### 导出函数 (actions.ts) > 所有 3 个 actions 均使用 `requirePermission(AI_CONFIGURE)` 进行权限校验。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `getAiProviderSummaries()` | AI_CONFIGURE | 获取 AI Provider 列表 | | `upsertAiProviderAction(data)` | AI_CONFIGURE | 创建/更新 AI Provider | | `testAiProviderAction(data)` | AI_CONFIGURE | 测试 AI Provider 连通性 | ### 导出函数 (actions-password.ts) #### `changePasswordAction` - 签名:`(prevState: ActionState, formData: FormData) => Promise>` - 权限:`requireAuth()`(任何登录用户可修改自己的密码) - 功能:修改当前用户密码(校验当前密码、新密码策略、速率限制 PASSWORD_CHANGE: 5次/分钟) - 依赖:`requireAuth`, `validatePassword`, `rateLimit`, bcryptjs (`hash`/`compare`), `shared/db` (users, passwordSecurity) - 被使用:settings/components/password-change-form.tsx ### 类型/接口 #### `AiProviderSummary` - 定义:AI Provider 摘要类型(含 id, provider, baseUrl, model, apiKeyLast4, isDefault 等,不含完整 apiKey) - 被使用:getAiProviderSummaries, settings/components ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `ai-provider-list.tsx` | AI Provider 列表 | | `ai-provider-form.tsx` | AI Provider 创建/编辑表单 | | `ai-provider-card.tsx` | AI Provider 卡片 | | `ai-provider-test-dialog.tsx` | AI Provider 测试对话框 | | `ai-provider-actions.tsx` | AI Provider 操作菜单 | | `settings-layout.tsx` | 设置页面布局 | | `password-change-form.tsx` | 密码修改表单(当前密码/新密码/确认密码 + 强度指示器 + 需求提示) | | `notification-preferences-form.tsx` | 通知偏好表单(Delivery Channels: push/email/sms + Notification Categories: messages/announcements/homework/grades/attendance;Switch 切换 + 隐藏 checkbox 提交;useActionState 调用 updateNotificationPreferencesAction;toast 反馈) | | `admin-settings-view.tsx` | 管理员设置视图(General/Appearance/Security/Notifications tab,Security 含 PasswordChangeForm,Notifications 含 NotificationPreferencesForm;接收 notificationPreferences prop) | | `teacher-settings-view.tsx` | 教师设置视图(同上,含 Notifications tab) | | `student-settings-view.tsx` | 学生设置视图(同上,含 Notifications tab) | --- ## 模块:users ### 模块职责 用户个人资料管理:当前用户查看与更新自己的资料;用户批量导入/导出(Excel)。 ### 模块路径 `src/modules/users` ### 导出函数 (actions.ts) #### `updateUserProfile` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 功能:更新当前用户个人资料(name, phone, address, gender, age 等) - 依赖:`requireAuth()`, `shared/db` - 被使用:profile-form.tsx > 注:本模块仅使用 `requireAuth()` 校验登录状态,不涉及权限点(用户只能修改自己的资料)。 #### `downloadUserTemplateAction` - 签名:`() => Promise>` - 权限:`requirePermission(USER_MANAGE)` - 功能:生成用户导入模板(返回 base64 编码的 Excel) - 依赖:`requirePermission`, `import-export.generateUserImportTemplate` - 被使用:components/user-import-dialog.tsx #### `importUsersAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 权限:`requirePermission(USER_MANAGE)` - 功能:导入用户:接收文件,解析+验证+批量创建(默认密码 123456) - 依赖:`requirePermission`, `shared/lib/excel.parseExcel`, `import-export.parseUserImportData`, `import-export.batchImportUsers` - 被使用:components/user-import-dialog.tsx #### `exportUsersAction` - 签名:`(role?: string) => Promise>` - 权限:`requirePermission(USER_MANAGE)` - 功能:导出用户列表(返回 base64 编码的 Excel) - 依赖:`requirePermission`, `import-export.exportUsersToExcel` - 被使用:待扩展 ### 导出函数 (data-access.ts) #### `getUserProfile` - 签名:`(userId: string) => Promise` - 功能:获取用户个人资料(含 name, email, phone, address, gender, age, grade, department 等) - 依赖:`shared/db` - 被使用:profile/page.tsx, profile-form.tsx ### 导出函数 (import-export.ts) #### `generateUserImportTemplate` - 签名:`() => Promise` - 功能:生成用户导入模板(列:姓名/邮箱/角色/手机/班级邀请码,含示例行) - 依赖:`shared/lib/excel.generateTemplate` - 被使用:`downloadUserTemplateAction` #### `parseUserImportData` - 签名:`(rows: Record[]) => UserImportValidation` - 功能:解析并验证导入行(校验姓名/邮箱格式/角色枚举/邀请码仅 student) - 依赖:无 - 被使用:`importUsersAction` #### `batchImportUsers` - 签名:`(records: UserImportRecord[]) => Promise` - 功能:批量创建用户(默认密码 123456 bcrypt 哈希,自动创建 usersToRoles,student 通过邀请码自动加入班级) - 依赖:`shared/db`, `bcryptjs`, `@paralleldrive/cuid2` - 被使用:`importUsersAction` #### `exportUsersToExcel` - 签名:`(params: { scope: DataScope; role?: string }) => Promise` - 功能:导出用户列表到 Excel(含姓名/邮箱/手机/性别/年龄/角色/创建时间) - 依赖:`shared/db`, `shared/lib/excel.exportToExcel` - 被使用:`exportUsersAction`, `app/api/export/route.ts` ### 类型/接口 #### `UserProfile` - 定义:用户资料类型(含 id, name, email, phone, address, gender, age, gradeId?, departmentId?, onboardedAt? 等) - 被使用:profile/page.tsx, profile-form.tsx #### `UpdateUserProfileInput` - 定义:更新用户资料输入类型(name?, phone?, address?, gender?, age? 等可选字段) - 被使用:`updateUserProfile`, profile-form.tsx #### `UserImportRecord` - 定义:`{ name, email, role, phone?, invitationCode? }` - 被使用:`parseUserImportData`, `batchImportUsers` #### `UserImportValidation` - 定义:`{ valid: UserImportRecord[], invalid: Array<{ row, record, errors }> }` - 被使用:`parseUserImportData` #### `UserImportResult` - 定义:`{ successCount, failedCount, errors: Array<{ row, email, error }> }` - 被使用:`batchImportUsers`, `importUsersAction` ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `user-import-dialog.tsx` | 用户批量导入对话框(4 状态:idle/preview/importing/done;下载模板→上传预览→确认导入→结果展示) | --- ## 模块:audit ### 模块职责 操作日志与登录日志查询:提供审计日志的列表展示、筛选与分页能力,支撑管理员审计与合规需求。 ### 模块路径 `src/modules/audit` ### 导出函数 (data-access.ts) #### `getAuditLogs` - 签名:`getAuditLogs(params?: AuditLogQueryParams): Promise>` - 参数说明:`params`: AuditLogQueryParams,含 userId?, module?, action?, status?, page?, pageSize?, startDate?, endDate? - 功能:分页查询操作日志(支持按模块/操作/状态/日期范围过滤) - 依赖:`shared/db` (auditLogs 表) - 被使用:app/(dashboard)/admin/audit-logs/page.tsx #### `getLoginLogs` - 签名:`getLoginLogs(params?: LoginLogQueryParams): Promise>` - 参数说明:`params`: LoginLogQueryParams,含 userId?, action?, status?, page?, pageSize?, startDate?, endDate? - 功能:分页查询登录日志(支持按操作/状态/日期范围过滤) - 依赖:`shared/db` (loginLogs 表) - 被使用:app/(dashboard)/admin/audit-logs/login-logs/page.tsx #### `getAuditModuleOptions` - 签名:`getAuditModuleOptions(): Promise` - 功能:获取操作日志中所有不同的 module 值(用于筛选下拉框) - 依赖:`shared/db` (auditLogs 表) - 被使用:app/(dashboard)/admin/audit-logs/page.tsx #### `getDataChangeLogs` - 签名:`getDataChangeLogs(params?: DataChangeLogQueryParams): Promise>` - 参数说明:`params`: DataChangeLogQueryParams,含 tableName?, recordId?, action?, userId?, page?, pageSize?, startDate?, endDate? - 功能:分页查询数据变更日志(支持按表名/记录ID/操作/用户/日期范围过滤) - 依赖:`shared/db` (dataChangeLogs 表) - 被使用:`getDataChangeLogsAction` #### `getDataChangeStats` - 签名:`getDataChangeStats(): Promise` - 功能:按 tableName 分组统计数据变更日志数量(用于统计卡片) - 依赖:`shared/db` (dataChangeLogs 表) - 被使用:`getDataChangeLogsAction` #### `getDataChangeTableOptions` - 签名:`getDataChangeTableOptions(): Promise` - 功能:获取数据变更日志中所有不同的 tableName 值(用于筛选下拉框) - 依赖:`shared/db` (dataChangeLogs 表) - 被使用:`getDataChangeLogsAction` #### `getDataChangeLogsForExport` - 签名:`getDataChangeLogsForExport(params?: DataChangeLogQueryParams): Promise` - 功能:导出用:按条件查询全部数据变更日志(取前 100 条,无分页上限封顶) - 依赖:`getDataChangeLogs` - 被使用:`exportDataChangeLogsAction` #### `getAuditLogsForExport` - 签名:`getAuditLogsForExport(params?: AuditLogQueryParams): Promise` - 功能:导出用:按条件查询全部操作日志(取前 100 条) - 依赖:`getAuditLogs` - 被使用:`exportAuditLogsAction` #### `getLoginLogsForExport` - 签名:`getLoginLogsForExport(params?: LoginLogQueryParams): Promise` - 功能:导出用:按条件查询全部登录日志(取前 100 条) - 依赖:`getLoginLogs` - 被使用:`exportLoginLogsAction` ### 导出函数 (actions.ts) > 所有 actions 均使用 `requirePermission(AUDIT_LOG_READ)` 进行权限校验(数据变更日志复用 AUDIT_LOG_READ 权限)。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `getDataChangeLogsAction` | AUDIT_LOG_READ | 获取数据变更日志(分页结果 + tableOptions + stats 三者并行加载) | | `exportAuditLogsAction` | AUDIT_LOG_READ | 导出操作日志为 Excel(返回 `{ buffer, filename }`) | | `exportLoginLogsAction` | AUDIT_LOG_READ | 导出登录日志为 Excel(返回 `{ buffer, filename }`) | | `exportDataChangeLogsAction` | AUDIT_LOG_READ | 导出数据变更日志为 Excel(返回 `{ buffer, filename }`) | ### 类型/接口 (types.ts) #### `AuditLog` - 定义:操作日志类型(含 id, userId, userName, action, module, targetId, targetType, detail, ipAddress, userAgent, status, createdAt) - 被使用:audit/components, audit/data-access #### `LoginLog` - 定义:登录日志类型(含 id, userId, userEmail, action, status, ipAddress, userAgent, errorMessage, createdAt) - 被使用:audit/components, audit/data-access #### `AuditLogQueryParams` - 定义:操作日志查询参数类型(含 userId?, module?, action?, status?, page?, pageSize?, startDate?, endDate?) - 被使用:`getAuditLogs`, audit-logs/page.tsx #### `LoginLogQueryParams` - 定义:登录日志查询参数类型(含 userId?, action?, status?, page?, pageSize?, startDate?, endDate?) - 被使用:`getLoginLogs`, login-logs/page.tsx #### `PaginatedResult` - 定义:分页结果接口(含 items: T[], total, page, pageSize, totalPages) - 被使用:`getAuditLogs`, `getLoginLogs`, `getDataChangeLogs`, audit/components #### `DataChangeAction` - 定义:`"create" | "update" | "delete"` - 被使用:data-access, change-logger, types.DataChangeLog #### `DataChangeLog` - 定义:数据变更日志类型(含 id, tableName, recordId, action, oldValue, newValue, changedBy, changedByName, ipAddress, createdAt) - 被使用:audit/data-access, audit/actions #### `DataChangeStat` - 定义:`{ tableName: string; count: number }`(按表名分组的变更统计) - 被使用:`getDataChangeStats`, `getDataChangeLogsAction` #### `DataChangeLogQueryParams` - 定义:数据变更日志查询参数类型(含 tableName?, recordId?, action?, userId?, page?, pageSize?, startDate?, endDate?) - 被使用:`getDataChangeLogs`, `getDataChangeLogsForExport`, `getDataChangeLogsAction`, `exportDataChangeLogsAction` ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `audit-log-table.tsx` | 操作日志表格(含分页控件) | | `audit-log-filters.tsx` | 操作日志筛选器(模块/操作/状态/日期,基于 nuqs) | | `audit-log-view.tsx` | 操作日志视图(筛选+表格+URL 分页) | | `login-log-table.tsx` | 登录日志表格(含分页控件) | | `login-log-filters.tsx` | 登录日志筛选器(操作/状态/日期,基于 nuqs) | | `login-log-view.tsx` | 登录日志视图(筛选+表格+URL 分页) | --- ## 模块:announcements ### 模块职责 通知公告系统:创建、编辑、发布、归档、删除公告,所有登录用户可查看已发布公告。 ### 模块路径 `src/modules/announcements` ### 导出函数 (actions.ts) > 所有 manage actions 均使用 `requirePermission(ANNOUNCEMENT_MANAGE)` 进行权限校验。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `createAnnouncementAction` | ANNOUNCEMENT_MANAGE | 创建公告(草稿/已发布) | | `updateAnnouncementAction` | ANNOUNCEMENT_MANAGE | 更新公告 | | `deleteAnnouncementAction` | ANNOUNCEMENT_MANAGE | 删除公告 | | `publishAnnouncementAction` | ANNOUNCEMENT_MANAGE | 发布公告(设置 published 状态) | | `archiveAnnouncementAction` | ANNOUNCEMENT_MANAGE | 归档公告 | | `getAnnouncementsAction` | requireAuth | 获取公告列表(所有登录用户可读) | ### 导出函数 (data-access.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `getAnnouncements` | `(params?: { status?, type?, page?, pageSize? }) => Promise` | admin/announcements, announcements 页面 | | `getAnnouncementById` | `(id: string) => Promise` | 编辑页面 | ### Schema (schema.ts) #### `CreateAnnouncementSchema` - 类型:Zod schema - 定义:创建公告的校验 schema(title, content, type, status, targetGradeId?, targetClassId?, publishedAt?) - 被使用:`createAnnouncementAction` #### `UpdateAnnouncementSchema` - 类型:Zod schema - 定义:更新公告的校验 schema(同上) - 被使用:`updateAnnouncementAction` ### 类型/接口 #### `Announcement` - 定义:公告完整类型(含 id, title, content, type, status, targetGradeId, targetClassId, authorId, authorName, publishedAt, createdAt, updatedAt) - 被使用:announcements/components, 页面 #### `AnnouncementListItem` - 定义:公告列表项类型(同 Announcement) - 被使用:列表页 #### `AnnouncementStatus` - 定义:`"draft" | "published" | "archived"` - 被使用:data-access, components #### `AnnouncementType` - 定义:`"school" | "grade" | "class"` - 被使用:data-access, components #### `GetAnnouncementsParams` - 定义:查询参数类型(status?, type?, page?, pageSize?) - 被使用:`getAnnouncements`, `getAnnouncementsAction` ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `announcement-list.tsx` | 公告列表(支持状态筛选) | | `announcement-card.tsx` | 单条公告卡片 | | `announcement-form.tsx` | 创建/编辑表单 | | `announcement-detail.tsx` | 详情查看(含发布/归档/删除操作) | | `admin-announcements-view.tsx` | 管理端公告视图(列表+创建对话框) | --- ## 模块:files ### 模块职责 文件上传与管理:通过 API 路由处理文件上传(保存到 `public/uploads/YYYY-MM/`),记录文件元数据到 DB,支持按关联资源(exam/textbook/question/announcement)多态查询、下载与删除。 ### 模块路径 `src/modules/files` ### 导出函数 (data-access.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `createFileAttachment` | `(data: CreateFileAttachmentInput) => Promise` | `app/api/upload/route.ts` | | `getFileAttachment` | `(id: string) => Promise` | `app/api/files/[id]/route.ts` | | `getFileAttachmentsByTarget` | `(targetType: string, targetId: string) => Promise` | 按关联资源查询文件列表 | | `getFileAttachmentsByUploader` | `(uploaderId: string) => Promise` | 按上传者查询文件列表 | | `getAllFileAttachments` | `(limit?: number) => Promise` | `app/(dashboard)/admin/files/page.tsx` | | `deleteFileAttachment` | `(id: string) => Promise` | `app/api/files/[id]/route.ts` | | `deleteFileAttachments` | `(ids: string[]) => Promise` | `app/api/files/batch-delete/route.ts`(批量删除 DB 记录,失败回退逐条删除) | | `getFileAttachmentsWithFilters` | `(params: FileAttachmentQueryParams) => Promise` | 管理员文件筛选(mimeType 精确/前缀匹配 + originalName/filename 模糊搜索 + limit/offset 分页) | | `getFileStats` | `() => Promise` | 文件统计(总数、总大小、按 mimeType 分组) | | `getFileAttachmentsByIds` | `(ids: string[]) => Promise` | `app/api/files/batch-delete/route.ts`(批量删除前获取磁盘路径) | ### 类型/接口 (types.ts) #### `FileAttachment` - 定义:文件附件完整类型(含 id, filename, originalName, mimeType, size, storagePath, url, uploaderId, targetType, targetId, createdAt) - 被使用:files/components, data-access, API 路由 #### `FileUploadResult` - 定义:上传成功返回类型 `{ id, url, filename, originalName, size, mimeType }` - 被使用:`app/api/upload/route.ts` 响应, file-upload.tsx 回调 #### `FileTargetType` - 定义:`"exam" | "textbook" | "question" | "announcement"` - 被使用:types.FileAttachment.targetType, file-upload.tsx #### `CreateFileAttachmentInput` - 定义:创建文件附件记录的输入类型 - 被使用:`createFileAttachment` ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `file-upload.tsx` | 文件上传组件(拖拽+点击上传,进度条,文件类型校验,调用 `/api/upload`) | | `file-list.tsx` | 文件列表展示(图标、文件名、大小、下载链接、删除按钮) | | `file-preview.tsx` | 文件预览(图片直接预览,PDF iframe,其他下载) | | `file-icon.tsx` | 根据 MIME 类型显示不同图标与颜色 | | `admin-files-view.tsx` | 管理端文件视图(上传+列表+删除) | ### API 路由 #### `POST /api/upload` - 文件:`app/api/upload/route.ts` - 功能:接收 `multipart/form-data`,保存文件到 `public/uploads/YYYY-MM/cuid.ext`,写入 DB,返回 `FileUploadResult` - 权限:`requireAuth()`(需登录) - 限制:10MB、白名单 MIME 类型 #### `GET /api/files/[id]` - 文件:`app/api/files/[id]/route.ts` - 功能:获取文件元数据 - 权限:`requireAuth()` #### `DELETE /api/files/[id]` - 文件:`app/api/files/[id]/route.ts` - 功能:删除文件(DB 记录 + 磁盘文件) - 权限:`requirePermission(FILE_DELETE)` #### `POST /api/files/batch-delete` - 文件:`app/api/files/batch-delete/route.ts` - 功能:批量删除文件(先查文件记录,通过 `storageProvider.delete` 删除磁盘文件静默失败,再调用 `deleteFileAttachments` 删除 DB 记录) - 权限:`requirePermission(FILE_DELETE)` - 请求体:JSON `{ ids: string[] }` - 响应:`{ success, message, deletedCount, failedIds }` #### `POST /api/export` - 文件:`app/api/export/route.ts` - 功能:Excel 导出(grades/users/attendance),按 `type` 分发到 `exportGradeRecordsToExcel`/`exportUsersToExcel`,返回 `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` 二进制流 - 权限:`requireAuth()`(需登录) - 请求体:JSON `{ type: "grades" | "users" | "attendance", params?: Record }` #### `POST /api/import` - 文件:`app/api/import/route.ts` - 功能:Excel 解析预览(不写 DB),接收 Excel 文件,调用 `parseExcel` 返回 sheets 预览数据(实际导入由 `users/actions.importUsersAction` 完成) - 权限:`requirePermission(USER_MANAGE)` - 请求体:`multipart/form-data`,字段 `file` - 限制:仅 `.xlsx`/`.xls`,10MB 上限 --- ## 模块:grades ### 模块职责 成绩分析模块:成绩录入(单条/批量)、查询、统计报表(均分、中位数、标准差、及格率、优秀率、班级排名)、Excel 导出(成绩明细+统计汇总/班级多科目横向对比)、趋势对比分析(成绩趋势、班级对比、科目对比、分数分布、排名趋势)。 ### 模块路径 `src/modules/grades` ### 导出函数 (actions.ts) > 所有 manage actions 均使用 `requirePermission(GRADE_RECORD_MANAGE)` 进行权限校验,read actions 使用 `requirePermission(GRADE_RECORD_READ)`。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `createGradeRecordAction` | GRADE_RECORD_MANAGE | 创建单条成绩记录 | | `batchCreateGradeRecordsAction` | GRADE_RECORD_MANAGE | 批量录入成绩(班级+科目+考试,表格形式) | | `updateGradeRecordAction` | GRADE_RECORD_MANAGE | 更新成绩记录 | | `deleteGradeRecordAction` | GRADE_RECORD_MANAGE | 删除成绩记录 | | `getGradeRecordsAction` | GRADE_RECORD_READ | 查询成绩列表(按 scope 过滤) | | `getClassGradeStatsAction` | GRADE_RECORD_READ | 获取班级成绩统计 | | `getStudentGradeSummaryAction` | GRADE_RECORD_READ | 获取学生成绩汇总(学生/家长只能查自己/子女) | | `getClassRankingAction` | GRADE_RECORD_READ | 获取班级排名 | | `getGradeRecordByIdAction` | GRADE_RECORD_READ | 获取单条成绩记录 | | `exportGradesAction` | GRADE_RECORD_READ | 导出成绩到 Excel(detail=成绩明细+统计汇总,class=班级多科目横向对比总表),返回 base64 buffer | ### 导出函数 (actions-analytics.ts) > 所有 analytics actions 均使用 `requirePermission(GRADE_RECORD_READ)` 进行权限校验。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `getGradeTrendAction` | GRADE_RECORD_READ | 获取成绩趋势(按学生/科目/学期,返回归一化分数趋势点) | | `getClassComparisonAction` | GRADE_RECORD_READ | 获取班级对比(同年级各班的均分/及格率/优秀率) | | `getSubjectComparisonAction` | GRADE_RECORD_READ | 获取科目对比(同班级各科目雷达图数据) | | `getGradeDistributionAction` | GRADE_RECORD_READ | 获取分数分布(90-100/80-89/70-79/60-69/<60 各区间人数) | | `getRankingTrendAction` | GRADE_RECORD_READ | 获取排名趋势(学生历次考试排名变化,含 DataScope 二次校验) | ### 导出函数 (data-access.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `getGradeRecords` | `(params: GradeQueryParams & { scope: DataScope; currentUserId?: string }) => Promise` | teacher/grades/page.tsx, getGradeRecordsAction | | `getGradeRecordById` | `(id: string) => Promise` | getGradeRecordByIdAction | | `createGradeRecord` | `(data: CreateGradeRecordInput, recordedBy: string) => Promise` | createGradeRecordAction | | `batchCreateGradeRecords` | `(data: BatchCreateGradeRecordInput, recordedBy: string) => Promise` | batchCreateGradeRecordsAction | | `updateGradeRecord` | `(id: string, data: UpdateGradeRecordInput) => Promise` | updateGradeRecordAction | | `deleteGradeRecord` | `(id: string) => Promise` | deleteGradeRecordAction | | `getClassGradeStats` | `(classId, subjectId?, examId?) => Promise` | getClassGradeStatsAction | | `getStudentGradeSummary` | `(studentId: string) => Promise` | getStudentGradeSummaryAction, student/grades, parent/grades | | `getClassRanking` | `(classId, subjectId?, examId?) => Promise` | getClassRankingAction, teacher/grades/stats | | `getClassStudentsForEntry` | `(classId: string) => Promise>` | teacher/grades/entry | | `getClassGradeStatsWithMeta` | `(classId, subjectId?, examId?) => Promise` | teacher/grades/stats | ### 导出函数 (data-access-analytics.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `getGradeTrend` | `(params: { studentId; subjectId?; semester?; scope: DataScope }) => Promise` | getGradeTrendAction, teacher/grades/analytics | | `getClassComparison` | `(params: { gradeId; subjectId; examId?; scope: DataScope }) => Promise` | getClassComparisonAction, teacher/grades/analytics | | `getSubjectComparison` | `(params: { classId; examId?; semester?; scope: DataScope }) => Promise` | getSubjectComparisonAction, teacher/grades/analytics | | `getGradeDistribution` | `(params: { classId; subjectId?; examId?; scope: DataScope }) => Promise` | getGradeDistributionAction, teacher/grades/analytics | ### 导出函数 (data-access-ranking.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `getRankingTrend` | `(studentId: string, subjectId?, semester?) => Promise` | getRankingTrendAction | ### 导出函数 (export.ts) #### `exportGradeRecordsToExcel` - 签名:`(params: { classId: string; subjectId?: string; examId?: string; scope: DataScope }) => Promise` - 功能:导出成绩单(Sheet1 成绩明细,Sheet2 统计汇总:均分/中位数/最高分/最低分/标准差/及格率/优秀率/参考人数) - 依赖:`shared/lib/excel.exportToExcel`, `data-access.getGradeRecords`, `data-access.getClassGradeStats` - 被使用:`exportGradesAction`, `app/api/export/route.ts` #### `exportClassGradeReportToExcel` - 签名:`(params: { classId: string; scope: DataScope }) => Promise` - 功能:导出班级成绩总表(多科目横向对比,含总分/平均分/排名列) - 依赖:`shared/db`, `shared/lib/excel.exportToExcel`, `data-access.getGradeRecords` - 被使用:`exportGradesAction` #### `formatDateForFile` - 签名:`(d?: Date) => string` - 功能:格式化日期为 `YYYY-MM-DD` 用于文件名 - 依赖:无 - 被使用:`exportGradesAction` ### Schema (schema.ts) #### `CreateGradeRecordSchema` - 类型:Zod schema - 定义:创建成绩记录的校验 schema(studentId, classId, subjectId, examId?, title, score, fullScore?, type?, semester?, remark?) - 被使用:`createGradeRecordAction` #### `BatchCreateGradeRecordSchema` - 类型:Zod schema - 定义:批量录入成绩的校验 schema(classId, subjectId, examId?, title, fullScore?, type?, semester?, records[{ studentId, score, remark? }]) - 被使用:`batchCreateGradeRecordsAction` #### `UpdateGradeRecordSchema` - 类型:Zod schema - 定义:更新成绩记录的校验 schema(title?, score?, fullScore?, type?, semester?, remark?, examId?) - 被使用:`updateGradeRecordAction` ### 类型/接口 (types.ts) #### `GradeRecord` - 定义:成绩记录完整类型(含 id, studentId, classId, subjectId, examId, title, score, fullScore, type, semester, recordedBy, remark, createdAt, updatedAt) - 被使用:grades/data-access, grades/components #### `GradeRecordListItem` - 定义:成绩列表项类型(含 studentName, className, subjectName, recorderName 等关联名称) - 被使用:grades/components, teacher/grades #### `GradeStats` - 定义:成绩统计类型(average, median, max, min, stdDev, passRate, excellentRate, count) - 被使用:grades/components, teacher/grades/stats #### `ClassGradeStats` - 定义:班级成绩统计类型(classId, className, stats: GradeStats, studentCount) - 被使用:grades/components, teacher/grades/stats #### `StudentGradeSummary` - 定义:学生成绩汇总类型(studentId, studentName, records[], averageScore, rank) - 被使用:grades/components, student/grades, parent/grades #### `ClassRankingItem` - 定义:班级排名项类型(studentId, studentName, averageScore, rank, recordCount) - 被使用:grades/components, teacher/grades/stats #### `GradeRecordType` - 定义:`"exam" | "quiz" | "homework" | "other"` - 被使用:grades/data-access, grades/components #### `GradeQueryParams` - 定义:查询参数类型(classId?, subjectId?, studentId?, type?, semester?, examId?) - 被使用:`getGradeRecords`, `getGradeRecordsAction` #### `GradeTrendPoint` / `GradeTrendResult` - 定义:成绩趋势类型(Point: date, title, score, fullScore, normalizedScore, type;Result: label, points[], averageScore) - 被使用:getGradeTrend, getGradeTrendAction, grade-trend-chart #### `ClassComparisonItem` - 定义:班级对比项类型(classId, className, averageScore, passRate, excellentRate, studentCount) - 被使用:getClassComparison, getClassComparisonAction, class-comparison-chart #### `SubjectComparisonItem` - 定义:科目对比项类型(subjectId, subjectName, averageScore, passRate, excellentRate) - 被使用:getSubjectComparison, getSubjectComparisonAction, subject-comparison-chart #### `GradeDistributionBucket` / `GradeDistributionResult` - 定义:分数分布类型(Bucket: label, min, max, count, percentage;Result: buckets[], totalCount) - 被使用:getGradeDistribution, getGradeDistributionAction, grade-distribution-chart #### `RankingTrendPoint` / `RankingTrendResult` - 定义:排名趋势类型(Point: title, date, rank, totalStudents, score;Result: studentName, points[]) - 被使用:getRankingTrend, getRankingTrendAction ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `grade-record-form.tsx` | 单条成绩录入表单 | | `batch-grade-entry.tsx` | 批量录入界面(选择班级+科目+考试,表格形式录入每个学生分数) | | `grade-record-list.tsx` | 成绩列表(含删除功能) | | `grade-stats-card.tsx` | 统计卡片(均分、中位数、及格率、优秀率等) | | `grade-query-filters.tsx` | 查询筛选器(班级、科目、考试类型、学期) | | `student-grade-summary.tsx` | 学生成绩汇总视图 | | `class-grade-report.tsx` | 班级成绩报表(含统计+排名) | | `export-button.tsx` | 成绩导出按钮(DropdownMenu 选择 detail/class 报表类型,调用 exportGradesAction 并触发浏览器下载) | | `grade-trend-chart.tsx` | 成绩趋势折线图(recharts LineChart,归一化分数 0-100) | | `class-comparison-chart.tsx` | 班级对比柱状图(recharts BarChart,均分/及格率/优秀率) | | `subject-comparison-chart.tsx` | 科目对比雷达图(recharts RadarChart) | | `grade-distribution-chart.tsx` | 分数分布柱状图(recharts BarChart,彩色区间 90-100/80-89/70-79/60-69/<60) | --- ## 模块:course-plans ### 模块职责 课程计划管理:创建、编辑、删除课程计划(含周计划条目),管理员可管理全部,教师/学生/年级主任/教务主任可查看。 ### 导出函数 (actions.ts) #### `createCoursePlanAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 权限:`requirePermission(COURSE_PLAN_MANAGE)` - 功能:创建课程计划 - 依赖:`requirePermission`, `shared/db`, `data-access.createCoursePlan` - 被以下模块使用:course-plan-form.tsx #### `updateCoursePlanAction` - 签名:`(id: string, prevState: ActionState | null, formData: FormData) => Promise>` - 权限:`requirePermission(COURSE_PLAN_MANAGE)` - 功能:更新课程计划 - 依赖:`requirePermission`, `shared/db`, `data-access.updateCoursePlan` - 被以下模块使用:course-plan-form.tsx #### `deleteCoursePlanAction` - 签名:`(id: string) => Promise>` - 权限:`requirePermission(COURSE_PLAN_MANAGE)` - 功能:删除课程计划 - 依赖:`requirePermission`, `shared/db`, `data-access.deleteCoursePlan` - 被以下模块使用:course-plan-detail.tsx #### `getCoursePlansAction` - 签名:`(params?: GetCoursePlansParams) => Promise>` - 权限:`requirePermission(COURSE_PLAN_READ)` - 功能:获取课程计划列表 - 依赖:`requirePermission`, `data-access.getCoursePlans` #### `getCoursePlanAction` - 签名:`(id: string) => Promise>` - 权限:`requirePermission(COURSE_PLAN_READ)` - 功能:获取课程计划详情(含周计划条目) - 依赖:`requirePermission`, `data-access.getCoursePlanById` #### `createCoursePlanItemAction` - 签名:`(prevState: ActionState | null, formData: FormData) => Promise>` - 权限:`requirePermission(COURSE_PLAN_MANAGE)` - 功能:创建周计划条目 - 依赖:`requirePermission`, `shared/db`, `data-access.createCoursePlanItem` - 被以下模块使用:course-plan-item-editor.tsx #### `updateCoursePlanItemAction` - 签名:`(id: string, prevState: ActionState | null, formData: FormData) => Promise>` - 权限:`requirePermission(COURSE_PLAN_MANAGE)` - 功能:更新周计划条目 - 依赖:`requirePermission`, `shared/db`, `data-access.updateCoursePlanItem` - 被以下模块使用:course-plan-item-editor.tsx #### `deleteCoursePlanItemAction` - 签名:`(id: string) => Promise>` - 权限:`requirePermission(COURSE_PLAN_MANAGE)` - 功能:删除周计划条目 - 依赖:`requirePermission`, `shared/db`, `data-access.deleteCoursePlanItem` - 被以下模块使用:course-plan-item-editor.tsx #### `toggleCoursePlanItemCompletedAction` - 签名:`(id: string, completed: boolean) => Promise>` - 权限:`requirePermission(COURSE_PLAN_MANAGE)` - 功能:切换周计划条目完成状态 - 依赖:`requirePermission`, `shared/db`, `data-access.updateCoursePlanItem` - 被以下模块使用:course-plan-detail.tsx ### 导出函数 (data-access.ts) #### `getCoursePlans` - 签名:`(params?: GetCoursePlansParams) => Promise` - 功能:查询课程计划列表(含班级/科目/教师名称,支持按 classId/teacherId/subjectId/status 过滤) - 依赖:`shared.db`, `shared.db.schema.coursePlans/classes/subjects/users` - 被以下模块使用:admin/course-plans/page.tsx, teacher/course-plans/page.tsx #### `getCoursePlanById` - 签名:`(id: string) => Promise` - 功能:按 ID 获取课程计划详情(含周计划条目列表) - 依赖:`shared.db`, `shared.db.schema.coursePlans/coursePlanItems/classes/subjects/users` - 被以下模块使用:admin/course-plans/[id]/page.tsx, teacher/course-plans/[id]/page.tsx #### `createCoursePlan` - 签名:`(data: CreateCoursePlanInput, createdBy: string) => Promise` - 功能:创建课程计划记录 - 依赖:`shared.db`, `shared.db.schema.coursePlans`, `@paralleldrive/cuid2` - 被以下模块使用:createCoursePlanAction #### `updateCoursePlan` - 签名:`(id: string, data: Partial) => Promise` - 功能:更新课程计划(部分字段) - 依赖:`shared.db`, `shared.db.schema.coursePlans` - 被以下模块使用:updateCoursePlanAction #### `deleteCoursePlan` - 签名:`(id: string) => Promise` - 功能:删除课程计划(级联删除周计划条目) - 依赖:`shared.db`, `shared.db.schema.coursePlans` - 被以下模块使用:deleteCoursePlanAction #### `createCoursePlanItem` / `updateCoursePlanItem` / `deleteCoursePlanItem` - 签名:`(data: CreateCoursePlanItemInput) => Promise` / `(id: string, data: Partial) => Promise` / `(id: string) => Promise` - 功能:周计划条目 CRUD - 依赖:`shared.db`, `shared.db.schema.coursePlanItems`, `@paralleldrive/cuid2` - 被以下模块使用:createCoursePlanItemAction, updateCoursePlanItemAction, deleteCoursePlanItemAction, toggleCoursePlanItemCompletedAction #### `reorderCoursePlanItems` - 签名:`(planId: string, items: ReorderCoursePlanItemInput[]) => Promise` - 功能:重排周计划条目顺序 - 依赖:`shared.db`, `shared.db.schema.coursePlanItems` #### `getSubjectOptions` - 签名:`() => Promise<{id:string;name:string}[]>` - 功能:获取科目选项列表 - 依赖:`shared.db`, `shared.db.schema.subjects` - 被以下模块使用:admin/course-plans/create/page.tsx, admin/course-plans/[id]/edit/page.tsx ### Zod Schema (schema.ts) | Schema | 用途 | |--------|------| | `CreateCoursePlanSchema` | 创建课程计划校验(classId, subjectId, teacherId, academicYearId?, semester?, totalHours?, weeklyHours?, startDate?, endDate?, syllabus?, objectives?, status?) | | `UpdateCoursePlanSchema` | 更新课程计划校验(所有字段可选,含 completedHours?) | | `CreateCoursePlanItemSchema` | 创建周计划条目校验(planId, week, topic, content?, hours?, textbookChapter?, notes?) | | `UpdateCoursePlanItemSchema` | 更新周计划条目校验(所有字段可选,含 isCompleted?, completedAt?) | ### 类型/接口 (types.ts) | 类型 | 定义 | |------|------| | `CoursePlan` | 课程计划基础接口 | | `CoursePlanItem` | 周计划条目接口 | | `CoursePlanListItem` | = CoursePlan & { className, subjectName, teacherName } | | `CoursePlanWithItems` | = CoursePlanListItem & { items: CoursePlanItem[] } | | `CoursePlanStatus` | `"planning" \| "active" \| "completed" \| "paused"` | | `CoursePlanSemester` | `"1" \| "2"` | | `GetCoursePlansParams` | { classId?, teacherId?, subjectId?, status? } | | `ReorderCoursePlanItemInput` | { id, week } | ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `course-plan-progress.tsx` | 进度条组件(completedHours/totalHours 百分比) | | `course-plan-list.tsx` | 课程计划列表(支持状态筛选,URL 同步) | | `course-plan-form.tsx` | 创建/编辑表单(班级、科目、教师、学年、学期、状态、课时、日期、大纲、目标) | | `course-plan-item-editor.tsx` | 周计划条目编辑器(Dialog,支持创建/编辑/删除/切换完成) | | `course-plan-detail.tsx` | 详情视图(计划信息、进度、大纲、目标、周计划表格、删除确认) | --- ## 模块:parent ### 模块职责 家长端仪表盘:聚合家长关联子女的学习数据(课表、作业、成绩、班级),支持多子女切换查看。家长通过 `parentStudentRelations` 表关联子女,DataScope 解析为 `children` 类型。 ### 模块路径 `src/modules/parent` ### 导出函数 (data-access.ts) > 所有函数使用 `cache()` 包装以实现请求级缓存。本模块仅用于读操作聚合,无 Server Action,权限校验在页面层通过 `requireAuth()` 完成。 | 函数 | 签名 | 被使用 | |------|------|--------| | `getChildren` | `(parentId: string) => Promise` | `getParentDashboardData` 内部, parent/children/[studentId] 页面 | | `getChildBasicInfo` | `(studentId: string, relation?: string \| null) => Promise` | `getChildDashboardData` 内部 | | `getChildDashboardData` | `(studentId: string, relation?: string \| null) => Promise` | parent/children/[studentId]/page.tsx, `getParentDashboardData` 内部 | | `getParentDashboardData` | `(parentId: string) => Promise` | parent/dashboard/page.tsx | #### `getChildren` - 依赖:`shared/db` (parentStudentRelations 表) - 功能:查询指定家长的所有子女关联记录,按 createdAt 升序 #### `getChildBasicInfo` - 依赖:`shared/db` (users, grades, classEnrollments, classes 表) - 功能:聚合子女基础信息(姓名、邮箱、头像、年级名称、班级名称、与家长关系) #### `getChildDashboardData` - 依赖:`getChildBasicInfo`, `classes/data-access.getStudentClasses`, `classes/data-access.getStudentSchedule`, `homework/data-access.getStudentHomeworkAssignments`, `homework/data-access.getStudentDashboardGrades`, `grades/data-access.getStudentGradeSummary` - 功能:并行聚合单个子女的完整仪表盘数据(基础信息、已加入班级、今日课表、作业概览、成绩趋势、成绩汇总) #### `getParentDashboardData` - 依赖:`shared/db` (users 表), `getChildren`, `getChildDashboardData` - 功能:聚合家长名称与所有子女的仪表盘数据 ### 类型/接口 (types.ts) #### `ParentChildRelation` - 定义:家长-子女关联记录类型(id, parentId, studentId, relation, createdAt) - 被使用:`getChildren`, `getParentDashboardData` #### `ChildBasicInfo` - 定义:子女基础信息类型(id, name, email, image, gradeName, className, classId, relation) - 被使用:`getChildBasicInfo`, `ChildDashboardData.basicInfo` #### `ChildScheduleItem` - 定义:子女今日课表项类型(id, classId, className, course, startTime, endTime, location) - 被使用:`ChildDashboardData.todaySchedule`, child-schedule-card.tsx #### `ChildHomeworkSummary` - 定义:子女作业概览类型(pendingCount, submittedCount, gradedCount, overdueCount, recentAssignments) - 被使用:`ChildDashboardData.homeworkSummary`, child-homework-summary.tsx #### `ChildDashboardData` - 定义:单个子女仪表盘数据类型(basicInfo, enrolledClasses, todaySchedule, homeworkSummary, gradeTrend, gradeSummary) - 依赖:`homework/types.StudentDashboardGradeProps`, `homework/types.StudentHomeworkAssignmentListItem`, `classes/types.StudentEnrolledClass`, `grades/types.StudentGradeSummary` - 被使用:`getChildDashboardData`, `ParentDashboardData.children`, 所有 child-* 组件 #### `ParentDashboardData` - 定义:家长仪表盘数据类型(parentName, children: ChildDashboardData[]) - 被使用:`getParentDashboardData`, parent-dashboard.tsx ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `parent-dashboard.tsx` | 主容器组件(问候语、子女卡片网格、空状态) | | `child-card.tsx` | 子女卡片(头像、姓名、班级、待完成/逾期/平均分统计,点击跳转详情) | | `child-detail-header.tsx` | 子女详情页头部(返回按钮、头像、姓名、班级、年级、关系) | | `child-detail-panel.tsx` | 子女详情面板容器(组合 homework/grade/schedule 三个子组件) | | `child-homework-summary.tsx` | 子女作业概览(pending/submitted/graded/overdue 统计 + 最近作业列表) | | `child-grade-summary.tsx` | 子女成绩概览(Recharts 折线图趋势 + 最新分数 + 班级排名 + 最近成绩列表,"use client") | | `child-schedule-card.tsx` | 子女今日课表卡片(课程、时间、地点、班级) | --- ## 模块:messaging ### 模块职责 站内消息系统:用户间私信收发(支持回复链)、站内通知(多态类型:message/announcement/homework/grade),SiteHeader 通知下拉菜单展示未读数。 ### 模块路径 `src/modules/messaging` ### 导出函数 (actions.ts) > send actions 使用 `requirePermission(MESSAGE_SEND)`,read actions 使用 `requirePermission(MESSAGE_READ)`,delete actions 使用 `requirePermission(MESSAGE_DELETE)`,通知读取使用 `requireAuth()`。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `sendMessageAction` | MESSAGE_SEND | 发送消息(同时为收件人创建通知;支持 parentMessageId 回复) | | `markMessageAsReadAction` | MESSAGE_READ | 标记消息已读(设置 readAt) | | `deleteMessageAction` | MESSAGE_DELETE | 删除消息(仅发送者或接收者可删) | | `getMessagesAction` | MESSAGE_READ | 获取消息列表(收件箱/已发送,分页) | | `getMessageDetailAction` | MESSAGE_READ | 获取消息详情(含回复线程) | | `getRecipientsAction` | MESSAGE_SEND | 获取可发送对象列表(按 DataScope 过滤) | | `getNotificationsAction` | requireAuth | 获取当前用户通知列表(分页) | | `markNotificationAsReadAction` | requireAuth | 标记单条通知已读 | | `markAllNotificationsAsReadAction` | requireAuth | 标记所有通知已读 | | `getNotificationPreferencesAction` | requireAuth | 获取当前用户通知偏好(无记录时自动创建默认记录) | | `updateNotificationPreferencesAction` | requireAuth | 更新(upsert)当前用户通知偏好(从 FormData 解析 checkbox "on" 为布尔值) | ### 导出函数 (data-access.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `getMessages` | `(userId: string, params?: { type?, page?, pageSize? }) => Promise>` | messages 页面 | | `getMessageById` | `(id: string, userId: string) => Promise` | 消息详情页 | | `getMessageThread` | `(rootId: string, userId: string) => Promise` | 消息详情页(回复链) | | `createMessage` | `(input: CreateMessageInput) => Promise` | sendMessageAction | | `markMessageAsRead` | `(id: string, userId: string) => Promise` | markMessageAsReadAction, 详情页自动已读 | | `deleteMessage` | `(id: string, userId: string) => Promise` | deleteMessageAction | | `getUnreadMessageCount` | `(userId: string) => Promise` | 待扩展 | | `getNotifications` | `(userId: string, params?: { page?, pageSize? }) => Promise>` | 通知列表/下拉菜单 | | `createNotification` | `(input: CreateNotificationInput) => Promise` | sendMessageAction(内部调用) | | `markNotificationAsRead` | `(id: string, userId: string) => Promise` | markNotificationAsReadAction | | `markAllNotificationsAsRead` | `(userId: string) => Promise` | markAllNotificationsAsReadAction | | `getUnreadNotificationCount` | `(userId: string) => Promise` | 待扩展 | | `getRecipients` | `(ctx: AuthContext) => Promise` | compose 页面(按 DataScope 过滤) | ### 导出函数 (notification-preferences.ts) > 文件标记 `"server-only"`,使用 React `cache` 包装读取函数。 #### `getNotificationPreferences` - 签名:`getNotificationPreferences(userId: string): Promise`(cache 包装) - 功能:获取用户通知偏好;若用户无记录则自动创建一条默认记录(pushEnabled/homeworkNotifications/gradeNotifications/announcementNotifications/messageNotifications/attendanceNotifications 默认 true,emailEnabled/smsEnabled 默认 false),并发冲突时回退到查询 - 依赖:`shared/db` (notificationPreferences 表), `@paralleldrive/cuid2`, `react` (cache) - 被使用:`getNotificationPreferencesAction`, `app/(dashboard)/settings/page.tsx` #### `upsertNotificationPreferences` - 签名:`upsertNotificationPreferences(userId: string, input: UpdateNotificationPreferencesInput): Promise` - 功能:upsert 语义更新通知偏好(存在则部分字段更新,不存在则插入;未提供的字段保留原值或使用默认值) - 依赖:`shared/db` (notificationPreferences 表), `@paralleldrive/cuid2` - 被使用:`updateNotificationPreferencesAction` ### Schema (schema.ts) #### `SendMessageSchema` - 类型:Zod schema - 定义:发送消息校验 schema(receiverId, subject?, content, parentMessageId?) - 被使用:`sendMessageAction` ### 类型/接口 #### `Message` - 定义:消息完整类型(含 id, senderId, receiverId, subject, content, isRead, readAt, parentMessageId, createdAt, senderName, receiverName) - 被使用:messaging/components, 页面 #### `MessageListItem` - 定义:消息列表项类型(同 Message 精简版) - 被使用:列表页 #### `MessageThread` - 定义:消息线程类型(root + replies) - 被使用:详情页 #### `Notification` - 定义:通知完整类型(含 id, userId, type, title, content, link, isRead, createdAt) - 被使用:notification-dropdown, notification-list #### `NotificationListItem` - 定义:通知列表项类型(同 Notification) - 被使用:列表页 #### `NotificationType` - 定义:`"message" | "announcement" | "homework" | "grade"` - 被使用:data-access, components #### `MessageType` - 定义:`"inbox" | "sent"` - 被使用:getMessages 参数 #### `CreateMessageInput` / `CreateNotificationInput` - 定义:创建消息/通知的输入类型 - 被使用:data-access.createMessage, createNotification #### `RecipientOption` - 定义:`{ id: string; name: string }` - 被使用:compose 页面下拉选项 #### `NotificationPreferences` - 定义:通知偏好完整类型(含 id, userId, emailEnabled, smsEnabled, pushEnabled, homeworkNotifications, gradeNotifications, announcementNotifications, messageNotifications, attendanceNotifications, createdAt, updatedAt) - 被使用:`getNotificationPreferences`, `upsertNotificationPreferences`, `getNotificationPreferencesAction`, `updateNotificationPreferencesAction`, settings/components/notification-preferences-form.tsx #### `UpdateNotificationPreferencesInput` - 定义:更新通知偏好的输入类型(所有字段可选:emailEnabled?, smsEnabled?, pushEnabled?, homeworkNotifications?, gradeNotifications?, announcementNotifications?, messageNotifications?, attendanceNotifications?;未提供则保留原值) - 被使用:`upsertNotificationPreferences`, `updateNotificationPreferencesAction` ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `message-list.tsx` | 消息列表(收件箱/已发送 Tab 切换,已读/未读标记,usePermission 控制"写消息"按钮) | | `message-detail.tsx` | 消息详情(含回复线程、回复/删除操作,AlertDialog 删除确认,usePermission 控制按钮可见性) | | `message-compose.tsx` | 写消息表单(收件人 Select、主题 Input、内容 Textarea,支持回复模式) | | `notification-dropdown.tsx` | SiteHeader 通知下拉菜单(Bell 图标 + 未读数 Badge,滚动列表,标记已读,查看全部链接) | | `notification-list.tsx` | 通知完整列表(全部标记已读、单条标记已读、查看链接) | --- ## 模块:notifications ### 模块职责 通知渠道集成层:基于用户通知偏好(`notification_preferences` 表)将通知分发到站内消息 / SMS / 微信公众号 / 邮件多渠道。所有渠道实现统一 `NotificationChannelSender` 接口,dispatcher 按偏好并行发送。支持 Mock 模式(开发环境无需外部服务即可运行)。 ### 模块路径 `src/modules/notifications` ### 依赖关系 - 依赖 `shared`(db, auth-guard, types) - 依赖 `messaging`(复用 `notification-preferences.getNotificationPreferences` 和 `data-access.createNotification`) - 所有渠道文件首行 `import "server-only"`,外部 SDK 使用动态 import ### 导出函数 (actions.ts) > 使用 `requirePermission(MESSAGE_SEND)` 校验权限(项目无独立 NOTIFICATION_SEND 权限点,复用 MESSAGE_SEND)。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `sendNotificationAction` | MESSAGE_SEND | 发送通知给指定用户(按偏好多渠道分发) | | `sendClassNotificationAction` | MESSAGE_SEND | 发送班级通知(批量发送给班级所有学生;教师只能给自己所教班级发送,通过 dataScope 校验) | ### 导出函数 (dispatcher.ts) > 文件标记 `"server-only"`。 #### `sendNotification` - 签名:`sendNotification(payload: NotificationPayload): Promise` - 功能:读取用户通知偏好 + 联系方式,按偏好选择渠道(in_app 总是启用;sms 需 smsEnabled+phone;email 需 emailEnabled+email;wechat 需 pushEnabled+openId),并行发送,记录日志 - 依赖:`data-access.getUserNotificationPreferences`, `data-access.getUserContactInfo`, `data-access.logNotificationSendBatch`, 各渠道 `createXxxSender` - 被使用:`sendNotificationAction`, `sendClassNotificationAction` #### `sendBatchNotifications` - 签名:`sendBatchNotifications(payloads: NotificationPayload[]): Promise` - 功能:批量发送通知(每个用户独立选择渠道,并行发送) - 依赖:`sendNotification` - 被使用:`sendClassNotificationAction` ### 导出函数 (data-access.ts) > 文件标记 `"server-only"`。 | 函数 | 签名 | 核心功能 | |------|------|---------| | `getUserNotificationPreferences` | `(userId: string) => Promise` | 获取用户通知偏好(复用 messaging.notification-preferences) | | `getUserContactInfo` | `(userId: string) => Promise` | 获取用户联系方式(phone/email;wechatOpenId 暂不支持,users 表无此字段;React cache 包装) | | `logNotificationSend` | `(result: ChannelSendResult) => void` | 记录单条发送日志(当前 console.info;未来可扩展 notification_logs 表) | | `logNotificationSendBatch` | `(results: ChannelSendResult[]) => void` | 批量记录发送日志 | ### 渠道实现 (channels/) > 所有渠道文件首行 `import "server-only"`,外部 SDK 使用动态 import 避免增加构建体积。 | 文件 | 渠道 | 工厂函数 | 说明 | |------|------|---------|------| | `sms-channel.ts` | sms | `createSmsSender()` | 支持 aliyun/tencent/mock(根据 `SMS_PROVIDER` 环境变量选择);模板变量替换 title/content | | `wechat-channel.ts` | wechat | `createWechatSender()` | 微信公众号模板消息;access_token 带缓存(提前 5 分钟刷新);配置完整用真实发送器,否则 Mock | | `email-channel.ts` | email | `createEmailSender()` | Nodemailer SMTP;HTML 模板按 type 着色;配置 EMAIL_HOST 启用,否则 Mock | | `in-app-channel.ts` | in_app | `createInAppSender()` | 复用 messaging.data-access.createNotification 写入 message_notifications 表;总是启用 | | `types.ts` | - | - | 渠道接口定义(NotificationChannelSender, ChannelRecipient) | ### 环境变量 | 变量 | 默认值 | 说明 | |------|--------|------| | `SMS_PROVIDER` | `mock` | SMS 渠道 provider:aliyun/tencent/mock | | `SMS_ACCESS_KEY_ID` | - | SMS AccessKey ID | | `SMS_ACCESS_KEY_SECRET` | - | SMS AccessKey Secret | | `SMS_SIGN_NAME` | - | SMS 签名 | | `SMS_TEMPLATE_CODE` | - | SMS 模板 ID | | `WECHAT_APP_ID` | - | 微信公众号 AppID | | `WECHAT_APP_SECRET` | - | 微信公众号 AppSecret | | `WECHAT_TEMPLATE_ID` | - | 微信模板消息 ID | | `EMAIL_HOST` | - | SMTP 主机(配置后启用真实发送) | | `EMAIL_PORT` | `587` | SMTP 端口 | | `EMAIL_USER` | - | SMTP 用户名 | | `EMAIL_PASS` | - | SMTP 密码 | | `EMAIL_FROM` | `noreply@example.com` | 发件人地址 | ### 类型/接口 #### `NotificationChannel` - 定义:`"in_app" | "email" | "sms" | "wechat"` - 被使用:所有渠道文件, dispatcher #### `NotificationPayload` - 定义:`{ userId, title, content, type: "info"|"warning"|"error"|"success", metadata?, actionUrl? }` - 被使用:dispatcher, actions, 所有渠道 #### `ChannelSendResult` - 定义:`{ channel, success, messageId?, error?, sentAt }` - 被使用:dispatcher, actions, 所有渠道 #### `NotificationChannelSender`(接口) - 定义:`{ channel: NotificationChannel, send(payload, recipient): Promise, sendBatch(items): Promise }` - 被使用:所有渠道实现, dispatcher #### `ChannelRecipient`(接口) - 定义:`{ userId, phone?, email?, wechatOpenId? }` - 被使用:所有渠道, data-access.getUserContactInfo ### 文档 - `docs/notifications/channels.md`:通知渠道配置说明、Mock 模式、生产环境配置、扩展新渠道指南 --- ## 模块:attendance ### 模块职责 学生考勤管理:教师按班级/日期点名(单条/批量)、查询考勤记录、统计出勤率/迟到率,学生/家长查看本人/子女考勤汇总,管理员查看全校考勤记录。支持班级考勤规则配置(迟到阈值、早退阈值、自动标记)。 ### 模块路径 `src/modules/attendance` ### 导出函数 (actions.ts) > 所有 manage actions 均使用 `requirePermission(ATTENDANCE_MANAGE)` 进行权限校验,read actions 使用 `requirePermission(ATTENDANCE_READ)`。学生/家长在 `getStudentAttendanceAction` 中进行 DataScope 二次校验(class_members 仅查自己,children 仅查子女)。 | 函数 | 权限 | 核心功能 | |------|------|---------| | `recordAttendanceAction` | ATTENDANCE_MANAGE | 创建单条考勤记录 | | `batchRecordAttendanceAction` | ATTENDANCE_MANAGE | 批量点名(班级+日期,表格形式录入每个学生状态) | | `updateAttendanceAction` | ATTENDANCE_MANAGE | 更新考勤记录(状态、备注) | | `deleteAttendanceAction` | ATTENDANCE_MANAGE | 删除考勤记录 | | `getAttendanceAction` | ATTENDANCE_READ | 分页查询考勤记录(按 scope 过滤) | | `getStudentAttendanceAction` | ATTENDANCE_READ | 获取学生考勤汇总(含 DataScope 二次校验) | | `getClassAttendanceStatsAction` | ATTENDANCE_READ | 获取班级考勤统计 | | `getClassAttendanceForDateAction` | ATTENDANCE_READ | 获取班级指定日期考勤(用于点名页加载已有记录) | | `saveAttendanceRulesAction` | ATTENDANCE_MANAGE | 保存班级考勤规则(upsert) | | `getAttendanceRulesAction` | ATTENDANCE_READ | 获取班级考勤规则 | ### 导出函数 (data-access.ts) | 函数 | 签名 | 被使用 | |------|------|--------| | `getAttendanceRecords` | `(params: AttendanceQueryParams & { scope: DataScope; currentUserId?: string }) => Promise` | getAttendanceAction | | `getClassAttendanceForDate` | `(classId: string, date: string) => Promise` | getClassAttendanceForDateAction | | `createAttendanceRecord` | `(data: RecordAttendanceInput, recordedBy: string) => Promise` | recordAttendanceAction | | `batchCreateAttendanceRecords` | `(data: BatchRecordAttendanceInput, recordedBy: string) => Promise` | batchRecordAttendanceAction | | `updateAttendanceRecord` | `(id: string, data: UpdateAttendanceInput) => Promise` | updateAttendanceAction | | `deleteAttendanceRecord` | `(id: string) => Promise` | deleteAttendanceAction | | `getClassStudentsForAttendance` | `(classId: string) => Promise>` | 点名页学生列表 | | `getAttendanceRules` | `(classId?: string) => Promise` | getAttendanceRulesAction | | `upsertAttendanceRules` | `(data: AttendanceRuleInput) => Promise` | saveAttendanceRulesAction | ### 导出函数 (data-access-stats.ts) > 从 data-access.ts 拆分以遵守单文件 ≤300 行规则。 | 函数 | 签名 | 被使用 | |------|------|--------| | `getStudentAttendanceSummary` | `(studentId: string, startDate?: string, endDate?: string) => Promise` | getStudentAttendanceAction, student/attendance, parent/attendance | | `getClassAttendanceStats` | `(classId: string, startDate?: string, endDate?: string) => Promise` | getClassAttendanceStatsAction, teacher/attendance/stats | ### Schema (schema.ts) | Schema | 用途 | |--------|------| | `AttendanceStatusEnum` | 考勤状态枚举(present/absent/late/early_leave/excused) | | `RecordAttendanceSchema` | 单条考勤记录校验(studentId, classId, scheduleId?, date, status, remark?) | | `BatchRecordAttendanceSchema` | 批量考勤校验(records[{ studentId, classId, scheduleId?, date, status, remark? }]) | | `UpdateAttendanceSchema` | 更新考勤校验(status?, remark?, scheduleId?) | | `AttendanceRuleSchema` | 考勤规则校验(classId, lateThresholdMinutes?, earlyLeaveThresholdMinutes?, enableAutoMark?) | ### 类型/接口 (types.ts) | 类型 | 定义 | |------|------| | `AttendanceStatus` | `"present" \| "absent" \| "late" \| "early_leave" \| "excused"` | | `AttendanceRecord` | 考勤记录完整类型 | | `AttendanceListItem` | 列表项类型(含 studentName, className, recorderName) | | `AttendanceStats` | 统计类型(total, present, absent, late, earlyLeave, excused, presentRate, lateRate) | | `StudentAttendanceSummary` | 学生考勤汇总(studentId, studentName, stats, recentRecords) | | `ClassAttendanceSummary` | 班级考勤汇总(classId, className, date, stats, studentRecords) | | `AttendanceRule` | 考勤规则类型(classId, lateThresholdMinutes, earlyLeaveThresholdMinutes, enableAutoMark) | | `AttendanceQueryParams` | 查询参数(classId?, studentId?, date?, startDate?, endDate?, status?, page?, pageSize?) | | `PaginatedAttendanceResult` | 分页结果(items, total, page, pageSize, totalPages) | | `ATTENDANCE_STATUS_LABELS` | 状态中文标签常量 | | `ATTENDANCE_STATUS_COLORS` | 状态颜色常量(用于 Badge) | ### 导出组件 (components/) | 组件文件 | 功能 | |---------|------| | `attendance-sheet.tsx` | 批量点名表单(班级/日期选择器 + 学生表格 + 每行状态 Select + "全部标记到场"按钮) | | `attendance-record-list.tsx` | 考勤记录列表表格(含删除确认对话框) | | `attendance-stats-card.tsx` | 统计卡片(总数、到场、缺勤、迟到、早退、请假、出勤率、迟到率) | | `attendance-filters.tsx` | URL 同步筛选器(班级、状态、日期) | | `student-attendance-view.tsx` | 学生/家长视图(统计卡片 + 最近记录表格) | | `attendance-rules-form.tsx` | 考勤规则配置表单(班级选择器、迟到/早退阈值、自动标记勾选) | --- ## 模块:scheduling `src/modules/scheduling` 排课与调课模块:管理员配置班级排课规则(每日课时、连续课时、午休、上下学时间、避免背靠背、科目均衡),自动排课引擎按规则生成周课表,调课/代课申请与审批流程,课表冲突检测。 > 所有 actions 均使用 `requirePermission()` 进行权限校验:规则配置/调课申请/冲突检测/查询使用 `requirePermission(SCHEDULE_ADJUST)`,自动排课/应用课表/审批调课使用 `requirePermission(SCHEDULE_AUTO)`。admin 角色拥有 SCHEDULE_AUTO+SCHEDULE_ADJUST,teacher 角色无排课权限。 ### Server Actions (`actions.ts`) | Action | 权限 | 用途 | |--------|------|------| | `saveSchedulingRulesAction` | SCHEDULE_ADJUST | 保存班级排课规则(upsert,classId 为空时为全局规则) | | `autoScheduleAction` | SCHEDULE_AUTO | 根据规则与科目分配生成预览课表(不落库) | | `applyAutoScheduleAction` | SCHEDULE_AUTO | 将生成的课表写入 classSchedule 表(事务:先删后插) | | `requestScheduleChangeAction` | SCHEDULE_ADJUST | 提交调课/代课申请(status=pending) | | `approveScheduleChangeAction` | SCHEDULE_AUTO | 审批通过调课申请(status=approved) | | `rejectScheduleChangeAction` | SCHEDULE_AUTO | 驳回调课申请(status=rejected) | | `getScheduleChangesAction` | SCHEDULE_ADJUST | 查询调课申请列表(可按 classId/status/requesterId 过滤) | | `getClassConflictsAction` | SCHEDULE_ADJUST | 检测班级课表时间重叠冲突 | ### Data Access (`data-access.ts`) | 函数 | 签名 | 被使用 | |------|------|--------| | `getSchedulingRules` | `(classId?: string) => Promise` | saveSchedulingRulesAction, autoScheduleAction, admin/scheduling/rules | | `upsertSchedulingRules` | `(data: SchedulingRuleInput) => Promise` | saveSchedulingRulesAction | | `getScheduleChanges` | `(params: ScheduleChangeQueryParams) => Promise` | getScheduleChangesAction, admin/scheduling/changes, teacher/schedule-changes | | `createScheduleChange` | `(data: ScheduleChangeInput, requestedBy: string) => Promise` | requestScheduleChangeAction | | `updateScheduleChangeStatus` | `(id, status, approverId) => Promise` | approveScheduleChangeAction, rejectScheduleChangeAction | | `getClassConflicts` | `(classId: string) => Promise` | getClassConflictsAction | | `getAdminClassesForScheduling` | `() => Promise>` | 所有 scheduling 页面 | | `getTeachersForScheduling` | `() => Promise>` | teacher/schedule-changes | | `getClassroomsForScheduling` | `() => Promise>` | autoScheduleAction | | `getClassSubjectsForScheduling` | `(classId) => Promise>` | autoScheduleAction | ### Auto Scheduler (`auto-scheduler.ts`) | 函数 | 用途 | |------|------| | `autoSchedule` | 贪心+冲突检测排课算法:按科目每周课时降序,为每节课选择第一个满足约束的时段(午休、每日窗口、班级/教师/教室冲突、每日最大课时、避免背靠背) | | `findOptimalSlot` | 在候选时段中找到第一个满足所有约束的时段 | | `validateSchedule` | 校验生成的课表是否违反规则,返回冲突列表 | | `buildDefaultTimeSlots` | 根据上下学时间和午休时间构建默认时段(周一至周五,上午4节+下午4节) | ### Schemas (`schema.ts`) | Schema | 用途 | |--------|------| | `SchedulingRuleSchema` | 排课规则校验(classId, maxDailyHours?, maxContinuousHours?, lunchBreakStart?, lunchBreakEnd?, morningStart?, afternoonEnd?, avoidBackToBack?, balancedSubjects?) | | `ScheduleChangeSchema` | 调课申请校验(classId, originalScheduleId?, originalTeacherId?, substituteTeacherId?, originalDate?, newDate?, newStartTime?, newEndTime?, reason) | | `AutoScheduleParamsSchema` | 自动排课参数校验(classId, rules, subjects[], teachers[], classrooms[], timeSlots[]) | | `ScheduleChangeStatusEnum` | 调课状态枚举(pending/approved/rejected/completed) | | `ApproveScheduleChangeSchema` | 审批校验(changeId, reason?) | ### Types (`types.ts`) | Type | 定义 | |------|------| | `ScheduleChangeStatus` | `"pending" \| "approved" \| "rejected" \| "completed"` | | `SchedulingRule` | 排课规则完整类型 | | `ScheduleChange` | 调课申请完整类型 | | `ScheduleChangeListItem` | 列表项类型(含 className, originalTeacherName, substituteTeacherName, requesterName, approverName) | | `TimeSlot` | `{ weekday, startTime, endTime }` | | `ScheduleConflict` | `{ type, description, scheduleIds }`(type: teacher_overlap/classroom_overlap/class_overlap/rule_violation) | | `AutoScheduleResult` | `{ success, scheduledCount, conflictCount, conflicts, schedules }` | | `GeneratedSchedule` | `{ classId, weekday, startTime, endTime, course, location, teacherId, subjectId }` | | `AutoScheduleParams` | `{ classId, rules, subjects, teachers, classrooms, timeSlots }` | | `ScheduleChangeQueryParams` | `{ classId?, status?, requesterId? }` | | `SCHEDULE_CHANGE_STATUS_LABELS` | 状态英文标签常量 | | `SCHEDULE_CHANGE_STATUS_COLORS` | 状态颜色常量(用于 Badge) | ### Components (`components/`) | 组件 | 用途 | |------|------| | `scheduling-rules-form.tsx` | 排课规则配置表单(班级选择器、每日最大课时、连续课时、午休时间、上下学时间、避免背靠背、科目均衡) | | `auto-schedule-panel.tsx` | 自动排课面板(班级选择→预览→应用流程) | | `auto-schedule-result.tsx` | 排课结果预览(课表表格 + 冲突/警告列表) | | `schedule-change-form.tsx` | 调课/代课申请表单(班级、原任课教师、代课教师、原日期、新日期、新时间、原因) | | `schedule-change-list.tsx` | 调课申请列表表格(含审批/驳回对话框,canApprove 控制审批按钮可见性) | | `schedule-conflicts-view.tsx` | 冲突检测视图(班级选择器 + 检测按钮 + 冲突结果列表) | --- ## 模块:proctoring `src/modules/proctoring` 考试监考模块:监考模式考试实时监控、防作弊事件采集、教师监考面板、学生端防作弊监控、考试模式配置。 > 权限:教师监考面板使用 `requirePermission(EXAM_PROCTOR)`;学生端上报事件使用 `requireAuth()`(学生上报自己的事件,不需要管理权限)。前端组件使用 `usePermission().hasPermission(EXAM_PROCTOR)` 控制权限。 ### Server Actions (`actions.ts`) | Action | 权限 | 用途 | |--------|------|------| | `recordProctoringEventAction` | requireAuth() | 学生端上报监考事件(含 submission 归属校验) | | `getProctoringDashboardAction` | EXAM_PROCTOR | 获取监考面板数据(摘要+学生状态+最近事件) | ### Data Access (`data-access.ts`) `import "server-only"` | 函数 | 签名 | 被使用 | |------|------|--------| | `recordProctoringEvent` | `(input: RecordProctoringEventInput) => Promise` | actions.recordProctoringEventAction, API /api/proctoring/event | | `getProctoringEvents` | `(examId, filters?) => Promise` | 待扩展 | | `getProctoringEventsBySubmission` | `(submissionId) => Promise` | 待扩展 | | `getExamProctoringSummary` | `(examId) => Promise` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx | | `getStudentProctoringStatuses` | `(examId) => Promise` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx | | `getExamForProctoring` | `(examId) => Promise<{id,title,examMode,config} | null>` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx | | `getRecentProctoringEvents` | `(examId, limit?) => Promise` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx | ### Types (`types.ts`) | 类型 | 定义 | |------|------| | `ProctoringEventType` | `"tab_switch" \| "window_blur" \| "copy_attempt" \| "paste_attempt" \| "right_click" \| "devtools_open" \| "fullscreen_exit" \| "idle_timeout"` | | `ExamMode` | `"homework" \| "timed" \| "proctored"` | | `ProctoringEvent` | `{ id, submissionId, studentId, examId, eventType, eventDetail?, occurredAt, createdAt }` | | `ProctoringEventWithDetails` | `ProctoringEvent & { studentName, examTitle }` | | `ExamProctoringSummary` | `{ examId, examTitle, examMode, totalStudents, startedStudents, submittedStudents, totalEvents, abnormalStudents, eventsByType }` | | `StudentProctoringStatus` | `{ studentId, studentName, submissionId, submissionStatus, eventCount, lastEventAt, isAbnormal, eventsByType }` | | `ProctoringDashboardData` | `{ summary, students, recentEvents }` | | `ExamModeConfig` | `{ examMode, durationMinutes, shuffleQuestions, allowLateStart, lateStartGraceMinutes, antiCheatEnabled }` | | `PROCTORING_EVENT_LABELS` | 事件类型中文标签常量 | | `EXAM_MODE_LABELS` | 考试模式中文标签常量 | | `ABNORMAL_EVENT_THRESHOLD` | 异常学生事件数阈值(3) | ### Components (`components/`) | 组件 | 用途 | |------|------| | `proctoring-dashboard.tsx` | 教师监考面板(实时学生状态、异常事件统计、异常学生高亮、10 秒轮询、usePermission 权限控制) | | `anti-cheat-monitor.tsx` | 学生端防作弊监控(visibilitychange/blur/copy/paste/contextmenu/keydown/fullscreenchange 监听、空闲超时检测、强制全屏、警告提示、事件上报) | | `exam-mode-config.tsx` | 考试模式配置(react-hook-form Controller,作业/限时/监考模式选择,限时设置时长,监考设置防作弊选项) | ### API Routes | 路由 | 方法 | 权限 | 用途 | |------|------|------|------| | `/api/proctoring/event` | POST | requireAuth() | 接收学生端上报的监考事件(含 submission 归属校验) | --- ## 模块:diagnostic `src/modules/diagnostic` 学情诊断报告模块:基于知识点掌握度(`knowledgePointMastery` 表)生成个人/班级诊断报告,掌握度雷达图(学生 vs 班级平均),强项/弱项分析,知识点掌握度热力图,需重点关注学生列表,报告发布/删除管理。 > 权限:所有 Server Actions 使用 `requirePermission()` 校验。生成/发布/删除报告使用 `requirePermission(DIAGNOSTIC_MANAGE)`,查询/详情使用 `requirePermission(DIAGNOSTIC_READ)`。admin/teacher/grade_head 角色拥有 DIAGNOSTIC_MANAGE+DIAGNOSTIC_READ,teaching_head/student 角色仅有 DIAGNOSTIC_READ。前端组件使用 `usePermission().hasPermission(DIAGNOSTIC_MANAGE)` 控制生成/发布/删除按钮可见性(无 `role === "xxx"` 硬编码)。页面路由通过 `getAuthContext()` 进行 DataScope 二次校验:`class_members` 仅查自己,`children` 仅查子女,`class_taught` 必须包含 classId。 ### Server Actions (`actions.ts`) `"use server"` | Action | 权限 | 用途 | |--------|------|------| | `generateStudentReportAction` | DIAGNOSTIC_MANAGE | 生成学生个人诊断报告(formData: studentId, period) | | `generateClassReportAction` | DIAGNOSTIC_MANAGE | 生成班级诊断报告(formData: classId, period) | | `publishReportAction` | DIAGNOSTIC_MANAGE | 发布诊断报告(formData: id,status → published) | | `deleteReportAction` | DIAGNOSTIC_MANAGE | 删除诊断报告(formData: id) | | `getDiagnosticReportsAction` | DIAGNOSTIC_READ | 查询诊断报告列表(params: DiagnosticReportQueryParams) | | `getDiagnosticReportByIdAction` | DIAGNOSTIC_READ | 获取诊断报告详情(id) | ### Data Access (`data-access.ts` + `data-access-reports.ts`) `import "server-only"`(两个文件均以此开头) #### `data-access.ts`(掌握度相关) | 函数 | 签名 | 被使用 | |------|------|--------| | `getStudentMastery` | `(studentId: string) => Promise` | getStudentMasterySummary, teacher/diagnostic/student/[studentId] | | `getStudentMasterySummary` | `(studentId: string) => Promise` | generateDiagnosticReport, teacher/diagnostic/student/[studentId], student/diagnostic | | `updateMasteryFromSubmission` | `(submissionId: string) => Promise` | 待扩展(作业/考试提交后触发,onDuplicateKeyUpdate upsert) | | `getClassMasterySummary` | `(classId: string) => Promise` | generateClassDiagnosticReport, teacher/diagnostic/class/[classId] | | `getKnowledgePointStats` | `(classId?: string, gradeId?: string) => Promise` | teacher/diagnostic/student/[studentId](班级平均对比) | #### `data-access-reports.ts`(报告相关) | 函数 | 签名 | 被使用 | |------|------|--------| | `generateDiagnosticReport` | `(studentId, period, generatedBy) => Promise` | generateStudentReportAction | | `generateClassDiagnosticReport` | `(classId, period, generatedBy) => Promise` | generateClassReportAction | | `getDiagnosticReports` | `(filters: DiagnosticReportQueryParams) => Promise` | getDiagnosticReportsAction, teacher/diagnostic, teacher/diagnostic/student/[studentId], student/diagnostic | | `getDiagnosticReportById` | `(id: string) => Promise` | getDiagnosticReportByIdAction | | `publishDiagnosticReport` | `(id: string) => Promise` | publishReportAction | | `deleteDiagnosticReport` | `(id: string) => Promise` | deleteReportAction | ### Types (`types.ts`) | Type | 定义 | |------|------| | `DiagnosticReportType` | `"individual" \| "class" \| "grade"` | | `DiagnosticReportStatus` | `"draft" \| "published" \| "archived"` | | `KnowledgePointMastery` | `{ id, studentId, knowledgePointId, masteryLevel(0-100), totalQuestions, correctQuestions, lastAssessedAt, createdAt, updatedAt }` | | `MasteryWithKnowledgePoint` | `KnowledgePointMastery & { knowledgePointName, knowledgePointDescription }` | | `StudentMasterySummary` | `{ studentId, studentName, averageMastery, totalKnowledgePoints, strengths(≥80), weaknesses(<60), allMastery }` | | `DiagnosticReport` | `{ id, studentId, generatedBy, reportType, period, summary, strengths[], weaknesses[], recommendations[], overallScore, status, createdAt, updatedAt }` | | `DiagnosticReportWithDetails` | `DiagnosticReport & { studentName, generatedByName }` | | `ClassMasterySummary` | `{ classId, className, studentCount, averageMastery, knowledgePointStats[], studentsNeedingAttention[] }` | | `KnowledgePointStat` | `{ knowledgePointId, knowledgePointName, averageMastery, masteredCount(≥80), notMasteredCount(<60), totalStudents }` | | `DiagnosticReportQueryParams` | `{ studentId?, reportType?, status?, period? }` | | `MasteryRadarPoint` | `{ knowledgePoint, student(0-100), classAverage?(0-100) }` | ### Components (`components/`) 所有组件以 `"use client"` 开头。 | 组件 | 用途 | |------|------| | `mastery-radar-chart.tsx` | 知识点掌握度雷达图(recharts RadarChart,学生 vs 班级平均对比,无数据时显示 EmptyState) | | `student-diagnostic-view.tsx` | 学生诊断视图(概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示) | | `class-diagnostic-view.tsx` | 班级诊断视图(概览卡片、知识点掌握度热力图[绿≥80/黄60-79/橙40-59/红<40]、知识点排名表、需重点关注学生表[链接到学生视图]、生成班级报告表单[DIAGNOSTIC_MANAGE]) | | `report-list.tsx` | 诊断报告列表(reportType/status 过滤器[URL searchParams]、报告表格、发布/删除操作[DIAGNOSTIC_MANAGE]、确认对话框) | ### 数据库表 | 表 | 用途 | |----|------| | `knowledgePointMastery` | 知识点掌握度记录(复合主键 studentId+knowledgePointId,onDuplicateKeyUpdate upsert) | | `learningDiagnosticReports` | 学情诊断报告(reportType: individual/class/grade;status: draft/published/archived) | ### 页面路由 | 路由 | 组件 | 权限 | DataScope 校验 | |------|------|------|----------------| | `/teacher/diagnostic` | ReportList | diagnostic:read | class_members 仅查看自己报告 | | `/teacher/diagnostic/student/[studentId]` | StudentDiagnosticView | diagnostic:read | class_members 仅自己,children 仅子女 | | `/teacher/diagnostic/class/[classId]` | ClassDiagnosticView | diagnostic:read | class_taught 必须包含 classId,class_members/children → notFound | | `/student/diagnostic` | StudentDiagnosticView | diagnostic:read | class_members 仅查自己(ctx.userId) | --- ## 模块:elective `src/modules/elective` 选课管理模块:选修课程 CRUD、选课开放/关闭、学生选课/退课、抽签模式批量录取(runLottery)、FCFS 即时录取、DataScope 行级过滤(admin 全部、teacher 所教、grade_head 所管年级、student 可选课程)。 > 权限:管理操作使用 `requirePermission(ELECTIVE_MANAGE)`;读取使用 `requirePermission(ELECTIVE_READ)`;学生选课/退课使用 `requirePermission(ELECTIVE_SELECT)`。前端组件使用 `usePermission().hasPermission()` 控制权限。`getStudentSelectionsAction` 对 class_members/children 进行 DataScope 二次校验。 ### Server Actions (`actions.ts`) | Action | 权限 | 用途 | |--------|------|------| | `createElectiveCourseAction` | ELECTIVE_MANAGE | 创建选修课程(formData: name, subjectId?, teacherId, gradeId?, description?, capacity?, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, selectionMode?, credit?) | | `updateElectiveCourseAction` | ELECTIVE_MANAGE | 更新选修课程(id + formData) | | `deleteElectiveCourseAction` | ELECTIVE_MANAGE | 删除选修课程(formData: courseId) | | `openSelectionAction` | ELECTIVE_MANAGE | 开放选课(formData: courseId) | | `closeSelectionAction` | ELECTIVE_MANAGE | 关闭选课(formData: courseId) | | `runLotteryAction` | ELECTIVE_MANAGE | 执行抽签录取(formData: courseId),返回 {enrolled, waitlist} | | `selectCourseAction` | ELECTIVE_SELECT | 学生选课(formData: courseId, priority?) | | `dropCourseAction` | ELECTIVE_SELECT | 学生退课(formData: courseId) | | `getElectiveCoursesAction` | ELECTIVE_READ | 查询选修课程列表(按 DataScope 过滤,传 currentUserId) | | `getStudentSelectionsAction` | ELECTIVE_READ | 查询学生选课记录(DataScope 二次校验:class_members 仅自己,children 仅子女) | | `getAvailableCoursesAction` | ELECTIVE_SELECT | 获取学生可选课程(status=open 且匹配年级) | ### Data Access #### `data-access.ts` (`import "server-only"`) | 函数 | 签名 | 被使用 | |------|------|--------| | `getElectiveCourses` | `(params?: GetElectiveCoursesParams & { scope?: DataScope; currentUserId?: string }) => Promise` | getElectiveCoursesAction, admin/elective, teacher/elective | | `getElectiveCourseById` | `(id: string) => Promise` | updateElectiveCourseAction, admin/elective/[id]/edit | | `createElectiveCourse` | `(data: CreateElectiveCourseInput, teacherId: string) => Promise` | createElectiveCourseAction | | `updateElectiveCourse` | `(id: string, data: Partial) => Promise` | updateElectiveCourseAction | | `deleteElectiveCourse` | `(id: string) => Promise` | deleteElectiveCourseAction | | `openSelection` | `(courseId: string) => Promise` | openSelectionAction | | `closeSelection` | `(courseId: string) => Promise` | closeSelectionAction | | `getSubjectOptions` | `() => Promise<{id, name}[]>` | admin/elective/create, admin/elective/[id]/edit | > `buildScopeFilter(scope, userId)` 内部函数:owned/class_taught 按 `teacherId` 过滤,grade_managed 按 `gradeIds` 过滤,class_members/children 返回 null(学生通过 `getAvailableCoursesForStudent` 获取可选课程)。 #### `data-access-selections.ts` (`import "server-only"`) > 从 data-access.ts 拆分以遵守单文件 ≤300 行规则。 | 函数 | 签名 | 被使用 | |------|------|--------| | `getCourseSelections` | `(courseId: string) => Promise` | 待扩展 | | `getStudentSelections` | `(studentId: string) => Promise` | getStudentSelectionsAction, student/elective | | `getStudentGradeId` | `(studentId: string) => Promise` | getAvailableCoursesForStudent | | `getAvailableCoursesForStudent` | `(studentId: string, gradeId?: string \| null) => Promise` | getAvailableCoursesAction, student/elective | #### `data-access-operations.ts` (`import "server-only"`) > 从 data-access.ts 拆分以遵守单文件 ≤300 行规则。包含选课/退课/抽签的写操作。 | 函数 | 签名 | 被使用 | |------|------|--------| | `runLottery` | `(courseId: string) => Promise<{enrolled: number, waitlist: number}>` | runLotteryAction | | `selectCourse` | `(courseId: string, studentId: string, priority?: number) => Promise<{status: CourseSelectionStatus, message: string}>` | selectCourseAction | | `dropCourse` | `(courseId: string, studentId: string) => Promise` | dropCourseAction | ### Schema (`schema.ts`) | Schema | 用途 | |--------|------| | `ElectiveCourseStatusEnum` | 课程状态枚举(draft/open/closed/cancelled) | | `ElectiveSelectionModeEnum` | 选课模式枚举(fcfs/lottery) | | `CourseSelectionStatusEnum` | 选课状态枚举(selected/enrolled/waitlist/dropped/rejected) | | `CreateElectiveCourseSchema` | 创建课程校验(name 必填,teacherId 必填,capacity 1-500 默认 30,selectionMode 默认 fcfs,credit 默认 1.0) | | `UpdateElectiveCourseSchema` | 更新课程校验(所有字段可选,含 status) | | `SelectCourseSchema` | 选课校验(courseId 必填,priority 1-10 可选) | | `DropCourseSchema` | 退课校验(courseId 必填) | | `RunLotterySchema` | 抽签校验(courseId 必填) | ### 类型/接口 (`types.ts`) | 类型 | 定义 | |------|------| | `ElectiveCourseStatus` | `"draft" \| "open" \| "closed" \| "cancelled"` | | `ElectiveSelectionMode` | `"fcfs" \| "lottery"` | | `CourseSelectionStatus` | `"selected" \| "enrolled" \| "waitlist" \| "dropped" \| "rejected"` | | `ElectiveCourse` | 课程完整类型(id, name, subjectId?, teacherId, gradeId?, description?, capacity, enrolledCount, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, status, selectionMode, credit, createdAt, updatedAt) | | `ElectiveCourseWithDetails` | `ElectiveCourse & { teacherName?, subjectName?, gradeName? }` | | `CourseSelection` | 选课记录类型(id, courseId, studentId, status, priority?, selectedAt, enrolledAt?, droppedAt?, lotteryRank?, createdAt, updatedAt) | | `CourseSelectionWithDetails` | `CourseSelection & { courseName?, studentName?, courseCapacity?, courseEnrolledCount?, courseStatus? }` | | `GetElectiveCoursesParams` | 查询参数(status?, gradeId?, subjectId?, teacherId?) | | `ELECTIVE_STATUS_LABELS` | 课程状态标签常量 | | `ELECTIVE_STATUS_COLORS` | 课程状态颜色常量(Badge variant) | | `SELECTION_MODE_LABELS` | 选课模式标签常量 | | `COURSE_SELECTION_STATUS_LABELS` | 选课状态标签常量 | | `COURSE_SELECTION_STATUS_COLORS` | 选课状态颜色常量(Badge variant) | ### 导出组件 (`components/`) | 组件文件 | 功能 | |---------|------| | `elective-course-list.tsx` | 课程卡片列表(管理员/教师视图,含编辑/开放/关闭/抽签/删除操作按钮,usePermission 权限控制) | | `elective-course-form.tsx` | 课程创建/编辑表单(name, subjectId, teacherId, gradeId, description, capacity, classroom, schedule, dates, selectionMode, credit) | | `student-selection-view.tsx` | 学生选课视图(可选课程列表 + 我的选课记录,含选课/退课按钮) | ### 路由页面 | 路由 | 组件 | 权限 | 说明 | |------|------|------|------| | `/admin/elective` | ElectiveCourseList | elective:manage | 管理员选修课程列表(scope=all) | | `/admin/elective/create` | ElectiveCourseForm | elective:manage | 创建选修课程 | | `/admin/elective/[id]/edit` | ElectiveCourseForm (edit) | elective:manage | 编辑选修课程 | | `/teacher/elective` | ElectiveCourseList (teacher) | elective:manage | 教师选修课程列表(scope=class_taught/owned,按 teacherId 过滤) | | `/student/elective` | StudentSelectionView | elective:select | 学生选课页面(可选课程 + 我的选课) | --- ## 模块间依赖矩阵 | ↓ 使用 → | shared | auth | exams | homework | questions | textbooks | classes | school | dashboard | layout | settings | users | audit | announcements | files | grades | course-plans | parent | messaging | attendance | scheduling | proctoring | diagnostic | elective | |----------|--------|------|-------|----------|-----------|-----------|---------|--------|-----------|--------|----------|-------|-------|---------------|-------|-------|-------------|--------|-----------|------------|-----------|------------|------------|----------| | **shared** | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **auth** | db,schema,permissions,login-logger | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **exams** | db,auth-guard,types,ai | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **homework** | db,auth-guard,types | auth | data-access.getExams | - | - | - | schema | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **questions** | db,auth-guard,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **textbooks** | db,auth-guard,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **classes** | db,auth-guard,types | auth | - | homework-insights | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **school** | db,auth-guard,types,audit-logger | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **dashboard** | db,types | auth | - | data-access.getTeacherGradeTrends,getStudentDashboardGrades | - | - | data-access.getTeacherClasses,getStudentClasses,getStudentSchedule | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **layout** | hooks.usePermission | auth(useSession) | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | notification-dropdown | - | - | - | - | | **settings** | db,auth-guard,ai,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **users** | db,auth-guard(requireAuth,requirePermission),types,lib.excel | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **audit** | db,auth-guard.requirePermission,types.permissions | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **announcements** | db,auth-guard,types | auth | - | - | - | - | - | - | data-access.getGrades | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **files** | db,auth-guard(requireAuth,requirePermission),types,lib/file-storage | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **grades** | db,auth-guard,types,lib.excel | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **course-plans** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | data-access.getAdminClasses,getStaffOptions | data-access.getAcademicYears | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **parent** | db,auth-guard(requireAuth),types | auth | - | data-access.getStudentHomeworkAssignments,getStudentDashboardGrades | - | - | data-access.getStudentClasses,getStudentSchedule | - | - | - | - | - | - | - | - | data-access.getStudentGradeSummary | - | - | - | - | - | - | | **messaging** | db,auth-guard(requirePermission,requireAuth),types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **notifications** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | notification-preferences,data-access.createNotification | - | - | - | - | - | | **attendance** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | data-access.getTeacherClasses,getAdminClasses | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **scheduling** | db,auth-guard(requirePermission,getAuthContext),types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **proctoring** | db,auth-guard(requirePermission,requireAuth),types,components.ui,hooks.usePermission | auth | schema.exams,examSubmissions,examProctoringEvents | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **diagnostic** | db,auth-guard(requirePermission,getAuthContext),types,hooks.usePermission,components.ui | auth | schema.examSubmissions,submissionAnswers,questionsToKnowledgePoints | - | - | - | schema.classes,classEnrollments | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | | **elective** | db,auth-guard.requirePermission,types,types.DataScope,hooks.usePermission,components.ui | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | --- ## 关键参数影响链 ### `userId` 1. 由 `auth.ts` JWT callback 从 `users` 表查询产生,存入 JWT 2. 通过 `session.user.id` 传递到所有 Server Components 和 Client Components 3. 通过 `getAuthContext().userId` 传递到所有 Server Actions 4. 在 `auth-guard.ts` 中用于查询 `usersToRoles`(获取角色)和 `classSubjectTeachers`/`grades`(获取 DataScope) 5. 在 exams/actions.ts 中作为 `creatorId` 写入 `exams` 表 6. 在 homework/actions.ts 中作为 `creatorId` 写入 `homeworkAssignments` 表 7. 在 classes/data-access.ts 中查询 `getTeacherClasses(teacherId)` 和 `getGradeManagedClasses(userId)` 8. 在 elective/actions.ts 中作为 `teacherId` 默认值写入 `electiveCourses` 表(createElectiveCourseAction),作为 `studentId` 查询学生选课(selectCourseAction/dropCourseAction/getStudentSelectionsAction) 9. 在 elective/data-access.ts 中 `getElectiveCourses({ scope, currentUserId })` 按 `teacherId` 过滤(class_taught/owned scope) ### `examId` 1. 由 `exams/actions.ts` 的 `createExamAction` 产生,通过 CUID2 生成,写入 `exams` 表 2. 被 `exams/data-access.getExamById(id)` 读取 3. 被 `exams/actions.ts` 的 `updateExamAction`/`deleteExamAction`/`duplicateExamAction` 用于定位考试 4. 传入 `homework/actions.ts` 的 `createHomeworkAssignmentAction` 的 `sourceExamId` 参数 5. 在 `homeworkAssignments` 表中作为外键关联到源考试 6. 被 `homework/data-access.getHomeworkAssignmentAnalytics` 用于追溯作业来源 ### `classId` 1. 由 `classes/actions.ts` 的 `createTeacherClassAction`/`createAdminClassAction` 产生 2. 被 `classes/data-access.getClassStudents(classId)` 读取学生列表 3. 被 `classes/data-access.getClassSchedule(classId)` 读取课表 4. 被 `classes/data-access.getClassHomeworkInsights(classId)` 读取作业洞察 5. 被 `homework/data-access.getHomeworkAssignments({ classId })` 过滤作业列表 6. 在 `auth-guard.ts` 中通过 `classSubjectTeachers` 查询教师关联的 classIds,构建 `DataScope.class_taught` ### `permission` 1. 由 `shared/types/permissions.ts` 的 `Permissions` 常量定义(57 个权限点,含 `AUDIT_LOG_READ`、`ANNOUNCEMENT_MANAGE`、`FILE_UPLOAD`、`FILE_READ`、`FILE_DELETE`、`GRADE_RECORD_MANAGE`、`GRADE_RECORD_READ`、`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`、`ELECTIVE_SELECT` 等) 2. 在 `shared/lib/permissions.ts` 中通过 `ROLE_PERMISSIONS` 映射角色到权限列表(admin 角色包含 `AUDIT_LOG_READ`、`COURSE_PLAN_MANAGE`+`COURSE_PLAN_READ`;admin/teacher 含 `FILE_UPLOAD/READ/DELETE` 及 `GRADE_RECORD_MANAGE/READ`;teacher/student/grade_head/teaching_head 含 `COURSE_PLAN_READ`;student/parent 含 `FILE_READ` 及 `GRADE_RECORD_READ`;admin/teacher 含 `ATTENDANCE_MANAGE`+`ATTENDANCE_READ`,student/parent/grade_head/teaching_head 含 `ATTENDANCE_READ`;admin/teacher/parent/grade_head/teaching_head 含 `MESSAGE_SEND/READ/DELETE`;student 含 `MESSAGE_READ/DELETE` 但无 `MESSAGE_SEND`;admin 含 `SCHEDULE_AUTO`+`SCHEDULE_ADJUST`,teacher/student/parent/grade_head/teaching_head 无排课权限;admin/teacher/grade_head 含 `DIAGNOSTIC_MANAGE`+`DIAGNOSTIC_READ`,teaching_head/student 含 `DIAGNOSTIC_READ`;admin/teacher 含 `ELECTIVE_MANAGE`+`ELECTIVE_READ`,student 含 `ELECTIVE_SELECT`+`ELECTIVE_READ`,grade_head/teaching_head 含 `ELECTIVE_READ`) 3. 在 `auth.ts` JWT callback 中通过 `resolvePermissions(roleNames)` 合并多角色权限,存入 JWT 4. 在 `proxy.ts` middleware 中通过 `token.permissions` 检查路由访问权限 5. 在 `shared/lib/auth-guard.ts` 中通过 `requirePermission(permission)` 在 Server Action 层断言权限(如 audit-logs 页面使用 `requirePermission(AUDIT_LOG_READ)`;`DELETE /api/files/[id]` 使用 `requirePermission(FILE_DELETE)`;messaging/actions.ts 使用 `requirePermission(MESSAGE_SEND/READ/DELETE)`;attendance/actions.ts 使用 `requirePermission(ATTENDANCE_MANAGE/READ)`;users/actions.ts 的 `importUsersAction`/`exportUsersAction`/`downloadUserTemplateAction` 使用 `requirePermission(USER_MANAGE)`;grades/actions.ts 的 `exportGradesAction` 使用 `requirePermission(GRADE_RECORD_READ)`;`POST /api/import` 使用 `requirePermission(USER_MANAGE)`;scheduling/actions.ts 使用 `requirePermission(SCHEDULE_AUTO/SCHEDULE_ADJUST)`;diagnostic/actions.ts 使用 `requirePermission(DIAGNOSTIC_MANAGE/READ)`;elective/actions.ts 使用 `requirePermission(ELECTIVE_MANAGE/READ/SELECT)`) 6. 在 `shared/hooks/use-permission.ts` 中通过 `hasPermission(permission)` 在客户端组件中条件渲染 7. 在 `layout/config/navigation.ts` 中作为 `NavItem.permission` 字段过滤侧边栏菜单(Audit Logs 菜单项使用 `Permissions.AUDIT_LOG_READ`;Messages 菜单项使用 `Permissions.MESSAGE_READ`;Attendance 菜单项 teacher 使用 `Permissions.ATTENDANCE_MANAGE`,student/parent 使用 `Permissions.ATTENDANCE_READ`;Import Users 菜单项使用 `Permissions.USER_MANAGE`;Scheduling 菜单项 admin 使用 `Permissions.SCHEDULE_ADJUST`/`SCHEDULE_AUTO`;teacher Schedule Changes 菜单项使用 `Permissions.SCHEDULE_ADJUST`;teacher/student Diagnostic 菜单项使用 `Permissions.DIAGNOSTIC_READ`;Electives 菜单项 admin/teacher 使用 `Permissions.ELECTIVE_MANAGE`,student 使用 `Permissions.ELECTIVE_SELECT`) ### `DataScope` 1. 由 `auth-guard.ts` 的 `resolveDataScope(userId, roles)` 根据用户角色和 DB 关系动态计算 2. 传递到 `exams/data-access.getExams({ scope })` 进行行级过滤 3. 传递到 `homework/data-access.getHomeworkAssignments({ scope })` 进行行级过滤 4. 传递到 `dashboard/data-access.getAdminDashboardData(scope)` 进行统计过滤 5. 在 exams/actions.ts 的 `updateExamAction`/`deleteExamAction` 中用于判断是否需要资源归属校验(`scope.type !== "all"`) 6. 对 parent 角色,`resolveDataScope` 查询 `parentStudentRelations` 表构建 `{ type: "children", childrenIds: string[] }`,传递到 `grades/data-access.getStudentGradeSummary` 等函数进行行级过滤 7. 在 `parent/children/[studentId]/page.tsx` 中通过 `ctx.dataScope.type === "children" && !ctx.dataScope.childrenIds.includes(studentId)` 二次校验家长拥有该子女 8. 传递到 `attendance/data-access.getAttendanceRecords({ scope })` 进行行级过滤(class_taught 按教师班级过滤,children 按子女过滤,class_members 仅查自己,all 查全部) 9. 在 `attendance/actions.ts` 的 `getStudentAttendanceAction` 中对 class_members/children 进行 DataScope 二次校验 10. 传递到 `elective/data-access.getElectiveCourses({ scope, currentUserId })` 进行行级过滤(owned/class_taught 按 `teacherId` 过滤,grade_managed 按 `gradeIds` 过滤,class_members/children 返回 null 由 `getAvailableCoursesForStudent` 处理) 11. 在 `elective/actions.ts` 的 `getStudentSelectionsAction` 中对 class_members/children 进行 DataScope 二次校验 --- ## 路由表 ### 根路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/` | 角色路由分发 | server | auth_required | 重定向到 `/dashboard` | ### auth/* 子路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/login` | LoginForm | client | public | 登录页面 | | `/register` | RegisterForm + registerAction | server | public | 注册页面(含未成年人信息保护、隐私政策/用户协议同意勾选) | | `/privacy` | PrivacyPage | server | public | 隐私政策页面(信息收集/使用/保护、用户权利、Cookie、未成年人保护条款、联系方式) | | `/terms` | TermsPage | server | public | 用户协议页面(服务说明、注册、行为规范、知识产权、免责、变更终止、法律适用) | ### admin/school/* 子路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/school` | SchoolManagementHome | server | school:manage | 学校管理首页(聚合入口) | | `/admin/school/schools` | SchoolList | server | school:manage | 学校列表(dataAccess: school/data-access.getSchools) | | `/admin/school/grades` | GradeList | server | grade:manage | 年级列表(dataAccess: school/data-access.getGrades) | | `/admin/school/grades/insights` | GradeInsights | server | grade:manage | 年级洞察(dataAccess: classes/data-access.getGradeHomeworkInsights) | | `/admin/school/departments` | DepartmentList | server | school:manage | 部门列表(dataAccess: school/data-access.getDepartments) | | `/admin/school/classes` | AdminClassList | server | school:manage | 班级列表(dataAccess: classes/data-access.getAdminClasses) | | `/admin/school/academic-year` | AcademicYearList | server | school:manage | 学年列表(dataAccess: school/data-access.getAcademicYears) | ### admin/audit-logs/* 子路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/audit-logs` | AuditLogView | server | audit_log:read | 操作日志列表(dataAccess: audit/data-access.getAuditLogs, getAuditModuleOptions;权限:requirePermission(AUDIT_LOG_READ)) | | `/admin/audit-logs/login-logs` | LoginLogView | server | audit_log:read | 登录日志列表(dataAccess: audit/data-access.getLoginLogs;权限:requirePermission(AUDIT_LOG_READ)) | ### admin/announcements/* 子路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/announcements` | AdminAnnouncementsView | client | announcement:manage | 公告管理首页(列表+创建对话框;dataAccess: announcements/data-access.getAnnouncements, school/data-access.getGrades) | | `/admin/announcements/[id]` | AnnouncementForm | client | announcement:manage | 编辑公告(dataAccess: announcements/data-access.getAnnouncementById, school/data-access.getGrades;actions: updateAnnouncementAction) | ### admin/files/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/files` | AdminFilesView | client | file:read | 管理员文件管理页面(上传+列表+删除;dataAccess: files/data-access.getAllFileAttachments;API: POST /api/upload, DELETE /api/files/[id] 需 file:delete) | ### admin/course-plans/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/course-plans` | CoursePlanList | client | course_plan:manage | 管理员课程计划列表(支持状态筛选;dataAccess: course-plans/data-access.getCoursePlans) | | `/admin/course-plans/create` | CoursePlanForm (create) | client | course_plan:manage | 创建课程计划(actions: createCoursePlanAction;dataAccess: classes/data-access.getAdminClasses, course-plans/data-access.getSubjectOptions, classes/data-access.getStaffOptions, school/data-access.getAcademicYears) | | `/admin/course-plans/[id]` | CoursePlanDetail | client | course_plan:manage | 课程计划详情(含周计划表格;actions: deleteCoursePlanAction, createCoursePlanItemAction, updateCoursePlanItemAction, deleteCoursePlanItemAction, toggleCoursePlanItemCompletedAction;dataAccess: course-plans/data-access.getCoursePlanById) | | `/admin/course-plans/[id]/edit` | CoursePlanForm (edit) | client | course_plan:manage | 编辑课程计划(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) | ### admin/users/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/users/import` | UserImportPage (含 UserImportDialog) | server | user:manage | 用户批量导入页面(说明卡片+字段文档表+导入对话框;actions: users/actions.downloadUserTemplateAction, users/actions.importUsersAction;权限:requirePermission(USER_MANAGE)) | ### announcements/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/announcements` | AnnouncementList | client | auth_required | 所有登录用户可查看的公告列表(仅 published;dataAccess: announcements/data-access.getAnnouncements) | ### management/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/management/grade/classes` | GradeManagedClasses | server | grade:manage | 年级主任管理的班级(dataAccess: classes/data-access.getGradeManagedClasses) | | `/management/grade/insights` | GradeInsightsView | server | grade:manage | 年级洞察视图(dataAccess: classes/data-access.getGradeHomeworkInsights) | ### student/learning/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/student/learning/assignments` | StudentAssignmentList | server | homework:submit | 学生作业列表(dataAccess: homework/data-access.getStudentHomeworkAssignments) | | `/student/learning/assignments/[assignmentId]` | HomeworkTakeView | client | homework:submit | 学生作答页面(actions: startHomeworkSubmissionAction, saveHomeworkAnswerAction, submitHomeworkAction) | | `/student/learning/courses` | StudentCourseList | server | class:read | 学生课程列表(dataAccess: classes/data-access.getStudentClasses) | | `/student/learning/textbooks` | StudentTextbookList | server | textbook:read | 学生教材列表(dataAccess: textbooks/data-access.getTextbooks) | | `/student/learning/textbooks/[id]` | TextbookReader | client | textbook:read | 学生教材阅读器(dataAccess: textbooks/data-access.getTextbookById, getChaptersByTextbookId) | | `/student/schedule` | StudentSchedule | server | class:read | 学生课表(dataAccess: classes/data-access.getStudentSchedule) | ### teacher/homework/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/teacher/homework` | HomeworkHome | server | homework:create | 作业管理首页(聚合入口) | | `/teacher/homework/assignments` | HomeworkAssignmentList | server | homework:create | 作业列表(dataAccess: homework/data-access.getHomeworkAssignments) | | `/teacher/homework/assignments/create` | HomeworkAssignmentForm | client | homework:create | 创建作业(actions: createHomeworkAssignmentAction) | | `/teacher/homework/assignments/[id]` | HomeworkAssignmentDetail | server | homework:create | 作业详情(dataAccess: homework/data-access.getHomeworkAssignmentById) | | `/teacher/homework/assignments/[id]/submissions` | HomeworkSubmissionList | server | homework:grade | 作业提交列表(dataAccess: homework/data-access.getHomeworkSubmissions) | | `/teacher/homework/submissions` | HomeworkReviewList | server | homework:grade | 批改列表(dataAccess: homework/data-access.getHomeworkAssignmentReviewList) | | `/teacher/homework/submissions/[submissionId]` | HomeworkGradingView | client | homework:grade | 批改页面(actions: gradeHomeworkSubmissionAction, dataAccess: getHomeworkSubmissionDetails) | ### teacher 其他路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/teacher/exams` | ExamDataTable | server | exam:read | 考试列表(dataAccess: exams/data-access.getExams) | | `/teacher/exams/[id]/build` | ExamAssemblyPanel | client | exam:update | 组卷页面(components: assembly/*, actions: updateExamAction) | | `/teacher/exams/[id]/proctoring` | ProctoringDashboard | server+client | exam:proctor | 教师监考面板(dataAccess: proctoring/data-access.getExamForProctoring,getExamProctoringSummary,getStudentProctoringStatuses,getRecentProctoringEvents;权限:requirePermission(EXAM_PROCTOR);组件:proctoring/components/proctoring-dashboard.tsx,10 秒轮询刷新) | | `/teacher/exams/grading` | ExamGradingList | server | exam:read | 考试批改列表 | | `/teacher/exams/grading/[submissionId]` | ExamGradingView | client | exam:read | 考试批改页面 | | `/teacher/classes/my/[id]` | ClassDetail | server | class:read | 班级详情(dataAccess: classes/data-access.getClassDetails) | | `/teacher/classes` | TeacherClassList | server | class:read | 教师班级列表(dataAccess: classes/data-access.getTeacherClasses) | | `/teacher/course-plans` | CoursePlanList (teacher) | client | course_plan:read | 教师课程计划列表(按当前用户 teacherId 过滤;dataAccess: course-plans/data-access.getCoursePlans) | | `/teacher/course-plans/[id]` | CoursePlanDetail (teacher) | client | course_plan:read | 教师课程计划详情(只读,无编辑按钮;dataAccess: course-plans/data-access.getCoursePlanById) | ### parent/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/parent/dashboard` | ParentDashboard | server | auth_required | 家长仪表盘首页(问候语 + 子女卡片网格;dataAccess: parent/data-access.getParentDashboardData;权限:requireAuth()) | | `/parent/children/[studentId]` | ChildDetailHeader + ChildDetailPanel | server | auth_required | 子女详情页(头部 + 作业/成绩/课表面板;dataAccess: parent/data-access.getChildDashboardData;权限:requireAuth() + 二次校验 ctx.dataScope.childrenIds 包含 studentId) | | `/parent/grades` | ParentGradesView | server | grade_record:read | 家长成绩视图(dataAccess: grades/data-access.getStudentGradeSummary,按 DataScope.children 过滤) | ### messages/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/messages` | MessageList + NotificationList | server | message:read | 消息首页(收件箱/已发送列表 + 通知列表;dataAccess: messaging/data-access.getMessages, getNotifications;权限:requirePermission(MESSAGE_READ)) | | `/messages/[id]` | MessageDetail | server | message:read | 消息详情(含回复线程;dataAccess: messaging/data-access.getMessageById, getMessageThread;actions: markMessageAsReadAction 自动已读;权限:requirePermission(MESSAGE_READ)) | | `/messages/compose` | MessageCompose | server | message:send | 写消息页面(支持 reply 模式 via searchParams: receiverId, subject, parentMessageId;dataAccess: messaging/data-access.getRecipients;权限:requirePermission(MESSAGE_SEND)) | ### attendance/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/teacher/attendance` | AttendanceRecordList + AttendanceFilters | server | attendance:manage | 教师考勤记录列表(dataAccess: attendance/data-access.getAttendanceRecords, classes/data-access.getTeacherClasses;权限:requirePermission(ATTENDANCE_MANAGE)) | | `/teacher/attendance/sheet` | AttendanceSheet | client | attendance:manage | 批量点名页面(班级/日期选择 + 学生表格;actions: batchRecordAttendanceAction, getClassAttendanceForDateAction, getClassStudentsForAttendance;权限:requirePermission(ATTENDANCE_MANAGE)) | | `/teacher/attendance/stats` | AttendanceStatsCard | server | attendance:read | 班级考勤统计(dataAccess: attendance/data-access-stats.getClassAttendanceStats, classes/data-access.getTeacherClasses;权限:requirePermission(ATTENDANCE_READ)) | | `/student/attendance` | StudentAttendanceView | server | attendance:read | 学生考勤视图(统计卡片 + 最近记录;dataAccess: attendance/data-access-stats.getStudentAttendanceSummary;权限:requirePermission(ATTENDANCE_READ),DataScope.class_members 仅查自己) | | `/parent/attendance` | StudentAttendanceView (per child) | server | attendance:read | 家长考勤视图(遍历子女,每个子女展示 StudentAttendanceView;dataAccess: parent/data-access.getChildren, attendance/data-access-stats.getStudentAttendanceSummary;权限:requirePermission(ATTENDANCE_READ),DataScope.children 仅查子女) | | `/admin/attendance` | AttendanceRecordList | server | attendance:manage | 管理员考勤总览(dataAccess: attendance/data-access.getAttendanceRecords(scope=all), classes/data-access.getAdminClasses;权限:requirePermission(ATTENDANCE_MANAGE)) | ### scheduling/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/scheduling/rules` | SchedulingRulesForm | server | schedule:adjust | 排课规则配置页面(dataAccess: scheduling/actions.getAdminClassesForScheduling, getSchedulingRules;actions: saveSchedulingRulesAction;权限:requirePermission(SCHEDULE_ADJUST)) | | `/admin/scheduling/auto` | AutoSchedulePanel + AutoScheduleResultView | server | schedule:auto | 自动排课页面(dataAccess: scheduling/actions.getAdminClassesForScheduling;actions: autoScheduleAction, applyAutoScheduleAction;权限:requirePermission(SCHEDULE_AUTO)) | | `/admin/scheduling/changes` | ScheduleChangeList + ScheduleConflictsView | server | schedule:adjust | 调课申请审批+冲突检测页面(dataAccess: scheduling/actions.getAdminClassesForScheduling, getScheduleChanges;actions: approveScheduleChangeAction, rejectScheduleChangeAction, getClassConflictsAction;权限:requirePermission(SCHEDULE_ADJUST);审批操作需 SCHEDULE_AUTO) | | `/teacher/schedule-changes` | ScheduleChangeForm + ScheduleChangeList | server | schedule:adjust | 教师调课/代课申请页面(dataAccess: scheduling/actions.getAdminClassesForScheduling, getTeachersForScheduling, getScheduleChanges(requesterId=ctx.userId);actions: requestScheduleChangeAction;权限:requirePermission(SCHEDULE_ADJUST);admin 角色查看全部申请) | ### grades/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/teacher/grades` | 成绩管理首页 | server | grade_record:read | 成绩列表(dataAccess: grades/actions.getGradeRecordsAction) | | `/teacher/grades/entry` | 批量成绩录入 | server | grade_record:manage | 批量录入(actions: batchCreateGradeRecordsAction, createGradeRecordAction) | | `/teacher/grades/stats` | 成绩统计报表 | server | grade_record:read | 班级统计+排名(dataAccess: getClassGradeStatsAction, getClassRankingAction) | | `/teacher/grades/analytics` | 成绩趋势对比分析 | server | grade_record:read | 分析仪表盘(4 个分析图表并行加载;dataAccess: getGradeTrend, getGradeDistribution, getSubjectComparison, getClassComparison;权限:requirePermission(GRADE_RECORD_READ)) | | `/student/grades` | StudentGradesView | server | grade_record:read | 学生成绩视图(dataAccess: getStudentGradeSummary,DataScope.class_members 仅查自己) | | `/parent/grades` | ParentGradesView | server | grade_record:read | 家长成绩视图(dataAccess: getStudentGradeSummary,按 DataScope.children 过滤) | ### settings/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/settings` | 角色分发设置页 | server | auth_required | 根据权限渲染 Admin/Teacher/Student 设置视图(含 General/Appearance/Security/Notifications tab,Security tab 含 PasswordChangeForm,Notifications tab 含 NotificationPreferencesForm;dataAccess: messaging/notification-preferences.getNotificationPreferences) | | `/settings/security` | SecuritySettingsPage | server | auth_required | 安全设置独立页面(PasswordChangeForm + 安全提示;权限:requireAuth()) | ### diagnostic/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/teacher/diagnostic` | ReportList | client | diagnostic:read | 学情诊断报告列表(reportType/status 过滤器[URL searchParams];dataAccess: diagnostic/data-access-reports.getDiagnosticReports;actions: publishReportAction, deleteReportAction[DIAGNOSTIC_MANAGE];权限:requirePermission(DIAGNOSTIC_READ);DataScope.class_members 仅查看自己报告) | | `/teacher/diagnostic/student/[studentId]` | StudentDiagnosticView | client | diagnostic:read | 学生学情诊断视图(概览卡片+雷达图+强项/弱项+生成报告[DIAGNOSTIC_MANAGE]+最新报告;dataAccess: getStudentMasterySummary, getKnowledgePointStats[班级平均对比], getDiagnosticReports;actions: generateStudentReportAction;权限:getAuthContext + DataScope 二次校验,class_members 仅自己,children 仅子女) | | `/teacher/diagnostic/class/[classId]` | ClassDiagnosticView | client | diagnostic:read | 班级学情诊断视图(概览+知识点热力图+排名表+需重点关注学生+生成班级报告[DIAGNOSTIC_MANAGE];dataAccess: getClassMasterySummary;actions: generateClassReportAction;权限:getAuthContext + DataScope 校验,class_taught 必须包含 classId,class_members/children → notFound) | | `/student/diagnostic` | StudentDiagnosticView | client | diagnostic:read | 学生本人学情诊断视图(概览+雷达图+强项/弱项+最新报告;dataAccess: getStudentMasterySummary(ctx.userId), getDiagnosticReports(studentId=ctx.userId);权限:requirePermission(DIAGNOSTIC_READ),DataScope.class_members 仅查自己) | ### elective/* 路由 | 路由 | 组件 | 类型 | 权限 | 说明 | |------|------|------|------|------| | `/admin/elective` | ElectiveCourseList | server | elective:manage | 管理员选修课程列表(dataAccess: getElectiveCourses(scope=all);actions: deleteElectiveCourseAction, openSelectionAction, closeSelectionAction, runLotteryAction;权限:requirePermission(ELECTIVE_MANAGE)) | | `/admin/elective/create` | ElectiveCourseForm | client | elective:manage | 创建选修课程(actions: createElectiveCourseAction;dataAccess: getSubjectOptions;权限:requirePermission(ELECTIVE_MANAGE)) | | `/admin/elective/[id]/edit` | ElectiveCourseForm (edit) | client | elective:manage | 编辑选修课程(actions: updateElectiveCourseAction;dataAccess: getElectiveCourseById, getSubjectOptions;权限:requirePermission(ELECTIVE_MANAGE)) | | `/teacher/elective` | ElectiveCourseList (teacher) | server | elective:manage | 教师选修课程列表(dataAccess: getElectiveCourses(scope=class_taught/owned, currentUserId);actions: deleteElectiveCourseAction, openSelectionAction, closeSelectionAction, runLotteryAction;权限:requirePermission(ELECTIVE_MANAGE);按 teacherId 过滤) | | `/student/elective` | StudentSelectionView | server | elective:select | 学生选课页面(dataAccess: getAvailableCoursesForStudent, getStudentSelections;actions: selectCourseAction, dropCourseAction;权限:requirePermission(ELECTIVE_SELECT)) | ### API 路由(含速率限制) | 路由 | 方法 | 限流规则 | 说明 | |------|------|---------|------| | `/api/auth/[...nextauth]` | GET, POST | — | NextAuth 认证(登录流程内置 LOGIN 限流: 5次/15分钟) | | `/api/ai/chat` | POST | AI_CHAT: 20次/分钟 | AI 聊天(按 userId 限流,超限返回 429 + Retry-After 头) | | `/api/upload` | POST | UPLOAD: 10次/分钟 | 文件上传(按 userId 限流,超限返回 429 + Retry-After 头) | | `/api/rate-limit-test` | GET | PASSWORD_CHANGE: 5次/分钟 | 限流测试端点(按 userId 限流,用于手动验证 429 响应) | | `/api/export` | POST | — | Excel 导出 | | `/api/import` | POST | — | Excel 解析预览 | | `/api/files/[id]` | GET, DELETE | — | 文件元数据/删除 | | `/api/files/batch-delete` | POST | — | 批量删除文件(需 FILE_DELETE 权限,先删磁盘文件再删 DB 记录) | | `/api/search` | GET | — | 全文检索(questions/textbooks/exams/announcements,需登录;参数 q/type/page/pageSize) | | `/api/onboarding/*` | GET, POST | — | 用户引导 | --- ## DevOps 与脚本 ### CI 配置 (`.gitea/workflows/ci.yml`) | Job | 触发条件 | 说明 | |-----|---------|------| | `build-deploy` | push/PR to main | 构建、测试、部署到 Docker(自托管 runner CDCD) | | `security-scan` | push/PR to main(needs: build-deploy) | 完整安全扫描:npm audit + Snyk + Trivy FS + OWASP ZAP 基线扫描,所有步骤 continue-on-error,上传 security-reports artifact(audit-report.json/trivy-fs-report.json/snyk.sarif) | | `scheduled-backup` | schedule cron `0 2 * * *` | 每天凌晨 2 点执行数据库备份→校验完整性→异地同步,上传 backups/ artifact(保留 30 天) | | `backup-verify` | schedule(needs: scheduled-backup) | 下载备份 artifact,独立校验备份完整性,运行健康检查,上传 backup-verify-report artifact(保留 7 天) | | `weekly-dr-drill` | schedule(needs: backup-verify,每周触发) | 灾备演练:从备份恢复到测试数据库,验证数据完整性,上传 dr-drill-report artifact(保留 90 天) | ### 灾备演练工作流 (`.gitea/workflows/dr-drill.yml`) 独立灾备演练工作流,触发方式:定时 `cron 0 4 * * 1`(每周一凌晨 4 点)/ 手动 `workflow_dispatch`(可指定 backup_file、no_cleanup)。 | 步骤 | 说明 | |------|------| | 安装 MySQL 客户端 | apt-get install mysql-client | | 准备备份 | 下载 db-backup artifact 或现场执行 backup-db.sh | | 执行演练 | 运行 scripts/dr-drill.sh(创建测试库→恢复→完整性检查→冒烟测试→清理→报告) | | 上传报告 | dr-drill-report artifact(保留 90 天) | | 失败通知 | webhook 通知运维团队(DR_NOTIFICATION_WEBHOOK) | ### 安全扫描工作流 (`.gitea/workflows/security.yml`) 独立深度安全扫描工作流,触发方式:定时 `cron 0 3 * * 1`(每周一凌晨 3 点)/ 手动 `workflow_dispatch`(可指定 target_url、skip_dast)。 | 步骤 | 工具 | 类型 | 输出 | |------|------|------|------| | 依赖扫描 | npm audit | 依赖 | audit-report.json | | 深度依赖 + 静态分析 | Snyk(severity-threshold=medium) | 依赖 + 代码 | snyk.sarif | | 文件系统扫描 | Trivy fs | 代码 + 依赖 | trivy-fs-report.json | | 容器镜像扫描 | Trivy image(构建 nextjs-app:scan 镜像) | 容器 | trivy-image-report.json | | DAST | OWASP ZAP baseline | 动态 | 控制台报告 | | 汇总报告 | shell + jq | 汇总 | security-summary.md | 所有报告上传为 artifact `security-reports-full`。安全扫描配置文件:`.gitea/suppressions.json`(Snyk 漏洞抑制)、`.trivyignore`(Trivy CVE 忽略列表)。 ### 运维脚本 (`scripts/`) | 脚本 | 用途 | |------|------| | `scripts/audit.sh` | Bash 依赖审计脚本,运行 `npm audit --audit-level=moderate`,失败时生成 audit-report.json | | `scripts/audit.ps1` | PowerShell 版本依赖审计脚本(Windows 环境) | | `scripts/security-scan.sh` | Bash 本地安全扫描脚本:npm audit + Trivy fs,彩色报告,退出码 0=无高危/1=有高危 | | `scripts/security-scan.ps1` | PowerShell 版本本地安全扫描脚本(Windows 环境) | | `scripts/backup-db.sh` | MySQL 数据库备份脚本,从 DATABASE_URL 解析连接信息,gzip 压缩备份,保留 30 天 | | `scripts/restore-db.sh` | MySQL 数据库恢复脚本,从指定备份文件恢复 | | `scripts/test-backup.sh` | 备份流程测试脚本,执行一次备份并验证 | | `scripts/backup-verify.sh` | 备份完整性校验脚本:检查文件存在/大小/gzip 完整性/SQL 内容结构/SQL 语法(可选,需 DATABASE_URL),退出码 0=通过/1=失败 | | `scripts/backup-offsite-sync.sh` | 异地备份同步脚本:支持 S3/OSS/NFS 后端,同步后校验文件数量,清理远程过期备份(保留 90 天),使用 aws-cli/rclone/ossutil/rsync | | `scripts/dr-drill.sh` | 灾备演练脚本(Bash):创建测试库→从备份恢复→数据完整性检查→冒烟测试→清理→生成报告到 docs/dr/reports/,退出码 0=成功/1=失败 | | `scripts/dr-drill.ps1` | 灾备演练脚本(Windows PowerShell 5.1+):功能同 Bash 版本 | | `scripts/failover.sh` | 故障切换脚本:检测主库健康→提升备库→更新应用配置→重启应用→验证切换,支持手动/半自动/演练模式 | | `scripts/health-check.sh` | 健康检查脚本:检查应用 HTTP/数据库连接/磁盘空间/备份新鲜度,输出 JSON 报告,退出码 0=健康/1=异常 | ### package.json 脚本 | 脚本 | 命令 | 说明 | |------|------|------| | `audit` | `npm audit --audit-level=moderate` | 依赖安全审计 | | `audit:report` | `npm audit --json > audit-report.json` | 生成 JSON 审计报告 | | `security:audit` | `npm audit --audit-level=moderate` | 依赖安全审计(security 别名) | | `security:scan` | `bash scripts/security-scan.sh` | 本地完整安全扫描(npm audit + Trivy fs) | | `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` | 灾备演练(Bash) | | `dr:drill:ps1` | `powershell -ExecutionPolicy Bypass -File scripts/dr-drill.ps1` | 灾备演练(PowerShell) | | `dr:health-check` | `bash scripts/health-check.sh` | 健康检查(JSON 报告) | | `dr:failover` | `bash scripts/failover.sh` | 故障切换 | ### 灾备文档 (`docs/dr/`) | 文档 | 用途 | |------|------| | `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 生成) | ### 灾备环境变量 (`.env.example`) | 变量 | 用途 | |------|------| | `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) | --- ## E2E 测试 (`tests/e2e/`) | 测试文件 | 覆盖范围 | 依赖 | |---------|---------|------| | `smoke-auth.spec.ts` | 登录/注册页面控件渲染冒烟测试 | 无需 DB | | `auth-business-flow.spec.ts` | 注册→登录→访问受保护区域完整流程 | DATABASE_URL | | `full-route-regression.spec.ts` | 全路由清单完整性 + 公开/受保护路由守卫 | 无需 DB(守卫测试) | | `auth.spec.ts` | 认证页面(登录/注册/隐私/协议)渲染 + 未认证重定向 | 无需 DB | | `navigation.spec.ts` | admin/teacher/student 导航链接无 404 | DATABASE_URL + 测试账号 | | `announcements.spec.ts` | 公告页面未认证重定向 + 登录后渲染 | 部分需 DATABASE_URL | | `grades.spec.ts` | 成绩页面未认证重定向 + 登录后渲染 | 部分需 DATABASE_URL | ### Playwright 配置 (`playwright.config.ts`) - `testDir`: `./tests`(顶层,由各 project 通过 `testDir` 限定范围) - `baseURL`: `http://127.0.0.1:3000` - `webServer`: 自动启动 `npm run dev`,端口 3000,超时 180s - `webServer.env`: 注入 `SKIP_ENV_VALIDATION=1`、`NEXTAUTH_SECRET`、`NEXTAUTH_URL`、`DATABASE_URL`(测试库) - `projects`: - `chromium`(E2E 测试,`testDir: ./tests/e2e`,CI 通道为 undefined,本地为 chrome) - `visual-chromium`(视觉回归测试,`testDir: ./tests/visual`,CI 通道为 undefined,本地为 chrome) - `snapshotPathTemplate`: `{testDir}/__screenshots__/{testFilePath}/{arg}{ext}` - `expect.toHaveScreenshot`: `maxDiffPixelRatio: 0.01`、`animations: "disabled"`、`caret: "hide"` - `retries`: CI 2 次,本地 0 次 - `workers`: CI 2 个,本地默认 --- ## 视觉回归测试 (`tests/visual/`) | 测试文件 | 覆盖范围 | 依赖 | |---------|---------|------| | `homepage.spec.ts` | 登录页在 desktop/tablet/mobile × light/dark 下的快照 | 无需 DB | | `admin-dashboard.spec.ts` | 管理员仪表盘整页 + 侧边栏/主内容区组件快照 | DATABASE_URL + admin 账号 | | `teacher-dashboard.spec.ts` | 教师仪表盘整页 + 侧边栏/主内容区组件快照 | DATABASE_URL + teacher 账号 | | `student-dashboard.spec.ts` | 学生仪表盘整页 + 侧边栏/主内容区组件快照 | DATABASE_URL + student 账号 | ### 视觉测试辅助 (`tests/visual/helpers/`) | 文件 | 用途 | |------|------| | `auth.ts` | 登录辅助 `setupAuthState(role)`、`loginByUI(page, role)`,测试账号默认 admin@xiaoxue.edu.cn / 123456 | | `visual-helpers.ts` | `setViewport`、`setTheme`、`waitForPageReady`、`maskDynamicElements`、`buildMaskOption` | ### 视觉测试配置 (`tests/visual/visual.config.ts`) - 视口: desktop 1920×1080、tablet 768×1024、mobile 375×812 - 主题: light、dark - 快照目录: `tests/visual/__screenshots__/` - storageState 目录: `tests/visual/.auth/`(已加入 .gitignore) - 默认容差: `maxDiffPixelRatio: 0.01`