feat(hotelGroups): add offer display fields and related logic

- Add new database columns (coverImage, priceAmount, priceUnit, tags, status) to HotelGroup model
- Create hotel_group_dict serializer to handle image/coverImage synchronization and tag formatting
- Update site config endpoints to use the new serializer and filter published hotel groups
- Add Alembic migration for the new database schema changes
- Update test fixtures and API contract tests for the new fields
- Revise public and admin API documentation to document the new hotel group fields and usage rules
This commit is contained in:
duanshuwen
2026-07-03 22:44:57 +08:00
parent 201e835eab
commit 1aac3c64b2
9 changed files with 188 additions and 28 deletions

View File

@@ -214,6 +214,11 @@ type SiteConfig = {
title: string;
description?: string | null;
image?: string | null;
coverImage?: string | null;
priceAmount?: number | null;
priceUnit?: string | null;
tags?: string[];
status: "draft" | "published";
isActive: boolean;
sortOrder: number;
createdAt?: string;
@@ -280,7 +285,7 @@ type SiteItemPatch = {
| `themes` | `label``image``targetType``targetValue``isActive` |
| `campaigns` | `title``description``coverImage``priceAmount``priceUnit``tags``status` |
| `routeSections` | `title``subtitle``productIds``isActive``sortOrder` |
| `hotelGroups` | `title``description``image``isActive``sortOrder` |
| `hotelGroups` | `title``description``image``coverImage``priceAmount``priceUnit``tags``status``isActive``sortOrder` |
| `vehicleOptions` | `title``description``image``isActive``sortOrder` |
| `ctaBanners` | `alt`(服务标题)、`image``targetType``targetValue``isActive``sortOrder` |
@@ -483,16 +488,32 @@ PATCH /api/admin/site-config/routeSections/reorder
- `DELETE /api/admin/site-config/routeSections/{section_id}` 只删除分组配置并解除关联,不删除商品本体;删除后后端重新整理剩余分组 `sortOrder`
- `PATCH /api/admin/site-config/routeSections/reorder``itemIds` 必须完整覆盖当前全部分组 ID不能缺失、重复或包含未知 ID。
- `GET /api/public/site-config` 只返回启用分组,且 `productIds` 只包含已发布商品;未发布、归档或不存在的商品不进入 Public 响应。
#### 特色酒店和万趣用车 `hotelGroups` / `vehicleOptions`
#### 特色酒店 `hotelGroups`
`hotelGroups` 首页“特色酒店”卡片配置,`vehicleOptions` 是首页“万趣用车”卡片配置。两者只维护首页模块卡片,不维护商品本体商品关联。
`hotelGroups` 对应首页“特色酒店”模块。该模块已按“特价优惠”的数据配置方式调整,运营可以新增、编辑、删除、排序酒店卡片,并维护标题、描述、价格、标签、封面图和前台展示状态。它只维护首页酒店卡片,不绑定商品本体,也不读取线路商品关联。
```http
POST /api/admin/site-config/hotelGroups
PATCH /api/admin/site-config/hotelGroups/{item_id}
DELETE /api/admin/site-config/hotelGroups/{item_id}
PATCH /api/admin/site-config/hotelGroups/reorder
```
字段规则:
- 新增请求至少包含 `title`,可包含 `description``image``coverImage``priceAmount``priceUnit``tags``status``isActive``sortOrder`
- 更新请求可包含 `title``description``image``coverImage``priceAmount``priceUnit``tags``status``isActive``sortOrder`
- `coverImage` 是酒店封面图主字段;为兼容旧前端,后端同时保留 `image`。当请求只传其中一个字段时,后端应同步另一个字段。
- `priceAmount` 为价格数值,`priceUnit` 为价格单位文案,默认建议为 `起/晚`
- `tags` 最多 3 个,保存时去掉空标签。
- `status` 只允许 `draft``published`;管理端“前台启用”开关会同步提交 `status``isActive`。Public API 只返回 `status="published"``isActive=true` 的酒店卡片。
- 删除只删除首页酒店卡片配置,不删除素材库资源;删除后后端重新整理剩余项 `sortOrder`
- `PATCH /reorder``itemIds` 必须完整覆盖当前 `hotelGroups` 全部配置项 ID不能缺失、重复或包含未知 ID。
#### 万趣用车 `vehicleOptions`
`vehicleOptions` 对应首页“万趣用车”模块,继续作为普通首页内容卡片维护,不复用酒店价格和标签字段。
```http
POST /api/admin/site-config/vehicleOptions
PATCH /api/admin/site-config/vehicleOptions/{item_id}
DELETE /api/admin/site-config/vehicleOptions/{item_id}
@@ -502,9 +523,9 @@ PATCH /api/admin/site-config/vehicleOptions/reorder
字段规则:
- 新增请求至少包含 `title`,可包含 `description``image``isActive``sortOrder`
- 更新请求可包含 `title``description``image``isActive``sortOrder`
- 删除只删除首页卡片配置,不删除商品、目的地或素材库资源;删除后后端重新整理剩余项 `sortOrder`
- `PATCH /reorder``itemIds` 必须完整覆盖当前同模块全部配置项 ID不能缺失、重复或包含未知 ID。
- `GET /api/public/site-config` 只返回启用卡片,并按 `sortOrder` 升序MiniAPP 在字段缺失或空数组时使用本地内容兜底。
- 删除只删除首页用车卡片配置,不删除素材库资源;删除后后端重新整理剩余项 `sortOrder`
- `PATCH /reorder``itemIds` 必须完整覆盖当前 `vehicleOptions` 全部配置项 ID不能缺失、重复或包含未知 ID。
- `GET /api/public/site-config` 只返回启用用车卡片,并按 `sortOrder` 升序MiniAPP 在字段缺失或空数组时使用本地内容兜底。
#### 更多服务 `ctaBanners`

View File

@@ -101,7 +101,7 @@ Public API 输出规则:
### `HomeCard`
`HomeCard` 用于首页“特色酒店”与“万趣用车”两个普通卡片模块。MiniAPP 只消费卡片展示字段,不在这两个模块里读取商品本体或线路商品关联。
`HomeCard` 用于首页“万趣用车”等普通内容卡片模块。MiniAPP 只消费卡片展示字段,不在这模块里读取商品本体或线路商品关联。
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
@@ -112,10 +112,30 @@ Public API 输出规则:
| `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` 只返回启用的 `hotelGroups` `vehicleOptions`
- `GET /api/public/site-config` 只返回 `status="published"` `isActive=true``hotelGroups`
- `GET /api/public/site-config` 只返回启用的 `vehicleOptions`
- 两个数组按后台 `sortOrder` 升序返回。
- 字段缺失、数组为空或图片为空MiniAPP 使用 `src/content.ts` 的本地特色酒店/万趣用车内容兜底。
- `hotelGroups` 字段缺失、数组为空或没有可用图片时MiniAPP 使用 `src/content.ts` 的本地特色酒店内容兜底;酒店图片优先取 `coverImage`,再取 `image`
- `vehicleOptions` 字段缺失、数组为空或图片为空时MiniAPP 使用 `src/content.ts` 的本地万趣用车内容兜底。
### `PublicProduct`
@@ -193,7 +213,7 @@ Public API 输出规则:
| `ctaBanners` | `CtaBanner[]` | “更多服务”卡片配置 |
| `campaigns` | `Campaign[]` | 活动元信息,可用于“特价优惠”入口;当前不包含活动产品结果列表 |
| `routeSections` | `RouteSection[]` | “精选线路”子分组定义 |
| `hotelGroups` | `HomeCard[]` | “特色酒店”卡片配置,只返回启用项 |
| `hotelGroups` | `HotelCard[]` | “特色酒店”卡片配置,只返回 `published`启用项 |
| `vehicleOptions` | `HomeCard[]` | “万趣用车”卡片配置,只返回启用项 |
#### 首页模块数据归属
@@ -203,7 +223,7 @@ Public API 输出规则:
| 特价优惠 | `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` | 返回启用酒店卡片,按后台排序展示;无有效配置时回退本地内容。 |
| 特色酒店 | `site-config.hotelGroups` | 返回 `published`启用酒店卡片,按后台排序展示;MiniAPP 优先消费 `coverImage`无有效配置时回退本地内容。 |
| 万趣用车 | `site-config.vehicleOptions` | 返回启用用车卡片,按后台排序展示;无有效配置时回退本地内容。 |
#### 响应示例
@@ -284,6 +304,11 @@ Public API 输出规则:
"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
}
@@ -476,7 +501,7 @@ Public API 输出规则:
- “精选线路”由 `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` 提供;字段缺失、数组为空或图片为空时使用本地内容兜底。
- “特色酒店”和“万趣用车”分别由 `site-config.hotelGroups``site-config.vehicleOptions` 提供;特色酒店优先使用 `coverImage`字段缺失、数组为空或图片为空时使用本地内容兜底。
- “特价优惠”当前没有独立 Public 结果列表字段;`site-config.campaigns` 只提供活动元信息,活动线路需通过产品标签/关键词筛选或后续扩展活动产品关联字段。
- 产品搜索当前主要在前端执行,依赖 `title``tags``destination.name``summary`
- 产品详情页当前使用已加载的产品列表数据;后续可改为进入详情页时请求 `GET /api/public/products/{product_id}`
@@ -489,7 +514,7 @@ Public API 输出规则:
-`GET /api/public/site-config` 验证返回 JSON 包含 `heroSlides``destinations``map``themes``ctaBanners``campaigns``routeSections``hotelGroups``vehicleOptions` 数组字段。
-`GET /api/public/site-config` 验证 `routeSections` 表达“精选线路”子分组;接口返回当前已配置且启用的分组,未配置时返回空数组并由 MiniAPP 本地内容兜底。
-`GET /api/public/site-config` 验证 `routeSections` 只返回启用分组,且 `productIds` 不包含未发布商品。
-`GET /api/public/site-config` 验证 `hotelGroups` `vehicleOptions` 只返回启用卡片,并按 `sortOrder` 升序。
-`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` 验证只返回启用目的地及别名字段。