一、前言:Uni-app小程序用地图组件的常见坑

很多人用Uni-app开发小程序时,只要用到地图组件就会遇到各种奇怪问题:要么地图不显示,要么定位不准,要么选点后拿不到坐标。其实这些问题大多不是组件本身的bug,而是对小程序的地图规则不熟悉,或者Uni-app的配置没弄对。这篇内容就把最常见的几个问题拆解清楚,给大家讲明白怎么解决。

二、问题1:地图组件完全不显示,一片空白

2.1 问题表现

页面里写了标签,预览时只有一块空白区域,没有任何地图内容,控制台也没报错。

2.2 原因分析

小程序的地图组件是依赖微信的地图服务的,所以必须先开通对应的权限,还要在小程序的后台配置合法域名。很多人会忘这一步,或者配置错了域名。

2.3 解决步骤

首先要在微信公众平台的小程序后台开通“位置接口”权限,路径是:设置→第三方设置→插件管理→添加插件,找到“微信位置接口”开通。然后要配置合法域名,路径是:设置→开发设置→服务器域名,把“request合法域名”“downloadFile合法域名”“uploadFile合法域名”都加上https://apis.map.qq.com(因为Uni-app的地图组件默认用的是腾讯地图)。

配置完后台,还要在Uni-app的项目里配置manifest.json文件,找到“小程序”→“权限配置”,添加位置权限的配置。

2.4 完整示例

先配置manifest.json的小程序权限,代码如下:

{
  "mp-weixin": {
    "permission": {
      "scope.userLocation": {
        "desc": "需要获取您的位置信息用于展示地图"
      }
    },
    "requiredPrivateInfos": ["getLocation"]
  }
}

然后写页面的map组件代码,确保有高度(很多人忘写高度也会导致空白):

<template>
  <view class="map-container">
    <!-- 地图组件,必须设置高度,否则会显示空白 -->
    <map 
      class="map" 
      :longitude="longitude" 
      :latitude="latitude"
      :scale="14"
    ></map>
  </view>
</template>

<script>
export default {
  data() {
    return {
      longitude: 116.397228, // 北京天安门的经度,测试用
      latitude: 39.907565    // 北京天安门的纬度,测试用
    }
  }
}
</script>

<style>
.map-container {
  width: 100%;
  height: 500rpx; /* 必须设置容器高度,map组件的高度默认继承父容器 */
}
.map {
  width: 100%;
  height: 100%;
}
</style>

三、问题2:定位不准,或者拿不到用户真实位置

3.1 问题表现

要么定位的位置和实际位置差几公里,要么获取到的位置一直是默认的天安门,或者调用getLocation方法报错。

3.2 原因分析

第一个原因是权限配置的desc内容太短,微信要求desc必须不少于10个字,否则会被驳回;第二个原因是调用getLocation时没配置正确的参数,比如用了低精度的定位模式;第三个原因是开发时用的是微信开发者工具,工具的定位默认是北京,不是真实位置。

3.3 解决步骤

首先检查manifest.json里的desc内容,确保不少于10个字;然后调用getLocation时配置高精度模式;开发时要在微信开发者工具的“详情”→“本地设置”里把“不校验合法域名”打开,或者用真机预览测试真实定位。

3.4 完整示例

先修改manifest.json的desc,确保足够长:

{
  "mp-weixin": {
    "permission": {
      "scope.userLocation": {
        "desc": "需要获取您的位置信息用于展示周边地图和定位服务"
      }
    },
    "requiredPrivateInfos": ["getLocation"]
  }
}

然后写获取定位的代码,用高精度模式:

<template>
  <view class="map-container">
    <map 
      class="map" 
      :longitude="longitude" 
      :latitude="latitude"
      :scale="14"
    ></map>
    <button @click="getUserLocation">获取我的位置</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      longitude: 116.397228,
      latitude: 39.907565
    }
  },
  methods: {
    // 获取用户真实位置
    getUserLocation() {
      uni.getLocation({
        type: 'wgs84', // 高精度定位模式,不要用gcj02,除非是需要国测局坐标的场景
        success: (res) => {
          // 拿到真实的经纬度后更新地图
          this.longitude = res.longitude
          this.latitude = res.latitude
          console.log('我的位置:', res.longitude, res.latitude)
        },
        fail: (err) => {
          console.error('获取位置失败:', err)
          // 提示用户开启位置权限
          uni.showToast({
            title: '请开启位置权限',
            icon: 'none'
          })
        }
      })
    }
  }
}
</script>

<style>
.map-container {
  width: 100%;
  height: 500rpx;
}
.map {
  width: 100%;
  height: 100%;
}
button {
  margin: 20rpx;
  padding: 10rpx 20rpx;
  background-color: #007aff;
  color: white;
  border-radius: 10rpx;
}
</style>

四、问题3:选点后拿不到经纬度,或者经纬度无效

4.1 问题表现

很多小程序需要用户在地图上选点,比如外卖选收货地址、租房选位置,选完点后拿不到经纬度,或者拿到的经纬度是空的,或者用这个经纬度反查地址时出错。

4.2 原因分析

第一个原因是没给map组件绑定markers或者bindtap事件,导致选点时没有触发获取经纬度的逻辑;第二个原因是选点后拿到的经纬度是字符串类型,不是数字类型,直接用会出错;第三个原因是没处理选点时的坐标转换,比如用了wgs84坐标去反查地址,而反查接口需要gcj02坐标。

4.3 解决步骤

首先给map组件绑定bindtap事件,监听用户点击地图的行为;然后拿到经纬度后转成数字类型;如果需要反查地址,要把wgs84坐标转成gcj02坐标。

4.4 完整示例

先写选点的代码,包括坐标转换:

<template>
  <view class="map-container">
    <map 
      class="map" 
      :longitude="longitude" 
      :latitude="latitude"
      :scale="14"
      @tap="onMapTap" <!-- 绑定地图点击事件 -->
      :markers="markers" <!-- 显示选点的标记 -->
    ></map>
    <view class="tip">点击地图选点</view>
    <view class="result" v-if="selectedLocation">
      选点经度:{{ selectedLocation.longitude }}<br>
      选点纬度:{{ selectedLocation.latitude }}
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      longitude: 116.397228,
      latitude: 39.907565,
      markers: [], // 选点的标记数组
      selectedLocation: null // 选点的位置信息
    }
  },
  methods: {
    // 地图点击事件,用户选点
    onMapTap(e) {
      // 拿到点击位置的经纬度,注意是字符串类型,需要转成数字
      const longitude = Number(e.detail.longitude)
      const latitude = Number(e.detail.latitude)

      // 更新选点的标记,显示在地图上
      this.markers = [{
        id: 1,
        longitude: longitude,
        latitude: latitude,
        iconPath: '/static/marker.png', // 选点的图标,需要自己准备
        width: 30,
        height: 30
      }]

      // 保存选点的位置信息
      this.selectedLocation = {
        longitude: longitude,
        latitude: latitude
      }

      // 如果需要反查地址,把wgs84转成gcj02坐标
      this.convertToGcj02(longitude, latitude)
    },
    // 坐标转换:wgs84转gcj02
    convertToGcj02(wgsLon, wgsLat) {
      // 调用腾讯地图的坐标转换接口,需要申请key
      uni.request({
        url: 'https://apis.map.qq.com/ws/coord/v1/translate',
        method: 'GET',
        data: {
          locations: `${wgsLat},${wgsLon}`, // 注意接口要求的格式是纬度,经度
          type: 1, // 1表示wgs84转gcj02
          key: '你的腾讯地图key' // 替换成自己申请的key
        },
        success: (res) => {
          if (res.data.status === 0) {
            const gcjLocation = res.data.locations[0]
            console.log('转换后的gcj02坐标:', gcjLocation)
            // 可以用这个坐标去反查地址
            this.reverseGeocode(gcjLocation.lng, gcjLocation.lat)
          }
        }
      })
    },
    // 反查地址:根据经纬度获取详细地址
    reverseGeocode(lon, lat) {
      uni.request({
        url: 'https://apis.map.qq.com/ws/geocoder/v1/',
        method: 'GET',
        data: {
          location: `${lat},${lon}`, // 纬度,经度
          key: '你的腾讯地图key' // 替换成自己申请的key
        },
        success: (res) => {
          if (res.data.status === 0) {
            const address = res.data.result.address
            console.log('选点的详细地址:', address)
            uni.showToast({
              title: '选点成功:' + address,
              icon: 'none',
              duration: 3000
            })
          }
        }
      })
    }
  }
}
</script>

<style>
.map-container {
  width: 100%;
  height: 500rpx;
}
.map {
  width: 100%;
  height: 100%;
}
.tip {
  text-align: center;
  margin: 20rpx;
  color: #666;
}
.result {
  margin: 20rpx;
  padding: 20rpx;
  border: 1rpx solid #eee;
  border-radius: 10rpx;
}
</style>

五、问题4:地图组件的层级问题,被其他元素遮挡

5.1 问题表现

地图组件上面要放一个搜索框或者按钮,结果地图组件把搜索框挡住了,或者搜索框放在地图上面却显示不出来。

5.2 原因分析

小程序的原生组件(包括map、video、canvas等)的层级是最高的,会覆盖所有普通的view、text等元素,所以普通元素放在地图上面会被挡住。

5.3 解决步骤

第一种方法是用cover-view、cover-text这些专门用来放在原生组件上面的标签;第二种方法是把地图组件和普通元素分开,比如普通元素放在地图的下面,不要重叠;第三种方法是给地图组件设置z-index为-1,给普通元素设置更高的z-index,但这种方法在有些平台可能不生效。

5.4 完整示例

用cover-view放在地图上面的代码:

<template>
  <view class="map-container">
    <map 
      class="map" 
      :longitude="longitude" 
      :latitude="latitude"
      :scale="14"
    ></map>
    <!-- 用cover-view放在地图上面,不会被遮挡 -->
    <cover-view class="search-box">
      <cover-input type="text" placeholder="搜索地点" class="search-input"></cover-input>
      <cover-view class="search-btn">搜索</cover-view>
    </cover-view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      longitude: 116.397228,
      latitude: 39.907565
    }
  }
}
</script>

<style>
.map-container {
  width: 100%;
  height: 500rpx;
  position: relative; /* 父容器设置相对定位,方便子元素绝对定位 */
}
.map {
  width: 100%;
  height: 100%;
}
/* cover-view必须设置绝对定位,否则不会显示在地图上面 */
.search-box {
  position: absolute;
  top: 20rpx;
  left: 20rpx;
  right: 20rpx;
  display: flex;
  align-items: center;
  background-color: white;
  padding: 10rpx;
  border-radius: 10rpx;
}
.search-input {
  flex: 1;
  padding: 10rpx;
  border: none;
  outline: none;
}
.search-btn {
  padding: 10rpx 20rpx;
  background-color: #007aff;
  color: white;
  border-radius: 10rpx;
}
</style>

六、应用场景、优缺点、注意事项

6.1 应用场景

Uni-app的地图组件在小程序里的应用场景非常广,比如外卖小程序的收货地址选择、打车小程序的司机定位、旅游小程序的景点展示、房产小程序的房源位置展示、校园小程序的校园导航等等,只要需要用到位置信息和地图展示的场景,都可以用这个组件。

6.2 技术优缺点

优点:首先是跨平台,用Uni-app写一次代码,可以同时发布到微信、支付宝、百度等多个小程序平台,不用为每个平台单独写地图代码;其次是开发成本低,不用自己从零开发地图服务,直接用组件就能实现基础的地图展示、定位、选点功能;最后是维护方便,只要组件有更新,不用修改自己的代码就能获得新的功能。 缺点:首先是依赖小程序平台的地图服务,比如微信小程序只能用微信的地图服务,不能随便换其他地图服务;其次是原生组件的层级问题,处理起来比较麻烦;最后是定制化程度有限,比如要实现一些特殊的地图功能,比如热力图、轨迹回放,可能需要自己集成第三方的地图SDK,增加开发难度。

6.3 注意事项

第一,一定要配置好小程序后台的权限和合法域名,否则地图组件会无法正常工作;第二,获取定位时一定要用高精度模式,并且处理好权限被拒绝的情况,给用户友好的提示;第三,选点后拿到的经纬度要转成数字类型,并且注意坐标的类型,需要反查地址时要转成gcj02坐标;第四,地图组件上面的元素要用cover-view,避免被遮挡;第五,开发时尽量用真机测试,因为微信开发者工具的定位是模拟的,和真实环境有差异。

七、文章总结

Uni-app在小程序中使用地图组件的常见问题,大多是因为对小程序的规则不熟悉,或者配置、代码的细节没处理好。只要按照上面的步骤,把权限配置好,代码的细节处理到位,就能解决大部分问题。如果遇到更复杂的问题,可以去Uni-app的官方文档或者社区找解决方案,也可以自己集成第三方的地图SDK来实现更复杂的功能。