原文地址:https://liubing.me/vue-gaode-...
整理了下日常地圖開發過程中遇到的問題及解決方法,供大家參考,文章將會持續更新。
地圖引入問題
網上搜索了一些資料,大部門都是index.html直接引入高德地圖的js文件,個人感覺沒有必要,畢竟地圖只是部分頁面需要使用,所以這種方法直接不考慮了。
然後又找到了一種地圖懶加載的方法,需要的時候按需引入地圖即可,
整理了下按需加載地圖的js,我們可以新建一個js文件,如loadMap.js
,位置可以隨意,
這裏爲了引入方便直接放組件同級目錄了,代碼如下:
/**
* 動態加載高德地圖
*
* @export
* @param {*} key 高德地圖key
* @param {*} plugins 高德地圖插件
* @param {string} [v='1.4.14'] 高德地圖版本
* @returns
*/
export default function loadMap (key, plugins, v = '1.4.14') {
return new Promise(function (resolve, reject) {
if (typeof AMap !== 'undefined') {
// eslint-disable-next-line no-undef
resolve(AMap)
return true
}
window.onCallback = function () {
// eslint-disable-next-line no-undef
resolve(AMap)
}
let script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://webapi.amap.com/maps?v=${v}&key=${key}&plugin=${plugins}&callback=onCallback`
script.onerror = reject
document.head.appendChild(script)
})
}
使用
需要用到的地方直接引用這個文件,再mounted
的時候執行
loadMap(this.key, this.plugins, this.v)
.then(AMap => {
// 此處地圖就加載成功了,然後就可以使用`new AMap.Map`來實例化地圖了
console.log('地圖加載成功!')
})
.catch(() => {
console.log('地圖加載失敗!')
})
完整代碼
爲了更好的體驗,這裏加了一個loading
動畫,地圖加載成功complete
後取消loading
效果。
<template>
<div class="map">
<div id="GDMap"
v-loading="loading">
</div>
</div>
</template>
<script>
import loadMap from './loadMap'
export default {
data () {
return {
// 地圖實例
GDMap: null,
// 加載的一些插件
// 更多參考:https://lbs.amap.com/api/javascript-api/guide/abc/plugins#plugins
plugins: [
'AMap.OverView',
'AMap.MouseTool',
'AMap.PolyEditor',
'AMap.RectangleEditor',
'AMap.PlaceSearch',
'AMap.DistrictLayer',
'AMap.CustomLayer'
],
// key
key: 'c5eac55551560531336988396dacbf53',
// 地圖版本
v: '1.4.14',
loading: true
}
},
mounted () {
loadMap(this.key, this.plugins, this.v)
.then(AMap => {
this.GDMap = new AMap.Map('GDMap', {
zoom: 11,
center: [116.397428, 39.90923]
})
this.GDMap.on('complete', () => {
this.loading = false
})
})
.catch(() => {
this.loading = false
console.log('地圖加載失敗!')
})
}
}
</script>
<style>
#GDMap {
width: 1200px;
height: 500px;
position: relative;
}
</style>
效果圖
獲取多邊形編輯的點的問題
說的直白點,就是多邊形在拖動點編輯過程中知道是哪個點
看了下官網的相關文檔:https://lbs.amap.com/api/javascript-api/reference/plugin#AMap.PolyEditor
沒找到相關的api,無奈只能自己寫了。
大致思路就是多邊形在編輯過程中其他的點的座標是不變了,唯一變化的就是被編輯的點,在編輯過程中做個判斷即可找到這個點的索引。
多邊形在被編輯過程中是會觸發change
事件,所以可以利用這個事件寫些判斷,
在拿到這個點的索引後我們就可以乾點其他事情了。
// 多邊形的path
let polygonPath = polygon.getPath()
// 索引
let index
// change事件
polygon.on('change', (ev) => {
const curPath = ev.target.getPath()
for (let i = 0; i < path.length; i++) {
// 判斷一直在變化的點
if (polygonPath[i].lng !== curPath[i].lng || polygonPath[i].lat !== curPath[i].lat) {
index = i
break
}
}
polygonPath = JSON.parse(JSON.stringify(curPath))
console.log('編輯點索引:', index)
})
完整代碼
<template>
<div class="map">
<div id="GDMap"
v-loading="loading">
</div>
</div>
</template>
<script>
import loadMap from './loadMap'
export default {
data () {
return {
// 地圖實例
GDMap: null,
// 加載的插件
plugins: [
'AMap.OverView',
'AMap.MouseTool',
'AMap.PolyEditor',
'AMap.RectangleEditor',
'AMap.PlaceSearch',
'AMap.DistrictLayer',
'AMap.CustomLayer'
],
// key
key: 'c5eac55551560531336988396dacbf53',
// 地圖版本
v: '1.4.14',
loading: true
}
},
mounted () {
loadMap(this.key, this.plugins, this.v)
.then(AMap => {
this.GDMap = new AMap.Map('GDMap', {
zoom: 11,
center: [116.397428, 39.90923]
})
this.GDMap.on('complete', () => {
this.loading = false
})
const path = [
[116.403322, 39.920255],
[116.410703, 39.897555],
[116.402292, 39.892353],
[116.389846, 39.891365]
]
const polygon = new AMap.Polygon({
path: path,
strokeColor: '#FF33FF',
strokeWeight: 6,
strokeOpacity: 0.2,
fillOpacity: 0.4,
fillColor: '#1791fc',
zIndex: 50
})
// 地圖添加多邊形
this.GDMap.add(polygon)
// 縮放地圖到合適的視野級別
this.GDMap.setFitView([ polygon ])
// 多邊形編輯實例
const polyEditor = new AMap.PolyEditor(this.GDMap, polygon)
// 開啓編輯
polyEditor.open()
// 多邊形的path
let polygonPath = polygon.getPath()
// 索引
let index
// change事件
polygon.on('change', (ev) => {
const curPath = ev.target.getPath()
for (let i = 0; i < path.length; i++) {
// 判斷一直在變化的點
if (polygonPath[i].lng !== curPath[i].lng || polygonPath[i].lat !== curPath[i].lat) {
index = i
break
}
}
polygonPath = JSON.parse(JSON.stringify(curPath))
console.log('編輯點索引:', index)
})
})
.catch(() => {
this.loading = false
console.log('地圖加載失敗!')
})
}
}
</script>
<style>
#GDMap {
width: 1200px;
height: 500px;
}
</style>
效果圖
信息窗口使用問題
看了下官網信息窗口的相關例子,發現寫法都不太友好,例如以下幾種形式的寫法:
要是按照這種寫法,vue中相關事件及數據傳遞怎麼整,後期維護還很困難,所以這種形式直接被我pass掉了,自己寫一個類似的組件,放在地圖上不就行了吧,自由程度高,可定製化也高。
簡單的信息窗口組件
自定義了一個簡單的信息窗口組件InfoWindow
,顯示出來後大致就長這個樣子,UI樣式可以自己定義,所以很方便。
組件代碼
<div id="GDMap">
<info-window></info-window>
</div>
<template>
<div class="info-window">
<div class="top">
<span class="title">標題</span>
<span class="close">x</span>
</div>
<div class="content">
我是窗口的內容
</div>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.info-window {
position: absolute;
top: 50%;
left: 50%;
padding: 10px;
min-width: 300px;
border-radius: 5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
z-index: 10;
transform: translate(-50%, -50%);
}
.info-window::after {
content: "◆";
font-size: 36px;
height: 24px;
color: #ffffff;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.info-window .top {
padding-bottom: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.info-window .top .title {
font-size: 12px;
}
.info-window .top .close {
width: 24px;
font-size: 12px;
cursor: pointer;
}
</style>
窗口位置
這是官網信息窗口在地圖拖拽過程中的表現,會發現一直固定在某一個點相對位置
再看看我們寫的信息窗口組件,看到差異了吧,信息窗口被固定死了。
通過審覈元素你會發現官方的信息窗口的位置隨着地圖的拖拽在實時變化。
所以加下來我們要做的就是這個。
看了下文檔,找到了地圖的一個方法lngLatToContainer
,
官方給出的描述是:地圖經緯度座標轉爲地圖容器像素座標
,
實際上窗口信息在地圖上顯示靠的是一個座標點,有了這個座標點信息窗口才知道需要哪個位置進行顯示,
對於信息窗口組件來說這個座標點就是absolute
定位中的top
和left
,對於地圖來講就是lng
和lat
經緯度,
所以先定一個地圖的座標點,pos: [116.397428, 39.90923]
,該座標也是地圖初始化時候的中心點座標(center)
,
地圖拖動過程中會觸發mapmove
事件,在該事件中,通過lngLatToContainer
方法獲取到pos
點在地圖容器的實時像素座標,然後實時改變組件的top
和left
即可達到效果
相關代碼
this.GDMap.on('mapmove', () => {
let position = this.GDMap.lngLatToContainer(this.pos)
this.$refs.infoWindow.$el.style.left = position.x + 'px'
this.$refs.infoWindow.$el.style.top = position.y + 'px'
})
預覽
這樣就差不多和官方的信息窗口差不多了。
代碼完善
接下來我們將代碼稍微完善以下,增加一個新功能,點擊地圖上的熱點的時候,出現信息窗口,顯示相應熱點的信息。
代碼
<template>
<div class="map">
<div id="GDMap"
v-loading="loading">
<info-window v-if="visible"
:pos="position"
:info="info"
@close="visible = false">
</info-window>
</div>
</div>
</template>
<script>
import loadMap from './loadMap'
import InfoWindow from './InfoWindow'
export default {
components: {
InfoWindow
},
data () {
return {
// 地圖實例
GDMap: null,
// 加載的插件
plugins: [
'AMap.OverView',
'AMap.MouseTool',
'AMap.PolyEditor',
'AMap.RectangleEditor',
'AMap.PlaceSearch',
'AMap.DistrictLayer',
'AMap.CustomLayer'
],
// key
key: 'c5eac55551560531336988396dacbf53',
// 地圖版本
v: '1.4.14',
loading: true,
pos: [116.397428, 39.90923],
position: {},
visible: false,
info: {}
}
},
mounted () {
loadMap(this.key, this.plugins, this.v)
.then(AMap => {
this.GDMap = new AMap.Map('GDMap', {
zoom: 11,
center: [116.397428, 39.90923]
})
// 地圖加載完成事件
this.GDMap.on('complete', () => {
this.loading = false
})
// 熱點點擊事件
this.GDMap.on('hotspotclick', ev => {
console.log(ev)
this.visible = true
this.pos = [ev.lnglat.lng, ev.lnglat.lat]
this.info = ev
})
})
.catch(() => {
this.loading = false
console.log('地圖加載失敗!')
})
},
methods: {
mapmove () {
this.position = this.GDMap.lngLatToContainer(this.pos)
}
},
watch: {
pos (newPos) {
this.position = this.GDMap.lngLatToContainer(newPos)
},
visible (newVisible) {
if (this.GDMap) {
if (newVisible) {
// 綁定地圖平移事件
this.GDMap.on('mapmove', this.mapmove)
} else {
// 移除事件綁定
this.GDMap.off('mapmove', this.mapmove)
}
}
}
}
}
</script>
<style>
#GDMap {
width: 1200px;
height: 500px;
position: relative;
}
</style>
信息窗口組件
<template>
<div class="info-window"
ref="infoWindow">
<div class="top">
<span class="title">{{ info.name }}</span>
<span class="close"
@click="handleClose">x</span>
</div>
<div class="content">
id:{{ info.id }}<br>
type:{{ info.type }}<br>
lnglat:{{ info.lnglat.lng }},{{ info.lnglat.lat }}<br>
</div>
<div class="footer">
by: liubing.me
</div>
</div>
</template>
<script>
export default {
props: {
// 像素座標
pos: Object,
// 窗口信息
info: Object,
// 信息窗口偏移
offset: {
type: Object,
default: () => {
return {
x: 0,
y: -20
}
}
}
},
methods: {
handleClose () {
this.$emit('close')
}
},
watch: {
pos: {
handler (newPos) {
if (newPos && newPos.x && newPos.y) {
this.$nextTick(() => {
const infoHeight = document.querySelector('.info-window').clientHeight
this.$refs.infoWindow.style.left = newPos.x + this.offset.x + 'px'
this.$refs.infoWindow.style.top = newPos.y - infoHeight / 2 + this.offset.y + 'px'
})
}
},
immediate: true
}
}
}
</script>
<style scoped>
.info-window {
position: absolute;
top: 50%;
left: 50%;
padding: 10px;
min-width: 300px;
border-radius: 5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
z-index: 10;
transform: translate(-50%, -50%);
}
.info-window::after {
content: "◆";
font-size: 36px;
height: 24px;
color: #ffffff;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.info-window .top {
padding-bottom: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.info-window .top .title {
font-size: 12px;
}
.info-window .top .close {
width: 24px;
font-size: 12px;
cursor: pointer;
}
.info-window .content {
text-align: left;
}
.info-window .footer {
font-size: 12px;
color: #cccccc;
text-align: right;
}
</style>
最終預覽
最後,大家可以根據這個法子自定義出自己的信息窗口組件,而不用受高德地圖信息窗口的約束了。