--- 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)` 函数: - 隐藏「立即订阅」按钮 - 显示「已订阅」区域 - 渲染二维码图片(`角色二维码`) - 添加「保存二维码」按钮(长按或下载) 3. 修改 `#role-detail` 视图 HTML: - `#detail-qr` 区域改为可容纳 `` 的容器 - 移除 `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 创建订单 → 显示真实二维码 → 用户扫码绑定