前言:騰訊地圖分為兩個版本,版本1是以Tmap為標誌,連結為
https://map.qq.com/api/gljs?v=1.exp的
1為主, 版本2是以
qq.map為標誌,連結為
https://map.qq.com/api/gljs?v=2.exp的
2為主, 可以在匯入了線上連結的頁面上使用
window.T或者
window.q找到其地圖,這是由於在匯入了地圖連結後,地圖api將其掛載在了
window上。 以版本1
Tmap`為例:
所以寫騰訊地圖時要特別注意自己用的騰訊地圖的版本,版本一與版本二兩者之間有很大區別,在看api
時也要注意字尾。
v1:
https://wemap.qq.com/Vis/JavascriptAPI/APIDoc/map
v2:
https://lbs.qq.com/webApi/javascriptV2/jsGuide/jsOverview
筆者在最開始使用的是
v1
版本,但是Modal
元件會顯示不正常的地圖,後來嘗試使用v2
版本,結果發現Modal
元件內根本無法回顯地圖,無奈之下只好選擇了v1
版本,通過Modal
元件本身的一些設定,實現了需求,另外,一些部落格上的關於使用騰訊地圖在彈窗上的實現,其彈窗都是自己實現的,程式碼不全,因而參考意義並不大。
貼貼,請留意Modal
自身的一些設定:
<Modal centered zIndex={1001} onCancel={() => closeMapModal && closeMapModal()} forceRender={true} getContainer={document.body} open={MapModalOpen} title='選擇位置' footer={false} width={1220}>
<div>
<div id='container' ref={mapRef} style={{ width: '100%', height: '500px', position: 'relative' }} />
<div>
</Modal>
然後就來到了坑2:
這是由於
Modal
元件的緣故。Modal
元件本身是可以有多次銷燬和顯示的,因而,假設要生成map
範例,要在生成範例外使用settiemout(fn)包裹(讀者也可以使用Promise
,Vue
開發者可以使用$nextTick
,總之,不能使用同步的方式生成map
範例)。
依然貼貼:
const initMap = useCallback(()=>{
// react中沒有nextTick,所以用了`setTimeout`
if(!isOpen)return
if(!mapRef.current)return
// 初始預設點
let tarLat = 39.984120
let tarLng = 116.307484
let initLatlng = new TMap.LatLng(tarLat, tarLng)
let myOptions = {
zoom: 12,
center: initLatlng,
offset: { // 中心點偏移
x: 0,
y: 100,
}
}
setTimeout(()=>{
// 引數1:容器範例,設定項
map = new TMap.Map(mapRef.current, myOptions)
})
},[isOpen])
v1
中要想實現關鍵字搜尋功能,可以使用Suggestion
類,想要使用Suggestion
類需要額外匯入service
此附加庫(注意,這個連結是替換動作,不是新增動作):
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=YOUR_KEY"></script>
// 外部必須要有setTimeout包裹,防止意外報錯
setTimeout(()=>{
// 如果不確認,可以在`new TMap`上檢視是否有service這個物件
let suggest = new TMap.service.Suggestion({
// 新建一個關鍵字輸入提示類
pageSize: 20, // 返回結果每頁條目數
region: '', // 限制城市範圍
regionFix: false, // 搜尋無結果時是否固定在當前城市
})
// keyword:關鍵詞 location :使用者經緯度
suggest.getSuggestions({ keyword: keyword, location: map.getCenter()}).then(res=>{
if (res.status === 0 && Array.isArray(res?.data) && res?.data.length > 0) {
// 這裡就拿到了資料
}
})
})
// res.data 子項範例,這是我們接下來操作的基礎:
{
"id": "8672089425561259711", // 這裡的id要記住,打點時要用到
"title": "山東大學(洪家樓校區)",
"address": "山東省濟南市歷城區洪家樓5號",
"category": "教育學校:大學",
"type": 0,
"_distance": 2429,
"location": {
"lat": 36.687334,
"lng": 117.068511,
"height": 0
},
"adcode": 370112,
"province": "山東省",
"city": "濟南市",
"district": "歷城區"
}
打點與批次打點,本質上是沒什麼區別的。都是通過範例化
MultiMarker
類來實現。為了避免頻繁生成marker
範例而造成效能損耗,我們可以在地圖範例生成後先初始化marker
範例,然後再在每次需要打點前先將上一次的marker
資料清空再生成本次的marker
。
因而,單個打點與批次打點的區別僅僅在於內部的geometries
的長度
剛剛講到初始化時要先範例化marker物件,其實 我們也應該將marker在最初時就定義,這個動作有點類似在vue的data中先賦初值
貼貼:
let marker
function MyMap(){
const initMap = useCallBack(()=>{
setTimeout(()=>{
if(!isOpen) return
// 此處為初始化地圖 略
// do init map
// 此處為初始化地圖 略
// 初始化marker
marker = new TMap.MultiMarker({
map: map,
styles: {
// `default`欄位指的是marker的樣式名,也可以寫多個,也可以寫別的
default: new TMap.MarkerStyle({
// 點標註的相關樣式
width: 34, // 寬度
height: 46, // 高度
anchor: { x: 17, y: 23 }, // 標註點圖片的錨點位置
src: pointImg, // 標註點圖片url或base64地址,不填則預設
color: '#ccc', // 標註點文字顏色
size: 16, // 標註點文字文字大小
direction: 'center', // 標註點文字文字相對於標註點圖片的方位
offset: { x: 0, y: -4 }, // 標註點文字文字基於direction方位的偏移屬性
strokeColor: '#fff', // 標註點文字描邊顏色
strokeWidth: 2, // 標註點文字描邊寬度
}),
},
geometries: [] // 這裡就是地圖上點的數量
})
})
},[isOpen])
}
不管是單個打點還是批次打點,筆者這邊的需求是打新的點前要先將上一次的點全部清空,因而,我們要先學習一下如何刪除點
// 新增點之前,先刪除點
// 此處拿得到marker範例是由於在init時就將new TMap.MultiMarker賦給了marker
// marker.getGeometries : 獲取當前marker物件有多少個點
// marker.remove([]) //remove方法用於刪除marker,根據內部的id,注意remove方法的引數是陣列格式
if (marker.getGeometries().length > 0) {
// 全部刪除,也可以使用remove([id])進行單個刪除
marker.remove(marker.getGeometries().map(v => v.id))
}
如果是以同一個marker物件下打點倒不用寫太多,甚至還想封裝一個函數...
/**
* markerMatter : marker物料資料,基於isBat ,
* list : 獲取到的總資料
* isBat是否批次,true則markerMatter為陣列態,false則為物件態
*/
const setMarker = (markerMatter, list, isBat = false)=>{
if (marker.getGeometries().length > 0) {
marker.remove(marker.getGeometries().map(v => v.id))
}
// 根據是否批次的標識isBat來判斷並生成geoList待傳陣列,geoList本質上就是marker中的geometries,本質上就是替換動作
const geoList = isBat ? list.map((item, i) => {
return ({
id: item.id,
styleId: 'marker',
position: new TMap.LatLng(item.location.lat, item.location.lng),
content: i + 1 + '', // 需求:批次打點時,要拿到搜尋的index項並標在marker上,但是單個的不需要。
properties: {
title: item.title
}
})
}) : [{
"id": markerMatter.id, // 非常重要,不填則刪不掉marker
"styleId": 'marker',
"position": new TMap.LatLng(markerMatter.location.lat, markerMatter.location.lng),
content: undefined, // 單個不需要填充索引內容
properties: {
title: markerMatter.title
}
}]
// !!!關鍵程式碼
marker.add(geoList)
}
彈窗與marker總是成對存在。
之前的需求是不同的marker上顯示不同的windowinfo
,需要多個windowinfo
存在,後來需求有變,就成了點選哪個搜尋項或者點選哪個marker
,對應的彈窗windwoinfo
就回顯出來,本質上渲染的還是同一個windowinfo
物件,所以我們依然可以使用同一個windowinfo
來實現此功能,同marker
。
// 首先依然要先外部定義infoWindow
let infoWindow
function MyMap(){
const initMap = () =>{
// 初始化地圖與範例化marker不再贅述
// 初始化地圖與範例化marker不再贅述
// 初始化info
info = new TMap.InfoWindow({
map: map,
enableCustom: true,
offset: { x: 0, y: -22 },// 設定偏移防止蓋住錨點
position: new TMap.LatLng(tarLat, tarLng) // 初始化
})
// 先呼叫一次關閉方法,防止出現多個
info.close()
}
}
基於需求:整個地圖動作過程中,僅僅會出現一個infowindow,因而也就只需要一個就夠了
基於需求:彈窗上要新增按鈕,傳遞事件
需求1是很簡單的,但是需求2是很麻煩的,筆者翻遍了國內外各大知名不知名網站也沒找到解決辦法,原因在於,info.setContent方法(setContent:用於設定表單內容)僅接受字串格式,可以使用模板字串,但是一般不能傳遞事件。
甚至又想封裝個函數
想了想還是要先把一些坑點說一下,讀者可以根據筆者的思路來做,也可以另闢蹊徑
// 設定表單資訊
// currentContent:當前選中的marker或者搜尋項,格式就是剛剛說的搜尋項的格式,不表;
// list: 就是總列表
const setWindowInfo = (currentContent, list) => {
// 首先先執行一次關閉方法,這是為了防止可能出現的彈窗異常。
info.close()
// 這裡是設定表單的內容,重點是要看下那個btn事件,它不能接受Antd的Button元件,但原生button的樣式又實在太醜,所以筆者直接將官網上Button元件的基礎樣式抄了下來,至於hover的樣式。。暫時不知道怎麼實現
//
info.setContent(`
<div style="position:relative">
<div style="text-align:left">${currentContent.title}</div>
<div style="text-align:left">${currentContent.address}</div>
<span><label>經度:</label> <span>${currentContent.location.lat}</span></span>
<span><label>緯度:</label><span>${currentContent.location.lng}</span></span>
<button style= "color: #fff; background-color: #1677ff; box-shadow: 0 2px 0 rgba(5, 145, 255, 0.1);cursor:pointer;height: 32px;
padding: 4px 15px; font-size: 14px;border:none;
border-radius: 6px;" data-id=${currentContent.id} id='btn'>新增</button>
</div>
`)
// 設定定位
info.setPosition(new TMap.LatLng(currentContent.location.lat,
currentContent.location.lng))
// 設定好內容和位置後,我們將它開啟
info.open()
/*
好的,到這裡,我們要仔細講一下原理了,原理是通過HTML5的自定義屬性,也就是data-xx的方式去給她設定唯一值,通過這個唯一值我們可以得知使用者究竟在哪個marker上點選了這個位置,換句話說,只要我們在點選時確定了這個唯一值,button的功能也就實現了
**/
// 原生dom方式拿到這個dom
let useLocationDom = document.getElementById('btn')
// 繫結事件
useLocationDom.addEventListener('click', function (e) {
// `dom.getAttribute`獲取繫結到data-xx的上的自定義屬性值
const checkedOpt = list.find(v => v.id === useLocationDom.getAttribute('data-id'))
// 業務邏輯,不表
// 你可以在這裡做任何你想做的...
// 業務邏輯,不表
message.success(`新增成功`)
// 此處用於功能完成後的一些樣式的優化
useLocationDom.disabled = true
useLocationDom.style.cursor = 'not-allowed'
useLocationDom.style.color = 'rgba(0, 0, 0, 0.25)'
useLocationDom.style.backgroundColor = 'rgba(0, 0, 0, 0.04)'
})
}
至於在搜尋項上點選使對應的點回顯彈窗,太過簡單,不表。
直接將選中項item跟總列表塞到這個setWindowInfo
裡就好了啊喂!
大概寫了這些,總的來說地圖還是蠻簡單,看著api一把嗦就完事了。
以上。