feat(mini-app, docs): 新增小程序详情页,更新项目文档与规范

新增全套关联组件:DetailHero、DetailOverviewCard、DetailInfoCard、DetailMediaGallery、DetailActionBar及ConciergeContactSheet。
新增详情页数据处理工具类detailPresentation.ts,处理商品展示数据的格式化与默认值兼容。
更新项目文档:补充三个领域API补充文档引用,优化AGENTS.md中的前端代码规范与docs文档列表。
This commit is contained in:
duanshuwen
2026-08-17 23:28:07 +08:00
parent 548f91c37f
commit ca6f9397e0
17 changed files with 1351 additions and 7 deletions

301
docs/concierge-api.md Normal file
View File

@@ -0,0 +1,301 @@
# 管家管理 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)