在前端开发中,地图功能往往是复杂业务场景的核心需求。无论是外卖配送、打卡签到还是物流追踪,都需要精准的定位和便捷的地点搜索能力。
 本文将基于 Vue 3 和 高德地图 JS API 2.0,详细解析如何实现一个集点击地图定位、关键词搜索联想、经纬度精确定位于一体的地图组件。


1. 项目准备与环境配置


1.1 安装依赖


我们需要使用官方推荐的加载器 @amap/amap-jsapi-loader 来异步加载高德地图 SDK,避免全局污染并更好地管理生命周期。

npm install @amap/amap-jsapi-loader

1.2 获取 Key 与安全密钥


在高德开放平台创建应用,获取 Key 和 安全密钥 (Security Code)。 注意:自高德地图 JS API 2.0 起,必须配合安全密钥使用,否则无法加载地图。

2. 核心架构设计

我们的组件主要包含以下模块:

  1. 地图容器:用于渲染地图实例。
  2. 工具栏:输入经纬度、设置半径、清除标记。
  3. 搜索栏:支持关键词输入,下拉展示搜索建议(POI)。
  4. 信息展示区:显示当前选中位置的详细地址。

3. 关键功能实现详解

3.1 初始化地图与安全配置

在 onMounted 中,我们首先处理 Mock 数据对地图请求的影响(开发环境中常见坑点),然后配置安全密钥并加载地图。

// 解决 Mock.js拦截高德地图请求导致白屏的问题
window.XMLHttpRequest = window._XMLHttpRequest || window.XMLHttpRequest;
window.fetch = window._fetch || window.fetch;

// 配置安全密钥
window._AMapSecurityConfig = {
  securityJsCode: AMAP_SECURITY_CODE,
};

// 加载地图
AMapLoader.load({
  key: AMAP_KEY,
  version: "2.0",
  plugins: [
    "AMap.Scale",       // 比例尺
    "AMap.ToolBar",     // 工具条
    "AMap.Geocoder",    // 地理编码/逆地理编码 a
    "AMap.PlaceSearch", // 地点搜索服务
  ],
})
.then((AMap) => {
  // 初始化地图实例
  const map = new AMap.Map("container", {
    viewMode: "3D",
    zoom: 11,
    center: [116.397428, 39.90923], // 默认北京
    resizeEnable: true,
  });
  
  // 添加控件
  map.addControl(new AMap.Scale());
  map.addControl(new AMap.ToolBar());
  
  mapInstance.value = map;
});

3.2 点击地图与逆地理编码

用户点击地图任意位置时,我们需要做三件事:

  1. 获取经纬度。
  2. 添加标记点(Marker)和范围圈(Circle)。
  3. 通过逆地理编码将坐标转换为人类可读的地址字符串。
// 地图点击事件
map.on("click", (e) => {
  const point = e.lnglat;
  const lng = point.lng;
  const lat = point.lat;

  // 更新双向绑定的数据
  longitude.value = lng;
  latitude.value = lat;
  
  // 添加标记和圆圈
  addMarker([lng, lat]);
  
  // 逆地理编码获取地址详情
  reverseGeocode({ lng, lat });
});

// 逆地理编码函数
const reverseGeocode = (point) => {
  const geocoder = new AMap.Geocoder();
  geocoder.getAddress([point.lng, point.lat], (status, result) => {
    if (status === "complete" && result.info === "OK") {
      // 获取格式化地址
      currentLocation.value = result.geocodes[0].formattedAddress;
    } else {
      currentLocation.value = `坐标: ${point.lng}, ${point.lat}`;
    }
  });
};

3.3 智能搜索与联想(PlaceSearch)

这是提升用户体验的关键。我们使用 AMap.PlaceSearch 插件来实现“边输边搜”的效果。

实现逻辑:

  1. 监听输入框的 input 事件。
  2. 当输入字符大于 2 时,调用 placeSearch.search()
  3. 将返回的 POI(兴趣点)数据格式化,渲染到下拉列表中。
  4. 用户点击某一项时,地图自动定位到该点。
const handleSearchInput = (value) => {
  if (!mapInstance.value || !value || value.length < 2) {
    searchTips.value = [];
    return;
  }

  const placeSearch = new AMap.PlaceSearch({
    city: "全球", 
    citylimit: false, // 允许全国搜索
    pageSize: 10,
    extensions: "all", // 返回详细信息
  });

  placeSearch.search(value, (status: string, result: any) => {
    if (status === "complete" && result.poiList) {
      searchTips.value = result.poiList.pois.map((poi) => ({
        name: poi.name,
        district: `${poi.pname}${poi.cityname}${poi.adname}`,
        address: poi.address,
        location: poi.location, // AMap.LngLat 对象
      }));
    } else {
      searchTips.value = [];
    }
  });
};

// 选择搜索结果
const selectSearchResult = (item) => {
  searchAddress.value = item.name;
  searchTips.value = []; // 隐藏列表
  
  const lng = item.location.lng;
  const lat = item.location.lat;
  
  // 地图视角切换
  mapInstance.value.setCenter([lng, lat]);
  mapInstance.value.setZoom(15);
  
  // 更新状态
  longitude.value = lng;
  latitude.value = lat;
  addMarker([lng, lat]);
  currentLocation.value = item.district + item.address;
};

3.4 经纬度手动定位与范围绘制

除了鼠标操作,业务场景中常需要直接输入坐标进行定位。同时,为了模拟“电子围栏”或“打卡范围”,我们需要绘制一个圆形覆盖物。

const locateByCoordinate = () => {
  const lng = parseFloat(longitude.value);
  const lat = parseFloat(latitude.value);

  if (isNaN(lng) || isNaN(lat)) {
    ElMessage.error("请输入有效的经纬度");
    return;
  }

  const center = [lng, lat];
  mapInstance.value.setCenter(center);
  mapInstance.value.setZoom(15);

  addMarker(center);
  // 绘制圆形范围
  createCircle(center, radius.value);
  reverseGeocode({ lng, lat });
};

// 创建圆形覆盖物
const createCircle = (center, radiusVal) => {
  clearCircleOnly(); // 先清除旧圆圈
  
  const circle = new AMap.Circle({
    center: center,
    radius: parseFloat(radiusVal), // 单位:米
    strokeColor: "#0091ff",
    strokeWeight: 2,
    fillColor: "rgba(0, 145, 255, 0.1)",
    fillOpacity: 0.5,
  });

  mapInstance.value.add(circle);
};

4. UI 交互细节优化

4.1 下拉列表样式

为了让搜索体验更像原生 App,我们使用绝对定位将下拉列表悬浮在输入框下方,并添加了阴影和滚动条限制。

.search-tips-list {
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  max-height: 300px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid #dcdfe6;
  z-index: 1000;
  
  li {
    padding: 10px 15px;
    cursor: pointer;
    &:hover {
      background-color: #f5f7fa;
      color: #409eff;
    }
  }
}

4.2 清理机制

在添加新标记或圆圈前,务必清除旧的覆盖物,防止地图元素堆积导致性能下降或视觉混乱。

const clearAll = () => {
  clearCircleOnly();
  if (currentMarker.value) {
    mapInstance.value.remove(currentMarker.value);
    currentMarker.value = null;
  }
  // 重置其他状态...
};

5. 常见问题与避坑指南

  1. Mock.js 冲突

    • 现象:引入 Mock.js 后,地图瓦片加载失败,页面白屏。
    • 解决:如代码所示,在加载地图前备份并恢复原生的 XMLHttpRequest 和 fetch
  2. 地图容器尺寸问题

    • 现象:地图只显示左上角一小块。
    • 解决:确保父容器有明确的高度。如果在 Tab 切换或动态显示中初始化地图,需在 DOM 更新后调用 map.resize()
  3. 安全密钥配置

    • 现象:控制台报错 INVALID_USER_SCODE
    • 解决:确保 window._AMapSecurityConfig 在 AMapLoader.load 之前执行,且密钥正确。

6. 总结

通过本文的实践,我们构建了一个功能完备的高德地图组件。它不仅实现了基础的地图展示,还结合了 PlaceSearch 和 Geocoder 提供了强大的搜索与反查能力。

核心亮点:

  • 响应式数据绑定:Vue 3 ref 与地图状态实时同步。
  • 用户体验优化:搜索联想、自动缩放、清晰的状态反馈。
  • 健壮性:处理了 Mock 冲突、无效坐标校验及覆盖物清理。

你可以在此基础上继续扩展,例如添加路径规划、多点标记聚合等功能,以满足更复杂的业务需求。

更多推荐