feat: auto committed
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
# parent-portal 对接契约
|
# parent-portal 对接契约
|
||||||
|
|
||||||
> 负责人:ai15
|
> 负责人:ai15
|
||||||
> 关联:[matrix.md](./matrix.md)
|
> 关联:[matrix.md](./matrix.md)、[parent-bff_contract.md](./parent-bff_contract.md)、[iam_contract.md](./iam_contract.md)、[push-gateway_contract.md](./push-gateway_contract.md)
|
||||||
|
> 依据:ARB-001(BFF GraphQL)、ARB-002(MF Shell 暴露清单)、[port-allocation.md](../../../infra/port-allocation.md) §4
|
||||||
|
> 待仲裁:ISSUE-001 ~ ISSUE-010(见 [objections/parent-portal_issue.md](../objections/parent-portal_issue.md)),仲裁前本契约按 ARB-001 GraphQL 方向编写
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,20 +11,20 @@
|
|||||||
|
|
||||||
### 1.1 gRPC 接口(如有)
|
### 1.1 gRPC 接口(如有)
|
||||||
|
|
||||||
无。parent-portal 是前端微前端 Remote。
|
无。parent-portal 是前端微前端 Remote,不提供 gRPC。
|
||||||
|
|
||||||
### 1.2 HTTP 端点(如有)
|
### 1.2 HTTP 端点(如有)
|
||||||
|
|
||||||
| Method | Path | 用途 | 认证 |
|
无对外 HTTP API 端点。parent-portal 是 Next.js 前端应用(MF Remote),不对外暴露 REST API。
|
||||||
| ------ | --------------------- | ------------ | ------------------------------------- |
|
|
||||||
| GET | / | 家长门户首页 | JWT 必需(前端路由守卫) |
|
> **说明**(ISSUE-006):parent-portal 的页面路由(`/parent/dashboard`、`/parent/grades` 等)是前端 SSR/CSR 路由,不是 HTTP API 端点。页面路由清单见 [01-understanding.md §8 L2 路由表](../../../apps/parent-portal/docs/01-understanding.md#8-l2-路由表)。
|
||||||
| GET | /children | 我的孩子列表 | JWT 必需 |
|
>
|
||||||
| GET | /child/:id/summary | 孩子概况 | JWT 必需 + 数据范围校验(仅自己孩子) |
|
> parent-portal 仅提供两个内部健康检查端点(非业务 API):
|
||||||
| GET | /child/:id/grades | 孩子成绩 | JWT 必需 + 数据范围校验 |
|
|
||||||
| GET | /child/:id/attendance | 孩子考勤 | JWT 必需 + 数据范围校验 |
|
| Method | Path | 用途 | 认证 |
|
||||||
| GET | /child/:id/homework | 孩子作业 | JWT 必需 + 数据范围校验 |
|
| ------ | ------------ | ----------------------- | ---- |
|
||||||
| GET | /child/:id/weakness | 孩子薄弱点 | JWT 必需 + 数据范围校验 |
|
| GET | /api/health | Dockerfile HEALTHCHECK | 无 |
|
||||||
| GET | /notifications | 通知中心 | JWT 必需 |
|
| GET | /api/ready | K8s readinessProbe | 无 |
|
||||||
|
|
||||||
### 1.3 GraphQL schema(如 BFF)
|
### 1.3 GraphQL schema(如 BFF)
|
||||||
|
|
||||||
@@ -30,19 +32,26 @@
|
|||||||
|
|
||||||
### 1.4 Kafka 事件发布(如有)
|
### 1.4 Kafka 事件发布(如有)
|
||||||
|
|
||||||
无。
|
无。前端不发布 Kafka 事件。
|
||||||
|
|
||||||
### 1.5 错误码前缀
|
### 1.5 错误码前缀
|
||||||
|
|
||||||
无(前端不定义错误码前缀,透传 BFF 错误码)。
|
parent-portal 不产生错误码前缀(前端不定义错误码)。消费侧错误码前缀见 §2.5。
|
||||||
|
|
||||||
### 1.6 微前端架构(补充)
|
### 1.6 微前端架构
|
||||||
|
|
||||||
| 角色 | 说明 |
|
| 角色 | 说明 |
|
||||||
| ---------------------- | ------------------------------------------------ |
|
| ---- | ---- |
|
||||||
| MF Remote | 家长门户是微前端远程模块 |
|
| MF 角色 | Remote(Shell = teacher-portal :4000) |
|
||||||
| 暴露的 remote 模块 | ParentApp(家长端完整应用)、shared 家长端组件 |
|
| Remote name | `parent_app` |
|
||||||
| module federation 配置 | `apps/parent-portal/module-federation.config.ts` |
|
| remoteEntry 路径 | `static/chunks/remoteEntry.js` |
|
||||||
|
| 暴露模块 | `./pages`(家长场景页面)、`./ChildSwitcher`(多子女切换组件) |
|
||||||
|
| MF 配置文件 | `apps/parent-portal/next.config.js`(NextFederationPlugin,见 [02-architecture-design §1.2](../../../apps/parent-portal/docs/02-architecture-design.md#12-mf-配置parent-portalnextconfigjs-remote-角色)) |
|
||||||
|
| MF shared(singleton) | react / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooks(ARB-002) |
|
||||||
|
| dev/prod 端口 | 4002([port-allocation.md](../../../infra/port-allocation.md) §4) |
|
||||||
|
| feature flag | `NEXT_PUBLIC_MF_ENABLED`(ARB-002,P4 默认开) |
|
||||||
|
|
||||||
|
> **注**(ISSUE-007):MF 配置文件统一为 `next.config.js`,不使用 `module-federation.config.ts`(与 02-architecture-design + teacher-portal Shell 一致)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -56,27 +65,85 @@
|
|||||||
|
|
||||||
无。前端不直接订阅 Kafka。
|
无。前端不直接订阅 Kafka。
|
||||||
|
|
||||||
### 2.3 HTTP 调用(如有)
|
### 2.3 HTTP 调用(非 GraphQL)
|
||||||
|
|
||||||
| 被调用方 | Method.Path | 用途 | mock 策略 |
|
| 被调用方 | Method.Path | 用途 | mock 策略 |
|
||||||
| ------------------- | ------------------------ | -------------------------------------------- | ---------------------------------------------------------------- |
|
| ------------------ | --------------------- | -------- | ------------------------------------------- |
|
||||||
| api-gateway (ai01) | POST /api/parent/graphql | 家长 GraphQL 查询(经网关代理到 parent-bff) | api-gateway/parent-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
|
| api-gateway (ai01) | POST /api/v1/iam/login | 家长登录 | api-gateway 就绪前 MSW 返回固定 JWT(parent 角色) |
|
||||||
| api-gateway (ai01) | POST /api/auth/login | 家长登录 | api-gateway 就绪前使用 MSW 返回固定 JWT |
|
|
||||||
| push-gateway (ai02) | GET /ws | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
|
> **注**(ISSUE-004):
|
||||||
|
> - 登录端点统一为 `POST /api/v1/iam/login`(与 [matrix.md](./matrix.md) §5 `/api/v1/iam/*` + 01 §3.1 前缀一致)
|
||||||
|
> - 登录是 parent-portal 唯一走 REST(非 GraphQL)的端点:登录前无 JWT,GraphQL endpoint 需鉴权
|
||||||
|
> - 待 coord 确认登录是否走 REST,其余走 GraphQL
|
||||||
|
|
||||||
### 2.4 GraphQL 查询域(经 api-gateway 代理到 parent-bff)
|
### 2.4 GraphQL 查询域(经 api-gateway 代理到 parent-bff)
|
||||||
|
|
||||||
| Query/Mutation | 用途 | mock 策略 |
|
> **依据**:ARB-001(BFF GraphQL)+ [parent-bff_contract.md](./parent-bff_contract.md) §1.3
|
||||||
| ---------------------------- | -------------------- | ----------------------------- |
|
>
|
||||||
| currentUser | 当前家长信息 | MSW 返回固定家长 |
|
> **端点**:`POST /api/v1/parent/graphql`(api-gateway 代理 `/api/v1/parent/*` → parent-bff :3010 `/graphql`)
|
||||||
| myChildren | 我的孩子列表(核心) | MSW 返回固定 2 个孩子 |
|
|
||||||
| childSummary | 孩子概况 | MSW 返回固定仪表盘 |
|
> **注**(ISSUE-001 / ISSUE-008):
|
||||||
| childGrades | 孩子成绩 | MSW 返回固定 5 个成绩 |
|
> - 01/02 文档描述为 REST 消费,与 ARB-001 冲突,待 coord 仲裁
|
||||||
| childAttendance | 孩子考勤 | MSW 返回固定 10 条考勤 |
|
> - 仲裁前本表按 GraphQL 方向编写(与 parent-bff contract + matrix.md 一致)
|
||||||
| childHomework | 孩子作业 | MSW 返回固定 3 个作业 |
|
> - 路径前缀统一为 `/api/v1/parent/graphql`(与 matrix.md §5 一致,旧版缺 `v1`)
|
||||||
| childWeakness | 孩子薄弱点 | MSW 返回固定 3 个 weak_points |
|
|
||||||
| childTrend | 孩子学习趋势 | MSW 返回固定趋势数据 |
|
| Query/Mutation | 类型 | 用途 | 对应 parent-bff 聚合 | mock 策略 |
|
||||||
| myNotifications / markAsRead | 通知中心 | MSW 返回固定通知 |
|
| ------------------------------- | -------- | -------------------- | ------------------------------------------- | ---------------------------------- |
|
||||||
|
| currentUser | Query | 当前家长信息 | iam.GetUserInfo + GetEffectivePermissions + GetViewports | MSW 返回固定家长(parent-001 王家长) |
|
||||||
|
| myChildren | Query | 我的子女列表(核心) | iam.GetChildrenByParent(I3/ISSUE-047 裁决) | MSW 返回固定 2 个子女(student-001 + student-002) |
|
||||||
|
| childSummary(childId) | Query | 子女仪表盘概览 | data-ana.GetParentDashboard | MSW 返回固定仪表盘 |
|
||||||
|
| childGrades(childId) | Query | 子女成绩 | core-edu.ListGradesByStudent | MSW 返回固定 5 个成绩 |
|
||||||
|
| childAttendance(childId) | Query | 子女考勤 | core-edu.ListAttendanceByStudent | MSW 返回固定 10 条考勤 |
|
||||||
|
| childHomework(childId) | Query | 子女作业 | core-edu.ListHomeworkByClass | MSW 返回固定 3 个作业 |
|
||||||
|
| childWeakness(childId) | Query | 子女薄弱点 | data-ana.GetStudentWeakness | MSW 返回固定 3 个 weak_points |
|
||||||
|
| childTrend(childId) | Query | 子女学习趋势 | data-ana.GetLearningTrend | MSW 返回固定趋势数据 |
|
||||||
|
| myNotifications | Query | 通知列表(P5) | msg.ListNotifications | MSW 返回固定 10 条通知 |
|
||||||
|
| markAsRead(notificationId) | Mutation | 标记已读(P5) | msg.MarkAsRead | MSW 返回 success=true |
|
||||||
|
| updateNotificationPreferences | Mutation | 更新通知偏好 | msg(待 ai05 确认) | MSW 返回 success=true |
|
||||||
|
| switchChild(childId) | Mutation | 切换当前子女 | 待 ISSUE-009 仲裁确认 | 见 ISSUE-009 |
|
||||||
|
|
||||||
|
> **switchChild 说明**(ISSUE-009):
|
||||||
|
> - parent-bff_contract.md §1.3 未列 switchChild Mutation
|
||||||
|
> - 待 coord 仲裁:switchChild 是 GraphQL Mutation(后端记录当前子女)还是纯前端状态(localStorage + Zustand)
|
||||||
|
> - 若纯前端:本表移除 switchChild,切换逻辑在 `useChildSwitcher` 内直接写 Zustand + localStorage
|
||||||
|
|
||||||
|
### 2.5 消费的错误码前缀(前端 i18n 路由)
|
||||||
|
|
||||||
|
parent-portal 不产生错误码,仅消费。前端 API 请求层根据 `error.code` 前缀路由到对应 i18n key:
|
||||||
|
|
||||||
|
| 前缀 | 来源服务 | i18n key 模式 |
|
||||||
|
| ------------- | ----------- | ------------------------- |
|
||||||
|
| `IAM_` | iam | `error.iam.{{code}}` |
|
||||||
|
| `CORE_EDU_` | core-edu | `error.core_edu.{{code}}` |
|
||||||
|
| `BFF_PARENT_` | parent-bff | `error.bff_parent.{{code}}` |
|
||||||
|
| `GW_` | api-gateway | `error.gw.{{code}}` |
|
||||||
|
| `NETWORK_` | 前端网络层 | `error.network.{{code}}` |
|
||||||
|
|
||||||
|
> **注**:与 [matrix.md](./matrix.md) §6 错误码前缀矩阵对齐。`BFF_PARENT_` 前缀由 parent-bff 定义(见 [parent-bff_contract.md](./parent-bff_contract.md) §1.5)。
|
||||||
|
|
||||||
|
### 2.6 WebSocket 推送(P5)
|
||||||
|
|
||||||
|
| 被调用方 | 协议 | 路径 | 用途 | mock 策略 |
|
||||||
|
| -------------------- | ----------- | ---- | ---------- | -------------------------------------- |
|
||||||
|
| push-gateway (ai02) | WebSocket | /ws | 实时推送 | mock-socket 模拟 WS 推送(每 30s 1 条) |
|
||||||
|
| push-gateway (ai02) | SSE(降级) | /sse | SSE 降级 | — |
|
||||||
|
|
||||||
|
> WebSocket 连接由 Shell 建立(统一连接管理),parent-portal 通过 Zustand ui-store 订阅事件流。
|
||||||
|
|
||||||
|
### 2.7 消费的 MF Shell 暴露(ARB-002)
|
||||||
|
|
||||||
|
| 暴露模块 | 来源 | 用途 |
|
||||||
|
| -------- | ---- | ---- |
|
||||||
|
| AppShell | teacher-portal Shell | 左栏导航 + 主内容区布局 |
|
||||||
|
| GraphQLProvider | teacher-portal Shell | urql client 单例(ARB-002) |
|
||||||
|
| useAuth | packages/hooks | 会话状态 |
|
||||||
|
| usePermission | packages/hooks | 权限查询 |
|
||||||
|
| useGraphQLClient | packages/hooks | urql client 获取 |
|
||||||
|
| ErrorBoundary | packages/ui-components | React 渲染异常兜底 |
|
||||||
|
| Loading / Empty | packages/ui-components | 骨架屏 / 空态 |
|
||||||
|
| RequirePermission | packages/ui-components | L3 组件级视口控制 |
|
||||||
|
|
||||||
|
> MF shared(singleton):react / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooks(ARB-002 裁决,见 [coord.md](../coord.md) §2)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -84,19 +151,37 @@
|
|||||||
|
|
||||||
### 3.1 我依赖的上游就绪标志
|
### 3.1 我依赖的上游就绪标志
|
||||||
|
|
||||||
- [ ] api-gateway HTTP :8080 启用(ai01)—— 前端请求入口
|
| 上游 | 就绪信号 | 提供方 | 状态 |
|
||||||
- [ ] parent-bff GraphQL :3010 启用(ai05)—— 数据来源
|
| ---- | -------- | ------ | ---- |
|
||||||
- [ ] push-gateway WebSocket :8081/ws 启用(ai02)—— 实时通知
|
| api-gateway | HTTP :8080 启用 + JWT 验签 + `/api/v1/parent/*` 代理 | ai01 | ⏳ |
|
||||||
|
| parent-bff GraphQL | `POST /graphql` :3010 + currentUser/myChildren/childSummary/childGrades Query | ai05 | ⏳ P4 |
|
||||||
|
| iam GetChildrenByParent | gRPC 50052 + `iam_student_guardians` 表(I3/ISSUE-047 裁决) | ai06 | ⏳ P3 补全 |
|
||||||
|
| teacher-portal Shell | MF exposes(AppShell + GraphQLProvider + hooks + UI 组件)+ shared singleton | ai13 | ⏳ P2 |
|
||||||
|
| push-gateway | WebSocket :8081/ws | ai02 | ⏳ P5 |
|
||||||
|
| msg | gRPC 50056 + NotificationService | ai10 | ⏳ P5 |
|
||||||
|
| shared-ts / contracts | ApiClient / Logger / Permissions 常量 | coord | ⏳ |
|
||||||
|
| ui-tokens / ui-components / hooks | 三层令牌 + shadcn + usePermission/useAuth | ai07/ai13 | ⏳ P2 收尾 |
|
||||||
|
|
||||||
|
> **P0 阻塞**(ISSUE-010):iam `GetChildrenByParent` 缺失,多子女场景无法落地。补全前用 mock(固定 2 个子女 student-001 + student-002)开发。
|
||||||
|
|
||||||
### 3.2 我的就绪标志(供下游消费)
|
### 3.2 我的就绪标志(供下游消费)
|
||||||
|
|
||||||
- [ ] parent-portal dev server :4002 启用
|
> 与 [matrix.md](./matrix.md) §8 就绪信号跟踪表对齐
|
||||||
- [ ] MF Remote 可被 AppShell 加载(暴露 ParentApp 模块)
|
|
||||||
- [ ] 独立壳渲染(首页 + 导航 + 路由守卫)
|
| 信号 | 说明 | 阶段 |
|
||||||
- [ ] 登录流程可用(POST /api/auth/login 获取 JWT 存入 cookie)
|
| ---- | ---- | ---- |
|
||||||
- [ ] GraphQL 查询可执行(currentUser / myChildren / childSummary 返回数据)
|
| parent-portal dev server :4002 启用 | MF Remote 可被 Shell 加载 | P4-1 |
|
||||||
- [ ] 数据范围校验生效(前端路由守卫校验 child:id 是否在 myChildren 返回列表中)
|
| MF Remote remoteEntry.js 可加载 | Shell 端 `remotes.parent = parent_app@http://localhost:4002/...` 可解析 | P4-1 |
|
||||||
- [ ] WebSocket 通知可接收
|
| 独立壳渲染 | 首页 + 导航 + 路由守卫 | P4-1 |
|
||||||
|
| 登录流程可用 | `POST /api/v1/iam/login` 获取 JWT 存入 httpOnly cookie | P4-2 |
|
||||||
|
| GraphQL 查询可执行 | currentUser / myChildren / childSummary 返回数据(mock 或真实) | P4-2 |
|
||||||
|
| 多子女切换可用 | ChildSwitcher + invalidate 流程通过 | P4-3 |
|
||||||
|
| 数据范围校验生效 | 前端路由守卫校验 childId 是否在 myChildren 返回列表中 | P4-3 |
|
||||||
|
| Dashboard 可访问 | 家长登录 → 看到 Dashboard(含子女卡片) | P4-4 |
|
||||||
|
| 健康检查通过 | `GET /api/health` + `GET /api/ready` 200 | P4-1 |
|
||||||
|
| 测试覆盖率达标 | 单元 ≥ 85% + 集成 ≥ 75% | P4-9 |
|
||||||
|
| Docker 镜像可构建 | `docker build` 成功 | P4-10 |
|
||||||
|
| WebSocket 通知可接收 | push-gateway WS 事件正确处理 | P5-1 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -111,14 +196,38 @@ parent-portal 是前端,无下游消费方。但对开发体验提供:
|
|||||||
|
|
||||||
### 4.2 我消费的 mock
|
### 4.2 我消费的 mock
|
||||||
|
|
||||||
在真实上游就绪前,parent-portal 使用以下 mock:
|
在真实上游就绪前,parent-portal 使用以下 mock(由 `NEXT_PUBLIC_API_MOCKING=enabled` 控制):
|
||||||
|
|
||||||
- **HTTP/GraphQL mock**:使用 MSW 拦截所有请求
|
- **GraphQL mock**:MSW 拦截 `POST /api/v1/parent/graphql`
|
||||||
- POST /api/auth/login → 返回固定 JWT + UserInfo(parent 角色)
|
- 按 operationName 返回对应 mock 响应(与 parent-bff mock 数据一致)
|
||||||
- POST /api/parent/graphql → 根据 operationName 返回对应 mock 响应(与 parent-bff mock 数据一致)
|
- currentUser → 固定家长(id="parent-001", name="王家长", roles=["parent"])
|
||||||
- myChildren mock 必须返回固定 2 个孩子(id="student-001" + "student-002"),与其他 child* 查询的 student_id 一致
|
- myChildren → 固定 2 个子女(id="student-001" 李同学 + id="student-002" 李妹妹)
|
||||||
|
- childSummary → 固定仪表盘(child_avg_score=85.0, child_class_rank=5)
|
||||||
|
- childGrades → 固定 5 个成绩
|
||||||
|
- childAttendance → 固定 10 条考勤
|
||||||
|
- childHomework → 固定 3 个作业
|
||||||
|
- myNotifications → 固定 10 条通知
|
||||||
- 所有 mock 响应定义在 `apps/parent-portal/src/mocks/fixtures/*.json`
|
- 所有 mock 响应定义在 `apps/parent-portal/src/mocks/fixtures/*.json`
|
||||||
- **WebSocket mock**:使用 mock-socket 库
|
- **HTTP mock**:MSW 拦截 `POST /api/v1/iam/login` → 返回固定 JWT + UserInfo(parent 角色)
|
||||||
- 连接后每 30 秒推送 1 条 mock 通知
|
- **WebSocket mock**:mock-socket 库,连接后每 30 秒推送 1 条 mock 通知
|
||||||
- **JWT mock**:使用固定 mock JWT,存入 httpOnly cookie
|
- **JWT mock**:固定 mock JWT,存入 httpOnly cookie
|
||||||
- **环境切换**:通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制,上游就绪后设为 `disabled`
|
- **环境切换**:`NEXT_PUBLIC_API_MOCKING=enabled`(开发)/ `disabled`(上游就绪后)
|
||||||
|
- **数据一致性**:myChildren mock 必须返回固定 2 个孩子(student-001 + student-002),与所有 child* 查询的 student_id 一致(否则前端数据范围校验失败)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 待协调事项(指向 objections)
|
||||||
|
|
||||||
|
以下事项已提请 coord 仲裁,仲裁结果可能影响本契约:
|
||||||
|
|
||||||
|
| ISSUE | 影响章节 | 当前处理 |
|
||||||
|
| ----- | -------- | -------- |
|
||||||
|
| ISSUE-001(REST vs GraphQL) | §2.4 | 按 GraphQL 编写(依 ARB-001),待 coord 确认 |
|
||||||
|
| ISSUE-004(登录端点) | §2.3 | 暂用 `POST /api/v1/iam/login`,待 coord 确认 |
|
||||||
|
| ISSUE-006(HTTP 端点分类) | §1.2 | 已修正为"无对外 HTTP API" |
|
||||||
|
| ISSUE-007(MF 配置文件名) | §1.6 | 已修正为 `next.config.js` |
|
||||||
|
| ISSUE-008(GraphQL 路径前缀) | §2.4 | 已修正为 `/api/v1/parent/graphql` |
|
||||||
|
| ISSUE-009(switchChild Mutation) | §2.4 | 列为待仲裁,标注两种方案 |
|
||||||
|
| ISSUE-010(iam GetChildrenByParent 缺失) | §3.1 | P0 阻塞,用 mock 开发 |
|
||||||
|
|
||||||
|
详见 [objections/parent-portal_issue.md](../objections/parent-portal_issue.md)。
|
||||||
|
|||||||
@@ -6,6 +6,33 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## §0 已有仲裁核查(ai15 复核 ARB-001 / ARB-002 落地情况)
|
||||||
|
|
||||||
|
> ai15 接管 parent-portal 后,核查 coord 已发布的两项仲裁(ARB-001 teacher-bff GraphQL schema、ARB-002 MF Shell 暴露清单)在 parent-portal 文档中的落地情况。
|
||||||
|
|
||||||
|
### 0.1 ARB-001(teacher-bff GraphQL schema 第一版)核查
|
||||||
|
|
||||||
|
| 核查项 | ARB-001 结论 | parent-portal 落地情况 | 状态 |
|
||||||
|
| ------ | ------------ | ---------------------- | ---- |
|
||||||
|
| BFF 用 GraphQL(非 REST) | ✅ 已裁决 GraphQL | 01-understanding §3.1 + 02-architecture-design §4.1 全部描述为 REST 消费 | ❌ 未落地 |
|
||||||
|
| ActionState 信封 | ✅ 已裁决 | 01 §3.2 已对齐 | ✅ |
|
||||||
|
| 错误码前缀路由 | ✅ 已裁决 | 01 §3.2 + 02 §6.2 已对齐 | ✅ |
|
||||||
|
|
||||||
|
**结论**:ARB-001 的核心裁决(BFF = GraphQL)在 parent-portal 的 01/02 文档中**未落地**,01 §3.1 与 02 §4.1 仍按 REST 编写,与 [parent-bff_contract.md](../contracts/parent-bff_contract.md) §1.3(GraphQL 端点 :3010)和 [matrix.md](../matrix.md) §3(parent-bff GraphQL)直接冲突。提请 ISSUE-001。
|
||||||
|
|
||||||
|
### 0.2 ARB-002(MF Shell 暴露清单)核查
|
||||||
|
|
||||||
|
| 核查项 | ARB-002 结论 | parent-portal 落地情况 | 状态 |
|
||||||
|
| ------ | ------------ | ---------------------- | ---- |
|
||||||
|
| Shell 暴露 GraphQLProvider | ✅ 已裁决 | 01 §4 技术栈未列 urql/GraphQL client;02 §4.1 用 `useApi()`(REST ApiClient)而非 `useGraphQLClient()` | ❌ 未落地 |
|
||||||
|
| MF shared 含 urql/graphql/@edu/* | ✅ 已裁决 | 02 §1.2 `shared` 仅列 react/react-dom/@tanstack/react-query/zustand/nuqs,缺 urql/graphql/@edu/ui-tokens/@edu/ui-components/@edu/hooks | ❌ 未落地 |
|
||||||
|
| Shell 暴露 AppShell | ✅ 已裁决 | 01 §9.1 + 02 §7.1 已对齐 | ✅ |
|
||||||
|
| feature flag NEXT_PUBLIC_MF_ENABLED | ✅ 已裁决 | 01/02 均未提及 | ❌ 未落地 |
|
||||||
|
|
||||||
|
**结论**:ARB-002 关于 GraphQL client 与 MF shared 的裁决在 parent-portal 文档中**部分未落地**。提请 ISSUE-002。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 问题列表
|
## 问题列表
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -21,4 +48,164 @@
|
|||||||
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X)
|
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
(暂无问题)
|
### ISSUE-001-ai15:01/02 文档 REST 消费 parent-bff 与 ARB-001 GraphQL 裁决冲突
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:契约不明确(文档与已裁决架构冲突)
|
||||||
|
- **描述**:
|
||||||
|
- 01-understanding.md §3.1 列出 parent-portal 经 REST 消费 parent-bff:`GET /parent/viewports`、`GET /parent/children`、`POST /parent/children/:childId/select`、`GET /parent/notifications`、`PUT /parent/notification-preferences` 等
|
||||||
|
- 02-architecture-design.md §4.1 `useParentApi` 实现全部基于 `api.get()`/`api.post()` REST 调用
|
||||||
|
- 但 ARB-001(coord.md §1)已裁决 BFF 用 GraphQL;[parent-bff_contract.md](../contracts/parent-bff_contract.md) §1.3 明确 parent-bff 提供 `POST /graphql`(:3010);[matrix.md](../matrix.md) §3 确认 parent-bff = GraphQL
|
||||||
|
- parent-portal 自己的 [contract.md](../contracts/parent-portal_contract.md) §2.3-2.4 也写明消费 GraphQL(`POST /api/parent/graphql`,Query 域:currentUser/myChildren/childSummary/childGrades 等)
|
||||||
|
- **文档内部自相矛盾**:01/02 用 REST,contract.md 用 GraphQL
|
||||||
|
- **建议方案**:
|
||||||
|
1. coord 确认 parent-portal 消费 parent-bff **统一用 GraphQL**(与 ARB-001、parent-bff contract、matrix.md 一致)
|
||||||
|
2. ai15 据此修订 01 §3.1(改为 GraphQL Query/Mutation 域)、§3.1.1(X-Fields 字段裁剪改为 GraphQL query 字段选择)、02 §4.1(`useParentApi` 改为 GraphQL hooks)、§4.2(TanStack Query 约定配合 GraphQL operations)、§11.3 未决设计决策 #2(移除,已裁决)
|
||||||
|
3. 若 coord 另有裁决(如 parent-portal 特殊走 REST),以 coord 裁决为准
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-002-ai15:MF shared 配置缺 urql/graphql/@edu/* 与 ARB-002 冲突
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:契约不明确(文档与已裁决 MF 配置冲突)
|
||||||
|
- **描述**:
|
||||||
|
- 02-architecture-design.md §1.2 MF `shared` 配置仅列:`react`、`react-dom`、`@tanstack/react-query`、`zustand`、`nuqs`
|
||||||
|
- ARB-002(coord.md §2)裁决的 `shared` 应包含:`react`、`react-dom`、`urql`、`graphql`、`@edu/ui-tokens`、`@edu/ui-components`、`@edu/hooks`
|
||||||
|
- 缺失 `urql`/`graphql` 会导致 Remote 与 Shell 各加载一份 GraphQL client 实例,破坏单例,引发缓存不一致与重复请求
|
||||||
|
- 缺失 `@edu/*` 会导致设计令牌/UI 组件/Hooks 各加载一份
|
||||||
|
- 同时 01 §4 技术栈表未列 GraphQL client(urql),与 ARB-002 Shell 暴露 GraphQLProvider 矛盾
|
||||||
|
- **建议方案**:
|
||||||
|
1. coord 确认 parent-portal MF `shared` 必须包含 ARB-002 全部 7 项(react/react-dom/urql/graphql/@edu/ui-tokens/@edu/ui-components/@edu/hooks)
|
||||||
|
2. ai15 修订 02 §1.2 `shared` 配置 + 01 §4 技术栈表(新增 urql + GraphQL client 行)
|
||||||
|
3. 02 §4.1 `useParentApi` 改为从 Shell 暴露的 `useGraphQLClient()` 获取 urql client,不再用 REST ApiClient
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-003-ai15:switch-child 端点在 01/02 文档间不一致
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:契约不明确(文档内部不一致)
|
||||||
|
- **描述**:
|
||||||
|
- 01-understanding.md §3.1 列 `POST /parent/children/:childId/select`
|
||||||
|
- 02-architecture-design.md §2.2 + §4.1 用 `POST /api/v1/parent/switch-child`(body 携带 childId)
|
||||||
|
- 两处路径与语义均不一致(URL param vs body param)
|
||||||
|
- parent-bff contract.md 未列 switch-child(其 §1.3 仅列 Query/Mutation 域,未细到 switch-child)
|
||||||
|
- **建议方案**:
|
||||||
|
1. 若走 GraphQL(依 ISSUE-001 裁决):switch-child 应为 `mutation switchChild(childId: ID!): SwitchChildPayload!`,不存在 REST 路径
|
||||||
|
2. 若走 REST:统一为 `POST /api/v1/parent/switch-child`(body 携带 childId,与 02 一致),修订 01 §3.1
|
||||||
|
3. 请 coord 一并明确 parent-bff GraphQL schema 是否包含 `switchChild` Mutation(当前 parent-bff contract.md 未列)
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-004-ai15:登录端点在 01 / contract.md / matrix.md 间三方不一致
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:契约不明确(跨文档不一致)
|
||||||
|
- **描述**:
|
||||||
|
- 01-understanding.md §3.1 列 `POST /iam/login`
|
||||||
|
- parent-portal_contract.md §2.3 列 `POST /api/auth/login`
|
||||||
|
- matrix.md §5 规范 iam 经 api-gateway 代理路径为 `/api/v1/iam/*`
|
||||||
|
- 三处不一致,且 contract.md 的 `/api/auth/login` 路径在 matrix.md 中不存在
|
||||||
|
- **建议方案**:
|
||||||
|
1. 统一为 `POST /api/v1/iam/login`(与 matrix.md §5 + 01 §3.1 的 `/api/v1/iam/*` 前缀一致)
|
||||||
|
2. ai15 修订 contract.md §2.3 路径
|
||||||
|
3. 注意:登录是 parent-portal 唯一可能走 REST(非 GraphQL)的端点,因登录前无 JWT,GraphQL endpoint 需鉴权。请 coord 确认登录是否走 REST `/api/v1/iam/login`,其余走 GraphQL
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-005-ai15:02 §11.3 未决设计决策 #2 "GraphQL vs REST" 已由 ARB-001 裁决,应移除
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:其他(文档过时)
|
||||||
|
- **描述**:
|
||||||
|
- 02-architecture-design.md §11.3 第 2 项将 "GraphQL vs REST" 列为未决设计决策,建议 "P4 用 REST,后续若 BFF 切 GraphQL 再引入 urql"
|
||||||
|
- 但 ARB-001(coord.md §1)已于 2026-07-09 裁决 BFF 用 GraphQL,且 parent-bff contract.md 确认 GraphQL
|
||||||
|
- 此项已过时,会误导后续开发
|
||||||
|
- **建议方案**:
|
||||||
|
1. coord 确认 ARB-001 适用于 parent-portal(即 parent-portal P4 起必须用 GraphQL 消费 parent-bff)
|
||||||
|
2. ai15 移除 02 §11.3 第 2 项,改为 "已裁决:见 ARB-001,parent-portal 用 GraphQL 消费 parent-bff"
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-006-ai15:contract.md §1.2 将前端页面路由误标为 HTTP 端点
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:其他(文档分类错误)
|
||||||
|
- **描述**:
|
||||||
|
- parent-portal_contract.md §1.2 "HTTP 端点" 列出:`GET /`、`GET /children`、`GET /child/:id/summary`、`GET /child/:id/grades` 等
|
||||||
|
- 但 parent-portal 是 Next.js 前端应用,这些是**前端页面路由**(SSR/CSR 路由),不是对外 HTTP API 端点
|
||||||
|
- 将页面路由放在 "HTTP 端点" 表中会误导下游消费方以为这些是 REST API
|
||||||
|
- 且这些路由与 01 §8 L2 路由表(`/parent/dashboard`、`/parent/children` 等)路径还不一致(contract 用 `/children`,01 用 `/parent/children`)
|
||||||
|
- **建议方案**:
|
||||||
|
1. coord 确认 parent-portal 作为前端 Remote,不对外提供 HTTP API 端点(§1.2 应为"无")
|
||||||
|
2. ai15 将 contract.md §1.2 改为 "无(parent-portal 是前端 Remote,不对外提供 HTTP API)",页面路由信息保留在 01 §8 L2 路由表中,不进 contract.md
|
||||||
|
3. 若需保留 MF 暴露信息,归入 §1.6 微前端架构(已有)
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-007-ai15:contract.md §1.6 module-federation.config.ts 与 02 next.config.js 不一致
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:其他(文档内部不一致)
|
||||||
|
- **描述**:
|
||||||
|
- parent-portal_contract.md §1.6 列 MF 配置文件为 `apps/parent-portal/module-federation.config.ts`
|
||||||
|
- 02-architecture-design.md §1.2 MF 配置写在 `next.config.js` 中(用 `NextFederationPlugin`)
|
||||||
|
- 两处文件名与位置不一致
|
||||||
|
- **建议方案**:
|
||||||
|
1. 统一为 `apps/parent-portal/next.config.js`(与 02 + teacher-portal Shell 一致,Next.js 项目 MF 配置应在 next.config.js)
|
||||||
|
2. ai15 修订 contract.md §1.6
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-008-ai15:contract.md §2.3 GraphQL 路径前缀与 matrix.md 不一致
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:契约不明确(跨文档不一致)
|
||||||
|
- **描述**:
|
||||||
|
- parent-portal_contract.md §2.3 列 `POST /api/parent/graphql`
|
||||||
|
- matrix.md §5 规范 api-gateway 代理 parent-bff 路径为 `/api/v1/parent/*`
|
||||||
|
- 缺 `v1` 版本号
|
||||||
|
- **建议方案**:
|
||||||
|
1. 统一为 `POST /api/v1/parent/graphql`(与 matrix.md §5 一致)
|
||||||
|
2. ai15 修订 contract.md §2.3 + §2.4
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-009-ai15:parent-bff GraphQL schema 是否包含 switchChild Mutation 未明确
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:前置依赖缺失(上游契约不全)
|
||||||
|
- **描述**:
|
||||||
|
- parent-bff_contract.md §1.3 列出的 Query/Mutation 域未包含 "switchChild"(切换当前选中子女)
|
||||||
|
- 01-understanding.md §2.2 + 02 §2.2 描述 parent-portal 需调用 `POST /parent/switch-child` 切换子女
|
||||||
|
- 若走 GraphQL(依 ISSUE-001),parent-bff 需提供 `mutation switchChild(childId: ID!): SwitchChildPayload!`
|
||||||
|
- 但 parent-bff contract 未列此 Mutation,且 iam.GetChildrenByParent 已返回子女列表,切换子女是否需后端记录(还是纯前端 localStorage)需明确
|
||||||
|
- **建议方案**:
|
||||||
|
1. 请 coord 协调 ai05(parent-bff)确认:switchChild 是 GraphQL Mutation 还是纯前端状态(localStorage + Zustand)
|
||||||
|
2. 若纯前端:01/02 移除 `POST /parent/switch-child` 调用,改为 `useChildSwitcher` 直接写 Zustand + localStorage
|
||||||
|
3. 若需后端记录:请 ai05 在 parent-bff contract.md §1.3 补充 `switchChild` Mutation
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
### ISSUE-010-ai15:iam GetChildrenByParent 接口缺失(P0 阻塞,跨模块)
|
||||||
|
|
||||||
|
- **提请方**:ai15
|
||||||
|
- **日期**:2026-07-10
|
||||||
|
- **类型**:前置依赖缺失(跨模块,承自 parent-bff §7.1)
|
||||||
|
- **描述**:
|
||||||
|
- 01-understanding.md §3.1 注明:iam 缺失 "家长-学生关联查询" 接口(`GetChildrenByParent` proto + `GET /iam/children` REST + `iam_student_guardians` 表三缺失)
|
||||||
|
- parent-bff_contract.md §2.1 也标注 "核心依赖 I3/ISSUE-047 裁决"
|
||||||
|
- parent-bff_contract.md §3.1 标注 "iam gRPC 50052 启用(ai06)—— 核心依赖 GetChildrenByParent(I3/ISSUE-047 裁决)"
|
||||||
|
- 此为 parent-portal 多子女场景的 P0 阻塞项,ai06(iam)需在 P3 收尾前补全
|
||||||
|
- ai15 在此提请,请 coord 跟踪 ai06 进度并确认补全时间点
|
||||||
|
- **建议方案**:
|
||||||
|
1. coord 确认 ai06 补全 `GetChildrenByParent` 的时间点(应在 P4 启动前)
|
||||||
|
2. 在补全前,parent-portal 用 mock(固定 2 个子女)开发,mock 数据与 parent-bff mock 一致(student-001 + student-002)
|
||||||
|
- **状态**:待 coord 仲裁
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 已裁决问题
|
||||||
|
|
||||||
|
(暂无已裁决问题)
|
||||||
|
|||||||
@@ -2,44 +2,331 @@
|
|||||||
|
|
||||||
> 负责人:ai15
|
> 负责人:ai15
|
||||||
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/parent-portal_contract.md](../contracts/parent-portal_contract.md)
|
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/parent-portal_contract.md](../contracts/parent-portal_contract.md)
|
||||||
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
|
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock,最后统一集成测试)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## §1 总览
|
## §1 总览
|
||||||
|
|
||||||
parent-portal 是家长端微前端,通过 MF Remote 接入主应用,覆盖 Dashboard、多子女切换等场景。全阶段目标:P2 MF Remote 骨架 → P3 Dashboard+多子女切换 → P4-P6 持续优化。
|
parent-portal 是家长端微前端(MF Remote),挂载到 teacher-portal Shell,覆盖家长仪表盘、多子女切换、子女学情查看、通知偏好等场景。
|
||||||
|
|
||||||
|
- **MF 角色**:Remote(Shell = teacher-portal :4000)
|
||||||
|
- **端口**:4002(dev/prod 一致,[port-allocation.md](../../../../infra/port-allocation.md) §4)
|
||||||
|
- **阶段归属**:P4 启动(依赖 P4 的 parent-bff + data-ana 就绪)
|
||||||
|
- **DataScope**:CHILDREN(仅查看自己绑定子女的数据)
|
||||||
|
- **全阶段目标**:P4 MF Remote 骨架 + 核心页面 → P5 推送接入 + 通知中心 → P6 硬化(A11y/性能/安全/PWA/多语言)
|
||||||
|
|
||||||
|
> **前置阻塞**(见 [objections/parent-portal_issue.md](../objections/parent-portal_issue.md) ISSUE-010):
|
||||||
|
> - iam `GetChildrenByParent` 接口缺失(P0 阻塞,ai06 补全),补全前用 mock(固定 2 个子女 student-001 + student-002)
|
||||||
|
> - ISSUE-001 待 coord 仲裁(REST vs GraphQL),仲裁前按 GraphQL 预排期,mock 用 MSW 拦截 GraphQL
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## §2 全阶段甘特图(P2-P6,各 AI 自行细化)
|
## §2 全阶段甘特图(P4-P6)
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
gantt
|
gantt
|
||||||
title ai15 parent-portal 全阶段排期
|
title ai15 parent-portal 全阶段排期(P4-P6)
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
axisFormat %m-%d
|
axisFormat %m-%d
|
||||||
|
|
||||||
section P2-P6
|
section P4 骨架与核心页面(11d)
|
||||||
[阶段任务] :a15a, 2026-07-10, Xd
|
4.1 MF Remote 骨架+next.config.js+健康检查 :crit, a15a, 2026-07-29, 2d
|
||||||
|
4.2 GraphQL client接入+MSW mock层 :crit, a15b, after a15a, 1d
|
||||||
|
4.3 ChildSwitcher+useChildSwitcher+Zustand slice :crit, a15c, after a15b, 2d
|
||||||
|
4.4 Dashboard页面+ParentDashboard组件 :a15d, after a15c, 2d
|
||||||
|
4.5 子女成绩页面+ChildGradeChart :a15e, after a15c, 1d
|
||||||
|
4.6 子女作业页面 :a15f, after a15e, 1d
|
||||||
|
4.7 通知偏好页面+PreferenceForm+Zod :a15g, after a15d, 1d
|
||||||
|
4.8 跨标签同步(BroadcastChannel) :a15h, after a15c, 1d
|
||||||
|
|
||||||
|
section P4 质量保障(并行)
|
||||||
|
4.9 Vitest单测+MSW集成测试 :a15i, after a15g, 2d
|
||||||
|
4.10 Dockerfile多阶段构建 :a15j, after a15a, 1d
|
||||||
|
|
||||||
|
section P5 推送与通知中心(5d)
|
||||||
|
5.1 WebSocket接入+事件处理 :crit, a15k, after a15i, 2d
|
||||||
|
5.2 通知中心页面+NotificationFeed :a15l, after a15k, 2d
|
||||||
|
5.3 推送降级(HTTP轮询) :a15m, after a15l, 1d
|
||||||
|
|
||||||
|
section P6 硬化(8d)
|
||||||
|
6.1 Web Vitals+OTel browser SDK :a15n, after a15m, 2d
|
||||||
|
6.2 A11y WCAG 2.2 AA审计+修复 :a15o, after a15n, 2d
|
||||||
|
6.3 性能优化+bundle分析 :a15p, after a15o, 1d
|
||||||
|
6.4 多语言扩展(en-US) :a15q, after a15p, 1d
|
||||||
|
6.5 PWA(Service Worker+manifest) :a15r, after a15q, 1d
|
||||||
|
6.6 安全硬化(CSP+敏感数据脱敏) :a15s, after a15r, 1d
|
||||||
```
|
```
|
||||||
|
|
||||||
> **注意**:以上为 coord 初始规划,ai15 接管后必须自行细化为完整 P2-P6 排期。
|
> **总工期**:P4 11d + P5 5d + P6 8d = 24d(约 5 周)
|
||||||
|
> **关键路径**(红色 crit):MF 骨架 → GraphQL client → ChildSwitcher → 质量保障 → WebSocket → 通知中心
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## §3 详细任务
|
## §3 详细任务
|
||||||
|
|
||||||
### 全阶段任务
|
### P4 阶段任务
|
||||||
|
|
||||||
|
#### P4-1:MF Remote 骨架 + next.config.js + 健康检查
|
||||||
|
|
||||||
- **负责人**:ai15
|
- **负责人**:ai15
|
||||||
- **交付物**:⚠️ 由 ai15 自行补充
|
- **依赖**:teacher-portal Shell MF 配置就绪(ARB-002,ai13 P2 交付);ISSUE-002 仲裁(MF shared 清单)
|
||||||
- **依赖**:见 [contracts/parent-portal_contract.md](../contracts/parent-portal_contract.md)
|
- **交付物**:
|
||||||
- **验收标准**:⚠️ 由 ai15 自行补充
|
- `apps/parent-portal/next.config.js`(NextFederationPlugin,Remote 角色,`name: parent_app`,`exposes: ./pages + ./ChildSwitcher`,`shared` 含 ARB-002 全部 7 项)
|
||||||
|
- `apps/parent-portal/src/app/layout.tsx`(RootLayout,复用 Shell 暴露的字体/令牌/i18n Provider)
|
||||||
|
- `apps/parent-portal/src/app/api/health/route.ts`(`GET /api/health` → `{ status: 'ok', ts }`)
|
||||||
|
- `apps/parent-portal/src/app/api/ready/route.ts`(`GET /api/ready` → 检查 API_GATEWAY_URL 可达)
|
||||||
|
- `apps/parent-portal/tsconfig.json`(继承 tsconfig.base.json,strict)
|
||||||
|
- `apps/parent-portal/package.json`(依赖对齐 Shell:react 18.3 + next 14 + urql + @tanstack/react-query v5 + zustand + nuqs)
|
||||||
|
- **验收标准**:
|
||||||
|
1. `pnpm --filter parent-portal dev` 启动 :4002
|
||||||
|
2. `GET /api/health` 返回 200
|
||||||
|
3. teacher-portal Shell 能加载 parent-portal remoteEntry.js(MF 拓扑验证)
|
||||||
|
4. feature flag `NEXT_PUBLIC_MF_ENABLED` 可控制 MF 开关
|
||||||
|
|
||||||
|
#### P4-2:GraphQL client 接入 + MSW mock 层
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-1;ISSUE-001 仲裁(确认 GraphQL);teacher-portal Shell 暴露 GraphQLProvider(ARB-002)
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/lib/graphql-client.ts`(从 Shell 暴露的 `useGraphQLClient()` 获取 urql client 单例)
|
||||||
|
- `apps/parent-portal/src/graphql/queries/`(currentUser / myChildren / childSummary / childGrades / childHomework / childTrend / childWeakness 查询文档)
|
||||||
|
- `apps/parent-portal/src/graphql/mutations/`(markAsRead / updateNotificationPreferences mutation 文档)
|
||||||
|
- `apps/parent-portal/src/mocks/handlers.ts`(MSW 拦截 `POST /api/v1/parent/graphql`,按 operationName 返回 mock)
|
||||||
|
- `apps/parent-portal/src/mocks/fixtures/*.json`(固定 2 个子女 student-001 李同学 + student-002 李妹妹,与 parent-bff mock 一致)
|
||||||
|
- `apps/parent-portal/src/mocks/browser.ts`(MSW worker 初始化,`NEXT_PUBLIC_API_MOCKING=enabled` 控制)
|
||||||
|
- **验收标准**:
|
||||||
|
1. MSW enabled 时,所有 GraphQL 查询返回 mock 数据
|
||||||
|
2. myChildren mock 返回 2 个子女,id 与 childGrades/childHomework mock 的 student_id 一致
|
||||||
|
3. urql client 单例跨组件共享(MF shared singleton 验证)
|
||||||
|
|
||||||
|
#### P4-3:ChildSwitcher + useChildSwitcher + Zustand slice
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-2;ISSUE-009 仲裁(switchChild 是 GraphQL Mutation 还是纯前端状态)
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/stores/childSwitcherSlice.ts`(Zustand slice:children / currentChildId / isLoading / error / switchChild / refreshChildren)
|
||||||
|
- `apps/parent-portal/src/hooks/useChildSwitcher.ts`(封装 myChildren GraphQL query + 切换逻辑 + invalidate 子女维度查询)
|
||||||
|
- `apps/parent-portal/src/components/ChildSwitcher.tsx`(variant: tab | dropdown,状态机:idle/switching/switched/error)
|
||||||
|
- `apps/parent-portal/src/components/MultiChildTabBar.tsx`(≤3 子女用 Tab,>3 用下拉,移动端友好)
|
||||||
|
- localStorage 持久化 `parent:currentChildId`(刷新恢复)
|
||||||
|
- **验收标准**:
|
||||||
|
1. 切换子女后,`['parent','grades',currentChildId]` 等子女维度查询 invalidate 重拉
|
||||||
|
2. 刷新页面后 currentChildId 从 localStorage 恢复
|
||||||
|
3. 0 子女显示 EmptyChildState;1 子女不显示 TabBar;2-3 子女显示 Tab
|
||||||
|
4. 切换子女竞态:快速连续切换,旧请求 abort,新数据正确显示
|
||||||
|
|
||||||
|
#### P4-4:Dashboard 页面 + ParentDashboard 组件
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-3
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/app/(app)/parent/dashboard/page.tsx`
|
||||||
|
- `apps/parent-portal/src/components/ParentDashboard.tsx`(组合 useChildren + useChildSummary,插槽:summary-cards / todo-reminders / recent-grades / attendance / custom)
|
||||||
|
- `apps/parent-portal/src/components/ChildSummaryCard.tsx`(单子女概览:头像/姓名/年级/今日作业数/成绩趋势缩略图)
|
||||||
|
- `apps/parent-portal/src/components/AttendanceCalendar.tsx`(出勤日历热力图)
|
||||||
|
- **验收标准**:
|
||||||
|
1. 多子女并列卡片展示
|
||||||
|
2. 权限校验:`PARENT_DASHBOARD_VIEW`,用 `<RequirePermission>`
|
||||||
|
3. SSR 首屏 + CSR 交互(依 02 §14.4 渲染策略)
|
||||||
|
|
||||||
|
#### P4-5:子女成绩页面 + ChildGradeChart
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-3
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/app/(app)/parent/grades/page.tsx`
|
||||||
|
- `apps/parent-portal/src/components/ChildGradeChart.tsx`(recharts 折线 + 班级均分对比 + 多子女对比模式)
|
||||||
|
- **验收标准**:
|
||||||
|
1. 权限校验:`GRADES_READ_CHILD`
|
||||||
|
2. 切换子女后图表刷新
|
||||||
|
3. 多子女对比模式可选
|
||||||
|
|
||||||
|
#### P4-6:子女作业页面
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-3
|
||||||
|
- **交付物**:`apps/parent-portal/src/app/(app)/parent/homework/page.tsx`
|
||||||
|
- **验收标准**:
|
||||||
|
1. 权限校验:`HOMEWORK_READ_CHILD`
|
||||||
|
2. 复用 Shell 暴露的 DataTable 展示作业列表
|
||||||
|
|
||||||
|
#### P4-7:通知偏好页面 + PreferenceForm + Zod
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-4
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/app/(app)/parent/preferences/page.tsx`
|
||||||
|
- `apps/parent-portal/src/components/PreferenceForm.tsx`(矩阵式 UI:子女×事件×渠道,react-hook-form + zodResolver)
|
||||||
|
- `apps/parent-portal/src/schemas/notificationPreferences.ts`(Zod schema,见 02 §14.4)
|
||||||
|
- **验收标准**:
|
||||||
|
1. 权限校验:`PARENT_PREFERENCES_UPDATE`
|
||||||
|
2. 不可用渠道 Toggle disabled + tooltip
|
||||||
|
3. 保存成功后 invalidate `['parent','preferences']`
|
||||||
|
4. 表单 dirty 状态追踪 + 离开页提示
|
||||||
|
|
||||||
|
#### P4-8:跨标签同步(BroadcastChannel)
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-3
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/lib/crossTabSync.ts`(见 02 §16.2 完整实现)
|
||||||
|
- 集成到 RootLayout(`useCrossTabSync()`)
|
||||||
|
- **验收标准**:
|
||||||
|
1. Tab A 切换子女 → Tab B 同步更新
|
||||||
|
2. LWW 冲突解决(ts 大的胜出)
|
||||||
|
3. Safari 降级为 storage 事件
|
||||||
|
|
||||||
|
#### P4-9:Vitest 单测 + MSW 集成测试
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-4 ~ P4-7
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/vitest.config.ts`
|
||||||
|
- 单测:组件渲染/交互、Hook 逻辑、Zod schema 校验、纯函数 utils(覆盖率 ≥ 85%)
|
||||||
|
- 集成测试:useChildSwitcher + Zustand slice + invalidate 流程(MSW mock,覆盖率 ≥ 75%)
|
||||||
|
- **验收标准**:
|
||||||
|
1. `pnpm --filter parent-portal test` 全绿
|
||||||
|
2. 覆盖率达标(单元 ≥ 85%,集成 ≥ 75%)
|
||||||
|
|
||||||
|
#### P4-10:Dockerfile 多阶段构建
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P4-1
|
||||||
|
- **交付物**:`apps/parent-portal/Dockerfile`(builder + runtime,node:22-alpine)
|
||||||
|
- **验收标准**:
|
||||||
|
1. `docker build` 成功
|
||||||
|
2. HEALTHCHECK 指向 `/api/health`
|
||||||
|
3. 镜像体积 < 300MB
|
||||||
|
|
||||||
|
### P5 阶段任务
|
||||||
|
|
||||||
|
#### P5-1:WebSocket 接入 + 事件处理
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:push-gateway :8081/ws 就绪(ai02);parent-portal P4 完成
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/hooks/useWebSocket.ts`(连接 push-gateway ws,JWT 鉴权,自动重连)
|
||||||
|
- 事件处理:`NotificationRequested` → toast + 未读数+1;`GradeRecorded` → toast + 成绩 invalidate;`SchoolAnnouncement` → toast + dashboard invalidate
|
||||||
|
- **验收标准**:
|
||||||
|
1. WS 连接建立后收事件正常
|
||||||
|
2. 断线自动重连(指数退避)
|
||||||
|
|
||||||
|
#### P5-2:通知中心页面 + NotificationFeed
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P5-1
|
||||||
|
- **交付物**:
|
||||||
|
- `apps/parent-portal/src/app/(app)/parent/notifications/page.tsx`
|
||||||
|
- `apps/parent-portal/src/components/NotificationFeed.tsx`(按子女×类型×已读筛选,批量已读,跳转,置顶)
|
||||||
|
- **验收标准**:
|
||||||
|
1. 权限校验:`NOTIFICATION_READ_OWN`
|
||||||
|
2. WebSocket 推送 invalidate 通知列表
|
||||||
|
|
||||||
|
#### P5-3:推送降级(HTTP 轮询)
|
||||||
|
|
||||||
|
- **负责人**:ai15
|
||||||
|
- **依赖**:P5-1
|
||||||
|
- **交付物**:WS 重试 5 次失败后降级为 HTTP 轮询(60s 拉取通知列表)
|
||||||
|
- **验收标准**:降级后通知延迟 ≤ 60s,用户感知降级提示
|
||||||
|
|
||||||
|
### P6 阶段任务
|
||||||
|
|
||||||
|
#### P6-1:Web Vitals + OTel browser SDK
|
||||||
|
|
||||||
|
- **交付物**:`next/web-vitals` 上报 + OTel browser SDK(复用 Shell 暴露的 TracerProvider)
|
||||||
|
- **验收标准**:LCP/CLS/TTFB 指标上报到 Gateway
|
||||||
|
|
||||||
|
#### P6-2:A11y WCAG 2.2 AA 审计 + 修复
|
||||||
|
|
||||||
|
- **交付物**:axe-core 自动扫描 + 手动键盘导航测试,0 严重违规
|
||||||
|
- **验收标准**:所有页面 0 严重 A11y 违规
|
||||||
|
|
||||||
|
#### P6-3:性能优化 + bundle 分析
|
||||||
|
|
||||||
|
- **交付物**:bundle 分析报告 + 代码分割优化(首屏 JS ≤ 80KB gzipped)
|
||||||
|
- **验收标准**:Lighthouse 移动端 4G ≥ 90 分
|
||||||
|
|
||||||
|
#### P6-4:多语言扩展(en-US)
|
||||||
|
|
||||||
|
- **交付物**:`apps/parent-portal/src/i18n/messages/en-US/*.json`(镜像 zh-CN 结构)
|
||||||
|
- **验收标准**:en-US 完成度 100%
|
||||||
|
|
||||||
|
#### P6-5:PWA(Service Worker + manifest)
|
||||||
|
|
||||||
|
- **交付物**:`public/manifest.json` + Service Worker 缓存策略(见 02 §18.4)
|
||||||
|
- **验收标准**:可安装到主屏,离线可查看缓存的子女数据
|
||||||
|
|
||||||
|
#### P6-6:安全硬化(CSP + 敏感数据脱敏)
|
||||||
|
|
||||||
|
- **交付物**:CSP 头配置(复用 Shell)+ 截图脱敏 + 页面离开遮罩
|
||||||
|
- **验收标准**:CSP 无违规报告;敏感数据不泄漏
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## §4 依赖与就绪信号
|
## §4 依赖与就绪信号
|
||||||
|
|
||||||
- **我依赖**:⚠️ 由 ai15 自行补充(见 contract.md)
|
### 4.1 我依赖的上游就绪标志
|
||||||
- **我的就绪信号**:⚠️ 由 ai15 自行补充
|
|
||||||
|
| 上游 | 就绪信号 | 提供方 | 状态 | 阻塞影响 |
|
||||||
|
| ---- | -------- | ------ | ---- | -------- |
|
||||||
|
| teacher-portal Shell | MF exposes(AppShell + GraphQLProvider + hooks + UI 组件)+ shared singleton | ai13 | ⏳ P2 | P4 无法启动 |
|
||||||
|
| parent-bff GraphQL | `POST /graphql` :3010 + currentUser/myChildren/childSummary/childGrades Query | ai05 | ⏳ P4 | 核心数据源 |
|
||||||
|
| iam GetChildrenByParent | gRPC 50052 + `iam_student_guardians` 表 | ai06 | ⏳ P3 补全 | P0 多子女阻塞(见 ISSUE-010) |
|
||||||
|
| core-edu | gRPC 50053 + GradeService/HomeworkService/AttendanceService | ai08 | ⏳ P3 | 成绩/作业数据 |
|
||||||
|
| data-ana | gRPC 50055 + AnalyticsService | ai11 | ⏳ P4 | 学情分析数据 |
|
||||||
|
| msg | gRPC 50056 + NotificationService | ai10 | ⏳ P5 | 通知中心 |
|
||||||
|
| push-gateway | :8081/ws WebSocket | ai02 | ⏳ P5 | 实时推送 |
|
||||||
|
| shared-ts | ApiClient/Logger(coord 维护) | coord | ⏳ | 基础工具 |
|
||||||
|
| contracts | Permissions 常量(coord 维护) | coord | ⏳ | 权限校验 |
|
||||||
|
| ui-tokens / ui-components / hooks | 三层令牌 + shadcn + usePermission/useAuth(ai07/ai13 维护) | ai07/ai13 | ⏳ P2 收尾 | UI 基础 |
|
||||||
|
|
||||||
|
### 4.2 我的就绪信号(供下游消费)
|
||||||
|
|
||||||
|
| 信号 | 说明 | 阶段 |
|
||||||
|
| ---- | ---- | ---- |
|
||||||
|
| parent-portal :4002 dev server 启用 | MF Remote 可被 Shell 加载 | P4-1 完成 |
|
||||||
|
| MF Remote remoteEntry.js 可加载 | Shell 端 `remotes.parent` 可解析 | P4-1 完成 |
|
||||||
|
| 核心 GraphQL 查询可执行 | currentUser / myChildren / childSummary 返回数据(mock 或真实) | P4-2 完成 |
|
||||||
|
| 多子女切换可用 | ChildSwitcher + invalidate 流程通过 | P4-3 完成 |
|
||||||
|
| Dashboard 可访问 | 家长登录 → 看到 Dashboard(含子女卡片) | P4-4 完成 |
|
||||||
|
| 健康检查通过 | `GET /api/health` + `GET /api/ready` 200 | P4-1 完成 |
|
||||||
|
| 测试覆盖率达标 | 单元 ≥ 85% + 集成 ≥ 75% | P4-9 完成 |
|
||||||
|
| Docker 镜像可构建 | `docker build` 成功 | P4-10 完成 |
|
||||||
|
|
||||||
|
### 4.3 全并行 Mock 策略
|
||||||
|
|
||||||
|
| 消费接口 | Mock 方式 | 切换真实时机 |
|
||||||
|
| -------- | --------- | ------------ |
|
||||||
|
| parent-bff GraphQL | MSW 拦截 `POST /api/v1/parent/graphql`,按 operationName 返回 fixtures | parent-bff GraphQL :3010 就绪 ✅ |
|
||||||
|
| iam login | MSW 返回固定 JWT(parent 角色) | api-gateway + iam 就绪 ✅ |
|
||||||
|
| push-gateway WebSocket | mock-socket 模拟 WS 推送(每 30s 1 条通知) | push-gateway :8081 就绪 ✅ |
|
||||||
|
| 子女数据一致性 | myChildren mock 返回 student-001 + student-002,与所有 child* 查询 student_id 一致 | iam GetChildrenByParent 就绪 |
|
||||||
|
|
||||||
|
> Mock 由 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制,上游就绪后设为 `disabled`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 风险与缓解
|
||||||
|
|
||||||
|
| 风险 | 影响 | 缓解 |
|
||||||
|
| ---- | ---- | ---- |
|
||||||
|
| ISSUE-001/002 未仲裁(REST vs GraphQL) | P4-2 GraphQL client 接入方向不确定 | 先按 GraphQL 预排期;仲裁若改 REST,P4-2 重写(预计 1d) |
|
||||||
|
| iam GetChildrenByParent 缺失(ISSUE-010) | 多子女场景无法落地 | mock 固定 2 子女开发;coord 跟踪 ai06 P3 补全 |
|
||||||
|
| MF SSR 对齐复杂 | Remote SSR 需 Shell 上下文 | 优先 CSR,仅 Dashboard 首屏 SSR;P4-1 PoC 验证 |
|
||||||
|
| parent-bff 契约未最终确认 | GraphQL schema 可能变动 | P4 启动前与 ai05 对齐 schema;MSW mock 解耦 |
|
||||||
|
| TanStack Query 缓存膨胀 | 多子女历史查询堆积 | gcTime 5min + 切换子女清理非当前子女缓存 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6 质量门禁
|
||||||
|
|
||||||
|
每个任务完成前必须通过:
|
||||||
|
|
||||||
|
- `pnpm --filter parent-portal lint` 零错误
|
||||||
|
- `pnpm --filter parent-portal typecheck` 零错误
|
||||||
|
- `pnpm --filter parent-portal test` 全绿(P4-9 起强制)
|
||||||
|
- 设计令牌三层规则(无 `#hex` / 无硬编码字体 / 无任意值,ESLint 强制)
|
||||||
|
- A11y:jsx-a11y error 级零违规
|
||||||
|
|
||||||
|
> 提交前校验见 [project_rules §8](../../../../.trae/rules/project_rules.md),commit 遵循 Conventional Commits:`feat(parent-portal): ...`
|
||||||
|
|||||||
Reference in New Issue
Block a user