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

@@ -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