feat(admin): add hotel groups and vehicle options site modules

Add support for two new standalone site content modules in the admin dashboard:
hotel groups and vehicle options. This commit includes:
- Added TypeScript types for HotelGroup and VehicleOption, extended SiteConfig and SiteModule enum
- Updated admin utilities (primary key lookup, empty drafts, payload compaction) for the new modules
- Added management UI panels and editors in the structure page for configuring the new modules
- Adjusted module rail styling and interactive states for consistent UI across the admin
- Updated ctaBanners type definition to include missing sortOrder, createdAt, and updatedAt fields
- Updated API and admin documentation to cover the new modules' CRUD endpoints and field contracts
This commit is contained in:
duanshuwen
2026-07-03 20:26:27 +08:00
parent 9fd91c84e5
commit 86a12dd1a0
9 changed files with 553 additions and 191 deletions

View File

@@ -1,6 +1,6 @@
# 页面模块配置 Admin API 契约
本文档定义 WonderQ-Admin 后端需要为 WonderQ-Admin-UI 实现的页面模块配置 CRUD 接口。接口用于维护小程序/H5 前台页面模块中的配置数据,例如首页轮播、目的地宫格、贵州地图、主题卡片、特价优惠、精选线路分组和底部运营入口
本文档定义 WonderQ-Admin 后端需要为 WonderQ-Admin-UI 实现的页面模块配置 CRUD 接口。接口用于维护小程序/H5 前台页面模块中的配置数据,例如首页轮播、目的地宫格、贵州地图、主题卡片、特价优惠、精选线路分组、特色酒店、万趣用车和更多服务
## 适用模块
@@ -14,9 +14,11 @@
| `themes` | 主题甄选 | 首页主题卡片和跳转 |
| `campaigns` | 特价优惠 | 首页特价优惠活动元信息 |
| `routeSections` | 精选线路子分组 | 首页“精选线路”按运营任务新增分组,维护标题、副文案、启用状态和关联商品 |
| `ctaBanners` | 底部运营入口 | 权益卡、管家入口、需求入口等 CTA |
| `hotelGroups` | 特色酒店 | 首页“特色酒店”卡片,维护标题、描述、封面图、启用状态和排序 |
| `vehicleOptions` | 万趣用车 | 首页“万趣用车”卡片,维护标题、描述、封面图、启用状态和排序 |
| `ctaBanners` | 更多服务 | 权益、管家、目的地和需求入口等更多服务卡片 |
商品本体、活动商品池和线索跟进继续走独立业务接口,不混入本契约。`routeSections` 只维护精选线路子分组和商品 ID 关联,不重复编辑商品详情。
商品本体、活动商品池和线索跟进继续走独立业务接口,不混入本契约。`routeSections` 只维护精选线路子分组和商品 ID 关联,不重复编辑商品详情`hotelGroups``vehicleOptions` 只维护首页卡片内容,不绑定商品本体
## 通用约定
@@ -41,21 +43,21 @@
WonderQ-Admin 后端实现页面模块配置接口时,需要把 `map``campaigns` 作为正式模块接入,而不是只在前端展示:
- 模块白名单必须包含 `heroSlides``destinations``map``themes``campaigns``routeSections``ctaBanners`
- 模块白名单必须包含 `heroSlides``destinations``map``themes``campaigns``routeSections``hotelGroups``vehicleOptions``ctaBanners`
- 权限校验、模块路由、服务层分发和数据模型映射都必须识别 `map``campaigns`,否则前端会收到 `MODULE_CONFIG_FORBIDDEN` 并以 toast 展示失败原因。
- `GET /api/admin/site-config` 即使没有地图数据,也必须返回 `map: []`,不要省略 `map` 字段。
- `GET /api/admin/site-config` 即使没有特价优惠数据,也必须返回 `campaigns: []`,不要省略 `campaigns` 字段。
- `GET /api/admin/site-config` 必须返回 `routeSections`,包含未启用分组和后台已配置的全部商品 ID。
- `GET /api/admin/site-config` 必须返回 `routeSections`,包含未启用分组和后台已配置的全部商品 ID。`hotelGroups``vehicleOptions` 也必须稳定返回数组,无数据时返回 `[]`
- `map` 只维护一张图片,只需要支持查询、创建、更新、删除,不需要排序接口。
- `campaigns` 维护活动元信息,只需要支持查询、创建、更新、删除,不需要排序接口。
- `routeSections` 允许按运营任务新增多个子分组;已创建分组支持更新字段、商品关联、删除和分组顺序。
- `routeSections` 允许按运营任务新增多个子分组;已创建分组支持更新字段、商品关联、删除和分组顺序。`hotelGroups``vehicleOptions` 支持新增、更新、删除和排序。
- 同一商品不能同时出现在多个 `routeSections` 子分组;更新 `productIds` 时后端需要校验互斥。
- 图片上传仍走 `POST /api/admin/media-assets/upload`,模块保存接口只接收上传结果里的 OSS `url` 字段并写入 `image`
## 类型定义
```ts
type SiteModule = "heroSlides" | "destinations" | "map" | "themes" | "campaigns" | "routeSections" | "ctaBanners";
type SiteModule = "heroSlides" | "destinations" | "map" | "themes" | "campaigns" | "routeSections" | "hotelGroups" | "vehicleOptions" | "ctaBanners";
type SiteItemPatch = {
title?: string;
@@ -157,6 +159,29 @@ type RouteSection = {
createdAt?: string;
updatedAt?: string;
};
type SiteCardItem = {
id: string;
title: string;
description: string | null;
image: string | null;
isActive: boolean;
sortOrder: number;
createdAt?: string;
updatedAt?: string;
};
type CtaBanner = {
id: string;
alt: string;
image: string;
targetType: string | null;
targetValue: string | null;
isActive: boolean;
sortOrder: number;
createdAt?: string;
updatedAt?: string;
};
```
各模块字段要求:
@@ -169,7 +194,9 @@ type RouteSection = {
| `themes` | `label` | `image``targetType``targetValue``isActive``sortOrder` |
| `campaigns` | `title``slug` | `description``coverImage``priceAmount``priceUnit``tags``status``startsAt``endsAt` |
| `routeSections` | `title` | `subtitle``productIds``isActive``sortOrder` |
| `ctaBanners` | `alt` | `image``targetType``targetValue``isActive``sortOrder` |
| `hotelGroups` | `title` | `description``image``isActive``sortOrder` |
| `vehicleOptions` | `title` | `description``image``isActive``sortOrder` |
| `ctaBanners` | `alt`(服务标题) | `image``targetType``targetValue``isActive``sortOrder` |
后端可以在创建时补全 `id`、默认 `isActive=true`、默认 `sortOrder=当前模块最后一位`
`campaigns` 创建时默认 `status="draft"`,不会进入 Public `site-config.campaigns`;只有 `status="published"` 的活动会进入 H5 Public API。
@@ -491,9 +518,67 @@ PATCH /api/admin/site-config/routeSections/reorder
- 同一商品不能出现在其他精选线路子分组;冲突时返回 `409 ROUTE_SECTION_PRODUCT_CONFLICT`
- `DELETE /api/admin/site-config/routeSections/:id` 删除分组配置并返回被删除 ID只移除首页分组不删除关联商品本体。
## 特色酒店和万趣用车 `hotelGroups` / `vehicleOptions` 专用契约
`hotelGroups` 对应首页“特色酒店”卡片,`vehicleOptions` 对应首页“万趣用车”卡片。两者都是普通首页内容卡片,不维护商品详情和商品关联。
字段语义:
| 字段 | 类型 | 创建 | 更新 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | `string` | 后端生成 | 不允许修改 | 卡片唯一 id |
| `title` | `string` | 必填 | 可选 | 卡片标题,提交时 trim 后不能为空 |
| `description` | `string \| null` | 可选 | 可选 | 卡片描述,空字符串可归一化为 `null` |
| `image` | `string \| null` | 可选 | 可选 | 卡片封面图 OSS URL上传仍走媒体接口 |
| `isActive` | `boolean` | 可选 | 可选 | 用户侧是否展示;未传默认 `true` |
| `sortOrder` | `number` | 可选 | 可选 | 展示顺序;未传时追加到模块末尾 |
两类模块均复用通用接口:
```http
POST /api/admin/site-config/hotelGroups
PATCH /api/admin/site-config/hotelGroups/:id
DELETE /api/admin/site-config/hotelGroups/:id
PATCH /api/admin/site-config/hotelGroups/reorder
POST /api/admin/site-config/vehicleOptions
PATCH /api/admin/site-config/vehicleOptions/:id
DELETE /api/admin/site-config/vehicleOptions/:id
PATCH /api/admin/site-config/vehicleOptions/reorder
```
删除只删除首页卡片配置不删除任何商品、目的地或素材库资源。Public API 只返回启用项,并按 `sortOrder` 升序输出;无启用项时 MiniAPP 使用本地 `src/content.ts` 兜底内容。
## 更多服务 `ctaBanners` 专用契约
`ctaBanners` 对应首页“更多服务”模块,用于维护权益、管家、目的地和需求入口等服务卡片。管理端复用顶部轮播的列表式操作:新增卡片、编辑标题和背景图、删除卡片、上移/下移排序,以及控制前台启用状态。
字段语义:
| 字段 | 类型 | 创建 | 更新 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | `string` | 后端生成 | 不允许修改 | 服务卡片唯一 id |
| `alt` | `string` | 必填 | 可选 | 服务标题,展示在“更多服务”卡片上,提交时 trim 后不能为空 |
| `image` | `string \| null` | 可选 | 可选 | 服务卡片背景图 OSS URL上传仍走媒体接口 |
| `targetType` | `string \| null` | 可选 | 可选 | 点击目标类型,例如权益、管家、目的地或需求入口 |
| `targetValue` | `string \| null` | 可选 | 可选 | 点击目标值;无额外参数时可为空 |
| `isActive` | `boolean` | 可选 | 可选 | 用户侧是否展示;未传默认 `true` |
| `sortOrder` | `number` | 可选 | 可选 | 展示顺序;未传时追加到模块末尾 |
复用通用接口:
```http
POST /api/admin/site-config/ctaBanners
PATCH /api/admin/site-config/ctaBanners/:id
DELETE /api/admin/site-config/ctaBanners/:id
PATCH /api/admin/site-config/ctaBanners/reorder
```
删除只删除首页“更多服务”卡片配置不删除任何素材库资源。Public API 只返回启用项,并按 `sortOrder` 升序输出;无启用项时 MiniAPP 使用本地 `src/content.ts` 兜底内容。
## 接口列表
以下路径由类页面模块复用;`heroSlides``map``campaigns``routeSections` 的请求体和响应体以各自专用契约为准。
以下路径由类页面模块复用;`heroSlides``map``campaigns``routeSections``hotelGroups``vehicleOptions``ctaBanners` 的请求体和响应体以各自专用契约为准。
### 获取完整站点配置
@@ -511,13 +596,15 @@ type SiteConfig = {
themes: ThemeCard[];
campaigns: Campaign[];
routeSections: RouteSection[];
hotelGroups: SiteCardItem[];
vehicleOptions: SiteCardItem[];
ctaBanners: CtaBanner[];
};
```
`map` 字段必须稳定返回数组;无数据时返回空数组 `[]`
`campaigns` 字段必须稳定返回数组;无数据时返回空数组 `[]`
`routeSections` 字段必须稳定返回数组;无数据时返回 `[]`,由管理端通过“新增”逐个创建子分组。
`routeSections` 字段必须稳定返回数组;无数据时返回 `[]`,由管理端通过“新增”逐个创建子分组。`hotelGroups``vehicleOptions` 字段也必须稳定返回数组;无数据时返回 `[]`
### 新增模块配置项
@@ -583,7 +670,7 @@ PATCH /api/admin/site-config/:module/reorder
- 不允许混入其他模块 id。
- 后端按数组顺序写入 `sortOrder`,从 0 开始。
`map``campaigns` 模块不提供排序能力。若收到 `PATCH /api/admin/site-config/map/reorder``PATCH /api/admin/site-config/campaigns/reorder`,后端应返回 `400``405`,不要创建任何排序数据。`routeSections` 支持排序,但 `itemIds` 必须刚好包含当前已保存的子分组 ID。
`map``campaigns` 模块不提供排序能力。若收到 `PATCH /api/admin/site-config/map/reorder``PATCH /api/admin/site-config/campaigns/reorder`,后端应返回 `400``405`,不要创建任何排序数据。`routeSections``hotelGroups``vehicleOptions` 支持排序,但 `itemIds` 必须刚好包含当前已保存的同模块配置项 ID。
响应状态码 `200`