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,6 @@
|
||||
export { useActionWithToast } from "./use-action-with-toast"
|
||||
export { useAriaLive } from "./use-aria-live"
|
||||
export { useCurrentTime } from "./use-current-time"
|
||||
export { useDebounce } from "./use-debounce"
|
||||
export { useMediaQuery } from "./use-media-query"
|
||||
export { useLocalStorage } from "./use-local-storage"
|
||||
|
||||
40
src/shared/hooks/use-current-time.test.ts
Normal file
40
src/shared/hooks/use-current-time.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { act, renderHook } from "@testing-library/react"
|
||||
import { useCurrentTime } from "./use-current-time"
|
||||
|
||||
describe("useCurrentTime", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it("returns a Date on initial render", () => {
|
||||
const { result } = renderHook(() => useCurrentTime(60000))
|
||||
expect(result.current).toBeInstanceOf(Date)
|
||||
})
|
||||
|
||||
it("updates after the specified interval", () => {
|
||||
const initialTime = new Date(2026, 0, 1, 10, 0, 0)
|
||||
vi.setSystemTime(initialTime)
|
||||
|
||||
const { result } = renderHook(() => useCurrentTime(60000))
|
||||
expect(result.current.getTime()).toBe(initialTime.getTime())
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(60000)
|
||||
})
|
||||
|
||||
const expectedTime = new Date(2026, 0, 1, 10, 1, 0)
|
||||
expect(result.current.getTime()).toBe(expectedTime.getTime())
|
||||
})
|
||||
|
||||
it("clears interval on unmount", () => {
|
||||
const { unmount } = renderHook(() => useCurrentTime(60000))
|
||||
const clearIntervalSpy = vi.spyOn(global, "clearInterval")
|
||||
unmount()
|
||||
expect(clearIntervalSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
22
src/shared/hooks/use-current-time.ts
Normal file
22
src/shared/hooks/use-current-time.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
/**
|
||||
* 返回当前时间,按指定间隔自动更新。
|
||||
*
|
||||
* SSR 安全:首次渲染返回 `initial`(默认 `new Date()`),挂载后开始定时更新。
|
||||
* 用于需要随时间刷新的 UI(如"进行中"徽章),避免 useMemo 依赖 `[items]` 导致过时。
|
||||
*
|
||||
* @param intervalMs 更新间隔(毫秒),默认 60000(1 分钟)
|
||||
*/
|
||||
export function useCurrentTime(intervalMs = 60000): Date {
|
||||
const [now, setNow] = useState(() => new Date())
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setNow(new Date()), intervalMs)
|
||||
return () => clearInterval(id)
|
||||
}, [intervalMs])
|
||||
|
||||
return now
|
||||
}
|
||||
Reference in New Issue
Block a user