• 腾讯地图开发填坑总结


    前言:腾讯地图分为两个版本,版本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 id='container' ref={mapRef} style={{ width: '100%', height: '500px', position: 'relative' }} />
    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此附加库(注意,这个链接是替换动作,不是添加动作):

    // 外部必须要有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(`
    ${currentContent.title}
    ${currentContent.address}
    ${currentContent.location.lat}
    ${currentContent.location.lng}
    padding: 4px 15px; font-size: 14px;border:none;
    border-radius: 6px;" data-id=${currentContent.id} id='btn'>添加
    `)
    // 设置定位
    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一把嗦就完事了。

    以上。

  • 相关阅读:
    java如何用drawString()绘制文字(三行代码)
    主机连接由虚拟机Linux搭建的redis,一步到胃,直接把坑踩完~
    4.3 x64dbg 搜索内存可利用指令
    【剑指 Offer II 032. 有效的变位词】
    SpringBoot集成Mybatis-Plus
    产品经理访谈 | 第五代验证码的创新与背景
    容器管理Rancher与容器监控
    【开发工具】idea 的全局搜索快捷键(Ctrl+shift+F)失效
    计算机两种体系结构及指令集
    PostgreSQL实战之物理复制和逻辑复制(二)
  • 原文地址:https://www.cnblogs.com/hjk1124/p/17337634.html