# 字典数据项新增 icon 图标字段

**日期**：2026-04-20
**PR**：#994（Closes #993）
**影响**：管理端字典管理页、所有使用字典的展示组件

---

## 为什么变

`sys_dict_data` 原先只有 `dict_label` / `dict_value` / `sort_order` / `remark`，部分业务场景（如活动分类、房型、状态等级）希望能给字典项配图标用于前端可视化（活动卡片角标、状态彩色 Dot、Tab 图标等）。本次加一个 `icon` 字段（TEXT，允许为空），字典 CRUD 和所有读取接口全部透传。

## 接口契约变化

### 新字段

所有字典数据项（`DictDataVO` 及 Entity 序列化）新增：

```ts
{
  "icon": "https://cdn.example.com/icons/camp.png" | null
}
```

**类型**：`string | null`（可为 URL、emoji、base64 data URL，旧数据默认 `null`）

### 受影响接口

| Method | Path | 变化 |
|---|---|---|
| POST | `/admin/dict/data` | **请求体**新增可选 `icon`；**响应**新增 `icon` |
| PUT | `/admin/dict/data/{id}` | **请求体**新增可选 `icon`（不传则不覆盖）；**响应**新增 `icon` |
| GET | `/admin/dict/data/{id}` | 响应新增 `icon` |
| GET | `/admin/dict/data/{dictType}` | 响应每项新增 `icon` |
| GET | `/admin/dict/all` | 响应每项新增 `icon` |
| GET | `/dict/all`（公开，无需 token） | 响应每项 `dataList[].icon` |

> `/internal/mp/**` 是服务间 Feign 内部接口，前端不直接调用，此处略。

### 响应样例

```jsonc
// GET /admin/dict/all
{
  "code": 200,
  "data": [
    {
      "dictType": "activity_billing_type",
      "dictName": "计费方式",
      "dataList": [
        {
          "dictValue": "PER_PERSON",
          "dictLabel": "按人",
          "icon": "https://cdn.example.com/icons/per-person.png",  // ⭐ 新增
          "sort": 1,
          "remark": null
        },
        {
          "dictValue": "PER_GROUP",
          "dictLabel": "按组/团",
          "icon": null,                                             // 旧数据默认 null
          "sort": 2,
          "remark": null
        }
      ]
    }
  ]
}
```

```jsonc
// POST /admin/dict/data 请求体
{
  "dictType": "activity_category",
  "dictLabel": "骑马",
  "dictValue": "HORSEBACK",
  "icon": "https://cdn.example.com/icons/horse.png",   // ⭐ 可选
  "sortOrder": 10,
  "remark": "骑马体验项目"
}
```

```jsonc
// PUT /admin/dict/data/{id} 请求体（只传要改的字段；不传 icon 不覆盖）
{
  "icon": "emoji:🏕"
}
```

---

## 前端改造建议

### 管理端 —— 字典管理页

- **字典项创建/编辑弹窗**：在 `dictLabel` 旁新增「图标」输入框
  - 建议 placeholder："图标 URL / emoji / base64 data URL，可为空"
  - 不做正则校验（后端 TEXT，无上限），由用户自己把控
  - 提交时 `icon` 传空字符串即落库空串；不想改就别带这个字段（PUT 语义是"字段缺失 = 不改"）
- **字典数据列表**：在 dictLabel 右侧展示一个小图标预览（URL 就 `<img>`，emoji 就文本，base64 就 `<img>`），null 留空

### 小程序端 / 管理端业务页

- 调 `/dict/all` 或 `/admin/dict/all` 时，每个 `dataList[].icon` 都能拿到
- 展示组件里按 `icon != null` 条件渲染，比如：
  - 分类 Tab：`icon ? <img src=icon /> : null` + `label`
  - 状态 Badge：`icon` 可以是 emoji 或彩色圆点 URL
- icon 内容不固定，前端按它的形态（http(s) URL / emoji 文本 / `data:image/...` base64）自适应渲染即可

---

## 不影响的接口

- `/admin/dict/type` 相关（字典类型 CRUD / 分页）
- 现有响应结构中已有的字段全部保持，只是**每个字典数据项多一个 `icon` 字段**
- 旧数据 `icon = null`，前端没做处理也不会报错，但展示上就没图标

## 缓存

- 后端字典缓存（Redis，5min TTL）会在字典数据 CRUD 后通过 `CacheEvictAfterCommit` 自动清，icon 更新后下一次读就能拿到新值（最坏延迟 = 事务提交 + 单次请求）
- 前端 `/dict/all` 通常也有本地缓存，建议管理端配置 icon 后引导用户 F5 或重新拉一次字典
