feat(app): add error/loading boundaries and update dashboard routes
- Add error.tsx and loading.tsx boundaries for admin, parent, student, teacher routes - Add dashboard-error-fallback and dashboard-loading-skeleton components - Add student/learning page, parent/leave routes, teacher textbook components - Update existing app routes across auth, dashboard, and API endpoints - Update proxy middleware and next-auth type declarations
This commit is contained in:
@@ -3,7 +3,11 @@ import { getTeacherClasses } 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 { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { getParam, type SearchParams } from "@/shared/lib/search-params"
|
||||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||
import { ClipboardList } from "lucide-react"
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
@@ -12,22 +16,52 @@ export default async function BatchEntryPage({
|
||||
}: {
|
||||
searchParams: Promise<SearchParams>
|
||||
}): Promise<JSX.Element> {
|
||||
const ctx = await requirePermission(Permissions.GRADE_RECORD_MANAGE)
|
||||
const sp = await searchParams
|
||||
|
||||
const defaultClassId = getParam(sp, "classId")
|
||||
const defaultSubjectId = getParam(sp, "subjectId")
|
||||
|
||||
// P3 修复:添加 scope 校验,对 class_taught scope 限制可录入的班级
|
||||
const [classes, allSubjects, students] = await Promise.all([
|
||||
getTeacherClasses(),
|
||||
getSubjectOptions(),
|
||||
defaultClassId
|
||||
? getClassStudentsForEntry(defaultClassId)
|
||||
? getClassStudentsForEntry(defaultClassId, ctx.dataScope)
|
||||
: Promise.resolve([] as Awaited<ReturnType<typeof getClassStudentsForEntry>>),
|
||||
])
|
||||
|
||||
const classOptions = classes.map((c) => ({ id: c.id, name: c.name }))
|
||||
// 对 class_taught scope,过滤掉不在 scope 中的班级
|
||||
const allowedClassIds =
|
||||
ctx.dataScope.type === "class_taught" ? ctx.dataScope.classIds : null
|
||||
const scopedClasses = allowedClassIds
|
||||
? classes.filter((c) => allowedClassIds.includes(c.id))
|
||||
: classes
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user