Files
NextEdu/docs/architecture/004_architecture_impact_map.md
SpecialX 3b6272c99d feat: 完成 P1 全部功能 + 修复 proxy 导出 + 切换 MySQL 端口至 14013
## P1 功能(20 项)
- 站内消息系统、家长仪表盘、学生考勤管理
- Excel 导入导出、用户批量导入、成绩导出
- 排课规则+自动排课+课表调整
- 成绩趋势+对比分析、密码安全策略、速率限制
- 数据变更日志、文件预览+存储策略、全文检索
- 依赖审计集成 CI、数据库定时备份、E2E 测试完善
- 通知偏好管理

## 基础设施修复
- src/proxy.ts: 将 middleware 导出重命名为 proxy(Next.js 16 要求)
- .env: MySQL 端口从 13002 切换至 14013
- scripts/create-db.ts: 新增数据库初始化脚本

## 架构文档同步
- 004_architecture_impact_map.md 和 005_architecture_data.json
  完整记录所有新增表、模块、路由、权限、依赖关系
2026-06-17 13:44:37 +08:00

2894 lines
154 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`: unknownHTTP 请求体
- 功能:解析并校验 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<boolean>` / `(providerId, overrides?) => Promise<boolean>`
- 功能:测试 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<AuthContext>`
- 功能获取当前用户的完整认证上下文userId, roles, permissions, dataScope
- 依赖:`auth` (NextAuth), `shared/db` (查询角色/班级/年级关系)
- 被以下模块使用:**所有业务模块**的 Server Actions
#### `requirePermission`
- 签名:`requirePermission(permission: Permission): Promise<AuthContext>`
- 参数说明:`permission`: Permission来自 `shared/types/permissions` 的权限常量
- 功能:断言当前用户拥有指定权限,否则抛出 PermissionDeniedError
- 依赖:`getAuthContext`
- 被以下模块使用:**所有业务模块**的 Server Actionsexams, homework, questions, textbooks, classes, school, settings, audit, announcements, files, grades, attendance
#### `checkPermission`
- 签名:`checkPermission(permission: Permission): Promise<{ allowed: boolean; ctx: AuthContext }>`
- 功能:非抛出版权限检查
- 依赖:`getAuthContext`
- 被以下模块使用:待扩展
#### `requireAuth`
- 签名:`requireAuth(): Promise<AuthContext>`
- 功能:仅断言用户已登录
- 依赖:`getAuthContext`
- 被以下模块使用:待扩展
#### `resolvePermissions`
- 签名:`resolvePermissions(roleNames: string[]): Permission[]`
- 参数说明:`roleNames`: string[],用户的角色名称列表
- 功能:合并多角色的权限列表(去重)
- 依赖:`ROLE_PERMISSIONS` 常量
- 被以下模块使用auth.ts (JWT callback)
#### `logAudit`
- 签名:`logAudit(params: LogAuditParams): Promise<void>`
- 参数说明:`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<void>`
- 参数说明:`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<Buffer>`
- 参数说明:`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[]>`
- 返回ParsedSheet[],每个含 `sheetName`/`rows`
- 功能:从 Buffer 解析 Excel 文件,首行作为表头,返回每 sheet 的行记录数组
- 依赖:`exceljs`
- 被以下模块使用users/actions.importUsersAction, `app/api/import/route.ts`
#### `generateTemplate`
- 签名:`(params: { sheets: TemplateSheet[] }): Promise<Buffer>`
- 参数说明:`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<string, string>`
- 功能:将限流结果转为 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<void>`
- 参数说明:`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<string>``read(storagePath) => Promise<Buffer>``delete(storagePath) => Promise<void>``exists(storagePath) => Promise<boolean>``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`
### 导出常量与实例
#### `Permissions` (常量对象)
- 文件:`types/permissions.ts`
- 定义47 个权限常量(`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` 等)
- 被使用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 无排课权限
- 被使用:`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`
### 导出 Hooks
#### `useActionWithToast`
- 签名:`useActionWithToast<T>(): { isPending, execute }`
- 功能:包装 Server Action + toast 反馈
- 被使用:待扩展
#### `useDebounce`
- 签名:`useDebounce<T>(value: T, delay?: number): T`
- 被使用:搜索输入框等
#### `useMediaQuery`
- 签名:`useMediaQuery(query: string): boolean`
- 被使用:响应式布局判断
#### `useLocalStorage`
- 签名:`useLocalStorage<T>(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<T>`
- 定义:`{ success: boolean; message?: string; errors?: Record<string, string[]>; 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 |
---
## 模块auth
### 模块职责
处理用户认证(登录/注册/JWT/Session提供 NextAuth 实例和中间件。通过 events 回调记录登录日志。
集成密码安全策略(账户锁定、失败登录追踪)和登录速率限制。
### 导出函数
#### `auth`
- 签名:`auth(): Promise<Session | null>` (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<NextResponse>`
- 功能:基于权限点的路由守卫,未登录重定向 /login无权限重定向角色首页
- 依赖:`getToken` (next-auth/jwt), `Permissions`
- 被使用Next.js middleware 层
---
## 模块exams
### 模块职责
考试全生命周期管理:创建(手动/AI、编辑、预览、发布、删除、复制。
### 导出函数 (actions.ts)
#### `createExamAction`
- 签名:`(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 参数说明:`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<AiPreviewData> | null, formData: FormData) => Promise<ActionState<AiPreviewData>>`
- 功能AI 预览试卷(不持久化)
- 依赖:`requirePermission(EXAM_AI_GENERATE)`, `ai-pipeline.generateAiPreviewData`
- 被使用exam-ai-generator.tsx (via useExamPreview)
#### `regenerateAiQuestionAction`
- 签名:`(prevState: ActionState<AiRewriteQuestionData> | null, formData: FormData) => Promise<ActionState<AiRewriteQuestionData>>`
- 功能AI 重写单个题目
- 依赖:`requirePermission(EXAM_AI_GENERATE)`, `ai-pipeline.regenerateAiQuestionByInstruction`
- 被使用exam-preview-question-editor.tsx
#### `updateExamAction`
- 签名:`(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 功能:更新考试信息,含资源归属校验(非 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<ActionState<...>>`
- 功能:获取考试预览数据
- 依赖:`requirePermission(EXAM_READ)`, `shared/db`
- 被使用exam-viewer.tsx
#### `getSubjectsAction`
- 签名:`() => Promise<ActionState<{ id: string; name: string }[]>>`
- 依赖:`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<Exam[]>`
- 参数说明:`scope` 来自 `auth-guard.getAuthContext().dataScope`
- 功能:查询考试列表,含数据权限过滤
- 依赖:`shared/db`, `DataScope`
- 被使用teacher/exams/all/page.tsx, homework 创建页面
#### `getExamById`
- 签名:`getExamById(id: string, scope?: DataScope): Promise<Exam | null>`
- 被使用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 生成题目的校验 schematype, 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<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 功能:从已有考试创建作业
- 依赖:`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<HomeworkAssignmentListItem[]>`
- 依赖:`shared/db`, `DataScope`
- 被使用teacher 作业列表页, homework-assignment-form.tsx
#### `getStudentHomeworkAssignments`
- 签名:`(studentId: string) => Promise<StudentHomeworkAssignmentListItem[]>`
- 被使用student/dashboard
#### `getStudentDashboardGrades`
- 签名:`(studentId: string) => Promise<StudentDashboardGradeProps>`
- 被使用dashboard/data-access.ts (学生仪表盘)
#### `getHomeworkAssignmentAnalytics`
- 签名:`(assignmentId: string) => Promise<HomeworkAssignmentAnalytics | null>`
- 被使用homework 错误分析组件
#### `getHomeworkAssignmentById`
- 签名:`(assignmentId: string, scope?: DataScope) => Promise<HomeworkAssignment | null>`
- 功能:按 ID 获取作业详情(含数据权限过滤)
- 被使用homework 详情/编辑页面, homework-take-view.tsx
#### `getHomeworkSubmissionDetails`
- 签名:`(submissionId: string, scope?: DataScope) => Promise<HomeworkSubmissionDetails | null>`
- 功能:获取作业提交详情(含答案列表、学生信息)
- 被使用homework-grading-view.tsx
#### `getDemoStudentUser`
- 签名:`() => Promise<{ id: string; name: string } | null>`
- 功能:获取演示学生用户(用于 demo/预览场景)
- 被使用homework demo 相关页面
#### `getStudentHomeworkTakeData`
- 签名:`(studentId: string, assignmentId: string) => Promise<StudentHomeworkTakeData | null>`
- 功能:获取学生作答作业所需完整数据(作业、题目、已答内容、提交状态)
- 被使用homework-take-view.tsx
### Schema (schema.ts)
#### `CreateHomeworkAssignmentSchema`
- 类型Zod schema
- 定义:创建作业的校验 schemasourceExamId, title, classIds, dueAt, allowLate 等)
- 被使用:`createHomeworkAssignmentAction`
#### `CreateHomeworkAssignmentInput`
- 类型TypeScript 类型(基于 `CreateHomeworkAssignmentSchema` 推断)
- 被使用:`createHomeworkAssignmentAction`, homework-assignment-form.tsx
#### `GradeHomeworkSchema`
- 类型Zod schema
- 定义:批改作业的校验 schemasubmissionId, 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<string> | undefined, formData: FormData | CreateQuestionInput) => Promise<ActionState<string>>`
- 依赖:`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<KnowledgePointOption[]>`
- 依赖:`requirePermission(QUESTION_READ)`, `shared/db`
- 被使用create-question-dialog.tsx
### 导出函数 (data-access.ts)
#### `getQuestions`
- 签名:`(params: GetQuestionsParams & { scope?: DataScope }) => Promise<Question[]>`
- 功能:查询题目列表(含知识点关联、数据权限过滤)
- 依赖:`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
- 定义:题目基础校验 schematype, 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<Textbook[]>` | teacher/textbooks/page.tsx |
| `getTextbookById` | `(id: string) => Promise<Textbook \| undefined>` | teacher/textbooks/[id]/page.tsx |
| `getChaptersByTextbookId` | `(textbookId: string) => Promise<Chapter[]>` | textbook-reader.tsx |
| `getKnowledgePointsByChapterId` | `(chapterId: string) => Promise<KnowledgePoint[]>` | textbook-reader.tsx |
| `getKnowledgePointsByTextbookId` | `(textbookId: string) => Promise<KnowledgePoint[]>` | textbook-reader.tsx |
#### 写操作函数 (data-access.ts)
| 函数 | 签名 | 被使用 |
|------|------|--------|
| `persistTextbook` | `(input: { textbookId, title, subject, grade, publisher, creatorId }) => Promise<void>` | createTextbookAction |
| `updateTextbookMeta` | `(textbookId, input: { title?, subject?, grade?, publisher? }) => Promise<void>` | updateTextbookAction |
| `deleteTextbookRecord` | `(textbookId) => Promise<void>` | deleteTextbookAction |
| `persistChapter` | `(input: { chapterId, textbookId, parentId?, title, order }) => Promise<void>` | createChapterAction |
| `updateChapterContent` | `(chapterId, content, textbookId) => Promise<void>` | updateChapterContentAction |
| `deleteChapterRecord` | `(chapterId, textbookId) => Promise<void>` | deleteChapterAction |
| `reorderChapters` | `(chapterId, newIndex, parentId, textbookId) => Promise<void>` | reorderChaptersAction |
| `persistKnowledgePoint` | `(input: { kpId, chapterId, textbookId, name, description?, anchorText? }) => Promise<void>` | createKnowledgePointAction |
| `updateKnowledgePointRecord` | `(kpId, textbookId, input: { name?, description?, anchorText? }) => Promise<void>` | updateKnowledgePointAction |
| `deleteKnowledgePointRecord` | `(kpId, textbookId) => Promise<void>` | 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<TeacherClass[]>` | teacher/classes/my, dashboard |
| `getAdminClasses` | `() => Promise<AdminClassListItem[]>` | admin 班级管理 |
| `getGradeManagedClasses` | `(userId) => Promise<AdminClassListItem[]>` | grade_head 班级管理 |
| `getStudentClasses` | `(studentId) => Promise<StudentEnrolledClass[]>` | student/dashboard |
| `getStudentSchedule` | `(studentId) => Promise<StudentScheduleItem[]>` | student 课表 |
| `getClassStudents` | `(classId, scope?) => Promise<ClassStudent[]>` | teacher/classes/students |
| `getClassSchedule` | `(classId) => Promise<ClassScheduleItem[]>` | teacher/classes/schedule |
| `getClassHomeworkInsights` | `(classId) => Promise<ClassHomeworkInsights \| null>` | classes 作业洞察 |
| `getGradeHomeworkInsights` | `(gradeId) => Promise<GradeHomeworkInsights \| null>` | 年级作业洞察 |
| `getClassById` | `(classId, scope?) => Promise<Class \| null>` | 班级详情页 |
| `getClassDetails` | `(classId, scope?) => Promise<ClassDetails \| null>` | 班级详情(含学生数、教师数) |
| `getClassSubjectTeachers` | `(classId) => Promise<ClassSubjectTeacher[]>` | 班级学科教师分配 |
| `getStudentEnrollmentStatus` | `(classId, studentId) => Promise<EnrollmentStatus \| null>` | 注册状态查询 |
| `validateInvitationCode` | `(code) => Promise<{ valid: boolean; classId?: string }>` | 邀请码校验 |
| `getClassInsights` | `(classId) => Promise<ClassInsights \| null>` | 班级洞察数据 |
#### 写操作函数
| 函数 | 签名 | 被使用 |
|------|------|--------|
| `persistTeacherClass` | `(input: { classId, teacherId, name, gradeId, ... }) => Promise<void>` | createTeacherClassAction |
| `updateTeacherClassRecord` | `(classId, input) => Promise<void>` | updateTeacherClassAction |
| `deleteTeacherClassRecord` | `(classId) => Promise<void>` | deleteTeacherClassAction |
| `persistGradeClass` | `(input: { classId, gradeId, ... }) => Promise<void>` | createGradeClassAction |
| `updateGradeClassRecord` | `(classId, input) => Promise<void>` | updateGradeClassAction |
| `deleteGradeClassRecord` | `(classId) => Promise<void>` | 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<void>` | setStudentEnrollmentStatusAction |
| `persistClassScheduleItem` | `(input: { scheduleId, classId, ... }) => Promise<void>` | createClassScheduleItemAction |
| `updateClassScheduleItem` | `(scheduleId, input) => Promise<void>` | updateClassScheduleItemAction |
| `deleteClassScheduleItem` | `(scheduleId) => Promise<void>` | deleteClassScheduleItemAction |
| `persistAdminClass` | `(input: { classId, ... }) => Promise<void>` | createAdminClassAction |
| `updateAdminClassRecord` | `(classId, input) => Promise<void>` | updateAdminClassAction |
| `deleteAdminClassRecord` | `(classId) => Promise<void>` | deleteAdminClassAction |
| `assignSubjectTeacher` | `(classId, teacherId, subjectId) => Promise<void>` | 学科教师分配 |
| `removeClassSubjectTeacher` | `(classId, teacherId, subjectId) => Promise<void>` | 移除学科教师 |
| `bulkEnrollStudents` | `(classId, studentIds[]) => Promise<void>` | 批量注册学生 |
| `transferStudent` | `(fromClassId, toClassId, studentId) => Promise<void>` | 学生转班 |
| `getClassInvitationInfo` | `(classId) => Promise<{ code, expiresAt? }>` | 邀请信息查询 |
| `countClassStudents` | `(classId) => Promise<number>` | 学生计数 |
### 类型/接口
#### `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 actionscreateSchoolAction/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
- 定义:创建学校的校验 schemaname, code
- 被使用:`createSchoolAction`
#### `CreateGradeSchema`
- 类型Zod schema
- 定义:创建年级的校验 schemaschoolId, name, order, gradeHeadId?, teachingHeadId?
- 被使用:`createGradeAction`
#### `CreateDepartmentSchema`
- 类型Zod schema
- 定义:创建部门的校验 schemaname, description?
- 被使用:`createDepartmentAction`
#### `CreateAcademicYearSchema`
- 类型Zod schema
- 定义:创建学年的校验 schemaname, 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<AdminDashboardData>`
- 依赖:`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<Role, NavItem[]>`
- 每个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
- student 角色菜单包含 "My Grades" 项icon: GraduationCap, href: /student/grades, permission: Permissions.GRADE_RECORD_READ
- student 角色菜单包含 "Attendance" 项icon: CalendarCheck, href: /student/attendance, permission: Permissions.ATTENDANCE_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<null>, formData: FormData) => Promise<ActionState<null>>`
- 权限:`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/attendanceSwitch 切换 + 隐藏 checkbox 提交useActionState 调用 updateNotificationPreferencesActiontoast 反馈) |
| `admin-settings-view.tsx` | 管理员设置视图General/Appearance/Security/Notifications tabSecurity 含 PasswordChangeFormNotifications 含 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<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 功能更新当前用户个人资料name, phone, address, gender, age 等)
- 依赖:`requireAuth()`, `shared/db`
- 被使用profile-form.tsx
> 注:本模块仅使用 `requireAuth()` 校验登录状态,不涉及权限点(用户只能修改自己的资料)。
#### `downloadUserTemplateAction`
- 签名:`() => Promise<ActionState<string>>`
- 权限:`requirePermission(USER_MANAGE)`
- 功能:生成用户导入模板(返回 base64 编码的 Excel
- 依赖:`requirePermission`, `import-export.generateUserImportTemplate`
- 被使用components/user-import-dialog.tsx
#### `importUsersAction`
- 签名:`(prevState: ActionState<UserImportResult> | null, formData: FormData) => Promise<ActionState<UserImportResult>>`
- 权限:`requirePermission(USER_MANAGE)`
- 功能:导入用户:接收文件,解析+验证+批量创建(默认密码 123456
- 依赖:`requirePermission`, `shared/lib/excel.parseExcel`, `import-export.parseUserImportData`, `import-export.batchImportUsers`
- 被使用components/user-import-dialog.tsx
#### `exportUsersAction`
- 签名:`(role?: string) => Promise<ActionState<{ buffer: string; filename: string }>>`
- 权限:`requirePermission(USER_MANAGE)`
- 功能:导出用户列表(返回 base64 编码的 Excel
- 依赖:`requirePermission`, `import-export.exportUsersToExcel`
- 被使用:待扩展
### 导出函数 (data-access.ts)
#### `getUserProfile`
- 签名:`(userId: string) => Promise<UserProfile | null>`
- 功能:获取用户个人资料(含 name, email, phone, address, gender, age, grade, department 等)
- 依赖:`shared/db`
- 被使用profile/page.tsx, profile-form.tsx
### 导出函数 (import-export.ts)
#### `generateUserImportTemplate`
- 签名:`() => Promise<Buffer>`
- 功能:生成用户导入模板(列:姓名/邮箱/角色/手机/班级邀请码,含示例行)
- 依赖:`shared/lib/excel.generateTemplate`
- 被使用:`downloadUserTemplateAction`
#### `parseUserImportData`
- 签名:`(rows: Record<string, unknown>[]) => UserImportValidation`
- 功能:解析并验证导入行(校验姓名/邮箱格式/角色枚举/邀请码仅 student
- 依赖:无
- 被使用:`importUsersAction`
#### `batchImportUsers`
- 签名:`(records: UserImportRecord[]) => Promise<UserImportResult>`
- 功能:批量创建用户(默认密码 123456 bcrypt 哈希,自动创建 usersToRolesstudent 通过邀请码自动加入班级)
- 依赖:`shared/db`, `bcryptjs`, `@paralleldrive/cuid2`
- 被使用:`importUsersAction`
#### `exportUsersToExcel`
- 签名:`(params: { scope: DataScope; role?: string }) => Promise<Buffer>`
- 功能:导出用户列表到 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<PaginatedResult<AuditLog>>`
- 参数说明:`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<PaginatedResult<LoginLog>>`
- 参数说明:`params`: LoginLogQueryParams含 userId?, action?, status?, page?, pageSize?, startDate?, endDate?
- 功能:分页查询登录日志(支持按操作/状态/日期范围过滤)
- 依赖:`shared/db` (loginLogs 表)
- 被使用app/(dashboard)/admin/audit-logs/login-logs/page.tsx
#### `getAuditModuleOptions`
- 签名:`getAuditModuleOptions(): Promise<string[]>`
- 功能:获取操作日志中所有不同的 module 值(用于筛选下拉框)
- 依赖:`shared/db` (auditLogs 表)
- 被使用app/(dashboard)/admin/audit-logs/page.tsx
#### `getDataChangeLogs`
- 签名:`getDataChangeLogs(params?: DataChangeLogQueryParams): Promise<PaginatedResult<DataChangeLog>>`
- 参数说明:`params`: DataChangeLogQueryParams含 tableName?, recordId?, action?, userId?, page?, pageSize?, startDate?, endDate?
- 功能:分页查询数据变更日志(支持按表名/记录ID/操作/用户/日期范围过滤)
- 依赖:`shared/db` (dataChangeLogs 表)
- 被使用:`getDataChangeLogsAction`
#### `getDataChangeStats`
- 签名:`getDataChangeStats(): Promise<DataChangeStat[]>`
- 功能:按 tableName 分组统计数据变更日志数量(用于统计卡片)
- 依赖:`shared/db` (dataChangeLogs 表)
- 被使用:`getDataChangeLogsAction`
#### `getDataChangeTableOptions`
- 签名:`getDataChangeTableOptions(): Promise<string[]>`
- 功能:获取数据变更日志中所有不同的 tableName 值(用于筛选下拉框)
- 依赖:`shared/db` (dataChangeLogs 表)
- 被使用:`getDataChangeLogsAction`
#### `getDataChangeLogsForExport`
- 签名:`getDataChangeLogsForExport(params?: DataChangeLogQueryParams): Promise<DataChangeLog[]>`
- 功能:导出用:按条件查询全部数据变更日志(取前 100 条,无分页上限封顶)
- 依赖:`getDataChangeLogs`
- 被使用:`exportDataChangeLogsAction`
#### `getAuditLogsForExport`
- 签名:`getAuditLogsForExport(params?: AuditLogQueryParams): Promise<AuditLog[]>`
- 功能:导出用:按条件查询全部操作日志(取前 100 条)
- 依赖:`getAuditLogs`
- 被使用:`exportAuditLogsAction`
#### `getLoginLogsForExport`
- 签名:`getLoginLogsForExport(params?: LoginLogQueryParams): Promise<LoginLog[]>`
- 功能:导出用:按条件查询全部登录日志(取前 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<T>`
- 定义:分页结果接口(含 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<Announcement[]>` | admin/announcements, announcements 页面 |
| `getAnnouncementById` | `(id: string) => Promise<Announcement \| null>` | 编辑页面 |
### Schema (schema.ts)
#### `CreateAnnouncementSchema`
- 类型Zod schema
- 定义:创建公告的校验 schematitle, 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<FileAttachment \| null>` | `app/api/upload/route.ts` |
| `getFileAttachment` | `(id: string) => Promise<FileAttachment \| null>` | `app/api/files/[id]/route.ts` |
| `getFileAttachmentsByTarget` | `(targetType: string, targetId: string) => Promise<FileAttachment[]>` | 按关联资源查询文件列表 |
| `getFileAttachmentsByUploader` | `(uploaderId: string) => Promise<FileAttachment[]>` | 按上传者查询文件列表 |
| `getAllFileAttachments` | `(limit?: number) => Promise<FileAttachment[]>` | `app/(dashboard)/admin/files/page.tsx` |
| `deleteFileAttachment` | `(id: string) => Promise<boolean>` | `app/api/files/[id]/route.ts` |
| `deleteFileAttachments` | `(ids: string[]) => Promise<BatchDeleteResult>` | `app/api/files/batch-delete/route.ts`(批量删除 DB 记录,失败回退逐条删除) |
| `getFileAttachmentsWithFilters` | `(params: FileAttachmentQueryParams) => Promise<FileAttachment[]>` | 管理员文件筛选mimeType 精确/前缀匹配 + originalName/filename 模糊搜索 + limit/offset 分页) |
| `getFileStats` | `() => Promise<FileStats>` | 文件统计(总数、总大小、按 mimeType 分组) |
| `getFileAttachmentsByIds` | `(ids: string[]) => Promise<FileAttachment[]>` | `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<string, unknown> }`
#### `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 | 导出成绩到 Exceldetail=成绩明细+统计汇总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<GradeRecordListItem[]>` | teacher/grades/page.tsx, getGradeRecordsAction |
| `getGradeRecordById` | `(id: string) => Promise<GradeRecord \| null>` | getGradeRecordByIdAction |
| `createGradeRecord` | `(data: CreateGradeRecordInput, recordedBy: string) => Promise<string>` | createGradeRecordAction |
| `batchCreateGradeRecords` | `(data: BatchCreateGradeRecordInput, recordedBy: string) => Promise<number>` | batchCreateGradeRecordsAction |
| `updateGradeRecord` | `(id: string, data: UpdateGradeRecordInput) => Promise<void>` | updateGradeRecordAction |
| `deleteGradeRecord` | `(id: string) => Promise<void>` | deleteGradeRecordAction |
| `getClassGradeStats` | `(classId, subjectId?, examId?) => Promise<GradeStats \| null>` | getClassGradeStatsAction |
| `getStudentGradeSummary` | `(studentId: string) => Promise<StudentGradeSummary \| null>` | getStudentGradeSummaryAction, student/grades, parent/grades |
| `getClassRanking` | `(classId, subjectId?, examId?) => Promise<ClassRankingItem[]>` | getClassRankingAction, teacher/grades/stats |
| `getClassStudentsForEntry` | `(classId: string) => Promise<Array<{ id, name, email }>>` | teacher/grades/entry |
| `getClassGradeStatsWithMeta` | `(classId, subjectId?, examId?) => Promise<ClassGradeStats \| null>` | teacher/grades/stats |
### 导出函数 (data-access-analytics.ts)
| 函数 | 签名 | 被使用 |
|------|------|--------|
| `getGradeTrend` | `(params: { studentId; subjectId?; semester?; scope: DataScope }) => Promise<GradeTrendResult>` | getGradeTrendAction, teacher/grades/analytics |
| `getClassComparison` | `(params: { gradeId; subjectId; examId?; scope: DataScope }) => Promise<ClassComparisonItem[]>` | getClassComparisonAction, teacher/grades/analytics |
| `getSubjectComparison` | `(params: { classId; examId?; semester?; scope: DataScope }) => Promise<SubjectComparisonItem[]>` | getSubjectComparisonAction, teacher/grades/analytics |
| `getGradeDistribution` | `(params: { classId; subjectId?; examId?; scope: DataScope }) => Promise<GradeDistributionResult>` | getGradeDistributionAction, teacher/grades/analytics |
### 导出函数 (data-access-ranking.ts)
| 函数 | 签名 | 被使用 |
|------|------|--------|
| `getRankingTrend` | `(studentId: string, subjectId?, semester?) => Promise<RankingTrendResult \| null>` | getRankingTrendAction |
### 导出函数 (export.ts)
#### `exportGradeRecordsToExcel`
- 签名:`(params: { classId: string; subjectId?: string; examId?: string; scope: DataScope }) => Promise<Buffer>`
- 功能导出成绩单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<Buffer>`
- 功能:导出班级成绩总表(多科目横向对比,含总分/平均分/排名列)
- 依赖:`shared/db`, `shared/lib/excel.exportToExcel`, `data-access.getGradeRecords`
- 被使用:`exportGradesAction`
#### `formatDateForFile`
- 签名:`(d?: Date) => string`
- 功能:格式化日期为 `YYYY-MM-DD` 用于文件名
- 依赖:无
- 被使用:`exportGradesAction`
### Schema (schema.ts)
#### `CreateGradeRecordSchema`
- 类型Zod schema
- 定义:创建成绩记录的校验 schemastudentId, classId, subjectId, examId?, title, score, fullScore?, type?, semester?, remark?
- 被使用:`createGradeRecordAction`
#### `BatchCreateGradeRecordSchema`
- 类型Zod schema
- 定义:批量录入成绩的校验 schemaclassId, subjectId, examId?, title, fullScore?, type?, semester?, records[{ studentId, score, remark? }]
- 被使用:`batchCreateGradeRecordsAction`
#### `UpdateGradeRecordSchema`
- 类型Zod schema
- 定义:更新成绩记录的校验 schematitle?, 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, typeResult: 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, percentageResult: buckets[], totalCount
- 被使用getGradeDistribution, getGradeDistributionAction, grade-distribution-chart
#### `RankingTrendPoint` / `RankingTrendResult`
- 定义排名趋势类型Point: title, date, rank, totalStudents, scoreResult: 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<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 权限:`requirePermission(COURSE_PLAN_MANAGE)`
- 功能:创建课程计划
- 依赖:`requirePermission`, `shared/db`, `data-access.createCoursePlan`
- 被以下模块使用course-plan-form.tsx
#### `updateCoursePlanAction`
- 签名:`(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 权限:`requirePermission(COURSE_PLAN_MANAGE)`
- 功能:更新课程计划
- 依赖:`requirePermission`, `shared/db`, `data-access.updateCoursePlan`
- 被以下模块使用course-plan-form.tsx
#### `deleteCoursePlanAction`
- 签名:`(id: string) => Promise<ActionState<string>>`
- 权限:`requirePermission(COURSE_PLAN_MANAGE)`
- 功能:删除课程计划
- 依赖:`requirePermission`, `shared/db`, `data-access.deleteCoursePlan`
- 被以下模块使用course-plan-detail.tsx
#### `getCoursePlansAction`
- 签名:`(params?: GetCoursePlansParams) => Promise<ActionState<CoursePlanListItem[]>>`
- 权限:`requirePermission(COURSE_PLAN_READ)`
- 功能:获取课程计划列表
- 依赖:`requirePermission`, `data-access.getCoursePlans`
#### `getCoursePlanAction`
- 签名:`(id: string) => Promise<ActionState<CoursePlanWithItems>>`
- 权限:`requirePermission(COURSE_PLAN_READ)`
- 功能:获取课程计划详情(含周计划条目)
- 依赖:`requirePermission`, `data-access.getCoursePlanById`
#### `createCoursePlanItemAction`
- 签名:`(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 权限:`requirePermission(COURSE_PLAN_MANAGE)`
- 功能:创建周计划条目
- 依赖:`requirePermission`, `shared/db`, `data-access.createCoursePlanItem`
- 被以下模块使用course-plan-item-editor.tsx
#### `updateCoursePlanItemAction`
- 签名:`(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>`
- 权限:`requirePermission(COURSE_PLAN_MANAGE)`
- 功能:更新周计划条目
- 依赖:`requirePermission`, `shared/db`, `data-access.updateCoursePlanItem`
- 被以下模块使用course-plan-item-editor.tsx
#### `deleteCoursePlanItemAction`
- 签名:`(id: string) => Promise<ActionState<string>>`
- 权限:`requirePermission(COURSE_PLAN_MANAGE)`
- 功能:删除周计划条目
- 依赖:`requirePermission`, `shared/db`, `data-access.deleteCoursePlanItem`
- 被以下模块使用course-plan-item-editor.tsx
#### `toggleCoursePlanItemCompletedAction`
- 签名:`(id: string, completed: boolean) => Promise<ActionState<string>>`
- 权限:`requirePermission(COURSE_PLAN_MANAGE)`
- 功能:切换周计划条目完成状态
- 依赖:`requirePermission`, `shared/db`, `data-access.updateCoursePlanItem`
- 被以下模块使用course-plan-detail.tsx
### 导出函数 (data-access.ts)
#### `getCoursePlans`
- 签名:`(params?: GetCoursePlansParams) => Promise<CoursePlanListItem[]>`
- 功能:查询课程计划列表(含班级/科目/教师名称,支持按 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<CoursePlanWithItems | null>`
- 功能:按 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<string>`
- 功能:创建课程计划记录
- 依赖:`shared.db`, `shared.db.schema.coursePlans`, `@paralleldrive/cuid2`
- 被以下模块使用createCoursePlanAction
#### `updateCoursePlan`
- 签名:`(id: string, data: Partial<UpdateCoursePlanInput>) => Promise<void>`
- 功能:更新课程计划(部分字段)
- 依赖:`shared.db`, `shared.db.schema.coursePlans`
- 被以下模块使用updateCoursePlanAction
#### `deleteCoursePlan`
- 签名:`(id: string) => Promise<void>`
- 功能:删除课程计划(级联删除周计划条目)
- 依赖:`shared.db`, `shared.db.schema.coursePlans`
- 被以下模块使用deleteCoursePlanAction
#### `createCoursePlanItem` / `updateCoursePlanItem` / `deleteCoursePlanItem`
- 签名:`(data: CreateCoursePlanItemInput) => Promise<string>` / `(id: string, data: Partial<UpdateCoursePlanItemInput>) => Promise<void>` / `(id: string) => Promise<void>`
- 功能:周计划条目 CRUD
- 依赖:`shared.db`, `shared.db.schema.coursePlanItems`, `@paralleldrive/cuid2`
- 被以下模块使用createCoursePlanItemAction, updateCoursePlanItemAction, deleteCoursePlanItemAction, toggleCoursePlanItemCompletedAction
#### `reorderCoursePlanItems`
- 签名:`(planId: string, items: ReorderCoursePlanItemInput[]) => Promise<void>`
- 功能:重排周计划条目顺序
- 依赖:`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<ParentChildRelation[]>` | `getParentDashboardData` 内部, parent/children/[studentId] 页面 |
| `getChildBasicInfo` | `(studentId: string, relation?: string \| null) => Promise<ChildBasicInfo \| null>` | `getChildDashboardData` 内部 |
| `getChildDashboardData` | `(studentId: string, relation?: string \| null) => Promise<ChildDashboardData \| null>` | parent/children/[studentId]/page.tsx, `getParentDashboardData` 内部 |
| `getParentDashboardData` | `(parentId: string) => Promise<ParentDashboardData>` | 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/gradeSiteHeader 通知下拉菜单展示未读数。
### 模块路径
`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<PaginatedResult<MessageListItem>>` | messages 页面 |
| `getMessageById` | `(id: string, userId: string) => Promise<Message \| null>` | 消息详情页 |
| `getMessageThread` | `(rootId: string, userId: string) => Promise<Message[]>` | 消息详情页(回复链) |
| `createMessage` | `(input: CreateMessageInput) => Promise<Message>` | sendMessageAction |
| `markMessageAsRead` | `(id: string, userId: string) => Promise<void>` | markMessageAsReadAction, 详情页自动已读 |
| `deleteMessage` | `(id: string, userId: string) => Promise<void>` | deleteMessageAction |
| `getUnreadMessageCount` | `(userId: string) => Promise<number>` | 待扩展 |
| `getNotifications` | `(userId: string, params?: { page?, pageSize? }) => Promise<PaginatedResult<NotificationListItem>>` | 通知列表/下拉菜单 |
| `createNotification` | `(input: CreateNotificationInput) => Promise<void>` | sendMessageAction内部调用 |
| `markNotificationAsRead` | `(id: string, userId: string) => Promise<void>` | markNotificationAsReadAction |
| `markAllNotificationsAsRead` | `(userId: string) => Promise<void>` | markAllNotificationsAsReadAction |
| `getUnreadNotificationCount` | `(userId: string) => Promise<number>` | 待扩展 |
| `getRecipients` | `(ctx: AuthContext) => Promise<RecipientOption[]>` | compose 页面(按 DataScope 过滤) |
### 导出函数 (notification-preferences.ts)
> 文件标记 `"server-only"`,使用 React `cache` 包装读取函数。
#### `getNotificationPreferences`
- 签名:`getNotificationPreferences(userId: string): Promise<NotificationPreferences>`cache 包装)
- 功能获取用户通知偏好若用户无记录则自动创建一条默认记录pushEnabled/homeworkNotifications/gradeNotifications/announcementNotifications/messageNotifications/attendanceNotifications 默认 trueemailEnabled/smsEnabled 默认 false并发冲突时回退到查询
- 依赖:`shared/db` (notificationPreferences 表), `@paralleldrive/cuid2`, `react` (cache)
- 被使用:`getNotificationPreferencesAction`, `app/(dashboard)/settings/page.tsx`
#### `upsertNotificationPreferences`
- 签名:`upsertNotificationPreferences(userId: string, input: UpdateNotificationPreferencesInput): Promise<NotificationPreferences | null>`
- 功能upsert 语义更新通知偏好(存在则部分字段更新,不存在则插入;未提供的字段保留原值或使用默认值)
- 依赖:`shared/db` (notificationPreferences 表), `@paralleldrive/cuid2`
- 被使用:`updateNotificationPreferencesAction`
### Schema (schema.ts)
#### `SendMessageSchema`
- 类型Zod schema
- 定义:发送消息校验 schemareceiverId, 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` | 通知完整列表(全部标记已读、单条标记已读、查看链接) |
---
## 模块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<PaginatedAttendanceResult>` | getAttendanceAction |
| `getClassAttendanceForDate` | `(classId: string, date: string) => Promise<AttendanceListItem[]>` | getClassAttendanceForDateAction |
| `createAttendanceRecord` | `(data: RecordAttendanceInput, recordedBy: string) => Promise<string>` | recordAttendanceAction |
| `batchCreateAttendanceRecords` | `(data: BatchRecordAttendanceInput, recordedBy: string) => Promise<number>` | batchRecordAttendanceAction |
| `updateAttendanceRecord` | `(id: string, data: UpdateAttendanceInput) => Promise<void>` | updateAttendanceAction |
| `deleteAttendanceRecord` | `(id: string) => Promise<void>` | deleteAttendanceAction |
| `getClassStudentsForAttendance` | `(classId: string) => Promise<Array<{ id, name, email }>>` | 点名页学生列表 |
| `getAttendanceRules` | `(classId?: string) => Promise<AttendanceRule[]>` | getAttendanceRulesAction |
| `upsertAttendanceRules` | `(data: AttendanceRuleInput) => Promise<string>` | saveAttendanceRulesAction |
### 导出函数 (data-access-stats.ts)
> 从 data-access.ts 拆分以遵守单文件 ≤300 行规则。
| 函数 | 签名 | 被使用 |
|------|------|--------|
| `getStudentAttendanceSummary` | `(studentId: string, startDate?: string, endDate?: string) => Promise<StudentAttendanceSummary \| null>` | getStudentAttendanceAction, student/attendance, parent/attendance |
| `getClassAttendanceStats` | `(classId: string, startDate?: string, endDate?: string) => Promise<ClassAttendanceSummary \| null>` | 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_ADJUSTteacher 角色无排课权限。
### Server Actions (`actions.ts`)
| Action | 权限 | 用途 |
|--------|------|------|
| `saveSchedulingRulesAction` | SCHEDULE_ADJUST | 保存班级排课规则upsertclassId 为空时为全局规则) |
| `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<SchedulingRule[]>` | saveSchedulingRulesAction, autoScheduleAction, admin/scheduling/rules |
| `upsertSchedulingRules` | `(data: SchedulingRuleInput) => Promise<string>` | saveSchedulingRulesAction |
| `getScheduleChanges` | `(params: ScheduleChangeQueryParams) => Promise<ScheduleChangeListItem[]>` | getScheduleChangesAction, admin/scheduling/changes, teacher/schedule-changes |
| `createScheduleChange` | `(data: ScheduleChangeInput, requestedBy: string) => Promise<string>` | requestScheduleChangeAction |
| `updateScheduleChangeStatus` | `(id, status, approverId) => Promise<void>` | approveScheduleChangeAction, rejectScheduleChangeAction |
| `getClassConflicts` | `(classId: string) => Promise<ScheduleConflict[]>` | getClassConflictsAction |
| `getAdminClassesForScheduling` | `() => Promise<Array<{ id, name, grade }>>` | 所有 scheduling 页面 |
| `getTeachersForScheduling` | `() => Promise<Array<{ id, name, email }>>` | teacher/schedule-changes |
| `getClassroomsForScheduling` | `() => Promise<Array<{ id, name, building }>>` | autoScheduleAction |
| `getClassSubjectsForScheduling` | `(classId) => Promise<Array<{ subjectId, subjectName, teacherId }>>` | 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` | 冲突检测视图(班级选择器 + 检测按钮 + 冲突结果列表) |
---
## 模块间依赖矩阵
| ↓ 使用 → | shared | auth | exams | homework | questions | textbooks | classes | school | dashboard | layout | settings | users | audit | announcements | files | grades | course-plans | parent | messaging | attendance | scheduling |
|----------|--------|------|-------|----------|-----------|-----------|---------|--------|-----------|--------|----------|-------|-------|---------------|-------|-------|-------------|--------|-----------|------------|-----------|
| **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 | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| **attendance** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | data-access.getTeacherClasses,getAdminClasses | - | - | - | - | - | - | - | - | - | - | - | - | - |
| **scheduling** | db,auth-guard(requirePermission,getAuthContext),types | 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)`
### `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` 常量定义47 个权限点,含 `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` 等)
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 无排课权限)
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)`
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`
### `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 二次校验
---
## 路由表
### 根路由
| 路由 | 组件 | 类型 | 权限 | 说明 |
|------|------|------|------|------|
| `/` | 角色路由分发 | 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.getGradesactions: updateAnnouncementAction |
### admin/files/* 路由
| 路由 | 组件 | 类型 | 权限 | 说明 |
|------|------|------|------|------|
| `/admin/files` | AdminFilesView | client | file:read | 管理员文件管理页面(上传+列表+删除dataAccess: files/data-access.getAllFileAttachmentsAPI: 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: createCoursePlanActiondataAccess: 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, toggleCoursePlanItemCompletedActiondataAccess: course-plans/data-access.getCoursePlanById |
| `/admin/course-plans/[id]/edit` | CoursePlanForm (edit) | client | course_plan:manage | 编辑课程计划actions: updateCoursePlanActiondataAccess: 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 | 所有登录用户可查看的公告列表(仅 publisheddataAccess: 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/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, getMessageThreadactions: markMessageAsReadAction 自动已读权限requirePermission(MESSAGE_READ) |
| `/messages/compose` | MessageCompose | server | message:send | 写消息页面(支持 reply 模式 via searchParams: receiverId, subject, parentMessageIddataAccess: 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 | 家长考勤视图(遍历子女,每个子女展示 StudentAttendanceViewdataAccess: 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, getSchedulingRulesactions: saveSchedulingRulesAction权限requirePermission(SCHEDULE_ADJUST) |
| `/admin/scheduling/auto` | AutoSchedulePanel + AutoScheduleResultView | server | schedule:auto | 自动排课页面dataAccess: scheduling/actions.getAdminClassesForSchedulingactions: autoScheduleAction, applyAutoScheduleAction权限requirePermission(SCHEDULE_AUTO) |
| `/admin/scheduling/changes` | ScheduleChangeList + ScheduleConflictsView | server | schedule:adjust | 调课申请审批+冲突检测页面dataAccess: scheduling/actions.getAdminClassesForScheduling, getScheduleChangesactions: 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: getStudentGradeSummaryDataScope.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 tabSecurity tab 含 PasswordChangeFormNotifications tab 含 NotificationPreferencesFormdataAccess: messaging/notification-preferences.getNotificationPreferences |
| `/settings/security` | SecuritySettingsPage | server | auth_required | 安全设置独立页面PasswordChangeForm + 安全提示权限requireAuth() |
### 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-audit` | push/PR to main | 依赖安全审计:`npm audit` moderate/critical 检查,上传 audit-report.json artifact |
| `scheduled-backup` | schedule cron `0 2 * * *` | 每天凌晨 2 点执行数据库备份,上传 backups/ artifact保留 30 天) |
### 运维脚本 (`scripts/`)
| 脚本 | 用途 |
|------|------|
| `scripts/audit.sh` | Bash 依赖审计脚本,运行 `npm audit --audit-level=moderate`,失败时生成 audit-report.json |
| `scripts/audit.ps1` | PowerShell 版本依赖审计脚本Windows 环境) |
| `scripts/backup-db.sh` | MySQL 数据库备份脚本,从 DATABASE_URL 解析连接信息gzip 压缩备份,保留 30 天 |
| `scripts/restore-db.sh` | MySQL 数据库恢复脚本,从指定备份文件恢复 |
| `scripts/test-backup.sh` | 备份流程测试脚本,执行一次备份并验证 |
### package.json 脚本
| 脚本 | 命令 | 说明 |
|------|------|------|
| `audit` | `npm audit --audit-level=moderate` | 依赖安全审计 |
| `audit:report` | `npm audit --json > audit-report.json` | 生成 JSON 审计报告 |
| `backup` | `bash scripts/backup-db.sh` | 执行数据库备份 |
| `restore` | `bash scripts/restore-db.sh` | 执行数据库恢复 |
---
## 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/e2e`
- `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`: chromiumCI 通道为 undefined本地为 chrome
- `retries`: CI 2 次,本地 0 次
- `workers`: CI 2 个,本地默认