Files
Edu/apps/parent-portal/docs/01-understanding.md
SpecialX db90b2e080 docs(parent-portal): ai15 阶段1+2 文档审计与补全
按 ai-allocation.md §3.2 接管 parent-portal,完成阶段 1+2 文档审计与补全。
修订端口(3002→4002)、所有权(ai07→ai15),补全 16 章长远架构。

AI Agent: ai15 (parent-portal remote)
Branch: docs/parent-portal-stage1-stage2-design-ai15
Coordinator: coord-ai
Predecessor: ai07
2026-07-09 18:51:10 +08:00

51 KiB
Raw Blame History

模块理解确认书 — parent-portal

AIai15TS/React · 家长场景域前端 remote 阶段:阶段 1 交付物v2 — ai15 接管审计与补全版) 初版日期2026-07-09ai07 起草) 审计日期2026-07-09ai15 修订:端口、所有权、长远架构遗漏补全) 关联:004 架构影响地图 §1.1a/1.1b/§5.4、AI 分配方案 §3.2 ai15、pending-features P4known-issues §2.12teacher-portal 阶段1teacher-portal 阶段2parent-bff 阶段1

审计修订摘要ai15 → ai07 初稿):

  1. 端口修订3002 → 4002004 §1.2 强制 4 端 4000-4003project memory 硬约束)
  2. MF URL 修订teacher/student/parent/admin 端 URL 全部从 3000-3003 修订为 4000-4003
  3. 所有权修订ai07 → ai15ai-allocation.md §3.2
  4. 遗漏补全i18n 策略、移动端/PWA、多子女边界场景、通知偏好数据模型、隐私合规COPPA/FERPA、测试策略分层、韧性模式、性能预算、CSP/前端安全、跨标签同步、API 版本演进、未来扩展铺垫、长远愿景
  5. 新增章节§11 多子女边界场景、§12 隐私合规、§13 测试策略、§14 性能与预算、§15 前端安全、§16 跨标签与跨设备同步、§17 i18n 深化、§18 移动端与 PWA、§19 长远愿景与演进路径

1. 我在架构中的位置

  • 层级L2 微前端层004 §3.1 六层架构中的前端层)
  • MF 角色Remote 子应用,挂载到 teacher-portal Shell
  • 上游(谁调用我):浏览器(家长)— 含桌面 Chrome/Edge/Safari、移动端 iOS Safari/Android Chrome
  • 下游(同步)api-gatewayREST经 Next.js rewrites 代理 /api/v1/*
  • 下游推送P5push-gatewayWebSocket含 SSE 降级)
  • BFF 对接parent-bffai04 设计,端口 3010
  • 通信方式HTTP/REST前端→Gateway+ WebSocket前端→push-gatewayP5+ SSE 降级P5+
  • 不直连:前端不直连任何业务服务或 BFF 后端实例,全部经 api-gateway 代理

说明

  • 通过 next.config.jsrewrites/api/v1/* 代理到 api-gateway
  • MF 架构下parent-portal 作为 Remote 子应用挂载到 teacher-portal Shell复用 Shell 的 AppShell、共享依赖、权限 Hook、API 请求层
  • 不独立提供 RootLayout / 登录页 / 字体加载 / 令牌初始化,全部由 Shell 提供
  • 与 teacher-portal 共享会话状态Session、视口Viewport、权限Permission三个核心模型

2. 我的限界上下文

2.1 我负责的聚合 / 实体(前端视图模型)

  • 多子女切换、通知偏好、学情查看、成绩通知(家长场景域前端视图)
  • 会话状态Session、视口Viewport、权限Permission—— 与 teacher-portal 共享
  • 多子女状态ChildSwitcher—— 家长端特有

2.2 业务领域

  • D5 家长场景域(前端场景域:家长场景域)

2.3 不负责

  • 教师沟通(归 teacher-portal
  • 学生作答(归 student-portal
  • 用户/角色/权限 CRUD归 admin-portal
  • 成绩录入(归 teacher-portal家长端仅查看

2.4 数据范围

  • DataScope L0仅子女—— 家长只能查看自己子女的数据,不能跨家庭

3. 我与外部的契约

3.1 消费的后端 API经 api-gateway 代理)

路径前缀 下游 BFF/服务 关键端点
/api/v1/parent/* parent-bff GET /parent/viewportsGET /parent/dashboardGET /parent/childrenPOST /parent/children/:childId/selectGET /parent/children/:childId/exams/homework/grades/analytics/trend/analytics/weaknessGET /parent/notificationsPUT /parent/notification-preferences
/api/v1/iam/* iam POST /iam/loginGET /iam/meGET /iam/effective-permissionsGET /iam/childrenP0 阻塞,待 ai02 补全)
/api/v1/notifications/* msg 通知中心P5

parent-bff 聚合 iam + core-edu + data-ana + msg对外暴露家长场景的统一接口子女关联、视口、学情聚合P0 阻塞项(来自 parent-bff §7.1iam 缺失"家长-学生关联查询"接口(GetChildrenByParent proto + GET /iam/children REST + iam_student_guardians 表三缺失)。在 ai02 补全前parent-portal 的多子女场景无法落地,仅能假设单子女硬编码 childId 进行开发调试。

3.1.1 API 契约版本演进策略

版本信号 携带位置 演进规则
主版本 URL 路径 /api/v1/*/api/v2/* 破坏性变更升主版本parent-portal 同时支持 v1 + v2 至少 1 个迭代周期4 周),通过 Feature Flag 切换
子版本 响应头 X-API-Version: 2026-07-09 向后兼容字段新增前端忽略未知字段Zod 默认行为)
Deprecation 响应头 Deprecation: true + Sunset: <date> 前端收到 Deprecation 头后上报埋点,跟踪使用率,确认 < 1% 后移除前端调用
字段裁剪 请求头 X-Fields: grades[].id,grades[].score 父 portal 在低带宽移动场景按需裁剪GraphQL 风格BFF REST 透传支持)

parent-portal 不主动驱动 API 版本升级;契约变更由 coord 协调各业务 AI 落地。parent-portal 仅负责消费侧的兼容与迁移。

3.2 统一响应契约

所有后端响应遵循 ActionState 结构(迁移指南 §7.5

type ActionState<T> =
  | { success: true; data: T }
  | {
      success: false;
      error: { code: string; message: string; details?: unknown };
    };

错误码前缀按服务名大写(如 IAM_CORE_EDU_GRADES_HOMEWORK_BFF_PARENT_GW_NETWORK_)。前端 API 请求层根据 error.code 前缀路由到对应的 i18n key。

3.3 推送契约P5

协议 场景
WebSocketpush-gateway 子女成绩发布、教师沟通、学校通知

3.4 proto 不直接消费

前端不调用 gRPCBFF 把 gRPC 聚合为 REST 暴露给前端。前端仅消费 packages/contracts/src/permissions.ts 中的权限点常量TS 文件,非 proto 生成)。

4. 我的技术栈

维度 选型 说明
框架 Next.js 14+App Router 与 teacher-portal 一致MF Remote 角色
语言 TypeScript 5.5+strict 沿用 tsconfig.base.json
微前端 Module Federation 2.0@module-federation/nextjs-mf parent-portal = Remote
样式 Tailwind CSS 3.4+ 配合设计令牌三层模型(与 teacher-portal 共享)
UI 组件库 shadcn/ui迁移指南 §7.2 复用 Shell 暴露的 packages/ui-components/
状态管理 L1 URL nuqs 可分享、可刷新状态
状态管理 L2 Server TanStack Query v5 服务端数据缓存、重试、乐观更新
状态管理 L3 Client Business Zustand slice 客户端业务状态(含 ChildSwitcher 多子女状态)
状态管理 L4 Global UI Zustand ui-store + ModalRoot 全局 UI 状态(复用 Shell 暴露)
状态管理 L5 Form react-hook-form + zodResolver 表单状态(通知偏好设置)
富文本 N/A 家长端不使用 Tiptap
图表 recharts 子女学情、Dashboard
i18n next-intl BFF/服务返回 i18n key + 参数,前端翻译
A11y eslint-plugin-jsx-a11yerror 级) WCAG 2.2 AA
字体 Intersans/ Frauncesserif/ JetBrains Monomono 由 Shell RootLayout 加载parent-portal 仅消费 CSS 变量

5. 我的阶段归属

  • 阶段P4
  • 当前状态📐 需设计(待 parent-bff + data-ana 就绪apps/parent-portal/ 目录为空(待建)
  • 依赖上游阶段P4parent-bff + data-ana

6. 我需要对齐的黄金模板项(对照 classes 服务)

前端无 @RequirePermission 装饰器后端概念对齐项改造为前端等价物。parent-portal 与 teacher-portal 共享前端等价物实现。

对齐项 classes后端黄金模板 parent-portal 前端等价 当前状态
权限校验 @RequirePermission(Permissions.XXX) usePermission().hasPermission("XXX") Hook + <RequirePermission> 组件 待建
错误码前缀统一 CLASSES_*IAM_* API 请求层根据 error.code 前缀路由 i18n 待建
logger pino 前端 console + SentryP6 待建
metrics prom-client /metrics 前端 Web Vitals → Gateway 上报 待建
tracer OTel SDK 前端 OTel browser SDKP6 待建
/healthz + /readyz GET /healthz GET /readyz Next.js /api/health route + Dockerfile HEALTHCHECK 待建
优雅关闭 SIGTERM handler Next.js 无长连接,无需 N/A
测试覆盖率 ≥ 80% Vitest Vitest + @testing-library/react + Playwright E2E 待建
Dockerfile 多阶段构建 builder + runtime builder + runtime 待建
Zod 输入验证 class-validator + Zod schema react-hook-form + zodResolver 待建
GlobalErrorFilter NestJS 全局异常过滤器 React ErrorBoundary + API 请求层统一错误处理 待建
设计令牌三层 primitive.css / semantic-light/dark.css / tailwind-theme.css 待建
A11y 工具集 useA11yId / mergeA11yProps / describeInput / focus-trap 待建

parent-portal 现状审计(对齐黄金模板)

审计表

维度 状态 说明
权限装饰器(前端等价 usePermission 待建,复用 Shell 暴露的 usePermission Hook
错误码前缀 待建,复用 Shell 暴露的 ApiClient
logger 待建,复用 packages/shared-ts/src/logger.ts
metrics 待建Web Vitals 上报
tracer 待建OTel browser SDKP6
/healthz 待建Next.js Route Handler
/readyz 待建
优雅关闭 N/A Next.js 无长连接
测试覆盖率 0%,无测试文件(待建)
Dockerfile 多阶段 待建builder + runtime
Zod 输入验证 待建react-hook-form + zodResolver
GlobalErrorFilterErrorBoundary 待建,复用 Shell 暴露的 ErrorBoundary
设计令牌三层 待建,复用 packages/ui-tokens
A11y 工具集 待建,复用 Shell 暴露的 A11y 工具集
Module Federation 配置 待建Remote 角色)
5 层状态管理 待建(含 ChildSwitcher 多子女 Zustand slice
共享组件库 待建(复用 Shell 暴露 + 新增 ChildSwitcher
i18n 待建next-intl
API 请求层 待建,复用 Shell 暴露的 ApiClient
ESLint flat config 自定义规则 待建,复用 teacher-portal 配置

现有文件清单

apps/parent-portal/
└─ (空目录,待建)

parent-portal 当前为空目录,所有维度均为 待建状态。基础设施Shell 暴露的 AppShell、共享依赖、权限 Hook、API 请求层、设计令牌、UI 组件、A11y 工具集)由 teacher-portal Shell 在 P2 收尾时建立parent-portal 在 P4 启动时直接复用。


7. L1 导航菜单(视口)

家长端 L1 导航菜单由 parent-bff 通过 GET /parent/viewports 返回AppShell 按 scope: 'parent' 过滤渲染:

  • Dashboard家长仪表盘
  • 子女切换
  • 成绩查看
  • 作业查看
  • 通知中心P5
  • 通知偏好设置

8. L2 路由表

路由 页面 权限
/parent/dashboard 家长仪表盘 PARENT_DASHBOARD_VIEW
/parent/children 子女列表 PARENT_CHILDREN_VIEW
/parent/grades 子女成绩 GRADES_READ_CHILD
/parent/homework 子女作业 HOMEWORK_READ_CHILD
/parent/notifications 通知中心P5 NOTIFICATION_READ_OWN
/parent/preferences 通知偏好 PARENT_PREFERENCES_UPDATE

L3 组件级视口用 <RequirePermission perm="PARENT_PREFERENCES_UPDATE"><Button>保存</Button></RequirePermission>

9. L3 组件级差异parent-portal 特有)

9.1 复用 Shell 暴露的组件

  • AppShell左栏导航 + 主内容区)
  • RequirePermissionL3 组件级视口控制)
  • ErrorBoundaryReact 渲染异常兜底)
  • Loading骨架屏
  • Empty空态
  • DataTable表格
  • Formreact-hook-form + zodResolver 封装)
  • Chartrecharts 封装)

9.2 parent-portal 特有组件(完整清单)

组件 用途 来源 是否 MF 暴露
ChildSwitcher 多子女切换组件(顶部 Tab / 移动端下拉),切换后 invalidate 子女相关查询 新建 ./ChildSwitcher
ChildSummaryCard 单个子女的概览卡片(头像/姓名/年级/今日作业数/近期成绩趋势缩略图) 新建 内部使用
ParentDashboard 家长仪表盘容器(多子女并列卡片 + 全家聚合统计 + 待办提醒) 新建 内部使用
ChildGradeChart 子女成绩趋势图(折线 + 班级均分对比 + 区间填充)+ 多子女对比模式 新建 内部使用
AttendanceCalendar 出勤日历热力图(按月网格展示出勤/缺勤/迟到/请假,全年概览) 新建 内部使用
NotificationFeed 通知流(按子女×类型×已读筛选,支持批量已读、跳转、置顶) 新建 内部使用
PreferenceForm 通知偏好设置表单子女×事件×渠道三维矩阵react-hook-form + Zod 校验) 新建 内部使用
ChildComparisonView 多子女横向对比视图(成绩/出勤/作业完成率并排表格P5+ 新建 内部使用
EmptyChildState 无子女绑定引导CTA 跳转绑定流程,含客服联系方式) 新建 内部使用
MultiChildTabBar 多子女 Tab 栏≤3 子女用 Tab>3 子女用下拉,移动端友好) 新建 内部使用

9.3 不使用的组件

  • RichTextEditorTiptap—— 家长端不编辑富文本
  • ExamTaking —— 学生考试专用
  • SSEViewer —— AI 流式响应查看器(教师端专用)
  • UserManagementTable —— 管理员端专用
  • LessonPlanEditor —— 教师备课专用
  • KnowledgeGraphViewer —— 教师查看知识点图谱专用(家长端仅看诊断结论)

10. L4 数据层差异

维度 teacher-portal parent-portal
主要数据源 teacher-bff parent-bff聚合 iam + core-edu + data-ana + msg
缓存策略 5-30s 短缓存 5-30s 短缓存,子女切换 invalidate 子女相关查询
多子女状态 N/A ChildSwitcher Zustand slice当前子女 ID 持久化到 localStorage
跨标签同步 N/A BroadcastChannel API + Storage 事件同步当前子女 ID见 §16
离线缓存 N/A P5+ PWA Service Worker 缓存最近查看的子女数据快照(见 §18

11. 多子女边界场景(家长端特有,必须覆盖)

家长端的核心复杂度来自多子女,必须在架构中预留所有边界场景的处理。

11.1 子女数量边界

场景 触发条件 前端处理 后端契约依赖
0 子女(未绑定) 新注册家长或子女关系被解除 显示 EmptyChildState 引导页CTA 跳转绑定流程;隐藏 dashboard/grades/homework 等业务路由 GET /parent/children 返回 []
1 子女 单子女家庭 不显示 MultiChildTabBar直接进入业务页面URL 不携带 ?childId= GET /parent/children 返回长度 1 数组
2-3 子女 多子女家庭(典型) MultiChildTabBar 显示 Tab 形式,默认选中最近查看的子女 同上
4-10 子女 大家庭或重组家庭 MultiChildTabBar 改为下拉选择器 + 头像缩略Tab 栏超过 3 个时自动切换 同上
>10 子女 校管理员或多监护人代管场景 强制下拉选择器 + 搜索框(按姓名/学号筛选);列表分页加载 GET /parent/children 支持分页 + 搜索

11.2 子女档案变更边界

场景 触发条件 前端处理 事件来源
子女被解绑 家长或管理员主动解绑 收到 WebSocket 事件 → invalidate children 列表 → 若当前选中子女被解绑,自动切换到第一个子女;若无子女则跳引导页 edu.identity.user.updated
子女档案被归档/转学 学校主动操作 收到事件后该子女标识为"已离校",灰色显示但保留历史数据查看权限;不可选为当前子女 edu.identity.user.updated
子女姓名/头像变更 学校维护档案 收到事件 → invalidate children 列表 → UI 自动刷新;不中断当前操作 edu.identity.user.updated
当前子女被切到其他监护人 监护权变更 同"解绑"处理 edu.identity.user.updated
新增子女绑定 家长新增绑定子女 收到事件 → invalidate children 列表 → 显示 toast 提示"已添加子女XXX" → 不自动切换当前选中子女 edu.identity.user.created

11.3 跨标签与跨设备同步边界

场景 触发条件 前端处理
同浏览器多标签切换子女 用户在 Tab A 切换子女 Tab B 通过 BroadcastChannel('parent-child-switch') 收到消息 → 同步更新 Zustand slice → invalidate 子女维度查询
隐身模式 / 不同浏览器 用户在另一浏览器登录 各自独立状态;服务端最终一致(依赖 iam currentChildId 是否持久化,按 ai04 建议方案 A 不持久化,仅前端 localStorage
移动端 + 桌面端同时登录 用户多设备登录 各自独立 currentChildId通知偏好等共享数据通过 WebSocket 实时同步
网络中断时切换子女 离线场景 切换操作入队列IDB网络恢复后批量同步UI 显示"离线模式"标识

11.4 子女数据访问越权

场景 触发条件 前端处理
URL 直接访问非绑定子女 用户篡改 ?childId=xxx BFF 在 GET /parent/children/:childId/* 端点做 DataScope=CHILDREN 校验,返回 403 → 前端 toast 错误并跳转默认子女
切换到刚解绑的子女 网络延迟,事件未到达 BFF 校验失败 403 → 前端 invalidate children 列表 → 自动切换到第一个绑定子女
子女档案查看权限被回收 学校临时限制 同上BFF 校验

12. 数据隐私与合规

子女数据是高敏感数据parent-portal 必须在设计阶段预留合规框架。本节是 ai15 新增,覆盖 COPPA、FERPA、PIPL 等法规对前端架构的要求。

12.1 适用法规矩阵

法规 适用范围 对前端的要求 阶段
COPPA 美国 <13 岁儿童 收集前需家长可验证同意;展示同意记录入口;可删除子女数据请求入口 P6 海外扩展
FERPA 美国教育记录 家长有权查看子女教育记录;学校有权限制家长访问(离婚/监护权争议场景) P6 海外扩展
PIPL 中国个人信息保护法 隐私政策弹窗 + 同意按钮;敏感信息(成绩)展示前需二次确认;数据导出请求入口 P4 起强制
GDPR 欧盟用户 Cookie 同意管理;被遗忘权请求入口;数据可携带权导出 P6 海外扩展
未成年人保护法 中国 <18 岁 14 岁以下需家长同意;展示适合年龄段的内容过滤 P4 起强制

12.2 前端合规设计

合规点 实现位置 阶段
隐私政策同意弹窗 Shell RootLayout 首次登录后弹窗 P4
Cookie 同意管理(按类别) Shell + parent-portal 复用 P4
子女成绩展示二次确认 ChildGradeChart 默认遮罩,点击"查看"展示 P4
数据导出请求入口 /parent/preferences#data-export 页面 P5
数据删除请求入口 /parent/preferences#data-deletion 页面 P5
同意记录查看 /parent/preferences#consent-history 页面 P5
监护权变更影响展示 收到 BFF 403 时显示"请联系学校"提示,不暴露细节 P4
敏感数据脱敏 截图/分享时自动遮罩成绩数字 P5+

12.3 数据保留策略(前端配合)

数据类型 前端保留 后端保留 前端处理
子女成绩列表缓存 30sTanStack Query 永久 staleTime 30s 后自动失效
通知列表 30s 90 天 同上
子女列表 5min 关系存续期 同上
当前选中子女 ID localStorage 永久 不持久化 用户主动清除或解绑时清除
行为埋点 内存队列 100 条 90 天 队列满后批量上报,上报后清空

13. 测试策略分层

ai07 初稿仅提到"覆盖率 ≥ 80%"未细化测试类型与覆盖率分目标。ai15 补全。

13.1 测试金字塔

层级 工具 覆盖率目标 测试范围
单元测试 Vitest + @testing-library/react ≥ 85% 所有组件渲染/交互、Hook 业务逻辑、Zod schema 校验、纯函数 utils
集成测试 Vitest + MSWMock Service Worker ≥ 75% API 请求层 + TanStack Query hook 组合、ChildSwitcher + Zustand slice + invalidate 流程
视觉回归 Playwright + Percy/ApplitoolsP6 引入) 关键页面 dashboard/children/grades/notifications/preferences 五个核心页面在 light/dark + mobile/desktop 4 组合
E2E 测试 Playwright 关键路径 登录→看 dashboard→切换子女→查成绩→改通知偏好→登出
A11y 测试 axe-core + jest-axe + @axe-core/playwright 0 严重违规 所有页面 WCAG 2.2 AA 自动扫描 + 手动键盘导航测试
性能测试 Lighthouse CI ≥ 90 分 LCP < 2.5s / CLS < 0.1 / TBT < 200ms移动端 4G 模拟)
契约测试 PactBFF ↔ parent-portal 双向P6 引入) 关键端点 防止 BFF 契约变更打破前端消费

13.2 关键 E2E 场景(必须覆盖)

- name: multi_child_switch_flow
  steps:
    - login as parent_with_3_children
    - assert MultiChildTabBar shows 3 tabs
    - click tab "child-2"
    - assert URL contains ?childId=child-2
    - assert grades list refreshes
    - assert TanStack Query cache invalidated for child-1 grades
    - reload page
    - assert current child preserved (localStorage)
    - open new tab
    - assert new tab syncs to child-2 (BroadcastChannel)

- name: zero_child_state
  steps:
    - login as parent_with_0_children
    - assert EmptyChildState shown
    - assert business routes hidden
    - assert CTA "联系学校绑定子女" displayed

- name: child_unbound_mid_session
  steps:
    - login as parent_with_2_children
    - select child-1
    - simulate WebSocket event: child-1 unbound
    - assert toast: "子女XXX已解绑"
    - assert auto-switch to child-2
    - assert no error page

- name: notification_preference_save
  steps:
    - login as parent
    - navigate to /parent/preferences
    - toggle "成绩推送" off for child-1
    - click save
    - assert success toast
    - assert TanStack Query cache invalidated
    - reload
    - assert preference persisted

- name: offline_mode
  steps:
    - login as parent
    - select child-1, view grades
    - simulate offline (Playwright network condition)
    - switch to child-2
    - assert queue indicator shown
    - simulate online
    - assert queued switch executed

13.3 Mock 数据策略

数据来源 Mock 方式 维护方
BFF API 响应 MSW handlersapps/parent-portal/src/mocks/handlers.ts 集中管理 ai15
WebSocket 事件 Mock WebSocket ServerPlaywright fixture ai15
i18n 文案 真实 next-intl messages不 Mock coord
设计令牌 真实 ui-tokens不 Mock ai07
权限列表 按 fixture 角色预设parent_with_X_children 等) ai15

14. 性能预算与代码分割

ai07 初稿未提及性能预算。ai15 补全。

14.1 性能预算Bundle Size

资源类型 预算gzipped 备注
parent-portal 首屏 JS ≤ 80 KB 含 ChildSwitcher + ParentDashboard + 共享依赖分摊
parent-portal 首屏 CSS ≤ 20 KB 含 Tailwind purged + 设计令牌
路由级懒加载 chunk ≤ 30 KB/chunk 每个二级路由单独 chunk
图片 ≤ 100 KB/页 子女头像、空态插画
总下载量(首屏) ≤ 200 KB 4G 网络下 LCP < 2.5s

14.2 代码分割策略

// apps/parent-portal/src/app/(app)/parent/[route]/page.tsx
import dynamic from "next/dynamic";

const GradesPage = dynamic(() => import("./GradesPage"), {
  loading: () => <Skeleton rows={8} />,
  ssr: false, // MF Remote 默认 CSR
});

const NotificationsPage = dynamic(() => import("./NotificationsPage"), {
  loading: () => <Skeleton rows={10} />,
});

// 通知偏好表单重型react-hook-form + zod独立 chunk
const PreferencesPage = dynamic(() => import("./PreferencesPage"), {
  loading: () => <Skeleton rows={6} />,
});

14.3 预加载策略

触发时机 预加载内容
Dashboard 加载完成 预加载 grades chunk + homework chunk最常访问
鼠标 hover Tab 栏子女头像 预加载该子女的 grades 数据TanStack Query prefetch
通知未读数 > 0 预加载 notifications chunk
用户进入 grades 页面 预加载 analytics chunk趋势图 next-step

14.4 渲染策略

页面 渲染模式 理由
/parent/dashboard SSR首屏+ CSR交互 SEO + 首屏速度
/parent/children CSR 认证后数据,无需 SEO
/parent/grades CSR + Suspense 数据频变SSR 反而拖慢
/parent/notifications CSR + 流式渲染 实时性要求
/parent/preferences CSR 表单交互

15. 前端安全

ai07 初稿仅在横切关注点提到 401 处理。ai15 补全完整前端安全策略。

15.1 安全头HTTP Headers

由 teacher-portal Shell 在 next.config.js 配置parent-portal 复用:

Header 用途
Content-Security-Policy default-src 'self'; script-src 'self' 'unsafe-inline'; ... XSS 防护MF 远程加载需放行 Shell 域
X-Frame-Options SAMEORIGIN 防止 click-jacking
X-Content-Type-Options nosniff 防止 MIME 嗅探
Referrer-Policy strict-origin-when-cross-origin 限制 referrer 泄漏
Permissions-Policy camera=(), microphone=(), geolocation=() 禁用不需要的浏览器能力
Strict-Transport-Security max-age=31536000; includeSubDomains 强制 HTTPS

15.2 XSS 防护

场景 防护措施
子女姓名/学校名称展示 React 默认转义,禁止 dangerouslySetInnerHTML
通知内容(含富文本) DOMPurify 清洗后渲染project_rules §4
URL 参数 ?childId= Zod 校验为 UUID 格式,禁止任意字符
localStorage 存储子女 ID 仅存 UUID不存敏感信息用户登出时清除

15.3 CSRF 防护

  • parent-portal 仅消费 GET/POST/PUT/DELETE所有 mutation 经 ApiClient
  • ApiClient 自动注入 X-Requested-With: XMLHttpRequest
  • 后端 BFF 校验该头 + 同源 Cookie SameSite=Strictproject_rules §4

15.4 敏感数据处理

数据 敏感级别 前端处理
子女姓名 默认展示截图时脱敏P5+
子女成绩 默认展示,但页面离开 5s 后自动遮罩(防偷窥)
子女出勤 同姓名
监护人联系方式 仅在 preferences 页面展示掩码显示138****1234
子女 ID URL 可携带,但 BFF 校验绑定关系
通知内容 不缓存到 localStorage仅 TanStack Query 内存缓存

16. 跨标签与跨设备同步

16.1 同步机制矩阵

场景 同步机制 同步内容 冲突解决
同浏览器多标签状态同步 BroadcastChannel API currentChildId、notification unread count 最后写入胜出LWW
localStorage 跨标签变更 storage 事件 currentChildId 持久化值 最后写入胜出
跨设备状态同步 WebSocket 事件P5 通知偏好变更、子女关系变更 服务端为准
网络恢复后状态对齐 重连后批量 invalidate + refetch 全部子女维度数据 服务端为准

16.2 BroadcastChannel 实现

// apps/parent-portal/src/lib/crossTabSync.ts
const channel = new BroadcastChannel("parent-child-switch");

// 发送:当前标签切换子女
export function broadcastChildSwitch(childId: string) {
  channel.postMessage({ type: "child-switched", childId, ts: Date.now() });
}

// 接收:其他标签同步切换
export function subscribeChildSwitch(callback: (childId: string) => void) {
  channel.onmessage = (event) => {
    if (event.data?.type === "child-switched") {
      callback(event.data.childId);
    }
  };
  return () => {
    channel.onmessage = null;
  };
}

16.3 跨标签选中子女冲突处理

若 Tab A 在 10:00:00 切换到 child-1Tab B 在 10:00:01 切换到 child-2

  • 两个 Tab 都收到对方的 BroadcastChannel 消息
  • 采用 LWWts 字段为依据ts 大的胜出
  • 胜出方写入 Zustand slice + localStorage
  • 败方 UI 自动同步到胜出方的选中子女
  • 用户感知:可能看到一瞬间的切换抖动,可接受

17. i18n 深化

ai07 初稿仅提到 next-intl。ai15 补全多语言策略。

17.1 支持语言矩阵

语言 locale 阶段 完成度要求
简体中文 zh-CN P4 强制 100%
英文 en-US P6 海外扩展 100%
繁体中文 zh-TW P6 海外扩展 100%
日文 ja-JP P6+ 未来 ≥ 80%
阿拉伯文RTL ar-SA P6+ 未来 ≥ 80%

17.2 locale 路由策略

采用 URL 前缀策略(与 Shell 共享,由 Shell 配置):

/zh-CN/parent/dashboard
/en-US/parent/dashboard
/parent/dashboard   → 默认重定向到浏览器首选语言
  • Next.js 中间件根据 Accept-Language 头自动重定向
  • 用户主动切换语言时写入 Cookie NEXT_LOCALE,下次访问直接命中
  • parent-portal 不维护 locale 路由,复用 Shell 中间件

17.3 翻译文件组织

apps/parent-portal/src/i18n/messages/
├─ zh-CN/
│  ├─ common.json       # 通用文案(确认/取消/加载中等)
│  ├─ dashboard.json
│  ├─ children.json
│  ├─ grades.json
│  ├─ homework.json
│  ├─ notifications.json
│  ├─ preferences.json
│  └─ errors.json       # 错误码 → i18n key 映射
├─ en-US/
│  └─ ... (镜像 zh-CN 结构)
└─ index.ts             # 按需加载 messages

按路由切分 message bundle避免首屏加载全部翻译。

17.4 国际化格式

数据类型 示例zh-CN 示例en-US
日期 Intl.DateTimeFormat 2026年7月9日 July 9, 2026
时间 同上 下午3:30 3:30 PM
数字 Intl.NumberFormat 1,234.56 1,234.56
百分比 同上 85.5% 85.5%
货币 同上 ¥1,234.50 $1,234.50
成绩等级 自定义映射表 优秀/良好/及格/不及格 A/B/C/D/F
时区 Intl.DateTimeFormat with timeZone Asia/Shanghai America/Los_Angeles

17.5 RTL 支持P6+ 预留)

  • Tailwind CSS logical propertiesms-*/me-*/ps-*/pe-* 替代 ml-*/mr-*/pl-*/pr-*
  • 设计令牌预留 RTL 语义令牌:--space-inline-start / --space-inline-end
  • 图标方向敏感(如返回箭头)需根据 dir 属性翻转

18. 移动端与 PWA

家长端是 4 端中移动端使用比例最高的(家长多在通勤/碎片时间查看),移动端策略必须前置。

18.1 响应式断点

断点 宽度 典型设备 parent-portal 布局变化
sm (default) < 640px iPhone/Android 手机 单列布局MultiChildTabBar 改为下拉;侧栏导航抽屉化
md 640-1024px iPad Mini/Android 平板 双列布局(侧栏 + 内容MultiChildTabBar 顶部 Tab
lg 1024-1280px iPad Pro/小笔记本 三列布局(侧栏 + 内容 + 详情);多子女并列卡片
xl > 1280px 桌面 三列布局;多子女对比视图横向滚动

18.2 移动端交互优化

场景 移动端优化
切换子女 下拉选择器 + 头像 + 姓名,单手操作可达
查看成绩 卡片式纵向滚动,避免横向表格;图表触摸缩放
通知列表 左滑标记已读、右滑删除iOS 风格)
通知偏好设置 大号 Toggle Switch手指点击友好
表单提交 底部固定按钮;软键盘弹出时自动避让
长列表 无限滚动 + 骨架屏;不上拉加载更多按钮

18.3 PWA 配置P5+ 引入)

// apps/parent-portal/public/manifest.json
{
  "name": "Edu 家长端",
  "short_name": "EduParent",
  "start_url": "/parent/dashboard",
  "display": "standalone",
  "orientation": "portrait",
  "background_color": "#ffffff",
  "theme_color": "#1677ff",
  "icons": [
    { "src": "/icons/parent-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/parent-512.png", "sizes": "512x512", "type": "image/png" }
  ],
  "shortcuts": [
    { "name": "子女成绩", "url": "/parent/grades" },
    { "name": "通知", "url": "/parent/notifications" }
  ]
}

18.4 Service Worker 策略P5+

资源类型 缓存策略 TTL
静态资源JS/CSS/图片) Cache First + 网络更新 24 小时
子女列表 Stale While Revalidate 5 分钟
子女成绩 Network First失败回退缓存 30 秒
通知列表 Network Only
通知偏好 Network Only
API 401 响应 不缓存

19. 长远愿景与演进路径

19.1 阶段演进路线

graph LR
    P4[P4 内容分析<br/>家长端 MVP<br/>dashboard+grades+homework] --> P5[P5 沟通AI<br/>通知中心+推送+通知偏好]
    P5 --> P6[P6 硬化<br/>PWA+A11y+性能+安全+多语言]
    P6 --> P7[P7+ 扩展<br/>家校沟通+缴费+活动 RSVP]
    P7 --> P8[P8+ 多租户<br/>学区/教育局版]

19.2 未来功能铺垫(架构预留)

未来功能 架构预留点 启用阶段
家校沟通IM 聊天) NotificationFeed 组件抽象为通用消息流WebSocket 事件协议预留 chat.* 类型 P7
缴费(学费/餐费) ParentDashboard 卡片插槽API 路由前缀 /parent/fees/* 预留 P7
活动 RSVP家长会/运动会) NotificationFeed 支持 rsvp 类型消息;状态机:待回复/已确认/已拒绝 P7
家长端 AI 助手 API 路由 /parent/ai/* 预留SSE 复用 teacher-portal 模式 P7+
多子女横向对比报告 ChildComparisonView 组件预留P5+ 启用) P5+
学区/教育局版多租户 URL 路由前缀 /{tenantId}/parent/* 预留; TanStack Query key 加 tenantId 维度 P8+
移动端原生壳 PWA → Capacitor 打包MF 不变 P8+
离线模式 Service Worker + IDB 队列P5+ 引入) P5+
推送通知Web Push Service Worker PushManager + VAPIDP5+ 引入) P5+

19.3 模块解耦与演化

演化方向 触发条件 迁移策略
parent-portal 拆分为多个 Remote bundle > 200KB 或团队规模 > 5 人 按场景域拆分parent-core-remotedashboard/grades+ parent-comm-remotenotifications/preferences
MF 2.0 → 3.0 升级 MF 3.0 稳定且解决 SSR 问题 Shell 端 @module-federation/nextjs-mf 升级parent-portal 仅改 name/filename 字段
切换为原生 SSR脱离 MF SEO 需求强烈或 MF 维护成本过高 保留 API 请求层和组件库;移除 MF 配置;独立部署为完整 Next.js 应用
状态管理迁移Zustand → Jotai Zustand 性能瓶颈或团队偏好 逐 slice 迁移Hook 接口保持不变

19.4 监控与降级

监控项 阈值 触发动作
parent-portal 5xx 错误率 > 1% 告警 SRE自动切换到只读模式隐藏 mutation 按钮)
MF Remote 加载失败 加载超时 10s Fallback 到 Shell 内置的最小化 dashboard静态引导页
WebSocket 连接失败 重试 5 次仍失败 降级为 HTTP 轮询(每 60s 拉取通知列表)
BFF 响应延迟 P95 > 3s 前端展示"加载缓慢"提示;自动缩短缓存 TTL
子女列表加载失败 3 次重试失败 显示"网络异常,请稍后重试"页面 + 重试按钮

AI Agent: ai15 (parent-portal remote) Branch: docs/parent-portal-stage1-stage2-design-ai15 Coordinator: coord-ai Predecessor: ai07初版起草ai15 接管审计与补全)