星路

追寻那一缕星光,在漆黑夜晚前行

0%

Vue组件方式使用Leaflet地图-popup篇

vue项目中用到Leaflet地图,想实现需求是:1、点击地图的marker可以弹窗显示对应的信息;2、弹窗里面有按钮或超链接,可以转跳到其它地址,并带上该点的信息。网上查阅应通过自定义popup实现,但基本都是简单的实现比如只是弹窗显示简单的文本或按钮,或者通过纯js的方式来进行生成。一开始通过这篇文章初步了解了自定义内容的方法,摸索了一下实现了传入参数,不过内容相当于需要用字符串来写,后续要修改调整十分不方便。后来看到了这篇文章,虽然代码不全并且内容过于简略,得到用mount挂载方式的启发,经过一番踩坑,最终通过动态渲染方式基本完美地解决了问题,将相关的过程分享一下。


0.环境

  • vue 2.9.6
  • vue2-leaflet 2.7.0

1.安装

相关资料还是比较多的,在此简略带过

  1. 安装插件
    1
    2
    npm install vue2-leaflet -S
    npm install leaflet -S
  2. main.js引入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // leaflet地图样式
    import 'leaflet/dist/leaflet.css'
    // 修复图标显示
    import { Icon } from 'leaflet'
    delete Icon.Default.prototype._getIconUrl
    Icon.Default.mergeOptions({
    iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
    iconUrl: require('leaflet/dist/images/marker-icon.png'),
    shadowUrl: require('leaflet/dist/images/marker-shadow.png')
    })

2.案例

LPopup组件是通过content属性绑定内容的,初始化可以传入html语言字符串,通过指定class和设置style,即能够展示个性化的内容;注意不能将content绑定到一个变量比如content,然后再后续在vue中使用this.content来改变内容,这并不会触发popup重新渲染,也可以理解为popup是和marker一起预先初始化好的。

后续介绍只展示关键代码,可前往github查看完整demo

2.1.版本一 直接拼接内容

  • 实现过程
  1. content初始化时,将marker的信息保存到dom属性或者dom标签内容。
  2. 绑定map对象的popupopen事件,popup弹窗显示后,为dom元素绑定事件;或者根据dom元素保存的id,获取相应的数据更新页面内容。
  • 关键代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    <div class="vue-leaflet-container">
    <l-map ref="map" style="width: 100%; height: 100%" :zoom="zoom" :center="center">
    <l-tile-layer :url="url" :attribution="attribution" />
    <template v-for="(marker, index) in markers">
    <l-marker :key="index" :lat-lng="marker.location">
    <l-popup :content="
    cardTemplate[0] +
    marker.name + // 传入站点名称
    cardTemplate[1] +
    marker.id + // 传入站点id
    cardTemplate[2]
    " />
    </l-marker>
    </template>
    </l-map>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    export default {
    created() {
    this.cardTemplate[0] = '<div id="stationName">'
    this.cardTemplate[1] = '</div><br>告警数量:<a id="alarmNum" href="javascript:void(0);" sid="'
    this.cardTemplate[2] = '">0&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp</a><br>'
    this.getMarks()
    },
    mounted() {
    var that = this
    this.$refs.map.mapObject.on('popupopen', function (e) {
    // 根据dom属性获取站点id,知道该弹窗属于哪个站点
    const sid = document.getElementById('alarmNum').getAttribute('sid')
    // 根据站点id获取该站点的告警数量,并更新弹窗页面
    document.getElementById('alarmNum').innerHTML = that.alarmNum[sid]
    // dom元素点击事件,根据需要跳转
    document.getElementById('alarmNum').onclick = function (e) {
    const stationName = document.getElementById('stationName').innerHTML
    alert('点击marker为' + stationName)
    }
    })
    }
    }

    Tips

    this.$refs.map.mapObject方式获取到原生dom对象,可以用js方法绑定事件。类似地,给LMarker、LPopup设置ref也有mapObject属性可以获取到原生dom对象。

  • 问题与优化

  1. 当有一个marker已弹窗,点另外一个marker后弹窗,无法更新数据。

    这是由于第二个弹窗出现到第一个弹窗消失期间,有2个弹窗并存,document.getElementById('alarmNum')获取绑定到了第一个窗口的元素,可以通过给元素id加上marker的站点id来保证唯一。

  2. 使用拼接的方式修改不够直观。

    是否将弹窗内容看作普通的vue组件,通过挂载来实现呢?这样可以用写普通vue代码方法方便地修改弹窗内容。答案是有的,请看接下来的方案。

2.2.版本二 动态渲染组件

  • 实现过程
  1. content初始化时,只初试化一个带id的div。
  2. LMarker组件绑定点击事件,点击时使用Vue.extend构建一个实例。
  3. 绑定map对象的popupopen事件,popup弹窗显示后,将步骤2的实例挂载到content的div下。
  • 关键代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <div class="vue-leaflet-container">
    <l-map id="lmap" ref="map" style="width: 100%; height: 100%" :zoom="zoom" :center="center">
    <l-tile-layer :url="url" :attribution="attribution" />
    <template v-for="(marker, index) in markers">
    <l-marker :key="index" :lat-lng="marker.location" @click="handleMarkerClick(marker)">
    <l-popup :content="cardTemplate" :options="popupOptions" />
    <!-- 优化 -->
    <!-- <l-popup :content="cardTemplate[0] + marker.id + cardTemplate[1]" :options="popupOptions" /> -->
    </l-marker>
    </template>
    </l-map>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    export default {
    mounted() {
    var that = this
    this.$refs.map.mapObject.on('popupopen', function(e) {
    that.pane.$mount('#pane')
    // 优化
    // that.pane.$mount('#pane_' + that.pane.id)
    })
    },
    methods: {
    handleMarkerClick(station) {
    if (this.pane != null) {
    this.pane.$destroy()
    }
    var Component = Vue.extend(Pane)
    this.pane = new Component()
    this.pane.alarmNum = this.stationAlarmNum[station.id]
    this.pane.name = station.name
    this.pane.id = station.id
    }
    }
    }
  • 问题与优化

  1. 如果通过通过dom元素的appendChild挂载弹窗实例的$el,有类似第版本一的问题:第一个marker弹窗显示的情况下点另一个会绑定到前一个marker弹窗的dom上。

    建议为每个popup中content的挂载点div指定唯一id(虽然直接$mount方法测试没问题)

Tips

LPopup可以通过options属性设置popup的options,相关设置项可通过官网查看api文档。

*** ## 3.小结 ## - 通过查看vue2-leaflet源码发现,leaflet的vue组件有属性可以设置,进行初始化后和原生js调用api初始化方式达到相同效果。 - 使用Vue.extend可以动态渲染组件进行挂载。