Bootstrap

Vue项目引入腾讯地图,实现可以根据关键词搜出相关位置,并定位到该位置

1、首先要先注册腾讯地图,去控制台创建应用,拿到key。

新建应用之后,要先给应用配置额度,否则请求相关接口的时候会报额度已满等相关提示。

  1. 关键词输入提示
    请求url:https://apis.map.qq.com/ws/place/v1/suggestion
    参数key是必须的,keyword是必须的(即关键词)

  2. 逆地址解析
    请求url: https://apis.map.qq.com/ws/geocoder/v1/?location=
    参数key是必须的,location也是必须的(即经纬度),格式lat,lng

2、请求相关接口的时候,可能会出现跨域的问题

解决跨域,可以使用jsonp的请求方式解决

3、在index.html文件引入

<script charset="utf-8" src="https://wemapvis.map.qq.com/api/gljs/v2?v=1.exp&key=您申请的key值"></script>

当然了,你也可以选择异步加载的方式引入,参考文档https://wemap.qq.com/Vis/JavascriptAPI/APIGuide/overview/prepare

3、这个自定义map组件传入的参数格式及回调函数

// 参数
selectedValue: {
	location: {
    	lat: null,
        lng: null
    },
    address: null,
    province: null,
    city: null,
    district: null
},
/选择定位后的回调函数
getLocation(data) {
	this.selectedValue = {
    	location: {
        	lat: data.latitude || null,
          	lng: data.longitude || null
        },
        address: data.address || null,
        province: data.province || null,
        city: data.city || null,
        district: data.district || null
   }
   const temp = {
   		address: data.address,
        latitude: data.latitude,
        longitude: data.longitude
   }
   this.$set(this.form_tableData.tableData, this.editIndex, { ...this.form_tableData.tableData[this.editIndex], ...temp })
},

3、绘制地图,这是自定义的map组件

<template>
  <el-dialog
    v-if="dialogVisible"
    id="mapDialog"
    title="请选择详细地址"
    :visible.sync="dialogVisible"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
    custom-class="map-Dialog"
    width="40%"
    @close="onClose"
    @closed="onClosed"
  >
    <div class="well-map">
      <div class="selected-info">
        <div>
          <span class="label">所选地址:</span>
          <span>{{
            locData.address ? locData.address : selectedValue.address
          }}</span>
        </div>
      </div>
      <div v-clickoutside="handleBlur" class="search-row">
        <el-row class="input-wrap" :gutter="20">
          <el-col :span="20">
            <el-input
              v-model="searchKey"
              placeholder="请输入要搜索的地址"
              @click="handleFoucus"
              @input="handleSearch()"
            />
          </el-col>
          <el-col :span="2">
            <el-button type="primary" @click="handleSearch()">搜索</el-button>
          </el-col>
        </el-row>

        <ul v-show="showAddressList && addressList.length">
          <li
            v-for="(item, index) in addressList"
            :key="index"
            @click.stop="handleSelect(item)"
          >
            <span class="title">{{ item.title }}</span>
            <span class="other-info">{{ item.address }}</span>
          </li>
        </ul>
      </div>
      <div id="well-container" class="well-map-container" />
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button
        type="default"
        @click.native="dialogVisible = false"
      >取消</el-button>
      <el-button
        type="primary"
        @click.native="findLocation"
      >确定</el-button>
    </div>
  </el-dialog>
</template>

相关的样式


```html
<style lang="scss" scoped>
#mapDialog {
  ::v-deep .el-dialog {
    min-width: 1000px;
    min-height: 800px;
    margin-top: 8vh !important;
    .el-dialog__body {
      padding-top: 10px;
    }
  }
}
.well-map {
  line-height: normal;
  .search-row {
    position: relative;
    width: 100%;
    margin: 12px 0;
    z-index: 99009;

    .input-wrap {
      height: 32px;
      line-height: 32px;
      display: flex;

      > input {
        box-sizing: border-box;
        margin: 0;

        position: relative;
        width: 100%;
        height: 100%;
        padding: 4px 11px;
        color: rgba(0, 0, 0, 0.65);
        font-size: 14px;
        background-color: #fff;
        /*background-image: none;*/
        border: 1px solid #d9d9d9;
        border-radius: 4px 0 0 4px;
        transition: all 0.3s;
        border-right: none;

        &:hover {
          border-color: #40a9ff;
          border-right-width: 1px !important;
        }

        &:focus {
          border-color: #40a9ff;
          border-right-width: 1px !important;
          outline: 0;
          box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
        }
      }

      > button {
        padding: 0 15px;
        border-radius: 0 4px 4px 0;
        color: #fff;
        background: #1890ff;
        border-color: #1890ff;
        box-shadow: none;
        border: none;
        width: 82px;
        height: 100%;
        cursor: pointer;

        &:hover,
        &:focus {
          color: #fff;
          background: #40a9ff;
          border-color: #40a9ff;
          outline: none;
        }

        &:active {
          color: #fff;
          background: #096dd9;
          border-color: #096dd9;
          outline: none;
        }
      }
    }

    > ul {
      position: absolute;
      top: 100%;
      left: 0;
      width: 100%;
      background: rgba(252, 250, 250, 0.918);
      border: 1px solid #f1f1f1;
      font-size: 13px;
      color: #5a5a5a;
      max-height: 280px;
      overflow-y: auto;
      list-style: none;
      padding: 0;
      margin: 0;

      > li {
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
        width: 100%;
        border-bottom: 1px solid #f1f1f1;
        padding: 10px;
        margin: 0;
        cursor: pointer;

        &:hover {
          background: #eff6fd;
        }

        .title {
          display: block;
          line-height: normal;
          margin-bottom: 4px;
        }

        .other-info {
          font-size: 12px;
          color: #b9b9b9;
          display: block;
          line-height: normal;
        }
      }
    }
  }

  .well-map-container {
    width: 100%;
    height: 550px;
    margin-top: 20px;
  }

  .selected-info {
    background: #ecf5ff;
    padding: 10px 14px;
    color: #565656;
    font-size: 13px;

    > div {
      margin-bottom: 4px;

      .label {
        color: #757575;
      }

      &:last-child {
        margin-bottom: 0;
      }
    }
  }
}
</style>

相关的方法

<script>
/**
 * 函数:
 * 1. selected-change:当选中的点发生变化时,触发
 * **/

import { getLocaltion, codeToaddress } from '@/api/common'
import editOperMixins from '@/mixins/dialog/edit-oper'
import baseConfig from '@/config/base-config'
import $script from 'scriptjs'
import { debounce } from 'throttle-debounce'
export default {
  directives: {
    /**
     * 封装指令,监听点击非目标元素之外的dom
     * ***/
    clickoutside: {
      bind(el, binding, vnode) {
        function documentHandler(e) {
          // 这里判断点击的元素是否是本身,是本身,则返回
          if (el.contains(e.target)) {
            return false
          }
          // 判断指令中是否绑定了函数
          if (binding.expression) {
            // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
            binding.value(e)
          }
        }

        // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
        el.__vueClickOutside__ = documentHandler
        document.addEventListener('click', documentHandler)
      },
      update() {},
      unbind(el, binding) {
        // 解除事件监听
        document.removeEventListener('click', el.__vueClickOutside__)
        delete el.__vueClickOutside__
      }
    }
  },
  mixins: [editOperMixins],
  props: {
    selectedValue: {
      type: Object,
      required: false,
      default: () => {}
    }
  },
  data() {
    return {
      mapKey: baseConfig.TXMAP_KEY,
      mapUrl: baseConfig.TXMAP_URL,
      searchKey: '', // 搜索Key
      addressList: [], // 搜索结果
      map: null,
      markerLayer: null,
      showAddressList: false,

      locData: {
        longitude: '',
        latitude: '',
        address: ''
      }
    }
  },
  watch: {
    dialogVisible(val) {
      if (val) {
        this.$nextTick(() => {
          $script(`https://map.qq.com/api/js?v=2.exp&key=${this.mapKey}`, () => {
            $script(`https://map.qq.com/api/gljs?v=1.exp&key=${this.mapKey}`, () => {
              const { lat, lng } = this.selectedValue.location
              this.initMap(lat || 39.984120, lng || 116.307484)
              if (lat && lng) {
                this._drawPoint(lat, lng, false)
              }
            })
          })
        })
      } else {
        this.$emit('update:visible', false)
      }
    },
    locData: {
      handler(val) {
        // console.log(val)
        // this.resetPoint()
      },
      deep: true
    }
  },
  mounted() {

  },
  created() {
  },
  methods: {
    /**
     * 初始化地图
     * **/
    initMap(lat = 39.984120, lng = 116.307484) {
      // 初始化地图
      // 定义地图中心点坐标
      var center = new TMap.LatLng(lat, lng)
      this.map = new TMap.Map('well-container', {
        center: center, // 设置地图中心点坐标
        rotation: 20, // 设置地图旋转角度
        pitch: 0, // 设置俯仰角度(0~45)
        zoom: 16 // 设置地图缩放级别
      })

      // 初始化marker图层
      this.markerLayer = new TMap.MultiMarker({
        id: 'marker-layer',
        map: this.map
      })

      // 监听点击事件添加marker
      this.map.on('click', this._clickMap)
    },
    /**
     * 搜索框聚焦
     * **/
    handleFoucus(e) {
      this.showAddressList = true
    },
    /**
     * 搜索框失焦
     * **/
    handleBlur() {
      this.showAddressList = false
    },
    /**
     * 搜索框内容发生变化
     * debounc防抖函数
     * **/
    handleSearch: debounce(300, false, function() {
      this.showAddressList = true
      if (!this.searchKey) {
        this.addressList = []
      } else {
        getLocaltion({ keyword: this.searchKey, key: this.mapKey }).then((res) => {
          if (res.status === 0) {
            this.addressList = res.data
          } else {
            this.$message(res.message)
          }
        }).catch((err) => {
          console.log(err)
        })
      }
    }),

    async handleSelect(row) {
      // console.log(JSON.parse(JSON.stringify(row)))
      this.searchKey = row.title
      // searchKey发生变化了,需触发搜索
      this.handleSearch()
      // 比较两个地址,tempAdress为省市区拼接的
      const tempAdress = row.province + row.city + row.district + row.title
      this.locData.address = this.getAdress(tempAdress, row.address)
      this.locData.latitude = row.location.lat
      this.locData.longitude = row.location.lng
      this.locData.province = row.province
      this.locData.city = row.city
      this.locData.district = row.district
      // selectedValue发生变化了,需重新绘制点
      this._drawPoint(this.locData.latitude, this.locData.longitude, true)
      // 选中之后需触发失去焦点
      this.handleBlur()
    },

    /**
     * 根据经纬度在地图上标注
     * @param lat 纬度
     * @param lng 经度
     * @param isUpdateCenter 是否更新地图中心点,有以下三种情况:
     * 1. 外部传入的点信息发生变化时,需更新中心点
     * 2. 选中搜索列表的某一项时,需更新中心点
     * 3. 点击地图标记点时,不需要更新中心点
     * **/
    _drawPoint(lat, lng, isUpdateCenter) {
      // 先清空点
      this.markerLayer.setGeometries([])
      if (!lat || !lng) {
        return
      }
      // 更新地图中心位置
      if (isUpdateCenter) {
        this.map.setCenter(new TMap.LatLng(lat, lng))
      }
      this.markerLayer.add({
        position: new TMap.LatLng(lat, lng)
      })
    },

    /**
     * 点击地图
     * 1. 根据经纬度画点
     * 2, 根据经纬度逆向查询到地址详细信息
     * 3. 通知父组件
     * **/
    _clickMap(evt) {
      console.log('点击地图:', evt)
      this.searchKey = ''
      this._drawPoint(evt.latLng.lat, evt.latLng.lng, false)
      const location = evt.latLng.lat + ',' + evt.latLng.lng
      codeToaddress({ key: this.mapKey, location: location })
        .then((res) => {
          console.log('逆地址解析结果:', res)
          if (res.status === 0) {
            this.locData.address = res.result.address
            this.locData.longitude = evt.latLng.lng
            this.locData.latitude = evt.latLng.lat
            this.locData.province = res.result.ad_info.province
            this.locData.city = res.result.ad_info.city
            this.locData.district = res.result.ad_info.district
          } else {
            this.$message.error(res.message)
          }
        })
        .catch((err) => {
          console.log('逆地址解析失败', err)
        })
    },

    // 进入地图弹窗重新定位
    resetPoint() {
      if (this.locData.latitude && this.locData.longitude) {
        this._drawPoint(
          this.selectedValue.latitude,
          this.selectedValue.longitude,
          true
        )
      }
    },
    // 返回选中点的位置
    findLocation() {
      if (
        !this.locData.latitude &&
        !this.locData.latitude &&
        !this.locData.address
      ) {
        this.$message.warning('请选择地址')
      } else {
        this.$emit('getLocation', this.locData)
        this.dialogVisible = false
      }
    },

    /**
     * 根据经纬度在地图上标注
     * @param address1 地址1
     * @param address2 地址2
     * 1. 比较两个地址,返回其中一个
     * **/
    getAdress(address1, address2) {
      // console.log(address1, address2)
      if (address2.indexOf('号') > 0 && address2.indexOf('路') > 0) {
        return address2
      } else {
        return address1
      }
    }
  }
}
</script>

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;