Files
NextEdu/src/modules/dashboard/components/student-dashboard/student-dashboard-view.tsx
SpecialX 2c0f81391b feat(dashboard): 实现所有长期问题修复(P2-1/P2-5/P2-7/P2-9)
P2-9: TeacherSchedule 重复渲染优化
- 将移动端(lg:hidden)和桌面端(hidden lg:block)的双实例渲染改为单实例
- 使用 CSS flex order + grid col-start/row-start 实现响应式布局重排序
- 消除服务端 HTML 负载翻倍问题

P2-5: StudentTodayScheduleCard 时间过时修复
- 新增 useCurrentTime hook(src/shared/hooks/use-current-time.ts)
- 每分钟自动更新当前时间,useMemo 依赖 [items, now] 确保徽章不过时
- SSR 安全:初始渲染用 new Date(),挂载后 setInterval 更新

P2-1: 流式/Suspense 架构改造
- 新增 getAdminDashboardStreams(streams.ts):返回各独立数据源的未解析 Promise
- Admin dashboard:7 个分区组件用 React use() 独立消费 Promise,各 Suspense 边界独立流式渲染
- Teacher/Student/Parent dashboard:传入未解析 Promise,视图用 use() 消费,启用 Suspense 流式
- 页面外壳(标题 + 快捷操作)立即渲染,数据到达后各分区按各自速度填充

P2-7: 组件测试 + 路由测试修复
- 修复 dashboard-routing.test.ts:移除误导性的 permissions 字段(实际用 resolvePermissions(roles))
- 新增 fallback 路由测试(未知角色 → teacher dashboard)
- 新增 DashboardSection 组件测试(6 个测试:骨架屏变体 + 错误边界 + 正常渲染)
- 新增 useCurrentTime hook 测试(3 个测试:初始值 + 间隔更新 + 清理)

同步更新:
- docs/architecture/005_architecture_data.json 新增 7 个流式组件 + useCurrentTime hook + getAdminDashboardStreams 条目
2026-06-23 09:04:40 +08:00

93 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 { use } from "react"
import { getTranslations } from "next-intl/server"
import { UserX } from "lucide-react"
import type { ActionState } from "@/shared/types/action-state"
import type { StudentDashboardProps } from "@/modules/dashboard/types"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { DashboardSection } from "../dashboard-section"
import { StudentDashboardHeader } from "./student-dashboard-header"
import { StudentGradesCard } from "./student-grades-card"
import { StudentStatsGrid } from "./student-stats-grid"
import { StudentTodayScheduleCard } from "./student-today-schedule-card"
import { StudentUpcomingAssignmentsCard } from "./student-upcoming-assignments-card"
type StudentDashboardResult = ActionState<{
student: { id: string; name: string } | null
dashboardProps: Omit<StudentDashboardProps, "studentName"> | null
}>
/**
* 学生仪表盘视图P2-1 流式架构)
*
* 接收未解析的 Promise用 React `use()` 消费。
* 页面外壳立即渲染,数据到达后在 DashboardSection 的 Suspense 边界内填充。
*/
export function StudentDashboard({ dataPromise }: { dataPromise: Promise<StudentDashboardResult> }) {
const result = use(dataPromise)
if (!result.success || !result.data) {
throw new Error(result.message ?? "Failed to load student dashboard")
}
return <StudentDashboardBody result={result.data} />
}
async function StudentDashboardBody({
result,
}: {
result: NonNullable<StudentDashboardResult["data"]>
}) {
const t = await getTranslations("dashboard")
if (!result.student || !result.dashboardProps) {
return (
<EmptyState
title={t("empty.noStudent")}
description={t("empty.noStudentDesc")}
icon={UserX}
className="border-none shadow-none h-auto"
/>
)
}
const { student, dashboardProps } = result
return (
<div className="space-y-6">
<header>
<StudentDashboardHeader studentName={student.name} />
</header>
<DashboardSection variant="stats">
<StudentStatsGrid
enrolledClassCount={dashboardProps.enrolledClassCount}
dueSoonCount={dashboardProps.dueSoonCount}
overdueCount={dashboardProps.overdueCount}
gradedCount={dashboardProps.gradedCount}
ranking={dashboardProps.grades.ranking}
/>
</DashboardSection>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<section
aria-label={t("sections.upcomingAssignments")}
className="lg:col-span-2 space-y-6"
>
<DashboardSection variant="list">
<StudentUpcomingAssignmentsCard upcomingAssignments={dashboardProps.upcomingAssignments} />
</DashboardSection>
<DashboardSection variant="card">
<StudentGradesCard grades={dashboardProps.grades} />
</DashboardSection>
</section>
<aside aria-label={t("sections.todaySchedule")} className="space-y-6">
<DashboardSection variant="card">
<StudentTodayScheduleCard items={dashboardProps.todayScheduleItems} />
</DashboardSection>
</aside>
</div>
</div>
)
}