feat(admin): add route sections admin management module

This commit adds the complete admin workflow for managing homepage curated route subgroups:
- Add `RouteSection` type and extend `SiteConfig`/`SiteModule` to support the new module
- Create all required admin components: panel, editors, product picker, and utility functions
- Update admin utilities and API types to handle routeSections CRUD operations
- Update documentation to include the new routeSections API contract
- Add supporting CSS styles for the new UI elements
This commit is contained in:
duanshuwen
2026-07-02 23:05:45 +08:00
parent 50be3cbfce
commit 9fd91c84e5
14 changed files with 1732 additions and 21 deletions

View File

@@ -1,6 +1,6 @@
# 页面模块配置 Admin API 契约
本文档定义 WonderQ-Admin 后端需要为 WonderQ-Admin-UI 实现的页面模块配置 CRUD 接口。接口用于维护小程序/H5 前台页面模块中的配置数据,例如首页轮播、目的地宫格、贵州地图、主题卡片、特价优惠和底部运营入口。
本文档定义 WonderQ-Admin 后端需要为 WonderQ-Admin-UI 实现的页面模块配置 CRUD 接口。接口用于维护小程序/H5 前台页面模块中的配置数据,例如首页轮播、目的地宫格、贵州地图、主题卡片、特价优惠、精选线路分组和底部运营入口。
## 适用模块
@@ -13,9 +13,10 @@
| `map` | 贵州地图 | 首页「探索贵州」区域内的地图图片素材 |
| `themes` | 主题甄选 | 首页主题卡片和跳转 |
| `campaigns` | 特价优惠 | 首页特价优惠活动元信息 |
| `routeSections` | 精选线路子分组 | 首页“精选线路”按运营任务新增分组,维护标题、副文案、启用状态和关联商品 |
| `ctaBanners` | 底部运营入口 | 权益卡、管家入口、需求入口等 CTA |
商品、活动商品池和线索跟进继续走独立业务接口,不混入本契约。
商品本体、活动商品池和线索跟进继续走独立业务接口,不混入本契约。`routeSections` 只维护精选线路子分组和商品 ID 关联,不重复编辑商品详情。
## 通用约定
@@ -40,21 +41,25 @@
WonderQ-Admin 后端实现页面模块配置接口时,需要把 `map``campaigns` 作为正式模块接入,而不是只在前端展示:
- 模块白名单必须包含 `heroSlides``destinations``map``themes``campaigns``ctaBanners`
- 模块白名单必须包含 `heroSlides``destinations``map``themes``campaigns``routeSections``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。
- `map` 只维护一张图片,只需要支持查询、创建、更新、删除,不需要排序接口。
- `campaigns` 维护活动元信息,只需要支持查询、创建、更新、删除,不需要排序接口。
- `routeSections` 允许按运营任务新增多个子分组;已创建分组支持更新字段、商品关联、删除和分组顺序。
- 同一商品不能同时出现在多个 `routeSections` 子分组;更新 `productIds` 时后端需要校验互斥。
- 图片上传仍走 `POST /api/admin/media-assets/upload`,模块保存接口只接收上传结果里的 OSS `url` 字段并写入 `image`
## 类型定义
```ts
type SiteModule = "heroSlides" | "destinations" | "map" | "themes" | "campaigns" | "ctaBanners";
type SiteModule = "heroSlides" | "destinations" | "map" | "themes" | "campaigns" | "routeSections" | "ctaBanners";
type SiteItemPatch = {
title?: string;
subtitle?: string | null;
kicker?: string;
name?: string;
slug?: string;
@@ -69,6 +74,7 @@ type SiteItemPatch = {
isHot?: boolean;
isActive?: boolean;
sortOrder?: number;
productIds?: string[];
status?: "draft" | "published";
startsAt?: string | null;
endsAt?: string | null;
@@ -140,6 +146,17 @@ type CampaignCreateInput = {
};
type CampaignUpdateInput = Partial<CampaignCreateInput>;
type RouteSection = {
id: string;
title: string;
subtitle: string | null;
productIds: string[];
isActive: boolean;
sortOrder: number;
createdAt?: string;
updatedAt?: string;
};
```
各模块字段要求:
@@ -151,6 +168,7 @@ type CampaignUpdateInput = Partial<CampaignCreateInput>;
| `map` | `image` | `isActive` |
| `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` |
后端可以在创建时补全 `id`、默认 `isActive=true`、默认 `sortOrder=当前模块最后一位`
@@ -414,9 +432,68 @@ DELETE /api/admin/site-config/campaigns/:id
特价优惠不定义 `sortOrder``isActive``targetType``targetValue`;兼容期如果请求体携带这些字段,后端可以忽略,但不要写入 `Campaign` 业务数据。
`tags` 保存前需要 trim 并过滤空字符串;有效标签超过 3 个时返回 `422 MODULE_CONFIG_VALIDATION_ERROR``details``{ "field": "tags", "max": 3 }`
## 精选线路子分组 `routeSections` 专用契约
`routeSections` 对应首页“精选线路”的运营分组。运营可按任务新增分组,并维护分组标题、副文案、启用状态、分组顺序、关联线路商品和商品顺序。
字段语义:
| 字段 | 类型 | 更新 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | 不允许修改 | 系统生成分组 ID |
| `title` | `string` | 可选 | 分组标题,不能为空 |
| `subtitle` | `string \| null` | 可选 | 分组副文案 |
| `productIds` | `string[]` | 可选 | 关联商品 ID按数组顺序展示 |
| `isActive` | `boolean` | 可选 | 用户侧是否展示该分组 |
| `sortOrder` | `number` | 可选 | 分组展示顺序 |
更新分组:
```http
POST /api/admin/site-config/routeSections
```
请求体示例:
```json
{
"title": "经典人文打卡线路",
"subtitle": "黄果树、荔波小七孔、千户苗寨、镇远古城、梵净山一次串联"
}
```
响应状态码 `201`,返回创建后的子分组。`id` 由后端生成;精选线路分组按运营任务动态新增,不再限制为固定三组,也不再使用 `routes` / `routes-outdoor` / `routes-mix` 作为固定槽位。
```http
PATCH /api/admin/site-config/routeSections/:id
```
请求体示例:
```json
{
"title": "经典人文打卡线路",
"subtitle": "黄果树、荔波小七孔、千户苗寨、镇远古城、梵净山一次串联",
"isActive": true,
"productIds": ["product-uuid-1", "product-uuid-2"]
}
```
调整分组顺序:
```http
PATCH /api/admin/site-config/routeSections/reorder
```
约束:
- `productIds` 中的商品必须存在。
- 同一商品不能出现在其他精选线路子分组;冲突时返回 `409 ROUTE_SECTION_PRODUCT_CONFLICT`
- `DELETE /api/admin/site-config/routeSections/:id` 删除分组配置并返回被删除 ID只移除首页分组不删除关联商品本体。
## 接口列表
以下路径由类页面模块复用;`heroSlides``map``campaigns` 的请求体和响应体以各自专用契约为准。
以下路径由类页面模块复用;`heroSlides``map``campaigns``routeSections` 的请求体和响应体以各自专用契约为准。
### 获取完整站点配置
@@ -433,12 +510,14 @@ type SiteConfig = {
map: MapImage[];
themes: ThemeCard[];
campaigns: Campaign[];
routeSections: RouteSection[];
ctaBanners: CtaBanner[];
};
```
`map` 字段必须稳定返回数组;无数据时返回空数组 `[]`
`campaigns` 字段必须稳定返回数组;无数据时返回空数组 `[]`
`routeSections` 字段必须稳定返回数组;无数据时返回 `[]`,由管理端通过“新增”逐个创建子分组。
### 新增模块配置项
@@ -504,7 +583,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`,不要创建任何排序数据。
`map``campaigns` 模块不提供排序能力。若收到 `PATCH /api/admin/site-config/map/reorder``PATCH /api/admin/site-config/campaigns/reorder`,后端应返回 `400``405`,不要创建任何排序数据。`routeSections` 支持排序,但 `itemIds` 必须刚好包含当前已保存的子分组 ID。
响应状态码 `200`