Vue实现功能齐全的地图组件 - 附效果图及源码

这是一个h5页面–

效果图

录图的时候网络条件不是很好,wifi卡了,所以数据加载有点慢。当网络情况不好的时候,这个组件还需要进一步的优化

前置条件

 <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=yourAK"></script>

功能点分解

  1. 分析数据
  2. 实现css布局
  3. 挂载百度map
  4. 定位当前位置
  5. 获取周边poi
  6. 搜索提示
  7. 点击右下角图标重新定位到当前位置
  8. 固定中心点,拖拽地图选择位置

分析数据

根据UI图分析组件需要用到哪些数据。

    data(){
      return {
        map:null,//实例化map
        searchValue:"",//搜索model
        currentAddress:"",//当前定位的地址
        point:{},//当前地址的经纬度
        poiKeyword:"",//周边的关键词,用来搜索,从adress中获取street拼接
        potentialLocation:[],//周边信息
      }
    }

css布局

这样的布局很简单,请随意发挥

挂载百度map

initMaps(){
        this.map = new BMap.Map("map");
        let mPoint = new BMap.Point(116.404, 39.915);//天安门
        this.map.centerAndZoom(mPoint,18);
       }

为了确保dom成功创建了,我们在nextTick中进行初始化

mounted(){
      this.$toast({text:"点击或拖动选址~",position:"top"});//提示信息
      this.$nextTick(()=>{
        this.initMaps();//调用初始化函数
      })

到这里我们应该能在idmap中的div里能够看到地图了

定位

我们调用百度提供的api进行定位,这里api定位的内部实现我猜测是基于h5的navigator。
在这里插入图片描述
根据警告提示,我们进去源码看看
在这里插入图片描述
警告信息说的是需要安全的origin,这里由于是在本地开发环境我们可以忽略这个警告。到了线上还是需要https的,不然遇到dns劫持也是很麻烦的。
好的,现在实现locate方法用于定位:

 locate(){
        let map = this.map;
        let geolocation = new BMap.Geolocation();
        const vm = this;
        geolocation.getCurrentPosition(function(r){
          if(this.getStatus() === BMAP_STATUS_SUCCESS){
            let mk = new BMap.Marker(r.point);
            map.addOverlay(mk);
            map.panTo(r.point);
          }else {
            console.log('failed ',this.getStatus());
          }
        });

这里我们就成功定位到了当前所在位置,当然别忘记了在mounted方法里调用这个方法了。

this.$nextTick(()=>{
        this.initMaps();
        this.locate();
      })

显示当前位置

刚刚在locate方法中获取到了当前的point值,也就是经纬度。那么如何显示地址信息呢?locate中回调函数参数r其实是包括了address信息的,但是!有时候address精确度只是到了城市级别–比如我这里console.log(r);打印出来的信息如下:
在这里插入图片描述

为了解决这个问题,我们需要从拿到的point对象入手,逆地址解析一次。
这里我们创建一个analyze函数调用百度的getLocation方法进行解析

      /**
       * Attention: 解析地址会有异常--有时候会解析正确,有时候只会解析到区
       * @param point lng and lat
       */
        analyze(point){//point:{lat:"",lng:""}
        const geoc = new BMap.Geocoder();
        geoc.getLocation(point, rs=>{
          this.point = rs.point;
          this.currentAddress = rs.address;
          this.poiKeyword = rs.street||rs.address;
        });
      },

并利用vue的数据响应相应的更新当前位置,当前经纬度,当前poiKeyword
poiKeyword有值了应该做什么呢?我们就可以开始根据point来搜索周边,提供周围的位置信息啦。
那么这里我们为poiKeyword创建一个监听器,进行实时操作。

    watch:{
      poiKeyword(n){
        this.getAroundPOI(["栋","店","小区","学校","餐饮",n]);//n就是最新值
      }
    }

实现getAroundPOI函数

在监听器中我们提供了poi的搜索关键词,于是我们可以调用百度地图LocalSearchsearchNearby函数。这里我们搜索周围方圆1000m的周边数据。

getAroundPOI(keyword){
        let map = this.map;
        let mPoint = new BMap.Point(this.point.lng, this.point.lat);
        let vm = this;
        let local =  new BMap.LocalSearch(map, {
          onSearchComplete(results){
            if (local.getStatus() === BMAP_STATUS_SUCCESS){
              let temp = [];
              results.forEach(item=>{
                temp = temp.concat(item.Ar);
              });
              vm.potentialLocation = temp;//更新ui
            }else{
              console.warn("get poi error ,code -> ",local.getStatus());
            }
          }
        });
        local.searchNearby(keyword,mPoint,1000);
      },

onSearchComplete回调中更新potentialLocation的值之后vue会帮助我们更新这部分ui

搜索提示

百度地图的搜索提示其实很简单,只需要创建一个自动装填对象。搜索下拉提示框会根据你传入的input参数(input id)进行定位,宽度与input相同。

getSuggestion(){
        let ac = new BMap.Autocomplete({"input" : "suggestId","location" : this.map});
        ac.addEventListener("onconfirm", e=> {
          let _value = e.item.value;
          this.searchValue = _value.province + _value.city + _value.district + _value.street + _value.business;
          this.setPlace(this.searchValue);
        });
      },

在上述代码中,我们定义了自动装填对象的监听器(点击下拉列表项时触发),并更新了input的值,在vue中我们一般用v-model进行双向绑定。
获取到了地址信息之后,我们调用了setPlace方法并传入了搜索值。
setPlace方法主要用作给地图重定位,移动地图中心到搜索值所在地点。

 setPlace(val){
        let map = this.map;
        map.clearOverlays();
        const vm = this;
        let local = new BMap.LocalSearch(map, {
          onSearchComplete(){
            let pp = local.getResults().getPoi(0).point;
            map.centerAndZoom(pp, 18);
            map.addOverlay(new BMap.Marker(pp));
            vm.analyze(pp);
          }
        });
        local.search(val);
      }

同时,注意在上面的代码中我们调用了analyze方法,用来更新周边信息。为什么能更新呢,忘了就往上翻回去看看analyze方法是怎么实现的吧。

重定位及拖拽中心

为了实现这个需求,首先我们要想办法地图上的自定义控件。自定义控件很麻烦,而且也会影响网页性能,所以我们应该另辟蹊径。论坛上其实有更简单的方法,这里我将论坛中的思路实现了。
关键就在于将控件看成网页的dom去相对于百度canvas地图进行定位。
看一下dom结构:

<div class="map-wrapper">
      <div id="map"></div>
      <img class="position" src="../../assets/icon/position.svg" alt="position">
      <img class="nowposition" @click="locate" src="../../assets/icon/nowposition.svg" alt="nowposition">
    </div>

以及css

 .map-wrapper{
      height: 50%;
      position: relative;

      #map{
        height: 100%;
      }
      img{
        width: 32px;
        object-fit: contain;
      }
      .position{
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-75%);//The bottom of the icon is centered,75  = 50(center) + 25(top)
        z-index: 100;
      }
      .nowposition{
        position: absolute;
        right: 20px;
        bottom:20px;
        z-index: 100;
      }
      }

这里要注意的一点就是利用position以及translate(-50%,-50%)居中以后为什么要上调25%?因为中间的图标是尖尖的,肉眼更倾向于利用图标的底部(那个尖尖)进行定位。我们给它的样式是正方形,并且是contain的,所以x轴是正好居中的不用管。但是y轴的问题是这样做仅仅是将y的中心点放在地图的中心位置,会有一点点的纬度偏差。我们应该与肉眼保持一致,因此需要将底部位于地图的正中心。因此我们还要上调25%。(自身高度的一半,因为就剩一半了)

为了能够让中心点定位,我们还需要给map注册一个监听器。
initMaps方法中加上一个监听器。

 this.map.addEventListener('dragend',()=>{
          let pixel = this.map.pointToOverlayPixel(this.map.getCenter());
          let point = this.map.overlayPixelToPoint({x:pixel.x,y:pixel.y});
          this.analyze(point);
        })

在方法的回调里再调用analyze方法重新获取poi,实时更新周边位置信息。

源码

<template>
  <div class="ys-map">

    <div class="map-wrapper">
      <div id="map"></div>
      <img class="position" src="../../assets/icon/position.svg" alt="position">
      <img class="nowposition" @click="locate" src="../../assets/icon/nowposition.svg" alt="nowposition">
    </div>

    <div id="tips">
      <AddressItem :title="'当前位置'" :address="currentAddress" :extra="'(以图上标记位置为准)'"/>
      <AddressItem v-for="(item,index) in potentialLocation" v-bind:key="index" :title="item.title" :address="item.address" @click.native="selectAddress(item)"/>
      <div v-if="potentialLocation.length===0">{{point.lng}},{{point.lat}}</div>
    </div>

    <div class="ys-search-address">
      <img class="back" src="../../assets/icon/back.svg" alt="back" @click="onBackClick">
      <div class="ys-search-wrapper">
        <input type="text" v-model="searchValue" title="" id="suggestId" placeholder="定位不准?试试手动输入">
        <img src="../../assets/icon/close.svg" alt="search" @click="searchValue=''">
      </div>
      <a class="okBtn" href="javascript:;" @click="onOkClick">确定</a>
    </div>

    <div class="search-tips" id="result">
      tips
    </div>
  </div>
</template>

<script>
  import AddressItem from "./AddressItem";
  export default {
    name: "Map",
    components: {AddressItem},
    mounted(){
      this.$toast({text:"点击或拖动选址~",position:"top"});
      this.$nextTick(()=>{
        this.initMaps();
        this.locate();
        this.getSuggestion();
      })
    },
    methods:{
      initMaps(){
        this.map = new BMap.Map("map");
        let mPoint = new BMap.Point(116.404, 39.915);//Tiananmen Square
        this.map.centerAndZoom(mPoint,18);
        this.map.addEventListener("click", this.onMapClicked);
        this.map.addEventListener('dragend',()=>{
          let pixel = this.map.pointToOverlayPixel(this.map.getCenter());
          let point = this.map.overlayPixelToPoint({x:pixel.x,y:pixel.y});
          this.analyze(point);
        })
      },
      locate(){
        let map = this.map;
        let geolocation = new BMap.Geolocation();
        const vm = this;
        geolocation.getCurrentPosition(function(r){
          if(this.getStatus() === BMAP_STATUS_SUCCESS){
            let mk = new BMap.Marker(r.point);
            map.addOverlay(mk);
            map.panTo(r.point);
            vm.analyze(r.point);
          }else {
            console.log('failed ',this.getStatus());
          }
        });
        //loading--
      },
      getAroundPOI(keyword){
        let map = this.map;
        let mPoint = new BMap.Point(this.point.lng, this.point.lat);//h5 112.983323,28.141431
        let vm = this;
        let local =  new BMap.LocalSearch(map, {
          onSearchComplete(results){
            if (local.getStatus() === BMAP_STATUS_SUCCESS){
              let temp = [];
              results.forEach(item=>{
                temp = temp.concat(item.Ar);
              });
              vm.potentialLocation = temp;
            }else{
              console.warn("get poi error ,code -> ",local.getStatus());
            }
          }
        });
        local.searchNearby(keyword,mPoint,1000);
      },
      /**
       * Attention: 解析地址会有异常--有时候会解析正确,有时候只会解析到区
       * @param point lng and lat
       */
      analyze(point){//point:{lat:"",lng:""}
        const geoc = new BMap.Geocoder();
        geoc.getLocation(point, rs=>{
          this.point = rs.point;//===r.point
          this.currentAddress = rs.address;
          this.poiKeyword = rs.street||rs.address;
        });
      },
      /**
       * search tips
       */
      getSuggestion(){
        let ac = new BMap.Autocomplete({"input" : "suggestId","location" : this.map});
        ac.addEventListener("onconfirm", e=> {
          let _value = e.item.value;
          this.searchValue = _value.province + _value.city + _value.district + _value.street + _value.business;
          this.setPlace(this.searchValue);
        });
      },
      selectAddress(item){
        //todo: return item the same as onOkClicked function
      },
      setPlace(val){
        let map = this.map;
        map.clearOverlays();
        const vm = this;
        let local = new BMap.LocalSearch(map, {
          onSearchComplete(){
            let pp = local.getResults().getPoi(0).point;
            map.centerAndZoom(pp, 18);
            map.addOverlay(new BMap.Marker(pp));
            vm.analyze(pp);
          }
        });
        local.search(val);
      },
      onMapClicked(e){
        console.log(e);//todo: get point with dialog and return
      },
      onBackClick(){
        //todo: cancel
      },
      onOkClick(){
        //todo: get this address and return.
        console.log("已选中当前位置!");
        console.log(this.currentAddress,this.point)
      }
    },
    data(){
      return {
        map:null,
        searchValue:"",
        currentAddress:"",
        point:{},
        poiKeyword:"",
        potentialLocation:[],
      }
    },
    watch:{
      poiKeyword(n){
        this.getAroundPOI(["栋","店","小区","学校","餐饮",n]);
      }
    }
  }
</script>

<style lang="less" scoped>
  .ys-map{
    height: 100%;
    overflow: hidden;
    position: relative;
    .map-wrapper{
      height: 50%;
      position: relative;

      #map{
        height: 100%;
      }
      img{
        width: 32px;
        object-fit: contain;
      }
      .position{
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-75%);//The bottom of the icon is centered,75  = 50(center) + 25(top)
        z-index: 100;
      }
      .nowposition{
        position: absolute;
        right: 20px;
        bottom:20px;
        z-index: 100;
      }

    }
    #tips{
      width: 100%;
      height: 50%;
      overflow-y: scroll;
      position: relative;
      padding: 1px 12px;
    }
    .ys-search-address{
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 50px;

      background: #f2f2f2;
      padding: 8px 0;
      display: flex;
      justify-content: space-around;
      align-items: center;

      .back{
        width: 20px;
        object-fit: contain;
      }
      .okBtn{
        background: #FFDF5C;
        padding: 5px 12px;
        border: 1px solid #ffcf6e;
        color: #333;
        border-radius: 8px;
      }
      .ys-search-wrapper{
        width: 68%;
        border-radius: 20px;
        background: rgba(0, 0, 0, .06);

        display: flex;
        align-items: center;
        input{
          padding: 8px 12px;
          background: transparent;

          width: 90%;
          height: 100%;
        }

        img{
          width: 12px;
          object-fit: contain;
        }
      }
    }
    .search-tips{
      position: absolute;
      left: 0;
      top: 50px;
      background: rgba(255,255,255,.8);
      border:1px solid #C0C0C0;
      height:auto;
      display:none;
    }
  }
</style>

  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值