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

22 KiB
Raw Blame History

title status created origin plan_depth
feat: Admin UI + Creator Review Status + Order Payment active 2026-06-21 docs/plans/2026-06-21-001-feat-admin-review-hermes-sync-plan.md (U7 前端缺失) 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 已返回 reviewStatusqrCodeUrlsyncedAtreviewNote,但前端 renderCreatorRoles() 只显示 statusrunning/stopped创作者无法知道自己角色处于待审核/已通过/已同步/被驳回哪个状态,也看不到驳回原因和同步后的二维码。

  3. 付款流程是前端 Mockprisma/schema.prisma 定义了 Order 模型但无任何路由,app.jspayRole() 仅切换 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 存储在 localStorageeternal_ai_admin_token,与用户 tokeneternal_ai_token)隔离。管理员 state 存储在 eternal_ai_admin_state,与用户 stateeternal_ai_state)隔离。

理由: 管理员和用户可能是同一人token 隔离避免互相覆盖。管理员 API 使用独立的 ADMIN_JWT_SECRETtoken 不可混用。

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 优先

角色卡片状态标签优先显示 reviewStatuspending_review/approved/rejected/syncing/synced/failed仅当 reviewStatussynced 时才显示 statusrunning/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-42adminApi 镜照此模式但读取 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-339renderRoleLibrary()
  • 创作者角色卡片渲染模式(app.js:415-450renderCreatorRoles()
  • 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-617showHermesDeployGuide()

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.jsauthMiddleware 使用)
  • 现有错误处理模式(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. 创作者卡片状态展示复杂性: reviewStatusstatus 两个状态字段的展示逻辑需仔细设计,避免混淆。缓解: 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 创建订单 → 显示真实二维码 → 用户扫码绑定