最近在做公司后台ERP系统时,用到了腾讯地图,遇到了不少坑,在这里记录下:
用到了几个主要知识点先说下:
1、单例的设计思想(在判断是否有map实例那块)
2、对象深度克隆 (创建门店那块)
3、this指向问题 (有很多,自己看)
引入 qqmap
腾讯官方未提供通过npm插件方式来安装腾讯地图,还好有大佬写了一个:
https://www.npmjs.com/package/qqmap
npm install qqmap --save
在vue中导入,导入方式有两种,自己挑一种:
var maps = require('qqmap') // CommonJS 导入方式
import maps from 'qqmap' // 使用ES module
业务逻辑梳理
引入完之后,咱们切入正题:
首先说下业务逻辑,在点击添加门店时,会出现一个tabs标签页,这块用的是element tabs标签页组件,点击显示腾讯地图,店长可搜索想要添加的门店地址,在地图中标记门店;或直接拖动地图,来标记需要创建的门店,当在地图中标记好地址,对应的地址会显示在输入框中, 这样门店 地址会直接拿到,我会把经纬度坐标、门店地址 发送给后端,公司小程序的地图与后台系统的数据是共用的,大体效果如下:
上代码:
html模板代码:
<template>
<div>
<!-- el-button 添加门店按钮 -->
<el-button type="primary" @click="createdShop">添加门店</el-button>
<!-- el-dialog 弹出的蒙层 -->
<el-dialog title="腾讯地图" :visible.sync="dialogFormVisible" @close="dielogClose">
<el-tabs type="border-card" v-model="TXMapParams.selectValue" @tab-click="selectTab">
<!-- tab1一个站位的页 -->
<el-tab-pane label="站位" name="1"><h1 style="width:200px; height:200px;">站位</h1></el-tab-pane>
<!-- tab2腾讯地图展示页 -->
<el-tab-pane label="腾讯地图" name="2">
<!-- el-select 输入框-->
<el-select v-model="TXMapParams.searchValue" filterable remote reserve-keyword placeholder="请输入关键词"
style="width:400px; marginBottom:20px;" :remote-method="searchKeyword"
>
<el-option v-for="item in TXMapParams.addressOptions"
:key="item.name"
:label="item.name"
:value="item.name"
>
</el-option>
</el-select>
<!-- 下边这个div就是地图-->
<div ref="mapBox" style="width:800px; height:300px;"></div>
</el-tab-pane>
</el-tabs>
</div>
</template>
js代码:
因为js代码有点多,为了方便查看,我分成了几个部分:
// 我在全局定义了一些默认的数据,与地图有关的数据我全部放在这个全局对象中,在data数据对象中用深克隆的方
// 式,赋值给 TXMapParams,之所以要这样做,因为在创建完一个门店时,第二次在创建一个门店,数据是初
// 始化之后的, 而不会是第一次创建的数据。
const defaultParams = {
map: null, // 地图实例 (地图)
marker: '', // 地图的标识(标注的点)(地图)
appkey: 'xxxxxxxxx', // appkey是开发者key(地图)
selectValue: '1', // 初始化tabs标签栏,让它默认显示第一栏(element)
increaseZB:{ // 这个参数是地图的经纬度(地图)
lat: 37.52, // 纬度
lng: 121.39 // 经度
},
addressOptions: [],// 搜索框的下拉菜单需要渲染的数据(element)
searchValue: '', // 搜索框中的数据(element)
searchService: null // 关键字搜索时,搜索周边/相关地域/地址(地图)
}
import maps from 'qqmap' // 引入腾讯地图
export default {
data() {
return {
dialogFormVisible: false,
TXMapParams: {...defaultParams, increaseZB:{...defaultParams['increaseZB']}}
}
}
methods:{
// 创建门店的方法
createdShop() {
this.dialogFormVisible = true // 蒙层显示
this.TXMapParams = {...defaultParams, increaseZB:{...defaultParams['increaseZB']}} // 这块是一个深度克隆,上边有解释!
},
// 这个方法是监听蒙层关闭的,只要关闭,就初始化地图,这个目的是当点击编辑的时候,每次地图都会先初始化一次,然后在把对应的数据赋值给地图,否则造成的bug是编辑每一行数据,地图信息都会一样的!!!
dielogClose() {
this.TXMapParams.selectValue = '1'
this.TXMapParams.map = null
},
// 这个方法是当循环切换tabs时,判断地图是否已经被创建,这块利用了单例的设计思想,如果有map实例,用被存储的,没有则初始化地图,这样实例只会被初始化一次
selectTab() {
if(this.TXMapParams.selectValue === '2') {
this.TXMapParams.map || this.initMap()
}
},
// 初始化地图
initMap() {
maps.init(this.TXMapParams.appkey, () => { // 参数一:开发者key,参数二:一个函数
// 其实这个判断是否有map实例,不写也行,在上边已经判断过了
if (!this.TXMapParams.map) {
// createZuoBiao方法是 初始化坐标位置,括号两个参数是经纬度
const myLatLng = this.createZuoBiao(this.TXMapParams.increaseZB.lat, this.TXMapParams.increaseZB.lng)
this.TXMapParams.map = new maps.Map(this.$refs.mapBox, { // 实例化地图,赋值给data中的map
center: myLatLng, // 目前的位置
zoom: 13,
draggable: true, // 是否可移动
scrollwheel: true, // 是否可滚动
disableDoubleClickZoom: false
})
}
// 初始化marker标识
this.TXMapParams.marker = new maps.Marker({
position: this.createZuoBiao(this.TXMapParams.increaseZB.lat, this.TXMapParams.increaseZB.lng), // 坐标位置
map: this.TXMapParams.map // 实例化的地图
})
const _this = this // 修正在事件中的this指向
// 初始化点击事件,绑定单击事件添加参数,参数一:地图实例,参数二:点击事件,可以通过第三个函数的参数event来拿到每次点击的经纬度。
maps.event.addListener(_this.TXMapParams.map, 'click', function(event) {
_this.createdMarker(event) // 提取经纬度的方法
})
})
},
// 创建经纬度
createZuoBiao(myLatitude, myLongitude) {
return new maps.LatLng(myLatitude, myLongitude)
},
// 创建marker标识,里边的判断是每次创建判断之前是否有marker,有话先删除,因为只能标识一个门店
createdMarker(event) {
if (this.TXMapParams.marker) {
this.deleteOverlays() // 删除marker的方法
}
this.TXMapParams.increaseZB.lat = event.latLng['lat']
this.TXMapParams.increaseZB.lng = event.latLng['lng']
// 创建标识
this.TXMapParams.marker = new maps.Marker({
position: this.createZuoBiao(this.TXMapParams.increaseZB.lat, this.TXMapParams.increaseZB.lng),
map: this.TXMapParams.map
})
// 根据经纬度调用获取位置方法,这个方法就是根据经纬度解析成地址
this.geocoder().getAddress(this.createZuoBiao(this.TXMapParams.increaseZB.lat, this.TXMapParams.increaseZB.lng))
},
// 删除marker 标识
deleteOverlays() {
this.TXMapParams.marker.setMap(null)
},
// 经纬度解析地址
geocoder() {
const _this = this // 修正下边this指向的问题
return new maps.Geocoder({
complete: function(result) {
// 通过result参数 可以拿到当前点击的位置的详细地址,展开运算符,获取里边的参数
const { city, district, province, street, streetNumber, town, village } = { ...result['detail']['addressComponents'] }
_this.TXMapParams.searchValue = `${province}${city}${district}${street}${town}${village}${streetNumber}` // 参数拼接赋值给搜索框
}
})
},
// 检索关键字的方法
searchShop() {
var _this = this // 修正下边this指向
var latlngBounds = new maps.LatLngBounds()
// 设置Poi检索服务,用于本地检索、周边检索
this.TXMapParams.searchService = new maps.SearchService({
// 设置搜索范围为烟台
location: '烟台',
// 设置搜索页码为1
pageIndex: 1,
// 设置每页的结果数为5
pageCapacity: 5,
// 设置展现查询结构到infoDIV上
// panel: document.getElementById('infoDiv'),
// 设置动扩大检索区域。默认值true,会自动检索指定城市以外区域。
autoExtend: true,
// 检索成功的回调函数,result参数是搜索的结果
complete: function(results) {
// 把地址相关数据提取出来
var pois = results.detail.pois
if (pois) {
for (var i = 0, l = pois.length; i < l; i++) {
var poi = pois[i]
_this.TXMapParams.addressOptions = []
if (poi.address) { // 判断是否有address,因为会有没有搜到的时候
_this.TXMapParams.addressOptions.push(poi)// 地址数据push到门店下拉菜单
}
// 扩展边界范围,用来包含搜索到的Poi点
latlngBounds.extend(poi.latLng)
}
}
// 调整地图视野
_this.TXMapParams.map.fitBounds(latlngBounds)
},
// 服务请求失败
error: function() {
_this.$message({
type: 'error',
message: '请正确输入查询结果!',
duration: 5000
})
}
})
},
// 设置搜索的范围和关键字等属性
searchKeyword(query) {
this.searchShop()
// 根据输入的城市设置搜索范围
this.TXMapParams.searchService.setLocation('烟台')
// 根据输入的关键字在搜索范围内检索
this.TXMapParams.searchService.search(query)
}
}
}
总结
到这,就已经结束了,实现的功能就是刚开始所看到的那样,其实逻辑一点页不难,因为腾讯地图api上边都有很详细的例子,但是如果你想把它拼起来,形成一个完整的功能,还需要有点耐心的,上边的逻辑不单单是添加店铺,还有点击门店编辑的时候,拿到当前的门店信息直接赋值给地图即可,亲测,无bug,而且抗霍霍!
就到这吧,有啥问题或不懂的地方欢迎小伙伴们留言。。。