VueBaiduMap组件封装:多边形组件和右键菜单
1. 在地图上点右键,弹出菜单:添加多边形。点击菜单项后,进⼊绘制模式。
2. 在绘制模式下,每次⿏标点击百度地图,都会在图上添加⼀个点。多次添加的点构成⼀个多边形。
3. 随着⿏标在地图上移动,多边形的最后⼀个点也随之改变。
4. 点击右键,弹出菜单:添加结束。点击菜单项后,多边形绘制完成。
5. 在多边形上点右键,弹出菜单:修改多边形,删除多边形。
6. 选择修改多边形,则多边形进⼊修改模式,可⽤⿏标拖动其每⼀个点。
7. 选择删除多边形,则会将多边形从地图中删除。
8. 可添加多个多边形,每个多边形的菜单互不影响。
分析整个流程,可以得出要解决的关键点有以下⼏个:
1. 为地图添加右键菜单。
2. 为地图添加左键点击事件和mousemove事件。
3. 在地图上显⽰多边形。
4. 在多边形上添加右键菜单。
5. 将功能封装为⼀个单独的vue。
关键点
Vue Baidu Map实际是百度官⽅对百度JavaScript API v2.0进⾏的封装,动态为document创建了⼀个<script>标签并下载JavaScript API v2.0,因此其核⼼功能依然是JavaScript API v2.0提供的,Vue Baidu Map只是⼀层壳⽽已。
Vue Baidu Map包括BaiduMap在内的组件都是异步加载,加载完成后会触发ready事件。ready事件传⼊的参数为{BMap, map},因此在ready中可以拿到BMap与实例化后的map对象。既然有了这两个对象,那么对地图的操作完全可以使⽤常规html页⾯中调⽤百度JavaScript API v2.0的⽅式。
为地图添加右键菜单
官⽅⽂档中明确给出了为地图添加右键菜单的⽅式,即使⽤BmContextMenu组件:
<baidu-map class="map" :center="center" :zoom="zoom">
<bm-context-menu>
<bm-context-menu-item :callback="addPolygon" text="添加多边形"></bm-context-menu-item>
</bm-context-menu>
</baidu-map>
使⽤组件的⽅式⾮常简单明了。
除了组件的⽅式,也可以在BaiduMap的@ready事件中使⽤js的⽅式添加:
mapReady({BMap, map}) {
var addPolygon = function() {}
var menu = new BMap.ContextMenu()
menu.addItem(new BMap.MenuItem('添加多边形', addPolygon))
map.addContextMenu(menu)
}
添加后,在地图上点右键,菜单就会弹出。
为地图添加左键点击事件和mousemove事件
左键点击事件可以直接为BaiduMap绑定@click事件。
若使⽤js的⽅式,可以⽤addEventListener()来添加:
var clickCallback = function(e) {}
map.addEventListener('click', clickCallback)
mousemove事件同理:
var mousemoveCallback = function(e) {}
map.addEventListener('mousemove', mousemoveCallback)
在地图上显⽰多边形
在地图上显⽰多边形可以使⽤BmPolygon组件:
<baidu-map class="map" :center="center" :zoom="zoom">
<bm-polygon
v-for="(item, index) in polygons"
:path="item.path"
:editing="item.editing"
:key="index">
</bm-polygon>
</baidu-map>
其中polygons是在data中定义的⼀个数组,其每个数据的结构为:
{
path: [], // 所有点
editing: false, // 是否可编辑
}
这样就可以在地图上显⽰多个多边形了。
在多边形上添加右键菜单
在百度地图的定义中,多边形属于覆盖物(overlay)。为覆盖物添加右键菜单,js的⽅式为:
var editPolygon = function() {}
var deletePolygon = function() {}
var menu = new BMap.ContextMenu()
menu.addItem(new BMap.MenuItem('修改', editPolygon.bind(polygonObj)))
menu.addItem(new BMap.MenuItem('删除', deletePolygon.bind(polygonObj)))
polygonObj.addContextMenu(menu)
其中polygonObj为多边形对象。
尝试使⽤BmContextMenu来为多边形添加菜单:
<bm-polygon
:path="path"
:editing="editing"
<bm-context-menu>
<bm-context-menu-item :callback="editPolygon" text="修改"></bm-context-menu-item>
<bm-context-menu-item :callback="deletePolygon" text="删除"></bm-context-menu-item>
</bm-context-menu>
</bm-polygon>
<template>
<div>
<slot></slot>
</div>
</template>
<script>
...
</script>
再查看BmPolygon的源码:
<script>
.
..
</script>
可见,BmMarker预留了⼀个<slot>给可能塞进来的元素,⽽BmPolygon则没有。因此将BmMarker的<template>部分复制给BmPolygon,再次编译运⾏,发现右键点击BmPolygon,会弹出菜单了,且其回调函数可以正常响应。
更新问题
多边形需要动态绘制。此时出现了⼀个问题:若页⾯初始化时多边形的各个点就已经确定,那么多边形点右键会弹出菜单。然⽽,若多边形是动态添加的,则点右键不会弹出菜单。
当多边形动态添加或修改时,会构造⼀个全新的对象。该对象与之前的多边形是独⽴的。⽽菜单依然依附在之前的多边形对象上。于是当菜单进⾏reload时就会有问题。
于是考虑放弃使⽤BmContextMenu,改为当在多边形上点右键时,调⽤js API动态⽣成多边形的菜单。
所以现在的问题变为:为多边形添加右键点击事件。
为多边形添加右键点击事件
查询js的事件可知,⿏标右键点击事件名为rightclick。然⽽直接为BmPolygon添加@rightclick响应函数,并不会触发。于是可断定BmPolygon并没有触发该事件。
实际上,官⽅给出的js API的demo⾥,覆盖物本⾝并不监听这些事件,需要调⽤addEventListener()来为其注册事件。于是封装的BmPolygon没有触发该事件也就理所当然了。
查看BmPolygon的源码,⼤致结构为:
<script>
...
export default {
name: 'BmPolygon',
mixins: [commonMixin('overlay')],
props: {...},
watch: {...},
methods: {
load() {
const { BMap, map, path, ... } = this
const overlay = new BMap.Polygon(
...
})
map.addOverlay(overlay)
bindEvents.call(this, overlay)
...
}
}
mousemove是什么键}
</script>
可以看到在load函数中调⽤js API来new了⼀个Polygon并添加给map对象,并将其保存在iginInstance中。这个Polygon对象就是整个组件的核⼼对象。
于是,要为Polygon对象注册rightclick事件,就在这⾥对overlay调⽤addEventListener()即可:
load() {
...
const that = this
overlay.addEventListener('rightclick', function(e) {
that.$emit('rightclick', e)
})
}
注意需要同步为BmPolygon触发⼀个rightclick,这样通过@rightclick注册给BmPolygon的函数才能响应。
其中参数e与click的参数e格式相同,其e.currentTarget为当前覆盖物js对象。
右键事件注册成功,剩下的就是在右键事件中调⽤js API为当前多边形对象添加菜单了。
添加给多边形的菜单,只有当多边形更新时才会失效。若多边形的点⼀直没有变化,那么添加给它的菜单也会⼀直都能响应。考虑到这⼀点,将菜单对象保存在多边形对象中,并添加⼀个if判断:
onRightClick(e) {
var polygonObj = e.currentTarget
u === undefined) {
var menu = new BMap.ContextMenu() // BMap需⾃⾏保存
menu.addItem(new BMap.MenuItem('修改', function() {
...
}))
polygonObj.addContextMenu(menu)
// 将menu保存在多边形对象中
// 添加菜单后不会⽴即弹出,需额外触发⼀次右键点击事件
polygonObj.dispatchEvent('rightclick', e)
}
}
这样,只有当多边形更新时才会创建新的菜单。
需要注意的⼀点是,⽤户在多边形上点击右键,是希望看到弹出菜单。然⽽,多边形右键的触发顺序是:若有菜单,先弹出菜单→触发rightclick函数。因此,若多边形此时没有菜单,尽管会在rightclick中创建并添加,但由于菜单的显⽰流程在前,所以菜单是不会弹出来的。于是在最后额外触发了⼀次右键点击事件,这次事件触发后,由于菜单已有,所以会正常弹出;再次触发rightclick函数,因为u也不为undefined了,所以什么也不做。
对于⽤户的感知就是,点击矩形区域,弹出了菜单。
封装
功能已经实现了,接下来要对整体功能进⾏封装。然⽽遇到了⼀个新的问题:
当把BmPolygon直接放在BaiduMap组件中时,显⽰⼀切正常:
<baidu-map class="map" :center="center" :zoom="zoom">
<bm-polygon
v-for="(item, index) in polygons"
:path="item.path"
:editing="item.editing"
:key="index">
</bm-polygon>
</baidu-map>
若将BmPolygon封装到⼀个PolygonEx.vue⽂件中,⽤<div>进⾏包裹,然后再引⼊到BaiduMap组件中时,多边形不显⽰了:
<template>
<div>
<bm-polygon
v-for="(item, index) in polygons"
:path="item.path"
:editing="item.editing"
:key="index">
</bm-polygon>
</div>
</template>
考虑到封装的PolygonEx.vue类似⼀个overlay组件,于是分析了⼀下Vue Baidu Map的overlay组件写法:
<script>
import commonMixin from '../base/mixins/common.js'
export default {
...
mixins: [commonMixin('overlay')],
...
methods: {
load () {
const {BMap, map, $el, pane} = this
...
const overlay = new XXX()
map.addOverlay(overlay)
}
}
}
</script>
如上,其结构都有如下特点:
1. 都混⼊了⼀个commonMixin。
2. 都提供了⼀个load()函数。
查看commonMixin源码,发现其ready函数为:
ready () {
const $parent = getParent(this.$parent)
const BMap = this.BMap = $parent.BMap
const map = this.map = $parent.map
this.load()
this.$emit('ready', {
BMap,
map
})
}
其中调⽤了this.load()。所以load()函数是必须实现的。
因此,要⾃⼰实现⼀个覆盖物组件PolygonEx.vue,只需要混⼊commonMixin,并提供load()函数即可。
按这个规则修改PolygonEx.vue,多边形可以正常显⽰了。
其他
上⾯已经解决了最主要的⼀些问题。还有⼀些其他的细节,例如令矩形随⿏标移动⽽动态修改最后⼀个点,矩形不同状态下弹出的菜单不同,多个矩形之间状态独⽴菜单互不影响,矩形数组改变则所有矩形重绘等,都是需要考虑的。具体可以参考下⾯的源码。
源码
在页⾯的index.vue所在⽬录下,添加⼀个components⽂件夹,在其下添加两个vue⽂件:Polygon.vue和PolygonEx.vue。Polygon.Vue
<template>
<div>
<slot/>
</div>
</template>
<script>
import commonMixin from 'vue-baidu-map/components/base/mixins/common.js'
import bindEvents from 'vue-baidu-map/components/base/bindEvent.js'
import { createPoint } from 'vue-baidu-map/components/base/factory.js'
export default {
name: 'BmPolygon',
mixins: [commonMixin('overlay')],