chore(parent-portal): merge parent-portal module into main

Merge feat/parent-portal-ai15 into main, conflicts resolved in favor of feature branch
This commit is contained in:
SpecialX
2026-07-10 19:01:25 +08:00
77 changed files with 9358 additions and 350 deletions

View File

@@ -458,24 +458,36 @@
> parent-portal 复用 teacher-portal Shell 暴露的 AppShell / usePermission / ApiClient / ErrorBoundary / 设计令牌 / A11y 工具集,本节仅记录 parent-portal **特有**的"场景→技术"映射,通用前端映射见 §2.12。
| 场景 | 技术/规则 |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P4 端口分配 | parent-portal = **4002**004 §1.2 强制 4 端 4000-4003ai07 初稿误用 3002 与 iam 冲突ai15 修订) |
| P4 MF Remote 角色 | next.config.js 配 `@module-federation/nextjs-mf` 暴露 `./ChildSwitcher` 等家长端特有组件,复用 Shell 提供的 `AppShell`/`usePermission`/`ApiClient`/`RequirePermission` |
| P4 DataScope CHILDREN | 家长仅能查看自己绑定子女的数据004 §5.4 L0 SELF 变体),前端通过 ApiClient 透传 userId + childId下游服务按 DataScope 过滤 |
| P4 多子女切换 ChildSwitcher | Zustand slice`selectedChildId`+ 切换后 `queryClient.invalidateQueries({ queryKey: ['child', prevId] })`;多子女 Tab≤3或下拉≥4空态/单子女/多子女三态分支 |
| P4 多子女数量边界 | 0 子女EmptyChildState 引导绑定)→ 1 子女(隐藏切换器)→ 2-3Tab Bar→ 4-10下拉→ >10搜索 + 列表);超出 10 触发性能预警 |
| P4 通知偏好三维矩阵 | `Record<childId, Record<eventType, Record<channel, boolean>>>`react-hook-form + Zod 校验;默认值矩阵(成绩发布=邮件+短信、作业批改=邮件、教师沟通=短信、学校通知=邮件);渠道可用性校验 |
| P4 跨标签同步 | BroadcastChannel API`parent-portal-sync`+ localStorage 持久化 + LWWLast-Write-Wins冲突解决同步 selectedChildId / 通知偏好 / 已读状态 |
| P4 隐私合规COPPA/FERPA/PIPL | 未成年人数据最小化采集;通知偏好默认 opt-in数据保留 7 年后自动归档;前端不缓存敏感字段到 localStorage仅 childId + 已读位图) |
| P4 i18n 5 语言含 RTL | next-intl 支持 zh-CN / en-US / zh-TW / ja-JP / ar-SAar-SA 用 `dir="rtl"` + Tailwind `rtl:` 变体;翻译文件按域分 namespaceparent/grades/notifications/preferences |
| P4 PWA + Service Worker | manifest.jsonname/short_name/icons/theme_color+ Service Worker 缓存策略App Shell precache + API stale-while-revalidate + 离线兜底页P5+ 支持离线查看子女成绩缓存 |
| P4 API 契约版本管理 | 主版本 URL `/api/v1/*` → `/api/v2/*` + 子版本响应头 `X-API-Version` + Deprecation/Sunset 头埋点 + 低带宽场景 `X-Fields` 字段裁剪GraphQL 风格BFF REST 透传) |
| P4 性能预算 | Bundle ≤ 80KB JS / 20KB CSS / 200KB 总recharts 懒加载MF shared 单例避免 React 重复打包dynamic import 非首屏组件;预加载子女数据 |
| P4 监控与降级 | 10 项前端指标MF 加载失败率/ApiClient 错误率/Web Vitals/切换延迟等)+ 9 项降级策略MF 失败回退独立页/BFF 5xx 降级缓存/WS 重试指数退避等) |
| P4 模块演化与解耦 | 拆分触发条件Bundle > 120KB / 团队 > 3 人 / 独立部署需求);拆分后 MF 配置从 Shell 暴露状态管理迁移路径Zustand → Server Component + Server ActionsMF 升级路径MF 2.0 → 3.0 |
| P4 iam 家长-学生关联 P0 阻塞 | iam 缺 `GetChildrenByParent` proto RPC + `GET /iam/children` REST + `iam_student_guardians` 表ai02 补全前 parent-portal 仅能假设单子女硬编码 childId 开发调试 |
| P4 BFF 错误码前缀 | **BFF_PARENT\_=parent-bff**coord final §B5统一 `BFF_XXX_` 风格);前端按 `BFF_PARENT_*` 前缀路由 i18n key与 BFF_TEACHER_/BFF_STUDENT_ 对齐 |
| 场景 | 技术/规则 |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P4 端口分配 | parent-portal = **4002**004 §1.2 强制 4 端 4000-4003ai07 初稿误用 3002 与 iam 冲突ai15 修订) |
| P4 MF Remote 角色 | next.config.js 配 `@module-federation/nextjs-mf` 暴露 `./ChildSwitcher` 等家长端特有组件,复用 Shell 提供的 `AppShell`/`usePermission`/`ApiClient`/`RequirePermission` |
| P4 DataScope CHILDREN | 家长仅能查看自己绑定子女的数据004 §5.4 L0 SELF 变体),前端通过 ApiClient 透传 userId + childId下游服务按 DataScope 过滤 |
| P4 多子女切换 ChildSwitcher | Zustand slice`selectedChildId`+ 切换后 `queryClient.invalidateQueries({ queryKey: ['child', prevId] })`;多子女 Tab≤3或下拉≥4空态/单子女/多子女三态分支 |
| P4 多子女数量边界 | 0 子女EmptyChildState 引导绑定)→ 1 子女(隐藏切换器)→ 2-3Tab Bar→ 4-10下拉→ >10搜索 + 列表);超出 10 触发性能预警 |
| P4 通知偏好三维矩阵 | `Record<childId, Record<eventType, Record<channel, boolean>>>`react-hook-form + Zod 校验;默认值矩阵(成绩发布=邮件+短信、作业批改=邮件、教师沟通=短信、学校通知=邮件);渠道可用性校验 |
| P4 跨标签同步 | BroadcastChannel API`parent-portal-sync`+ localStorage 持久化 + LWWLast-Write-Wins冲突解决同步 selectedChildId / 通知偏好 / 已读状态 |
| P4 隐私合规COPPA/FERPA/PIPL | 未成年人数据最小化采集;通知偏好默认 opt-in数据保留 7 年后自动归档;前端不缓存敏感字段到 localStorage仅 childId + 已读位图) |
| P4 i18n 5 语言含 RTL | next-intl 支持 zh-CN / en-US / zh-TW / ja-JP / ar-SAar-SA 用 `dir="rtl"` + Tailwind `rtl:` 变体;翻译文件按域分 namespaceparent/grades/notifications/preferences |
| P4 PWA + Service Worker | manifest.jsonname/short_name/icons/theme_color+ Service Worker 缓存策略App Shell precache + API stale-while-revalidate + 离线兜底页P5+ 支持离线查看子女成绩缓存 |
| P4 API 契约版本管理 | 主版本 URL `/api/v1/*` → `/api/v2/*` + 子版本响应头 `X-API-Version` + Deprecation/Sunset 头埋点 + 低带宽场景 `X-Fields` 字段裁剪GraphQL 风格BFF REST 透传) |
| P4 性能预算 | Bundle ≤ 80KB JS / 20KB CSS / 200KB 总recharts 懒加载MF shared 单例避免 React 重复打包dynamic import 非首屏组件;预加载子女数据 |
| P4 监控与降级 | 10 项前端指标MF 加载失败率/ApiClient 错误率/Web Vitals/切换延迟等)+ 9 项降级策略MF 失败回退独立页/BFF 5xx 降级缓存/WS 重试指数退避等) |
| P4 模块演化与解耦 | 拆分触发条件Bundle > 120KB / 团队 > 3 人 / 独立部署需求);拆分后 MF 配置从 Shell 暴露状态管理迁移路径Zustand → Server Component + Server ActionsMF 升级路径MF 2.0 → 3.0 |
| P4 iam 家长-学生关联 P0 阻塞 | iam 缺 `GetChildrenByParent` proto RPC + `GET /iam/children` REST + `iam_student_guardians` 表ai02 补全前 parent-portal 仅能假设单子女硬编码 childId 开发调试 |
| P4 BFF 错误码前缀 | **BFF_PARENT\_=parent-bff**coord final §B5统一 `BFF_XXX_` 风格);前端按 `BFF_PARENT_*` 前缀路由 i18n key与 BFF_TEACHER_/BFF_STUDENT_ 对齐 |
| P4 认证 token 存储 | localStorageF12 裁决P4 用 localStorage非 cookierefreshAccessToken 竞态防护用 inflight Promise 复用,并发请求只触发一次 refresh fetch |
| P4 switchChild 纯前端 | ISSUE-009 裁决:切换子女不调后端,仅 Zustand slice + localStorage 持久化 selectedChildId切换后由前端 invalidateQueries 触发数据刷新 |
| P4 通知偏好 localStorage 降级 | ISSUE-033 裁决P4 localStorage 为准,更新先写本地再尝试 GraphQL mutation失败不阻塞P5+ 切换到后端为主存储 |
| P4 上游 mock 策略 | MSW 拦截 parent-bff GraphQL + iam RESTiam GetChildrenByParent P0 阻塞用 mock fixturesstudent-001 + student-002 双子女MSW server 在 providers 启动 |
| TS noUncheckedIndexedAccess | Record 索引访问返回 `T \| undefined`;内层 Record 改 `Partial<Record<...>>` 或空对象回退(`?? {}`)避免 undefined 传播 |
| urql fetchOptions headers 类型 | 返回类型须用 `Record<string, string>` 累积再展开,不可用条件对象(`{...(token?{Authorization}:{})}`)否则 TS 推断不兼容 |
| Vitest Promise 身份比较 | `expect(p1).toBe(p2)` 对 Promise 不可靠async 包装产生新 Promise改为验证行为副作用如 fetch 调用次数 === 1 证明竞态防护生效) |
| jsdom window.location.href | 设置触发 "Not implemented: navigation" stderr 警告非错误测试仍通过mock 用 `Object.defineProperty(window, "location", {...})` 替代 `vi.spyOn` |
| MSW 响应过快观察加载态 | MSW 默认同步响应,加载态测试看不到 "加载中";用 `server.use()` + `delay(ms)` 延迟响应再断言按钮文本 |
| Testing Library getByRole 误匹配 | `getByRole("button", { name: /未读/ })` 会匹配通知项 aria-label="未读";改用 `getAllByRole("button")` + find 文本内容过滤精确选中筛选按钮 |
| ESLint flat config 测试文件 | 放宽规则需含 `*.test.tsx`/`*.spec.tsx`/`__tests__/**` 模式,否则 tsx 测试文件按严格规则报错 |
| Dockerfile 多阶段构建 | G1 裁决首次即多阶段buildernode:20-alpine 跑 build+ runnernode:20-alpine 跑 server端口 4002HEALTHCHECK 指向 `/api/health`,非 root 用户 nextjs |
### 2.14 parent-bffTS/NestJSP4