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 条目
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { use } from "react"
|
||||
import type { TeacherDashboardData } from "@/modules/dashboard/types"
|
||||
import type { TeacherDashboardMetrics } from "@/modules/dashboard/lib/dashboard-utils"
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import type { TeacherTodoItem } from "./teacher-todo-card"
|
||||
|
||||
@@ -13,11 +15,24 @@ import { TeacherStats } from "./teacher-stats"
|
||||
import { TeacherGradeTrends } from "./teacher-grade-trends"
|
||||
import { TeacherTodoCard } from "./teacher-todo-card"
|
||||
|
||||
interface TeacherDashboardViewProps {
|
||||
data: TeacherDashboardData & { metrics: TeacherDashboardMetrics }
|
||||
type TeacherDashboardResult = ActionState<TeacherDashboardData & { metrics: TeacherDashboardMetrics }>
|
||||
|
||||
/**
|
||||
* 教师仪表盘视图(P2-1 流式架构)
|
||||
*
|
||||
* 接收未解析的 Promise,用 React `use()` 消费。
|
||||
* 页面外壳立即渲染,数据到达后在 DashboardSection 的 Suspense 边界内填充。
|
||||
*/
|
||||
export function TeacherDashboardView({ dataPromise }: { dataPromise: Promise<TeacherDashboardResult> }) {
|
||||
const result = use(dataPromise)
|
||||
if (!result.success || !result.data) {
|
||||
throw new Error(result.message ?? "Failed to load teacher dashboard")
|
||||
}
|
||||
|
||||
return <TeacherDashboardContent data={result.data} />
|
||||
}
|
||||
|
||||
export async function TeacherDashboardView({ data }: TeacherDashboardViewProps) {
|
||||
async function TeacherDashboardContent({ data }: { data: TeacherDashboardData & { metrics: TeacherDashboardMetrics } }) {
|
||||
const t = await getTranslations("dashboard")
|
||||
const { metrics } = data
|
||||
|
||||
@@ -58,13 +73,15 @@ export async function TeacherDashboardView({ data }: TeacherDashboardViewProps)
|
||||
/>
|
||||
</DashboardSection>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-12">
|
||||
<section aria-label={t("sections.pendingGrading")} className="flex flex-col gap-6 lg:col-span-8">
|
||||
<div className="lg:hidden">
|
||||
<DashboardSection variant="card">
|
||||
<TeacherSchedule items={metrics.todayScheduleItems} />
|
||||
</DashboardSection>
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 lg:grid lg:grid-cols-12">
|
||||
{/* 课表:移动端首位,桌面端右上 — 仅渲染一次(P2-9 修复,原为双实例) */}
|
||||
<div className="order-1 lg:col-start-9 lg:col-span-4 lg:row-start-1">
|
||||
<DashboardSection variant="card">
|
||||
<TeacherSchedule items={metrics.todayScheduleItems} />
|
||||
</DashboardSection>
|
||||
</div>
|
||||
|
||||
<section aria-label={t("sections.pendingGrading")} className="flex flex-col gap-6 order-2 lg:col-start-1 lg:col-span-8 lg:row-start-1 lg:row-span-2">
|
||||
<DashboardSection variant="card">
|
||||
<TeacherTodoCard items={todoItems} />
|
||||
</DashboardSection>
|
||||
@@ -81,12 +98,7 @@ export async function TeacherDashboardView({ data }: TeacherDashboardViewProps)
|
||||
</DashboardSection>
|
||||
</section>
|
||||
|
||||
<aside aria-label={t("sections.myClasses")} className="flex flex-col gap-6 lg:col-span-4">
|
||||
<div className="hidden lg:block">
|
||||
<DashboardSection variant="card">
|
||||
<TeacherSchedule items={metrics.todayScheduleItems} />
|
||||
</DashboardSection>
|
||||
</div>
|
||||
<aside aria-label={t("sections.myClasses")} className="flex flex-col gap-6 order-3 lg:col-start-9 lg:col-span-4 lg:row-start-2">
|
||||
<DashboardSection variant="list">
|
||||
<TeacherHomeworkCard assignments={data.assignments} />
|
||||
</DashboardSection>
|
||||
|
||||
Reference in New Issue
Block a user