}) {
- const ctx = await getAuthContext()
+ const ctx = await requirePermission(Permissions.GRADE_RECORD_READ)
const [sp, summary] = await Promise.all([
searchParams,
getStudentGradeSummary(ctx.userId),
diff --git a/src/app/(dashboard)/teacher/diagnostic/class/[classId]/error.tsx b/src/app/(dashboard)/teacher/diagnostic/class/[classId]/error.tsx
new file mode 100644
index 0000000..af5f442
--- /dev/null
+++ b/src/app/(dashboard)/teacher/diagnostic/class/[classId]/error.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import { AlertCircle } from "lucide-react"
+
+import { EmptyState } from "@/shared/components/ui/empty-state"
+
+export default function DiagnosticClassError({
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+ reset(),
+ }}
+ className="border-none shadow-none h-auto"
+ />
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/diagnostic/class/[classId]/loading.tsx b/src/app/(dashboard)/teacher/diagnostic/class/[classId]/loading.tsx
new file mode 100644
index 0000000..0ae5d06
--- /dev/null
+++ b/src/app/(dashboard)/teacher/diagnostic/class/[classId]/loading.tsx
@@ -0,0 +1,30 @@
+import { Card, CardContent, CardHeader } from "@/shared/components/ui/card"
+import { Skeleton } from "@/shared/components/ui/skeleton"
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/diagnostic/error.tsx b/src/app/(dashboard)/teacher/diagnostic/error.tsx
new file mode 100644
index 0000000..0849608
--- /dev/null
+++ b/src/app/(dashboard)/teacher/diagnostic/error.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import { AlertCircle } from "lucide-react"
+
+import { EmptyState } from "@/shared/components/ui/empty-state"
+
+export default function TeacherDiagnosticError({
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+ reset(),
+ }}
+ className="border-none shadow-none h-auto"
+ />
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/diagnostic/loading.tsx b/src/app/(dashboard)/teacher/diagnostic/loading.tsx
new file mode 100644
index 0000000..142ce84
--- /dev/null
+++ b/src/app/(dashboard)/teacher/diagnostic/loading.tsx
@@ -0,0 +1,23 @@
+import { Card, CardContent, CardHeader } from "@/shared/components/ui/card"
+import { Skeleton } from "@/shared/components/ui/skeleton"
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/diagnostic/student/[studentId]/error.tsx b/src/app/(dashboard)/teacher/diagnostic/student/[studentId]/error.tsx
new file mode 100644
index 0000000..cc75dae
--- /dev/null
+++ b/src/app/(dashboard)/teacher/diagnostic/student/[studentId]/error.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import { AlertCircle } from "lucide-react"
+
+import { EmptyState } from "@/shared/components/ui/empty-state"
+
+export default function DiagnosticStudentError({
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+ reset(),
+ }}
+ className="border-none shadow-none h-auto"
+ />
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/diagnostic/student/[studentId]/loading.tsx b/src/app/(dashboard)/teacher/diagnostic/student/[studentId]/loading.tsx
new file mode 100644
index 0000000..0ae5d06
--- /dev/null
+++ b/src/app/(dashboard)/teacher/diagnostic/student/[studentId]/loading.tsx
@@ -0,0 +1,30 @@
+import { Card, CardContent, CardHeader } from "@/shared/components/ui/card"
+import { Skeleton } from "@/shared/components/ui/skeleton"
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/analytics/error.tsx b/src/app/(dashboard)/teacher/grades/analytics/error.tsx
new file mode 100644
index 0000000..ecc3756
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/analytics/error.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import { AlertCircle } from "lucide-react"
+
+import { EmptyState } from "@/shared/components/ui/empty-state"
+
+export default function TeacherGradesAnalyticsError({
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+ reset(),
+ }}
+ className="border-none shadow-none h-auto"
+ />
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/analytics/loading.tsx b/src/app/(dashboard)/teacher/grades/analytics/loading.tsx
new file mode 100644
index 0000000..a07cef9
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/analytics/loading.tsx
@@ -0,0 +1,31 @@
+import { Card, CardContent, CardHeader } from "@/shared/components/ui/card"
+import { Skeleton } from "@/shared/components/ui/skeleton"
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/entry/error.tsx b/src/app/(dashboard)/teacher/grades/entry/error.tsx
new file mode 100644
index 0000000..181823d
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/entry/error.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import { AlertCircle } from "lucide-react"
+
+import { EmptyState } from "@/shared/components/ui/empty-state"
+
+export default function TeacherGradesEntryError({
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+ reset(),
+ }}
+ className="border-none shadow-none h-auto"
+ />
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/entry/loading.tsx b/src/app/(dashboard)/teacher/grades/entry/loading.tsx
new file mode 100644
index 0000000..db96284
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/entry/loading.tsx
@@ -0,0 +1,23 @@
+import { Card, CardContent, CardHeader } from "@/shared/components/ui/card"
+import { Skeleton } from "@/shared/components/ui/skeleton"
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 8 }).map((_, i) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/error.tsx b/src/app/(dashboard)/teacher/grades/error.tsx
new file mode 100644
index 0000000..7c3d50c
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/error.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import { AlertCircle } from "lucide-react"
+
+import { EmptyState } from "@/shared/components/ui/empty-state"
+
+export default function TeacherGradesError({
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+ reset(),
+ }}
+ className="border-none shadow-none h-auto"
+ />
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/loading.tsx b/src/app/(dashboard)/teacher/grades/loading.tsx
new file mode 100644
index 0000000..889c68b
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/loading.tsx
@@ -0,0 +1,28 @@
+import { Card, CardContent, CardHeader } from "@/shared/components/ui/card"
+import { Skeleton } from "@/shared/components/ui/skeleton"
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/stats/error.tsx b/src/app/(dashboard)/teacher/grades/stats/error.tsx
new file mode 100644
index 0000000..afc6697
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/stats/error.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import { AlertCircle } from "lucide-react"
+
+import { EmptyState } from "@/shared/components/ui/empty-state"
+
+export default function TeacherGradesStatsError({
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+ reset(),
+ }}
+ className="border-none shadow-none h-auto"
+ />
+
+ )
+}
diff --git a/src/app/(dashboard)/teacher/grades/stats/loading.tsx b/src/app/(dashboard)/teacher/grades/stats/loading.tsx
new file mode 100644
index 0000000..5976064
--- /dev/null
+++ b/src/app/(dashboard)/teacher/grades/stats/loading.tsx
@@ -0,0 +1,33 @@
+import { Card, CardContent, CardHeader } from "@/shared/components/ui/card"
+import { Skeleton } from "@/shared/components/ui/skeleton"
+
+export default function Loading() {
+ return (
+
+
+
+
+
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/modules/diagnostic/components/mastery-radar-chart.tsx b/src/modules/diagnostic/components/mastery-radar-chart.tsx
index 6267246..d550476 100644
--- a/src/modules/diagnostic/components/mastery-radar-chart.tsx
+++ b/src/modules/diagnostic/components/mastery-radar-chart.tsx
@@ -35,35 +35,37 @@ export function MasteryRadarChart({ data }: MasteryRadarChartProps) {
emptyDescription="No knowledge point mastery records found for this student."
emptyClassName="h-60"
>
-
+
+
+
)
}
diff --git a/src/modules/diagnostic/components/report-list.tsx b/src/modules/diagnostic/components/report-list.tsx
index e067f9b..bf78763 100644
--- a/src/modules/diagnostic/components/report-list.tsx
+++ b/src/modules/diagnostic/components/report-list.tsx
@@ -157,6 +157,7 @@ export function ReportList({ reports }: ReportListProps) {
) : (
+ 学情诊断报告列表
Type
@@ -175,7 +176,7 @@ export function ReportList({ reports }: ReportListProps) {
{typeLabels[r.reportType] ?? r.reportType}
- {r.studentName}
+ {r.studentName ?? (r.reportType === "class" ? "(班级报告)" : r.reportType === "grade" ? "(年级报告)" : "-")}
{r.period ?? "-"}
{r.overallScore !== null ? `${r.overallScore.toFixed(1)}%` : "-"}
@@ -195,6 +196,7 @@ export function ReportList({ reports }: ReportListProps) {
className="h-8 w-8 text-green-600"
onClick={() => setPublishId(r.id)}
title="Publish"
+ aria-label={`发布报告 ${r.studentName}`}
>
@@ -205,6 +207,7 @@ export function ReportList({ reports }: ReportListProps) {
className="h-8 w-8 text-destructive"
onClick={() => setDeleteId(r.id)}
title="Delete"
+ aria-label={`删除报告 ${r.studentName}`}
>
diff --git a/src/modules/diagnostic/components/student-diagnostic-view.tsx b/src/modules/diagnostic/components/student-diagnostic-view.tsx
index 5eb61a1..8f7b447 100644
--- a/src/modules/diagnostic/components/student-diagnostic-view.tsx
+++ b/src/modules/diagnostic/components/student-diagnostic-view.tsx
@@ -96,7 +96,7 @@ export function StudentDiagnosticView({ summary, reports, classAverageMastery }:
{summary.strengths.length === 0 ? (
No strengths identified yet.
) : (
-