feat: add campaigns module with CRUD operations and UI integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 页面模块配置 Admin API 契约
|
||||
|
||||
本文档定义 WonderQ-Admin 后端需要为 WonderQ-Admin-UI 实现的页面模块配置 CRUD 接口。接口用于维护小程序/H5 前台页面模块中的配置数据,例如首页轮播、目的地宫格、贵州地图、主题卡片和底部运营入口。
|
||||
本文档定义 WonderQ-Admin 后端需要为 WonderQ-Admin-UI 实现的页面模块配置 CRUD 接口。接口用于维护小程序/H5 前台页面模块中的配置数据,例如首页轮播、目的地宫格、贵州地图、主题卡片、特价优惠和底部运营入口。
|
||||
|
||||
## 适用模块
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
| `destinations` | 目的地 | 首页/目的地页展示、搜索入口和热门标记 |
|
||||
| `map` | 贵州地图 | 首页「探索贵州」区域内的地图图片素材 |
|
||||
| `themes` | 主题甄选 | 首页主题卡片和跳转 |
|
||||
| `campaigns` | 特价优惠 | 首页特价优惠活动元信息 |
|
||||
| `ctaBanners` | 底部运营入口 | 权益卡、管家入口、需求入口等 CTA |
|
||||
|
||||
商品池、活动商品池和线索跟进继续走独立业务接口,不混入本契约。
|
||||
@@ -37,18 +38,20 @@
|
||||
|
||||
## 后端实现重点
|
||||
|
||||
WonderQ-Admin 后端实现页面模块配置接口时,需要把 `map` 作为正式模块接入,而不是只在前端展示:
|
||||
WonderQ-Admin 后端实现页面模块配置接口时,需要把 `map` 和 `campaigns` 作为正式模块接入,而不是只在前端展示:
|
||||
|
||||
- 模块白名单必须包含 `heroSlides`、`destinations`、`map`、`themes`、`ctaBanners`。
|
||||
- 权限校验、模块路由、服务层分发和数据模型映射都必须识别 `map`,否则前端会收到 `MODULE_CONFIG_FORBIDDEN` 并以 toast 展示失败原因。
|
||||
- 模块白名单必须包含 `heroSlides`、`destinations`、`map`、`themes`、`campaigns`、`ctaBanners`。
|
||||
- 权限校验、模块路由、服务层分发和数据模型映射都必须识别 `map` 和 `campaigns`,否则前端会收到 `MODULE_CONFIG_FORBIDDEN` 并以 toast 展示失败原因。
|
||||
- `GET /api/admin/site-config` 即使没有地图数据,也必须返回 `map: []`,不要省略 `map` 字段。
|
||||
- `GET /api/admin/site-config` 即使没有特价优惠数据,也必须返回 `campaigns: []`,不要省略 `campaigns` 字段。
|
||||
- `map` 只维护一张图片,只需要支持查询、创建、更新、删除,不需要排序接口。
|
||||
- `campaigns` 维护活动元信息,只需要支持查询、创建、更新、删除,不需要排序接口。
|
||||
- 图片上传仍走 `POST /api/admin/media-assets/upload`,模块保存接口只接收上传结果里的 OSS `url` 字段并写入 `image`。
|
||||
|
||||
## 类型定义
|
||||
|
||||
```ts
|
||||
type SiteModule = "heroSlides" | "destinations" | "map" | "themes" | "ctaBanners";
|
||||
type SiteModule = "heroSlides" | "destinations" | "map" | "themes" | "campaigns" | "ctaBanners";
|
||||
|
||||
type SiteItemPatch = {
|
||||
title?: string;
|
||||
@@ -59,11 +62,16 @@ type SiteItemPatch = {
|
||||
label?: string;
|
||||
alt?: string;
|
||||
image?: string | null;
|
||||
description?: string | null;
|
||||
coverImage?: string | null;
|
||||
targetType?: string | null;
|
||||
targetValue?: string | null;
|
||||
isHot?: boolean;
|
||||
isActive?: boolean;
|
||||
sortOrder?: number;
|
||||
status?: "draft" | "published";
|
||||
startsAt?: string | null;
|
||||
endsAt?: string | null;
|
||||
};
|
||||
|
||||
type HeroSlide = {
|
||||
@@ -101,6 +109,37 @@ type MapImageCreateInput = {
|
||||
};
|
||||
|
||||
type MapImageUpdateInput = Partial<MapImageCreateInput>;
|
||||
|
||||
type Campaign = {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
coverImage: string | null;
|
||||
priceAmount: number | null;
|
||||
priceUnit: string | null;
|
||||
tags: string[];
|
||||
status: "draft" | "published";
|
||||
startsAt: string | null;
|
||||
endsAt: string | null;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
type CampaignCreateInput = {
|
||||
title: string;
|
||||
slug: string;
|
||||
description?: string | null;
|
||||
coverImage?: string | null;
|
||||
priceAmount?: number | null;
|
||||
priceUnit?: string | null;
|
||||
tags?: string[];
|
||||
status?: "draft" | "published";
|
||||
startsAt?: string | null;
|
||||
endsAt?: string | null;
|
||||
};
|
||||
|
||||
type CampaignUpdateInput = Partial<CampaignCreateInput>;
|
||||
```
|
||||
|
||||
各模块字段要求:
|
||||
@@ -111,9 +150,12 @@ type MapImageUpdateInput = Partial<MapImageCreateInput>;
|
||||
| `destinations` | `name` | `slug`、`region`、`image`、`isHot`、`isActive`、`sortOrder` |
|
||||
| `map` | `image` | `isActive` |
|
||||
| `themes` | `label` | `image`、`targetType`、`targetValue`、`isActive`、`sortOrder` |
|
||||
| `campaigns` | `title`、`slug` | `description`、`coverImage`、`priceAmount`、`priceUnit`、`tags`、`status`、`startsAt`、`endsAt` |
|
||||
| `ctaBanners` | `alt` | `image`、`targetType`、`targetValue`、`isActive`、`sortOrder` |
|
||||
|
||||
后端可以在创建时补全 `id`、默认 `isActive=true`、默认 `sortOrder=当前模块最后一位`。
|
||||
`campaigns` 创建时默认 `status="draft"`,不会进入 Public `site-config.campaigns`;只有 `status="published"` 的活动会进入 H5 Public API。
|
||||
Admin UI 面向运营只展示活动标题、活动描述、封面图和前台启用状态;`slug` 是接口必填技术标识,前端可在新建时自动生成,编辑时复用原值。
|
||||
|
||||
## 顶部轮播 `heroSlides` 专用契约
|
||||
|
||||
@@ -310,9 +352,71 @@ PATCH /api/admin/site-config/map/:id
|
||||
DELETE /api/admin/site-config/map/:id
|
||||
```
|
||||
|
||||
## 特价优惠 `campaigns` 专用契约
|
||||
|
||||
特价优惠模块对应 H5 Public API 的 `site-config.campaigns`,只维护活动元信息,不维护活动商品关联列表。
|
||||
|
||||
字段语义:
|
||||
|
||||
| 字段 | 类型 | 创建 | 更新 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | 后端生成 | 不允许修改 | 活动唯一 id |
|
||||
| `slug` | `string` | 必填 | 可选 | 活动标识,需要全局唯一 |
|
||||
| `title` | `string` | 必填 | 可选 | 活动标题,后台列表主标题 |
|
||||
| `description` | `string \| null` | 可选 | 可选 | 活动描述,后台列表副文案 |
|
||||
| `coverImage` | `string \| null` | 可选 | 可选 | 活动封面图 OSS URL |
|
||||
| `priceAmount` | `number \| null` | 可选 | 可选 | 参考起价,单位按 `priceUnit` 展示 |
|
||||
| `priceUnit` | `string \| null` | 可选 | 可选 | 价格单位文案,默认 `起/人` |
|
||||
| `tags` | `string[]` | 可选 | 可选 | 活动卡片标签,最多 3 个 |
|
||||
| `status` | `"draft" \| "published"` | 可选 | 可选 | 新建默认 `draft`;Public API 只返回 `published` |
|
||||
| `startsAt` | `string \| null` | 可选 | 可选 | 活动开始时间 |
|
||||
| `endsAt` | `string \| null` | 可选 | 可选 | 活动结束时间 |
|
||||
| `createdAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 |
|
||||
| `updatedAt` | `string` | 后端生成 | 后端维护 | ISO 时间字符串,可选返回 |
|
||||
|
||||
### 特价优惠 CRUD
|
||||
|
||||
新增特价优惠:
|
||||
|
||||
```http
|
||||
POST /api/admin/site-config/campaigns
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"slug": "classic-deal",
|
||||
"title": "经典打卡特惠",
|
||||
"description": "经典首游活动",
|
||||
"coverImage": "https://bucket.oss-cn-example.aliyuncs.com/admin/campaigns/2026/07/02/classic.webp",
|
||||
"priceAmount": 162500,
|
||||
"priceUnit": "起/人",
|
||||
"tags": ["臻藏旅位", "赛事庆典"],
|
||||
"status": "draft",
|
||||
"startsAt": null,
|
||||
"endsAt": null
|
||||
}
|
||||
```
|
||||
|
||||
更新特价优惠:
|
||||
|
||||
```http
|
||||
PATCH /api/admin/site-config/campaigns/:id
|
||||
```
|
||||
|
||||
删除特价优惠:
|
||||
|
||||
```http
|
||||
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 }`。
|
||||
|
||||
## 接口列表
|
||||
|
||||
以下路径由五类页面模块复用;`heroSlides`、`map` 的请求体和响应体以各自专用契约为准。
|
||||
以下路径由六类页面模块复用;`heroSlides`、`map`、`campaigns` 的请求体和响应体以各自专用契约为准。
|
||||
|
||||
### 获取完整站点配置
|
||||
|
||||
@@ -328,11 +432,13 @@ type SiteConfig = {
|
||||
destinations: Destination[];
|
||||
map: MapImage[];
|
||||
themes: ThemeCard[];
|
||||
campaigns: Campaign[];
|
||||
ctaBanners: CtaBanner[];
|
||||
};
|
||||
```
|
||||
|
||||
`map` 字段必须稳定返回数组;无数据时返回空数组 `[]`。
|
||||
`campaigns` 字段必须稳定返回数组;无数据时返回空数组 `[]`。
|
||||
|
||||
### 新增模块配置项
|
||||
|
||||
@@ -398,7 +504,7 @@ PATCH /api/admin/site-config/:module/reorder
|
||||
- 不允许混入其他模块 id。
|
||||
- 后端按数组顺序写入 `sortOrder`,从 0 开始。
|
||||
|
||||
`map` 模块不提供排序能力。若收到 `PATCH /api/admin/site-config/map/reorder`,后端应返回 `400` 或 `405`,不要创建任何排序数据。
|
||||
`map` 和 `campaigns` 模块不提供排序能力。若收到 `PATCH /api/admin/site-config/map/reorder` 或 `PATCH /api/admin/site-config/campaigns/reorder`,后端应返回 `400` 或 `405`,不要创建任何排序数据。
|
||||
|
||||
响应状态码 `200`:
|
||||
|
||||
@@ -441,7 +547,7 @@ WonderQ-Admin-UI 当前调用函数位于 `src/api.ts`:
|
||||
|
||||
## 图片素材上传
|
||||
|
||||
用于 WonderQ-Admin-UI 在维护页面模块图片时上传本地图片,并把返回的 OSS URL 写入 `image` 字段。
|
||||
用于 WonderQ-Admin-UI 在维护页面模块图片时上传本地图片,并把返回的 OSS URL 写入对应模块图片字段,例如 `image` 或 `coverImage`。
|
||||
|
||||
```http
|
||||
POST /api/admin/media-assets/upload
|
||||
|
||||
Reference in New Issue
Block a user