Files
WonderQ-Project/docs/public-api.md
duanshuwen 16caad1c40 feat(mini-app): 实现玩法、管家页面与首页组件化重构
- 新增管家服务全套页面、组件与模拟数据
- 新增玩法分类页面与线路展示组件
- 重构首页为可复用组件模块
- 更新路由配置替换旧目的地页面路由
- 移除废弃页面与客服弹窗冗余逻辑
- 更新项目文档与gitignore配置
2026-08-11 22:05:22 +08:00

654 lines
29 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.

# WonderQ-MiniAPP Public API 对接文档
最后更新:2026-07-07
本文档定义 `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` | 否 | 是否启用 |
| `sortOrder` | `number` | 否 | 后台排序值,Public API 按该字段升序输出 |
### `Destination`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 目的地 ID |
| `name` | `string` | 是 | 目的地名称 |
| `image` | `string \| null` | 否 | 图片 URL |
| `isHot` | `boolean` | 否 | 是否热门 |
| `isActive` | `boolean` | 否 | 是否启用 |
| `sortOrder` | `number` | 否 | 后台排序值,Public API 按该字段升序输出 |
| `aliases` | `Array<{ id: string; alias: string }>` | 否 | 搜索别名 |
MiniAPP 兼容说明:
- Public API 使用 `name` 表示目的地名称;MiniAPP 会在 `src/lib/data.ts` 中归一化为页面内部使用的 `label`。
- 玩法页 `src/pages/play/index.vue` 当前使用 `src/pages/play/components/playData.ts` 的本地模拟分类和线路数据,后续接入 Public API 时再按页面契约替换。
- `isActive=false` 的目的地不得出现在 Public `site-config.destinations`;Admin API 仍需返回未启用项供后台维护。
### `DestinationHero`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 目的地页主视觉 ID |
| `title` | `string` | 是 | 主视觉标题 |
| `kicker` | `string \| null` | 否 | 主视觉上方小标题 |
| `image` | `string \| null` | 否 | 主视觉图片 URL;为空时 MiniAPP 使用本地兜底图 |
| `isActive` | `boolean` | 否 | 是否启用 |
| `sortOrder` | `number` | 否 | 后台排序值;MiniAPP 取启用列表第一项作为顶部主视觉 |
### `DestinationRegion`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 热门区域 ID |
| `label` | `string` | 是 | 区域卡片名称 |
| `keyword` | `string \| null` | 否 | 点击后进入搜索的关键词;为空时 MiniAPP 使用 `label` |
| `spots` | `string \| null` | 否 | 卡片下方景点摘要 |
| `isActive` | `boolean` | 否 | 是否启用 |
| `sortOrder` | `number` | 否 | 后台排序值,Public API 按该字段升序输出 |
### 需求页配置类型
`DemandHero`、`DemandFeatureCard`、`DemandForm`、`DemandRecommendation` 用于 `src/pages/demand/index.vue`。MiniAPP 只消费启用项;接口缺字段、数组为空或商品无法匹配时回退本地硬编码兜底。
| 类型 | 关键字段 | 说明 |
| --- | --- | --- |
| `DemandHero` | `title`、`kicker`、`description`、`steps`、`isActive`、`sortOrder` | 需求页顶部定制说明;MiniAPP 取启用列表第一项 |
| `DemandFeatureCard` | `title`、`description`、`isActive`、`sortOrder` | 需求页服务说明卡,按 `sortOrder` 升序展示 |
| `DemandForm` | `destinationLabel`、`destinationPlaceholder`、`phoneLabel`、`phonePlaceholder`、`noteLabel`、`notePlaceholder`、`submitLabel`、`chips`、`isActive` | 固定表单字段的文案配置;后台按单例维护,Public 以数组返回 |
| `DemandRecommendation` | `title`、`subtitle`、`productIds`、`isActive`、`sortOrder` | 需求页热门推荐线路;`productIds` 只包含已发布商品 ID |
### `Theme`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 主题 ID |
| `label` | `string` | 是 | 主题名称 |
| `image` | `string` | 是 | 主题图片 URL |
| `targetType` | `string \| null` | 否 | 点击目标类型 |
| `targetValue` | `string \| null` | 否 | 点击目标值 |
| `isActive` | `boolean` | 否 | 是否启用 |
| `sortOrder` | `number` | 否 | 后台排序值,Public API 按该字段升序输出 |
### `CtaBanner`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 服务卡片 ID |
| `alt` | `string` | 是 | 服务标题,展示在“更多服务”卡片上 |
| `image` | `string` | 是 | 服务卡片背景图 URL |
| `targetType` | `string \| null` | 否 | 点击目标类型 |
| `targetValue` | `string \| null` | 否 | 点击目标值 |
| `isActive` | `boolean` | 否 | 是否启用 |
| `sortOrder` | `number` | 否 | 后台排序值,Public API 按该字段升序输出 |
### `Campaign`
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 活动 ID |
| `slug` | `string` | 是 | 活动标识 |
| `title` | `string` | 是 | 活动标题 |
| `description` | `string \| null` | 否 | 活动描述 |
| `coverImage` | `string \| null` | 否 | 活动封面图 |
| `priceAmount` | `number \| null` | 否 | 参考起价,单位按 `priceUnit` 展示 |
| `priceUnit` | `string \| null` | 否 | 价格单位文案,默认 `起/人` |
| `tags` | `string[]` | 否 | 活动卡片标签,最多 3 个 |
| `status` | `string` | 是 | 活动状态;Public API 只返回 `published` |
| `startsAt` | `string \| null` | 否 | 活动开始时间 |
| `endsAt` | `string \| null` | 否 | 活动结束时间 |
### `RouteSection`
`RouteSection` 用于描述首页“精选线路”下的动态运营分组。后台可按任务新增、编辑、删除和排序分组;MiniAPP 不应依赖固定分组 ID,只按接口返回的分组顺序和 `productIds` 渲染。
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 后端生成的分组 ID,客户端只用于列表 key 和商品关联,不作为固定业务枚举 |
| `title` | `string` | 是 | 分组标题 |
| `subtitle` | `string \| null` | 否 | 分组副文案 |
| `productIds` | `string[]` | 是 | 该分组包含的产品 ID;产品详情来自 `/api/public/products.items` |
| `isActive` | `boolean` | 否 | 是否启用;Public API 通常只返回启用分组 |
| `sortOrder` | `number` | 否 | 后台展示顺序;Public API 按该字段升序输出 |
Public API 输出规则:
- `GET /api/public/site-config` 只返回启用的 `routeSections`。
- `routeSections[].productIds` 只包含已发布商品 ID;未发布、归档或不存在的商品不得出现在 Public 响应中。
- 动态分组按后台 `sortOrder` 升序返回,`productIds` 的顺序就是用户侧商品卡展示顺序。
- MiniAPP 会按 `productIds` 匹配 `/api/public/products.items[].id`;接口缺失、`routeSections` 为空或没有可匹配商品时回退 `src/content.ts` 的本地精选线路兜底内容。
### `HomeCard`
`HomeCard` 用于首页“万趣用车”等普通内容卡片模块。MiniAPP 只消费卡片展示字段,不在这些模块里读取商品本体或线路商品关联。
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 后端生成的卡片 ID |
| `title` | `string` | 是 | 卡片标题 |
| `description` | `string \| null` | 否 | 卡片描述 |
| `image` | `string \| null` | 否 | 卡片封面图 URL;为空时客户端可使用本地兜底图 |
| `isActive` | `boolean` | 否 | 是否启用;Public API 通常只返回启用卡片 |
| `sortOrder` | `number` | 否 | 后台展示顺序;Public API 按该字段升序输出 |
### `HotelCard`
`HotelCard` 用于首页“特色酒店”模块。该模块按管理端“特价优惠”同类配置方式维护标题、描述、价格、标签、封面图和发布状态,但仍然只代表首页酒店展示卡片,不绑定商品本体。
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 是 | 后端生成的酒店卡片 ID |
| `title` | `string` | 是 | 酒店卡片标题 |
| `description` | `string \| null` | 否 | 酒店卡片描述 |
| `image` | `string \| null` | 否 | 兼容旧字段;后端会与 `coverImage` 保持一致 |
| `coverImage` | `string \| null` | 否 | 酒店封面图主字段;MiniAPP 优先使用该字段 |
| `priceAmount` | `number \| null` | 否 | 价格数值,前端可按页面需要展示 |
| `priceUnit` | `string \| null` | 否 | 价格单位文案,例如 `起/晚` |
| `tags` | `string[]` | 否 | 酒店标签,最多 3 个 |
| `status` | `"draft" \| "published"` | 否 | 发布状态;Public API 只返回 `published` |
| `isActive` | `boolean` | 否 | 是否启用;Public API 只返回启用项 |
| `sortOrder` | `number` | 否 | 后台展示顺序;Public API 按该字段升序输出 |
Public API 输出规则:
- `GET /api/public/site-config` 只返回 `status="published"` 且 `isActive=true` 的 `hotelGroups`。
- `GET /api/public/site-config` 只返回启用的 `vehicleOptions`。
- 两个数组按后台 `sortOrder` 升序返回。
- `hotelGroups` 字段缺失、数组为空或没有可用图片时,MiniAPP 使用 `src/content.ts` 的本地特色酒店内容兜底;酒店图片优先取 `coverImage`,再取 `image`。
- `vehicleOptions` 字段缺失、数组为空或图片为空时,MiniAPP 使用 `src/content.ts` 的本地万趣用车内容兜底。
### `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`
用于首页轮播、目的地页卡片、主题入口和更多服务配置。MiniAPP 启动时会和产品列表并行请求该接口;接口不可用或关键数组为空时,前台会回退本地静态内容。
#### 响应字段
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `heroSlides` | `HeroSlide[]` | 首页顶部轮播 |
| `destinations` | `Destination[]` | 首页和目的地页的省内目的地入口 |
| `destinationHero` | `DestinationHero[]` | 目的地页顶部主视觉;MiniAPP 取启用列表第一项 |
| `destinationRegions` | `DestinationRegion[]` | 目的地页“热门区域”快捷入口 |
| `demandHero` | `DemandHero[]` | 需求页顶部定制说明;MiniAPP 取启用列表第一项 |
| `demandFeatureCards` | `DemandFeatureCard[]` | 需求页服务说明卡 |
| `demandForm` | `DemandForm[]` | 需求页固定表单文案配置 |
| `demandRecommendations` | `DemandRecommendation[]` | 需求页热门推荐线路分组 |
| `map` | `Array<{ id: string; image: string; isActive?: boolean }>` | 贵州地图图片;MiniAPP 当前消费 `map[0].image` |
| `themes` | `Theme[]` | 主题甄选入口 |
| `ctaBanners` | `CtaBanner[]` | “更多服务”卡片配置 |
| `campaigns` | `Campaign[]` | 活动元信息,可用于“特价优惠”入口;当前不包含活动产品结果列表 |
| `routeSections` | `RouteSection[]` | “精选线路”子分组定义 |
| `hotelGroups` | `HotelCard[]` | “特色酒店”卡片配置,只返回 `published` 且启用项 |
| `vehicleOptions` | `HomeCard[]` | “万趣用车”卡片配置,只返回启用项 |
#### 首页模块数据归属
| 首页模块 | 当前接口归属 | 说明 |
| --- | --- | --- |
| 特价优惠 | `site-config.campaigns` + `/api/public/products` | 当前 Public API 只返回活动元信息,不直接返回“特价优惠结果列表”。MiniAPP 若要展示活动线路,可按活动标题、标签或后续扩展的活动产品关联从 `/api/public/products` 中筛选。 |
| 精选线路 | `site-config.routeSections` + `/api/public/products` | `routeSections` 返回动态分组与 `productIds`;具体产品卡片数据由 `/api/public/products.items` 提供。后台可按任务新增、删除、停用和排序分组,用户侧不假设固定三组。 |
| 更多服务 | `site-config.ctaBanners` | 返回启用服务卡片,按后台排序展示;无有效配置时回退本地 `bottomCtas` 内容。 |
| 特色酒店 | `site-config.hotelGroups` | 返回 `published` 且启用的酒店卡片,按后台排序展示;MiniAPP 优先消费 `coverImage`,无有效配置时回退本地内容。 |
| 万趣用车 | `site-config.vehicleOptions` | 返回启用用车卡片,按后台排序展示;无有效配置时回退本地内容。 |
#### 目的地页数据归属
| 目的地页模块 | 当前接口归属 | 说明 |
| --- | --- | --- |
| 顶部主视觉 | `site-config.destinationHero` | 只展示启用项;按 `sortOrder` 升序后 MiniAPP 使用第一项;无有效配置时回退本地静态资源。 |
| 热门区域 | `site-config.destinationRegions` | 只展示启用区域;按 `sortOrder` 升序;MiniAPP 将 `keyword` 为空的项回退为 `label` 后用于搜索。 |
| 省内目的地 | `site-config.destinations` | 只展示启用目的地;按 `sortOrder` 升序;MiniAPP 将 `name` 映射为页面内部 `label` 后展示和搜索。 |
| 推荐线路 | `/api/public/products` | 当前目的地页使用已加载的产品列表前 4 条作为推荐线路;商品卡数据来自 `products.items`。 |
#### 需求页数据归属
| 需求页模块 | 当前接口归属 | 说明 |
| --- | --- | --- |
| 顶部定制说明 | `site-config.demandHero` | 只展示启用项;按 `sortOrder` 升序后 MiniAPP 使用第一项;无有效配置时回退本地文案。 |
| 服务说明卡 | `site-config.demandFeatureCards` | 只展示启用卡片;按 `sortOrder` 升序;无有效配置时回退本地三张说明卡。 |
| 需求表单配置 | `site-config.demandForm` | 后台单例维护,Public 以数组返回;MiniAPP 消费字段标签、占位符、提交按钮和快捷选项。 |
| 热门推荐线路 | `site-config.demandRecommendations` + `/api/public/products.items` | MiniAPP 按 `productIds` 匹配产品列表;无有效匹配时回退本地 `products.slice(16, 22)`。 |
| 表单提交 | `POST /api/public/leads` | 仍使用现有字段,`sourcePage` 固定为 `demand_page`,不新增必填项。 |
#### 响应示例
```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": "小七孔" }]
}
],
"destinationHero": [
{
"id": "destination-hero-1",
"title": "山水、苗寨、古城与野咖同程安排",
"kicker": "贵州小包团目的地",
"image": "/assets/guizhou/huangguoshu-waterfall.jpg",
"isActive": true,
"sortOrder": 0
}
],
"destinationRegions": [
{
"id": "destination-region-1",
"label": "贵阳/安顺",
"keyword": "贵阳安顺",
"spots": "甲秀楼、黄果树",
"isActive": true,
"sortOrder": 0
}
],
"demandHero": [
{
"id": "demand-hero-1",
"kicker": "3步定制",
"title": "告诉我们日期、人数和想法",
"description": "管家会按同行人、预算和体力强度,重新组合酒店、用车和景点节奏。",
"steps": ["提交需求", "管家沟通", "确认方案"],
"isActive": true,
"sortOrder": 0
}
],
"demandFeatureCards": [
{
"id": "demand-feature-1",
"title": "动线",
"description": "按天数顺路排",
"isActive": true,
"sortOrder": 0
}
],
"demandForm": [
{
"id": "demand-form-1",
"destinationLabel": "目的地/玩法",
"destinationPlaceholder": "例如:贵州、黄果树、西江苗寨",
"phoneLabel": "联系方式",
"phonePlaceholder": "手机号 / 微信号",
"noteLabel": "补充说明",
"notePlaceholder": "出行日期、人数、酒店偏好、预算范围",
"submitLabel": "提交出行需求",
"chips": ["贵州", "黄果树", "荔波小七孔"],
"isActive": true
}
],
"demandRecommendations": [
{
"id": "demand-recommendation-1",
"title": "热门推荐",
"subtitle": "也可以先挑一条线路沟通",
"productIds": ["8a6e7c4f-0000-4000-9000-000000000001"],
"isActive": true
}
],
"map": [
{
"id": "map-1",
"image": "/assets/guizhou/guizhou-map.jpg",
"isActive": true
}
],
"themes": [],
"ctaBanners": [],
"campaigns": [
{
"id": "campaign-1",
"slug": "classic-deal",
"title": "经典打卡特惠",
"description": "经典首游活动",
"coverImage": "/assets/guizhou/libo-xiaoqikong.jpg",
"priceAmount": 162500,
"priceUnit": "起/人",
"tags": ["臻藏旅位", "赛事庆典"],
"status": "published",
"startsAt": null,
"endsAt": null
}
],
"routeSections": [
{
"id": "route-section-001",
"title": "经典人文打卡线路",
"subtitle": "黄果树、荔波小七孔、千户苗寨、镇远古城、梵净山一次串联",
"productIds": ["8a6e7c4f-0000-4000-9000-000000000001"],
"isActive": true
},
{
"id": "route-section-002",
"title": "极限山野户外野咖线路",
"subtitle": "溶洞、峡谷、漂流、峰林骑行和山野咖啡组合",
"productIds": [],
"isActive": true
},
{
"id": "route-section-003",
"title": "人文+户外综合混搭线路",
"subtitle": "非遗村寨、古城夜游、自然轻探险和精品住宿同程安排",
"productIds": [],
"isActive": true
}
],
"hotelGroups": [
{
"id": "hotel-group-001",
"title": "经典酒店",
"description": "城市接驳、景区度假和温泉休整,适合首游贵州的小包团动线。",
"image": "/assets/guizhou/bailian-hot-spring.jpg",
"coverImage": "/assets/guizhou/bailian-hot-spring.jpg",
"priceAmount": 68000,
"priceUnit": "起/晚",
"tags": ["温泉", "亲子"],
"status": "published",
"isActive": true,
"sortOrder": 0
}
],
"vehicleOptions": [
{
"id": "vehicle-option-001",
"title": "5座舒适用车",
"description": "适合2-4人家庭或好友小团,城市接送、景区穿梭更灵活。",
"image": "/assets/guizhou/jiaxiu-tower.jpg",
"isActive": true,
"sortOrder": 0
}
]
}
```
### `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 会使用本地产品兜底数据。
- 目的地页的“顶部主视觉”来自 `site-config.destinationHero`;MiniAPP 使用启用列表第一项,并在缺字段或空数组时回退本地静态主视觉。
- 目的地页的“热门区域”来自 `site-config.destinationRegions`;Public API 返回字段为 `label`、`keyword`、`spots`,MiniAPP 归一化后用于区域快捷搜索。
- 目的地页的“省内目的地”来自 `site-config.destinations`;Public API 返回字段为 `name`,MiniAPP 归一化为页面内部 `label` 后用于展示和点击搜索。
- 目的地页的“推荐线路”来自 `/api/public/products.items`;当前页面展示前 4 条产品,后台通过商品维护创建/编辑/归档商品,不通过目的地页接口物理删除商品。
- “精选线路”由 `site-config.routeSections` 定义动态分组标题、副文案和商品 ID 顺序,由 `/api/public/products.items` 提供产品详情;客户端不依赖固定分组 ID 或固定三组数量。
- `routeSections` 缺失、为空或无法匹配到有效商品时,MiniAPP 使用 `src/content.ts` 的本地精选线路内容回退。
- `ctaBanners` 缺失或为空时,MiniAPP 使用 `src/content.ts` 的本地 `bottomCtas` 内容回退。
- “特色酒店”和“万趣用车”分别由 `site-config.hotelGroups`、`site-config.vehicleOptions` 提供;特色酒店优先使用 `coverImage`,字段缺失、数组为空或图片为空时使用本地内容兜底。
- “特价优惠”当前没有独立 Public 结果列表字段;`site-config.campaigns` 只提供活动元信息,活动线路需通过产品标签/关键词筛选或后续扩展活动产品关联字段。
- 产品搜索当前主要在前端执行,依赖 `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`、`destinationHero`、`destinationRegions`、`map`、`themes`、`ctaBanners`、`campaigns`、`routeSections`、`hotelGroups`、`vehicleOptions` 数组字段。
- 为 `GET /api/public/site-config` 验证 `destinations` 只返回启用目的地、按 `sortOrder` 升序,并包含目的地页展示所需的 `id`、`name`、`image`、`isHot` 字段。
- 为 `GET /api/public/site-config` 验证 `destinationHero`、`destinationRegions` 只返回启用项并按 `sortOrder` 升序。
- 为 `GET /api/public/site-config` 验证 `routeSections` 表达“精选线路”子分组;接口返回当前已配置且启用的分组,未配置时返回空数组并由 MiniAPP 本地内容兜底。
- 为 `GET /api/public/site-config` 验证 `routeSections` 只返回启用分组,且 `productIds` 不包含未发布商品。
- 为 `GET /api/public/site-config` 验证 `hotelGroups` 只返回 `published` 且启用卡片,包含 `coverImage`、价格和标签字段;验证 `vehicleOptions` 只返回启用卡片,并按 `sortOrder` 升序。
- 为 `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` 规范化、必填校验、人数/预算边界和备注长度限制。