Files
WonderQ-Project/docs/concierge-api.md
duanshuwen ca6f9397e0 feat(mini-app, docs): 新增小程序详情页,更新项目文档与规范
新增全套关联组件:DetailHero、DetailOverviewCard、DetailInfoCard、DetailMediaGallery、DetailActionBar及ConciergeContactSheet。
新增详情页数据处理工具类detailPresentation.ts,处理商品展示数据的格式化与默认值兼容。
更新项目文档:补充三个领域API补充文档引用,优化AGENTS.md中的前端代码规范与docs文档列表。
2026-08-17 23:28:07 +08:00

302 lines
10 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-MiniAPP/src/pages/concierge/components/conciergeTypes.ts` 定义管家顾问资料,以及管理端需要的查询、维护和排序接口。它不是 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` | 调整管家顾问展示顺序 |
## 通用约定
- 请求和响应使用 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`。
## 图片与素材
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、路由、序列化和审计日志;由 `WonderQ-Admin-UI` 补充 API 类型、请求封装、管家列表、表单和排序交互。当前后端没有 `/api/admin/concierge/*` 路由,Admin UI 的管家入口仍是空态,MiniAPP 顾问数据仍在 `index.vue` 中静态定义。
相关文档:
- [Admin API 主契约](./admin-api-requirements.md)
- [玩法管理 Admin API](./wanfa-api.md)
- [管家数据类型](../WonderQ-MiniAPP/src/pages/concierge/components/conciergeTypes.ts)