32 KiB
模块架构设计文档 — admin-portal
AI:ai16(TS/React · 管理场景域前端 remote) 阶段:阶段 2 交付物(仲裁后修订版 v2) 日期:2026-07-10 关联:
- 阶段 1 理解确认书
- 004 架构影响地图 §5.4
- 总统最终裁决 §5.1-5.5(ISSUE-044~048)
- admin-portal 对接契约
- pending-features P6
- known-issues §2.16 状态:已实现(P2-P6 全阶段交付完成)
1. 模块内部分层图(admin-portal Remote + Shell 引用 + Gateway + push-gateway)
graph TB
subgraph Browser["浏览器(系统/校管理员)"]
URL[URL 路由 /admin/*]
end
subgraph Shell["teacher-portal(Shell 宿主,端口 4000)"]
AppShell[AppShell<br/>左栏导航 + 主内容区<br/>按 scope=admin 过滤视口]
RootLayout[RootLayout<br/>字体/令牌/i18n Provider]
Router[Next.js App Router<br/>动态加载 admin Remote]
SharedDeps["共享依赖暴露(singleton)<br/>react/react-dom/urql/graphql<br/>@edu/ui-tokens/@edu/ui-components/@edu/hooks/@edu/contracts"]
GraphQLProviderShell[GraphQLProvider<br/>Shell 暴露 urql client 单例]
end
subgraph RemoteAdmin["admin-portal(Remote 子应用,端口 4003)"]
AdminApp[AdminApp<br/>MF Remote 入口 exposes ./AdminApp]
AdminPages[管理场景页面<br/>dashboard/users/roles/permissions/viewports/organization<br/>classes/teachers/students/audit-logs/system]
AdminComponents["admin 特有组件<br/>UserManagementTable/RolePermissionMatrix<br/>ViewportConfigEditor/OrganizationTree/NotificationPanel"]
AdminHooks["admin 业务 Hooks<br/>useUsers/useRoles/usePermissions/useViewports<br/>useOrganization/useClasses/useTeachers/useStudents<br/>useAuditLogs/useDashboard/useSystemSettings/useWebSocket"]
end
subgraph Gateway["api-gateway"]
GW[Gin 路由/鉴权/限流]
end
subgraph PushGateway["push-gateway"]
WS[WebSocket :8081/ws]
end
subgraph Backend["后端服务"]
IAM[iam<br/>用户/角色/权限/视口 CRUD + AuditEvent 发布]
BFF[teacher-bff<br/>GraphQL admin 命名空间 聚合]
end
Browser --> URL
URL --> RootLayout
RootLayout --> AppShell
AppShell --> Router
Router -->|动态加载 /admin/*| AdminApp
AdminApp --> AdminPages
AdminPages -->|消费 singleton| SharedDeps
SharedDeps --> GraphQLProviderShell
AdminPages --> AdminComponents
AdminPages --> AdminHooks
AdminHooks -->|useGraphQuery/useGraphMutation| GraphQLProviderShell
GraphQLProviderShell -->|POST /api/admin/graphql| GW
GW --> BFF
BFF --> IAM
AdminHooks -->|useWebSocket| WS
IAM -.->|Kafka edu.iam.audit.created| BFF
AppShell -->|fetch /api/auth/login| GW
1.1 Remote 与 Shell 的职责边界
| 职责 | 归属 | 说明 |
|---|---|---|
| RootLayout(字体/令牌/Provider) | teacher-portal Shell | admin-portal MF 模式复用;standalone 模式自建 |
| AppShell(左栏 + 主内容区) | admin-portal 内部 | admin-shell.tsx,11 项导航 + 跳过链接 A11y |
路由表 /admin/* |
admin-portal | src/app/admin/*/page.tsx |
| 登录页 | admin-portal | standalone 模式 src/app/login/page.tsx(mock 登录);MF 模式复用 Shell /login |
rewrites /api/admin/graphql |
admin-portal | next.config.js rewrites 代理到 api-gateway |
| GraphQLProvider | Shell 暴露 / 自建 | MF 模式复用 Shell 单例(ARB-004);standalone 模式 graphql-provider.tsx 自建 urql client |
| 管理场景页面 | admin-portal Remote | /admin/* 下的所有 page.tsx |
| 管理特有组件 | admin-portal Remote | UserManagementTable 等 6 个 |
| 管理业务 Hooks | admin-portal Remote | 13 个业务 Hook + useWebSocket |
| 共享组件库 | Shell 暴露 | @edu/ui-components |
| 共享 Hooks | Shell 暴露 | @edu/hooks |
1.2 MF 配置(admin-portal/next.config.js,Remote 角色)
const NextFederationPlugin = require("@module-federation/nextjs-mf");
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["@edu/ui-tokens", "@edu/ui-components", "@edu/hooks"],
async rewrites() {
const gatewayUrl = process.env.API_GATEWAY_URL || "http://localhost:8080";
return [
{
source: "/api/admin/graphql",
destination: `${gatewayUrl}/api/admin/graphql`,
},
{ source: "/api/:path*", destination: `${gatewayUrl}/api/:path*` },
];
},
webpack(config, { isServer }) {
if (process.env.NEXT_PUBLIC_MF_ENABLED === "true") {
config.plugins.push(
new NextFederationPlugin({
name: "admin_app",
filename: "static/chunks/remoteEntry.js",
exposes: { "./AdminApp": "./src/app/admin-app.tsx" },
remotes: {
teacher: `teacher_app@http://localhost:4000/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
},
shared: {
react: { singleton: true, requiredVersion: "^18.3.0" },
"react-dom": { singleton: true, requiredVersion: "^18.3.0" },
urql: { singleton: true },
graphql: { singleton: true },
"@edu/ui-tokens": { singleton: true },
"@edu/ui-components": { singleton: true },
"@edu/hooks": { singleton: true },
},
extraOptions: { exposePages: false },
}),
);
}
return config;
},
};
module.exports = nextConfig;
说明:
NEXT_PUBLIC_MF_ENABLED环境变量控制 MF 启用(standalone 模式默认false,MF 模式设true)- admin-portal 暴露
./AdminApp模块(非旧文档的./pages),供 Shell 动态 importshared全部singleton: true,确保 urql/graphql/react 单例(避免多实例报错)- admin-portal 自身实现
rewrites(代理/api/admin/graphql到 api-gateway),standalone 模式可独立运行
2. 领域模型(前端视角)
前端不持有业务聚合根,仅持有"视图模型"(ViewModel)和"会话状态"。定义于 src/types/view-models.ts。
2.1 视图模型清单
// 数据范围
type DataScope = "ALL" | "SCHOOL" | "GRADE" | "CLASS" | "DISTRICT";
// 用户管理
interface UserViewModel {
id: string;
email: string;
name: string;
roles: { id: string; name: string; code: string }[];
status: "active" | "disabled" | "locked";
dataScope: DataScope;
organizationId: string | null;
schoolName?: string;
lastLoginAt: number | null;
createdAt: number;
updatedAt: number;
}
// 角色管理
interface RoleViewModel {
id: string;
name: string;
code: string;
description: string;
permissions: PermissionViewModel[];
userCount: number;
dataScope: DataScope;
isSystem: boolean;
createdAt: number;
updatedAt: number;
}
// 权限管理
interface PermissionViewModel {
id: string;
code: string;
name: string;
description: string;
resource: string;
action: string;
isSystem: boolean;
}
// 视口配置
interface ViewportConfigViewModel {
id: string;
key: string;
label: string;
route: string;
icon: string | null;
sortOrder: number;
requiredPermission: string | null;
scope: "teacher" | "student" | "parent" | "admin";
isVisible: boolean;
}
// 组织树
interface OrganizationNode {
id: string;
name: string;
type: "school" | "grade" | "class";
parentId: string | null;
childrenCount: number;
path: string;
}
// 学校设置
interface SystemSettingsViewModel {
schoolName: string;
schoolYear: string;
semester: string;
timezone: string;
locale: string;
// ... 更多设置项
}
// 班级/教师/学生(admin 全局视角)
interface AdminClassViewModel {
id: string;
name: string;
grade: string;
headTeacher: string;
studentCount: number;
status: string;
}
interface AdminTeacherViewModel {
id: string;
email: string;
name: string;
subjects: string[];
classes: string[];
status: string;
}
interface AdminStudentViewModel {
id: string;
email: string;
name: string;
className: string;
grade: string;
status: string;
}
// 审计日志
interface AuditLogViewModel {
id: string;
userId: string;
userName: string;
action: string;
resourceType: string;
resourceId: string;
beforeState: string | null;
afterState: string | null;
ip: string;
userAgent: string;
occurredAt: number;
}
// 管理仪表盘
interface AdminDashboardViewModel {
totalUsers: number;
totalTeachers: number;
totalStudents: number;
totalClasses: number;
activeSessions: number;
serviceHealth: {
serviceName: string;
status: "healthy" | "degraded" | "down";
latencyMs: number;
}[];
recentActivity: {
timestamp: number;
action: string;
user: string;
resource: string;
}[];
}
// WebSocket 通知
interface WsNotification {
id: string;
type: "audit_alert" | "abnormal_login" | "system_error" | "info";
severity: "info" | "warning" | "error";
title: string;
message: string;
timestamp: number;
}
2.2 当前用户与会话
interface CurrentUser {
id: string;
email: string;
name: string;
roles: string[];
permissions: string[];
dataScope: DataScope;
schoolId: string | null;
schoolName: string | null;
}
会话状态由 AuthProvider 管理,token 存 localStorage(edu_access_token),用户信息存 localStorage(edu_user)。
3. 数据模型(前端缓存层)
admin-portal 无数据库,仅有 urql GraphQL client 缓存层。
| 数据类型 | 存储 | 缓存策略 |
|---|---|---|
| Session(token + user) | localStorage + React Context | access token 持久化;用户信息持久化;登出清除 |
| GraphQL 查询缓存 | urql document cache | 默认缓存,mutation 后手动 invalidate |
| 视口配置(拖拽态) | React useState | 本地编辑,保存时批量 mutation |
| 表单临时态 | react-hook-form | 卸载即销毁 |
| WebSocket 通知 | React useState(限 20 条) | 新通知头部插入,超 20 条裁剪 |
缓存策略说明:admin 数据低频变,urql 默认 document cache 已满足;监控指标经 WebSocket 实时推送(不再轮询);视口配置支持本地编辑 + 批量保存。
4. API 设计(GraphQL 为主)
4.1 GraphQL Client(urql,ARB-004)
standalone 模式(src/lib/graphql-client.ts):
import { createClient, fetchExchange, type Client } from "@urql/core";
const GRAPHQL_ENDPOINT = "/api/admin/graphql";
export function createGraphQLClient(): Client {
return createClient({
url: GRAPHQL_ENDPOINT,
exchanges: [fetchExchange],
fetchOptions: () => {
const token =
typeof window !== "undefined"
? localStorage.getItem("edu_access_token")
: null;
return token ? { headers: { Authorization: `Bearer ${token}` } } : {};
},
});
}
MF 模式:复用 Shell 暴露的 GraphQLProvider + useGraphQLClient(singleton,ARB-004)。
urql 类型断言技巧:urql 泛型与纯字符串 query 兼容性有限,用 as unknown as 从 unknown 转换:
// use-graphql.ts
const [executeMutation] = useMutation(mutation) as unknown as [
(variables: V) => { toPromise: () => Promise<OperationResult<T, V>> },
unknown,
];
4.2 通用 GraphQL Hooks(src/hooks/use-graphql.ts)
export function useGraphQuery<T, V = Record<string, never>>(
query: string,
variables?: V,
): QueryResult<T> { ... }
export function useGraphMutation<T, V>(
mutation: string,
): [MutationFn<T, V>, MutationResult] { ... }
4.3 业务 Hooks 清单
| Hook | 职责 |
|---|---|
useUsers(filter) |
用户列表查询(含筛选/分页) |
useUser(userId) |
用户详情 |
useCreateUser() |
创建用户 mutation |
useUpdateUser() |
更新用户 mutation |
useToggleUserStatus() |
启用/禁用用户 mutation |
useUserFilter() |
用户筛选状态(搜索/角色/状态/组织) |
useRoles() |
角色列表查询 |
useCreateRole() |
创建角色 mutation |
useUpdateRolePermissions() |
更新角色权限 mutation(RolePermissionMatrix 用) |
usePermissions() |
全量权限列表 |
useViewports() |
视口配置列表 |
useUpdateViewport() |
更新视口配置 mutation |
useOrganization(parentId) |
组织树查询(按 parentId 递归) |
useClasses(filter) |
班级列表查询 |
useClassFilter() |
班级筛选状态 |
useTeachers(filter) |
教师列表查询 |
useTeacherFilter() |
教师筛选状态 |
useStudents(filter) |
学生列表查询 |
useStudentFilter() |
学生筛选状态 |
useAuditLogs(filter) |
审计日志查询 |
useAuditLogFilter() |
审计日志筛选状态 |
exportAuditLogsCsv(logs) |
审计日志 CSV 导出(BOM + UTF-8) |
useDashboard() |
管理仪表盘聚合查询 |
useSystemSettings() |
学校设置查询 |
useUpdateSystemSettings() |
学校设置更新 mutation |
useWebSocket(maxItems) |
WebSocket 实时通知(mock 模式 30s 定时推送) |
5. 事件设计
5.1 WebSocket 实时通知(ARB-006)
admin-portal 接入 push-gateway GET /ws,消费 3 类通知:
// src/hooks/use-websocket.ts
export function useWebSocket(maxItems = 20) {
// 真实模式:连接 push-gateway NEXT_PUBLIC_WS_URL
// mock 模式:30s 定时器模拟推送
// 返回:{ notifications, connected, dismiss, clear }
}
| 通知类型 | severity | 触发场景 |
|---|---|---|
audit_alert |
warning | 敏感操作(权限变更/批量删除) |
abnormal_login |
error | 异地登录/异常时段登录 |
system_error |
error | 服务降级/宕机 |
5.2 不消费 Kafka
admin-portal 不直接订阅 Kafka。审计日志经 teacher-bff 聚合后通过 GraphQL auditLogs Query 消费(ARB-005):
链路:iam → Kafka edu.iam.audit.created → teacher-bff 消费 → GraphQL auditLogs Query → admin-portal
不再用轮询:旧文档的"60s 轮询监控 + 5min 轮询统计"已被 ARB-006 的 WebSocket 推送替换。
6. 横切关注点对齐清单
6.1 权限(前端等价,ARB-003)
权限点常量定义于 src/lib/permissions.ts:
ADMIN_PERMISSIONS:admin 自身资源(ADMIN_DASHBOARD_VIEW/ADMIN_SYSTEM_MANAGE/ADMIN_AUDIT_READ/ADMIN_CLASS_READ/ADMIN_TEACHER_READ/ADMIN_STUDENT_READ/ADMIN_ORG_MANAGE等)IAM_PERMISSIONS:跨服务资源(IAM_USER_READ/IAM_ROLE_READ/IAM_PERMISSION_READ/IAM_VIEWPORT_READ等)ROUTE_PERMISSIONS:11 路由 → 权限点映射
权限校验:usePermission().hasPermission("XXX") Hook + AuthGuard 组件(路由级)。
6.2 错误码清单(前端 i18n 路由,ARB-002)
| 前缀 | 来源服务 | i18n key 模式 |
|---|---|---|
IAM_ |
iam | iam.error.{{code}} |
BFF_TEACHER_ |
teacher-bff | bff.error.{{code}} |
GW_ |
api-gateway | gateway.error.{{code}} |
NETWORK_ |
前端网络层 | network.error.{{code}} |
ADMIN_ |
admin 域 | error.admin.* |
6.3 Web Vitals(src/lib/web-vitals.ts)
| 指标 | 类型 | 上报方式 |
|---|---|---|
| LCP | 最大内容绘制 | navigator.sendBeacon |
| CLS | 累积布局偏移 | 同上 |
| FCP | 首次内容绘制 | 同上 |
| INP | 交互到下一绘制 | 同上 |
| TTFB | 首字节时间 | 同上 |
仅 production 启用(WebVitalsInitializer 组件控制)。
6.4 健康检查
| 端点 | 用途 | 实现 |
|---|---|---|
GET /api/health |
Liveness | 返回 200 { status: "ok" } |
GET /api/ready |
Readiness | 检查 gateway 可达性(2s 超时);mock 模式直接 ready |
6.5 A11y(WCAG 2.2 AA)
eslint-plugin-jsx-a11y(error 级)- skip-link(
admin-shell.tsx跳过到主内容区) focus-visible样式(globals.css)- 组织树:
<button role="treeitem" aria-expanded aria-selected onKeyDown> - 通知面板:
<li>内嵌<button aria-label> - Toast:
aria-live="polite"+aria-atomic="true" - 注:
@next/eslint-plugin-next14.x 与 ESLint 9 不兼容(context.getAncestors已移除),已移除
6.6 横切关注点对齐清单(汇总)
| 对齐项 | 实现 |
|---|---|
| 权限校验 | usePermission() + AuthGuard,ADMIN_*/IAM_* 前缀 |
| 错误码前缀 | 5 个前缀(IAM_/BFF_TEACHER_/GW_/NETWORK_/ADMIN_) |
| logger | console + 结构化(生产上报) |
| metrics | Web Vitals v4 → navigator.sendBeacon |
| /health + /ready | Next.js Route Handler |
| 优雅关闭 | N/A(Next.js 无长连接) |
| 测试覆盖率 ≥ 80% | Vitest + @testing-library/react(vitest.config.ts 已配置) |
| Dockerfile 多阶段构建 | node:22-alpine builder + runner,standalone 输出,EXPOSE 4003 |
| Zod 输入验证 | react-hook-form + zod |
| GlobalErrorFilter | React ErrorBoundary + Toast 错误处理 |
| 设计令牌三层 | globals.css(primitive/semantic)+ tailwind-theme |
| A11y | eslint-plugin-jsx-a11y + skip-link + focus-visible |
| Module Federation | next.config.js Remote,NEXT_PUBLIC_MF_ENABLED 控制 |
7. 共享组件库(复用 Shell + admin 特有)
7.1 复用 Shell 暴露的组件(@edu/ui-components)
AppShell 风格、ErrorBoundary、RequirePermission、Loading、Empty、Button/Input/Select 等。
7.2 admin-portal 特有组件(新建)
| 组件 | 用途 | 实现要点 |
|---|---|---|
AdminShell |
左栏 11 项导航 + 主内容区 + 跳过链接 | 内部组件,11 项导航按 ROUTE_PERMISSIONS 过滤;纸面风格 |
UserManagementTable |
用户管理表格(列表/筛选/分页) | 列:邮箱/姓名/角色/状态/数据范围/最后登录;筛选:搜索/角色/状态 |
UserFormModal |
用户表单弹窗(创建/编辑) | react-hook-form + zod;字段:邮箱/姓名/角色/数据范围/组织 |
RolePermissionMatrix |
角色-权限矩阵编辑器(checkbox 网格) | 行=角色,列=权限(按 resource 分组);全选/半选/单选;系统预置角色只读 |
OrganizationTree |
组织树(递归 + A11y treeitem) | 按 parentId 懒加载;<button role="treeitem" aria-expanded aria-selected onKeyDown> |
NotificationPanel |
通知面板(WebSocket 实时通知) | 固定右上角;显示最近 20 条;点击触发 Toast + dismiss;<li> 内嵌 <button aria-label> |
7.3 通用 UI 组件(src/components/ui.tsx)
PaperCard / PageHeader / Button(4 variant)/ Input / Select / Badge / Table / TableRow / TableCell / EmptyState / LoadingState / ErrorState / Pagination
7.4 不使用的组件(明确排除)
| 组件 | 归属 | 排除原因 |
|---|---|---|
RichTextEditor |
teacher-portal | 管理端无富文本场景 |
ExamTaking |
student-portal | 管理端无作答场景 |
ChildSwitcher |
parent-portal | 管理端无多子女切换 |
PlatformMonitor(Grafana iframe) |
— | 已移除,改为管理仪表盘 + 服务健康表格 |
8. Mock 策略(MSW + mock-socket)
8.1 MSW handlers(src/mocks/handlers.ts)
开发期拦截全部 GraphQL/HTTP 请求:
/api/auth/login→ mock 登录(admin@edu.test / admin123)graphql.operation→ 按 operationName 分发 18 个 GraphQL operation mock:- CurrentUser / AdminUsers / AdminUser / CreateUser / UpdateUser / ToggleUserStatus
- AdminRoles / CreateRole / UpdateRolePermissions
- AdminPermissions / AdminViewports / UpdateViewport
- AdminOrganization / AdminClasses / AdminTeachers / AdminStudents
- AuditLogs / AdminDashboard / SystemSettings / UpdateSystemSettings
- 内存可变副本:支持增删改 + 自动审计记录(
recordAudit) - 分页:
paginate(items, page, pageSize)辅助函数
8.2 mock fixtures(src/mocks/fixtures.ts)
mockCurrentUser:admin 角色,permissions=["*"]mockPermissions:24 项权限点mockRoles:4 个角色(admin/teacher/student/parent)mockUsers:6 个用户mockViewports:7 个视口配置mockOrganization:12 节点(school/grade/class)mockClasses/mockTeachers/mockStudents:6/6/5 条mockAuditLogs:5 条审计日志mockDashboard:仪表盘聚合数据mockSystemSettings:学校设置
8.3 WebSocket mock(src/hooks/use-websocket.ts)
MSW 不拦截 WebSocket,用定时器模拟推送:
- mock 模式:30s 定时器推送 1 条通知(轮换 3 类:audit_alert/abnormal_login/system_error)
- 真实模式:连接
NEXT_PUBLIC_WS_URL(push-gatewayGET /ws)
8.4 环境切换
NEXT_PUBLIC_API_MOCKING=enabled 启用 MSW;disabled 使用真实后端。
9. 设计令牌三层(src/app/globals.css)
9.1 Layer 1 Primitive(原始色板)
:root {
--paper: 0 0% 100%; /* 白纸 */
--ink: 30 10% 15%; /* 墨色 */
--ink-muted: 30 5% 45%;
--rule: 30 10% 90%; /* 分隔线 */
--accent: 210 60% 40%; /* 强调色 */
--accent-light: 210 60% 95%;
--danger: 0 70% 50%;
--success: 140 50% 40%;
--warning: 40 80% 50%;
}
9.2 Layer 2 Semantic
:root {
--bg-paper: hsl(var(--paper));
--color-ink: hsl(var(--ink));
--color-ink-muted: hsl(var(--ink-muted));
--color-rule: hsl(var(--rule));
--color-accent: hsl(var(--accent));
--color-accent-light: hsl(var(--accent-light));
--color-danger: hsl(var(--danger));
--color-success: hsl(var(--success));
--color-warning: hsl(var(--warning));
}
9.3 Layer 3 Tailwind Theme(tailwind.config.js)
paper / ink / accent / rule / danger / success / warning 色板映射为 bg-* / text-* 类。
9.4 强制规则(project_rules §3.10)
- 禁止
#hex字面量 - 禁止
'Inter'/'Fraunces'/'JetBrains Mono'字面量 - 禁止
font-size: Npx - 禁止 Tailwind 任意值
w-[Npx]
10. 与其他模块的交互点(契约清单)
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 | 阶段 |
|---|---|---|---|---|---|
| 调用 | api-gateway | HTTP/GraphQL | POST /api/admin/graphql(经 rewrites) |
全部业务 GraphQL 查询 | P6+ |
| 调用 | api-gateway | HTTP | POST /api/auth/login |
登录(standalone 模式) | P6+ |
| 调用 | push-gateway | WebSocket | GET /ws |
实时通知(ARB-006) | P6+ |
| 消费 | teacher-bff | GraphQL(经 Gateway) | admin 命名空间 18 operations | 全部业务数据 | P6+ |
| 消费 | iam(经 teacher-bff) | GraphQL(间接) | 用户/角色/权限/视口 CRUD + AuditEvent | 管理数据 | P6+ |
| 依赖 | coord 维护 | — | packages/contracts |
Permissions 常量 + 类型 | P6+ |
| 依赖 | coord 维护 | — | packages/shared-proto |
TS 类型(仅 contracts 部分) | P6+ |
| 依赖 | teacher-portal Shell | — | Shell 暴露的 GraphQLProvider + 共享依赖 | 宿主环境(MF 模式) | P6+ |
proto 不直接消费:前端不调用 gRPC,teacher-bff 把 gRPC 聚合为 GraphQL 暴露给前端。
11. 风险与假设
11.1 假设
- 假设 teacher-bff admin 命名空间 schema 就绪(ARB-001,contract §2.4 18 operations):若 ai03 未补齐,admin-portal 用 MSW mock 开发,上游就绪后切换。
- 假设 teacher-portal Shell 已就绪:MF 模式依赖 Shell 暴露 GraphQLProvider + 共享依赖。standalone 模式可独立运行。
- 假设 push-gateway WebSocket 就绪(ARB-006):若未就绪,用 mock-socket + 30s 定时器模拟。
- *假设
packages/contractsADMIN* 权限点常量就绪_*(coord 维护):admin-portalsrc/lib/permissions.ts引用,若 coord 未建立则本地定义。
11.2 技术风险
| 风险 | 影响 | 缓解 |
|---|---|---|
| Shell 未就绪阻塞 admin | MF 模式无法挂载 | standalone 模式自建 Provider 链,独立可运行 |
| teacher-bff admin schema 缺失 | GraphQL 查询失败 | MSW mock 全覆盖 18 operations,上游就绪后切换 |
| urql 类型兼容性 | TypeScript 编译错误 | as unknown as 从 unknown 转换(见 §4.1) |
| ESLint 9 兼容性 | @next/eslint-plugin-next 14.x 不兼容 |
移除该插件,用 jsx-a11y + tseslint 替代 |
| WebSocket mock 不真实 | 开发期通知体验与生产不一致 | 30s 定时器 + 3 类通知轮换,接近真实推送频率 |
| 权限缓存陈旧 | 角色变更后前端仍用旧权限 | 登录时拉取最新权限;admin-portal 本身是管理端,可主动刷新 |
12. 端口矩阵
| 端 | dev 端口 | 生产端口 | 备注 |
|---|---|---|---|
| admin-portal | 4003 | 4003 | Remote,挂载到 Shell |
| teacher-portal Shell | 4000 | 4000 | Shell 宿主 |
| api-gateway | 8080 | 8080 | GraphQL 代理 |
| push-gateway | 8081 | 8081 | WebSocket |
| teacher-bff | 3003 | 3003 | GraphQL admin 命名空间 |
13. 实施路线(已完成)
P2-P6 全阶段交付(已完成)
- ✅ P2 骨架 Phase 1:配置文件 + 设计令牌 + 类型定义
- ✅ P2 骨架 Phase 2:GraphQL client + auth + permissions + providers + AdminShell + MF Remote 入口 + standalone 壳
- ✅ P2 骨架 Phase 3:MSW mock(handlers + fixtures + browser)+ mock-socket WebSocket mock
- ✅ P3 业务:用户管理(useUsers/useUser/CRUD hooks + UserManagementTable + /admin/users 页面)
- ✅ P3 业务:角色权限矩阵 + 权限点管理 + 视口配置
- ✅ P4 业务:组织管理 + 学校设置 + 班级/教师/学生全局管理
- ✅ P5 业务:审计日志 + WebSocket 实时通知 + 管理仪表盘
- ✅ P6 硬化:A11y + Web Vitals + Dockerfile + /api/health + /api/ready + 质量校验
- ✅ 文档回写:01/02 文档对齐仲裁结果 + known-issues §2.16 + arch:scan
验收标准
- ✅ 所有 11 个路由页面可访问,权限校验生效
- ✅ MF Remote 可被 Shell 加载(暴露
./AdminApp) - ✅ standalone 模式独立可运行(自建 Provider 链 + mock 登录)
- ✅ GraphQL 18 operations 全部实现(MSW mock + 真实切换)
- ✅ WebSocket 实时通知(mock 模式 30s 定时推送)
- ✅ Web Vitals 采集(LCP/CLS/FCP/INP/TTFB → sendBeacon)
- ✅ A11y WCAG 2.2 AA(eslint-plugin-jsx-a11y error 级 + skip-link + focus-visible)
- ✅
/api/health+/api/ready返回 200 - ✅ Dockerfile 多阶段构建(node:22-alpine,standalone 输出,EXPOSE 4003)
- ✅
pnpm run typecheck+pnpm run lint通过(0 errors)
AI Agent: ai16 (admin-portal remote) Branch: admin-portal-arbitration-done-TaFkyb Coordinator: coord-ai