hl-api-changelog/changelogs/2026-05/11_followup_admin_poi-search-city-source-clarification.md

5.9 KiB

admin/product-v2: POI 搜索按城市限定 — 城市字段已在响应里, 在 itinerary[0].gatherPlace

服务: hl-product-service-v2 (端口 8083) 关联: 11_feat_admin_itinerary-mileage-anomaly-warnings.md 的第四节"前端建议改造 B" 日期: 2026-05-11 影响范围: 管理端产品编辑(step2) POI 搜索控件 — 仅前端改造, 后端 0 改动


⚠️ 关键变化(澄清前端误判)

前端反馈"产品详情没返回城市, 没法按 cityCode 限定 POI 搜索"。这个判断不准确:

  • 顶层 ProductDetailRespVO 确实没有 city/cityCode 字段
  • itinerary[i].gatherPlace / itinerary[i].dismissalPlace 这两个 JSON 字段 早就含完整城市/行政区信息(后端从高德 POI 选点时就一并存了 cityName / adcode / districtName / provinceName / fullPath / township)

后端不需要新加字段, 前端直接读 itinerary[0].gatherPlace.cityName / adcode 即可。


一、字段位置实证

正式服 GET /admin/product/item/{id} 实测响应(产品 2053003990289129474、2052985893312008193、2052926308213891073 三例一致):

{
  "data": {
    "itinerary": [
      {
        "dayNumber": 1,
        "gatherPlace": {
          "name": "云上呼伦贝尔",
          "poiId": "B0FFL...",
          "adcode": "150702",
          "address": "呼伦贝尔东山国际机场",
          "cityName": "呼伦贝尔市",
          "fullPath": "内蒙古自治区/呼伦贝尔市/海拉尔区/胜利街道",
          "location": { "lng": 119.809336, "lat": 49.5568 },
          "towncode": "150702010000",
          "township": "胜利街道",
          "districtName": "海拉尔区",
          "provinceName": "内蒙古自治区"
        },
        "dismissalPlace": { /* 同样结构 */ }
      }
    ]
  }
}

dismissalPlace 字段结构同 gatherPlace


二、前端实现建议

1. 城市数据源(优先级)

function getProductCity(detail) {
  const firstDay = detail.itinerary?.[0];
  const gather = firstDay?.gatherPlace;
  // 高德 POI 搜索 region 参数支持 adcode (推荐) 或 cityname
  return {
    cityName: gather?.cityName,
    adcode: gather?.adcode,
    fullPath: gather?.fullPath,
  };
}

2. 高德 POI 搜索调用

// 高德 web 服务 v3 关键字搜索: region + citylimit=true
const params = {
  key: AMAP_KEY,
  keywords: userInput,
  region: productCity.adcode,      // adcode 比 cityname 更精确
  city_limit: true,                 // 强制只在 region 内搜
  output: 'json',
};

3. POI 搜索结果显示完整地址(根因防御)

每个 POI 结果项除了 name 之外, 把 district + township 也显示出来:

[结果项]
  恩和                                       ← name
  呼伦贝尔市 / 额尔古纳市 / 恩和俄罗斯族民族乡  ← district + township

让运营肉眼区分同名异地 POI(例如"恩和"在宝日希勒镇 vs 恩和俄罗斯族民族乡)。


三、边界场景处理

Case A: DAY 1 集合地还没填(新建产品首次进 step2)

  • 此时 itinerary[0].gatherPlacenull
  • 第一个 POI 选择无 city 可限定 → 降级为全国搜索, 选完后 setState 本地缓存 cityName/adcode
  • 后续所有 POI 选择(DAY 1 节点 / DAY 1 解散 / DAY 2+...)都拿本地缓存做 region 限定
const [productCity, setProductCity] = useState(null);
// 编辑已有产品时初始化:
useEffect(() => {
  if (detail.itinerary?.[0]?.gatherPlace?.cityName) {
    setProductCity(getProductCity(detail));
  }
}, [detail]);
// 用户选完 DAY 1 集合地后:
function onGatherPlacePicked(poi) {
  if (!productCity) {
    setProductCity({ cityName: poi.cityName, adcode: poi.adcode });
  }
  // ... 落库
}

Case B: 跨城市产品(如内蒙呼伦贝尔 + 蒙古国边境)

  • 取 DAY 1 gather 即可, 这是产品的"主城市"
  • 跨城市产品本身在编辑时, 跨城段的 POI 会被 detector 的 单段 > 100km warning 命中, 让运营人工确认, 不依赖 region 限定

Case C: gatherPlace 是老行政区格式(无 cityName, 只有 adcode + fullPath)

  • 测试服新 schema 是行政区粒度, 字段更少
  • 兜底: cityName = fullPath?.split('/')?.[1](第 1 段省, 第 2 段市)
  • 或: 拿 adcode 前 4 位 → 高德反查城市
function extractCity(place) {
  if (!place) return null;
  if (place.cityName && place.adcode) return { cityName: place.cityName, adcode: place.adcode };
  if (place.fullPath) {
    const parts = place.fullPath.split('/');
    return { cityName: parts[1], adcode: place.adcode };
  }
  return null;
}

四、后端不需要做的事

明确不做(避免误改):

  • 不在 ProductDetailRespVO 顶层加 cityName/adcode 冗余字段(数据已在 itinerary 里, 加冗余字段反而双源更易错乱)
  • 不加 product.city 数据库字段(产品级城市当前业务无明确归属, "产品起点 = itinerary[0].gather" 这个隐式约定足够用)
  • 不抽 routeInfo.city 字段(routeInfo VO 当前不含 city, 改它需要改 Entity + VO + 保存逻辑, 不值)

五、Test plan(给 mmg)

  • 编辑产品 2053003990289129474, 在 DAY 4 集合地 POI 搜索输入"恩和", 验证只返回额尔古纳市恩和(50.83°), 不返回宝日希勒错点(49.28°)
  • 新建产品, DAY 1 集合地选呼伦贝尔机场后, DAY 2/3/4 节点 POI 搜索自动限定到呼伦贝尔市
  • POI 结果项显示 district + township, 让运营肉眼区分同名 POI
  • 跨城市产品(如海拉尔出发去阿尔山)能正常编辑, 跨城段触发 detector warning 但不阻塞保存

六、参考

  • 上一份 changelog: 11_feat_admin_itinerary-mileage-anomaly-warnings.md 第四节有 POI 搜索改造的高层建议
  • 实证脚本(只读 dump 字段): D:\work2\HL\.tmp\prod_smoke_pr1939_mileage_warnings.py (修改版可复用)
  • 高德 web 服务文档: https://lbs.amap.com/api/webservice/guide/api/search (POI 搜索, region + citylimit 参数)