# 管家管理 Admin API > 适用端:`WonderQ-Admin`、`WonderQ-Admin-UI-Vue`。 > > 状态:已实现。本文档约定 `WonderQ-Admin`、`WonderQ-Admin-UI-Vue` 的管家管理接口,并记录 MiniAPP 使用的对应 Public API。 所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。 Admin 顾问记录的 `id` 为服务端生成的稳定 UUID 字符串;MiniAPP Public 响应按现有契约不返回顾问 ID。不要使用姓名、角色或本地 mock ID 作为正式顾问标识。 ## 领域边界 管家管理只维护管家顾问卡片资料: - 头像、姓名和职位。 - 服务详情条目,包括图标和说明文案。 - 添加管家时展示的二维码。 - 顾问启用状态和展示顺序。 本接口不负责: - 需求线索、客户、客服会话或登录。 - 订单、预订、商品和商品图片关联。 - 管家页面 Hero 文案和服务原则内容。 ## 路线详情关联 路线详情可以关联一名管家顾问。`DetailRecord` 只保存可空的 `conciergeAdvisorId`,不建立数据库外键,也不复制头像、二维码或服务详情。玩法详情由详情接口维护,管家资料仍由本领域的 Admin CRUD 和 Public API 维护。 `GET /api/public/details/{key}` 会在关联顾问存在且启用时嵌入最新的 `conciergeAdvisor`;未配置、顾问已删除或已停用时返回 `conciergeAdvisor: null`,不会阻塞路线详情读取。Admin UI 选择顾问时使用本接口返回的稳定 `id`,清空选择即解除关联。 当前 `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 `。 - `GET` 默认返回启用和停用的全部顾问,按 `sortOrder` 升序返回,供管理端完整维护。 - 创建、编辑、删除和排序成功后写入审计日志,再提交事务。 - 变更接口返回最新顾问对象;排序接口返回排序后的 `items`。 - ID 由后端生成并作为非空字符串返回。 - 空列表返回 `[]`,不能返回 `null` 或省略字段。 - 成功结果放入 `data`;失败返回数字 `code`、`msg`、`data: null`,可选 `errorCode` 和 `details`。 ## 数据类型 `conciergeTypes.ts` 是 MiniAPP 渲染模型,当前没有 `id`、`isActive` 和 `sortOrder`。为了支持 Admin UI 编辑、删除和排序,Admin API 在渲染字段之外增加管理元数据;MiniAPP Public API 适配时可以移除这些管理字段。 ```ts 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; type ConciergeAdvisorListResponse = { advisors: ConciergeAdvisorRecord[]; }; type ConciergeReorderRequest = { itemIds: string[]; }; ``` ## 字段约束 | 字段 | 类型 | 必填 | 约束和用途 | | --- | --- | --- | --- | | `id` | `string` | 响应必填 | 顾问稳定标识,由后端生成。管理端不使用姓名作为编辑、删除或列表 `key`。 | | `avatar` | `string` | 是 | 顾问头像 URL,前台按圆形头像展示。 | | `name` | `string` | 是 | 顾问姓名,去除首尾空白后不得为空。 | | `role` | `string` | 是 | 顾问职位或英文职称,去除首尾空白后不得为空。 | | `details` | `ConciergeDetail[]` | 是 | 服务详情列表,保留数组顺序;允许为空数组。 | | `details[].icon` | `string` | 是 | `uni-icons` 使用的图标名称,例如 `calendar`、`navigate`。 | | `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 表单校验。 ## 接口详情 ### 获取全部顾问 ```http GET /api/admin/concierge/advisors Authorization: Bearer ``` 成功响应: ```json { "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" } ] } } ``` ### 新增顾问 ```http POST /api/admin/concierge/advisors Authorization: Bearer Content-Type: application/json ``` 请求: ```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 } ``` 成功返回 `201` 和 `data` 内新建的 `ConciergeAdvisorRecord`。未传 `sortOrder` 时追加到当前最大顺序之后。 ### 编辑顾问 ```http PATCH /api/admin/concierge/advisors/{advisorId} Authorization: Bearer Content-Type: application/json ``` 请求体为 `ConciergeAdvisorPatch`,只更新提交的字段。例如只更新服务详情: ```json { "details": [ { "icon": "calendar", "label": "服务经验:10年" }, { "icon": "navigate", "label": "擅长领域:亲子与自然探索" } ] } ``` 成功返回 `data` 内更新后的 `ConciergeAdvisorRecord`。不存在的顾问返回 `404`,业务码为 `CONCIERGE_ADVISOR_NOT_FOUND`。 ### 删除顾问 ```http DELETE /api/admin/concierge/advisors/{advisorId} Authorization: Bearer ``` 删除成功返回: ```json { "code": 200, "msg": "success", "data": { "id": "advisor-001" } } ``` 删除后应重新规范化剩余顾问的 `sortOrder`,从 `0` 开始连续编号。若业务要求至少保留一名启用顾问,服务端在删除最后一名启用顾问时返回 `409 CONCIERGE_LAST_ACTIVE_ADVISOR`,否则允许删除并由前台处理空状态。 ### 调整顾问顺序 ```http PATCH /api/admin/concierge/advisors/reorder Authorization: Bearer Content-Type: application/json ``` 请求必须完整包含当前全部顾问 ID,不能重复: ```json { "itemIds": ["advisor-002", "advisor-001"] } ``` 成功响应: ```json { "code": 200, "msg": "success", "data": { "items": [] } } ``` 其中 `items` 为更新 `sortOrder` 后的 `ConciergeAdvisorRecord[]`。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400 CONCIERGE_REORDER_INVALID`。 ### MiniAPP Public API ```http GET /api/public/concierge/advisors ``` 无需鉴权。接口只返回 `isActive === true` 的顾问,按 `sortOrder` 升序排列,并移除 `id`、`sortOrder`、时间和其他管理字段: ```ts type PublicConciergeResponse = { advisors: ConciergeAdvisorContent[]; }; ``` 顾问列表为空时返回 `data: { "advisors": [] }`。MiniAPP 应在请求期间展示 loading,失败时展示错误和重试入口,响应字段缺失时通过归一化函数过滤无效顾问。 ## 图片与素材 Admin UI 使用现有素材上传接口获取图片 URL: ```http POST /api/admin/media-assets/upload ``` 建议管家页面上传时使用 `group=concierge`,头像和二维码分别将返回的 `url` 写入 `avatar` 和 `qrImage`。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. 处理 `401`、`404`、`409`、`422` 和 `5xx`,保存或排序请求进行中禁用重复提交。 8. 图片上传失败时不得提交旧草稿中的空 URL;表单应保留其他已填写字段,允许用户重试上传。 建议的 Admin UI API 封装函数: ```ts getConciergeAdvisors(); createConciergeAdvisor(input: ConciergeAdvisorCreate); updateConciergeAdvisor(advisorId: string, input: ConciergeAdvisorPatch); deleteConciergeAdvisor(advisorId: string); reorderConciergeAdvisors(itemIds: string[]); ``` ## 与当前 MiniAPP 类型的映射 当前 `conciergeTypes.ts` 的 `ConciergeAdvisor` 仅包含前台渲染字段: ```ts type ConciergeAdvisor = { avatar: string; name: string; role: string; details: Array<{ icon: string; label: string }>; qrImage: string; }; ``` Admin API 返回的 `ConciergeAdvisorRecord` 可以通过以下方式映射为前台模型: ```ts const advisor: ConciergeAdvisor = { avatar: record.avatar, name: record.name, role: record.role, details: record.details, qrImage: record.qrImage, }; ``` 只有 `isActive === true` 的记录进入已发布 Public 内容;`id`、`sortOrder`、`createdAt` 和 `updatedAt` 属于管理元数据,不应要求前台组件展示。 ## 后端落地边界 当前实现由 `WonderQ-Admin` 提供 ORM 模型、schema、Admin/Public 路由、序列化和审计日志;由 `WonderQ-Admin-UI-Vue` 提供 API 类型、请求封装、管家列表、表单、图片上传、启停、排序和删除确认;由 `WonderQ-MiniAPP` 调用 Public API 并归一化顾问数据。Hero 和服务原则仍不在管家表中维护。 相关文档: - [Admin API 主契约](./admin-api-requirements.md) - [玩法管理 Admin API](./wanfa-api.md) - [管家数据类型](../WonderQ-MiniAPP/src/pages/concierge/components/conciergeTypes.ts)