Files
WonderQ-Project/docs/concierge-api.md
duanshuwen d3a246a873 docs: 统一业务资源ID为服务端生成的稳定UUID
更新所有业务API文档,明确持久化资源的正式ID必须为服务端生成的稳定UUID,本地调试或接口失败时可使用语义ID作为fallback。新增数据库迁移脚本0022_opaque_ids,用于将历史语义ID转换为稳定UUID,并同步外键关联、详情记录的key字段以及审计日志的实体ID引用。新增该迁移的单元测试用例,验证ID替换与关联数据同步的逻辑正确性。调整MiniAPP前端代码,优化导航工具函数的格式,移除废弃函数并修改首页跳转逻辑,使用接口返回的UUID作为详情跳转参数。
2026-08-19 22:28:13 +08:00

12 KiB
Raw Blame History

管家管理 Admin API

适用端:WonderQ-AdminWonderQ-Admin-UI

状态:已实现。本文档约定 WonderQ-AdminWonderQ-Admin-UI 的管家管理接口,并记录 MiniAPP 使用的对应 Public API。

所有 JSON 响应遵循 三端统一 API 响应契约,成功业务对象位于 data,失败时 datanull

Admin 顾问记录的 id 为服务端生成的稳定 UUID 字符串MiniAPP Public 响应按现有契约不返回顾问 ID。不要使用姓名、角色或本地 mock ID 作为正式顾问标识。

领域边界

管家管理只维护管家顾问卡片资料:

  • 头像、姓名和职位。
  • 服务详情条目,包括图标和说明文案。
  • 添加管家时展示的二维码。
  • 顾问启用状态和展示顺序。

本接口不负责:

  • 需求线索、客户、客服会话或登录。
  • 订单、预订、商品和商品图片关联。
  • 管家页面 Hero 文案和服务原则内容。

当前 WonderQ-MiniAPP/src/pages/concierge/index.vue 中的 Hero 和服务原则仍是本地内容;顾问卡片由 Admin UI 维护MiniAPP 通过独立的 Public API 消费已启用顾问。

接口清单

API 前缀为 /api/admin,除登录接口外均需要后台 JWT。

方法 路径 用途
GET /api/admin/concierge/advisors 获取全部管家顾问
POST /api/admin/concierge/advisors 新增管家顾问
PATCH /api/admin/concierge/advisors/{advisorId} 编辑管家顾问
DELETE /api/admin/concierge/advisors/{advisorId} 删除管家顾问
PATCH /api/admin/concierge/advisors/reorder 调整管家顾问展示顺序

MiniAPP Public API

方法 路径 用途
GET /api/public/concierge/advisors 获取已启用的管家顾问展示数据

通用约定

  • 请求和响应使用 JSON字段使用 camelCase。
  • 所有管理接口需要 Authorization: Bearer <admin-jwt>
  • GET 默认返回启用和停用的全部顾问,按 sortOrder 升序返回,供管理端完整维护。
  • 创建、编辑、删除和排序成功后写入审计日志,再提交事务。
  • 变更接口返回最新顾问对象;排序接口返回排序后的 items
  • ID 由后端生成并作为非空字符串返回。
  • 空列表返回 [],不能返回 null 或省略字段。
  • 成功结果放入 data;失败返回数字 codemsgdata: null,可选 errorCodedetails

数据类型

conciergeTypes.ts 是 MiniAPP 渲染模型,当前没有 idisActivesortOrder。为了支持 Admin UI 编辑、删除和排序Admin API 在渲染字段之外增加管理元数据MiniAPP Public API 适配时可以移除这些管理字段。

type ConciergeDetail = {
  icon: string;
  label: string;
};

type ConciergeAdvisorContent = {
  avatar: string;
  name: string;
  role: string;
  details: ConciergeDetail[];
  qrImage: string;
};

type ConciergeAdvisorRecord = ConciergeAdvisorContent & {
  id: string;
  isActive: boolean;
  sortOrder: number;
  createdAt: string;
  updatedAt: string;
};

type ConciergeAdvisorCreate = {
  avatar: string;
  name: string;
  role: string;
  details: ConciergeDetail[];
  qrImage: string;
  isActive?: boolean;
  sortOrder?: number;
};

type ConciergeAdvisorPatch = Partial<ConciergeAdvisorCreate>;

type ConciergeAdvisorListResponse = {
  advisors: ConciergeAdvisorRecord[];
};

type ConciergeReorderRequest = {
  itemIds: string[];
};

字段约束

字段 类型 必填 约束和用途
id string 响应必填 顾问稳定标识由后端生成。Admin UI 不使用姓名作为编辑、删除或 React key
avatar string 顾问头像 URL前台按圆形头像展示。
name string 顾问姓名,去除首尾空白后不得为空。
role string 顾问职位或英文职称,去除首尾空白后不得为空。
details ConciergeDetail[] 服务详情列表,保留数组顺序;允许为空数组。
details[].icon string uni-icons 使用的图标名称,例如 calendarnavigate
details[].label string 服务详情文案,去除首尾空白后不得为空。
qrImage string 添加管家时展示的二维码图片 URL。接口只保存图片 URL不保存二维码原始 payload。
isActive boolean 响应必填 是否在已发布前台内容中展示,创建默认 true
sortOrder number 响应必填 非负整数,数值越小越靠前;创建时未传则追加到末尾。
createdAt string 响应必填 ISO 8601 创建时间。
updatedAt string 响应必填 ISO 8601 最后更新时间。

头像和二维码应使用已上传素材的最终 URL。服务端应校验必填文本、URL 格式、details 数组结构和 sortOrder 非负整数;具体文本最大长度由后端 schema 统一定义,并同步到 Admin UI 表单校验。

接口详情

获取全部顾问

GET /api/admin/concierge/advisors
Authorization: Bearer <admin-jwt>

成功响应:

{
  "code": 200,
  "msg": "success",
  "data": {
    "advisors": [
      {
        "id": "advisor-001",
        "avatar": "https://example.test/assets/advisor-avatar.jpg",
        "name": "示例顾问",
        "role": "SENIOR TRAVEL ADVISOR",
        "details": [
          { "icon": "calendar", "label": "服务经验8年" },
          { "icon": "navigate", "label": "擅长领域:自然探索" }
        ],
        "qrImage": "https://example.test/assets/advisor-qr.png",
        "isActive": true,
        "sortOrder": 0,
        "createdAt": "2026-01-01T00:00:00Z",
        "updatedAt": "2026-01-01T00:00:00Z"
      }
    ]
  }
}

新增顾问

POST /api/admin/concierge/advisors
Authorization: Bearer <admin-jwt>
Content-Type: application/json

请求:

{
  "avatar": "https://example.test/assets/advisor-avatar.jpg",
  "name": "示例顾问",
  "role": "SENIOR TRAVEL ADVISOR",
  "details": [
    { "icon": "calendar", "label": "服务经验8年" },
    { "icon": "navigate", "label": "擅长领域:自然探索" }
  ],
  "qrImage": "https://example.test/assets/advisor-qr.png",
  "isActive": true
}

成功返回 201data 内新建的 ConciergeAdvisorRecord。未传 sortOrder 时追加到当前最大顺序之后。

编辑顾问

PATCH /api/admin/concierge/advisors/{advisorId}
Authorization: Bearer <admin-jwt>
Content-Type: application/json

请求体为 ConciergeAdvisorPatch,只更新提交的字段。例如只更新服务详情:

{
  "details": [
    { "icon": "calendar", "label": "服务经验10年" },
    { "icon": "navigate", "label": "擅长领域:亲子与自然探索" }
  ]
}

成功返回 data 内更新后的 ConciergeAdvisorRecord。不存在的顾问返回 404,业务码为 CONCIERGE_ADVISOR_NOT_FOUND

删除顾问

DELETE /api/admin/concierge/advisors/{advisorId}
Authorization: Bearer <admin-jwt>

删除成功返回:

{
  "code": 200,
  "msg": "success",
  "data": { "id": "advisor-001" }
}

删除后应重新规范化剩余顾问的 sortOrder,从 0 开始连续编号。若业务要求至少保留一名启用顾问,服务端在删除最后一名启用顾问时返回 409 CONCIERGE_LAST_ACTIVE_ADVISOR,否则允许删除并由前台处理空状态。

调整顾问顺序

PATCH /api/admin/concierge/advisors/reorder
Authorization: Bearer <admin-jwt>
Content-Type: application/json

请求必须完整包含当前全部顾问 ID不能重复

{ "itemIds": ["advisor-002", "advisor-001"] }

成功响应:

{
  "code": 200,
  "msg": "success",
  "data": { "items": [] }
}

其中 items 为更新 sortOrder 后的 ConciergeAdvisorRecord[]。缺少 ID、包含未知 ID 或出现重复 ID 时返回 400 CONCIERGE_REORDER_INVALID

MiniAPP Public API

GET /api/public/concierge/advisors

无需鉴权。接口只返回 isActive === true 的顾问,按 sortOrder 升序排列,并移除 idsortOrder、时间和其他管理字段:

type PublicConciergeResponse = {
  advisors: ConciergeAdvisorContent[];
};

顾问列表为空时返回 data: { "advisors": [] }。MiniAPP 应在请求期间展示 loading失败时展示错误和重试入口响应字段缺失时通过归一化函数过滤无效顾问。

图片与素材

Admin UI 使用现有素材上传接口获取图片 URL

POST /api/admin/media-assets/upload

建议管家页面上传时使用 group=concierge,头像和二维码分别将返回的 url 写入 avatarqrImage。API 不接受 base64 图片,也不在管家表中复制图片二进制内容。

二维码必须作为图片 URL 保存,不能把真实个人微信号、手机号或二维码 payload 写入文档、前端类型或接口日志。

Admin UI 对接要求

Admin UI 应按以下方式调用:

  1. 进入管家页面时调用 GET /api/admin/concierge/advisors,按 sortOrder 渲染全部顾问。
  2. 列表展示头像、姓名、职位、详情数量、启用状态和编辑/删除操作。
  3. 新增和编辑表单维护头像、姓名、职位、详情列表、二维码和启用状态。
  4. details 使用可增删的重复字段编辑器,提交时保留用户排列顺序,不把多个详情拼成一个字符串。
  5. 上移或下移顾问时提交完整顾问 ID 列表,不直接修改本地 sortOrder 后假设保存成功。
  6. 删除前要求二次确认;处理最后一名启用顾问的 409 提示。
  7. 处理 4014044094225xx,保存或排序请求进行中禁用重复提交。
  8. 图片上传失败时不得提交旧草稿中的空 URL表单应保留其他已填写字段允许用户重试上传。

建议的 Admin UI API 封装函数:

getConciergeAdvisors();
createConciergeAdvisor(input: ConciergeAdvisorCreate);
updateConciergeAdvisor(advisorId: string, input: ConciergeAdvisorPatch);
deleteConciergeAdvisor(advisorId: string);
reorderConciergeAdvisors(itemIds: string[]);

与当前 MiniAPP 类型的映射

当前 conciergeTypes.tsConciergeAdvisor 仅包含前台渲染字段:

type ConciergeAdvisor = {
  avatar: string;
  name: string;
  role: string;
  details: Array<{ icon: string; label: string }>;
  qrImage: string;
};

Admin API 返回的 ConciergeAdvisorRecord 可以通过以下方式映射为前台模型:

const advisor: ConciergeAdvisor = {
  avatar: record.avatar,
  name: record.name,
  role: record.role,
  details: record.details,
  qrImage: record.qrImage,
};

只有 isActive === true 的记录进入已发布 Public 内容;idsortOrdercreatedAtupdatedAt 属于管理元数据,不应要求前台组件展示。

后端落地边界

当前实现由 WonderQ-Admin 提供 ORM 模型、0016_concierge_advisors 迁移、schema、Admin/Public 路由、序列化和审计日志;由 WonderQ-Admin-UI 提供 API 类型、请求封装、管家列表、表单、图片上传、启停、排序和删除确认;由 WonderQ-MiniAPP 调用 Public API 并归一化顾问数据。Hero 和服务原则仍不在管家表中维护。

相关文档: