騰訊地圖開發填坑總結

前言:騰訊地圖分爲兩個版本,版本1是以Tmap爲標誌,鏈接爲https://map.qq.com/api/gljs?v=1.exp1爲主, 版本2是以qq.map爲標誌,鏈接爲https://map.qq.com/api/gljs?v=2.exp2爲主, 可以在導入了在線鏈接的頁面上使用window.T或者window.q找到其地圖,這是由於在導入了地圖鏈接後,地圖api將其掛載在了window上。 以版本1Tmap`爲例:

所以寫騰訊地圖時要特別注意自己用的騰訊地圖的版本,版本一與版本二兩者之間有很大區別,在看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)包裹(讀者也可以使用PromiseVue開發者可以使用$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一把嗦就完事了。

以上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章