Files
Edu/apps/portal-shell/ARCHITECTURE.md
SpecialX a28a6bd6ea feat(portal-shell): clean widget design tokens and fix lint:tokens (P1-6)
516 mechanical replacements across 25 widget files:
- spacing xs/sm/md/lg/xl to numeric 1/2/3/4/6
- text-heading-3 to text-lg font-semibold
- bg-danger to bg-destructive
- border border dedup

Fix .eslintrc.tokens.js to use typescript-eslint parser (was importing
uninstalled @typescript-eslint/parser). lint:tokens now passes.
2026-07-22 15:07:38 +08:00

121 KiB
Raw Blame History

portal-shell 前端架构总纲v3.0

版本3.0 日期2026-07-20 状态:P0 已完成 + P1-1/P1-2/P1-3/P1-4 已完成2026-07-22 验收)+ P1 进行中;架构审计完成 + 重设计方案定稿 本文档地位:portal-shell 前端工作的唯一权威指导文档。所有后续 AI/人工在此模块的工作必须先读本文件,以其为准。

关联文档(按效力排序):

  1. 本文件v3.0 总纲:审计结论 + 目标架构 + 路线图 + 工作规范)
  2. README.mdv2.0 模块文档:微内核仪表盘子系统的详细设计,仅其"插件仪表盘"部分继续有效)
  3. 004 架构影响地图后端架构唯一源§5.4 视口四层模型、§11.7 前端数据层)
  4. 项目规则强制约束§3.10 设计令牌、§4 安全、§14 多 AI 协作)
  5. MIGRATION_GUIDE.mdCICD → Edu 迁移背景)

效力声明README v2.0 中与本文件冲突的表述(完成度声明、设计方向、"旧 portal 已下线"等)以本文件为准;docs/standards/ui-design-system.mdv1.0,面向已废弃的 4 微前端方案)自本文件发布之日起废止,其有效内容已并入本文件 §8docs/architecture/0020_portal_shell_architecture.mdv1.0)为历史评审稿,仅作背景参考。


目录

  1. 现状审计2026-07-20 快照)
  2. 问题根因分析
  3. 目标架构 v3.0
  4. 认证与身份链
  5. 数据层架构
  6. 安全架构(安全边际)
  7. 信息架构与页面体系
  8. 前端设计规范(强制)
  9. 页面迁移总表(~140 页)
  10. 实施路线图P0P6
  11. 后续 AI 工作规范(强制)
  12. 风险登记册
  13. 附录

1. 现状审计2026-07-20 快照)

审计方法:全部结论均可复现。每项发现标注证据(文件路径 + 行号 / 可执行命令。审计环境Windows 11 + Node 24分支 feat/architecture-v2.1,工作区干净。

1.1 验收声明 vs 实测结果

README v2.0 声称"P0P4 全部验证通过、功能验收 23 项全部 [x]"。实测:构建级声明属实,功能级声明不成立——现有门禁只验证"骨架不塌",从不验证"功能可用"。

README 声明 实测结果 结论
typecheck 0 错误 node node_modules/typescript/bin/tsc --noEmit 无输出退出 属实
vitest 206/206 通过 实测 19 文件 206 用例全部通过8.12s 属实
build 6 路由生成成功 实测成功://_not-found/api/health/api/log/api/ready/shell/[[...route]] 属实——但全应用只有 1 条业务路由
"31 个内置插件全部加载正常" 31 插件全部注册并能渲染30 REAL + 1 PARTIAL无 STUB/MOCK但其 GraphQL 查询严格匹配 0/50(见 §1.3-F2运行时全部返回错误/空数据 ⚠️ 代码真实,数据全断
"31 个 widget 旧纸感令牌全部迁移1104 次替换,零违规)" src/widgets/ 仍残留 271 处旧令牌类(text-heading-*/mt-sm/py-xs/p-md 等),这些类在 Tailwind v4 主题中不存在,渲染时无样式 不属实
"M10 旧 portal 下线完成" 4 个旧 portal 完整保留在 apps/teacher 143 文件 / student 118 / parent 190 / admin 84含全部页面源码 不属实(未删 ≠ 已下线;但也未迁移)
"三层安全边界 L1/L2/L3 已落地" checkRoutePermission 仅被测试文件引用;无 middleware.tsshell/[[...route]]/page.tsx 不调用任何权限检查。L1/L2 在生产请求路径上是死代码 代码存在但从未接线
"admin 改配置 → 用户刷新生效" 无后端时 fetchPluginConfig 返回空插件集,仪表盘渲染"暂无可见插件";本地无种子数据/无 mock开发态开箱即为空壳 链路存在,起点即断
ESLint 强制插件隔离与 notify 封装(no-restricted-imports eslint.config.js 仅有 hex 颜色 + 字体名两条规则;无任何 import 限制规则 不属实

1.2 一句话诊断

portal-shell 当前是一个"质量门禁全绿、但没有任何可用页面"的空壳微内核仪表盘骨架Shell/Registry/SlotRenderer/PluginBoundary/数据层封装)工程质量合格,但它之上既没有登录认证链,也没有一个真实业务页面31 个卡片插件查询的是后端并不存在的 GraphQL 字段;距离 CICD 原版35 模块、4 端完整页面体系)的功能差距约为 140 个页面

1.3 十大断裂点(按严重度排序,证据化)

F1.【致命】没有任何真实业务页面 —— "前端不可用"的第一根因

  • 全应用仅 1 条业务路由:src/app/shell/[[...route]]/page.tsxcatch-allShellPage 不读取 route 参数src/app/shell/[[...route]]/page.tsx:26-45/shell/teacher/grades/shell/admin/users 渲染完全相同的内容;业务区分被压缩到 query params?classId/?termId 等)。
  • route-permissions.ts 声明的 24 条路由(如 /shell/admin/users/shell/teacher/lesson-plans)在文件系统中一条都不存在——权限表保护的是幽灵路由(该表从 CICD 项目平移,本意给网关 middleware 用Next.js 侧从未接线)。
  • 对照资产CICD 原版 155 个页面auth 4 + onboarding 1 + dashboard 公共 8 + admin 41 + teacher 54 + student 25 + parent 14 + management/grade 5含 register/privacy/termsEdu 旧 4 portal 共 140 个 page.tsxteacher 56 / student 36 / parent 24 / admin 24已在仓库内但从未接入 portal-shell旧 portal 本身缺 management/grade 与 register/onboarding 体系,属缺口待补)。

F2.【致命】GraphQL 契约前后端全面脱节 —— 50 个操作严格匹配 0 个

  • 前端 operations/*.graphql.ts 定义 50 个操作32 query + 18 mutationREADME 写 51其中 GetMyChildrenOverview 改名去重后为 50
  • 合并 schemasrc/lib/api/__generated__/combined-schema.graphql700 行)仅有 38 个 Query 字段,且完全没有 Mutation 类型
  • 严格匹配数0/50。唯一 root 字段名命中的是 GetLayoutTemplateslayoutTemplates,但其选择的 availableSlots 字段在 LayoutTemplateGql 上不存在,仍然校验失败。
  • 按域不匹配统计:admin 19/19、teacher 6/6、student 8/8、parent 4/4、sidebar 3/3、topbar 3/3、universal 7/7全部不匹配。典型样例:
    • 列表查询不存在:grades(classId)/homeworks(classId)/exams(classId) → 后端仅有 grade(id)/homework(id)/exam(id) 单查;schedule/attendance/announcements/myClasses/terms/myChildren/users/roles/permissions/auditLogs/invitationCodes/school/lessonPlans/schedulingRules/myLearningPath/electiveCourses/aiTutorSessions/leaveRequests/search 在 schema 中全部不存在
    • 形状不符:me{id,name,email,role} → iam User 实为 userId/email/name/status/dataScopeexamsname/subject/maxScoreExam 实为 title/totalScorenotifications(limit,offset) 期望 {items,total} 包装 → msg 实为 notifications(userId: ID!) 平铺数组;errorBookItems 底层为 snake_casequestion_id 等)。
    • 18 个 mutation 全部无契约schema 无 Mutation 类型SaveLessonPlan/ApproveLeave/RejectLeave/UpdateUserStatus/UpdateUserRole/UpdateRolePermissions/CreateInvitationCode/RevokeInvitationCode/UpdateSchool/SaveSchedulingRule/EnrollCourse/DropCourse/MarkErrorMastered/SendAiTutorMessage 及 plugin-manager 的 4 个配置 mutation。
  • config-service resolver 实测(唯一声称支撑全架构配置的后端):仅实现 plugin/plugins/layoutTemplates/userLayoutOverride/pluginConfig 5 个 Queryplugin-manager 面板依赖的 rolePluginMapping/roleLayoutDefault 及全部 update*/reset mutation 在 schema 与 resolver 双层均未实现——也就是说,即使后端全栈启动,"admin 改配置"这个插件架构的旗舰功能也无法保存
  • 唯一验证通过的查询:仪表盘启动用的 pluginConfig(不经 operations/,内联于 config-fetcher.ts:24-58PluginConfigLayoutGqlavailableSlots/layoutSchemaJson,形状吻合)——这就是"后端就绪时仪表盘能出框架、但所有卡片空转"的原因。
  • 编译期防线自毁:codegen.yml:49-56 注释自述"forward-looking spec fields … not yet present in services subgraph SDL … 44 个 'Cannot query field X' 错误",被迫 skipDocumentsValidation: true;同时 codegen 未配置 typescript-operations没有生成任何 per-operation 类型lib/api 的返回类型全部手写——编译期 0 报错,运行期全报错。
  • 后端真正可用但没有任何 widget 使用的资产data-ana 的 4 个仪表盘聚合查询(teacherDashboard/studentDashboard/parentDashboard/adminDashboard+ warnings/mastery*/diagnosticReports/errorBook* + config-service 5 个配置查询 + iam user/role/dataScope + core-edu/content 按 id 单查。
  • 无 GraphQL schema 的服务:classesstudent-bffteacher-bffparent-bffpush-gatewayapi-gateway 为 router 除外)——classes 域查询当前无处落地

F3.【致命】认证与身份链断裂 —— 没有登录页,角色写死 teacher

  • 无登录/注册页面;src/app/page.tsx 直接 redirect("/shell")
  • ShellPagex-user-id/x-user-role 请求头取身份,缺失时默认 dev-user / teacherpage.tsx:28-31)——任何人直达 portal-shell 都是"教师"。
  • Apollo Client 期望从 localStorage["edu_token"] 读 JWTApolloProvider.tsx:18-27),但全应用没有任何代码写入这个 token(没有登录流程)→ 所有 GraphQL 请求永远匿名。
  • 浏览器直连 apollo-router :3000绕过 api-gatewayJWT 校验在 gateway而 gateway 不在浏览器→router 路径上)。

F4.【严重】安全边界是"纸面合规" —— L1/L2 从未接入请求路径

  • middleware.tscheckRoutePermission/batchCheckRoutePermission 仅被 *.test.ts 引用grep 实测 60+ 处引用全部在 __tests__)。
  • 后果:/shell/admin/users 对 teacher/student/parent 角色直接放行;唯一的真实过滤发生在 config-serviceL3而它过滤的只是"仪表盘上显示哪些卡片"。
  • 权限位图 PERMISSION_BITMAP_ORDERGRADE_READ 重复定义两次permission-bitmap.ts:62:79)——按"顺序不可变"铁律属数据缺陷,趁未签发真实 JWT 前必须去重。
  • infra/docker-compose.yml 的 portal-shell 服务(含生产 profile环境变量写入 NEXT_PUBLIC_DEV_MODE: "true"——生产部署也以 dev 模式运行,等于永久绕过认证。
  • infra/apollo-router/router.yaml 硬编码 router-authorization: dev-router-secret(明文入库)。

F5.【严重】设计系统三方冲突 + 令牌断裂

  • 三个互相矛盾的"权威"docs/standards/ui-design-system.md(纸感米白 + Fraunces/Inter/JetBrains Mono 三字体 + 学术深蓝,面向已废弃的 4 微前端方案README v2.0shadcn 标准 + Inter 单字体);实际实现(packages/ui-tokens = shadcn zinc 默认色板)。
  • 31 个 widget 残留 271 处旧令牌类greptext-heading-3/mt-sm/py-xs/p-md/space-y-xs 等),这些类在 @theme没有定义 → 相关间距/字号渲染为零值widget 视觉上是坏的。
  • 另有批量出现的 border border 重复类(如 grades-widget/index.tsx:32,42)。
  • src/styles/tokens.cssREADME 附录自称"旧,待 P1 移除")仍在源码树中。

F6.【严重】31 个插件 ≈ 31 张信息卡片,不是功能

  • 全量审计结论31/3130 REAL + 1 PARTIAL0 STUB / 0 MOCK——插件代码是"真"的,问题是且数据断供。平均 139 行/个,形态为"一张卡片 + 列表/表单局部"。
  • 典型缺陷(审计实测):
    • question-bank(唯一 PARTIAL新建题目不接 mutation,仅 push 进本地 statelocal-${Date.now()}),刷新即失。
    • lesson-plan-editor:仅 标题/目标/内容/资源 四字段表单216 行);对照 CICD 同功能lesson-preparation 是全仓最大模块113 文件:结构树 + 纸面编辑器 + AI 建议 + 版本/审核/发布 + xyflow 画布);错误处理 catch { /* toast */ } 空吞(违反 notify 强制)。
    • quick-actions:导航目标 /homework/new/schedule/grades全部不存在(缺 /shell 前缀且无对应路由),点击即掉进 catch-all 仪表盘。
    • global-search:跳转目标 /students/:id 等不存在;user-menu 无登出项;locale-switcher 只切本地 state 不加载任何 i18nai-tutor 切会话不加载历史;notifications-widget 分页 offset 固定 0。
  • 对比 CICD 同功能页面量级:考试模块 11 页all/create/new/[id]/build/edit-rich/analytics/proctoring/grading、作业模块含扫描阅卷、成绩模块 5 页、教案模块 7 页。
  • 插件间无页面级导航目标(没有可跳转的详情页),"点击卡片进详情"无从谈起。

F7.【严重】开发体验不可用 —— 无后端 = 空壳

  • fetchPluginConfig 三级降级的终点是 getDefaultConfig()plugins: []config-fetcher.ts:193-212)→ 无后端/无种子数据时,仪表盘渲染空壳"暂无可见插件"。
  • 仓库内没有本地 mock/seed/MSW 任何一条让前端独立可运行的路径;旧 portal 里的 MSW handlersteacher-portal/src/mocks/handlers-p*.ts,覆盖 P4/P5/P7 场景)未被复用。

F8.【中】路由权限命名双轨制混乱

  • README 示例用点号权限(grade.read),实际 PERMISSION_BITMAP_ORDER<RESOURCE>_<ACTION> 大写下划线(GRADE_READwidget manifest 的 requiredPermissions 字段 31 个插件无一使用L2 插件级门禁名存实亡)。

F9.【中】基础设施与文档脱节

  • README §6.1 说 7 domain API 文件 "Widget 数" 与 §13.2 表格不一致topbar 4 插件 vs 2 API 的统计口径混乱,实际 admin.ts 有 20 个 hook§11.3 测试矩阵与附录 B "95 用例" 自相矛盾(实际 206
  • src/shell/Registry.tsx 头注释写"内置插件共 28 个…admin3",实际注册 31 个admin 6——代码对、注释陈旧。
  • docs/standards/ui-design-system.md 全文面向"4 个微前端 + Module Federation"——该架构已被 ADR-033 废弃。
  • MIGRATION_GUIDE.md/根 README 仍写"前端Next.js + Module Federation4 微前端)"。

F10.【低】工程细节

  • next.config.js 同时保留 Turbopack 与 webpack 双份配置(注释已说明,可接受)。
  • .env.local 入库(含本地配置,虽无密钥,但 .env.example 已存在时应 gitignore
  • tsconfig.tsbuildinfo286KB 构建缓存)入库。

1.4 可复用资产盘点(不要把婴儿和洗澡水一起倒掉)

资产 位置 状态 处置
微内核仪表盘骨架Shell/LayoutManager/SlotRenderer/Registry/PluginBoundary/PluginLifecycle/PropsMerger src/shell/ + src/shared/components/plugin-boundary.tsx 工程质量合格,测试覆盖 保留,收缩为"角色首页仪表盘"专用
数据层封装apollo-client/useWidgetQuery/useWidgetMutation/ApiError src/lib/ 合格 保留改走同域代理§5.2
lib/api 7 domain + 50 operations src/lib/api/ ⚠️ 结构合格;50 操作严格匹配 0 个,类型全部手写 保留结构,按真实 schema 逐域重写操作并恢复 codegen 全量类型§5.3
三级错误边界 + useErrorReport + notify src/shared/ + packages/hooks/ 合格 保留,扩展到页面级
权限位图 + route-permissions 表 packages/shared-ts/ + src/shared/lib/ ⚠️ 未接线 + GRADE_READ 重复 接线到 middleware,位图去重
shadcn 令牌三层 + ui-components 19 件 packages/ui-tokens/ + packages/ui-components/ 合格 保留为唯一设计系统
4 个旧 portal 的 140 个页面实现 apps/{teacher,student,parent,admin}-portal/ ⚠️ 完整但栈不同urql + next-intl + MSW + 纸感令牌) 迁移源:逐页移植 + 换数据层 + 换令牌
旧 portal 的 MSW handlersP4/P5/P7 全覆盖) apps/teacher-portal/src/mocks/ 可用 迁移为 portal-shell 开发兜底层
next-intl messageszh-CN/en 旧 portal src/messages/ 可用 随页面迁移
PQ Manifest + APQ + Router limits 安全栈 public/pq-manifest.json + infra/apollo-router/router.yaml 链路完整 保留manifest 随操作清单更新重新生成
测试基座vitest + 19 文件 206 用例) src/**/__tests__/ 合格 保留,新增页面级测试

2. 问题根因分析

为什么"质量门禁全绿"的系统会完全不可用?后续工作必须避免重蹈覆辙,四条根因:

  1. 验收指标错位:门禁只覆盖 typecheck/lint/单测/build 这些"骨架指标",从未把"页面数、契约匹配率、关键用户流 E2E"纳入验收。于是"206 测试全过"与"没有一个可用页面"同时成立。对策§10 的每个阶段退出标准必须包含功能指标 + 可复现证据。
  2. 契约逆向编写:前端 operations 按 spec 文档"超前"编写50 个操作按设计意图而非后端实现,0 个通过严格校验),后端子图只落地了 38 个只读查询、0 个 mutation。skipDocumentsValidation: true + 不生成 per-operation 类型,让这种脱节编译期不可见。对策§5.3 契约纪律——operations 只允许引用真实 schema 字段mock 数据走 MSW 而不是"假契约"。
  3. 范围误判v2.1 把"统一前端"收缩成"统一仪表盘",默认了"页面以后再说"但没有文档记录这个范围缺口README 反而把仪表盘骨架的完成写成了整个前端的完成。对策:本文件 §7/§9 把页面体系定义为 portal-shell 的一等公民职责。
  4. 文档激励扭曲:多处"已完成"声明与实测不符(令牌迁移、旧 portal 下线、安全边界),说明验收只看了 PR 描述没看运行态。对策§11.6 文档同步纪律——验收声明必须附可复现命令输出Reviewer 运行命令复核。

3. 目标架构 v3.0

3.1 设计原则

# 原则 含义
P1 页面一等公民 业务功能落在真实 Next.js 路由页面上(可寻址、可分享、可深链);插件仪表盘只承担"角色首页聚合"
P2 契约真实 前端 GraphQL 操作只允许引用后端真实 schema后端未就绪的功能用 MSW mock 数据,禁止"假契约"
P3 fail-closed 无身份 → 登录页;无权限 → 403 页;配置缺失 → 内置默认(而非空壳);错误 → 显式兜底(而非静默)
P4 单一设计系统 shadcn 标准令牌(@edu/ui-tokens为唯一视觉权威任何页面/插件不引入第二套令牌
P5 微内核收敛 Shell 插件系统收缩为仪表盘专用机制,不再承载"整个应用"的野心;新增功能默认建页面而非建插件
P6 迁移优先于重写 140 个旧页面是迁移源而非重写对象移植时换数据层urql→Apollo hooks、换令牌纸感→shadcn、保留业务交互逻辑
P7 证据化验收 每个里程碑以可复现命令 + 运行态截图/录屏验收,禁止"声明即完成"

3.2 总体结构(混合路由模型)

┌────────────────────────────────────────────────────────────────┐
│ 浏览器                                                          │
└──────────────┬─────────────────────────────────────────────────┘
               │
┌──────────────▼─────────────────────────────────────────────────┐
│ portal-shell :4010Next.js 16 App Router 单容器)              │
│                                                                │
│  middleware.ts                                            │
│   ├─ 公共路径放行(/login、/api/health、静态资源               │
│   ├─ JWT cookie 校验 → 注入 x-user-id/x-user-role/x-perms      │
│   └─ checkRoutePermissionL1 角色 + L2 权限点fail-closed   │
│                                                                │
│  /login                        登录页(新)                     │
│  /shell/forbidden              403 页(新)                     │
│  /shell ─────────────────────  ┐                               │
│   layout.tsx新框架          │  AppFrameTopBar + Sidebar    │
│    ├─ page.tsxcatch-all    │  + 导航菜单(按角色/权限过滤)  │
│    │   = 角色仪表盘            │                               │
│    │   (微内核插件区,保留)   │                               │
│    ├─ teacher/exams/page.tsx   │  真实业务页面(迁移)          │
│    ├─ teacher/exams/[id]/…    │                               │
│    ├─ student/my-grades/…     ┘                               │
│    └─ … ~140 页面                                              │
│                                                                │
│  /api/graphql            GraphQL 同域代理                 │
│   └─ 从 httpOnly cookie 取 JWT → Authorization → apollo-router │
│  /api/auth/login         登录代理:调 iam → Set-Cookie    │
│  /api/health /api/ready /api/log  保留                          │
└──────────────┬─────────────────────────────────────────────────┘
               │ 仅两条出站:
               ├─ /api/graphql → apollo-router :3000业务查询APQ
               └─ /api/v1/*    → api-gateway :8080REST认证/上传/SSE 等)

关键决策v3.0 ADR 摘要,详见 §3.4

  • V3-A1 混合路由模型:真实页面 + 保留仪表盘微内核。废除"一切皆卡片、单 catch-all 承载全站"的极端catch-all 仅保留给仪表盘(可选路由参数如 ?layout=),新页面一律走显式路由。
  • V3-A2 认证链闭环:登录页 + /api/auth/login 代理 + httpOnly cookie + middleware 校验 + /api/graphql 代理。删除 localStorage JWT 方案
  • V3-A3 门禁接线checkRoutePermission 移入 middleware.ts 真实执行;新增路由必须在 route-permissions.ts 登记CI 校验路由表与文件系统一致性)。
  • V3-A4 契约纪律operations 与真实 schema 强绑定codegen 恢复 typescript-operations 全量类型;skipDocumentsValidation 随后端补齐分域关闭§5.3)。
  • V3-A5 设计系统单源shadcn 标准zinc为唯一方向废止纸感全局方案备课编辑器二期可用模块命名空间--lp-*)局部纸感化。
  • V3-A6 接入 next-intl:复用旧 portal 的 messages页面迁移不丢失已翻译文案。
  • V3-A7 MSW 开发兜底NEXT_PUBLIC_MSW=1 时启用,前端无后端可开发/演示/跑 E2E生产构建永不包含。
  • V3-A8 验收门禁改革CI 增加契约匹配率检查、路由表一致性检查、页面计数、Playwright E2EP6

3.3 与现有架构的关系(保留什么、改变什么)

现有v2.0 v3.0 处置 理由
单 catch-all /shell/[[...route]] 承载一切 收缩:仅渲染角色仪表盘;新增显式路由优先于 catch-allNext.js 路由规则天然支持) 页面可寻址、RSC 预取、loading/error 按路由生效
LayoutManager 5 模板classic/focus/split/triple/canvas 保留为仪表盘区布局;页面区用固定 AppFrameTopBar+Sidebar+Main 模板化价值在仪表盘;页面需要稳定框架
31 个 widget 插件 保留骨架,内容逐步替换/下线:与页面功能重复的卡片改为"页面入口卡 + 关键摘要",数据接通真实 schema 卡片应该是页面的摘要与入口,不是功能的全部
config-service 三层插件配置 保留,职责收缩为"仪表盘 + 导航可见性"配置 页面不再依赖插件配置,配置挂了不影响页面
SWR 配置静默刷新 保留于仪表盘 低频配置变更无需推送
URL Search Params 跨插件共享 保留并扩展为页面间状态契约classId/childId/termId 已验证有效
RSC 流式渲染use() + Suspense 保留并推广到页面级(页面 RSC 预取 + 流式注入) 收益真实
三级错误边界 保留扩展Route页面 error.tsx→ Section页面区块→ Widget卡片 与页面体系对齐

3.4 v3.0 ADR 全文

V3-A1混合路由模型真实页面 + 仪表盘微内核)

  • 背景v2.0 用单 catch-all + 插件配置承载全站导致页面不可寻址F1、权限表空转F4、功能止步于卡片F6
  • 决策:业务功能落在显式 Next.js 路由页面(/shell/<role>/<module>/...),共享 AppFrame 布局;插件系统收缩为 /shell 角色首页的聚合仪表盘。
  • 取舍:放弃"admin 可配置一切页面"的幻想——页面是代码、受版本控制、可 code reviewadmin 可配置的收缩为"导航可见性 + 仪表盘卡片 + 卡片 props"。这换来:真实 URL、浏览器前进后退、RSC 流式预取、路由级 loading/error、中间件门禁——全部是 Next.js 原生能力,不再自造。
  • 插件与页面的关系:卡片 = 页面的摘要 + 入口。点击卡片 → router.push 到对应页面。universal 插件按 role 渲染不同摘要视图的机制保留。

V3-A2认证链闭环 + 删除 localStorage JWT

  • 背景F3无登录页、localStorage token 无人写入、默认角色 teacher
  • 决策
    1. /login 页面(迁移自旧 portal login换 shadcn 令牌)。
    2. /api/auth/login Route Handler转发凭证到 iam经 api-gateway成功后把 JWT 写入 httpOnly + Secure + SameSite=Strict cookieproject_rules §4JS 永远接触不到 token。
    3. middleware.ts:读取 cookie → 校验(开发态 decode 验签跳过,生产用 iam JWKS RS256 验签)→ 注入 x-user-id/x-user-role/x-user-permissions 请求头供 RSC 读取;无 token → redirect /login?next=<pathname>
    4. /api/graphql Route HandlerApollo Client 的 HttpLink 指向同域 /api/graphqlHandler 从 cookie 取 token 加 Authorization: Bearer 转发 apollo-router响应原样回传含 APQ hash 请求)。
  • 取舍:多一跳同域代理(<5ms 本地开销),换来 token 全程不出 httpOnly cookie——消除 XSS 窃取凭证面;同时修复"浏览器绕过 api-gateway"问题(代理在服务端调 router与 gateway 同信任域)。
  • DEV_MODE:仅本地开发;提供 dev-token 合成身份middleware 生成 x-user-* 头)。生产环境变量缺失/为 true 时启动直接拒绝(instrumentation.ts 启动校验)。

V3-A3路由门禁 middleware 接线 + 权限模型修正

  • 背景F4checkRoutePermission 死代码、F8命名双轨
  • 决策
    1. middleware.ts 对每个 /shell/** 请求执行 checkRoutePermission(pathname, bitmap, role);拒绝 → /shell/forbidden403 页,新)。
    2. route-permissions.ts 四表保留,新增/移动路由必须同步登记CI 运行一致性脚本:扫描 src/app/**/page.tsx 路由 ⇄ 权限表双向核对,未登记即失败。
    3. 权限点唯一来源 PERMISSION_BITMAP_ORDERREADME 的点号示例作废31 个 manifest 补登 requiredPermissionsL2 插件级config-service 过滤用)。
    4. GRADE_READ 重复项去重:保留首次出现位(索引 26删除第二次索引 31 处),该位标记 _RESERVED_31 占位永不复用;趁系统未签发真实 JWT现在就改P0
  • 边界说明middleware 是 L1/L2 的第一道防线(性能/体验不是唯一防线——resolver 级 @RequirePermission(后端)仍是权威。前端门禁防"走错门",后端门禁防"恶意请求"。

V3-A4GraphQL 契约纪律

  • 背景F250 操作 vs 后端 38 查询/0 mutation严格匹配 0/50
  • 决策
    1. 操作清单真实化operations/*.graphql.ts 只允许引用 combined-schema.graphql 真实存在的字段。每新增一个页面,先确认 schema 有所需字段;没有 → 走"契约工单"§11.4)推动后端补齐,同时用 MSW mock 让页面先行。
    2. codegen 恢复强校验:分域关闭 skipDocumentsValidation(哪个域后端补齐了就关哪个域),恢复 typescript-operations 生成操作级类型,删除 lib/api 手写 inline 类型(漂移源)。
    3. schema 同步自动化scripts/normalize-schema.ts 保留;新增 CI 步骤:子图 SDL 变更 → codegen diff 检查operations 引用不存在字段即失败。
    4. PQ Manifest 随构建更新prebuild 已串联,保留。
  • 后端已就绪可立即使用的资产config-service 全量配置查询data-ana 的 4 个 *Dashboard 聚合查询 + warnings/mastery*/diagnosticReports/errorBook*iam 的 user/role/dataScopecore-edu/content 的按 id 单查。仪表盘首页应优先改用这些真实查询P1 任务)。

V3-A5设计系统单源决议

  • 背景F5三方冲突 + 271 处断裂类)。
  • 决策
    1. shadcn 标准令牌(@edu/ui-tokenszinc 色板)为唯一设计系统全局适用dashboard + 全部页面 + 登录页)。
    2. docs/standards/ui-design-system.md 废止;其仍有价值的内容并入本文件 §8布局/组件/动效/a11y 规范,按 shadcn 令牌重写)。
    3. 纸感编辑器方向Fraunces 主文 + 米白纸面)降级为二期备课编辑器模块命名空间--lp-*),仅 lesson-plan 工作台内部使用;现在不做。
    4. 31 widget 令牌清债271 处)列入 P1机械替换text-heading-3text-lg font-semiboldmt-smmt-2py-xspy-1px-smpx-2space-y-xsspace-y-1p-mdp-4gap-xsgap-1border borderborderarch:scan 增加"未知 Tailwind 类"检测规则。
  • 理由CICD 是已验证的 UX 基线shadcn 生态组件、文档、AI 协作默契度)最成熟;纸感全局化在旧 portal 实践中与 shadcn 组件冲突成本高,收敛为局部模块更现实。

V3-A6next-intl 正式接入

  • 背景:旧页面用 useTranslationsmessages 资产完整portal-shell 现有自造 useT() 只有 3 条文案。
  • 决策:接入 next-intlApp Router 模式),messages/zh-CN.json + messages/en.json 从旧 portal 合并迁移;ThemeI18nProvider 删除自造 i18n保留主题切换新代码文案一律走 useTranslations禁止硬编码中文字符串ESLint 规则 P1 后启用,迁移期 warn
  • 取舍:增加一个依赖与少量配置,换取不重写全部文案 + 与 CICD/旧 portal 一致的 i18n 习惯。

V3-A7MSW 开发兜底层

  • 背景F7无后端 = 空壳)。
  • 决策:迁移旧 portal 的 MSW handlers 为 portal-shell 的 src/mocks/(按 domain 分文件);NEXT_PUBLIC_MSW=1 时 browser worker 拦截 /api/graphql/api/v1/*src/instrumentation.ts(或 middleware 旁路)在 dev + MSW 开启时给 RSC 侧也注入 mock fetcher。生产构建通过 env 条件 import 保证 mocks 零字节进 bundle
  • 双重收益前端不依赖后端就绪即可开发页面Playwright E2E 直接复用 MSW 场景数据,测试稳定。

V3-A8验收门禁改革

  • 背景:根因 1验收指标错位
  • 决策CI 质量门禁从 4 项扩为 8 项:lint + typecheck + vitest + build + 契约校验codegen diff+ 路由表一致性 + 页面计数回归pages 数不得下降)+ E2E 冒烟P6 起:登录 → 仪表盘 → 每角色 1 条核心流)。每个阶段的 README/本文件验收勾选必须附命令输出或截图链接

4. 认证与身份链

4.1 目标流程

sequenceDiagram
    participant U as 浏览器
    participant PS as portal-shell
    participant GW as api-gateway :8080
    participant IAM as iam
    participant RT as apollo-router :3000

    U->>PS: POST /api/auth/login {email, password}
    PS->>GW: POST /api/v1/iam/auth/login
    GW->>IAM: 验证凭证
    IAM-->>GW: JWT (RS256) + user + permissions bitmap
    GW-->>PS: 200 {token, user}
    PS-->>U: Set-Cookie: edu_session=<JWT>; httpOnly; Secure; SameSite=Strict
    Note over U: JS 永远拿不到 token

    U->>PS: GET /shell/teacher/exams
    PS->>PS: middleware: 校验 cookie → 注入 x-user-* 头 → checkRoutePermission
    alt 无/坏 token
        PS-->>U: 302 /login?next=/shell/teacher/exams
    else 无权限
        PS-->>U: 302 /shell/forbidden
    else 放行
        PS->>PS: RSC 读 x-user-* 头渲染页面
    end

    U->>PS: POST /api/graphql (Apollo, APQ hash)
    PS->>PS: 从 cookie 取 token
    PS->>RT: POST /graphql + Authorization: Bearer <JWT>
    RT-->>PS: data
    PS-->>U: data

4.2 组件清单

组件 文件 职责
登录页 src/app/login/page.tsx 表单 → /api/auth/login;已登录跳转 next迁移自旧 portal换 shadcn 令牌
登录代理 src/app/api/auth/login/route.ts 调 gateway iam 登录 → Set-Cookie失败归一化错误限流透传
登出 src/app/api/auth/logout/route.ts 清 cookie + 调 iam 注销
中间件 src/middleware.ts 公共路径白名单 → cookie 校验 → 注入身份头 → checkRoutePermission → 重定向
GraphQL 代理 src/app/api/graphql/route.ts cookie→Bearer 转发APQ 透传;错误归一化;cache: "no-store"
RSC 身份工具 src/lib/auth/server.ts getServerIdentity(): Promise<{userId, role, permissions}>(读 middleware 注入的头,缺失即抛 → error boundary 跳登录)
DEV_MODE 守卫 src/instrumentation.ts NODE_ENV=production && NEXT_PUBLIC_DEV_MODE=true → 启动即抛错
403 页 src/app/shell/forbidden/page.tsx 说明 + 返回仪表盘链接

4.3 会话细节决策

  • cookie 名edu_sessionPath=/Max-Age 与 JWT exp 对齐;Secure 仅生产(本地 http 开发豁免,用 env 控制)。
  • 验签middleware 用 josecreateRemoteJWKSet(iam JWKS endpoint) 验 RS256JWKS 端点不可达时 fail-closed跳登录dev 模式降级为仅 decode日志警告
  • 权限位图来源:登录响应中 iam 返回 permissions → login route 计算 base36 位图 → 写入第二个非 httpOnly cookie edu_permsJS 可读,用于客户端按钮级显隐;仅为 UX不作为安全依据——安全判断一律走后端 resolver + middleware 的 httpOnly 通道。middleware 同样把位图注入 x-user-permissions 头。
  • 角色切换/多角色iam 返回单主角色(与现 x-user-role 对齐);多角色支持属后端二期,前端不预留。
  • 旧 portal 的 lib/auth.tslocalStorage 方案):迁移时删除,不按原样搬运。

5. 数据层架构

5.1 分层(保留 v2.0 四层,修正两端)

页面/插件UI
  → lib/api/<domain>.ts语义化 hooksuseExams(classId)
  → lib/api/operations/*.graphql.tsgql 文档,真实 schema 子集)
  → lib/useWidgetQuery / useWidgetMutationApollo 封装 + ApiError
  → Apollo ClientAPQ link→ /api/graphql同域代理
  → apollo-router :3000 → 子图
  • RSC 服务端:页面 RSC 用 createApolloClient()(已有)经内网直连 router服务端不经代理直接带 middleware 解析出的身份头);客户端组件经 /api/graphql 代理。
  • SWR/轮询:仪表盘配置保留 SWR业务数据默认 Apollo fetchPolicy: "cache-first",需要实时性的(通知铃铛)用 pollIntervalSSE 通知二期经 realtime-gateway。

5.2 GraphQL 同域代理(/api/graphql

  • 单文件 Route HandlerPOST only透传 bodyAPQ hash 或 query注入 Authorization;响应状态/JSON 原样返回。
  • 不缓存(export const dynamic = "force-dynamic")。
  • 错误归一化:网络错误 → { errors: [{ message: "UPSTREAM_UNAVAILABLE" }] },客户端 ApiError 统一。
  • 保留直连开关:APOLLO_ROUTER_URL(服务端 RSC 用),客户端永远只用 /api/graphql

5.3 契约纪律frontend ⇄ backend

  1. schema 唯一源src/lib/api/__generated__/combined-schema.graphqlscripts/normalize-schema.tsservices/*/src/graphql/generated/schema.graphql 生成;后端 SDL 变更后必须重跑 codegenCI 做 diff 检查。
  2. operations 真实性:每个 operation 引用字段必须存在于 combined schema恢复 typescript-operations 生成类型lib/api 的手写 interface 逐步删除P1 起按域推进config → data-ana → core-edu → content → msg → iam
  3. 后端未就绪的功能:页面允许先上,数据走 MSWsrc/mocks/),并在页面头部注释 @contract-pending: <工单号>契约工单§11.4)登记后端待补字段,每周同步一次状态
  4. 禁止事项:禁止在 widget/页面内联 gql禁止手写与 schema 冲突的类型;禁止为迁就前端瞎改 normalize 脚本(当前对 ai 子图的 String 改写保留,单独登记 TD

5.4 MSW 兜底层

  • 位置:src/mocks/browser.ts/server.ts/handlers/<domain>.ts),自旧 portal src/mocks/handlers-p*.ts 迁移并按 50+ 操作重组。
  • 启用:NEXT_PUBLIC_MSW=1(仅 dev/testsrc/app/layout.tsx 条件 import("@/mocks/browser")(动态 importproduction env 下该 import 永不执行 → tree-shaken
  • 数据原则:使用与种子用户一致的语义数据(teacher2@edu.test 的班级/学生/成绩),数据量足够展示分页/空态/超长文本三种边界。
  • E2E 复用Playwright webServer 以 NEXT_PUBLIC_MSW=1 启动,用例不依赖真实后端。

5.5 后端已就绪查询的立即利用P1 必做)

真实查询schema 存在且 resolver 已实现) 用于 注意
pluginConfig(userId, role) / plugins / plugin(pluginId) / userLayoutOverride(userId) 仪表盘配置(已接,保留) 全字段吻合
layoutTemplates admin 模板列表 ⚠️layoutId/displayName/description/isActiveavailableSlots——前端选择集必须裁剪
teacherDashboard / studentDashboard / parentDashboard / adminDashboard 角色首页仪表盘主数据源(替换现有卡片的假契约查询) ,底层字段 snake_case 需在 lib/api 层映射
warnings / masterySummary / studentMastery / masteryDistribution 学情预警卡片 ,同上
diagnosticReports / errorBookItems / errorBookStats 诊断/错题卡片与页面 ,字段形状与现 widget 期望不同,需重写 hook
user(userId) / role(roleId) / dataScope(userId) 用户菜单、权限显隐 ⚠️ User 字段为 userId/email/name/status/dataScope(无 id/role
exam(id) / homework(id) / grade(id) / classInfo(id) / textbook(id) / question(id) / chapter(id) / knowledgePoint(id) 详情页首版(列表点击带 id 进入时可真实查询) ⚠️ 字段名需按真实类型重写(如 Exam.title/totalScore 而非 name/maxScore
notifications(userId) 通知中心首版 ⚠️ 平铺数组,无分页包装
lessonPlanStatus ai 子图唯一查询 仅状态轮询可用

6. 安全架构(安全边际)

6.1 威胁模型与防线矩阵

威胁 防线 位置 现状 v3.0 处置
未认证访问业务页 middleware cookie 校验 src/middleware.ts 不存在 P0 实现fail-closed
越权访问路由teacher 进 admin 页) L1 角色 + L2 权限点 middleware + route-permissions 表 死代码 P0 接线 + CI 一致性
越权访问数据 resolver @RequirePermission + DataScope 后端子图 (审计过 50 resolver 保持;前端不兜底也不替代
凭证被 XSS 窃取 httpOnly cookieJS 零接触 /api/auth/login localStorage 方案 P0 替换
GraphQL 任意查询探测 APQ + PQ Manifest 白名单 Apollo link + router 链路完整(require_manifest 生产需开) 保持;部署 env 列入 P6 检查单
深度/成本/批量 DoS router limits router.yaml 已配 保持
绕过 router 直调子图 RouterAuthGuard + router-authorization 子图 ⚠️ 密钥明文 dev-router-secret 入库 P6 换 env 注入;生产 secret 轮换
CSRF SameSite=Strict cookie + 自定义头要求 cookie + 代理 ⚠️ 依赖 cookie 属性 /api/graphql 要求 content-type: application/json(预检),登录接口 SameSite=Strict
生产 DEV_MODE 误开 启动自检 instrumentation.ts compose 生产 profile 写 true P0compose 改 false + 启动校验抛错
错误上报刷爆 sessionStorage digest 节流 useErrorReport 已实现 保持
XSS 富文本 dangerouslySetInnerHTMLgrep 实测 src 内 0 处) ESLint + 规范 保持规则;富文本编辑器(二期)必须 DOMPurify
敏感信息入仓 .env.local 已入库(无密钥) 仓库 ⚠️ P0移出 + gitignore 补规则

6.2 三层安全边界(接线后真实形态)

机制 执行点 失败行为
L1 角色门禁 requiredRoles middleware(每个 /shell/** 请求) 302 → /shell/forbidden
L2 权限点 requiredPermissions/anyOfPermissionsAND/OR位图校验 middleware(路由级)+ config-service卡片级 302 → 403 页 / 卡片不渲染
L3 数据范围 DataScope 6 级 后端 resolver权威前端仅 UX 显隐 后端拒绝 / 空态

边界纪律(防"安全边际"幻觉)

  1. 前端门禁是体验层,后端 resolver 是权威层;任何"前端已挡"不得成为后端不加 @RequirePermission 的理由。
  2. edu_perms 可读 cookie 只用于按钮显隐;禁止用它做任何数据请求的放行判断。
  3. middleware 校验失败一律 fail-closedcheckRoutePermission 未匹配的路由默认拒绝当前实现默认放行P0 改为:/shell/** 下未登记路由默认拒绝,公共路径显式白名单)。
  4. JWT 过期统一处理:/api/graphql 收到 401 → 清 cookie → 返回 { errors: [{extensions:{code:"UNAUTHENTICATED"}}] };客户端 Apollo errorLink 拦截 → location.href = "/login"

7. 信息架构与页面体系

7.1 路由树(目标态)

/login                          登录
/shell/forbidden                403
/shell                          角色仪表盘(按 x-user-role 分发渲染对应仪表盘)
/shell/teacher/…                教师功能区(~50 页,见 §9.1
/shell/student/…                学生功能区(~34 页,见 §9.2
/shell/parent/…                 家长功能区(~21 页,见 §9.3
/shell/admin/…                  管理功能区(~22 页,见 §9.4
/shell/settings                 通用设置(全角色)
/shell/notifications            通知中心(全角色)
  • URL 前缀 /shell/<role>/ 与 route-permissions 表语义对齐PREFIX 表按前缀批量保护)。
  • 仪表盘 /shell 按角色渲染不同插件集(现有机制),同时是各功能卡的入口枢纽。
  • 跨角色同构页面(如 notifications/settings放共享路径权限表按角色开放。

7.2 AppFrame页面框架

src/app/shell/layout.tsx(新):

┌──────────────────────────────────────────────┐
│ TopBarglobal-search · notification-bell    │
│         locale-switcher · user-menu          │
├──────────┬───────────────────────────────────┤
│ Sidebar  │ Main{children}                │
│ 导航菜单  │  页面区(显式路由页面)           │
│ (按角色/ │  或仪表盘(/shell               │
│  权限过滤)│                                  │
│ + 上下文  │                                  │
│ 选择器    │                                  │
└──────────┴───────────────────────────────────┘
  • TopBar:复用现有 4 个 topbar 插件(它们本就是全站级组件),不再走插件配置(改为静态挂载 + 权限显隐),仪表盘配置不再能"关掉导航"导致页面失联。
  • Sidebar 导航菜单(新组件):导航项 = 静态注册表 src/shared/lib/navigation.tslabel/i18n key/icon/href/requiredRoles/requiredPermissions渲染前用 batchCheckRoutePermission 过滤(对应 004 §5.4 视口 L1当前路由高亮收纳现有 4 个 sidebar 插件class-selector/child-selector/term-switcher/quick-actions于菜单下方"上下文区"。
  • Main:仪表盘路由渲染微内核仪表盘;业务路由渲染页面。页面内可用 DashboardSection/SectionErrorBoundary 组织区块。
  • RSC 流式layout 保持轻量(导航静态),页面各自 RSC 预取 + Suspense现有 ClientShell 流式机制迁移为页面级工具(src/lib/streaming.tsx 导出 StreamedPage 封装,避免每页重写 Provider/Suspense 样板)。

7.3 页面四种类型与模板(新页面必须套模板)

类型 适用 结构骨架 示例
列表页 集合浏览 + 筛选 + 分页 PageHeader + FilterBar + DataTable/列表 + Pagination + 空态/骨架/错误三态 exams、homework、users
详情页 单实体查看 + 关联操作 PageHeader标题+操作) + 信息区 + Tabs/分区 + 关联列表 exams/[id]、textbooks/[id]
表单页 创建/编辑 PageHeader + 表单react-hook-form + zod+ 提交/取消 + 错误摘要 exams/new、lesson-plans/new
工作台页 多栏协同复杂任务 三栏(树/画布/属性复合组件Workbench 模式) lesson-plans/[id]/edit、exams/[id]/build

模板代码位置:src/shared/components/page-templates/{list-page,detail-page,form-page,workbench-page}.tsxP1 建)。新页面一律 import 模板而非手排布局——保证 140 页视觉/交互一致,也是 AI 批量迁移时的统一落点。

7.4 页面级数据获取模式(统一)

// src/app/shell/teacher/exams/page.tsxRSC 示例骨架)
export default async function ExamsPage(): Promise<React.ReactElement> {
  const { userId, role } = await getServerIdentity();
  const client = createApolloClient();
  const dataPromise = client.query({
    query: GET_EXAMS_DOC,
    variables: {/* … */},
  });
  return (
    <ListPageShell titleKey="exams.title">
      {/* Promise 直传客户端组件use() 消费(沿用现有流式模式) */}
      <ExamsList dataPromise={dataPromise} />
    </ListPageShell>
  );
}
  • 列表/详情页RSC 预取首屏 → use() 流式注入 → 客户端交互(筛选/翻页)走 Apollo hooks。
  • 表单页:客户端组件 + useWidgetMutation;成功后 router.refresh()
  • 每页必须实现三态:loading.tsx(骨架,复用 PluginSkeleton 变体)/ error.tsxRoute 级边界,已有模式)/ 空态(empty-state.tsx)。

8. 前端设计规范(强制)

本章是 portal-shell 唯一的视觉/交互权威,废止 docs/standards/ui-design-system.md。所有新页面/插件/组件必须遵守;迁移页面时按本章改造。违反本节任意"铁律"的 PR 不得合入。

8.1 设计令牌(三层,唯一来源)

位置 业务可用性
L1 Primitive packages/ui-tokens/src/primitive.css 禁止直接引用
L2 Semantic packages/ui-tokens/src/semantic-{light,dark}.css hsl(var(--*))
L3 Tailwind packages/ui-tokens/src/tailwind-theme.css@theme inline 首选:bg-background/text-foreground/bg-card/border

铁律ESLint + code review 双重执行)

  1. #hex 字面量(现有规则保留)。
  2. 禁字体名字面量(现有规则保留)。
  3. font-size: Npx;字号用 Tailwind 阶梯(text-xs/sm/base/lg/xl/2xl)或 text-size-*
  4. 禁 Tailwind 任意值(w-[137px]);间距用默认阶梯(p-2/p-4/p-6/gap-2/gap-4)或 --space-*
  5. 禁引入第二套令牌/第二套 CSS 变量体系(纸感 --paper-*/--ink-* 一律不得回潮;迁移旧页面时必须清除)。
  6. 暗色主题:仅经 .dark class + 语义令牌响应;禁组件内写死亮色值。
  7. 圆角:rounded-xl(卡)/rounded-md(控件)/rounded-full(头像徽章);阴影克制:浮层 shadow-sm,卡片默认无阴影。

8.2 字体与排版

  • 单一字体族 Internext/font 已挂载 --font-interfont-sans);数字密集表格列用 font-monoTailwind 默认等宽)右对齐。
  • 标题层级:页面标题 text-2xl font-semibold;区块标题 text-lg font-semibold;卡片标题 text-base font-medium;正文 text-sm;辅助 text-xs text-muted-foreground
  • 行高:正文 leading-relaxed;标题 leading-tight
  • 中文界面默认 lang="zh-CN"已配置i18n 文案经 next-intl。

8.3 组件使用

  • 基础组件一律来自 src/shared/components/ui/*shadcn 标准件)与 packages/ui-components/*data-table/form/modal/chart/calendar 等 19 件);缺组件先扩库再使用,禁止页面内手写第三套按钮/输入框
  • 业务复合件:page-templates 四件套§7.3+ page-header/stat-card/stats-grid/empty-state/filter-bar(已有)。
  • Toast 一律 notify.*src/shared/lib/notify.ts import { toast } from "sonner"P0 补 ESLint no-restricted-imports 真正落地);禁空 catch(现有 catch { /* toast */ } 反例必须改)。
  • 图标:lucide-react 单一来源;图标按钮必须 aria-label
  • 表格:数据列数字 font-mono 右对齐;行分隔 divide-y divide-border;禁大面积色块。

8.4 布局与间距

  • 页面容器:px-6 py-6(桌面),最大宽度不设限(工作台型产品),列表页可 max-w-7xl
  • 区块间距 space-y-6;卡片内边距 p-4p-6
  • 三栏工作台仅在"编辑类"页面使用(备课/组卷),比例 260px / 1fr / 380pxWorkbench 模板内置)。

8.5 动效

  • 交互反馈 ≤ 200ms页面过渡 ≤ 300ms遵守 prefers-reduced-motionglobals.css 已有)。
  • 加载一律骨架屏PluginSkeleton 五变体 / 页面 loading.tsx禁居中 spinner 长转
  • 禁装饰性持续动画hover 反馈用 bg-muted/bg-accent,禁 scale-105

8.6 可访问性WCAG AA

  • 对比度:正文 ≥ 4.5:1zinc 令牌天然满足,禁自调浅色)。
  • 所有交互元素可 Tab 聚焦;focus-visible:ring-2 focus-visible:ring-ring
  • 语义化标签(nav/main/aside/table);表单控件必须 <label>aria-label;动态更新区 aria-live="polite"(骨架组件已带,页面复用即可)。
  • 右键/下拉菜单支持键盘Radix 组件默认满足,勿破坏)。

8.7 文案与 i18n

  • 一律 useTranslations;命名空间按页面域(exams.*/homework.*/common.*)。
  • 占位/空态文案必须给出"下一步行动"(如"暂无考试 → 新建考试"按钮),禁裸"暂无数据"。

9. 页面迁移总表(~140 页)

功能全景基准CICD 原版 155 页Next.js 16 + Server Actions + Drizzle无 GraphQLauth 4 + onboarding 1 + dashboard 公共 8 + admin 41 + teacher 54 + student 25 + parent 14 + management/grade 5。 迁移源:仓库内 4 个旧 portalapps/*-portal/,共 140 个 page.tsx其页面实现完整但栈为 urql + next-intl + MSW + 纸感令牌。 缺口说明:旧 portal 未覆盖 CICD 的 management/grade 年级组维度5 页)与 register/privacy/terms/onboarding4+ 页)——这两块列为新增N,排在对应批次末尾。 迁移动作统一定义:M = 迁移改造(移植交互逻辑 + 换 Apollo 数据层 + 换 shadcn 令牌 + 套页面模板);N = 新建(旧版没有);R = 替换(现有 widget 升级为页面)。 批次即 §10 路线图的阶段B1=先行骨架P1、B2=教师P2、B3=学生P3、B4=家长P4、B5=管理P5。 契约列:=schema 已有(注意字段形状差异,见 §5.5🟡=部分(单查有/列表无);=schema 无,需契约工单 + MSW 先行。

9.1 教师域teacher-portal 56 页 → /shell/teacher/*B1+B2

源路由teacher-portal 目标路由 动作 契约 批次
login /login M全站唯一登录页 B1
(app)/dashboard /shell(教师仪表盘) R仪表盘改接 teacherDashboard 真实查询) B1
(app)/exams /shell/teacher/exams M 列表页 🟡 B2
(app)/exams/new /shell/teacher/exams/new M 表单页 mutation B2
(app)/exams/[id] /shell/teacher/exams/[id] M 详情页 exam(id) B2
(app)/exams/[id]/build /shell/teacher/exams/[id]/build M 工作台页 B2
(app)/exams/[id]/edit-rich /shell/teacher/exams/[id]/edit M 工作台页 B2
(app)/exams/[id]/analytics /shell/teacher/exams/[id]/analytics M 详情页(图表) 🟡 assignmentAnalysis B2
(app)/exams/[id]/proctoring /shell/teacher/exams/[id]/proctoring M二期WS B2 末
(app)/homework/new/[id]/submissions(共 7 页) /shell/teacher/homework/*(同构 7 页) M 🟡/ B2
(app)/grades/entry/analytics/stats/report-card5 页) /shell/teacher/grades/*5 页) M 🟡/ B2
(app)/lesson-plans/new/library/calendar/heatmap/[planId]/edit6 页) /shell/teacher/lesson-plans/*6 页) Medit 为工作台页) B2
(app)/questions /shell/teacher/questions M 列表页 🟡 question(id) B2
(app)/textbooks/[id]2 页) /shell/teacher/textbooks/*2 页) M 🟡 textbook(id) B2
(app)/attendance/sheet/report/stats4 页) /shell/teacher/attendance/*4 页) M B2
(app)/classes/[id]/schedule3 页) /shell/teacher/classes/*3 页) M 🟡 classInfo(id) B2
(app)/students /shell/teacher/students M B2
(app)/course-plans/[id]2 页) /shell/teacher/course-plans/*2 页) M B2
(app)/elective/create/[id]/edit3 页) /shell/teacher/elective/*3 页) M B2
(app)/error-book /shell/teacher/error-book M errorBookItems/Stats B2
(app)/diagnostic/class/[classId]2 页) /shell/teacher/diagnostic/*2 页) M diagnosticReports B2
(app)/analytics/[studentId]2 页) /shell/teacher/analytics/*2 页) M 🟡 data-ana 多查询 B2
(app)/ai-assist/ai-lesson-plan/ai-report3 页) /shell/teacher/ai/*3 页) M ai 子图仅 lessonPlanStatus B2 末
(app)/knowledge-graph /shell/teacher/knowledge-graph M 🟡 knowledgePoint(id) B2 末
(app)/practice /shell/teacher/practice M B2
(app)/schedule-changes /shell/teacher/schedule-changes M B2
(app)/leave /shell/teacher/leave M B2
(app)/notifications /shell/notifications(共享) M notifications(userId) B1
(app)/settings /shell/settings(共享) M B1

9.2 学生域student-portal 36 页 → /shell/student/*B3

源路由 目标路由 动作 契约 批次
dashboard/trend/weakness /shell(学生仪表盘)+ 2 详情页 R + M studentDashboard/learningTrend/studentWeakness B3
my-grades/report-card /shell/student/grades/*2 页) M (列表) B3
my-exams/[id]/result/[id]/take /shell/student/exams/*3 页) Mtake 为作答工作台) 🟡/ B3
my-homework/[id]/submit/[id]/analysis /shell/student/homework/*3 页) M 🟡/ B3
schedule /shell/student/schedule M B3
my-attendance /shell/student/attendance M B3
my-classes /shell/student/classes M myClasses B3
courses/[id] /shell/student/courses/*2 页) M B3
course-plans/[id] /shell/student/course-plans/*2 页) M B3
lesson-plans/[id]/view /shell/student/lesson-plans/*2 页) M B3
textbooks/[id]/chapters /shell/student/textbooks/*2 页) M 🟡 B3
error-book /shell/student/error-book M B3
learninglearning-path /shell/student/learning/learning-path M B3
practice/[sessionId] /shell/student/practice/*2 页) M B3
elective/[id] /shell/student/elective/*2 页) M B3
ai-tutor /shell/student/ai-tutor M B3 末
announcements/[id] /shell/announcements/*(共享 2 页) M (列表) B3
messages /shell/messages M B3 末
notificationssettings 共享路由 M B1/B3
leave /shell/student/leave M B3

9.3 家长域parent-portal 24 页 → /shell/parent/*B4

源路由 目标路由 动作 契约 批次
parent/dashboard/trend/weakness /shell(家长仪表盘)+ 2 页 R + M parentDashboard B4
parent/children/[studentId] /shell/parent/children/[studentId] M myChildren B4
parent/grades/report-card /shell/parent/grades/*2 页) M B4
parent/exams/[id]/result /shell/parent/exams/*2 页) M 🟡/ B4
parent/homework /shell/parent/homework M B4
parent/attendance /shell/parent/attendance M B4
parent/classes /shell/parent/classes M B4
parent/course-plans/[id] /shell/parent/course-plans/*2 页) M B4
parent/lesson-plans/[planId]/view /shell/parent/lesson-plans/*2 页) M B4
parent/error-book /shell/parent/error-book M B4
parent/diagnostic /shell/parent/diagnostic M B4
parent/learning-path /shell/parent/learning-path M B4
parent/practice /shell/parent/practice M B4
parent/elective /shell/parent/elective M B4
parent/leave /shell/parent/leave M B4
parent/notifications/settings/preferences 共享 + /shell/parent/preferences M / B4

9.4 管理域admin-portal 24 页 → /shell/admin/*B5

源路由 目标路由 动作 契约 批次
admin/dashboard /shell(管理仪表盘) R adminDashboard B5
admin/users /shell/admin/users R现有 widget 升级整页) users 列表 B5
admin/rolesadmin/permissions /shell/admin/roles/permissions2 页) R/M B5
admin/audit-logs + 3 子页 /shell/admin/audit-logs/*4 页) M B5
admin/invitation-codes /shell/admin/invitation-codes R B5
admin/school + 5 子页 /shell/admin/school/*6 页) M B5
admin/classes /shell/admin/classes M B5
admin/studentsadmin/teachers /shell/admin/students/teachers2 页) M B5
admin/organization /shell/admin/organization M B5
admin/announcements /shell/admin/announcements M B5
admin/files /shell/admin/files M B5
admin/ai-settings /shell/admin/ai-settings M B5
admin/system /shell/admin/system M B5
admin/viewports /shell/admin/viewports M对齐 004 §5.4 视口配置) B5
—(新)插件管理 /shell/admin/plugins R现有 plugin-manager 升级整页) config-service B5

9.5 计数与节奏

页面数 契约已就绪(/🟡 批次
共享login/notifications/settings/announcements/messages/forbidden ~7 4 B1/B3
教师 ~50 ~14 B1+B2
学生 ~34 ~8 B3
家长 ~21 ~5 B4
管理 ~22 ~3 B5
缺口新增management/grade 5 页 + register/onboarding/privacy/terms ~5 页CICD 有而旧 portal 无) ~10 0 B2/B5 末N
合计 ~144 ~34

节奏原则:契约就绪页先行; 页用 MSW 先上 UI契约工单跟踪后端补齐后切换真实查询切换 = 改 hook 的 fetcher 指向,页面不动)。


10. 实施路线图P0P6

每阶段列出:目标 / 范围 / 验收标准(可复现命令 + 运行态证据)。阶段内任务按 §11 规范拆给多 AI 并行。未完成上一阶段退出标准不得进入下一阶段。

P0 · 地基止血1 周)— 已完成2026-07-22 验收)

目标:消除"空壳即不可用 + 认证裸奔"两大致命伤。

# 任务 验收 状态
P0-1 /login 页 + `/api/auth/login logout+edu_session` httpOnly cookie 本地起 iam/gateway错误密码报错、正确密码进 /shellcookie 在 DevTools Application 可见且 httpOnly
P0-2 src/middleware.tscookie 校验 + 身份头注入 + checkRoutePermission 接线 + /shell/forbidden 无 cookie 访问 /shell/** → 302 /loginstudent 访问 /shell/admin/users → 302 /shell/forbidden截图
P0-3 /api/graphql 代理 + Apollo Client 指向同域 + 删除 localStorage token 方案 DevTools Network 面板无 localhost:3000 直连;grep -r "localStorage" src/providers 无 token 读取
P0-4 配置兜底改进:getDefaultConfig() 返回内置默认仪表盘插件集按角色静态定义config-service 不可用时仪表盘仍有内容 停掉 config-service 与 router启动前端/shell 显示默认卡片而非"暂无可见插件"(截图)
P0-5 DEV_MODE 生产守卫:instrumentation.ts 启动校验 + docker-compose.yml 生产 profile 改 NEXT_PUBLIC_DEV_MODE: "false" NODE_ENV=production NEXT_PUBLIC_DEV_MODE=true pnpm start 启动即报错退出
P0-6 权限位图修正:GRADE_READ 去重(保留索引位,二见位改 _RESERVED_+ 单测更新 vitest run permission-bitmap 通过;validateRoutePermissionConfigs() 返回空
P0-7 ESLint 补齐:no-restricted-imports(禁 widget 跨目录 import、禁直 import sonner、禁页面绕过 lib/api 直 import @apollo/client pnpm lint 对预埋反例报错(附输出)
P0-8 仓库卫生:.env.local 移出版本控制 + .gitignoretsconfig.tsbuildinfo git status 干净

P0 验收证据2026-07-22

P1 · 框架与数据源接通12 周)— 进行中P1-1/P1-2/P1-3/P1-4/P1-5/P1-6 2026-07-22 验收)

目标AppFrame + 导航 + 真实数据仪表盘 + 页面模板 + MSW + i18n页面迁移的"流水线"建成。

# 任务 验收 状态
P1-1 src/app/shell/layout.tsx AppFrameTopBar 静态挂载 + Sidebar 导航菜单 navigation.ts + 权限过滤) 4 角色各见各菜单;导航项与 route-permissions 表一致(单测)
P1-2 仪表盘改接真实查询:teacherDashboard/studentDashboard/parentDashboard/adminDashboard + warnings/errorBookStats 起全栈4 角色仪表盘显示真实聚合数据截图widget 假契约查询grades/homeworks/schedule/attendance/exams/announcements全部下线或改造
P1-3 页面模板四件套list/detail/form/workbench+ 三态规范 Storybook 或示例页 4 张(/shell/dev/templates/*,仅 dev 可见Next.js 私有文件夹 _xxx 不参与路由,故使用 dev 而非 _dev
P1-4 next-intl 接入 + messages 合并迁移 切换 locale 页面文案切换(截图);useT 自造函数删除
P1-5 MSW 兜底层(迁移旧 handlers覆盖 dashboard/users/exams/grades 四域起步) NEXT_PUBLIC_MSW=1 无后端启动,仪表盘 + users 页有数据(截图);生产构建 bundle 无 mocks
P1-6 31 widget 令牌清债271 处机械替换)+ border border 去重 + 未知类检测进 arch:scan grep -c "text-heading-|mt-sm|py-xs|p-md" src/widgets = 0pnpm lint:tokens 通过
P1-7 codegen 恢复 typescript-operationsconfig + data-ana 两域先行关闭 skipDocumentsValidation 生成操作级类型lib/api 对应域删除手写 interfacetypecheck 通过
P1-8 CI 增补:路由表一致性脚本 + 页面计数 + codegen diff 检查 CI 对预埋违规报红(附 pipeline 链接)

P1-1 验收证据2026-07-22

  • 单测vitest run --reporter=verbose navigation → 7/7 passed
    • 每个导航项 href 在 EXACT/PREFIX/DASHBOARD 表登记
    • teacher/student/parent/admin 各仅见本组导航项
    • 4 角色导航项两两不交叉(角色隔离)
    • admin 无权限位图时 checkRoutePermission 拒绝
  • 实现文件
  • 质量校验tsc --noEmit 通过;eslint5 文件)通过

P1-2 验收证据2026-07-22

  • 假契约查询全部下线grep -rn "useGrades\|useHomework\|useSchedule\|useAttendance\|useExams\|useAnnouncements" src 无业务调用6 个 widgetgrades/homework/schedule/attendance/exams/announcements改为"数据源已迁移"占位 Card
  • 6 个真实聚合查询接入
    • src/lib/api/operations/dashboard.graphql.tsGET_TEACHER_DASHBOARD_DOC / GET_STUDENT_DASHBOARD_DOC / GET_PARENT_DASHBOARD_DOC / GET_ADMIN_DASHBOARD_DOC / GET_WARNINGS_DOC / GET_ERROR_BOOK_STATS_DOC,字段全部 snake_case 对齐 data-ana 子图
    • src/lib/api/dashboard.ts6 个 hooksuseTeacherDashboard / useStudentDashboard / useParentDashboard / useAdminDashboard / useWarnings / useErrorBookStats+ 全部领域模型类型(TeacherDashboard / StudentDashboard / ParentDashboard / AdminDashboard / WarningInfo / ErrorBookStats 等)
  • 4 角色仪表盘页面(显式路由,先于 catch-all 匹配):
    • src/app/shell/teacher/page.tsxStatCard×4班级总数/学生总数/班级平均分/待批作业)+ DashboardSection×2班级概况/近期预警)
    • src/app/shell/student/page.tsxStatCard×3平均分/班级排名/待交作业)+ DashboardSection×2薄弱知识点/近期成绩趋势)
    • src/app/shell/parent/page.tsxStatCard×2孩子平均分/班级排名)+ DashboardSection×2薄弱知识点/预警通知)
    • src/app/shell/admin/page.tsxStatCard×4教师总数/学生总数/班级总数/全校平均分)+ DashboardSection×2近期预警/AI 用量)
  • 混合路由模型V3-A1src/app/shell/[[...route]]/page.tsx 新增 params 异步读取 + /shell 空路由 redirect(\/shell/${role}`)` 兜底
  • 三态规范4 个仪表盘页统一遵循 loadingStatCard isLoading 骨架)→ errorCard 错误提示)→ success真实数据三态
  • 质量校验tsc --noEmit 通过;eslint(新/改文件)通过;vitest run 全量 20 test files / 212 tests 全部通过

P1-3 验收证据2026-07-22

  • 4 个页面模板组件src/shared/components/page-templates/
    • list-page.tsxListPageShell + ListPageSkeleton,结构 = PageHeader + FilterBar + 主内容 + Pagination三态 = loading/empty/errorNode 优先级链
    • detail-page.tsxDetailPageShell + DetailSection + DetailField + DetailPageSkeleton,结构 = PageHeader含 backHref + 分区 + 字段表
    • form-page.tsxFormPageShell + FormPageSkeleton,结构 = PageHeader + form + errorSummary + 提交/取消按钮
    • workbench-page.tsxWorkbenchPageShell + WorkbenchPanel + WorkbenchPageSkeleton,结构 = PageHeader + 三栏(左树/中画布/右属性),左右栏宽度可配置
    • index.tsbarrel 导出
  • 4 个 dev 示例页(仅 dev 可见,生产环境 notFound() 兜底):
  • 路径命名修正:原 ARCHITECTURE.md 写 /shell/_dev/templates/*,但 Next.js 将下划线开头的文件夹视为"私有文件夹"(不参与路由),实测 /shell/_dev/templates[[...route]]/page.tsx catch-all 兜底接管ClientShell 微内核渲染)。改用 dev 命名后显式路由优先匹配catch-all 不再触发。route-permissions.ts 同步登记 PREFIX /shell/dev/(空 config = 仅校验登录身份)
  • 三态规范验证HTTP 200 + 内容断言):
    • GET /shell/dev/templates/list?state=loading → 200HTML 含 animate-pulse 骨架,无表格数据
    • GET /shell/dev/templates/list?state=empty → 200HTML 含"暂无数据"空态,无表格数据
    • GET /shell/dev/templates/list(默认 success→ 200HTML 含表格行"考试 A"
  • 单测vitest run --reporter=verbose page-templates → 19/19 passed覆盖 4 个模板的三态、字段渲染、提交按钮禁用、errorSummary alert 等)
  • 质量校验tsc --noEmit 通过;eslint src/shared/components/page-templates src/app/shell/dev src/shared/lib/route-permissions.ts 通过;vitest run 全量 21 test files / 231 tests 全部通过212 原有 + 19 新增)

P1-4 验收证据2026-07-22

  • next-intl v4.13.2 接入(无 i18n 路由模式)
    • next.config.jscreateNextIntlPlugin("./src/i18n/request.ts") 包装 nextConfigTurbopack resolveAlias 注入 next-intl/config
    • src/i18n/request.tsgetRequestConfigNEXT_LOCALE cookie 读取 locale缺省 zh-CN),动态 import 对应 messages JSON
    • src/app/layout.tsxasync RootLayout + getLocale() / getMessages() + <NextIntlClientProvider> 注入全局 messages<html lang={locale}> 随 cookie 切换
  • messages 合并迁移
    • src/messages/zh-CN.json:从 teacher-portal 合并 + 新增 shell.dev.templates 命名空间title/description/list/detail/form/workbench 及其 listExample 等子键)
    • src/messages/en.json:与 zh-CN.json 结构完全对称
  • 自造 i18n 删除
  • locale-switcher 改造src/widgets/topbar/locale-switcher/index.tsx 使用 useLocale() + useTranslations() + cookie 写入 + router.refresh() 触发 RSC 重新渲染
  • dev/templates 页面演示src/app/shell/dev/templates/page.tsx 使用 getTranslations("shell.dev.templates")Server Component
  • locale 切换 HTTP 验证dev server next dev --turbopack -p 4010
    • GET /login(无 cookie<html lang="zh-CN"> + NextIntlClientProvider locale="zh-CN" + 中文文案("保存"/"取消"/"切换侧栏"
    • GET /loginCookie: NEXT_LOCALE=en)→ <html lang="en"> + NextIntlClientProvider locale="en" + 英文文案("Save"/"Cancel"/"Toggle sidebar"
  • 质量校验tsc --noEmit 通过;eslint src → 0 errors, 2 warnings__generated__/types.ts 生成文件);vitest run 全量 21 test files / 231 tests 全部通过

P1-5 验收证据2026-07-22

  • MSW v2.7.0 兜底层(覆盖 dashboard/users/exams/grades 四域起步)
  • SSR 端 mock 数据Apollo Client 走同域代理)
  • 生产构建安全bundle 无 mocks
    • src/mocks/empty.ts:空 stub导出与 index.ts/graphql-data.ts 同签名的 no-op 函数
    • next.config.jsNEXT_PUBLIC_MSW!=1 时 Turbopack resolveAlias + webpack resolve.alias@/mocks@/mocks/graphql-data 重定向到 @/mocks/emptyTurbopack 不支持 Windows 绝对路径,故使用 @/ 说明符)
    • src/providers/MswProvider.tsx:静态 import { initMocks } from "@/mocks"alias 生效后指向 empty.tsMSW_ENABLED=false 时 useEffect 分支被 dead-code 消除
    • 构建验证NEXT_PUBLIC_MSW=0 next build 成功20 路由生成);Select-String -Path ".next/static/chunks/**/*.js",".next/server/**/*.js" -Pattern "张老师","stu-001","setupWorker","dev-teacher-001","二次函数"CLEAN客户端 + 服务端 bundle 均无 mock 字符串
  • dev server 验证MSW=1 无后端)GET /api/graphql 返回 {msw:true}POST /api/graphql 返回 mock 数据teacherDashboard: total_classes=5/total_students=142users: 5 条记录questions: 3 条grades: 3 条)
  • 质量校验tsc --noEmit 通过;eslint src → 0 errors, 2 warnings__generated__/types.tsvitest run 全量 21 test files / 231 tests 全部通过

P1-6 验收证据2026-07-22

  • 31 widget 令牌清债516 处机械替换,覆盖 25 文件)
    • 间距工具类 xs/sm/md/lg/xl → 数字 1/2/3/4/6mt-smmt-2px-smpx-2py-xspy-1p-mdp-3gap-smgap-2space-y-xsspace-y-1h-xsh-1 等)
    • text-heading-3text-lg font-semibold25 处)
    • bg-dangerbg-destructive6 处shadcn 标准)
    • border borderborder(去重,仅匹配真正的重复 border 类)
  • lint:tokens 修复.eslintrc.tokens.js 原导入 @typescript-eslint/parser(未安装)导致脚本不可用,改用 typescript-eslint 包的 tseslint.parser(与 eslint.config.js 一致)
  • 验收命令
    • grep -rn "text-heading-\|mt-sm\|py-xs\|p-md" src/widgets0 匹配
    • eslint -c .eslintrc.tokens.js src0 errors
  • 质量校验tsc --noEmit 通过;eslint src → 0 errors, 2 warningsvitest run 全量 21 test files / 231 tests 全部通过

P2 · 教师域页面23 周,可与 P3 部分并行)

  • 范围§9.1 全表(~50 页。顺序建议exams → homework → grades → lesson-plans → questions/textbooks → attendance/classes/students → diagnostic/error-book/analytics → elective/course-plans → ai-* → practice/schedule-changes/leave。
  • 每模块退出:列表/详情/表单页齐 + 三态 + MSW 场景 + 单测(数据变换函数)+ 页面计数更新。
  • 契约工单exams CRUD mutation、homework CRUD、grades 录入/列表、lessonPlans CRUD、attendance、classes 列表等,随模块开工即登记。

P3 · 学生域页面1.52 周)

  • 范围§9.2 全表(~34 页。先行dashboard 详情trend/weakness 已 )→ error-book)→ grades/exams/homework 只读 → exams/take 作答台 → practice → ai-tutor。

P4 · 家长域页面11.5 周)

  • 范围§9.3 全表(~21 页)。多为只读视图,复用学生域组件;重点:myChildren 契约 + child-overview 聚合。

P5 · 管理域页面1.52 周)

  • 范围§9.4 全表(~22 页。先行users 列表契约优先推)→ roles/permissions → audit-logs → school 体系 → invitation-codes → plugins)→ viewports对齐 004 §5.4)。
  • 收尾:旧 4 portal 目录归档(apps/_archive/)或删除(决策点:待全量验收后执行,单独 PR

P6 · 硬化与验收12 周)

  • Playwright E2E登录 → 各角色核心流 1 条(教师建考试/学生交作业/家长看成绩/管理员建用户)+ 三级错误边界 + 门禁越权。
  • 视觉回归5 布局 + 每域代表页截图基线。
  • 性能:首屏 JS ≤ 300KB(gzip)、LCP < 2s本地 Docker 压测,附报告)。
  • 生产检查单:APOLLO_REQUIRE_PQ_MANIFEST=trueAPOLLO_ROUTER_INTROSPECTION=falseNEXT_PUBLIC_DEV_MODE=falseROUTER_AUTH_SECRET env 化、CSP 头(next.config.js headers())、/api/log 换生产端点。
  • 文档README v3.0 重写(本文件内容沉淀回模块 README + 本文件标记"已并入 README")。

11. 后续 AI 工作规范(强制)

本章是给所有后续在本模块工作的 AI/人的操作规程。多 AI 协作同时遵守 project_rules §14模块单一负责制本模块只改 apps/portal-shell/,跨模块变更按 §14.4 顺序)。

11.1 开工前必读(按序)

  1. 本文件v3.0 总纲)
  2. project_rules.md §3.4/§3.10/§4/§14
  3. 所接任务的页面源(旧 portal 对应 page.tsx与目标模板§7.3

11.2 目录与文件约定

src/
├─ app/
│  ├─ login/page.tsx                      # 登录
│  ├─ shell/
│  │  ├─ layout.tsx                       # AppFrame唯一框架
│  │  ├─ forbidden/page.tsx               # 403
│  │  ├─ page.tsx                         # 角色仪表盘catch-all 保留于仪表盘内部可选)
│  │  ├─ <role>/<module>/page.tsx         # 业务页面(显式路由)
│  │  └─ <role>/<module>/{loading,error}.tsx
│  └─ api/{graphql,auth/login,auth/logout,health,ready,log}/route.ts
├─ middleware.ts                          # 认证 + 门禁(唯一入口)
├─ features/<domain>/                     # 页面私有实现(组件/hooks/类型),按域组织
│  └─ exams/{exams-list.tsx, exam-detail.tsx, use-exam-filters.ts}
├─ widgets/                               # 仅仪表盘卡片(存量,冻结新增——新功能建页面不建插件)
├─ shell/                                 # 微内核(存量,冻结)
├─ lib/{api,apollo-client,auth}           # 数据层/认证工具
├─ mocks/                                 # MSWdev/test only
├─ shared/{components,lib}                # 共享 UI 与工具
└─ messages/{zh-CN,en}.json               # i18n
  • 页面文件瘦身page.tsx 只做 RSC 预取 + 组装,实现代码放 features/<domain>/;单文件 ≤ 300 行(超出即拆分)。
  • features 域清单exams / homework / grades / lesson-plans / questions / textbooks / attendance / classes / students / course-plans / elective / error-book / diagnostic / analytics / practice / schedule-changes / leave / ai / notifications / settings / users / rbac / audit / school / invitations / plugins / organization / files / system / viewports / children / messages / announcements。新增域先在本文件 §9 登记。

11.3 每页硬性清单DoD

  • 显式路由 + route-permissions.ts 已登记CI 一致性通过)
  • 套页面模板§7.3),未手排布局
  • loading.tsx + error.tsx + 空态三态齐全
  • 数据走 lib/api hooks无内联 gql无手写与 schema 冲突类型
  • schema 未就绪的查询走 MSW + 文件头 @contract-pending: <工单> 注释 + §11.4 登记
  • 文案走 useTranslations;无硬编码中文(迁移期 lint warnP2 末转 error
  • 无禁用类/字面量§8.1 铁律);pnpm lint + lint:tokens 通过
  • 交互错误有 notify.error;无空 catch
  • 数据变换/权限判断等纯函数有 vitest 单测
  • i18n 两份 messages 同步更新
  • 提交信息:feat(portal-shell): <module> <page> migrationConventional Commits

11.4 契约工单流程(前端缺 schema 字段时)

  1. docs/architecture/issues/contracts/<service>_contract.md 追加需求(字段名/类型/权限点/使用页面)。
  2. 本文件 §9 对应行"契约"列标注工单号。
  3. 页面用 MSW 先行;后端落地后:重跑 normalize + codegen → 该域关 skipDocumentsValidation → 切换 fetcher → 删 mock → 工单关闭。
  4. 禁止为绕过校验把查询写得与 schema 不符后开启 skip。

11.5 多 AI 并行分工建议

AI 角色 负责 边界
框架 AI P0/P1middleware/代理/AppFrame/模板/MSW/i18n 独占 src/{app/shell/layout.tsx,middleware.ts,shared/,lib/,mocks/}
教师域 AI §9.1 独占 src/app/shell/teacher/** + src/features/{exams,homework,grades,lesson-plans,...}
学生域 AI §9.2 独占 src/app/shell/student/** + 对应 features
家长域 AI §9.3 独占 src/app/shell/parent/** + 对应 features
管理域 AI §9.4 独占 src/app/shell/admin/** + 对应 features
公共区login/notifications/settings 框架 AI 防止撞车
  • 跨域共享组件必须先提"共享申请"(改 src/shared/packages/ui-components/),由框架 AI 评审合入;禁止各自复制粘贴。
  • 分支:每域一个 feature 分支(feat/portal-shell-<domain>),按 project_rules §14.3。

11.6 文档与验收纪律

  • 每个 PR 更新:本文件 §9 对应行状态 +如涉及README 对应章节。
  • 验收勾选必须附可复现命令输出typecheck/lint/test/build/截图),禁止"已完成"裸声明——本文件 §1.1 的对照表即为反面教材。
  • 每阶段结束运行 pnpm run arch:scan 更新 arch.db并同步 004 相关章节。

11.7 禁止事项(红线)

  1. 禁止新增 widget 插件实现业务功能(冻结 src/widgets/ 新增;新功能 = 新页面)。
  2. 禁止恢复 localStorage 存 token禁止 JS 读取 edu_session
  3. 禁止在页面/widget 内联 gql 或绕过 lib/api 直接 useQuery
  4. 禁止引入第二套设计令牌 / 第二套组件库 / 第二套 toast。
  5. 禁止"默认放行"式的权限代码(未匹配路由必须拒绝)。
  6. 禁止跳过 @contract-pending 登记直接写假查询。
  7. 禁止删除/弱化现有测试来让门禁变绿。

12. 风险登记册

# 风险 等级 缓解
R1 后端契约补齐速度成为页面切换瓶颈(~100 页 MSW 先行解耦;契约工单周同步;优先推 mutations 框架(后端 0 mutation 是系统性缺口)
R2 140 页迁移量大AI 并行产出不一致 模板四件套强制 + 共享组件申请制 + 每模块 DoD 清单 + CI 一致性脚本
R3 旧页面纸感令牌清理遗漏导致视觉混杂 迁移 DoD 含令牌检查arch:scan 未知类检测P6 视觉回归
R4 middleware 验签引入 JWKS 可用性依赖 JWKS 缓存5min+ 故障 fail-closed + 启动自检
R5 next-intl 与 RSC 流式模式冲突 P1 先做技术验证(一个页面跑通再推广)
R6 MSW 数据与真实 schema 漂移 mock 数据类型从 codegen types 导入;契约切换时删 mock
R7 旧 portal 删除时发现有未迁移边角功能 P5 删除前做路由 diff 清单评审;先归档后删除
R8 config-service 单点故障影响仪表盘 P0-4 内置默认配置已兜底;页面不依赖插件配置

13. 附录

13.1 命令手册

# 开发(无后端)
NEXT_PUBLIC_MSW=1 NEXT_PUBLIC_DEV_MODE=true node node_modules/next/dist/bin/next dev -p 4010

# 质量门禁CI 全量)
node node_modules/typescript/bin/tsc --noEmit          # typecheck
node node_modules/.bin/eslint src                      # lint或 pnpm lint
node node_modules/vitest/vitest.mjs run                # 单测
node node_modules/next/dist/bin/next build             # 构建
node node_modules/.bin/eslint -c .eslintrc.tokens.js src  # 令牌专项

# 契约
node_modules/.bin/tsx scripts/normalize-schema.ts && node_modules/.bin/graphql-codegen --config codegen.yml
node_modules/.bin/tsx scripts/generate-pq-manifest.ts

# 一致性校验P1 起)
node scripts/check-route-permissions.mjs               # 路由表 ⇄ 文件系统

13.2 关键文件速查

主题 文件
微内核仪表盘 src/shell/{Shell,ClientShell,LayoutManager,SlotRenderer,Registry,PropsMerger,PluginLifecycle,PluginStore}.tsx?
数据层 src/lib/{apollo-client,useWidgetQuery,useWidgetMutation,config-fetcher,usePluginConfig}.ts + src/lib/api/
安全 src/shared/lib/route-permissions.ts + packages/shared-ts/src/permission-bitmap.ts +(新)src/middleware.ts
错误处理 src/shared/components/{plugin,route,section}-boundary.tsx + packages/hooks/src/use-error-report.ts
设计令牌 packages/ui-tokens/src/{primitive,semantic-light,semantic-dark,tailwind-theme}.css
组件库 src/shared/components/ui/* + packages/ui-components/src/*
迁移源 apps/{teacher,student,parent,admin}-portal/src/app/**/page.tsx + apps/teacher-portal/src/mocks/handlers-p*.ts
后端 schema 源 services/*/src/graphql/generated/schema.graphqlsrc/lib/api/__generated__/combined-schema.graphql

13.3 参考文档

文档 用途
README.md v2.0 微内核仪表盘子系统详细设计(保留有效部分)
004 §5.4 视口四层模型(导航/路由/组件/数据)
004 §11.7 前端数据层 + GraphQL 安全栈设计意图
GraphQL @auth 审计 后端 resolver 权限现状
MIGRATION_GUIDE.md CICD → Edu 迁移背景与资产清单
多 AI 协作规范 并行工作规则

13.4 术语表

术语 定义
AppFrame 全站页面框架TopBar + Sidebar + Mainsrc/app/shell/layout.tsx
微内核仪表盘 /shell 角色首页的插件聚合区v2.0 遗产,保留收缩)
页面模板四件套 list / detail / form / workbench 四种页面骨架§7.3
契约工单 前端缺 schema 字段时的后端需求登记单§11.4
MSW 兜底层 src/mocks/ 开发态模拟后端§5.4
@contract-pending 页面头注释,标记数据来自 MSW 待契约切换
fail-closed 无身份/无权限/服务不可用时一律拒绝并显式提示,禁止静默放行
视口四层 004 §5.4:导航 L1 / 路由 L2 / 组件 L3 / 数据 L4

本文件自发布之日起为 portal-shell 前端工作唯一权威。任何"已完成"声明必须能以 §1.1 同等严格度复现。下一阶段开工前,先完成 P0 并回填本文件 §9/§10 状态。