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:
SpecialX
2026-06-23 17:38:28 +08:00
parent c4d3433cc9
commit 1a9377222c
90 changed files with 1690 additions and 741 deletions

View File

@@ -3,7 +3,7 @@ import Link from "next/link"
import { PlusCircle, BarChart3, ClipboardList } from "lucide-react"
import { Button } from "@/shared/components/ui/button"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { ListPagination, computePagination, paginate } from "@/shared/components/ui/list-pagination"
import { ListPagination, computePagination } from "@/shared/components/ui/list-pagination"
import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions"
import { getParam, type SearchParams } from "@/shared/lib/search-params"
@@ -43,7 +43,11 @@ export default async function TeacherGradesPage({
const type = getParam(sp, "type")
const semester = getParam(sp, "semester")
const [classes, allSubjects, records] = await Promise.all([
// P3 修复:使用 DB 层分页,移除重复计算
const { page } = computePagination(sp, PAGE_SIZE)
const offset = (page - 1) * PAGE_SIZE
const [classes, allSubjects, result] = await Promise.all([
getTeacherClasses(),
getSubjectOptions(),
getGradeRecords({
@@ -53,18 +57,19 @@ export default async function TeacherGradesPage({
subjectId: subjectId && subjectId !== "all" ? subjectId : undefined,
type: type && type !== "all" ? parseGradeType(type) : undefined,
semester: semester && semester !== "all" ? parseSemester(semester) : undefined,
limit: PAGE_SIZE,
offset,
}),
])
const classOptions = classes.map((c) => ({ id: c.id, name: c.name }))
const subjectOptions = allSubjects.map((s) => ({ id: s.id, name: s.name }))
// 分页计算
const { page } = computePagination(sp, PAGE_SIZE)
const total = records.length
// 使用 DB 返回的 total 和 totalPages移除重复计算
const total = result.total
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
const currentPage = Math.min(page, totalPages)
const pagedRecords = paginate(records, currentPage, PAGE_SIZE)
const pagedRecords = result.records
const hasFilters = Boolean(classId || subjectId || type || semester)
return (
@@ -103,7 +108,7 @@ export default async function TeacherGradesPage({
<GradeQueryFilters classes={classOptions} subjects={subjectOptions} />
{records.length === 0 && !hasFilters ? (
{total === 0 && !hasFilters ? (
<EmptyState
title="暂无成绩记录"
description="开始为您的班级录入成绩。"