feat(route-sections): add dynamic route sections
Replace hardcoded homepage featured route groups with a fully managed dynamic system: - add database models RouteSection and RouteSectionProduct for storing route groups and their associated products - create Alembic migration 0004_route_sections for the new tables - extend SiteConfigPatchIn schema with subtitle and productIds fields - refactor shared site_config utility to load dynamic route sections instead of fixed groups - implement admin CRUD API with validation for duplicate/conflicting product associations - update public and admin API documentation to reflect the new system - add default route section seed data and comprehensive test coverage
This commit is contained in:
@@ -78,14 +78,21 @@
|
||||
|
||||
### `RouteSection`
|
||||
|
||||
`RouteSection` 用于描述首页“精选线路”下的分组。`经典人文打卡线路`、`极限山野户外野咖线路`、`人文+户外综合混搭线路` 等属于“精选线路”的子集,不是独立一级模块。
|
||||
`RouteSection` 用于描述首页“精选线路”下的动态运营分组。后台可按任务新增、编辑、删除和排序分组;MiniAPP 不应依赖固定分组 ID,只按接口返回的分组顺序和 `productIds` 渲染。
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | 是 | 分组 ID,例如 `routes`、`routes-outdoor`、`routes-mix` |
|
||||
| `id` | `string` | 是 | 后端生成的分组 ID,客户端只用于列表 key 和商品关联,不作为固定业务枚举 |
|
||||
| `title` | `string` | 是 | 分组标题 |
|
||||
| `productIds` | `string[]` | 是 | 该分组包含的产品 ID,产品详情来自 `/api/public/products` 的 `items` |
|
||||
| `subtitle` | `string \| null` | 否 | 分组副文案 |
|
||||
| `productIds` | `string[]` | 是 | 该分组包含的产品 ID;产品详情来自 `/api/public/products.items` |
|
||||
| `isActive` | `boolean` | 否 | 是否启用;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` 的本地精选线路兜底内容。
|
||||
### `PublicProduct`
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
@@ -168,11 +175,7 @@
|
||||
| 首页模块 | 当前接口归属 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 特价优惠 | `site-config.campaigns` + `/api/public/products` | 当前 Public API 只返回活动元信息,不直接返回“特价优惠结果列表”。MiniAPP 若要展示活动线路,可按活动标题、标签或后续扩展的活动产品关联从 `/api/public/products` 中筛选。 |
|
||||
| 精选线路 | `site-config.routeSections` + `/api/public/products` | `routeSections` 只返回分组与 `productIds`;具体产品卡片数据由 `/api/public/products.items` 提供。 |
|
||||
| 经典人文打卡线路 | `routeSections` 子集 | 属于“精选线路”子分组,当前后端分组 ID 为 `routes`。 |
|
||||
| 极限山野户外野咖线路 | `routeSections` 子集 | 属于“精选线路”子分组,当前后端分组 ID 为 `routes-outdoor`。 |
|
||||
| 人文+户外综合混搭线路 | `routeSections` 子集 | 属于“精选线路”子分组,当前后端分组 ID 为 `routes-mix`。 |
|
||||
|
||||
| 精选线路 | `site-config.routeSections` + `/api/public/products` | `routeSections` 返回动态分组与 `productIds`;具体产品卡片数据由 `/api/public/products.items` 提供。后台可按任务新增、删除、停用和排序分组,用户侧不假设固定三组。 |
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
@@ -224,19 +227,25 @@
|
||||
],
|
||||
"routeSections": [
|
||||
{
|
||||
"id": "routes",
|
||||
"id": "route-section-001",
|
||||
"title": "经典人文打卡线路",
|
||||
"productIds": ["8a6e7c4f-0000-4000-9000-000000000001"]
|
||||
"subtitle": "黄果树、荔波小七孔、千户苗寨、镇远古城、梵净山一次串联",
|
||||
"productIds": ["8a6e7c4f-0000-4000-9000-000000000001"],
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "routes-outdoor",
|
||||
"id": "route-section-002",
|
||||
"title": "极限山野户外野咖线路",
|
||||
"productIds": []
|
||||
"subtitle": "溶洞、峡谷、漂流、峰林骑行和山野咖啡组合",
|
||||
"productIds": [],
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "routes-mix",
|
||||
"id": "route-section-003",
|
||||
"title": "人文+户外综合混搭线路",
|
||||
"productIds": []
|
||||
"subtitle": "非遗村寨、古城夜游、自然轻探险和精品住宿同程安排",
|
||||
"productIds": [],
|
||||
"isActive": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -414,7 +423,8 @@
|
||||
|
||||
- `site-config` 与 `products` 会在应用启动时并行请求;任一请求失败时,MiniAPP 会回退到本地静态内容。
|
||||
- `products.items` 为空时,MiniAPP 会使用本地产品兜底数据。
|
||||
- “精选线路”由 `site-config.routeSections` 定义分组,由 `/api/public/products.items` 提供产品详情;`经典人文打卡线路`、`极限山野户外野咖线路`、`人文+户外综合混搭线路` 是“精选线路”的子集。
|
||||
- “精选线路”由 `site-config.routeSections` 定义动态分组标题、副文案和商品 ID 顺序,由 `/api/public/products.items` 提供产品详情;客户端不依赖固定分组 ID 或固定三组数量。
|
||||
- `routeSections` 缺失、为空或无法匹配到有效商品时,MiniAPP 使用 `src/content.ts` 的本地精选线路内容回退。
|
||||
- “特价优惠”当前没有独立 Public 结果列表字段;`site-config.campaigns` 只提供活动元信息,活动线路需通过产品标签/关键词筛选或后续扩展活动产品关联字段。
|
||||
- 产品搜索当前主要在前端执行,依赖 `title`、`tags`、`destination.name`、`summary`。
|
||||
- 产品详情页当前使用已加载的产品列表数据;后续可改为进入详情页时请求 `GET /api/public/products/{product_id}`。
|
||||
@@ -425,7 +435,8 @@
|
||||
|
||||
- 为 `GET /health` 增加或保留健康检查测试。
|
||||
- 为 `GET /api/public/site-config` 验证返回 JSON 包含 `heroSlides`、`destinations`、`map`、`themes`、`ctaBanners`、`campaigns`、`routeSections` 数组字段。
|
||||
- 为 `GET /api/public/site-config` 验证 `routeSections` 表达“精选线路”子分组,并包含 `routes`、`routes-outdoor`、`routes-mix` 三个当前约定分组。
|
||||
- 为 `GET /api/public/site-config` 验证 `routeSections` 表达“精选线路”子分组;接口返回当前已配置且启用的分组,未配置时返回空数组并由 MiniAPP 本地内容兜底。
|
||||
- 为 `GET /api/public/site-config` 验证 `routeSections` 只返回启用分组,且 `productIds` 不包含未发布商品。
|
||||
- 为 `GET /api/public/products` 验证响应结构为 `{ items: [...] }`,并覆盖 `keyword`、`destinationId`、`status`、`take` 参数。
|
||||
- 为 `GET /api/public/products/{product_id}` 验证 UUID、数字 `sourceId` 和 404 场景。
|
||||
- 为 `GET /api/public/destinations` 验证只返回启用目的地及别名字段。
|
||||
|
||||
Reference in New Issue
Block a user