# 模块理解确认书 — parent-portal

> AI：ai15（TS/React · 家长场景域前端 remote）
> 阶段：阶段 1 交付物（v2 — ai15 接管审计与补全版）
> 初版日期：2026-07-09（ai07 起草）
> 审计日期：2026-07-09（ai15 修订：端口、所有权、长远架构遗漏补全）
> 关联：[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §1.1a/1.1b/§5.4、[AI 分配方案](../../../docs/architecture/ai-allocation.md) §3.2 ai15、[pending-features P4](../../../docs/architecture/roadmap/pending-features.md)、[known-issues §2.12](../../../docs/troubleshooting/known-issues.md)、[teacher-portal 阶段1](../../teacher-portal/docs/01-understanding.md)、[teacher-portal 阶段2](../../teacher-portal/docs/02-architecture-design.md)、[parent-bff 阶段1](../../../services/parent-bff/docs/01-understanding.md)

> **审计修订摘要**（ai15 → ai07 初稿）：
>
> 1. **端口修订**：3002 → **4002**（004 §1.2 强制 4 端 4000-4003，project memory 硬约束）
> 2. **MF URL 修订**：teacher/student/parent/admin 端 URL 全部从 3000-3003 修订为 4000-4003
> 3. **所有权修订**：ai07 → **ai15**（ai-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-gateway（REST，经 Next.js `rewrites` 代理 `/api/v1/*`）
- **下游（推送，P5）**：push-gateway（WebSocket，含 SSE 降级）
- **BFF 对接**：parent-bff（ai04 设计，端口 3010）
- **通信方式**：HTTP/REST（前端→Gateway）+ WebSocket（前端→push-gateway，P5）+ SSE 降级（P5+）
- **不直连**：前端不直连任何业务服务或 BFF 后端实例，全部经 api-gateway 代理

**说明**：

- 通过 `next.config.js` 的 `rewrites` 将 `/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/viewports`、`GET /parent/dashboard`、`GET /parent/children`、`POST /parent/children/:childId/select`、`GET /parent/children/:childId/exams`、`/homework`、`/grades`、`/analytics/trend`、`/analytics/weakness`、`GET /parent/notifications`、`PUT /parent/notification-preferences` |
| `/api/v1/iam/*`           | iam           | `POST /iam/login`、`GET /iam/me`、`GET /iam/effective-permissions`、`GET /iam/children`（**P0 阻塞**，待 ai02 补全）                                                                                                                                                                             |
| `/api/v1/notifications/*` | msg           | 通知中心（P5）                                                                                                                                                                                                                                                                                   |

> parent-bff 聚合 iam + core-edu + data-ana + msg，对外暴露家长场景的统一接口（子女关联、视口、学情聚合）。
> **P0 阻塞项**（来自 parent-bff §7.1）：iam 缺失"家长-学生关联查询"接口（`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）：

```typescript
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）

| 协议                      | 场景                             |
| ------------------------- | -------------------------------- |
| WebSocket（push-gateway） | 子女成绩发布、教师沟通、学校通知 |

### 3.4 proto 不直接消费

前端不调用 gRPC，BFF 把 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-a11y（error 级）                       | WCAG 2.2 AA                                             |
| 字体                        | Inter（sans）/ Fraunces（serif）/ JetBrains Mono（mono） | 由 Shell RootLayout 加载，parent-portal 仅消费 CSS 变量 |

## 5. 我的阶段归属

- **阶段**：P4
- **当前状态**：📐 需设计（待 parent-bff + data-ana 就绪），apps/parent-portal/ 目录为空（待建）
- **依赖上游阶段**：P4（parent-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 + Sentry（P6）                                              | ❌ 待建  |
| metrics               | prom-client `/metrics`                | 前端 Web Vitals → Gateway 上报                                           | ❌ 待建  |
| tracer                | OTel SDK                              | 前端 OTel browser SDK（P6）                                              | ❌ 待建  |
| /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 SDK（P6）                  |
| /healthz                             | ❌     | 待建，Next.js Route Handler                   |
| /readyz                              | ❌     | 待建                                          |
| 优雅关闭                             | ✅ N/A | Next.js 无长连接                              |
| 测试覆盖率                           | ❌     | 0%，无测试文件（待建）                        |
| Dockerfile 多阶段                    | ❌     | 待建（builder + runtime）                     |
| Zod 输入验证                         | ❌     | 待建（react-hook-form + zodResolver）         |
| GlobalErrorFilter（ErrorBoundary）   | ❌     | 待建，复用 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（左栏导航 + 主内容区）
- RequirePermission（L3 组件级视口控制）
- ErrorBoundary（React 渲染异常兜底）
- Loading（骨架屏）
- Empty（空态）
- DataTable（表格）
- Form（react-hook-form + zodResolver 封装）
- Chart（recharts 封装）

### 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 不使用的组件

- RichTextEditor（Tiptap）—— 家长端不编辑富文本
- 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 数据保留策略（前端配合）

| 数据类型         | 前端保留              | 后端保留   | 前端处理                     |
| ---------------- | --------------------- | ---------- | ---------------------------- |
| 子女成绩列表缓存 | 30s（TanStack 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 + MSW（Mock Service Worker）        | ≥ 75%      | API 请求层 + TanStack Query hook 组合、ChildSwitcher + Zustand slice + invalidate 流程                |
| 视觉回归  | Playwright + Percy/Applitools（P6 引入）   | 关键页面   | 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 模拟）                                                |
| 契约测试  | Pact（BFF ↔ parent-portal 双向，P6 引入）  | 关键端点   | 防止 BFF 契约变更打破前端消费                                                                         |

### 13.2 关键 E2E 场景（必须覆盖）

```yaml
- 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 handlers，按 `apps/parent-portal/src/mocks/handlers.ts` 集中管理 | ai15   |
| WebSocket 事件 | Mock WebSocket Server（Playwright 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 代码分割策略

```typescript
// 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=Strict（project_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 实现

```typescript
// 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-1，Tab B 在 10:00:01 切换到 child-2：

- 两个 Tab 都收到对方的 BroadcastChannel 消息
- 采用 LWW：以 `ts` 字段为依据，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 properties：`ms-*`/`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+ 引入）

```json
// 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 阶段演进路线

```mermaid
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 + VAPID（P5+ 引入）                                 | P5+      |

### 19.3 模块解耦与演化

| 演化方向                        | 触发条件                         | 迁移策略                                                                                              |
| ------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------- |
| parent-portal 拆分为多个 Remote | bundle > 200KB 或团队规模 > 5 人 | 按场景域拆分：parent-core-remote（dashboard/grades）+ parent-comm-remote（notifications/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 接管审计与补全）
