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:
55
src/app/(dashboard)/teacher/exams/[id]/proctoring/page.tsx
Normal file
55
src/app/(dashboard)/teacher/exams/[id]/proctoring/page.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { ProctoringDashboard } from "@/modules/proctoring/components/proctoring-dashboard"
|
||||
import {
|
||||
getExamForProctoring,
|
||||
getExamProctoringSummary,
|
||||
getStudentProctoringStatuses,
|
||||
getRecentProctoringEvents,
|
||||
} from "@/modules/proctoring/data-access"
|
||||
import type { ProctoringDashboardData } from "@/modules/proctoring/types"
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
export default async function ExamProctoringPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
try {
|
||||
await requirePermission(Permissions.EXAM_PROCTOR)
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) {
|
||||
return (
|
||||
<div className="p-10 text-center text-muted-foreground">
|
||||
您没有监考权限(exam:proctor)。
|
||||
</div>
|
||||
)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
const exam = await getExamForProctoring(id)
|
||||
if (!exam) return notFound()
|
||||
|
||||
// 并行拉取面板初始数据
|
||||
const [summary, students, recentEvents] = await Promise.all([
|
||||
getExamProctoringSummary(id),
|
||||
getStudentProctoringStatuses(id),
|
||||
getRecentProctoringEvents(id, 20),
|
||||
])
|
||||
|
||||
const initialData: ProctoringDashboardData = {
|
||||
summary,
|
||||
students,
|
||||
recentEvents,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col space-y-4 p-4">
|
||||
<ProctoringDashboard examId={id} initialData={initialData} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user