# 前端 BUG: CostDescPanel 不展示「游玩项目」类型

> 📌 **状态变更记录**:
> - 17:35 首次发现并通知 mmg
> - 17:38 用户当面说"前端没问题,之后能看到活动",一度撤回
> - **17:55 用户复测确认确实是前端 BUG,恢复通知**(用户原话:「为啥不显示活动 告知前端」)
> - 后端 + DB 均无问题,前端排查 CostDescPanel.vue 数据 filter / 字典映射

**类型**: 前端 BUG(后端零改动)
**前端处理者**: mmg
**日期**: 2026-04-30
**影响范围**: 管理后台「产品编辑 → 补充信息 → 费用说明」页面 — 自动生成的费用包含列表

## ⚠️ 前端 mmg 第二次反馈(2026-04-30 20:43)

前端 mmg 截图:
```
includedFees: [{name: "扎赉诺尔博物馆，巴尔虎蒙古部落，海拉尔国家森林公园", description: null}, ...]
0: {name: "扎赉诺尔博物馆，巴尔虎蒙古部落，海拉尔国家森林公园", description: null}
1: {name: "海拉尔首旅京伦酒店, 额尔古纳豪星酒店", description: null}
```

→ "返回的费用包含不对  自动的没有类型"

## 后端实测(管理者再次 round-trip 确认)

`GET /admin/product/item/2045018534152478721`(测试服) 实测响应:

```jsonc
includedFees: [
  {
    "id": "2049778058755268609",
    "productId": "2045018534152478721",
    "feeCategory": "INCLUDED",
    "feeType": "门票",            // ← 后端有这个字段!
    "source": "AUTO",
    "name": "黑山头",
    "description": null,
    ...
  }, ...  // 17 条单条记录,全部含 feeType
]
```

`PUT /admin/product/item/{id}/supplement` 写入 `[{feeType:"景区",name:"X1，X2",sortOrder:1}]` →
`GET /admin/product/item/{id}` 读出来 `includedFees=[{feeType:"景区",name:"X1，X2",...}]`

**字符串完全一致透传**,后端 round-trip 验证齐全。

## 前端排查焦点

1. 前端 mmg console 截图只看到 2 项(0/1),**第三项「游玩项目」消失** — 跟首次反馈一致
2. 每项 keys 只显示 `name + description`,但后端实际返 16 个字段(`id/productId/feeCategory/feeType/source/name/description/resourceType/resourceId/costItemId/unitPrice/quantity/daily/unit/icon/sortOrder`)→ 前端 console 打印应该是只挑了部分字段(可能 `console.log({name:..., description:...})`)还是真的丢了完整字段?

## 排查建议升级版

请前端在 `mounted()` 钩子加完整打印:

```js
async function loadIncludedCosts() {
  const res = await getFeeDeductionPreview(productId)
  console.log('[FULL res]', JSON.parse(JSON.stringify(res)))     // ← 看完整字段
  console.log('[res.length]', res?.length)                       // ← 看是不是真的少了 1 项
  console.log('[item keys]', res?.[0] && Object.keys(res[0]))    // ← 看每项实际字段
  // ...
}
```

如果 `JSON.parse(JSON.stringify(res))` 完整打印里:
- 看到 3 项含 `feeType="景区"/"游玩项目"/"住宿"` 字段 → 是渲染层 v-for 过滤了「游玩项目」(跟之前推测一致),修 v-for 模板 / filter 逻辑
- 看到 2 项缺「游玩项目」/字段不全 → axios 拦截器 / store / 数据预处理吞了字段(grep `filter.*feeType` / `pick.*name.*description` 之类)

## 后端 round-trip 实测命令(mmg 可复测)

```bash
TOKEN=...
curl -k "https://api.test.1814.love:9443/admin/product/item/2045018534152478721/fee-deduction-preview" \
  -H "Authorization: Bearer $TOKEN" | jq '.data | length, .data[].feeType'
# 期望:3 / "景区" / "游玩项目" / "住宿"
```

---

## 现象

产品编辑页 → 补充信息 → 费用说明 →「根据行程自动生成」按钮加载后,用户实测产品 `2045018534152478721`「游牧的森林·南线经典」:

- 期望显示 **3 行**:景区 / **游玩项目** / 住宿
- 实际只显示 **2 行**:景区 / 住宿
- **「游玩项目」那行没出现**

## 后端验证(测试服已部署 PR #1568 + #1577 + #1579 + #1581 + #1583)

```bash
curl https://api.test.1814.love:9443/admin/product/item/2045018534152478721/fee-deduction-preview \
  -H "Authorization: Bearer <admin_token>"
```

**实测响应(3 条齐全)**:

```jsonc
{
  "code": 200,
  "data": [
    {
      "feeType": "景区",
      "name": "黑山头，白桦林景区，莫日格勒河，莫日格勒河断崖，呼伦湖，阿尔山火车站",
      "source": "AUTO",
      "sourceNodeName": null,
      "icon": null
    },
    {
      "feeType": "游玩项目",                                           // ← 后端有这条!
      "name": "ATV全地形越野车穿越，驯鹿拉车体验",
      "source": "AUTO",
      "sourceNodeName": null,
      "icon": null
    },
    {
      "feeType": "住宿",
      "name": "海拉尔洲季酒店，呼伦贝尔香格里拉大酒店，额尔古纳玖成万豪水汇智能酒店，额尔古纳豪星酒店，额尔古纳白桦大酒店，海拉尔海棠酒店，满洲里饭店（百年俄式），敖根栖息地，阿尔善国际维景度假温泉酒店",
      "source": "AUTO",
      "sourceNodeName": null,
      "icon": null
    }
  ]
}
```

后端返 3 条,前端展示 2 条 → **前端过滤掉了「游玩项目」**。

## 推测原因

后端 `feeType` 字段值演进历史(2026-04-30 同日):
- 旧值:`门票 / 活动 / 住宿 / 餐饮 / 交通`(NodeTypeEnum.getFeeLabel())
- 新值:**`景区 / 游玩项目 / 住宿 / 餐饮 / 交通`**(PR #1581 加 displayLabel 映射)

可能性 A:前端有 feeType 白名单(只允许 `门票/活动/住宿/餐饮/交通`),收到新的 `景区`/`游玩项目` 不在白名单被 filter 掉 → 但用户截图能看到「景区」chip,说明白名单包含「景区」却不含「游玩项目」(可能是前端字典只补了一半)

可能性 B:前端有 feeType→中文显示名映射字典(比如 `{门票:'景区', 活动:'游玩项目'}`),后端改成直接返显示名后,字典里查不到 `游玩项目`(因为 key 是 `活动`),返 null/undefined 被 v-if 过滤

可能性 C:某个数据预处理 `data.filter(...)` 用了硬编码 feeType 列表

## 排查建议

请前端检查 `D:\work2\hl-ui\src\views\product\edit\components\supplement\CostDescPanel.vue`(或对应实际页面所在的 vue 组件),关键看:

1. 数据加载后是否有 `filter`/`map` 处理 feeType
2. `<template>` 是否有 `v-if="includes('门票'/'活动'/...)"` 类型白名单
3. feeType 显示是否走中文字典查表
4. `included-grid` CSS 网格是否被某个 `v-if` 控制只渲染部分项

## 临时验证方法

前端 console 打开:

```js
// 在 CostDescPanel mounted 后,看 includedCosts 实际值
console.log(includedCosts.value)
// 如果有 3 项,说明数据没问题,问题出在 v-for 或 filter
// 如果只有 2 项,说明 res 取 data 时被过滤,或 axios 拦截器吞了一项
```

## 后端不再改动

后端 `feeType` 字段值已稳定为 `景区 / 游玩项目 / 住宿 / 餐饮 / 交通`(PR #1581 + #1583 已合并部署),与前端 chip 显示文案对齐。前端只需识别「游玩项目」这个值即可。

如果前端字典有 `{门票: '景区', 活动: '游玩项目'}` 这种映射,**直接删掉**即可(后端已经返显示名了)。

## 部署状态

- 后端(测试服):✅ 已部署 hl-product-service-v2 双实例(2026-04-30 17:25)
- 前端(测试服):待 mmg 排查并发版
