diff --git a/changelogs/2026-04/30_feat_product_itinerary-place-poi-precision.md b/changelogs/2026-04/30_feat_product_itinerary-place-poi-precision.md index 763f333..e31c9fc 100644 --- a/changelogs/2026-04/30_feat_product_itinerary-place-poi-precision.md +++ b/changelogs/2026-04/30_feat_product_itinerary-place-poi-precision.md @@ -1,11 +1,13 @@ # 行程编排集合地/解散地:行政区 → 高德 POI 精准定位 -**类型**: 后端 FEAT(字段扩展+读路径增强) + 前端 picker 切换 -**关联**: 工单 #1563 / PR #1570 +**类型**: 后端 FEAT(字段扩展+读路径增强) + 前端 picker 切换 + regeo 调用 +**关联**: 工单 #1563 / PR #1570 + follow-up 工单 #1574 / PR #1575(镇级行政区字段) **日期**: 2026-04-30 **前端处理者**: mmg **影响范围**: 管理后台产品编辑页 Step 2「行程编排」→ DAY X 的「开始 / 结束」字段(即 `gatherPlace` / `dismissalPlace`) +> ⚠️ **2026-04-30 16:25 update(PR #1575)**:行政区精度要求**到镇/街道级**。POI 自身只到区县,前端选 POI 后**必须额外调高德 regeo (逆地理编码)** 拿镇级数据。详见下方 schema 和 mmg 前端 mapping。 + --- ## 业务目的 @@ -18,20 +20,26 @@ ## JSON Schema 升级 -### 新格式(POI 精准定位,2026-04-30 起新数据按这个落库) +### 新格式(POI 精准定位 + 镇级行政区,2026-04-30 起新数据按这个落库) ```jsonc { - "name": "海拉尔东山国际机场", // POI 名称 - "address": "内蒙古呼伦贝尔市海拉尔区机场路", // 详细地址 - "location": { "lng": 119.825, "lat": 49.205 },// 嵌套对象,POI 精确坐标 - "poiId": "B0FFGTEST1", // 高德 POI ID - "adcode": "150702", // POI 解析所属行政区编码 - "cityName": "呼伦贝尔市", // POI 解析所属城市 - "districtName": "海拉尔区" // POI 解析所属行政区 + "name": "海拉尔东山国际机场", // POI 名称 + "address": "内蒙古呼伦贝尔市海拉尔区机场路", // 详细地址 + "location": { "lng": 119.825, "lat": 49.205 }, // POI 精确坐标 + "poiId": "B0FFGTEST1", // 高德 POI ID + "adcode": "150702", // 区县编码(POI 自带) + "provinceName": "内蒙古自治区", // 省(regeo addressComponent.province) + "cityName": "呼伦贝尔市", // 市(regeo addressComponent.city) + "districtName": "海拉尔区", // 区(regeo addressComponent.district) + "township": "东山街道", // 镇/街道(regeo addressComponent.township) + "towncode": "150702002000", // 镇编码(regeo addressComponent.towncode) + "fullPath": "内蒙古自治区/呼伦贝尔市/海拉尔区/东山街道" // 4 段(前端拼) } ``` +**关键点**:POI 本身(高德 PlaceSearch 返回)只到区县级 `adname`,**到镇级必须前端额外调高德 regeo (`AMap.Geocoder.getAddress`)** 拿 `addressComponent.{township, towncode}` 后 merge 到 gatherPlace JSON。 + ### 老格式(行政区,2026-04-30 之前已落库的存量数据,零迁移继续工作) ```jsonc @@ -62,20 +70,39 @@ ### 2. 落库 JSON 字段映射(高德 POI → 后端字段) ```js -// 高德 PlaceSearch 单个 POI 结果 → 后端 gatherPlace/dismissalPlace -function poiToPlace(poi) { - return { - name: poi.name, // POI 名称 - address: poi.address || '', // 详细地址 - location: { - lng: Number(poi.location.lng), // 高德 LngLat 对象 - lat: Number(poi.location.lat) - }, - poiId: poi.id || '', - adcode: poi.adcode || '', // POI 自带 adcode - cityName: poi.cityname || '', // 高德 cityname 全小写 - districtName: poi.adname || '' // 高德 adname - }; +// 选 POI 后,先用 location 调 regeo 拿镇级行政区,再 merge +function poiToPlace(poi, callback) { + const geocoder = new AMap.Geocoder(); + geocoder.getAddress([poi.location.lng, poi.location.lat], (status, result) => { + if (status !== 'complete') { + // 降级:regeo 失败时退回到 POI 自带数据(无 township/towncode) + callback({ + name: poi.name, + address: poi.address || '', + location: { lng: Number(poi.location.lng), lat: Number(poi.location.lat) }, + poiId: poi.id || '', + adcode: poi.adcode || '', + cityName: poi.cityname || '', + districtName: poi.adname || '' + // 此时 provinceName/township/towncode/fullPath 留空,后端兼容 + }); + return; + } + const ac = result.regeocode.addressComponent; + callback({ + name: poi.name, + address: poi.address || result.regeocode.formattedAddress, + location: { lng: Number(poi.location.lng), lat: Number(poi.location.lat) }, + poiId: poi.id || '', + adcode: poi.adcode || ac.adcode, + provinceName: ac.province || '', + cityName: (ac.city && ac.city.length) ? ac.city : '', // 直辖市 city 是空数组 + districtName: ac.district || '', + township: ac.township || '', + towncode: ac.towncode || '', + fullPath: [ac.province, ac.city, ac.district, ac.township].filter(s => s && s.length).join('/') + }); + }); } ```