EternalAI/docs/plans/2026-06-21-002-feat-admin-u...

462 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "feat: Admin UI + Creator Review Status + Order Payment"
status: active
created: 2026-06-21
origin: docs/plans/2026-06-21-001-feat-admin-review-hermes-sync-plan.md (U7 前端缺失)
plan_depth: standard
---
# Plan: 管理员后台 UI + 创作者审核状态展示 + 订单付款流程
## Summary
补齐 EternalAI 平台的三个核心功能缺口:(1) 管理员后台 UI让管理员可通过浏览器完成角色审核和 Hermes 同步发起;(2) 创作者角色卡片审核状态展示,让创作者看到自己角色的审核进度和二维码;(3) Order 订单后端 API + 前端付款流程对接,替换当前的纯前端 Mock。微信 OAuth 和收入/提现后端不在本次范围内。
---
## Problem Frame
当前平台存在三个阻断性缺口:
1. **管理员无法通过浏览器操作**:后端审核 API`/api/admin/reviews/*`)、同步 API`/api/admin/sync/*`)、配置 API`/api/admin/config/*`)均已就绪,但 `index.html` 中无任何 admin 视图,`app.js` 中无 admin 相关代码。管理员只能通过 curl/Postman 操作,无法实际使用。
2. **创作者看不到审核状态**:后端 `GET /api/roles/my/roles` 已返回 `reviewStatus`、`qrCodeUrl`、`syncedAt`、`reviewNote`,但前端 `renderCreatorRoles()` 只显示 `status`running/stopped创作者无法知道自己角色处于待审核/已通过/已同步/被驳回哪个状态,也看不到驳回原因和同步后的二维码。
3. **付款流程是前端 Mock**`prisma/schema.prisma` 定义了 Order 模型但无任何路由,`app.js` 的 `payRole()` 仅切换 DOM 显示,无 API 调用、无订单创建。用户"付款"后看不到真实二维码,平台无法记录订阅关系。
---
## Requirements
### 功能需求
- **R1**: 管理员可通过浏览器登录后台(独立登录入口,与用户登录隔离)
- **R2**: 管理员可查看待审核角色列表,支持按状态筛选
- **R3**: 管理员可查看角色详情,通过或驳回(驳回需填写原因)
- **R4**: 管理员可对已通过角色发起 Hermes 同步,填写 profile 名、model key、服务商等参数
- **R5**: 同步成功后管理员可看到二维码和 profileId
- **R6**: 管理员可查看同步状态列表approved/syncing/synced/failed
- **R7**: 管理员可配置系统参数HERMES_WEBHOOK_URL 等)
- **R8**: 创作者角色卡片显示审核状态标签(待审核/已通过/同步中/已同步/同步失败/已驳回)
- **R9**: 被驳回角色卡片显示驳回原因
- **R10**: 已同步角色卡片显示二维码图片和「转发二维码」按钮
- **R11**: 后端提供 Order CRUD API创建/查询)
- **R12**: 前端付款流程调用后端 API 创建订单,付款成功后展示真实二维码
### 非功能需求
- 管理员 token 与用户 token 隔离存储,不互相干扰
- 管理员 UI 遵循现有视图路由模式showView + views 注册)
- Order API 需用户认证authMiddleware
- 付款流程暂不对接真实支付网关创建订单即视为已付款status=paid返回角色二维码
### 范围边界
**在范围内**
- 管理员后台 UI登录、审核列表、审核详情、同步表单、同步状态、系统配置
- 创作者角色卡片审核状态展示
- Order 后端 API + 前端付款流程对接
**不在范围内**Deferred
- 微信 OAuth 真实接入hermes-server/src/routes/bind.js 仍为占位符)
- 收入/提现后端 API仍为前端 Mock
- 真实支付网关对接(本次创建订单即视为已付款)
- 角色删除/上下架切换
- 定时任务调度逻辑Hermes Agent 侧实现)
---
## Key Technical Decisions
### KTD1: 管理员 token 独立存储
管理员 token 存储在 `localStorage``eternal_ai_admin_token`,与用户 token`eternal_ai_token`)隔离。管理员 state 存储在 `eternal_ai_admin_state`,与用户 state`eternal_ai_state`)隔离。
**理由**: 管理员和用户可能是同一人token 隔离避免互相覆盖。管理员 API 使用独立的 `ADMIN_JWT_SECRET`token 不可混用。
### KTD2: 管理员入口通过 URL hash 访问
管理员后台不显示在底部 TabBar 中(普通用户不可见)。入口通过 URL hash `#admin` 直达,或在首页添加隐藏入口(如长按 logo
**理由**: 管理员后台是内部工具不应暴露给普通用户。URL hash 方式简单直接,管理员可收藏书签。
### KTD3: Order 状态简化为 pending → paid
本次 Order 状态仅 `pending`(待付款)和 `paid`(已付款)。不对接真实支付网关,创建订单时前端直接标记为 `paid`。后续对接真实支付时扩展 `failed`/`refunded` 状态。
**理由**: 本次目标是让付款流程有后端记录,而非实现完整支付系统。简化状态机避免过度设计。
### KTD4: 付款后返回角色 qrCodeUrl
Order 创建(付款)成功后,后端返回角色的 `qrCodeUrl`(来自 Role 表的 `qrCodeUrl` 字段,由 Hermes 同步时写入)。前端展示该二维码供用户扫码绑定微信。
**理由**: 用户付款的目的是获取角色二维码以扫码绑定。`qrCodeUrl` 已在同步流程中存储到 Role 表,直接返回即可。若角色未同步(无 qrCodeUrl则返回错误提示。
### KTD5: 创作者角色卡片状态基于 reviewStatus 优先
角色卡片状态标签优先显示 `reviewStatus`pending_review/approved/rejected/syncing/synced/failed仅当 `reviewStatus``synced` 时才显示 `status`running/stopped作为运行状态。
**理由**: `reviewStatus` 是角色的生命周期主状态,`status` 仅在同步完成后才有意义。避免两个状态标签混淆。
---
## High-Level Technical Design
### 管理员后台视图架构
```
用户访问 #admin
检查 adminToken 是否有效GET /api/admin-auth/me
有效 → 显示审核列表页 无效 → 显示管理员登录页
↓ ↓
点击角色 → 审核详情页 登录成功 → 存储 adminToken → 显示审核列表页
通过 → 返回列表页(状态更新)
驳回 → 弹窗填写原因 → 返回列表页
同步 → 同步表单页 → 提交 → 显示二维码 → 返回列表页
```
### Order 付款流程
```
用户在角色详情页点击「立即订阅」
POST /api/orders { roleId, amount }
后端校验角色已同步reviewStatus=synced, qrCodeUrl 非空)
创建 Order 记录status=paid
返回 { order, role: { qrCodeUrl } }
前端展示二维码
```
---
## Implementation Units
### U1. 管理员登录页 + 认证状态管理
**Goal**: 实现管理员登录页面和独立的 token/state 管理机制。
**Requirements**: R1
**Dependencies**: 无(后端 API 已就绪)
**Files**:
- `index.html` — 新增 `#admin-login` 视图和 `#admin-reviews` 视图骨架
- `app.js` — 新增管理员 state 管理、adminApi 封装、登录逻辑、hash 路由
**Approach**:
1.`index.html` 中新增两个视图:
- `#admin-login`:管理员登录表单(账号、密码、登录按钮)
- `#admin-reviews`:管理员后台主视图(包含审核列表、详情、同步等子面板,通过内部 Tab 切换)
2.`app.js` 中新增:
- `adminState` 对象和 `loadAdminState()`/`saveAdminState()` 函数key: `eternal_ai_admin_state`
- `getAdminToken()`/`setAdminToken()` 函数key: `eternal_ai_admin_token`
- `adminApi(path, options)` 封装(自动添加管理员 Authorization 头)
- `handleHashRoute()` 函数:监听 `hashchange`,当 hash 为 `#admin` 时检查管理员登录状态并显示对应视图
- `handleAdminLogin(formData)` 函数:调用 `POST /api/admin-auth/login`,成功后存储 token 并跳转审核列表
3.`views` 对象中注册 `'admin-login'``'admin-reviews'`
4.`viewLabels` 中添加对应标签
**Patterns to follow**:
- 用户登录表单模式(`app.js:1189-1232` 行的 auth 表单提交)
- `api()` 封装模式(`app.js:4-42` 行adminApi 镜照此模式但读取 adminToken
- 视图注册模式(`app.js:112-122` 行 views 对象)
**Test scenarios**:
- **Happy path**: 管理员访问 `#admin`,输入正确账号密码,登录成功跳转审核列表页
- **Error path**: 管理员输入错误密码,显示错误提示
- **Edge case**: 管理员已登录时访问 `#admin`,直接跳转审核列表页(不显示登录表单)
- **Edge case**: 非管理员用户访问 `#admin`,显示登录页(用户 token 不能用于管理员 API
- **Integration**: 登录后调用 `GET /api/admin-auth/me` 验证 token 有效
**Verification**: 管理员可通过 `#admin` URL 访问登录页,登录成功后进入审核列表页,刷新页面后仍保持登录状态。
---
### U2. 管理员审核列表页 + 角色详情审核页
**Goal**: 实现审核列表(支持状态筛选)和角色详情审核页(通过/驳回)。
**Requirements**: R2, R3, R6
**Dependencies**: U1
**Files**:
- `index.html` — 在 `#admin-reviews` 视图中添加审核列表和详情面板的 HTML 结构
- `app.js` — 新增 `renderAdminReviews()`、`renderAdminReviewDetail()`、`handleApprove()`、`handleReject()` 函数
- `e2e/admin-sync-flow.spec.js` — 扩展 E2E 测试覆盖管理员审核 UI 流程(可选,已有 API 级测试)
**Approach**:
1. 审核列表页结构:
- 顶部状态筛选 Tab全部/待审核/已通过/已同步/同步失败/已驳回)
- 角色卡片列表(头像、名称、创作者、审核状态标签、创建时间)
- 点击卡片进入详情页
2. 详情审核页结构:
- 角色完整信息(头像、名称、描述、人设字段)
- 当前审核状态
- 操作按钮区:
- `pending_review` 状态:显示「通过」和「驳回」按钮
- `approved` 状态:显示「发起同步」按钮(跳转 U3
- `synced` 状态:显示二维码图片和 profileId
- `rejected` 状态:显示驳回原因
- `failed` 状态:显示「重新同步」按钮
3. 驳回流程:点击「驳回」→ 弹窗输入原因 → 调用 `POST /api/admin/reviews/:roleId/reject`
4. 列表数据通过 `GET /api/admin/reviews?status=xxx` 获取,支持分页
**Patterns to follow**:
- 角色库列表渲染模式(`app.js:302-339` 行 `renderRoleLibrary()`
- 创作者角色卡片渲染模式(`app.js:415-450` 行 `renderCreatorRoles()`
- Tab 切换模式(`app.js:409-431` 行 creator-center 的 center-tabs
**Test scenarios**:
- **Happy path**: 管理员登录后看到待审核角色列表,点击角色进入详情,点击「通过」后角色状态变为 approved
- **Happy path**: 管理员点击「驳回」,输入原因,角色状态变为 rejected列表中显示驳回原因
- **Edge case**: 切换状态筛选 Tab列表正确过滤
- **Error path**: 对已审核角色再次操作,后端返回 400前端显示错误提示
- **Integration**: 审核通过后,角色出现在「已通过」筛选列表中
**Verification**: 管理员可查看各状态角色列表,可审核通过或驳回角色,操作后列表实时更新。
---
### U3. 管理员同步表单 + 二维码展示 + 系统配置
**Goal**: 实现同步发起表单、同步成功后二维码展示、系统配置页面。
**Requirements**: R4, R5, R7
**Dependencies**: U2
**Files**:
- `index.html` — 在 `#admin-reviews` 视图中添加同步表单面板和配置面板的 HTML
- `app.js` — 新增 `renderSyncForm(roleId)`、`handleSyncSubmit()`、`renderSyncResult()`、`renderAdminConfig()`、`handleConfigUpdate()` 函数
**Approach**:
1. 同步表单(在详情页中 `approved` 状态时显示):
- 字段profile 名(必填)、主 model key必填、主服务商必填下拉选择 openrouter/siliconflow 等)、多媒体 model key、多媒体服务商、定时任务开关、webhook URL 覆盖(可选)
- 提交按钮:调用 `POST /api/admin/sync/:roleId`
- 提交后显示 loading 状态
2. 同步结果展示:
- 成功:显示二维码图片(`qrCodeUrl`、profileId、「复制二维码链接」按钮
- 失败:显示错误信息、「重新同步」按钮
3. 系统配置页(管理员后台第三个 Tab
- 列出所有系统配置(`GET /api/admin/config`
- 每项配置可编辑(`PUT /api/admin/config/:key`
- 敏感配置SYNC_SECRET显示 `***` 且不可编辑
- 重点配置HERMES_WEBHOOK_URL、HERMES_ADMIN_TOKEN 等
**Patterns to follow**:
- 角色创建表单的 stepper 模式(`app.js:633-936` 行)
- 设置表单提交模式(`app.js:1233-1255` 行 settings 表单)
- Hermes 部署指南弹窗模式(`app.js:546-617` 行 `showHermesDeployGuide()`
**Test scenarios**:
- **Happy path**: 管理员在 approved 角色详情页填写同步参数,提交后显示二维码和 profileId
- **Error path**: 同步失败Hermes 不可达),显示错误信息
- **Edge case**: webhook URL 留空时使用全局配置
- **Edge case**: 同步中状态syncing时按钮禁用
- **Happy path**: 管理员在配置页修改 HERMES_WEBHOOK_URL保存成功
- **Error path**: 尝试修改 SYNC_SECRET返回 403 禁止操作
**Verification**: 管理员可发起同步并看到二维码,可修改系统配置。
---
### U4. 创作者角色卡片审核状态展示
**Goal**: 修改创作者角色卡片,展示审核状态、驳回原因、二维码。
**Requirements**: R8, R9, R10
**Dependencies**: 无(后端已返回所需字段)
**Files**:
- `app.js` — 修改 `renderCreatorRoles()` 函数(约 415-450 行)
- `styles.css` — 新增审核状态标签样式
- `e2e/creator.spec.js` — 扩展测试覆盖审核状态展示
**Approach**:
1. 修改 `renderCreatorRoles()` 中的卡片渲染逻辑:
- 根据 `reviewStatus` 渲染状态标签(优先于 `status`
- `pending_review` → 灰色标签「待审核」
- `approved` → 蓝色标签「已通过」
- `rejected` → 红色标签「已驳回」+ 显示 `reviewNote`
- `syncing` → 黄色标签「同步中」
- `synced` → 绿色标签「已同步」+ 显示 `status`(运行中/已停止)
- `failed` → 橙色标签「同步失败」
- `synced` 状态角色卡片额外显示:
- 二维码缩略图(`qrCodeUrl`
- 「查看二维码」按钮(点击放大显示)
- 「转发二维码」按钮(复制链接到剪贴板)
- `rejected` 状态角色卡片额外显示:
- 驳回原因文本(`reviewNote`
- 「编辑」按钮高亮提示修改
2. 新增 `showQrCodeModal(qrCodeUrl)` 函数:弹窗显示二维码大图
3. 新增 `copyQrCodeLink(qrCodeUrl)` 函数:复制二维码链接到剪贴板
**Patterns to follow**:
- 现有角色卡片状态标签模式(`role-card__status--${statusClass}`
- Hermes 部署指南弹窗模式(`app.js:546-617` 行)
- `escapeHtml()` 用于所有用户可控内容
**Test scenarios**:
- **Happy path**: 创作者创建角色后,卡片显示「待审核」标签
- **Happy path**: 管理员审核通过后,创作者卡片显示「已通过」标签
- **Happy path**: 同步完成后,创作者卡片显示「已同步」+ 二维码缩略图
- **Happy path**: 被驳回后,创作者卡片显示「已驳回」+ 驳回原因
- **Edge case**: 点击「查看二维码」弹窗显示大图
- **Edge case**: 点击「转发二维码」复制链接到剪贴板
**Verification**: 创作者可在角色列表看到每个角色的审核状态、驳回原因和二维码。
---
### U5. Order 订单后端 API
**Goal**: 实现 Order 模型的 CRUD 后端端点。
**Requirements**: R11
**Dependencies**: 无Order 模型已在 schema 中定义)
**Files**:
- `src/routes/orders.js` — 新建订单路由文件
- `server.js` — 挂载 `/api/orders` 路由
- `e2e/orders.spec.js` — 新建订单 E2E 测试
**Approach**:
1. 新建 `src/routes/orders.js`,实现以下端点:
- `POST /api/orders` — 创建订单(付款)
- 认证:`authMiddleware`
- 请求体:`{ roleId, amount }`
- 逻辑:
- 校验 roleId 存在且角色 `reviewStatus='synced'``qrCodeUrl` 非空
- 校验 amount > 0
- 创建 Order 记录status='paid'userId=当前用户)
- 返回 `{ order: { id, roleId, amount, status, createdAt }, role: { qrCodeUrl, displayName } }`
- `GET /api/orders` — 查询当前用户订单列表
- 认证:`authMiddleware`
- 返回:`{ orders: [{ id, roleId, role: { displayName, avatar }, amount, status, createdAt }] }`
- `GET /api/orders/:id` — 查询订单详情
- 认证:`authMiddleware`
- 校验:订单属于当前用户
- 返回:`{ order: { ...完整字段, role: { ...角色信息 } } }`
2.`server.js` 中挂载路由:`app.use('/api/orders', require('./src/routes/orders'));`
3. 错误处理:
- 角色未同步:返回 400 `{ error: '该角色尚未同步,无法订阅' }`
- 角色不存在:返回 404
- 订单不属于当前用户:返回 403
**Patterns to follow**:
- 现有路由模式(`src/routes/roles.js` 的 `authMiddleware` 使用)
- 现有错误处理模式(`res.status(400).json({ error: '...' })`
- Prisma 查询模式(`prisma.order.findMany({ where: { userId }, include: { role: true } })`
**Test scenarios**:
- **Happy path**: 用户对已同步角色发起付款,创建订单成功,返回 qrCodeUrl
- **Error path**: 用户对未同步角色发起付款,返回 400
- **Error path**: 未认证用户发起付款,返回 401
- **Edge case**: 用户查询自己的订单列表,只返回自己的订单
- **Edge case**: 用户查询他人订单,返回 403
- **Integration**: 创建订单后,订单列表中包含该订单
**Verification**: Order API 可创建、查询订单,角色未同步时拒绝创建。
---
### U6. 前端付款流程对接
**Goal**: 修改前端 `payRole()` 函数,调用后端 API 创建订单并展示真实二维码。
**Requirements**: R12
**Dependencies**: U5
**Files**:
- `app.js` — 修改 `payRole()` 函数(约 361-367 行),新增 `renderPaidState()` 函数
- `index.html` — 修改 `#role-detail` 视图中的付款后区域结构
- `e2e/roles.spec.js` — 扩展测试覆盖付款流程
**Approach**:
1. 修改 `payRole()` 函数:
- 调用 `POST /api/orders` 传入 `{ roleId: currentRole.id, amount: currentRole.price }`
- 成功后调用 `renderPaidState(data.role.qrCodeUrl)` 展示二维码
- 失败时 `alert` 错误信息
- 添加 loading 状态(按钮禁用 + 文字变为「处理中…」)
2. 新增 `renderPaidState(qrCodeUrl)` 函数:
- 隐藏「立即订阅」按钮
- 显示「已订阅」区域
- 渲染二维码图片(`<img src="${qrCodeUrl}" alt="角色二维码" />`
- 添加「保存二维码」按钮(长按或下载)
3. 修改 `#role-detail` 视图 HTML
- `#detail-qr` 区域改为可容纳 `<img>` 的容器
- 移除 `qr-placeholder` 占位符
**Patterns to follow**:
- 现有 `api()` 调用模式(`app.js:4-42` 行)
- 现有按钮 loading 模式(禁用 + 文字变化)
- `escapeHtml()` 用于所有动态内容
**Test scenarios**:
- **Happy path**: 用户点击「立即订阅」,调用 API 成功,显示真实二维码
- **Error path**: 角色未同步API 返回 400显示错误提示
- **Error path**: 网络错误,显示「无法连接服务器」
- **Edge case**: 重复点击「立即订阅」按钮第二次点击被禁用loading 状态)
- **Integration**: 付款后刷新页面,角色详情页仍显示已订阅状态(基于订单记录)
**Verification**: 用户付款后看到真实二维码,非占位符。
---
## Risks & Dependencies
### 风险
1. **管理员 UI 复杂度**: 管理员后台涉及多个面板审核列表、详情、同步表单、配置HTML 和 JS 代码量较大。**缓解**: 分三个实现单元U1/U2/U3逐步实现每个单元可独立验证。
2. **Order 付款流程简化**: 本次创建订单即视为已付款,不对接真实支付。后续对接支付网关时需重构。**缓解**: Order 模型已预留 `status` 字段,后续扩展状态机即可。
3. **创作者卡片状态展示复杂性**: `reviewStatus``status` 两个状态字段的展示逻辑需仔细设计,避免混淆。**缓解**: KTD5 明确了优先级规则——`reviewStatus` 优先,仅 `synced` 时显示 `status`
### 依赖
- 后端管理员 API 已全部就绪U1-U3 依赖)
- 后端 `GET /api/roles/my/roles` 已返回所需字段U4 依赖)
- Order 模型已在 Prisma schema 中定义U5 依赖)
- 前端 `payRole()` 函数已存在U6 修改对象)
---
## Deferred to Follow-Up Work
- 微信 OAuth 真实接入hermes-server/src/routes/bind.js 的 `wechat_oauth_placeholder`
- 收入/提现后端 API当前仍为前端 Mock 数据)
- 真实支付网关对接(当前创建订单即视为已付款)
- 角色删除/上下架切换
- 定时任务调度逻辑enableSchedule 标志已传递,但 Hermes Server 无实际调度)
- 管理员后台移动端适配优化(本次优先功能完整性)
---
## System-Wide Impact
- **用户**: 普通用户在角色详情页付款后能看到真实二维码U6
- **创作者**: 创作者可在角色列表看到审核状态和二维码U4
- **管理员**: 管理员可通过浏览器完成审核和同步操作U1-U3
- **数据库**: Order 表开始有数据写入U5
- **API**: 新增 `/api/orders` 路由U5
---
## Acceptance Examples
- **AE1**: 管理员访问 `#admin`,登录后看到待审核角色列表,点击「通过」→ 角色状态变为 approved → 点击「发起同步」→ 填写参数 → 显示二维码
- **AE2**: 创作者创建角色后,在角色列表看到「待审核」标签 → 管理员驳回后,看到「已驳回」+ 驳回原因 → 修改后重新提交,看到「待审核」
- **AE3**: 用户在角色详情页点击「立即订阅」→ 调用 API 创建订单 → 显示真实二维码 → 用户扫码绑定