feat: Add WonderQ-MiniAPP Public API documentation
- Introduced a comprehensive API contract for the WonderQ-MiniAPP, detailing endpoints for site configuration, product listings, and lead submissions. - Defined data types for various entities including HeroSlide, Destination, Theme, CtaBanner, PublicProduct, and more. - Specified request and response formats, including error handling guidelines. chore: Update requirements to include python-multipart - Added python-multipart dependency to requirements.txt for handling file uploads. test: Implement API contract tests - Created test suite for API contracts, validating serializers and endpoints for public products and leads. - Included tests for destination and product serializers, ensuring correct data handling and validation. test: Add configuration tests for OSS settings - Implemented tests to verify that OSS settings are correctly loaded from environment variables.
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
本目录存放后端 API 相关文档。
|
||||
|
||||
- `backend/README.md`:当前 Python + FastAPI 后端运行说明。
|
||||
- `admin-ui-api-requirements.md`:后台管理前端当前必需的 Admin API 对接需求与字段契约。
|
||||
- `miniapp-public-api.md`:WonderQ-MiniAPP 前台对接 Public API 契约。
|
||||
- `admin-backend-plan.md`:早期后台建设规划,保留作业务范围和阶段规划参考;其中技术栈建议已被当前 Python 迁移方案取代。
|
||||
|
||||
后台管理前端文档位于 `D:\www\znkj\WonderQ-Admin-UI\docs`。
|
||||
|
||||
506
docs/admin-ui-api-requirements.md
Normal file
506
docs/admin-ui-api-requirements.md
Normal file
@@ -0,0 +1,506 @@
|
||||
# WonderQ-Admin-UI Admin API 接口需求
|
||||
|
||||
本文档用于指导 `WonderQ-Admin` 后端按当前 `WonderQ-Admin-UI` 管理端完成 Admin API 对接。接口需求来源于前端 `src/api.ts` 与 `src/App.tsx` 的实际类型、请求封装和页面调用。
|
||||
|
||||
## 范围
|
||||
|
||||
- 本文只覆盖当前后台管理前端必需的 Admin API。
|
||||
- 不包含 H5 Public API、订单、媒体库、审计日志、活动专题等后续规划能力。
|
||||
- 后端现有 `GET /api/admin/me`、`GET /api/admin/media-assets` 不在当前 UI 必需范围内。
|
||||
|
||||
## 通用约定
|
||||
|
||||
- 基础路径:`/api/admin`。
|
||||
- 请求与响应均使用 JSON。
|
||||
- 除 `POST /api/admin/auth/login` 外,其余接口都需要后台登录态。
|
||||
- 登录成功后前端会把返回的 `token` 存入本地,并在后续请求头中发送:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
- 日期时间字段返回 ISO 8601 字符串。
|
||||
- ID 字段按字符串处理。
|
||||
- 前端错误提示优先读取响应体的 `message` 字段;如后端沿用 FastAPI 默认 `detail`,建议同时兼容输出 `message`,避免管理端展示兜底错误。
|
||||
|
||||
## 当前对接状态
|
||||
|
||||
| 接口 | 前端依赖 | 后端现状 | 备注 |
|
||||
| --- | --- | --- | --- |
|
||||
| `POST /api/admin/auth/login` | 登录页 | 已覆盖 | 返回 token 和 user |
|
||||
| `GET /api/admin/dashboard` | 客户端已封装 | 已覆盖 | 当前 UI 暂未展示 |
|
||||
| `GET /api/admin/products` | 商品维护、结构维护 | 已覆盖 | UI 当前只传 `keyword` |
|
||||
| `POST /api/admin/products` | 新建商品 | 已覆盖 | 返回完整 Product |
|
||||
| `PATCH /api/admin/products/{id}` | 编辑商品 | 已覆盖 | 返回完整 Product |
|
||||
| `GET /api/admin/destinations` | 商品目的地下拉、目的地页 | 已覆盖 | 需要返回别名和商品数 |
|
||||
| `GET /api/admin/site-config` | 首页/目的地/活动结构维护 | 已覆盖 | 需要包含未启用内容 |
|
||||
| `PATCH /api/admin/site-config/{module}/{item_id}` | 模块内容编辑 | 已覆盖 | 模块名需保持一致 |
|
||||
| `GET /api/admin/leads` | 需求线索页 | 已覆盖 | UI 当前不传筛选参数 |
|
||||
| `PATCH /api/admin/leads/{id}/status` | 线索状态流转 | 已覆盖 | UI 更新后会重新拉列表 |
|
||||
| `POST /api/admin/publish` | 结构维护发布 | 已覆盖 | UI 使用 `title` 提示发布结果 |
|
||||
| `POST /api/admin/reset-guizhou-content` | 贵州内容重置 | 已覆盖 | 高风险操作,需鉴权和审计 |
|
||||
|
||||
## 枚举
|
||||
|
||||
### ProductStatus
|
||||
|
||||
```ts
|
||||
type ProductStatus = "draft" | "published" | "archived";
|
||||
```
|
||||
|
||||
### LeadStatus
|
||||
|
||||
```ts
|
||||
type LeadStatus = "new" | "assigned" | "contacted" | "planning" | "won" | "invalid";
|
||||
```
|
||||
|
||||
### SiteModule
|
||||
|
||||
```ts
|
||||
type SiteModule = "heroSlides" | "destinations" | "themes" | "ctaBanners";
|
||||
```
|
||||
|
||||
## 公共数据结构
|
||||
|
||||
### Destination
|
||||
|
||||
```ts
|
||||
type Destination = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
region?: string | null;
|
||||
image?: string | null;
|
||||
isHot: boolean;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
aliases?: Array<{ id: string; alias: string }>;
|
||||
_count?: { products: number };
|
||||
};
|
||||
```
|
||||
|
||||
### Product
|
||||
|
||||
```ts
|
||||
type Product = {
|
||||
id: string;
|
||||
sourceId?: number | null;
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
priceAmount?: number | null;
|
||||
priceUnit: string;
|
||||
tags: string[];
|
||||
coverImage?: string | null;
|
||||
summary?: string | null;
|
||||
images?: Array<{ id?: string; url: string; alt?: string | null; sortOrder: number }>;
|
||||
detailSections?: ProductDetailSection[] | null;
|
||||
status: ProductStatus;
|
||||
sortWeight: number;
|
||||
updatedAt: string;
|
||||
destination?: Destination | null;
|
||||
destinationId?: string | null;
|
||||
};
|
||||
```
|
||||
|
||||
### ProductDetailSection
|
||||
|
||||
```ts
|
||||
type ProductDetailBlock =
|
||||
| { type: "text"; text: string }
|
||||
| { type: "image"; url: string; alt?: string | null };
|
||||
|
||||
type ProductDetailSection = {
|
||||
key: string;
|
||||
label: string;
|
||||
title?: string | null;
|
||||
blocks: ProductDetailBlock[];
|
||||
};
|
||||
```
|
||||
|
||||
### ProductInput
|
||||
|
||||
`POST /products` 与 `PATCH /products/{id}` 复用该结构;`PATCH` 可以只提交需要修改的字段。
|
||||
|
||||
```ts
|
||||
type ProductInput = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
destinationId?: string | null;
|
||||
priceAmount?: number | null;
|
||||
priceUnit?: string;
|
||||
tags: string[];
|
||||
coverImage?: string | null;
|
||||
summary?: string | null;
|
||||
images?: Array<{ url: string; alt?: string | null; sortOrder: number }>;
|
||||
detailSections?: ProductDetailSection[];
|
||||
status: ProductStatus;
|
||||
sortWeight: number;
|
||||
};
|
||||
```
|
||||
|
||||
字段处理要求:
|
||||
|
||||
- `title` 必填,后端至少应校验非空;当前后端 schema 为最少 2 个字符。
|
||||
- `priceAmount` 可为空;不为空时应为大于等于 0 的整数。
|
||||
- `priceUnit` 为空时后端默认使用 `起/人`。
|
||||
- `images` 保存前按数组顺序重排 `sortOrder`。
|
||||
- `detailSections` 中空 key、空 label、空 blocks 的模块不应保存为有效详情模块。
|
||||
- 当 `status` 首次变为 `published` 时,后端可写入发布时间。
|
||||
|
||||
### Lead
|
||||
|
||||
```ts
|
||||
type Lead = {
|
||||
id: string;
|
||||
destination?: string | null;
|
||||
phone: string;
|
||||
note?: string | null;
|
||||
sourcePage?: string | null;
|
||||
status: LeadStatus;
|
||||
createdAt: string;
|
||||
sourceProduct?: { id: string; title: string } | null;
|
||||
assignedUser?: { id: string; name: string } | null;
|
||||
};
|
||||
```
|
||||
|
||||
后端可额外返回 `travelDate`、`peopleCount`、`budgetMin`、`budgetMax` 等字段,但以上字段是当前管理端展示所需的最小集合。手机号属于隐私信息,日志、错误和文档示例中不得输出真实号码。
|
||||
|
||||
### SiteConfig
|
||||
|
||||
```ts
|
||||
type SiteConfig = {
|
||||
heroSlides: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
kicker?: string | null;
|
||||
image: string;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
isActive: boolean;
|
||||
}>;
|
||||
destinations: Destination[];
|
||||
themes: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
image: string;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
isActive: boolean;
|
||||
}>;
|
||||
ctaBanners: Array<{
|
||||
id: string;
|
||||
alt: string;
|
||||
image: string;
|
||||
targetType: string;
|
||||
targetValue?: string | null;
|
||||
isActive: boolean;
|
||||
}>;
|
||||
};
|
||||
```
|
||||
|
||||
### SiteItemPatch
|
||||
|
||||
```ts
|
||||
type SiteItemPatch = {
|
||||
title?: string;
|
||||
kicker?: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
alt?: string;
|
||||
image?: string | null;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
isActive?: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
模块字段映射:
|
||||
|
||||
| module | 可编辑字段 |
|
||||
| --- | --- |
|
||||
| `heroSlides` | `title`、`kicker`、`image`、`targetType`、`targetValue`、`isActive` |
|
||||
| `destinations` | `name`、`image`、`isActive` |
|
||||
| `themes` | `label`、`image`、`targetType`、`targetValue`、`isActive` |
|
||||
| `ctaBanners` | `alt`、`image`、`targetType`、`targetValue`、`isActive` |
|
||||
|
||||
## 接口明细
|
||||
|
||||
### 登录
|
||||
|
||||
```http
|
||||
POST /api/admin/auth/login
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "admin@example.com",
|
||||
"password": "example-password"
|
||||
}
|
||||
```
|
||||
|
||||
响应体:
|
||||
|
||||
```ts
|
||||
{
|
||||
token: string;
|
||||
user: {
|
||||
id?: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
状态码要求:
|
||||
|
||||
- `200`:登录成功。
|
||||
- `401`:账号不存在、密码错误或账号停用。
|
||||
|
||||
### 工作台统计
|
||||
|
||||
```http
|
||||
GET /api/admin/dashboard
|
||||
```
|
||||
|
||||
当前前端客户端已封装该接口,但页面暂未展示。后端保持兼容即可。
|
||||
|
||||
响应体:
|
||||
|
||||
```ts
|
||||
{
|
||||
stats: {
|
||||
productCount: number;
|
||||
publishedProductCount: number;
|
||||
destinationCount: number;
|
||||
newLeadCount: number;
|
||||
leadCount: number;
|
||||
campaignCount: number;
|
||||
};
|
||||
recentLeads: Lead[];
|
||||
}
|
||||
```
|
||||
|
||||
### 商品列表
|
||||
|
||||
```http
|
||||
GET /api/admin/products?keyword=<keyword>
|
||||
```
|
||||
|
||||
查询参数:
|
||||
|
||||
| 参数 | 类型 | 当前 UI 是否使用 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `keyword` | `string` | 是 | 搜索商品标题、短标题或标签 |
|
||||
| `status` | `ProductStatus` | 否 | 后端可支持状态筛选 |
|
||||
| `take` | `number` | 否 | 后端当前可限制返回条数 |
|
||||
|
||||
响应体:
|
||||
|
||||
```ts
|
||||
{
|
||||
items: Product[];
|
||||
}
|
||||
```
|
||||
|
||||
排序建议:`sortWeight` 升序,再按 `updatedAt` 倒序。结构维护页和商品维护页都会读取该接口。
|
||||
|
||||
### 新建商品
|
||||
|
||||
```http
|
||||
POST /api/admin/products
|
||||
```
|
||||
|
||||
请求体:`ProductInput`
|
||||
|
||||
响应体:`Product`
|
||||
|
||||
状态码要求:
|
||||
|
||||
- `201`:创建成功。
|
||||
- `422`:字段校验失败。
|
||||
|
||||
### 更新商品
|
||||
|
||||
```http
|
||||
PATCH /api/admin/products/{id}
|
||||
```
|
||||
|
||||
请求体:`Partial<ProductInput>`
|
||||
|
||||
响应体:`Product`
|
||||
|
||||
状态码要求:
|
||||
|
||||
- `200`:更新成功。
|
||||
- `404`:商品不存在。
|
||||
- `422`:字段校验失败。
|
||||
|
||||
当前 UI 保存商品后会使用响应体刷新编辑状态,因此后端需要返回完整 Product,而不是只返回成功标记。
|
||||
|
||||
### 目的地列表
|
||||
|
||||
```http
|
||||
GET /api/admin/destinations
|
||||
```
|
||||
|
||||
响应体:
|
||||
|
||||
```ts
|
||||
{
|
||||
items: Destination[];
|
||||
}
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 返回所有目的地,包括未启用项,便于后台维护。
|
||||
- 按 `sortOrder` 升序。
|
||||
- 每个目的地包含 `aliases`。
|
||||
- 每个目的地建议包含 `_count.products`,用于后台判断关联商品数量。
|
||||
|
||||
### 站点配置
|
||||
|
||||
```http
|
||||
GET /api/admin/site-config
|
||||
```
|
||||
|
||||
响应体:`SiteConfig`
|
||||
|
||||
要求:
|
||||
|
||||
- 返回 `heroSlides`、`destinations`、`themes`、`ctaBanners` 四个模块。
|
||||
- Admin API 需要返回未启用内容;Public API 才按发布/启用状态过滤。
|
||||
- 各模块按 `sortOrder` 升序。
|
||||
|
||||
### 更新站点配置项
|
||||
|
||||
```http
|
||||
PATCH /api/admin/site-config/{module}/{item_id}
|
||||
```
|
||||
|
||||
路径参数:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `module` | `SiteModule` | 只能为 `heroSlides`、`destinations`、`themes`、`ctaBanners` |
|
||||
| `item_id` | `string` | 对应模块内容项 ID |
|
||||
|
||||
请求体:`SiteItemPatch`
|
||||
|
||||
响应体:更新后的内容项对象。
|
||||
|
||||
状态码要求:
|
||||
|
||||
- `200`:更新成功。
|
||||
- `400`:模块不存在。
|
||||
- `404`:内容项不存在。
|
||||
- `422`:字段校验失败。
|
||||
|
||||
当前 UI 保存后会重新调用 `GET /api/admin/site-config` 刷新页面,响应体只需保证是合法 JSON。
|
||||
|
||||
### 线索列表
|
||||
|
||||
```http
|
||||
GET /api/admin/leads
|
||||
```
|
||||
|
||||
查询参数:
|
||||
|
||||
| 参数 | 类型 | 当前 UI 是否使用 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `status` | `LeadStatus` | 否 | 后端可支持状态筛选 |
|
||||
| `take` | `number` | 否 | 后端当前可限制返回条数 |
|
||||
|
||||
响应体:
|
||||
|
||||
```ts
|
||||
{
|
||||
items: Lead[];
|
||||
}
|
||||
```
|
||||
|
||||
排序建议:`createdAt` 倒序。当前 UI 展示客户手机号、创建时间、目的地/备注、来源商品/来源页面和状态。
|
||||
|
||||
### 更新线索状态
|
||||
|
||||
```http
|
||||
PATCH /api/admin/leads/{id}/status
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```ts
|
||||
{
|
||||
status: LeadStatus;
|
||||
}
|
||||
```
|
||||
|
||||
响应体:更新后的 `Lead`,至少需要包含 `id` 和 `status`。
|
||||
|
||||
状态码要求:
|
||||
|
||||
- `200`:更新成功。
|
||||
- `404`:线索不存在。
|
||||
- `422`:状态值非法。
|
||||
|
||||
当前 UI 更新后会重新调用 `GET /api/admin/leads`,因此响应体不会直接用于渲染列表。
|
||||
|
||||
### 发布站点配置
|
||||
|
||||
```http
|
||||
POST /api/admin/publish
|
||||
```
|
||||
|
||||
请求体:空 JSON 对象或无请求体均可兼容。
|
||||
|
||||
响应体:
|
||||
|
||||
```ts
|
||||
{
|
||||
id: string;
|
||||
title: string;
|
||||
publishedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
当前 UI 只读取 `title` 展示发布结果。后端可额外返回 `status`、`snapshot` 等字段。
|
||||
|
||||
### 重置贵州内容
|
||||
|
||||
```http
|
||||
POST /api/admin/reset-guizhou-content
|
||||
```
|
||||
|
||||
请求体:空 JSON 对象或无请求体均可兼容。
|
||||
|
||||
响应体:
|
||||
|
||||
```ts
|
||||
{
|
||||
heroSlides: number;
|
||||
destinations: number;
|
||||
themes: number;
|
||||
ctaBanners: number;
|
||||
products: number;
|
||||
}
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 该接口会重置内容数据,必须走后台鉴权。
|
||||
- 后端需要记录审计日志。
|
||||
- 生产环境调用前应通过部署流程或权限控制额外确认。
|
||||
|
||||
## 后端实现注意事项
|
||||
|
||||
- Admin API 默认使用 `require_admin`,登录接口除外。
|
||||
- 所有外部输入通过 Pydantic schema 校验。
|
||||
- 变更类接口需要继续写入 `AuditLog`。
|
||||
- 响应字段使用 camelCase,以匹配当前前端类型。
|
||||
- 允许后端返回额外字段,但不要移除本文列出的前端依赖字段。
|
||||
- 当前管理端不会直接上传图片,只维护图片 URL;媒体库接口暂不属于本需求范围。
|
||||
|
||||
353
docs/miniapp-public-api.md
Normal file
353
docs/miniapp-public-api.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# WonderQ-MiniAPP Public API 对接文档
|
||||
|
||||
最后更新:2026-06-30
|
||||
|
||||
本文档定义 `WonderQ-MiniAPP` 前台 H5/小程序对接 `WonderQ-Admin` 后端所需的 Public API 契约。当前 MiniAPP 主动调用站点配置、产品列表和线索提交 3 个接口;后端已存在的健康检查、产品详情和目的地列表接口建议继续保留,供后续前台按需接入。
|
||||
|
||||
## 基础约定
|
||||
|
||||
- 基础地址由 MiniAPP 环境变量 `VITE_API_BASE_URL` 控制;为空时前台按同源 `/api` 请求。
|
||||
- Public API 不要求前台登录认证。
|
||||
- 请求和响应均使用 JSON,字符集为 UTF-8。
|
||||
- 图片字段应返回可被 H5 和微信小程序访问的 URL;现有前台兼容 `/assets/...` 形式。
|
||||
- 列表字段建议返回空数组,不建议返回 `null`;MiniAPP 对站点配置和产品列表有本地兜底内容。
|
||||
- 错误响应需提供可展示信息,兼容 `{ "message": "..." }` 或 FastAPI 默认 `{ "detail": "..." }`。不要暴露内部异常、真实环境变量、Token、JWT secret、客服链接或企业 ID。
|
||||
|
||||
## 数据类型
|
||||
|
||||
### `HeroSlide`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | 是 | 轮播图 ID |
|
||||
| `title` | `string` | 是 | 主标题 |
|
||||
| `kicker` | `string \| null` | 否 | 辅助短文案 |
|
||||
| `image` | `string` | 是 | 图片 URL |
|
||||
| `targetType` | `string \| null` | 否 | 点击目标类型 |
|
||||
| `targetValue` | `string \| null` | 否 | 点击目标值 |
|
||||
| `isActive` | `boolean` | 否 | 是否启用 |
|
||||
|
||||
### `Destination`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | 是 | 目的地 ID |
|
||||
| `name` | `string` | 是 | 目的地名称 |
|
||||
| `image` | `string \| null` | 否 | 图片 URL |
|
||||
| `isHot` | `boolean` | 否 | 是否热门 |
|
||||
| `isActive` | `boolean` | 否 | 是否启用 |
|
||||
| `aliases` | `Array<{ id: string; alias: string }>` | 否 | 搜索别名 |
|
||||
|
||||
### `Theme`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | 是 | 主题 ID |
|
||||
| `label` | `string` | 是 | 主题名称 |
|
||||
| `image` | `string` | 是 | 图片 URL |
|
||||
| `targetType` | `string \| null` | 否 | 点击目标类型 |
|
||||
| `targetValue` | `string \| null` | 否 | 点击目标值 |
|
||||
| `isActive` | `boolean` | 否 | 是否启用 |
|
||||
|
||||
### `CtaBanner`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | 是 | Banner ID |
|
||||
| `alt` | `string` | 是 | 图片替代文案 |
|
||||
| `image` | `string` | 是 | 图片 URL |
|
||||
| `targetType` | `string \| null` | 否 | 点击目标类型 |
|
||||
| `targetValue` | `string \| null` | 否 | 点击目标值 |
|
||||
| `isActive` | `boolean` | 否 | 是否启用 |
|
||||
|
||||
### `PublicProduct`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | 是 | 后端产品 UUID |
|
||||
| `sourceId` | `number \| null` | 否 | 历史采集产品 ID;前台可用于兼容旧数据 |
|
||||
| `title` | `string` | 是 | 产品标题 |
|
||||
| `subtitle` | `string \| null` | 否 | 副标题 |
|
||||
| `destination` | `{ id: string; name: string } \| null` | 否 | 目的地信息 |
|
||||
| `priceAmount` | `number \| null` | 否 | 参考起价,单位按后端内容约定 |
|
||||
| `priceUnit` | `string \| null` | 否 | 价格单位文案 |
|
||||
| `tags` | `string[]` | 否 | 标签列表 |
|
||||
| `coverImage` | `string \| null` | 否 | 封面图 URL |
|
||||
| `summary` | `string \| null` | 否 | 摘要 |
|
||||
| `images` | `Array<ProductImage>` | 否 | 图集 |
|
||||
| `detailSections` | `ProductDetailSection[] \| null` | 否 | 产品详情分区 |
|
||||
| `status` | `string` | 否 | 产品状态,前台主要消费 `published` 内容 |
|
||||
|
||||
### `ProductImage`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | 否 | 图片 ID |
|
||||
| `url` | `string` | 是 | 图片 URL |
|
||||
| `alt` | `string \| null` | 否 | 图片说明 |
|
||||
| `sortOrder` | `number` | 是 | 排序值 |
|
||||
|
||||
### `ProductDetailSection`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `key` | `string` | 是 | 分区 key,例如 `overview`、`itinerary`、`service`、`notice`、`price`、`manager` |
|
||||
| `label` | `string` | 是 | Tab 展示文案 |
|
||||
| `title` | `string \| null` | 否 | 分区标题 |
|
||||
| `blocks` | `ProductDetailBlock[]` | 是 | 内容块 |
|
||||
|
||||
`ProductDetailBlock` 支持两种结构:
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "文本内容" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "image", "url": "/assets/example.jpg", "alt": "图片说明" }
|
||||
```
|
||||
|
||||
## 接口清单
|
||||
|
||||
### `GET /health`
|
||||
|
||||
用于服务健康检查。
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"service": "miniapp-api"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/public/site-config`
|
||||
|
||||
用于首页轮播、目的地、主题入口和底部 CTA 配置。MiniAPP 启动时会和产品列表并行请求该接口;接口不可用或关键数组为空时,前台会回退本地静态内容。
|
||||
|
||||
#### 响应字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `heroSlides` | `HeroSlide[]` | 首页顶部轮播 |
|
||||
| `destinations` | `Destination[]` | 首页目的地入口 |
|
||||
| `themes` | `Theme[]` | 主题甄选入口 |
|
||||
| `ctaBanners` | `CtaBanner[]` | 底部 CTA Banner |
|
||||
| `campaigns` | `unknown[]` | 后端现有扩展字段,可保留 |
|
||||
| `routeSections` | `Array<{ id: string; title: string; productIds: string[] }>` | 后端现有扩展字段,可保留 |
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"heroSlides": [
|
||||
{
|
||||
"id": "hero-1",
|
||||
"title": "贵州小包团定制",
|
||||
"kicker": "万趣,你的小包团首选",
|
||||
"image": "/assets/guizhou/libo-xiaoqikong.jpg",
|
||||
"targetType": "search",
|
||||
"targetValue": "贵州",
|
||||
"isActive": true
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "dest-1",
|
||||
"name": "荔波小七孔",
|
||||
"image": "/assets/guizhou/libo-xiaoqikong.jpg",
|
||||
"isHot": true,
|
||||
"isActive": true,
|
||||
"aliases": [{ "id": "alias-1", "alias": "小七孔" }]
|
||||
}
|
||||
],
|
||||
"themes": [],
|
||||
"ctaBanners": []
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/public/products`
|
||||
|
||||
用于首页产品分区、搜索结果、活动页、目的地页、详情推荐和预订入口。当前 MiniAPP 一次拉取列表后在前端做搜索、筛选和推荐。
|
||||
|
||||
#### Query 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `keyword` | `string` | 否 | 无 | 关键词搜索,建议匹配产品标题、副标题、标签、目的地名称和目的地别名 |
|
||||
| `destinationId` | `string` | 否 | 无 | 按目的地 ID 筛选 |
|
||||
| `status` | `string` | 否 | `published` | 产品状态 |
|
||||
| `take` | `number` | 否 | `48` | 返回数量,后端当前限制 1-100 |
|
||||
|
||||
#### 响应字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `items` | `PublicProduct[]` | 产品列表 |
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "8a6e7c4f-0000-4000-9000-000000000001",
|
||||
"sourceId": 101,
|
||||
"title": "黄果树瀑布小包团",
|
||||
"subtitle": "错峰入园,私家车接送",
|
||||
"destination": { "id": "dest-anshun", "name": "黄果树" },
|
||||
"priceAmount": 398000,
|
||||
"priceUnit": "起/人",
|
||||
"tags": ["贵州", "黄果树", "小包团"],
|
||||
"coverImage": "/assets/guizhou/huangguoshu.jpg",
|
||||
"summary": "适合首次到贵州的经典线路。",
|
||||
"images": [
|
||||
{
|
||||
"id": "img-1",
|
||||
"url": "/assets/guizhou/huangguoshu.jpg",
|
||||
"alt": "黄果树瀑布",
|
||||
"sortOrder": 0
|
||||
}
|
||||
],
|
||||
"detailSections": [
|
||||
{
|
||||
"key": "overview",
|
||||
"label": "行程概述",
|
||||
"title": "小包团专属概览",
|
||||
"blocks": [{ "type": "text", "text": "按同行人、预算和体力强度重排行程。" }]
|
||||
}
|
||||
],
|
||||
"status": "published"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/public/products/{product_id}`
|
||||
|
||||
后端已存在,建议保留给 MiniAPP 后续详情页按需拉取。当前 MiniAPP 主要通过产品列表缓存进入详情。
|
||||
|
||||
#### Path 参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `product_id` | `string` | 产品 UUID;如果传入纯数字,后端按 `sourceId` 查询 |
|
||||
|
||||
#### 成功响应
|
||||
|
||||
返回单个 `PublicProduct`。
|
||||
|
||||
#### 异常响应
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| --- | --- |
|
||||
| `404` | 产品不存在 |
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "线路不存在"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/public/destinations`
|
||||
|
||||
后端已存在,建议保留给 MiniAPP 后续目的地页独立拉取。当前 MiniAPP 首页目的地来自 `site-config.destinations`。
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "dest-libo",
|
||||
"name": "荔波小七孔",
|
||||
"slug": "libo-xiaoqikong",
|
||||
"region": "黔南",
|
||||
"image": "/assets/guizhou/libo-xiaoqikong.jpg",
|
||||
"isHot": true,
|
||||
"sortOrder": 0,
|
||||
"isActive": true,
|
||||
"aliases": [{ "id": "alias-1", "alias": "小七孔" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/public/leads`
|
||||
|
||||
用于首页快速定制、搜索页快速定制、需求页和预订咨询页提交线索。
|
||||
|
||||
#### 请求字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `phone` | `string` | 是 | 联系方式。前台输入文案为“手机号 / 微信号”,后端当前会去除首尾空白并压缩连续空格 |
|
||||
| `destination` | `string` | 否 | 目的地或玩法 |
|
||||
| `travelDate` | `string` | 否 | 出行日期;MiniAPP 当前传 `YYYY-MM-DD` 字符串 |
|
||||
| `peopleCount` | `number` | 否 | 出行人数,需大于 0 |
|
||||
| `budgetMin` | `number` | 否 | 最低预算,需大于等于 0 |
|
||||
| `budgetMax` | `number` | 否 | 最高预算,需大于等于 0 |
|
||||
| `note` | `string` | 否 | 补充说明,后端当前限制最长 1000 字符 |
|
||||
| `sourcePage` | `string` | 否 | 来源页面 |
|
||||
| `sourceProductId` | `string` | 否 | 来源产品 UUID |
|
||||
|
||||
#### `sourcePage` 当前取值
|
||||
|
||||
| 值 | 来源 |
|
||||
| --- | --- |
|
||||
| `home_inline` | 首页快速定制入口 |
|
||||
| `search_inline` | 搜索结果页快速定制入口 |
|
||||
| `demand_page` | 提交需求页 |
|
||||
| `product_consult` | 产品预订咨询页 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"destination": "荔波小七孔",
|
||||
"phone": "187 8617 4929",
|
||||
"travelDate": "2027-01-01",
|
||||
"peopleCount": 2,
|
||||
"note": "咨询线路:黄果树瀑布小包团;方案偏好:经典人文",
|
||||
"sourcePage": "product_consult",
|
||||
"sourceProductId": "8a6e7c4f-0000-4000-9000-000000000001"
|
||||
}
|
||||
```
|
||||
|
||||
#### 成功响应
|
||||
|
||||
状态码:`201`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "lead-uuid",
|
||||
"status": "new"
|
||||
}
|
||||
```
|
||||
|
||||
#### 常见异常
|
||||
|
||||
| 状态码 | 场景 |
|
||||
| --- | --- |
|
||||
| `422` | 请求体校验失败,例如 `phone` 为空、`peopleCount` 小于等于 0、`note` 超长 |
|
||||
| `500` | 服务端异常,响应不得暴露内部细节 |
|
||||
|
||||
## MiniAPP 当前依赖说明
|
||||
|
||||
- `site-config` 与 `products` 会在应用启动时并行请求;任一请求失败时,MiniAPP 会回退到本地静态内容。
|
||||
- `products.items` 为空时,MiniAPP 会使用本地产品兜底数据。
|
||||
- 产品搜索当前主要在前端执行,依赖 `title`、`tags`、`destination.name`、`summary`。
|
||||
- 产品详情页当前使用已加载的产品列表数据;后续可改为进入详情页时请求 `GET /api/public/products/{product_id}`。
|
||||
- 收藏、浏览历史和最近咨询记录由 MiniAPP 本地存储处理,不需要后端接口。
|
||||
- 企业微信客服由 MiniAPP 环境变量控制,不属于 `WonderQ-Admin` Public API;文档和接口不得写入真实链接或企业 ID。
|
||||
|
||||
## 后端验证建议
|
||||
|
||||
- 为 `GET /health` 增加或保留健康检查测试。
|
||||
- 为 `GET /api/public/site-config` 验证返回 JSON 包含 `heroSlides`、`destinations`、`themes`、`ctaBanners` 数组字段。
|
||||
- 为 `GET /api/public/products` 验证响应结构为 `{ items: [...] }`,并覆盖 `keyword`、`destinationId`、`status`、`take` 参数。
|
||||
- 为 `GET /api/public/products/{product_id}` 验证 UUID、数字 `sourceId` 和 404 场景。
|
||||
- 为 `GET /api/public/destinations` 验证只返回启用目的地及别名字段。
|
||||
- 为 `POST /api/public/leads` 验证成功创建、`phone` 规范化、必填校验、人数/预算边界和备注长度限制。
|
||||
Reference in New Issue
Block a user