Files
WonderQ-Project/docs/concierge-api.md
duanshuwen 7ba92f3c5d feat: 新增客片案例与团队共创模块,重构玩法与配置
- 新增客片案例全流程功能,包含前台页面、管理端配置、后端API、数据库迁移与文档
- 新增团队共创详情页面与对应接口
- 重构玩法模块:将静态playData替换为动态API获取,拆分类型定义到playTypes.ts
- 优化vite构建配置与环境变量处理,调整详情页操作栏文案
- 更新全量相关文档与配图,新增客片案例、团队共创API文档
- 新增测试用例,完善数据归一化逻辑
- 调整路由配置与环境变量示例文件
2026-08-19 20:23:04 +08:00

324 lines
11 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.

# 管家管理 Admin API
> 适用端:`WonderQ-Admin`、`WonderQ-Admin-UI`。
>
> 状态:已实现。本文档约定 `WonderQ-Admin`、`WonderQ-Admin-UI` 的管家管理接口,并记录 MiniAPP 使用的对应 Public API。
## 领域边界
管家管理只维护管家顾问卡片资料:
- 头像、姓名和职位。
- 服务详情条目,包括图标和说明文案。
- 添加管家时展示的二维码。
- 顾问启用状态和展示顺序。
本接口不负责:
- 需求线索、客户、客服会话或登录。
- 订单、预订、商品和商品图片关联。
- 管家页面 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` 或省略字段。
- 失败响应沿用 Admin API 约定,包含 `message``code` 和可选的 `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<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` 使用的图标名称,例如 `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 <admin-jwt>
```
成功响应:
```json
{
"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 <admin-jwt>
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` 和新建的 `ConciergeAdvisorRecord`。未传 `sortOrder` 时追加到当前最大顺序之后。
### 编辑顾问
```http
PATCH /api/admin/concierge/advisors/{advisorId}
Authorization: Bearer <admin-jwt>
Content-Type: application/json
```
请求体为 `ConciergeAdvisorPatch`,只更新提交的字段。例如只更新服务详情:
```json
{
"details": [
{ "icon": "calendar", "label": "服务经验10年" },
{ "icon": "navigate", "label": "擅长领域:亲子与自然探索" }
]
}
```
成功返回更新后的 `ConciergeAdvisorRecord`。不存在的顾问返回 `404 CONCIERGE_ADVISOR_NOT_FOUND`
### 删除顾问
```http
DELETE /api/admin/concierge/advisors/{advisorId}
Authorization: Bearer <admin-jwt>
```
删除成功返回:
```json
{ "id": "advisor-001" }
```
删除后应重新规范化剩余顾问的 `sortOrder`,从 `0` 开始连续编号。若业务要求至少保留一名启用顾问,服务端在删除最后一名启用顾问时返回 `409 CONCIERGE_LAST_ACTIVE_ADVISOR`,否则允许删除并由前台处理空状态。
### 调整顾问顺序
```http
PATCH /api/admin/concierge/advisors/reorder
Authorization: Bearer <admin-jwt>
Content-Type: application/json
```
请求必须完整包含当前全部顾问 ID不能重复
```json
{ "itemIds": ["advisor-002", "advisor-001"] }
```
成功响应:
```json
{ "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[];
};
```
顾问列表为空时返回 `{ "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 模型、`0016_concierge_advisors` 迁移、schema、Admin/Public 路由、序列化和审计日志;由 `WonderQ-Admin-UI` 提供 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)