hl-api-changelog/changelogs/2026-05/14_backend_admin_amap-poi-proxy-ready-replace-frontend-direct.md

7.2 KiB

date, type, module, priority, backend_pr, backend_issue, notify, status, related
date type module priority backend_pr backend_issue notify status related
2026-05-14 backend-feature admin-amap-poi-proxy low
#2253
#2259
#2261
#2264
#2249
@mmg
backend-ready-but-frontend-chose-self-fix
12_frontend_bug_admin_mappicker-citylimit-blocks-cross-city-search.md

⚠️ 2026-05-14 更新:mmg 决定前端继续直连高德, 不切到本接口, 自行修复 citylimit 锁城 BUG。后端接口已上线测试服并保留备用(代码不删, nacos key 不撤), 以备未来需要"统一入口/统一限流/key 收回"时再启用。本 changelog 不再要求 mmg 切接口, 只作技术档案。

后端代理高德 POI 搜索接口已就绪 — 前端可切换不再直连高德

TL;DR

后端新增 2 个 admin 接口代理高德 POI 搜索, citylimit 后端固定 false 不可改, 解决多次复发的「跨盟市/跨省搜不到 POI」 BUG (citylimit:true 锁城市)。

测试服已真测通过(阿尔山/故宫 跨盟市/跨省都返). 前端 PoiAutocomplete.vue / MapPicker.vue 两个组件可以删 AMap.AutoComplete / AMap.PlaceSearch 直连, 改调本接口。

接口契约

1. 输入提示 (下拉联想, 替代 AMap.AutoComplete)

GET /admin/amap/poi/inputtips
  ?keywords=阿尔山        必填, 用户输入关键字 (空白 returns 400 关键字不能为空)
  &city=呼伦贝尔          可选, 仅影响排序权重, 不约束搜索范围
  &type=                  可选, 高德 POI 类型代码 (typecode 多个逗号分隔)

Headers: Authorization: Bearer {admin_token}

返回(真实样本):

{
  "code": 200, "message": "成功",
  "data": [
    {
      "id": "B01E20023T",
      "name": "阿尔山站",
      "district": "内蒙古自治区兴安盟阿尔山市",
      "address": "兴林路附近",
      "lng": 119.947575, "lat": 47.172702,
      "adcode": "152202", "typecode": "150200"
    },
    { "name": "阿尔山伊尔施机场", "district": "内蒙古自治区兴安盟阿尔山市", "lng": 119.91948, "lat": 47.311868, "adcode": "152202", "typecode": "150104" },
    { "name": "阿尔山", "district": "内蒙古自治区呼和浩特市回民区", "address": "成吉思汗西街101号慧谷·蓝庭", "lng": ..., "lat": ... }
  ]
}

2. 关键字搜索 (回车精确搜, 替代 AMap.PlaceSearch)

GET /admin/amap/poi/search
  ?keywords=故宫          必填
  &city=                  可选
  &page=1                 可选, 默认 1, 范围 [1,100]
  &size=20                可选, 默认 20, 上限 25 (高德硬限制, >25 returns 400)

返回:

{
  "code": 200, "message": "成功",
  "data": {
    "total": 100,
    "page": 1, "size": 20,
    "list": [
      {
        "id": "B000A8UIN8",
        "name": "故宫博物院",
        "district": "北京市东城区",
        "address": "景山前街4号",
        "lng": 116.397029, "lat": 39.917839,
        "adcode": "...", "typecode": "..."
      },
      { "name": "故宫博物院-午门", "district": "北京市东城区", ... }
    ]
  }
}

注: district 字段两接口都是完整省市区路径 (含直辖市去重), 直接显示给用户即可。

前端改造点(逐文件)

hl-ui/src/components/PoiAutocomplete.vue (第 100-104 行 + 130 行)

// 删除
autoComplete = new window.AMap.AutoComplete({
  city: props.city || '呼伦贝尔',
  citylimit: true,
  datatype: 'all',
})
async function doAutoComplete(query) {
  ...
  autoComplete.search(query, (status, result) => {...})
}

// 替换 (新增 src/api/amap.js 包一层)
import { poiInputtips } from '@/api/amap'

async function doAutoComplete(query) {
  loading.value = true
  try {
    const list = await poiInputtips({ keywords: query, city: props.city || '' })
    options.value = list.slice(0, 15).map((tip) => ({
      label: tip.name,
      value: tip,           // 整个对象往上传, 含 lng/lat/district/adcode/typecode
      fullPath: tip.district || '',
    }))
  } finally {
    loading.value = false
  }
}

hl-ui/src/components/MapPicker.vue (第 360-431 行 + 444-448 行)

// 删除两段
new window.AMap.AutoComplete({...})
new window.AMap.PlaceSearch({...})

// 删除第 444-448 行 filteredPois 二次过滤 (后端已不锁城, 全国结果都该展示)

// 替换为
import { poiInputtips, poiSearch } from '@/api/amap'

// AutoComplete (输入框联想) → poiInputtips({keywords, city})
// PlaceSearch (回车精确搜) → poiSearch({keywords, city, page:1, size:20})

hl-ui/src/views/product/edit/components/itinerary/RouteTagBar.vue (第 26-28/79/105 行)

// 删除 productCity computed (不再传 city 锁范围)
const productCity = computed(() => store.productData.itinerary?.[0]?.gatherPlace?.cityName || '呼伦贝尔')

// PoiAutocomplete 上的 :city="productCity" 也删掉, 不传 city 即全国搜
<PoiAutocomplete :value="..." placeholder="..." size="small"
   - :city="productCity"
   :disabled="store.isReadonly" @update:value="..." />

地图渲染部分保留前端 JS SDK

MapPicker.vuenew AMap.Map() / Marker / 逆地理编码 (AMap.Geocoder) 仍前端 JS SDK 直连, 渲染必须前端。前端 JS API key (VITE_AMAP_KEY / sys_frontend_config.AMAP_KEY) 保留不动, 只是搜索这块走后端。

新建 hl-ui/src/api/amap.js

import request from '@/utils/request'

export function poiInputtips(params) {
  return request.get('/admin/amap/poi/inputtips', { params }).then(r => r.data)
}

export function poiSearch(params) {
  return request.get('/admin/amap/poi/search', { params }).then(r => r.data)
}

测试服真测结果(已 PASS, 前端可放心切)

验收 结果
inputtips?keywords=阿尔山 跨盟市 10 条, 9 条非呼伦贝尔, 含「阿尔山站/阿尔山伊尔施机场/阿尔山北站/阿尔山服务区」
search?keywords=故宫 跨省 total=100, 返「故宫博物院/午门/西南角楼/景仁宫/慈宁宫」全在「北京市东城区」
inputtips?keywords= (空) 业务码 400 「关键字不能为空」
search?size=30 (>25) 业务码 400 「每页大小不能超过 25(高德硬限制)」

测试服环境: https://api.test.1814.love:9443/admin/amap/poi/..., Bearer token 走管理后台登录拿到即可。

灰度建议

  1. 后端已上线 dev 测试服 (4 个 PR 已合并)
  2. 建议先切 PoiAutocomplete.vue (1 个组件 / 1 个页面 = 行程编排「开始/结束」), 验证「阿尔山市」「呼伦贝尔学院」等跨地搜索正常
  3. 再切 MapPicker.vue (5 个资源 EditModal: 景区/酒店/餐厅/活动/服务), 验证「阿尔山白狼峰景区」搜得到
  4. 浏览器 Network 看搜索请求 URL 应是 /admin/amap/poi/..., 不应再有 restapi.amap.com 直接调用

关闭老 BUG

本次后端实现彻底解决 12_frontend_bug_admin_mappicker-citylimit-blocks-cross-city-search.md 的根因 — 前端不再有 citylimit 这个开关, 业务上根本不会再被锁城。前端切完后老 changelog 可以 status: closed。

不在本 PR 范围

  • 地图渲染 (new AMap.Map() / Marker) 仍前端 JS SDK
  • 行政区划查询 / 驾车路线 / 天气 / 静态地图 / 地理编码 都已有后端实现, 不动

@mmg 接口可用了, 请按上面 4 个文件改造点替换前端代码。问题随时找我。