vue项目中用到Leaflet地图,想实现需求是:1、点击地图的marker可以弹窗显示对应的信息;2、弹窗里面有按钮或超链接,可以转跳到其它地址,并带上该点的信息。网上查阅应通过自定义popup实现,但基本都是简单的实现比如只是弹窗显示简单的文本或按钮,或者通过纯js的方式来进行生成。一开始通过这篇文章初步了解了自定义内容的方法,摸索了一下实现了传入参数,不过内容相当于需要用字符串来写,后续要修改调整十分不方便。后来看到了这篇文章,虽然代码不全并且内容过于简略,得到用mount挂载方式的启发,经过一番踩坑,最终通过动态渲染方式基本完美地解决了问题,将相关的过程分享一下。
0.环境
- vue 2.9.6
- vue2-leaflet 2.7.0
1.安装
相关资料还是比较多的,在此简略带过
- 安装插件
1
2npm install vue2-leaflet -S
npm install leaflet -S - 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.版本一 直接拼接内容
- 实现过程
- content初始化时,将marker的信息保存到dom属性或者dom标签内容。
- 绑定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
22export 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        </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对象。
问题与优化
当有一个marker已弹窗,点另外一个marker后弹窗,无法更新数据。
这是由于第二个弹窗出现到第一个弹窗消失期间,有2个弹窗并存,
document.getElementById('alarmNum')
获取绑定到了第一个窗口的元素,可以通过给元素id加上marker的站点id来保证唯一。使用拼接的方式修改不够直观。
是否将弹窗内容看作普通的vue组件,通过挂载来实现呢?这样可以用写普通vue代码方法方便地修改弹窗内容。答案是有的,请看接下来的方案。
2.2.版本二 动态渲染组件
- 实现过程
- content初始化时,只初试化一个带id的div。
- LMarker组件绑定点击事件,点击时使用Vue.extend构建一个实例。
- 绑定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
22export 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
}
}
}问题与优化
如果通过通过dom元素的appendChild挂载弹窗实例的$el,有类似第版本一的问题:第一个marker弹窗显示的情况下点另一个会绑定到前一个marker弹窗的dom上。
建议为每个popup中content的挂载点div指定唯一id(虽然直接$mount方法测试没问题)
Tips
LPopup可以通过options属性设置popup的options,相关设置项可通过官网查看api文档。