feat(P2): 实现选课管理、考试监考、学情诊断三大功能模块

## 新增功能模块

### 1. 选课管理(elective)
- 新增表:electiveCourses、courseSelections
- 新增权限:ELECTIVE_MANAGE/ELECTIVE_READ/ELECTIVE_SELECT
- 支持先到先得 + 抽签两种选课模式
- admin/teacher/student 三端页面

### 2. 考试监考(proctoring)
- exams 表扩展:examMode/durationMinutes/antiCheatEnabled 等字段
- 新增表:examProctoringEvents
- 新增权限:EXAM_PROCTOR/EXAM_PROCTOR_READ
- 教师监考面板 + 学生端防作弊监控
- API:/api/proctoring/event 接收事件上报

### 3. 学情诊断报告(diagnostic)
- 新增表:knowledgePointMastery、learningDiagnosticReports
- 新增权限:DIAGNOSTIC_MANAGE/DIAGNOSTIC_READ
- 基于提交答案自动计算知识点掌握度
- 生成个人/班级诊断报告(强项/弱项/建议)
- 雷达图可视化

## 其他改动
- 项目规则:单文件行数限制从 300 行调整为企业级规范(组件≤500/Actions≤800/硬上限1000)
- scripts/seed.ts:消除全部 any 类型,定义内部类型,0 lint 错误
- 架构文档 004/005 同步更新三个新模块
- 迁移文件 0001_heavy_sage.sql 生成

## 验证
- npx tsc --noEmit:0 错误
- npm run lint:0 错误 0 警告
This commit is contained in:
SpecialX
2026-06-17 19:12:51 +08:00
parent baf8f679bf
commit b86255f0ea
46 changed files with 13234 additions and 80 deletions

View File

@@ -0,0 +1,50 @@
import { auth } from "@/auth"
import { getElectiveCourses } from "@/modules/elective/data-access"
import { ElectiveCourseList } from "@/modules/elective/components/elective-course-list"
import type { ElectiveCourseStatus } from "@/modules/elective/types"
export const dynamic = "force-dynamic"
type SearchParams = { [key: string]: string | string[] | undefined }
const getParam = (params: SearchParams, key: string) => {
const v = params[key]
return Array.isArray(v) ? v[0] : v
}
const isValidStatus = (v?: string): v is ElectiveCourseStatus =>
v === "draft" || v === "open" || v === "closed" || v === "cancelled"
export default async function TeacherElectivePage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
const session = await auth()
const teacherId = String(session?.user?.id ?? "")
const sp = await searchParams
const statusParam = getParam(sp, "status")
const status = isValidStatus(statusParam) ? statusParam : undefined
const courses = teacherId
? await getElectiveCourses({ teacherId, status })
: []
return (
<div className="flex h-full flex-col space-y-8 p-8">
<div className="space-y-1">
<h2 className="text-2xl font-bold tracking-tight">My Elective Courses</h2>
<p className="text-muted-foreground">
View and manage the elective courses you teach.
</p>
</div>
<ElectiveCourseList
courses={courses}
canManage
createHref="/admin/elective/create"
editHrefBuilder={(id) => `/admin/elective/${id}/edit`}
/>
</div>
)
}