- 新增`api_response.py`统一响应封装工具类,提供标准成功/错误响应构造方法 - 重构WonderQ-Admin全局异常处理器,将所有异常转换为标准响应格式 - 修改所有公共和管理端接口的返回逻辑,统一使用`code`(与HTTP状态码一致)、`msg`和`data`的三层结构 - 新增`api-response-contract.md`文档,定义完整的三端统一JSON响应规范 - 更新所有领域API文档,明确业务数据需位于`data`字段内,补充响应格式说明 - 为WonderQ-MiniAPP和WonderQ-Admin-UI新增响应解析逻辑和类型定义,自动完成协议校验和错误处理 - 更新所有测试用例,适配新的响应结构确保接口符合契约要求 - 新增`module-config-api.md`模块配置API文档,补充站点模块配置的接口约定 - 更新项目README文档,调整文档分类顺序将响应契约置于首位
350 lines
12 KiB
Markdown
350 lines
12 KiB
Markdown
# 玩法管理 Admin API
|
||
|
||
> 适用端:`WonderQ-Admin`、`WonderQ-Admin-UI`。
|
||
>
|
||
> 状态:已实现契约。本文件参考 `WonderQ-MiniAPP/src/pages/play/components/playData.ts` 定义玩法分类和路线的数据结构,以及管理端需要的查询、维护和排序接口。它不是 MiniAPP Public API 文档。
|
||
|
||
当前实现:`WonderQ-Admin` 通过迁移 `0015_wanfa` 创建 `WanfaCategory`、`WanfaRoute` 表并导入稳定初始 ID;`WonderQ-Admin-UI` 已接入分类和路线的查询、新增、编辑、删除及排序操作。
|
||
|
||
所有 JSON 响应遵循 [三端统一 API 响应契约](./api-response-contract.md),成功业务对象位于 `data`,失败时 `data` 为 `null`。
|
||
|
||
路线接口只维护分类和路线摘要字段。路线详情不写入 `WanfaRoute`,由独立 `DetailRecord` 通过 `docs/detail-api.md` 管理,详情记录的 `key` 等于路线 ID。首页玩法推荐和玩法页路线点击后统一跳转 `/pages/detail/index?routeId={route.id}`;无关联路线时才回退到需求页。
|
||
|
||
## 领域边界
|
||
|
||
玩法管理只维护玩法分类和路线卡片内容:
|
||
|
||
- 分类名称和展示顺序。
|
||
- 路线标题、副标题、封面、路线数量和需求关键词。
|
||
- 分类与路线的新增、编辑、删除和排序。
|
||
- 首页玩法推荐与分类的关联由首页内容域维护,本域只提供可被关联的分类和路线数据。
|
||
|
||
玩法领域不负责:
|
||
|
||
- 商品、商品详情、预订或订单。
|
||
- 目的地实体和目的地维护。
|
||
- 线索创建或线索跟进。
|
||
- Product、ProductImage 或其他已移除商品关联表。
|
||
|
||
`routeCount` 只是前台卡片展示数量,不是 Product 表的外键或实时关联统计。
|
||
|
||
## 接口清单
|
||
|
||
API 前缀为 `/api/admin`,除登录接口外均需要后台 JWT。
|
||
|
||
| 方法 | 路径 | 用途 |
|
||
| --- | --- | --- |
|
||
| `GET` | `/api/admin/wanfa/categories` | 获取全部玩法分类及其路线 |
|
||
| `POST` | `/api/admin/wanfa/categories` | 新增玩法分类 |
|
||
| `PATCH` | `/api/admin/wanfa/categories/{categoryId}` | 编辑玩法分类 |
|
||
| `DELETE` | `/api/admin/wanfa/categories/{categoryId}` | 删除玩法分类 |
|
||
| `PATCH` | `/api/admin/wanfa/categories/reorder` | 调整玩法分类顺序 |
|
||
| `POST` | `/api/admin/wanfa/categories/{categoryId}/routes` | 新增分类路线 |
|
||
| `PATCH` | `/api/admin/wanfa/categories/{categoryId}/routes/{routeId}` | 编辑分类路线 |
|
||
| `DELETE` | `/api/admin/wanfa/categories/{categoryId}/routes/{routeId}` | 删除分类路线 |
|
||
| `PATCH` | `/api/admin/wanfa/categories/{categoryId}/routes/reorder` | 调整分类内路线顺序 |
|
||
|
||
## 通用约定
|
||
|
||
- 请求和响应使用 JSON,字段使用 camelCase。
|
||
- 所有管理接口需要 `Authorization: Bearer <admin-jwt>`。
|
||
- 创建、编辑、删除和排序成功后写入审计日志,再提交事务。
|
||
- 变更接口返回最新变更对象;排序接口返回排序后的 `items`。
|
||
- ID 由后端生成并作为非空字符串返回。初始化迁移时应优先保留 `playData.ts` 中已有的稳定 ID。
|
||
- 分类和路线的数组顺序就是管理端和前台的展示顺序,接口不要求前端依赖 `sortOrder` 字段。
|
||
- 空集合返回 `[]`,不能返回 `null` 或省略字段。
|
||
- 成功结果放入 `data`;失败返回数字 `code`、`msg`、`data: null`,可选 `errorCode` 和 `details`。
|
||
- 被首页玩法推荐关联的分类不能直接删除;需先调用首页内容域的移除关联接口,否则返回 `409 WANFA_CATEGORY_RECOMMENDED`。
|
||
|
||
## 数据类型
|
||
|
||
以下类型与 `playData.ts` 保持字段兼容。管理端接口不应向前台模型强制增加商品 ID、预订 ID 或数据库关联字段。
|
||
|
||
```ts
|
||
type WanfaConfig = {
|
||
categories: WanfaCategory[];
|
||
};
|
||
|
||
type WanfaCategory = {
|
||
id: string;
|
||
label: string;
|
||
routes: WanfaRoute[];
|
||
};
|
||
|
||
type WanfaRoute = {
|
||
id: string;
|
||
title: string;
|
||
subtitle: string;
|
||
image: string;
|
||
routeCount: number;
|
||
demandKeyword: string;
|
||
};
|
||
|
||
type WanfaCategoryCreate = {
|
||
label: string;
|
||
};
|
||
|
||
type WanfaCategoryPatch = {
|
||
label?: string;
|
||
};
|
||
|
||
type WanfaRouteCreate = {
|
||
title: string;
|
||
subtitle: string;
|
||
image: string;
|
||
routeCount: number;
|
||
demandKeyword: string;
|
||
};
|
||
|
||
type WanfaRoutePatch = Partial<WanfaRouteCreate>;
|
||
|
||
type WanfaReorderRequest = {
|
||
itemIds: string[];
|
||
};
|
||
```
|
||
|
||
## 字段约束
|
||
|
||
| 字段 | 类型 | 必填 | 约束和用途 |
|
||
| --- | --- | --- | --- |
|
||
| `category.id` | `string` | 响应必填 | 分类稳定标识,例如 `family-route`。创建时由后端生成。 |
|
||
| `category.label` | `string` | 是 | 左侧分类显示名称,去除首尾空白后不得为空。 |
|
||
| `category.routes` | `WanfaRoute[]` | 响应必填 | 当前分类下的路线,按展示顺序返回。 |
|
||
| `route.id` | `string` | 响应必填 | 路线稳定标识,例如 `family-water`。创建时由后端生成。 |
|
||
| `route.title` | `string` | 是 | 路线卡片标题,去除首尾空白后不得为空。 |
|
||
| `route.subtitle` | `string` | 是 | 路线卡片副标题或目的地说明。 |
|
||
| `route.image` | `string` | 是 | 可直接用于图片组件的封面 URL。 |
|
||
| `route.routeCount` | `number` | 是 | 卡片右下角展示的路线数量,服务端应校验为不小于 0 的整数。 |
|
||
| `route.demandKeyword` | `string` | 是 | 跳转需求页时可使用的预填关键词,去除首尾空白后不得为空。 |
|
||
|
||
字段最大长度应由后端 schema 统一定义,并同步到 Admin UI 表单校验;在未形成统一长度常量前,前端不能通过截断文本代替服务端校验。
|
||
|
||
## 接口详情
|
||
|
||
### 获取分类及路线
|
||
|
||
```http
|
||
GET /api/admin/wanfa/categories
|
||
Authorization: Bearer <admin-jwt>
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"categories": [
|
||
{
|
||
"id": "family-route",
|
||
"label": "亲子路线",
|
||
"routes": [
|
||
{
|
||
"id": "family-water",
|
||
"title": "亲子玩水",
|
||
"subtitle": "贵州·轻松节奏与自然课堂",
|
||
"image": "https://example.test/assets/family-water.jpg",
|
||
"routeCount": 4,
|
||
"demandKeyword": "亲子玩水"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 新增分类
|
||
|
||
```http
|
||
POST /api/admin/wanfa/categories
|
||
Authorization: Bearer <admin-jwt>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求:
|
||
|
||
```json
|
||
{ "label": "亲子路线" }
|
||
```
|
||
|
||
成功返回 `201` 和 `data` 内的新分类对象,初始 `routes` 为 `[]`,并追加到分类列表末尾。
|
||
|
||
### 编辑分类
|
||
|
||
```http
|
||
PATCH /api/admin/wanfa/categories/{categoryId}
|
||
Authorization: Bearer <admin-jwt>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求只允许修改分类名称:
|
||
|
||
```json
|
||
{ "label": "家庭路线" }
|
||
```
|
||
|
||
成功返回 `data` 内的更新后 `WanfaCategory`。不存在的分类返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND`。
|
||
|
||
### 删除分类
|
||
|
||
```http
|
||
DELETE /api/admin/wanfa/categories/{categoryId}
|
||
Authorization: Bearer <admin-jwt>
|
||
```
|
||
|
||
为避免误删路线,分类仍包含路线时不得级联删除,应返回 `409`,业务码为 `WANFA_CATEGORY_NOT_EMPTY`。删除前由 Admin UI 提示先移除分类内路线。删除成功返回:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": { "id": "family-route" }
|
||
}
|
||
```
|
||
|
||
### 分类排序
|
||
|
||
```http
|
||
PATCH /api/admin/wanfa/categories/reorder
|
||
Authorization: Bearer <admin-jwt>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求必须完整包含当前全部分类 ID,不能重复:
|
||
|
||
```json
|
||
{ "itemIds": ["photo-route", "family-route", "healing-route"] }
|
||
```
|
||
|
||
成功返回:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": { "items": [] }
|
||
}
|
||
```
|
||
|
||
其中 `items` 为排序后的 `WanfaCategory[]`。缺少 ID、包含未知 ID 或出现重复 ID 时返回 `400`,业务码为 `WANFA_CATEGORY_REORDER_INVALID`。
|
||
|
||
### 新增路线
|
||
|
||
```http
|
||
POST /api/admin/wanfa/categories/{categoryId}/routes
|
||
Authorization: Bearer <admin-jwt>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"title": "亲子玩水",
|
||
"subtitle": "贵州·轻松节奏与自然课堂",
|
||
"image": "https://example.test/assets/family-water.jpg",
|
||
"routeCount": 4,
|
||
"demandKeyword": "亲子玩水"
|
||
}
|
||
```
|
||
|
||
成功返回 `201` 和 `data` 内新建的 `WanfaRoute`,并追加到对应分类路线末尾。分类不存在返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND`。
|
||
|
||
### 编辑路线
|
||
|
||
```http
|
||
PATCH /api/admin/wanfa/categories/{categoryId}/routes/{routeId}
|
||
Authorization: Bearer <admin-jwt>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体为 `WanfaRoutePatch`,只更新提交的字段。成功返回 `data` 内更新后的 `WanfaRoute`;分类或路线不存在时分别返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND` 或 `WANFA_ROUTE_NOT_FOUND`。
|
||
|
||
### 删除路线
|
||
|
||
```http
|
||
DELETE /api/admin/wanfa/categories/{categoryId}/routes/{routeId}
|
||
Authorization: Bearer <admin-jwt>
|
||
```
|
||
|
||
成功返回 `data: { "id": "..." }`。删除后,分类内路线保持原有相对顺序。
|
||
|
||
### 分类内路线排序
|
||
|
||
```http
|
||
PATCH /api/admin/wanfa/categories/{categoryId}/routes/reorder
|
||
Authorization: Bearer <admin-jwt>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求必须完整包含该分类当前全部路线 ID,不能重复:
|
||
|
||
```json
|
||
{ "itemIds": ["family-grassland", "family-water", "family-village"] }
|
||
```
|
||
|
||
成功返回排序后的路线:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": { "items": [] }
|
||
}
|
||
```
|
||
|
||
分类不存在返回 `404`,业务码为 `WANFA_CATEGORY_NOT_FOUND`;路线 ID 不完整、重复或不属于该分类时返回 `400`,业务码为 `WANFA_ROUTE_REORDER_INVALID`。
|
||
|
||
## 当前本地数据映射
|
||
|
||
迁移初始数据时,分类和路线应按以下 ID 与顺序导入:
|
||
|
||
| 分类 ID | 分类名称 | 路线 ID(当前顺序) |
|
||
| --- | --- | --- |
|
||
| `family-route` | 亲子路线 | `family-water`、`family-village`、`family-grassland` |
|
||
| `photo-route` | 旅拍路线 | `miao-photo`、`peak-photo`、`terrace-photo` |
|
||
| `healing-route` | 疗愈路线 | `mountain-healing`、`hot-spring-healing`、`river-healing` |
|
||
| `team-building` | 团建 | `team-challenge`、`team-stream`、`team-culture` |
|
||
| `guizhou-panorama` | 贵州全景 | `classic-panorama`、`mountain-panorama`、`wild-panorama` |
|
||
| `private-custom` | 私人定制 | `private-family`、`private-business`、`private-wild` |
|
||
|
||
图片别名的当前解析规则位于 `playData.ts` 的 `routeImageByAsset` 和 `resolveRouteImage`:已配置别名解析为完整远程 URL,完整 `http` URL 直接使用,其他值按 `/assets/guizhou/{image}.jpg` 解析。Admin API 建议保存最终 URL,Admin UI 通过现有媒体上传接口获取 URL 后再提交 `image`。
|
||
|
||
## Admin UI 对接要求
|
||
|
||
Admin UI 应按以下方式调用:
|
||
|
||
1. 进入玩法管理页时调用 `GET /api/admin/wanfa/categories`,以返回数组顺序渲染分类和路线。
|
||
2. 添加分类调用 `POST /categories`;编辑和删除分类分别调用对应 `PATCH`、`DELETE`。
|
||
3. 添加、编辑和删除路线使用分类嵌套路由。
|
||
4. 上移或下移分类时提交完整分类 ID 列表;上移或下移路线时提交完整路线 ID 列表。
|
||
5. 每次变更成功后以接口返回数据更新本地状态;必要时重新请求列表,不直接拼接数据库字段。
|
||
6. 删除非空分类前展示阻止性提示,不自动级联删除路线。
|
||
7. 处理 `401`、`404`、`409`、`422` 和 `5xx`,并在保存中禁用重复提交。
|
||
|
||
建议的 Admin UI API 封装函数:
|
||
|
||
```ts
|
||
getWanfaCategories();
|
||
createWanfaCategory(input: WanfaCategoryCreate);
|
||
updateWanfaCategory(categoryId: string, input: WanfaCategoryPatch);
|
||
deleteWanfaCategory(categoryId: string);
|
||
reorderWanfaCategories(itemIds: string[]);
|
||
createWanfaRoute(categoryId: string, input: WanfaRouteCreate);
|
||
updateWanfaRoute(categoryId: string, routeId: string, input: WanfaRoutePatch);
|
||
deleteWanfaRoute(categoryId: string, routeId: string);
|
||
reorderWanfaRoutes(categoryId: string, itemIds: string[]);
|
||
```
|
||
|
||
## 后端落地边界
|
||
|
||
玩法数据由 `WonderQ-Admin` 的 `WanfaCategory`、`WanfaRoute` ORM 模型和 Admin API 负责持久化;`WonderQ-Admin-UI` 负责 API 类型、请求封装、表单、删除确认和排序交互。`playData.ts` 仅作为迁移初始数据来源,不能视为数据库或 API 数据。
|
||
|
||
相关文档:
|
||
|
||
- [Admin API 主契约](./admin-api-requirements.md)
|
||
- [页面模块配置契约](./module-config-api.md)
|
||
- [玩法本地数据](../WonderQ-MiniAPP/src/pages/play/components/playData.ts)
|