Vue3+element plus+高德地图,不使用vue-amap依赖,标记点+信息浮窗+地理编码+逆地理编码+搜索+回显+获取详细地址去掉省市区

1,128 阅读1分钟

没想到小小一个地图组件,我翻来覆去重构了好几回,总算实现了所需要的基础功能:可拖拽的标记点+信息浮窗+地理编码+逆地理编码+搜索+只读回显/编辑+获取详细地址去掉省市区。

最开始找了很多文章参考,现在成型的组件也是参考了大佬们的结构,但是一直报错,后来发现是我没搞清AMap(组件内命名为AMapObj) 跟 new AMap.Map(组件内命名为amap) 导致的。

源码:

<script setup name="GaoDeMapDemo">
import { ref, shallowRef, onMounted, onUnmounted, watch, computed, toRefs } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
import { ElMessage } from 'element-plus'
import { debounce } from 'lodash'

// 2021年起 ,key 必须跟 密钥搭配使用
window._AMapSecurityConfig = {
  securityJsCode: '密钥'
}

const props = defineProps({
  modelValue: { // 坐标点数据,包括坐标/地址/省市区等
    type: Object,
    default: () => ({})
  },
  isRead: { // 只读时禁止搜索,禁止修改坐标
    type: Boolean,
    default: false
  }
})
const { modelValue, isRead } = toRefs((props))
const emit = defineEmits(['update:modelValue', 'on-select'])
const map = shallowRef(null)
// 地点
const location = computed({
  get: () => modelValue.value,
  set: (val) => {
    emit('update:modelValue', val)
  }
})
watch(location, (val) => {
  if (val.longitude && val.latitude && AMapObj) {
    drawMarker()
  }
})
// 搜索地点
const keyword = ref('')

// 搜索,AMap,标记点,地理编码,new AMap.Map()实例
let placeSearch, AMapObj, marker, geocoder, amap

const initMap = () => {
  AMapLoader.load({
    key: 'key,需要先申请', // 申请好的Web端Key,首次调用 load 时必填
    version: '2.0',
    plugins: [
      'AMap.Scale', //工具条,控制地图的缩放、平移等
      'AMap.ToolBar', //比例尺,显示当前地图中心的比例尺
      'AMap.PlaceSearch', // POI搜索插件
      'AMap.Geocoder' //定位
    ] // 需要使用的的插件列表,如比例尺'AMap.Scale'等
  }).then(AMap => {
    // 方便其他方法引用 AMap
    AMapObj = AMap
    let map = new AMap.Map('container', {
      zoom: 16, //初始化地图层级
      zooms: [6, 20], //缩放范围
      resizeEnable: true, //如果没有传入center参数,将自动定位到当前城市
      viewMode: '2D', //是否为3D地图模式
      center: [113.17, 23.8] //初始化地图中心点位置
    })
    // 方便其他方法引用 map实例
    amap = map
    //异步同时加载多个插件
    map.addControl(new AMap.Scale())
    map.addControl(new AMap.ToolBar())

    // 搜索地点
    placeSearch = new AMap.PlaceSearch({
      map: map.value,
      city: '',
      pageSize: 30, // 但也显示结果条数
      pageIndex: 1,// 页码
      citylimit: false,  // 是否强制限制在设置的城市内搜索
      autoFitView: true
    })
    // 存在坐标时,给地图添加标记
    if (location?.value?.longitude && location?.value?.latitude) {
      drawMarker()
    }

    // 地理编码,用于点击事件
    geocoder = new AMap.Geocoder({ city: '全国' })
    // 添加点击事件,只读时不可点击
    if (!isRead.value) {
      map.on('click', onMapClick)
    }
  }).catch(e => {
    ElMessage.error({ message: e })
  })
}

// 远程搜索搜索地图
const handleSearch = (queryString, cb) => {
  let _result=queryString
  if(location?.value?.zone?.length){
    // 补全城市,减少输入
    _result = `${location.value.zone[1]}${ queryString }`
  }
  placeSearch.search(_result, (status, result) => {
    if (result && typeof result === 'object' && result.poiList) {
      const list = result.poiList.pois
      list.forEach(item => {
        item.value = item.name
        item.label = item.name
      })
      cb(list)
    } else {
      cb([])
    }
  })
}

// 点击地图 获取坐标信息
const onMapClick = (item) => {
  const { lng, lat } = item.lnglat
  // 逆地理编码
  geocoder.getAddress([lng, lat], (status, result) => {
    if (status === 'complete' && result.info === 'OK') {
      const { addressComponent, formattedAddress } = result.regeocode
      // township 镇/街道办 street街  streetNumber门牌号
      let { city, province, district, township, street, streetNumber } = addressComponent
      // 去掉省市区,同时插入街道+门牌号,作为详细地址
      let _address = township + street + streetNumber + formattedAddress.replace(province, '').replace(city, '').replace(district, '').replace(township, '')
      if (!city) {
        // 直辖市
        city = province
      }
      location.value = {
        longitude: lng,
        latitude: lat,
        address: _address,
        zone: [province, city, district]
      }
    }
  })
}

// 点击搜索项
const handleSelect = (item) => {
  const _pos = {
    lnglat: {
      lng: item.location.lng,
      lat: item.location.lat
    }
  }
  onMapClick(_pos)
  amap.setZoomAndCenter(16, [_pos.lnglat.lng, _pos.lnglat.lat])
}

// 绘制地点marker
const drawMarker = () => {
  let { longitude, latitude } = location.value
  if (marker) {
    marker.setMap(null)
  }
  marker = new AMapObj.Marker({
    map: amap,
    position: new AMapObj.LngLat(longitude, latitude),
    anchor: 'bottom-center',
    draggable: isRead.value ? false : true
  })

  marker.on('dragging', onDragging)
  amap.add(marker)
  setInfoWindow()
  amap.setZoomAndCenter(16, [longitude, latitude])
}

// 信息浮窗 参数用于onDragging时减少偏移
let infoPopper
const setInfoWindow = (lng = null, lat = null, offTop = -44) => {
  let longitude,
      latitude,
      address = location.value.address
  if (lng && lat) {
    longitude = lng
    latitude = lat
  } else {
    longitude = location.value.longitude
    latitude = location.value.latitude
  }
  mapData.keyword = address
  if (infoPopper) {
    infoPopper.setMap(null)
  }

  infoPopper = new AMapObj.InfoWindow({
    position: new AMapObj.LngLat(longitude, latitude),
    anchor: 'bottom-center',
    isCustom: true,
    content: `<div class="marker-info-popper">
                <p>${ address }</p>
            </div>`,
    offset: new AMapObj.Pixel(0, offTop) // 位置偏移
  })
  amap.add(infoPopper)
}

// 拖动标记点时获取偏移后的坐标数据
const onDragging = debounce(item => {
  const { lng, lat } = item.lnglat
  location.value.longitude = lng
  location.value.latitude = lat
  setInfoWindow(lng, lat, -24)
}, 350)

// 调用地图初始化函数
onMounted(() => {
  initMap()
  //初始化时,获取关键字
  if (modelValue?.value?.address) {
    keyword.value = modelValue.value?.address
  }
})

onUnmounted(() => {
  if (amap) {
    amap.destroy()
  }
})
</script>
<template>
  <div class="gao-de-map-search">
    <div class="mr-1 gao-de-map-search__label">详细地址</div>
    <ElAutocomplete
        class="flex-1"
        v-model="keyword"
        :fetch-suggestions="handleSearch"
        style="width:100%"
        :trigger-on-focus="false"
        @select="handleSelect"
        clearable
        :disabled="isRead"
        placeholder="输入城市+关键字搜索"
    >
      <template #prepend>
          <span v-if="location.zone.length">
            {{ location.zone[0] }}/{{ location.zone[1] }}/{{ location.zone[2] }}
          </span>
        <span v-else>省/市/区</span>
      </template>
    </ElAutocomplete>
  </div>
  <div class="gao-de-map" id="container" ref="map"></div>
</template>

<style lang="scss">
.gao-de-map {
  margin: 10px auto;
  width: 100%;
  height: 400px;
}

.gao-de-map-search {
  display: flex;

  .gao-de-map-search__label {
    width: 120px;
    line-height: 30px;
    text-align: right;
    color: var(--el-text-color-regular);
  }
}
</style>