feat: 景区季节过滤 + 节点季节标签 changelog (PR #2229 / Issue #2227)

这个提交包含在:
API Changelog Bot 2026-05-14 11:02:59 +08:00
父节点 e4605c3755
当前提交 60544ca71c

查看文件

@ -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<String>`(JSON 数组多选)。
### 接口变更
#### 1. `GET /admin/scenic/spots` — 景区列表
**新增 query 参数:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `seasons` | `List<String>` | 否 | 多选 OR 过滤,枚举 `spring/summer/autumn/winter`。传任一交集景区即返回。空/不传 = 不过滤 |
请求格式(数组多参):
```http
GET /admin/scenic/spots?seasons=spring&seasons=autumn&pageSize=20
```
**`ScenicSpotListVO` 新增 2 个字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `seasons` | `List<String>` | 该景区在 `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<String>` | ✅ 已落地,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 分组 ✅