高德地图自定义信息窗体以及各种点位操作

412 阅读1分钟

前言

最近接手了公司的大屏项目,地图使用了高德地图,将自己的遇到的问题做个简单记录。

  • 组件如何渲染到自定义信息窗体内(地图上多种点位,点击之后出现的弹窗内容差异很大,所以每种点位的弹窗都单独使用一个组件,如何信息窗体的content如何拿到组件的dom)
  • 点位的label内容更新(点位上方的label内容需要轮询接口更新,如何值更新label内容)

代码

弹窗组件代码

地图上点位弹窗要展示什么, 这个组件就写什么,后面可以完整显示在信息窗体中

<template>
  <div class="marker-popup-container">
    <span class="close-btn" @click="$emit('close')">x</span>
    <div class="popup-content">{{data.name}}-{{data.value}}</div>
  </div>
</template>

<script>
export default {
  props: {
    data: {
      type: Object,
      default () {
        return {}
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.marker-popup-container {
  position: relative;
  width: 500px;
  height: 300px;
  border-radius: 5px;
  background-color: rgba(0, 0, 0, 0.7);
  .close-btn {
    position: absolute;
    top: 0;
    right: 0;
    width: 15px;
    font-size: 15px;
    color: #ccc;
    line-height: 1;
    cursor: pointer;
  }
  .popup-content {
    text-align: center;
    font-size: 20px;
    color: #ccc;
  }
}
</style>

主要组件代码

<template>
  <div class="map-box">
    <div class="header">
      <el-button type="primary" @click="addCustomIcon">添加自定义图标</el-button>
      <el-button type="primary" @click="switchRefreshDataStatus">{{refreshStatua ? '关闭' : '开启'}}刷新数据</el-button>
      <el-button type="primary" @click="clearAllMarkers">清空点位</el-button>
    </div>
    <MapContainer ref="map" @complete="complete=true" />
  </div>
</template>

<script>
// import Vue from 'vue'
import Vue from 'vue/dist/vue.esm.js'
import MapContainer from '@/components/MapContainer'
import MarkerPopup from '@/components/MarkerPopup'
export default {
  components: {
    MapContainer
  },
  data () {
    return {
      complete: false, // 地图是否加载完成
      refreshStatua: false, // 是否开启了刷新数据
      dataList: [
        {
          id: '1',
          name: '杭州地铁1号线',
          value: '100',
          lng: 120.294282,
          lat: 30.172709
        },
        {
          id: '2',
          name: '杭州地铁2号线',
          value: '200',
          lng: 120.304282,
          lat: 30.182709
        },
        {
          id: '3',
          name: '杭州地铁3号线',
          value: '300',
          lng: 120.284282,
          lat: 30.162709
        }
      ],
      markerList: [],  // 点位标记列表(需要定时刷新数据的所有点位)
      infoWindow: null // 信息窗体
    }
  },
  watch: {
    /**
     * @introduction 监听器-监听数据变化,刷新点位label内容
     * @description 详细描述
     */
    dataList: {
      handler (val) {
        val.forEach(item => {
          const { id, value } = item
          // 根据id找到当前点位后更新点位的扩展数据
          const marker = this.markerList.find(marker => marker.getExtData().id === id)
          marker.setExtData({
            ...marker.getExtData(),
            value
          })
          // 重新设置点位的label内容
          this.setLabelContent(marker)
        })
      },
      deep: true
    }
  },
  methods: {
    /**
     * @introduction 设置信息窗体内容
     * @description 详细描述
     * @param {参数类型} 参数 参数说明
     */
    setInfoWindowContent(infoWindow, item) {
      // 创建一个构造器
      const Content = Vue.extend({
        template: `<MarkerPopup :data="data" @close="closePopup" />`,
        components: {
          MarkerPopup
        },
        data () {
          return {
            data: item
          }
        },
        methods: {
          closePopup () {
            infoWindow.close()
          }
        }
      })
      // 设置信息窗体内容
      infoWindow.setContent(new Content().$mount().$el)
    },
    /**
     * @introduction 添加自定义图标
     * @description 详细描述
     * @param {参数类型} 参数 参数说明
     */
    addCustomIcon () {
      this.dataList.forEach(item => {
        const marker = this.createMarker(item) // 创建点标记
        this.markerList.push(marker) // 将点标记添加到点位标记列表中
        const group = this.createOverlayGroup()  // 创建覆盖物群组
        group.addOverlay(marker)  // 将点标记添加到覆盖物群组中
        this.setLabelContent(marker)  // 设置点位标记label内容
        this.$refs.map.map.add(group) // 将覆盖物添加到地图上
        // 监听覆盖物群组的点击事件
        group.on('click', (e) => {
          const extData = e.target.getExtData()
          if(!this.infoWindow) {
            this.infoWindow = new AMap.InfoWindow({
              isCustom: true,  // 使用自定义窗体
              autoMove: true,  // 是否自动调整窗体到视野内
              content: '',  // 窗体内容
              offset: new AMap.Pixel(0, -80), // 窗体偏移量
            })
          }
          this.setInfoWindowContent(this.infoWindow, extData)
          this.infoWindow.open(this.$refs.map.map, [extData.lng, extData.lat])
        })
      })
    },
    /**
     * @introduction 切换刷新数据状态
     * @description 详细描述
     * @param {参数类型} 参数 参数说明
     */
    switchRefreshDataStatus () {
      this.refreshStatua = !this.refreshStatua
      if (!this.refreshStatua) {
        return false
      }
      this.loopRefreshData()
    },
    /**
     * @introduction 设置点位标记label内容
     * @description 点位内容是根据点位的扩展数据来设置的,所以需要在创建点位时设置点位的扩展数据
     * @param {参数类型} 参数 参数说明
     */
    setLabelContent (marker) {
      const { name, value } = marker.getExtData()
      const content = `<div class="map-label">
                          <p class="name">${name}</p>
                          <p class="address">${value}</p>
                        </div>`
      marker.setLabel({
        content: content, // 设置文本标注内容
        direction: 'center' // 设置文本标注方位
      })
    },
    /**
     * @introduction 创建覆盖物群组
     * @description 详细描述
     * @param {参数类型} 参数 参数说明
     */
    createOverlayGroup () {
      const group = new AMap.OverlayGroup()
      return group
    },
    /**
     * @introduction 创建图标点位
     * @description 详细描述
     * @param {参数类型} 参数 参数说明
     */
    createMarker (data) {
      const { lng, lat } = data
      const icon = new AMap.Icon({
        size: new AMap.Size(220, 82.5), // 图标尺寸
        image: require('@/assets/imgs/subwayStations.png'), // Icon的图像
        imageSize: new AMap.Size(220, 82.5), // 根据所设置的大小拉伸或压缩图片
      })
      const marker = new AMap.Marker({
        position: new AMap.LngLat(lng, lat),
        icon: icon,
        offset: new AMap.Pixel(-34, -82.5), // 图标的偏移量,最好设置一下,要不缩放地图时,图标会跑偏
        extData: data
      })
      return marker
    },
    clearAllMarkers () {
      this.markerList.forEach(marker => {
        this.$refs.map.map.remove(marker)
      })
      this.markerList = []
    },
    /**
     * @introduction 刷新数据
     * @description 模拟请求
     * @param {参数类型} 参数 参数说明
     */
    loopRefreshData () {
      const timer = setInterval(() => {
        if (this.refreshStatua === false) {
          clearInterval(timer)
          return false
        }
        console.log('刷新数据')
        this.dataList.forEach(item => {
          item.value = Math.floor(Math.random() * 1000)
        })
      }, 3000)
      this.$once('hook:beforeDestroy', () => {
        clearInterval(timer)
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.map-box {
  width: 100%;
  height: 100%;
  position: relative;
  .header {
    width: 100%;
    height: 60px;
    display: flex;
    align-items: center;
    position: absolute;
    top: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.8);
    z-index: 1;
  }
}
// 覆盖图标label的默认样式
::v-deep .amap-marker-label {
  background-color: transparent;
  border: none;
}
</style>

demo效果

image.png