# 产品标签改单选 + 字典值整体替换

**日期**：2026-04-20
**PR**：无代码变更（纯字典数据迁移）
**Issue**：#1001
**影响**：管理端产品编辑页 Step1 基础信息「产品标签」

---

## 为什么变

产品侧简化标签心智：
1. 每个产品只配 **1 个标签**（原来多选 chip，现在单选）
2. 字典 `product_tag` 整体换值：
   - **旧**：亲子 / 城记 / 摄影 / 研学 / 露营
   - **新**：热内 / 新品 / 限定 / 硬核 / 治愈

---

## 后端改动

**只改字典，不动接口、不动字段类型**：

- `product_tag` 字典：删除 5 个老项，新增 5 个新项（sortOrder 1→5）
- `ProductDO.tags` 字段**仍然是 `List<String>`**（JSON 数组，DB 列类型 JSON，不动）
- `ProductBasicSaveReqVO.tags` **仍然是 `List<String>`**，**没加 `@Size(max=1)` 校验**
  - 原因：老产品 tags 已有多值（图示摄影/露营/亲子），加硬校验会直接卡死这些产品的保存
- `MpProduct*RespVO.tags` **仍然是 `List<String>`**
- 没有 DDL 变更

### 接口签名不变

| Method | Path | 说明 |
|---|---|---|
| POST | `/admin/product/item/basic` | `tags: string[]` 字段类型完全不变 |
| GET | `/admin/product/item/detail` | 同上 |
| GET | `/mp/product/list` / `/mp/product/detail` | 同上 |
| GET | `/admin/dict/data/product_tag` | 现在返回 5 个新字典项 |
| GET | `/dict/all`（公开）| 其中 `product_tag` 类目返回 5 个新字典项 |

---

## 前端改造（要做）

### 产品编辑页 Step1 —— 产品标签输入控件

- 从**多选 chip** 改为**单选下拉** (Select / Radio Group / 单选 chip 二选一，UI 同学定)
- 选项数据源：`/admin/dict/all` → `dictType === 'product_tag'` → `dataList` 5 项
- **提交格式仍然是数组**：
  ```js
  // 保持兼容后端 List<String> 字段
  saveReqVO.tags = selected ? [selected] : []
  ```
  不要直接传字符串 —— 后端仍按数组接
- **后端没加 max=1 硬校验**，所以就算前端误传 ≥2 个也不会报错，但业务上就按单选出

### 老数据兼容（关键）

现有产品 tags 里可能存了老字典值（摄影/露营/亲子/研学/城记），编辑页打开时：

- **不要把 `tags` 里不在新字典 5 项里的值过滤掉** —— 这会悄悄丢数据
- **建议展示**：老 tag 值按字符串原样显示，加个灰色提示 "已下架标签，请重新选择"
- 用户保存时前端强制用户从 5 个新值中选一个（单选默认行为）

### `/dict/all` 的 `product_tag` 新响应

```jsonc
{
  "dictType": "product_tag",
  "dictName": "产品标签",
  "dataList": [
    { "dictValue": "热内", "dictLabel": "热内", "icon": null, "sort": 1 },
    { "dictValue": "新品", "dictLabel": "新品", "icon": null, "sort": 2 },
    { "dictValue": "限定", "dictLabel": "限定", "icon": null, "sort": 3 },
    { "dictValue": "硬核", "dictLabel": "硬核", "icon": null, "sort": 4 },
    { "dictValue": "治愈", "dictLabel": "治愈", "icon": null, "sort": 5 }
  ]
}
```

- 注意 `dictValue` 和 `dictLabel` 都是中文（和老字典保持一致，不是英文 code）
- `icon` 字段是昨天新加的（见 [2026-04-20_dict-icon-field.md](./2026-04-20_dict-icon-field.md)），暂时都是 null，产品侧后续可配图标

---

## 数据迁移

**不迁移**。

- 老产品 tags JSON 里存的老字典值字符串**保留**，编辑时按上面"老数据兼容"逻辑展示
- 用户在下次编辑该产品时，按单选从新 5 项里选一个，保存后老值就被覆盖了
- 小程序端列表/详情展示老 tag 时原样显示（即使字典里没这个值）

---

## 验证

- ✅ 测试环境 `/admin/dict/data/product_tag` 返回 5 项（热内/新品/限定/硬核/治愈）
- ✅ `/dict/all` 公开接口 `product_tag` 也是 5 项新值
- ✅ DB `sys_dict_data WHERE dict_type='product_tag'` 5 行无残留老值
- ⏳ 生产环境字典同步：由 wx 手动在正式库执行相同的删 5 加 5（后端不出包，不走部署）
