需求
使用vue2.x来实现高德地图自定义弹窗内容,可以通过一个按钮来切换不同的样式风格,即改变弹窗内容样式。
分析
高德地图官网为开发者提供了自定义弹窗内容的例子,见这里。官方提供的方式是Dom操作,即createElement、appendChild等方式。我们选择用vue,用vue的方式来实现Dom操作。vue的全局API中第一个是Vue.extend,这个是用来实现一个vue的子类构造器,其实可以看做是一个vue模板对象构造器,其实例化对象类似于Dom中的DocumentFragment接口。
由于Vue.extend可以生成一个vue子类构造器,从而可以利用vue的响应式特性和组件化思想,来实现弹窗的内容。弹窗里的内容就是一个vue子类实例。
实现
1 PopupWindow.vue
这里我们定制弹窗里的内容,利用vue单文件组件的写法,可以快速实现一个弹窗模板并实现数据响应绑定。我们甚至可以在里面使用自定义的组件chart。这里我们只列出template的内容:
<template>
<div class="info-win-container">
<div class="header-container">
<div class="title-container">{{deviceAttrInfo.name.value}}</div>
<div class="status-btn-container">在线</div>
<i class="el-icon-close" :data-feature-id="deviceAttrInfo.sn.value" @click="closeInfoWindow"></i>
</div>
<div class="content-container">
<el-row>
<el-col :span="12" v-if="deviceAttrInfo.sn.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">序列号:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.sn.value}}</div>
</div>
</el-col>
<el-col :span="12" v-if="deviceAttrInfo.model.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">型号:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.model.value}}</div>
</div>
</el-col>
<el-col :span="12" v-if="deviceAttrInfo.manufacturer.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">生产厂商:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.manufacturer.value || ''}}</div>
</div>
</el-col>
<el-col :span="12" v-if="deviceAttrInfo.deviceCategory.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">所属类别:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.deviceCategory.value || ''}}</div>
</div>
</el-col>
<el-col :span="12" v-if="deviceAttrInfo.trade.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">所属行业:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.trade.value || ''}}</div>
</div>
</el-col>
<el-col :span="12" v-if="deviceAttrInfo.description.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">设备描述:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.description.value || ''}}</div>
</div>
</el-col>
<el-col :span="12" v-if="deviceAttrInfo.uploadData.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">上报数据:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.uploadData.value||''}}</div>
</div>
</el-col>
<el-col :span="12" v-if="deviceAttrInfo.uploadTime.isShow">
<div class="content-item-container" :style="itemStyleAjust">
<div class="item-label">上报时间:</div>
<div class="item-value" :style="valueStyleAjust">{{deviceAttrInfo.uploadTime.value||''}}</div>
</div>
</el-col>
<el-col :span="24" v-if="controlModelList.includes(deviceAttrInfo.model.value)">
<div class="item-controller-container" :style="itemStyleAjust">
<div class="item-label">开关控制:</div>
<div class="item-value" :style="valueStyleAjust">
<el-switch v-model="isOpen" size="mini" active-text="开" inactive-text="关"></el-switch>
</div>
</div>
</el-col>
</el-row>
<div class="chart-container" v-if="deviceAttrInfo.model.value === 'demo_video' || deviceAttrInfo.model.value === 'demo_tmp'">
<img src="/static/assets/map/video_img.png" v-if="deviceAttrInfo.model.value === 'demo_video'" />
<!--自定义组件-->
<chart
class="chart"
:option="lineChartOption"
:data="lineChartData"
v-if="deviceAttrInfo.model.value === 'demo_tmp'"
></chart>
</div>
</div>
</div>
</template>
关于里面涉及的数据,由于比较多,就不贴代码了。
2 map.vue
这里是地图组件,可以在这里处理弹窗,我们地图选用的是高德地图,所以调用高德地图弹窗的API,即代码摘录如下:
//初始化地图弹窗实例,采用自定义窗体
initInfoWindow() {
this.infoWindow = new AMap.InfoWindow({
isCustom: true,//自定义窗体
offset: new AMap.Pixel(0, -40)
})
},
//点击地图标记物弹窗
handleFeatureClick(e) {
const feature = e.target
const featureList = feature.getMap().getAllOverlays('marker')
const activeFeature = featureList.find(item => item.active === true)
if (activeFeature) {
activeFeature.setAnimation('AMAP_ANIMATION_NONE')
activeFeature.active = false
}
feature.setAnimation('AMAP_ANIMATION_BOUNCE')
feature.active = true
const content = this.initInfoWindowContent(e)
this.infoWindow.setContent(content)
this.infoWindow.open(this.map, e.target.location)
},
高德地图弹窗中的内容是以字符串或者dom对象的形式传入,我们这里采用dom对象方式,那如何将上面的PopupWindow组件转化为dom对象呢?这就要靠Vue.extend的帮助了。代码如下:
createInfoWindowComponent(content) {
const InfoWindow = Vue.extend(PopupWindow)
return new InfoWindow()
},
initInfoWindowContent(e) {
const { sn, name, model } = e.target
const { manufacturer, deviceCategoryCode, tradeCode, description } = GLOBAL.deviceModelList.find(deviceModelObj => deviceModelObj.deviceModel === model)
const selectedCategory = this.categoryOpts.find(categoryObj => categoryObj.code === deviceCategoryCode)
const deviceCategory = selectedCategory ? selectedCategory.name : ''
const selectedTrade = this.tradeOpts.find(tradeObj => tradeObj.code === tradeCode)
const trade = selectedTrade ? selectedTrade.name : ''
const deviceInfo = {
sn: {
value: sn,
isShow: true
}, name: {
value: name,
isShow: true
}, model: {
value: model,
isShow: true
}, manufacturer: {
value: manufacturer,
isShow: true
}, deviceCategory: {
value: deviceCategory,
isShow: true
}, trade: {
value: trade,
isShow: true
}, description: {
value: description,
isShow: true
}, uploadData: {
value: '',
isShow: true
}, uploadTime: {
value: '',
isShow: true
}}
const controlPopInfo = this.$store.state.map.popupInfo
for (const key of Object.keys(deviceInfo)) {
deviceInfo[key].isShow = controlPopInfo[key].isShow
}
const infoWinCom = this.createInfoWindowComponent()
infoWinCom.deviceAttrInfo = Object.assign({}, infoWinCom.deviceAttrInfo, deviceInfo)
infoWinCom.map = this.map
infoWinCom.itemStyleAjust = this.itemStyleAjust
infoWinCom.valueStyleAjust = this.valueStyleAjust
return infoWinCom.$mount().$el
},
我们每次弹窗显示时,借助Vue.extend()生成一个文档外的片段(DocumentFragment),我们不挂载在任何dom元素上,即 $mount(),这个时候dom对象就已经存在一个轻量版的文档中了,只是没有在正式的文档中,我们也不需要它存在正式文档中,这样它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。然后我们通过 $el 属性即可取出dom元素。
其实在取出dom元素之前,我们可以对组件对象进行一系列的逻辑操作,比如修改数据,调整样式等。这样就给我们提供了当即修改弹窗内容的能力。我们可以调整弹窗内容,也可以调整样式。这个中间的操作可以通过js来实时实现。那自然就可以通过一个按钮,点击按钮执行相应的js操作即可。最终实现了弹窗内容的实时变化。