diff --git a/changelogs/2026-05/14_frontend_feature_admin_scenic-season-filter-and-tags.md b/changelogs/2026-05/14_frontend_feature_admin_scenic-season-filter-and-tags.md new file mode 100644 index 0000000..8cf2aec --- /dev/null +++ b/changelogs/2026-05/14_frontend_feature_admin_scenic-season-filter-and-tags.md @@ -0,0 +1,136 @@ +--- +date: 2026-05-14 +type: frontend-feature +module: admin-product-v2-itinerary-scenic-picker +priority: medium +backend_pr: "#2229" +backend_issue: "#2227" +notify: ["@mmg"] +status: pending +--- + +# 产品设计选景区按产品线季节过滤 + 节点展示季节标签 + +## 需求 + +产品设计行程编辑器的「**从资源库添加活动 → 景区景点**」弹窗当前列出全部景区,无视产品线季节。期望: + +1. 弹窗景区列表按当前产品线的 `seasons` 过滤 — 比如产品线是秋季就只看到秋季景区 +2. 列表行的**景区缩略图**按季节切换(春→春景图、秋→秋景图) +3. **节点上**(已添加进行程的景区卡片)显示该景区配置的所有季节标签(可多个) + +## 后端已就位 (PR #2229 已合并并部署测试服) + +### 数据基础 + +景区已有独立 `scenic_season` 子表,每季节可配封面/轮播/视频。产品线 `seasons` 已是 `List`(JSON 数组多选)。 + +### 接口变更 + +#### 1. `GET /admin/scenic/spots` — 景区列表 + +**新增 query 参数:** + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `seasons` | `List` | 否 | 多选 OR 过滤,枚举 `spring/summer/autumn/winter`。传任一交集景区即返回。空/不传 = 不过滤 | + +请求格式(数组多参): +```http +GET /admin/scenic/spots?seasons=spring&seasons=autumn&pageSize=20 +``` + +**`ScenicSpotListVO` 新增 2 个字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `seasons` | `List` | 该景区在 `scenic_season` 配置的全部季节(distinct,枚举 `spring/summer/autumn/winter`)。**节点标签直接读这个**。 | + +> ⚠️ **已有的 `coverUrl` 字段含义变更**: 当 query 含 `seasons` 时,`coverUrl` 返回 **query.seasons[0] 对应的 `scenic_season.cover_material_id`** 物化 URL;该季节未配/物化失败回落到主表 `coverMaterialId`。无 query.seasons 时维持主表 cover(行为不变)。 + +#### 真实响应示例 + +**请求**: `GET /admin/scenic/spots?seasons=winter&pageSize=2` + +```json +{ + "code": 200, + "message": "成功", + "data": { + "total": 17, + "records": [ + { + "scenicId": 3001000000000000019, + "name": "中俄边境公路(卡线)", + "cityName": "鄂温克族自治旗", + "coverUrl": "https://images.unsplash.com/photo-1511131341194-...?w=800&q=80", + "seasons": ["spring", "summer", "autumn", "winter"], + "tags": [{"tagId":"3","tagName":"摄影打卡","tagColor":"#409EFF","tagType":0}], + "honors": ["AAA","最美边境公路"], + "highlights": "...", + "longitude": 119.756219, + "latitude": 49.153445, + "rating": 0.0, + "viewCount": 31200, + "sortOrder": 19, + "status": 1 + } + ] + }, + "success": true +} +``` + +同一 scenicId 在 `seasons=spring` 时 `coverUrl` 会变成春景图,无 query 时变回主表 OSS 封面 — 已测试服真测验证切换正确。 + +**反例**(不存在的季节): +```http +GET /admin/scenic/spots?seasons=xxx +→ { "code": 200, "data": { "total": 0, "records": [] } } +``` + +#### 2. 行程节点 VO 透传 `seasons` + +| VO 类 | 端 | 字段 | 状态 | +|-------|-----|------|------| +| `InternalProductDetailVO.NodeItem` | admin | `seasons: List` | ✅ 已落地,SCENIC 节点会回填 | +| `MpProductDetailInternalVO.ItineraryNodeItem` | mp | `seasons` | ⚠️ M2 占位,**当前永远 null**(注释已标注) | + +> Mp 端 itinerary 当前走 M2 占位降级返空数组,等 M2 节点详情上线后才填充。**短期 Mp 端不要依赖 seasons 字段**。 + +## 前端实现建议 + +### 弹窗「从资源库添加活动 → 景区景点」 + +1. 弹窗打开时取当前编辑的产品线 `seasons`(产品线详情接口已返),**调景区列表时透传**: + ```js + const params = new URLSearchParams() + productLineSeasons.forEach(s => params.append('seasons', s)) + await api.get(`/admin/scenic/spots?${params}&pageSize=20`) + ``` +2. 列表行的**缩略图**直接用 VO 返回的 `coverUrl`(后端已按 `seasons[0]` 切换好)。 +3. 列表行的**季节标签**渲染 `vo.seasons` 数组,建议 4 色: + - `spring` → 春绿 `#67C23A` + - `summer` → 夏蓝 `#409EFF` + - `autumn` → 秋橙 `#E6A23C` + - `winter` → 冬蓝灰 `#909399` + +### 已选节点(行程卡片上) + +行程节点详情 admin 端 `InternalProductDetailVO.NodeItem.seasons` 已有 — 在景区节点卡片上渲染同样的 4 色季节标签(可多个)。 + +### 兜底 + +- 老景区数据没配 `scenic_season` 的,`vo.seasons` 返 `[]` 空数组,前端不渲染标签即可。 +- `coverUrl` 任何时候都不会因为新逻辑变 null(已做回落)。 + +## 涉及前端文件 (仅供参考定位) + +- 弹窗组件: 大概率在 `D:\work2\hl-ui\src\views\product-v2\` 下的 itinerary editor 子组件 +- 景区资源 picker: 搜 `从资源库添加活动` / `ScenicResourcePicker` / `scenic-spot-list` + +## 工单 & PR + +- Backend Issue: #2227 +- Backend PR: #2229 (squash merged dev,已部署测试服) +- 测试服真测通过: 过滤 (66→18 autumn/17 winter/0 xxx) ✅ / OR 多选 ✅ / 季节封面切换 ✅ / VO seasons 按 scenic_id 分组 ✅