# fix: 早鸟计划接口 VO 加 productNames / products 字段（解决列表显示"全部产品"、编辑页显示 ID 问题）

> **服务**: hl-order-service-v2
> **PR**: #925
> **Issue**: #916
> **日期**: 2026-04-19
> **前端是否需要改动**: **无需改动**（前端代码本来就期望这两个字段，之前是后端未返回导致 fallback）

---

## 一、背景

管理端早鸟计划页面之前存在两个 bug：
- **列表页**"适用产品"列显示"全部产品"（误导，实际已绑产品）
- **编辑弹窗**"适用产品"tag 显示产品 ID（如 `2045345825172639746`）

根因：后端 `EarlyBirdPlanVO` 只返 `productIds: List<Long>` 不带产品名，前端拿不到名字做 fallback。

## 二、变更接口

所有返回 `EarlyBirdPlanVO` 的接口均受益：

| 方法 | 路径 | 变更类型 |
|------|------|---------|
| GET | `/admin/order/early-bird/{id}` | 响应新增字段 |
| POST | `/admin/order/early-bird` | 同上 |
| PUT | `/admin/order/early-bird/{id}` | 同上 |
| GET | `/admin/order/early-bird` (列表/分页) | 响应 records 新增字段 |

（其他如 `/admin/order/early-bird/{id}/validate` `/match` 等同样收益）

## 三、新增字段

```json
{
  "planId": "2045586012523864065",
  "planName": "默认早鸟优惠",
  "discountAmount": 100.00,
  "minPeople": 1,
  "productIds": ["2045345825172639746"],
  "productNames": ["游牧的森林-短途版"],            // ← 新增，列表列用
  "products": [                                    // ← 新增，编辑弹窗 tag 用
    {"productId": "2045345825172639746", "name": "游牧的森林-短途版"}
  ]
}
```

字段约定：
- `productNames` 与 `productIds` **下标对齐**
- 产品被删除时，对应位置填 `"产品[ID]已删除"`（而非 "全部产品" 误导）
- Feign 查失败时，全部填占位字符串
- 空的 productIds 保持空数组（前端按原逻辑显示 "全部产品"）

## 四、前端行动项

**无需改代码**。以下是预期的前端渲染逻辑（已跟代码对齐）：

```js
// 列表页
{ label: '适用产品', render: row => row.productNames?.length ? row.productNames.join('、') : '全部产品' }

// 编辑弹窗（优先用 products 结构化字段）
const tags = row.products?.length
  ? row.products
  : row.productIds?.map(id => ({ productId: id, name: String(id) }))  // fallback
```

修复后列表会直接显示 `"游牧的森林-短途版"`，编辑弹窗 tag 显示产品名称。

## 五、测试环境已验证

```bash
GET /admin/order/early-bird/2045586012523864065
→ {
    "code":200,
    "data":{
      "productIds":["2045345825172639746"],
      "productNames":["游牧的森林-短途版"],
      "products":[{"productId":"2045345825172639746","name":"游牧的森林-短途版"}]
    }
  }
```

## 六、相关 Issue
- Issue #912 早鸟列表 GET `?page=...` 返 "参数类型错误: orderId" 仍待修（与本 PR 无关）
- Issue #917 一产品一早鸟唯一约束 仍待修
