# 模块架构设计文档 — admin-portal

> AI：ai16（TS/React · 管理场景域前端 remote）
> 阶段：阶段 2 交付物（仲裁后修订版 v2）
> 日期：2026-07-10
> 关联：
>
> - [阶段 1 理解确认书](./01-understanding.md)
> - [004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §5.4
> - [总统最终裁决](../../../docs/architecture/president-final-rulings.md) §5.1-5.5（ISSUE-044~048）
> - [admin-portal 对接契约](../../architecture/issues/contracts/admin-portal_contract.md)
> - [pending-features P6](../../../docs/architecture/roadmap/pending-features.md)
> - [known-issues §2.16](../../../docs/troubleshooting/known-issues.md)
>   状态：已实现（P2-P6 全阶段交付完成）

---

## 1. 模块内部分层图（admin-portal Remote + Shell 引用 + Gateway + push-gateway）

```mermaid
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 角色）

```javascript
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 动态 import
> - `shared` 全部 `singleton: true`，确保 urql/graphql/react 单例（避免多实例报错）
> - admin-portal 自身实现 `rewrites`（代理 `/api/admin/graphql` 到 api-gateway），standalone 模式可独立运行

## 2. 领域模型（前端视角）

前端不持有业务聚合根，仅持有"视图模型"（ViewModel）和"会话状态"。定义于 `src/types/view-models.ts`。

### 2.1 视图模型清单

```typescript
// 数据范围
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 当前用户与会话

```typescript
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`）：

```typescript
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 转换：

```typescript
// 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`）

```typescript
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 类通知：

```typescript
// 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-next` 14.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-gateway `GET /ws`）

### 8.4 环境切换

`NEXT_PUBLIC_API_MOCKING=enabled` 启用 MSW；`disabled` 使用真实后端。

## 9. 设计令牌三层（`src/app/globals.css`）

### 9.1 Layer 1 Primitive（原始色板）

```css
: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

```css
: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 假设

1. **假设 teacher-bff admin 命名空间 schema 就绪**（ARB-001，contract §2.4 18 operations）：若 ai03 未补齐，admin-portal 用 MSW mock 开发，上游就绪后切换。
2. **假设 teacher-portal Shell 已就绪**：MF 模式依赖 Shell 暴露 GraphQLProvider + 共享依赖。standalone 模式可独立运行。
3. **假设 push-gateway WebSocket 就绪**（ARB-006）：若未就绪，用 mock-socket + 30s 定时器模拟。
4. _*假设 `packages/contracts` ADMIN_* 权限点常量就绪_*（coord 维护）：admin-portal `src/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 全阶段交付（已完成）

1. ✅ **P2 骨架 Phase 1**：配置文件 + 设计令牌 + 类型定义
2. ✅ **P2 骨架 Phase 2**：GraphQL client + auth + permissions + providers + AdminShell + MF Remote 入口 + standalone 壳
3. ✅ **P2 骨架 Phase 3**：MSW mock（handlers + fixtures + browser）+ mock-socket WebSocket mock
4. ✅ **P3 业务**：用户管理（useUsers/useUser/CRUD hooks + UserManagementTable + /admin/users 页面）
5. ✅ **P3 业务**：角色权限矩阵 + 权限点管理 + 视口配置
6. ✅ **P4 业务**：组织管理 + 学校设置 + 班级/教师/学生全局管理
7. ✅ **P5 业务**：审计日志 + WebSocket 实时通知 + 管理仪表盘
8. ✅ **P6 硬化**：A11y + Web Vitals + Dockerfile + /api/health + /api/ready + 质量校验
9. ✅ **文档回写**：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
