chore(admin-portal): merge admin-portal full implementation into main

This commit is contained in:
SpecialX
2026-07-10 19:19:52 +08:00
69 changed files with 9274 additions and 1448 deletions

View File

@@ -609,38 +609,46 @@
> student-portal 复用 teacher-portal Shell 暴露的 AppShell / usePermission / ApiClient / ErrorBoundary / 设计令牌 / A11y 工具集,本节仅记录 student-portal **特有**的"场景→技术"映射,通用前端映射见 §2.12。
| 场景 | 技术/规则 |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| P3 端口分配 | student-portal = **4001**004 §1.2 强制 4 端 4000-4003ai07 初稿误用 3001 与 iam 冲突ai14 修订) |
| P3 MF Remote 角色 | next.config.js 配 `@module-federation/nextjs-mf`name: `student_app`port 4001暴露 `./pages` + `./ExamTaking`,复用 Shell 的 `AppShell`/`usePermission`/`ApiClient` |
| P3 DataScope SELF | 学生仅看自己数据004 §5.4 L0前端**无班级/年级/学校切换器**(与 teacher-portal L1-L5 核心差异),路由参数不含 classId/gradeId/schoolId |
| P3 错误码前缀统一 | core-edu 子域统一 `CORE_EDU_`(移除 `EXAMS_`/`HOMEWORK_`/`GRADES_` 独立前缀BFF 前缀 `BFF_STUDENT_`coord §5.2 裁决,从 `STUDENT_BFF_` 迁移) |
| P3 考试防作弊 useAntiCheat | `visibilitychange` 切屏检测 + `blur` 失焦 + `copy`/`paste`/`contextmenu` 禁止 + 违规计数maxViolations=3+ 超限自动交卷;前端层仅辅助,最终权威由后端考试时长 + 服务端时间校准保证 |
| P3 服务器时间同步 | `useServerTimeSync` HookRTT 计算 `offset = (t1 + rtt/2) - serverTime` + 5min 重同步周期;`CountdownTimer` 用 `role="timer"` + `aria-live="polite"` 朗读剩余时间 |
| P3 考试作答状态机 | NotStarted→InProgress→Paused切屏警告→AutoSubmitting倒计时归零→Submitting→SubmittedZustand L3 + localStorage 持久化草稿,自动保存每 30s + blur 时保存 |
| P3 考试页面 CSP iframe 防嵌套 | 考试路由 `frame-ancestors 'none'` + `X-Frame-Options: DENY`;其他路由允许 Shell 同源嵌入 |
| P3 考试 chunk 性能预算 | 考试作答 chunk < 200KB动态 import 隔离);总 Bundle < 300KBLCP < 2.5s / CLS < 0.1 / INP < 200ms |
| P3 移动端一等公民 | 学生端高频移动场景Tailwind 响应式断点 + viewport meta + 触摸优化(按钮 ≥ 44px考试页面支持横屏锁定 |
| P3 A11y 考试专项 | WCAG 2.2 AA键盘导航作答Tab/Enter/方向键)+ `aria-live` 倒计时 + 焦点陷阱(考试中不可 Tab 到地址栏)+ 屏幕阅读器兼容 |
| P3 考试草稿多设备同步预留 | 当前 localStorage 单设备P5+ 预留草稿同步LWW 冲突解决 + WebSocket 推送 + version 字段) |
| P3 Feature Flag 阶段化 | P3-P5 用环境变量(`NEXT_PUBLIC_FEATURE_*`P6+ 预留 Consul 配置中心动态开关 |
| P3 PWA 离线壳预留 | P5+ Service Worker 缓存考试壳,断网时仍可作答,恢复后自动提交 |
| P3 MF 降级与版本协商 | Remote `remoteEntry.js` 加载失败 → fallback 独立页面 + 错误提示;共享依赖版本不匹配 → 运行时协商降级 |
| P3 AI 答疑 SSE 流式P5 | `POST /student/ai/stream-chat` SSE 流式响应 + `streamingText` 逐字渲染 + 打字机效果 + AbortController 取消 |
| P3 权限点命名 | 与 student-bff §3.2 对齐 `STUDENT_*_READ` 风格(`STUDENT_DASHBOARD_READ`/`STUDENT_EXAM_READ`/`STUDENT_HOMEWORK_SUBMIT`/`STUDENT_GRADE_READ` 等) |
| P2 MF Remote 独立壳双模式 | `NEXT_PUBLIC_MF_ENABLED=false` 独立壳渲染(自建 urql client + AppShell`true` 接入 Shell复用 Shell 暴露的 GraphQL client 单例 + AppShell |
| P2 设计令牌三层实现 | primitive.css原始 HSL 分量 + 字面量白名单)→ semantic.css语义令牌组合→ tailwind.config.js映射为 bg-_/text-_ 类ESLint `no-restricted-syntax` 禁止 #hex 字面量 |
| P2 GraphQL endpoint 路径 | `POST /api/v1/student/graphql`(经 api-gateway 反向代理到 student-bff :3009next.config.js `rewrites` 配置 `/api/v1/*` + `/api/auth/*` → api-gateway |
| P2 MSW mock ActionState 信封 | MSW handlers 返回 `{ success: true, errors: [], data: {...}, extensions: { degraded: false } }`,按 operationName 分发到 fixtures/*.json |
| P3 考试状态机实现 | useReducer 实现 NotStarted→InProgress→AutoSaving→Submitting→Submitted 状态机,`canSubmit` 防止非 InProgress/AutoSaving 状态提交 |
| P3 IDB 草稿持久化 | idb-keyval 存储草稿key 格式 `exam-draft:${examId}:${studentId}`,自动保存每 30sNEXT_PUBLIC_EXAM_AUTOSAVE_INTERVAL+ 卸载时保存 |
| P3 服务器时间同步实现 | useServerTimeSync 轮询 serverTime queryRTT 中点估算 offset = (t1 + rtt/2) - serverTime5min 重同步NEXT_PUBLIC_EXAM_TIME_SYNC_INTERVAL失败标记降级 |
| P3 多标签检测降级方案 | BroadcastChannel('student-exam-guard') 优先Safari<15.4 降级到 localStorage StorageEvent |
| P3 提交防重复 | crypto.randomUUID 生成幂等 key降级方案 timestamp+randomuseRef 锁防快速点击重复提交 |
| P3 防作弊批量上报 | 违规事件队列(满 10 条批量上报 recordExamViolation mutationvisibilitychange hidden 计数 ≥3 显示警告遮罩 |
| P5 WebSocket 通知降级 | useNotificationsWebSocket 指数退避重连1s/2s/4s/8s/max 30smock-socket 模拟推送BroadcastChannel('edu-notification') 跨 Tab 同步 |
| P6 健康检查端点 | `/api/health` 返回 liveness无外部依赖`/api/ready` 检查 GraphQL endpoint 可达性fetch simple query503 if not ready |
| P6 Dockerfile 多阶段构建 | node:22-alpine 三阶段install→build→runtime非 root 用户运行EXPOSE 4001healthcheck 用 /api/health |
| P6 Sentry PII 过滤 | beforeSend 过滤 studentName/studentNo/email 字段,仅 NEXT_PUBLIC_SENTRY_DSN 配置时初始化 |
| P2 @edu/hooks ESM .js 后缀解析 | webpack `resolve.extensionAlias` 映射 `.js → [.ts, .tsx, .js]`(仅 .js不映射 .mjs 避免破坏 urql 等库内部解析) |
| P2 MSW setupWorker SSR 报错 | `setupWorker` 仅在浏览器调用,使用动态 `import('msw/browser')` + `typeof window === 'undefined'` 守卫,避免 Next.js prerender 时执行 |
| 场景 | 技术/规则 |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| P3 端口分配 | student-portal = **4001**004 §1.2 强制 4 端 4000-4003ai07 初稿误用 3001 与 iam 冲突ai14 修订) |
| P3 MF Remote 角色 | next.config.js 配 `@module-federation/nextjs-mf`name: `student_app`port 4001暴露 `./pages` + `./ExamTaking`,复用 Shell 的 `AppShell`/`usePermission`/`ApiClient` |
| P3 DataScope SELF | 学生仅看自己数据004 §5.4 L0前端**无班级/年级/学校切换器**(与 teacher-portal L1-L5 核心差异),路由参数不含 classId/gradeId/schoolId |
| P3 错误码前缀统一 | core-edu 子域统一 `CORE_EDU_`(移除 `EXAMS_`/`HOMEWORK_`/`GRADES_` 独立前缀BFF 前缀 `BFF_STUDENT_`coord §5.2 裁决,从 `STUDENT_BFF_` 迁移) |
| P3 考试防作弊 useAntiCheat | `visibilitychange` 切屏检测 + `blur` 失焦 + `copy`/`paste`/`contextmenu` 禁止 + 违规计数maxViolations=3+ 超限自动交卷;前端层仅辅助,最终权威由后端考试时长 + 服务端时间校准保证 |
| P3 服务器时间同步 | `useServerTimeSync` HookRTT 计算 `offset = (t1 + rtt/2) - serverTime` + 5min 重同步周期;`CountdownTimer` 用 `role="timer"` + `aria-live="polite"` 朗读剩余时间 |
| P3 考试作答状态机 | NotStarted→InProgress→Paused切屏警告→AutoSubmitting倒计时归零→Submitting→SubmittedZustand L3 + localStorage 持久化草稿,自动保存每 30s + blur 时保存 |
| P3 考试页面 CSP iframe 防嵌套 | 考试路由 `frame-ancestors 'none'` + `X-Frame-Options: DENY`;其他路由允许 Shell 同源嵌入 |
| P3 考试 chunk 性能预算 | 考试作答 chunk < 200KB动态 import 隔离);总 Bundle < 300KBLCP < 2.5s / CLS < 0.1 / INP < 200ms |
| P3 移动端一等公民 | 学生端高频移动场景Tailwind 响应式断点 + viewport meta + 触摸优化(按钮 ≥ 44px考试页面支持横屏锁定 |
| P3 A11y 考试专项 | WCAG 2.2 AA键盘导航作答Tab/Enter/方向键)+ `aria-live` 倒计时 + 焦点陷阱(考试中不可 Tab 到地址栏)+ 屏幕阅读器兼容 |
| P3 考试草稿多设备同步预留 | 当前 localStorage 单设备P5+ 预留草稿同步LWW 冲突解决 + WebSocket 推送 + version 字段) |
| P3 Feature Flag 阶段化 | P3-P5 用环境变量(`NEXT_PUBLIC_FEATURE_*`P6+ 预留 Consul 配置中心动态开关 |
| P3 PWA 离线壳预留 | P5+ Service Worker 缓存考试壳,断网时仍可作答,恢复后自动提交 |
| P3 MF 降级与版本协商 | Remote `remoteEntry.js` 加载失败 → fallback 独立页面 + 错误提示;共享依赖版本不匹配 → 运行时协商降级 |
| P3 AI 答疑 SSE 流式P5 | `POST /student/ai/stream-chat` SSE 流式响应 + `streamingText` 逐字渲染 + 打字机效果 + AbortController 取消 |
| P3 权限点命名 | 与 student-bff §3.2 对齐 `STUDENT_*_READ` 风格(`STUDENT_DASHBOARD_READ`/`STUDENT_EXAM_READ`/`STUDENT_HOMEWORK_SUBMIT`/`STUDENT_GRADE_READ` 等) |
### 2.16 admin-portal管理端微前端 RemoteP2-P6
> admin-portal 复用 teacher-portal Shell 暴露的 AppShell / usePermission / ApiClient / 设计令牌,本节仅记录 admin-portal **特有**的"场景→技术"映射,通用前端映射见 §2.12。
| 场景 | 技术/规则 |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P2 端口分配 | admin-portal = **4003**004 §1.2 强制 4 端 4000-4003初稿误用 3003 与 data-ana 冲突,修订对齐仲裁 ARB-003 |
| P2 GraphQL admin 命名空间 | 复用 teacher-bff GraphQL endpoint`POST /api/admin/graphql`),不新建 admin-bffARB-001urql client standalone 自建MF 模式复用 Shell 单例 |
| P2 权限点 ADMIN_ 前缀 | admin 自身资源用 `ADMIN_` 前缀(如 `ADMIN_DASHBOARD_VIEW`/`ADMIN_AUDIT_READ`),跨服务资源保留原前缀(如 `IAM_USER_READ`);路由权限映射在 `lib/permissions.ts` ROUTE_PERMISSIONS |
| P2 MSW mock 全量拦截 | `NEXT_PUBLIC_API_MOCKING=enabled` 时 MSW 拦截所有 GraphQL/HTTP`mocks/handlers.ts` 实现 18 个 GraphQL operation + `/api/auth/login` mockfixtures 内存可变副本支持增删改 |
| P2 WebSocket 实时通知 | 接入 push-gateway `GET /ws`ARB-006开发期 `use-websocket.ts` 用定时器模拟推送30s/条3 类通知:`audit_alert`/`abnormal_login`/`system_error` |
| P2 MF Remote 入口 | `next.config.js` 暴露 `./AdminApp`exposes: `./src/app/admin-app.tsx``NEXT_PUBLIC_MF_ENABLED` 控制启用standalone 默认 falseShell 就绪后设 true |
| P3 用户管理 CRUD | `useUsers`/`useUser`/`useCreateUser`/`useUpdateUser`/`useToggleUserStatus` hooks`UserManagementTable` + `UserFormModal` 组件mock 登录 admin@edu.test/admin123 |
| P3 角色权限矩阵 | `RolePermissionMatrix` 组件按 resource 分组、支持全选/半选/单选;`useUpdateRolePermissions` 提交权限码列表;系统预置角色不可删除 |
| P3 视口拖拽排序 | `@dnd-kit/sortable` 实现拖拽排序 + `useSortable` hook`SortableViewportItem` 每行可编辑 label/permission/visible保存时批量调用 `updateViewport` |
| P4 组织树递归加载 | `OrganizationTree` 递归组件 + `useOrganization(parentId)` 按需加载子节点;展开/折叠状态本地管理A11y `role="tree"`/`role="treeitem"`/`aria-expanded`/`aria-selected` |
| P5 审计日志 CSV 导出 | `exportAuditLogsCsv` 生成 BOM + UTF-8 CSV`useAuditLogs` 支持 search/action 过滤;审计日志经 teacher-bff GraphQL 消费ARB-005 不直连 Kafka |
| P5 仪表盘服务健康 | `AdminDashboardViewModel.serviceHealth` 含 serviceName/status/latencyMs`Badge` 组件映射 healthy/degraded/down 状态色 |
| P6 A11y WCAG 2.2 AA | `eslint-plugin-jsx-a11y` flat configESLint 9skip-link + focus-visible + aria-* 完整;`@next/eslint-plugin-next` 14.x 与 ESLint 9 不兼容,暂用 jsx-a11y + tseslint 替代 |
| P6 Web Vitals 采集 | `web-vitals` 包LCP/CLS/FCP/INP/TTFB`WebVitalsInitializer` 组件仅 production 启用;`navigator.sendBeacon` 上报到 `/api/admin/web-vitals` |
| P6 健康检查端点 | `/api/health`liveness 200+ `/api/ready`readiness 检查 gateway 可达性2s 超时mock 模式直接 ready |
| P6 Dockerfile no-push | 多阶段构建 `node:22-alpine`builder 阶段 `pnpm install --frozen-lockfile` + `next build`runner 阶段 `standalone` 输出 + EXPOSE 4003 |
| P6 urql 类型断言 | urql `useQuery`/`useMutation` 泛型与纯字符串 query 兼容性有限;`use-graphql.ts` 用 `as unknown as` 从 unknown 转换(项目规则允许从 unknown 断言) |