feat(teacher-portal): 完整实现 teacher-portal 微前端

包含 settings/students/api、graphql、mocks、ui-tokens 设计令牌等
This commit is contained in:
SpecialX
2026-07-10 19:10:20 +08:00
parent 14d836beb0
commit 1eacd1ed87
48 changed files with 3897 additions and 1056 deletions

View File

@@ -131,26 +131,48 @@ gantt
### §4.1 我依赖的上游就绪标志
| 上游 | 就绪标志 | 阻塞阶段 | 状态 |
| --------------------------------------------------------- | ------------------------------------------------- | -------- | ---------------------- |
| packages 骨架ai13 自建) | ui-tokens/ui-components/hooks 可 import | P2 启动 | ✅ 已就绪(批次 0.15 |
| teacher-bff GraphQL schemaai03 + coord 仲裁 ISSUE-037 | packages/shared-ts/contracts/graphql/ 第一版 | P2 启动 | ⏳ 待 coord 仲裁 |
| api-gateway HTTP :8080ai01 | /api/v1/teacher/graphql + /api/auth/login 路由可用 | P2 启动 | ⏳ 待 ai01 |
| teacher-bff core-edu 扩展ai03 P3 | classExams/classHomework/studentGrades query 可用 | P3 启动 | ⏳ 待 ai03 P3 |
| teacher-bff content/data-ana 扩展ai03 P4 | knowledgeGraph/studentAnalytics query 可用 | P4 启动 | ⏳ 待 ai03 P4 |
| push-gateway WebSocket :8081/wsai02 P5 | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
| ai 服务 gRPCai12 P5 | generateQuestion/generateLessonPlan 可调 | P5 启动 | ⏳ 待 ai12 P5 |
| iam refresh cookie 端点ai06 P6 | POST /iam/auth/refresh 返回 httpOnly cookie | P6 启动 | ⏳ 待 ai06 P6 |
| 上游 | 就绪标志 | 阻塞阶段 | 状态 |
| --------------------------------------------------------- | -------------------------------------------------- | -------- | ---------------------- |
| packages 骨架ai13 自建) | ui-tokens/ui-components/hooks 可 import | P2 启动 | ✅ 已就绪(批次 0.15 |
| teacher-bff GraphQL schemaai03 + coord 仲裁 ISSUE-037 | packages/shared-ts/contracts/graphql/ 第一版 | P2 启动 | ⏳ 待 coord 仲裁 |
| api-gateway HTTP :8080ai01 | /api/v1/teacher/graphql + /api/auth/login 路由可用 | P2 启动 | ⏳ 待 ai01 |
| teacher-bff core-edu 扩展ai03 P3 | classExams/classHomework/studentGrades query 可用 | P3 启动 | ⏳ 待 ai03 P3 |
| teacher-bff content/data-ana 扩展ai03 P4 | knowledgeGraph/studentAnalytics query 可用 | P4 启动 | ⏳ 待 ai03 P4 |
| push-gateway WebSocket :8081/wsai02 P5 | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
| ai 服务 gRPCai12 P5 | generateQuestion/generateLessonPlan 可调 | P5 启动 | ⏳ 待 ai12 P5 |
| iam refresh cookie 端点ai06 P6 | POST /iam/auth/refresh 返回 httpOnly cookie | P6 启动 | ⏳ 待 ai06 P6 |
### §4.2 我的就绪信号(供下游消费)
| 信号 | 就绪标志 | 消费方 |
| ------------------------------- | -------------------------------------- | ---------------------------------- |
| teacher-portal dev server :4000 | next dev -p 4000 可访问 | 无(最前端) |
| MF Shell 可加载 | 首页渲染 AppShell + 导航 | student/parent/admin RemoteP3+ |
| 登录流程可用 | POST /api/auth/login → JWT → Dashboard | 无(最前端) |
| GraphQL 查询可执行 | currentUser/myClasses 返回数据 | 无(最前端) |
| parent-portal Remote 接入点 | MF remotes 配置就绪 | parent-portalai15 P4 |
| 信号 | 就绪标志 | 消费方 | P2 状态 |
| ------------------------------- | ----------------------------------------------------------------------- | ---------------------------------- | --------------------- |
| teacher-portal dev server :4000 | next dev -p 4000 可访问 | 无(最前端) | ✅ 已就绪 |
| MF Shell 可加载 | 首页渲染 AppShell + 导航 | student/parent/admin RemoteP3+ | ✅ 已就绪 |
| 登录流程可用 | POST /api/auth/login → JWT → Dashboard | 无(最前端) | ✅ 已就绪MSW mock |
| GraphQL 查询可执行 | DashboardQuery/ClassesQuery/MeQuery/ViewportsQuery/ClassQuery 返回数据 | 无(最前端) | ✅ 已就绪MSW mock |
| P3 扩展 Query 可执行 | ClassStudents/ClassExams/ClassHomework/ExamGrades + UpdateUser Mutation | 无(最前端) | ✅ 已就绪MSW mock |
| parent-portal Remote 接入点 | MF remotes 配置就绪 | parent-portalai15 P4 | ⏳ P4 启用 |
| /api/health + /api/ready | k8s livenessProbe + readinessProbe 端点 | infra部署 | ✅ 已就绪 |
### §4.3 P2 交付物完成清单
- ✅ packages 骨架ui-tokens/ui-components/hooks批次 0.15
- ✅ NextFederationPlugin 配置exposes AppShell + GraphQLProvider + hooks + UI 组件shared react/urql/graphql/@edu/* singleton
- ✅ GraphQLProvider + urql client 单例Shell 暴露Remote 复用§2.17 方案 A
- ✅ AppShellLayout + 侧边栏 + 路由守卫 + ErrorBoundary + 设计令牌三层 + GraphQL ViewportsQuery + MeQuery + usePermission
- ✅ 登录页POST /api/auth/login → JWT 存 localStorageF12
- ✅ 权限上下文usePermission + buildPermissionContext权限点 `<RESOURCE>_<ACTION>` F7
- ✅ Dashboard 框架GraphQL DashboardQuery + 统计卡片)
- ✅ 班级列表页GraphQL ClassesQuery
- ✅ 学生列表页GraphQL ClassStudentsQueryP3 扩展 MSW mock
- ✅ 个人设置页GraphQL MeQuery 只读展示)
- ✅ 考试/作业/成绩页面GraphQL ClassExams/ClassHomework/ExamGradesP3 扩展 MSW mock
- ✅ MSW mock 层handlers + fixtures + browser/server + initMocks + providers 集成 + mockServiceWorker.js
- ✅ /api/health + /api/ready 路由liveness + readiness
- ✅ Dockerfile 端口 3000→4000 + 健康检查路径更新
- ✅ .env.example 添加 teacher-portal 变量NEXT_PUBLIC_TEACHER_BFF_GRAPHQL_URL / NEXT_PUBLIC_API_MOCKING / NEXT_PUBLIC_MF_ENABLED / API_GATEWAY_URL
- ✅ 质量校验通过lint + typecheck 零错误)
- ✅ arch.db 已更新arch:scanTS 14 模块 / 343 符号 / Proto 138 契约)
---

View File

@@ -406,29 +406,41 @@
### 2.12 teacher-portal微前端宿主P1 测试页)
| 场景 | 技术/规则 |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P1 测试页 | 单一 Next.js 应用,验证 classes CRUD 端到端链路 |
| API 调用 | `fetch(${API_BASE}/api/v1/classes)` + `Authorization: Bearer ${TEST_JWT}` |
| P1 测试 JWT | 开发工具生成 HS256 tokenP2 起由 IAM 签发 RS256 |
| P2 Module Federation | next.config.js 配置,按场景域分 4 个稳定 portal |
| P2 路由组 + AppShell | `app/(app)/layout.tsx` 用 AppShell 包裹受保护页;`/login` 与 `/` 不套壳 |
| P2 真实 JWT | 登录后 token 存 localStorage`authHeaders()` 读 `Bearer ${getToken()}` |
| P2 视口驱动侧边栏 | AppShell fetch `/teacher/viewports` 渲染左侧导航active 路由高亮 |
| P2 根路径重定向 | `app/page.tsx` 客户端组件 `router.replace(isAuthenticated() ? '/dashboard' : '/login')` |
| fetch headers 类型 | `authHeaders(): Record<string, string>` 显式标注,避免 `{}` 与 `HeadersInit` 不兼容 |
| P2 MF Shell+Remote 架构 | teacher-portal:**4000** 为 Shellstudent:**4001**/parent:**4002**/admin:**4003** 为 Remote004 §1.2 + coord final §F1MF 暴露 **AppShell 整体**coord final §F10各 Remote 自行决定内部布局 |
| P2 统一 API 请求层 | ApiClient 封装 401 自动刷新 token 轮转 + ActionState `{success,data,error:{code,message,details?,traceId}}` 解析 + 错误码前缀路由 i18n + trace_id 透传;**P2 起对接 GraphQL**urql/apollocoord final §F9/B1 |
| P2 统一 API 请求层 | ApiClient 封装 401 自动刷新 token 轮转 + ActionState `{success,data,error}` 解析 + 错误码前缀路由 i18n替代页面级 `authHeaders()+fetch` 重复 |
| P2 设计令牌三层模型 | primitive原始色板→ semantic-light/dark语义→ tailwind-theme`@theme inline` 暴露 `bg-*`ESLint `no-restricted-syntax` 禁 `#hex` + `design-tokens/no-hardcoded-fonts` 禁字面量字体§3.10 |
| P2 4 端错误码前缀对齐 | **BFF_TEACHER\_=teacher-bff / BFF_STUDENT\_=student-bff / BFF_PARENT\_=parent-bff**coord final §B5统一 `BFF_XXX_` 风格admin-bff 待 P6 定;前端按前缀路由 i18n key |
| P2 4 端错误码前缀对齐 | TP_=teacher-bff / SP_=student-bff / PP_=parent-bff / AP_=admin-bff前端按前缀路由 i18n key |
| P2 i18n key 命名 | **统一 `error.<service>.<code_snake>`**(如 error.iam.invalid_credentials、error.bffTeacher.validation_errorcoord final §F4翻译文件按域分 namespace |
| P2 权限点命名风格 | **统一 `<RESOURCE>_<ACTION>`**(如 DASHBOARD_VIEW、EXAM_READ、GRADE_READ数据范围用后缀 `_OWN`/`_CHILD`(如 GRADE_READ_CHILDcoord final §F7 |
| P2 Token 存储 | **P2 用 localStorage**accessToken + refreshTokencoord final §F12httpOnly Cookie + CSRF Token **P6 单独评估**非分阶段迁移P2 直接采用最终方案的当前阶段形态) |
| P2 packages 归属 | **ai13 维护** packages/ui-tokens、ui-components、hookscoord final §F8coord 仅维护 shared-ts、contractsai13 需建立这 3 个共享包 |
| P2 GraphQL 客户端 | **P2 起前端用 urql 或 apollo**coord final §F9对接 teacher-bff GraphQL Yoga + DataLoader废弃 REST fetch 模式ApiClient 适配 GraphQL 操作类型query/mutation/subscription |
| P2 视口端点对齐 | **GET /iam/permissions/effective**(非 /iam/effective-permissionscoord final §F6/I8+ GET /iam/viewports + GET /iam/rolesparent 子女切换 POST /parent/children/:childId/select |
| 场景 | 技术/规则 |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P1 测试页 | 单一 Next.js 应用,验证 classes CRUD 端到端链路 |
| API 调用 | `fetch(${API_BASE}/api/v1/classes)` + `Authorization: Bearer ${TEST_JWT}` |
| P1 测试 JWT | 开发工具生成 HS256 tokenP2 起由 IAM 签发 RS256 |
| P2 Module Federation | next.config.js 配置,按场景域分 4 个稳定 portal |
| P2 路由组 + AppShell | `app/(app)/layout.tsx` 用 AppShell 包裹受保护页;`/login` 与 `/` 不套壳 |
| P2 真实 JWT | 登录后 token 存 localStorage`authHeaders()` 读 `Bearer ${getToken()}` |
| P2 视口驱动侧边栏 | AppShell fetch `/teacher/viewports` 渲染左侧导航active 路由高亮 |
| P2 根路径重定向 | `app/page.tsx` 客户端组件 `router.replace(isAuthenticated() ? '/dashboard' : '/login')` |
| fetch headers 类型 | `authHeaders(): Record<string, string>` 显式标注,避免 `{}` 与 `HeadersInit` 不兼容 |
| P2 MF Shell+Remote 架构 | teacher-portal:**4000** 为 Shellstudent:**4001**/parent:**4002**/admin:**4003** 为 Remote004 §1.2 + coord final §F1MF 暴露 **AppShell 整体**coord final §F10各 Remote 自行决定内部布局 |
| P2 统一 API 请求层 | ApiClient 封装 401 自动刷新 token 轮转 + ActionState `{success,data,error:{code,message,details?,traceId}}` 解析 + 错误码前缀路由 i18n + trace_id 透传;**P2 起对接 GraphQL**urql/apollocoord final §F9/B1 |
| P2 统一 API 请求层 | ApiClient 封装 401 自动刷新 token 轮转 + ActionState `{success,data,error}` 解析 + 错误码前缀路由 i18n替代页面级 `authHeaders()+fetch` 重复 |
| P2 设计令牌三层模型 | primitive原始色板→ semantic-light/dark语义→ tailwind-theme`@theme inline` 暴露 `bg-*`ESLint `no-restricted-syntax` 禁 `#hex` + `design-tokens/no-hardcoded-fonts` 禁字面量字体§3.10 |
| P2 4 端错误码前缀对齐 | **BFF_TEACHER\_=teacher-bff / BFF_STUDENT\_=student-bff / BFF_PARENT\_=parent-bff**coord final §B5统一 `BFF_XXX_` 风格admin-bff 待 P6 定;前端按前缀路由 i18n key |
| P2 4 端错误码前缀对齐 | TP_=teacher-bff / SP_=student-bff / PP_=parent-bff / AP_=admin-bff前端按前缀路由 i18n key |
| P2 i18n key 命名 | **统一 `error.<service>.<code_snake>`**(如 error.iam.invalid_credentials、error.bffTeacher.validation_errorcoord final §F4翻译文件按域分 namespace |
| P2 权限点命名风格 | **统一 `<RESOURCE>_<ACTION>`**(如 DASHBOARD_VIEW、EXAM_READ、GRADE_READ数据范围用后缀 `_OWN`/`_CHILD`(如 GRADE_READ_CHILDcoord final §F7 |
| P2 Token 存储 | **P2 用 localStorage**accessToken + refreshTokencoord final §F12httpOnly Cookie + CSRF Token **P6 单独评估**非分阶段迁移P2 直接采用最终方案的当前阶段形态) |
| P2 packages 归属 | **ai13 维护** packages/ui-tokens、ui-components、hookscoord final §F8coord 仅维护 shared-ts、contractsai13 需建立这 3 个共享包 |
| P2 GraphQL 客户端 | **P2 起前端用 urql 或 apollo**coord final §F9对接 teacher-bff GraphQL Yoga + DataLoader废弃 REST fetch 模式ApiClient 适配 GraphQL 操作类型query/mutation/subscription |
| P2 视口端点对齐 | **GET /iam/permissions/effective**(非 /iam/effective-permissionscoord final §F6/I8+ GET /iam/viewports + GET /iam/rolesparent 子女切换 POST /parent/children/:childId/select |
| P2 MSW mock 层 | `src/mocks/` 含 handlers.ts拦截 /api/auth/login + /api/v1/teacher/graphql+ fixtures/*.ts + browser.tssetupWorker+ server.tssetupServer+ index.tsinitMocks 按 NEXT_PUBLIC_API_MOCKING=enabled 启用) |
| P2 MSW worker 脚本生成 | `npx msw init public/` 生成 mockServiceWorker.js 静态文件package.json 自动添加 `msw.workerDirectory` 配置;必须在 teacher-portal 目录下执行msw 在 devDependencies |
| P2 MSW providers 集成 | GraphQLProvider 内 `initMocks()` ready 前返回 null 避免 GraphQL 请求未拦截server.ts `onUnhandledRequest: "bypass"` 放行非 mock 请求 |
| P2 urql useQuery 类型 | urql useQuery 返回 data 类型推断不足map 回调参数隐式 any需 `import type { XxxItem } from "@/lib/graphql"` + `(data.xxx as XxxItem[]).map(...)` 显式断言 |
| P2 Suspense + useSearchParams | Next.js App Router 中 useSearchParams 必须包裹 `<Suspense>`否则构建报错students/exams/homework/grades 页面均用 `Suspense fallback={<Loading/>}` 包裹 StudentsContent |
| P2 DataScope 类型对齐 | graphql.ts 的 DataScope 必须与 hooks/types.ts 的 DataScope 完全一致(`"SELF" \| "CLASS" \| "GRADE" \| "SCHOOL" \| "DISTRICT" \| "ALL"`ARB-001 §1.2 schema否则 buildPermissionContext 返回类型不匹配 |
| P2 TS4114 override | tsconfig.base.json `noImplicitOverride: true` 下React.Component 子类的 state/componentDidCatch/render 必须加 `override` 修饰符 |
| P2 cn.ts null vs undefined | `match ? match[1] : null` 在 noUncheckedIndexedAccess 下返回 `string \| undefined`,与 `string \| null` 不兼容;改用 `match?.[1] ?? null` |
| P2 AppShell GraphQL 化 | AppShell 从 REST `/teacher/viewports` 改为 GraphQL `ViewportsQuery` + `MeQuery`;权限上下文从 localStorage 读取 + usePermission 注入;视口按 requiredPermission 过滤 |
| P2 健康检查路由 | `app/api/health/route.ts`liveness+ `app/api/ready/route.ts`readiness均 `export const dynamic = "force-dynamic"`,返回 NextResponse.jsonDockerfile HEALTHCHECK 指向 :4000/api/health |
| P2 Dockerfile 端口对齐 | teacher-portal 端口 3000→4000004 §1.2Dockerfile `ENV PORT=4000` + `EXPOSE 4000` + `CMD next start -p 4000` + HEALTHCHECK `http://localhost:4000/api/health` |
| P2 MSW GraphQL 路由 | handlers.ts `http.post("*/api/v1/teacher/graphql")` 按 operationName switch 路由;未传 operationName 时从 query 文本正则提取 `/(?:query\|mutation)\s+(\w+)/` 兜底 |
### 2.13 parent-portal家长端微前端 RemoteP4