Files
Edu/apps/teacher-portal/docs/01-understanding.md
SpecialX e691cd267d docs(teacher-portal): ai07 阶段1+2 拆分到4端portal的docs目录
删除合并版README,按portal拆分8份文档(每端01-understanding+02-architecture-design)

teacher-portal(shell/P2)+student-portal(remote/P3)+parent-portal(remote/P4)+admin-portal(remote/P6)

AI Agent: ai07 (4 portals)

Branch: docs/portals-stage1-stage2-design-ai07
2026-07-09 18:23:27 +08:00

206 lines
17 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模块理解确认书 — teacher-portal
> AIai07TS/React · 教学场景域前端 shell
> 阶段:阶段 1 交付物
> 日期2026-07-09
> 关联:[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §1.1a/1.1b/§5.4、[AI 分配方案](../../../docs/architecture/ai-allocation.md) §5 ai13/ai07、[pending-features P2](../../../docs/architecture/roadmap/pending-features.md)、[known-issues §2.12](../../../docs/troubleshooting/known-issues.md)
---
## 1. 我在架构中的位置
- **层级**L2 微前端层004 §3.1 六层架构中的前端层)
- **MF 角色****Shell 宿主**(主应用),其余 3 端student/parent/admin-portal作为 Remote 子应用挂载
- **上游(谁调用我)**:浏览器(教师 / 教导主任 / 教研组长)
- **下游(同步)**api-gatewayREST经 Next.js `rewrites` 代理 `/api/v1/*`
- **下游推送P5**push-gatewayWebSocket/SSE
- **BFF 对接**teacher-bffGraphQL Yoga + DataLoaderP2-P3 用 REST 过渡)
- **通信方式**HTTP/REST前端→Gateway+ WebSocket前端→push-gatewayP5
- **不直连**:前端不直连任何业务服务或 BFF 后端实例,全部经 api-gateway 代理
**说明**
- 通过 `next.config.js``rewrites``/api/v1/*` 代理到 `api-gateway`
- MF 架构下teacher-portal 作为 Shell 宿主提供 AppShell + 共享组件库 + 权限 Hook + API 请求层
- 场景域 BFF 复用策略004 §5.4):教导主任/教研组长复用 teacher-portal + 额外管理视口,不单独建 portal
## 2. 我的限界上下文
### 2.1 我负责的聚合 / 实体(前端视图模型)
- 班级、考试、作业、成绩、备课、AI 出题(教学场景域前端视图)
- 会话状态Session、视口Viewport、权限Permission
### 2.2 业务领域
- **D3 教学核心领域**(前端场景域:教学场景域)
### 2.3 不负责
- 学生作答界面(归 student-portal
- 家长多子女切换(归 parent-portal
- 用户/角色/权限 CRUD归 admin-portal
### 2.4 数据范围
- DataScope L1-L5教师 L1 班级 / 教导主任 L2 年级 / 校管理员 L3 学校 / 区教研员 L4 / 系统管理员 L5
## 3. 我与外部的契约
### 3.1 消费的后端 API经 api-gateway 代理)
| 路径前缀 | 下游 BFF/服务 | 关键端点 |
| ------------------------------------------------------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/api/v1/iam/*` | iam | `POST /iam/login``POST /iam/register``POST /iam/refresh``GET /iam/me``GET /iam/rbac/...``GET /iam/effective-permissions` |
| `/api/v1/teacher/*` | teacher-bff | `GET /teacher/viewports``GET /teacher/dashboard``GET /teacher/classes/:id/exams``GET /teacher/classes/:id/homework``GET /teacher/exams/:id/grades` |
| `/api/v1/classes/*` | core-educlasses 模块) | CRUD黄金模板 |
| `/api/v1/exams/*` `/api/v1/homework/*` `/api/v1/grades/*` | core-edu | P3 教学核心 |
| `/api/v1/textbooks/*` `/api/v1/knowledge-points/*` `/api/v1/questions/*` | content | P4 内容 |
| `/api/v1/ai/*` | aiSSE 流式) | P5 AI 辅助出题 |
| `/api/v1/notifications/*` | msg | 通知中心P5 |
### 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_``CONTENT_``MSG_``AI_``BFF_TEACHER_``GW_`)。前端 API 请求层根据 `error.code` 前缀路由到对应的 i18n key。
### 3.3 推送契约P5
| 协议 | 场景 |
| ------------------------- | -------------------------------------------- |
| WebSocketpush-gateway | 学生提交作业通知、考试成绩录入提醒、全校广播 |
| SSEai 服务) | AI 辅助出题流式响应 |
### 3.4 proto 不直接消费
前端不调用 gRPCBFF 把 gRPC 聚合为 REST/GraphQL 暴露给前端。前端仅消费 `packages/contracts/src/permissions.ts` 中的权限点常量TS 文件,非 proto 生成)。
## 4. 我的技术栈
| 维度 | 选型 | 说明 |
| --------------------------- | -------------------------------------------------------- | ---------------------------------------------- |
| 框架 | Next.js 14+App Router | server components 默认client components 按需 |
| 语言 | TypeScript 5.5+strict | 沿用 tsconfig.base.json |
| 微前端 | Module Federation 2.0@module-federation/nextjs-mf | teacher-portal = Shell |
| 样式 | Tailwind CSS 3.4+ | 配合设计令牌三层模型 |
| UI 组件库 | shadcn/ui迁移指南 §7.2 | 平移至 `packages/ui-components/`MF 共享 |
| 状态管理 L1 URL | nuqs | 可分享、可刷新状态 |
| 状态管理 L2 Server | TanStack Query v5 | 服务端数据缓存、重试、乐观更新 |
| 状态管理 L3 Client Business | Zustand slice | 客户端业务状态 |
| 状态管理 L4 Global UI | Zustand ui-store + ModalRoot | 全局 UI 状态 |
| 状态管理 L5 Form | react-hook-form + zodResolver | 表单状态 |
| 富文本 | Tiptap备课、出题、反馈 | SSR 安全 |
| 图表 | recharts | 学情、Dashboard |
| i18n | next-intl | BFF/服务返回 i18n key + 参数,前端翻译 |
| A11y | eslint-plugin-jsx-a11yerror 级) | WCAG 2.2 AA |
| 字体 | Intersans/ Frauncesserif/ JetBrains Monomono | next/font/google 加载CSS 变量暴露 |
## 5. 我的阶段归属
- **阶段**P2
- **当前状态**:✅ 已实现 P1 测试页 + P2 骨架(登录/AppShell/Dashboard/classes CRUD 待审计对齐黄金模板 + 引入 MF + 共享组件库
- **依赖上游阶段**P1api-gateway + classes + iam
## 6. 我需要对齐的黄金模板项(对照 classes 服务)
> 前端无 `@RequirePermission` 装饰器(后端概念),对齐项改造为前端等价物。
| 对齐项 | classes后端黄金模板 | teacher-portal 前端等价 | 当前状态 |
| --------------------- | ------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------- |
| 权限校验 | `@RequirePermission(Permissions.XXX)` | `usePermission().hasPermission("XXX")` Hook + `<RequirePermission>` 组件 | ❌ 缺失,直接硬编码 `user.roles.join(", ")` |
| 错误码前缀统一 | `CLASSES_*``IAM_*` | API 请求层根据 `error.code` 前缀路由 i18n | ❌ 缺失统一请求层 |
| logger | pino | 前端 console + SentryP6 | ⚠️ 仅 console.error |
| metrics | prom-client `/metrics` | 前端 Web Vitals → Gateway 上报 | ❌ 缺失 |
| tracer | OTel SDK | 前端 OTel browser SDKP6 | ❌ 缺失 |
| /healthz + /readyz | `GET /healthz` `GET /readyz` | Next.js `/api/health` route + Dockerfile HEALTHCHECK | ⚠️ Dockerfile 有 HEALTHCHECK无 /api/health |
| 优雅关闭 | SIGTERM handler | Next.js 无长连接,无需 | ✅ N/A |
| 测试覆盖率 ≥ 80% | Vitest | Vitest + @testing-library/react + Playwright E2E | ❌ 0% |
| Dockerfile 多阶段构建 | 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 | ❌ 硬编码在 globals.css + tailwind.config.js |
| A11y 工具集 | — | useA11yId / mergeA11yProps / describeInput / focus-trap | ❌ 缺失 |
---
## 附teacher-portal 现状审计(对齐黄金模板)
### 审计表
| 维度 | 状态 | 说明 |
| ------------------------------------ | ------ | ---------------------------------------------------------------------- |
| 权限装饰器(前端等价 usePermission | ❌ | AppShell.tsx 直接 `user.roles.join(", ")`,违反 project_rules §3.8 |
| 错误码前缀 | ❌ | 无统一 API 请求层,错误处理散落在每个 page.tsx |
| logger | ⚠️ | 仅 `console.error`,无结构化、无 trace_id |
| metrics | ❌ | 无 Web Vitals 采集 |
| tracer | ❌ | 无 OTel browser SDK |
| /healthz | ⚠️ | Dockerfile 有 `HEALTHCHECK wget /`,但无 `/api/health` route |
| /readyz | ❌ | 无 |
| 优雅关闭 | ✅ N/A | Next.js 无长连接 |
| 测试覆盖率 | ❌ | 0%,无测试文件 |
| Dockerfile 多阶段 | ✅ | builder + runtime非 root 用户HEALTHCHECK |
| Zod 输入验证 | ❌ | 表单直接 useState无 zodResolver |
| GlobalErrorFilterErrorBoundary | ❌ | 无 React ErrorBoundary |
| 设计令牌三层 | ❌ | 硬编码在 globals.css`:root` 变量)+ tailwind.config.jshex 字面量) |
| A11y 工具集 | ❌ | 无 useA11yId、focus-trap 等 |
| Module Federation 配置 | ❌ | next.config.js 仅有 rewrites无 MF |
| 5 层状态管理 | ❌ | 仅 useState + localStorage无 nuqs/TanStack Query/Zustand |
| 共享组件库 | ❌ | 仅 AppShell无 ErrorBoundary/Loading/Empty/RequirePermission |
| i18n | ❌ | 中文硬编码在 JSX |
| API 请求层 | ❌ | 每页重复 fetch + authHeaders + try/catch |
| ESLint flat config 自定义规则 | ❌ | 未配置 no-hardcoded-fonts / design-tokens 规则 |
### 现有文件清单
```
apps/teacher-portal/
├─ src/
│ ├─ app/
│ │ ├─ (app)/ # 受保护路由组(套 AppShell
│ │ │ ├─ classes/page.tsx # 班级 CRUDP1 测试页)
│ │ │ ├─ dashboard/page.tsx # 教师仪表盘
│ │ │ ├─ exams/page.tsx # 考试列表
│ │ │ ├─ grades/page.tsx # 成绩查询
│ │ │ ├─ homework/page.tsx # 作业列表
│ │ │ └─ layout.tsx # 套 AppShell
│ │ ├─ login/page.tsx # 登录页(不套壳)
│ │ ├─ globals.css # 全局样式 + 设计令牌(硬编码)
│ │ ├─ layout.tsx # 根布局(字体加载)
│ │ └─ page.tsx # 根路径重定向
│ ├─ components/
│ │ └─ AppShell.tsx # 左侧栏 + 主内容区
│ └─ lib/
│ └─ auth.ts # token + userInfo localStorage 管理
├─ Dockerfile # 多阶段构建 ✅
├─ next.config.js # 仅 rewrites无 MF ❌
├─ package.json # 仅 next/react/react-dom无 MF/Query/Zustand ❌
├─ tailwind.config.js # 硬编码 hex ❌
├─ postcss.config.js
└─ tsconfig.json
```
### 主要违规点(必须在 P2 收尾或 P3 起步时修复)
1. **权限硬编码**[AppShell.tsx:143](../src/components/AppShell.tsx) `user.roles.join(", ")` 违反 project_rules §3.8,必须改为 `usePermission().hasPermission()`
2. **设计令牌硬编码**[globals.css:6-13](../src/app/globals.css) 与 [tailwind.config.js:7-19](../tailwind.config.js) 出现 `hsl(...)` 字面量与 `'Fraunces'`/`'Inter'` 字面量,违反 project_rules §3.10
3. **无统一 API 请求层**4 个 page.tsx 重复 `authHeaders()` + `fetch` + `try/catch` + `setError`,必须抽取到 `lib/api.ts`
4. **无权限 Hook**:缺少 `usePermission().hasPermission()`,无法做 L3 组件级视口控制
5. **无 ErrorBoundary**React 渲染异常会白屏
6. **无 5 层状态管理**:登录态用 localStorageL3但无 TanStack QueryL2导致每页重复 fetch
7. **字体名硬编码**[layout.tsx:3-7](../src/app/layout.tsx) 直接 import `Inter/Fraunces/JetBrains_Mono`,应改为 `var(--font-family-sans/serif/mono)`
---
**AI Agent**: ai07 (teacher-portal shell)
**Branch**: docs/teacher-portal-stage1-stage2-design-ai07