Files
NextEdu/src/app/(dashboard)/student/learning/textbooks/[id]/page.tsx
SpecialX 1a9377222c 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
2026-06-23 17:38:28 +08:00

76 lines
3.1 KiB
TypeScript
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.
import { notFound } from "next/navigation"
import { getTranslations } from "next-intl/server"
import { BookOpen } from "lucide-react"
import { getTextbookById, getChaptersByTextbookId } from "@/modules/textbooks/data-access"
import { TextbookReader } from "@/modules/textbooks/components/textbook-reader"
import { getSubjectLabelKey, getGradeLabelKey } from "@/modules/textbooks/constants"
import { Badge } from "@/shared/components/ui/badge"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { getCurrentStudentUser } from "@/modules/users/data-access"
import { getGradeNameById } from "@/modules/school/data-access"
export const dynamic = "force-dynamic"
export default async function StudentTextbookDetailPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const t = await getTranslations("textbooks")
const student = await getCurrentStudentUser()
if (!student) return notFound()
const { id } = await params
const [textbook, chapters] = await Promise.all([
getTextbookById(id),
getChaptersByTextbookId(id),
])
if (!textbook) notFound()
// P1-1 数据范围过滤:校验教材年级与学生年级匹配
// student.gradeId 是 grades 表 id需解析为年级名称后才能与 textbooks.grade 字符串比较
const studentGradeName = student.gradeId ? await getGradeNameById(student.gradeId) : null
if (studentGradeName && textbook.grade && textbook.grade !== studentGradeName) {
notFound()
}
return (
<div className="flex h-[calc(100vh-4rem-3rem)] flex-col overflow-hidden bg-muted/5">
<div className="flex items-center justify-between border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 py-3 px-6 shrink-0 z-10">
<div className="flex items-center gap-3 min-w-0">
<h1 className="text-lg font-bold tracking-tight truncate">{textbook.title}</h1>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span className="hidden sm:inline-block w-px h-4 bg-border" aria-hidden="true" />
<Badge variant="outline" className="font-normal text-xs">{t(`subject.${getSubjectLabelKey(textbook.subject)}`)}</Badge>
{textbook.grade && (
<Badge variant="secondary" className="font-normal text-xs">{t(`grade.${getGradeLabelKey(textbook.grade)}`)}</Badge>
)}
</div>
</div>
</div>
<div className="flex-1 overflow-hidden">
{chapters.length === 0 ? (
<div className="h-full flex items-center justify-center rounded-lg border border-dashed bg-card">
<EmptyState
icon={BookOpen}
title={t("reader.noChapters")}
description={t("reader.noChaptersDesc")}
className="border-none shadow-none"
/>
</div>
) : (
<div className="h-full min-h-0 max-w-[1600px] mx-auto w-full">
{/* 学生端不传 renderQuestionCreator无题目创建权限 */}
<TextbookReader key={id} chapters={chapters} textbookId={id} />
</div>
)}
</div>
</div>
)
}