feat(app): add lesson-plans, practice, and grade dashboard routes
- Add admin/lesson-plans, parent/lesson-plans, student/lesson-plans routes - Add student/practice and teacher/practice routes for adaptive practice - Add management/grade/dashboard and management/grade/practice routes - Add teacher/lesson-plans error and loading boundaries - Update existing admin, parent, student, teacher pages with new features - Update globals.css and proxy middleware
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type { JSX } from "react"
|
||||
import { getTeacherClasses } from "@/modules/classes/data-access"
|
||||
import { getTeacherClasses, getClassGradeIdsByClassIds } from "@/modules/classes/data-access"
|
||||
import { getClassStudentsForEntry } from "@/modules/grades/data-access"
|
||||
import { getSubjectOptions } from "@/modules/school/data-access"
|
||||
import { BatchGradeEntry } from "@/modules/grades/components/batch-grade-entry"
|
||||
import { getExamsForGradeEntry, getExamForGradeEntry } from "@/modules/exams/data-access"
|
||||
import { BatchGradeEntryByExam } from "@/modules/grades/components/batch-grade-entry"
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { getParam, type SearchParams } from "@/shared/lib/search-params"
|
||||
@@ -19,63 +19,73 @@ export default async function BatchEntryPage({
|
||||
const ctx = await requirePermission(Permissions.GRADE_RECORD_MANAGE)
|
||||
const sp = await searchParams
|
||||
|
||||
const defaultClassId = getParam(sp, "classId")
|
||||
const defaultSubjectId = getParam(sp, "subjectId")
|
||||
const examId = getParam(sp, "examId")
|
||||
const classId = getParam(sp, "classId")
|
||||
|
||||
// P3 修复:添加 scope 校验,对 class_taught scope 限制可录入的班级
|
||||
const [classes, allSubjects, students] = await Promise.all([
|
||||
// 获取试卷列表 + 班级列表
|
||||
const [exams, teacherClasses] = await Promise.all([
|
||||
getExamsForGradeEntry(ctx.dataScope),
|
||||
getTeacherClasses(),
|
||||
getSubjectOptions(),
|
||||
defaultClassId
|
||||
? getClassStudentsForEntry(defaultClassId, ctx.dataScope)
|
||||
: Promise.resolve([] as Awaited<ReturnType<typeof getClassStudentsForEntry>>),
|
||||
])
|
||||
|
||||
// 对 class_taught scope,过滤掉不在 scope 中的班级
|
||||
// scope 过滤班级
|
||||
const allowedClassIds =
|
||||
ctx.dataScope.type === "class_taught" ? ctx.dataScope.classIds : null
|
||||
const scopedClasses = allowedClassIds
|
||||
? classes.filter((c) => allowedClassIds.includes(c.id))
|
||||
: classes
|
||||
? teacherClasses.filter((c) => allowedClassIds.includes(c.id))
|
||||
: teacherClasses
|
||||
|
||||
const classOptions = scopedClasses.map((c) => ({ id: c.id, name: c.name }))
|
||||
const subjectOptions = allSubjects.map((s) => ({ id: s.id, name: s.name }))
|
||||
|
||||
// 如果指定了 classId 但 scope 不允许,显示提示
|
||||
if (defaultClassId && students.length === 0 && scopedClasses.length > 0) {
|
||||
const classExists = scopedClasses.some((c) => c.id === defaultClassId)
|
||||
if (!classExists) {
|
||||
return (
|
||||
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Batch Grade Entry</h1>
|
||||
<p className="text-muted-foreground">Enter grades for all students in a class at once.</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
title="无权访问该班级"
|
||||
description="您没有权限为该班级录入成绩。"
|
||||
icon={ClipboardList}
|
||||
className="border-none shadow-none"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
// 获取 classId → gradeId 映射(用于客户端按试卷年级过滤班级)
|
||||
const classGradeMap: Record<string, string> = {}
|
||||
if (scopedClasses.length > 0) {
|
||||
const gradeMap = await getClassGradeIdsByClassIds(
|
||||
scopedClasses.map((c) => c.id)
|
||||
)
|
||||
for (const [cid, gid] of gradeMap.entries()) {
|
||||
classGradeMap[cid] = gid
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有 examId,获取试卷详情(含题目列表)
|
||||
const exam = examId
|
||||
? await getExamForGradeEntry(examId, ctx.dataScope)
|
||||
: null
|
||||
|
||||
// 如果有 examId + classId,获取学生列表
|
||||
const students =
|
||||
examId && classId
|
||||
? await getClassStudentsForEntry(classId, ctx.dataScope)
|
||||
: []
|
||||
|
||||
return (
|
||||
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Batch Grade Entry</h1>
|
||||
<p className="text-muted-foreground">Enter grades for all students in a class at once.</p>
|
||||
<h1 className="text-2xl font-bold tracking-tight">批量录入成绩</h1>
|
||||
<p className="text-muted-foreground">
|
||||
从试卷库选择试卷,按每题得分录入,像填 Excel 表格一样。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<BatchGradeEntry
|
||||
classes={classOptions}
|
||||
subjects={subjectOptions}
|
||||
students={students}
|
||||
defaultClassId={defaultClassId}
|
||||
defaultSubjectId={defaultSubjectId}
|
||||
/>
|
||||
{exams.length === 0 ? (
|
||||
<EmptyState
|
||||
title="没有可用的试卷"
|
||||
description="请先在试卷管理中创建试卷并添加题目,才能录入成绩。"
|
||||
icon={ClipboardList}
|
||||
className="border-none shadow-none"
|
||||
/>
|
||||
) : (
|
||||
<BatchGradeEntryByExam
|
||||
exams={exams}
|
||||
classes={classOptions}
|
||||
classGradeMap={classGradeMap}
|
||||
exam={exam}
|
||||
students={students}
|
||||
defaultExamId={examId}
|
||||
defaultClassId={classId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user