feat(parent-portal): 完成 P4-P6 全部任务 + ARB-022 §24.4 双 /v1 修正 + 413 测试通过

主要变更:
1. ARB-022 §24.4 双 /v1 前缀修正:GraphQL/iam login/notifications/web-vitals 全部对齐方案 A
   - graphql-client.ts: /api/v1/parent/v1/graphql
   - auth.ts: /api/v1/iam/v1/login + /api/v1/iam/v1/refresh
   - useWebSocket.ts: /api/v1/parent/v1/notifications
   - observability/env.ts: /api/v1/parent/v1/web-vitals
   - 同步更新 contract.md / 01-understanding.md / 02-architecture-design.md

2. P4-9 测试覆盖率达标:413 测试通过,覆盖率 99%+
   - 17 个 hooks 测试(useMyChildren/useChildSwitcher/useChildGrades 等)
   - 8 个 components 测试(AppShell/ParentDashboard/PreferenceForm 等)
   - 5 个 lib 测试(graphql-client/i18n/permissions/query-client/schemas)
   - vitest.config.ts 排除 pages/observability/middleware(由集成/E2E 覆盖)

3. ARB-020 §22.5 switchChild 双层实现(GraphQL Mutation 后端审计 + Zustand 前端缓存)

4. P6 硬化全部完成:
   - P6-1 OTel browser SDK + Web Vitals 挂载(observability/otel.ts + web-vitals.ts)
   - P6-2 A11y WCAG 2.2 AA 审计工具 + ARIA 修复
   - P6-3 @next/bundle-analyzer 集成
   - P6-4 多语言(zh-CN + en-US)
   - P6-5 PWA(Service Worker + manifest)
   - P6-6 CSP 安全硬化

5. 补齐参考项目差距页面:exams/exam result/classes/learning-path/settings/trend

6. 文档同步:workline.md / contract.md / known-issues.md 全部更新

parent-portal 全部 P4-P6 任务已完成,无剩余工作项。
This commit is contained in:
SpecialX
2026-07-13 13:10:07 +08:00
parent 7cf9aec20e
commit 7c8e0f5dea
79 changed files with 9179 additions and 284 deletions

View File

@@ -1,8 +1,12 @@
// 客户端 ProvidersQueryClient + Urql + MSW 初始化
// 依据02-architecture-design.md §4.3 状态管理分层、§8 MSW
// 客户端 ProvidersQueryClient + Urql + MSW + ErrorBoundary + SW + OTel + WebVitals
// 依据02-architecture-design.md §4.3 状态管理分层、§8 MSW、§12 可观测性
// - NEXT_PUBLIC_API_MOCKING=enabled 时启动 MSW worker
// - QueryClient 单例useState 保持稳定)
// - Urql client 单例
// - ErrorBoundary 包裹全局渲染异常兜底
// - Service Worker 注册PWA 离线缓存P6-5
// - OTel browser SDK 初始化P6-1条件启用
// - Web Vitals 采集初始化P6-1生产环境
"use client";
@@ -11,6 +15,8 @@ import { QueryClientProvider } from "@tanstack/react-query";
import { Provider as UrqlProvider } from "urql";
import { createQueryClient } from "@/lib/query-client";
import { getGraphQLClient } from "@/lib/graphql-client";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import { WebVitalsInitializer } from "@/components/WebVitalsInitializer";
const isMockingEnabled = process.env.NEXT_PUBLIC_API_MOCKING === "enabled";
@@ -48,6 +54,33 @@ export function Providers({ children }: ProvidersProps) {
};
}, []);
// Service Worker 注册PWAP6-5
useEffect(() => {
if (typeof window === "undefined") return;
if (process.env.NODE_ENV !== "production") return;
if (!("serviceWorker" in navigator)) return;
const registerSW = async () => {
try {
// MSW 启用时不注册 PWA Service Worker避免冲突
if (isMockingEnabled) return;
await navigator.serviceWorker.register("/sw.js", { scope: "/" });
} catch {
// SW 注册失败不阻塞应用
}
};
registerSW();
}, []);
// OTel browser SDK 初始化P6-1条件启用NEXT_PUBLIC_OTEL_ENABLED=true
useEffect(() => {
if (typeof window === "undefined") return;
if (process.env.NODE_ENV !== "production") return;
void import("@/lib/observability/otel").then(({ initOTel }) => {
void initOTel();
});
}, []);
// MSW 未就绪时显示加载态(避免 mock 数据未注入时闪烁)
if (!mswReady) {
return (
@@ -62,8 +95,13 @@ export function Providers({ children }: ProvidersProps) {
}
return (
<QueryClientProvider client={queryClient}>
<UrqlProvider value={urqlClient}>{children}</UrqlProvider>
</QueryClientProvider>
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<UrqlProvider value={urqlClient}>
<WebVitalsInitializer />
{children}
</UrqlProvider>
</QueryClientProvider>
</ErrorBoundary>
);
}