---
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 分组 ✅
