前言:騰訊地圖分爲兩個版本,版本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
時也要注意後綴。
地圖api地址:
v1:
https://wemap.qq.com/Vis/JavascriptAPI/APIDoc/map
v2:
https://lbs.qq.com/webApi/javascriptV2/jsGuide/jsOverview
同樣是v2的:
https://lbs.qq.com/javascript_v2/doc/map.html
騰訊地圖放置在彈窗組件上的表現
筆者在最開始使用的是
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:
window上已掛載地圖,但是提示找不到地圖
這是由於
Modal
組件的緣故。Modal
組件本身是可以有多次銷燬和顯示的,因而,假設要生成map
實例,要在生成實例外使用settiemout(fn)包裹(讀者也可以使用Promise
,Vue
開發者可以使用$nextTick
,總之,不能使用同步的方式在Modal
組件上生成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一把嗦就完事了。
以上。