uniapp引入qqmap-wx-jssdk实现微信小程序端获取用户当前位置

1. 安装与配置

复制代码
# 在项目根目录安装
npm install qqmap-wx-jssdk

创建配置文件 src/utils/qqmap.config.js

复制代码
export const QQ_MAP_KEY = '你的腾讯地图KEY' // 申请地址:https://lbs.qq.com/

// 错误码映射
export const ERROR_CODE = {
  '112': '应用key错误',
  '113': '应用未授权使用该功能',
  '300': '参数错误',
  '307': '请求量超限',
  '310': '签名错误',
  '311': '权限错误'
}

2. 创建地图工具类 src/utils/qqmap.js

javascript 复制代码
import QQMapWX from 'qqmap-wx-jssdk'
import { QQ_MAP_KEY, ERROR_CODE } from './qqmap.config.js'

class QQMapManager {
  constructor() {
    this.map = null
    this.init()
  }

  init() {
    if (!this.map) {
      this.map = new QQMapWX({
        key: QQ_MAP_KEY
      })
    }
  }

  // 获取当前地理位置
  getCurrentLocation() {
    return new Promise((resolve, reject) => {
      uni.getLocation({
        type: 'wgs84',
        altitude: true,
        success: async (res) => {
          try {
            const location = await this.reverseGeocoder(res.latitude, res.longitude)
            resolve({
              ...res,
              ...location
            })
          } catch (error) {
            reject(error)
          }
        },
        fail: (error) => {
          console.error('获取位置失败:', error)
          reject(this.handleError(error))
        }
      })
    })
  }

  // 逆地址解析(坐标->地址)
  reverseGeocoder(latitude, longitude) {
    return new Promise((resolve, reject) => {
      this.map.reverseGeocoder({
        location: {
          latitude,
          longitude
        },
        success: (res) => {
          resolve({
            address: res.result.address,
            formatted_address: res.result.formatted_addresses?.recommend || '',
            address_component: res.result.address_component,
            ad_info: res.result.ad_info,
            pois: res.result.pois || []
          })
        },
        fail: (error) => {
          reject(this.handleError(error))
        }
      })
    })
  }

  // 地址解析(地址->坐标)
  geocoder(address) {
    return new Promise((resolve, reject) => {
      this.map.geocoder({
        address,
        success: (res) => {
          resolve({
            latitude: res.result.location.lat,
            longitude: res.result.location.lng,
            ...res.result
          })
        },
        fail: (error) => {
          reject(this.handleError(error))
        }
      })
    })
  }

  // 关键词搜索
  search(keyword, options = {}) {
    return new Promise((resolve, reject) => {
      const params = {
        keyword,
        page_size: 20,
        page_index: 1,
        ...options
      }

      this.map.search({
        ...params,
        success: (res) => {
          resolve({
            data: res.data,
            count: res.count
          })
        },
        fail: (error) => {
          reject(this.handleError(error))
        }
      })
    })
  }

  // 获取两点距离
  calculateDistance(from, to) {
    return new Promise((resolve, reject) => {
      this.map.calculateDistance({
        from,
        to,
        success: (res) => {
          resolve(res.result.elements)
        },
        fail: (error) => {
          reject(this.handleError(error))
        }
      })
    })
  }

  // 错误处理
  handleError(error) {
    const code = error.status || error.code
    const message = ERROR_CODE[code] || error.message || '地图服务异常'
    
    return {
      code,
      message,
      originalError: error
    }
  }
}

// 单例模式
export default new QQMapManager()

3. 创建 Vue3 组合式函数 src/composables/useLocation.js

javascript 复制代码
import { ref, computed } from 'vue'
import qqmap from '@/utils/qqmap.js'

export function useLocation() {
  // 状态
  const location = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  // 计算属性
  const currentAddress = computed(() => {
    if (!location.value) return ''
    return location.value.formatted_address || 
           location.value.address || 
           '未知地址'
  })
  
  const currentCity = computed(() => {
    if (!location.value?.address_component) return ''
    return location.value.address_component.city || 
           location.value.address_component.province
  })
  
  // 获取当前位置
  const getLocation = async (options = {}) => {
    loading.value = true
    error.value = null
    
    try {
      // 检查权限
      const permission = await checkLocationPermission()
      if (!permission) {
        throw new Error('位置权限未授权')
      }
      
      // 获取位置
      const result = await qqmap.getCurrentLocation()
      
      // 合并额外配置
      location.value = {
        ...result,
        timestamp: Date.now(),
        ...options
      }
      
      return location.value
    } catch (err) {
      error.value = err.message || '获取位置失败'
      console.error('获取位置失败:', err)
      throw err
    } finally {
      loading.value = false
    }
  }
  
  // 检查权限
  const checkLocationPermission = () => {
    return new Promise((resolve) => {
      uni.getSetting({
        success: (res) => {
          const hasPermission = res.authSetting['scope.userLocation'] === true
          
          if (hasPermission) {
            resolve(true)
          } else {
            // 请求授权
            uni.authorize({
              scope: 'scope.userLocation',
              success: () => resolve(true),
              fail: () => {
                // 引导用户打开设置
                showPermissionGuide()
                resolve(false)
              }
            })
          }
        }
      })
    })
  }
  
  // 显示权限引导
  const showPermissionGuide = () => {
    uni.showModal({
      title: '位置权限申请',
      content: '需要获取您的位置信息,请前往设置开启位置权限',
      confirmText: '去设置',
      success: (res) => {
        if (res.confirm) {
          uni.openSetting()
        }
      }
    })
  }
  
  // 根据地址获取坐标
  const getLocationByAddress = async (address) => {
    loading.value = true
    try {
      const result = await qqmap.geocoder(address)
      return result
    } catch (err) {
      error.value = err.message
      throw err
    } finally {
      loading.value = false
    }
  }
  
  // 搜索地点
  const searchPlaces = async (keyword, options = {}) => {
    loading.value = true
    try {
      const result = await qqmap.search(keyword, options)
      return result
    } catch (err) {
      error.value = err.message
      throw err
    } finally {
      loading.value = false
    }
  }
  
  // 清除位置数据
  const clearLocation = () => {
    location.value = null
    error.value = null
  }
  
  return {
    // 状态
    location,
    loading,
    error,
    
    // 计算属性
    currentAddress,
    currentCity,
    
    // 方法
    getLocation,
    getLocationByAddress,
    searchPlaces,
    clearLocation,
    checkLocationPermission
  }
}

4. 页面组件使用示例pages/index/index.vue

html 复制代码
<template>
  <view class="container">
    <!-- 加载状态 -->
    <view v-if="loading" class="loading">
      <text>获取位置中...</text>
    </view>
    
    <!-- 错误提示 -->
    <view v-else-if="error" class="error">
      <text>{{ error }}</text>
      <button @click="handleRetry" size="mini">重试</button>
    </view>
    
    <!-- 位置信息 -->
    <view v-else-if="location" class="location-info">
      <view class="section">
        <text class="label">当前位置:</text>
        <text class="value">{{ currentAddress }}</text>
      </view>
      
      <view class="section">
        <text class="label">所在城市:</text>
        <text class="value">{{ currentCity }}</text>
      </view>
      
      <view class="section" v-if="location?.address_component">
        <text class="label">详细地址:</text>
        <text class="value">
          {{ location.address_component.province }}
          {{ location.address_component.city }}
          {{ location.address_component.district }}
          {{ location.address_component.street }}
        </text>
      </view>
      
      <view class="section">
        <text class="label">经纬度:</text>
        <text class="value">
          {{ location.latitude?.toFixed(6) }}, 
          {{ location.longitude?.toFixed(6) }}
        </text>
      </view>
      
      <!-- POI点 -->
      <view v-if="location.pois?.length" class="pois">
        <text class="label">附近地点:</text>
        <view v-for="poi in location.pois.slice(0, 5)" :key="poi.id" class="poi-item">
          <text>{{ poi.title }}</text>
          <text class="distance">{{ poi._distance }}米</text>
        </view>
      </view>
    </view>
    
    <!-- 操作按钮 -->
    <view class="actions">
      <button @click="handleGetLocation" type="primary" :loading="loading">
        获取当前位置
      </button>
      
      <button @click="handleClear" style="margin-top: 20rpx;">
        清除位置
      </button>
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useLocation } from '@/composables/useLocation.js'

const {
  location,
  loading,
  error,
  currentAddress,
  currentCity,
  getLocation,
  clearLocation
} = useLocation()

// 页面加载时获取位置
onMounted(() => {
  // 可以根据需要决定是否自动获取
  // handleGetLocation()
})

// 获取位置
const handleGetLocation = async () => {
  try {
    await getLocation({
      getPoi: true, // 获取周边POI点
      poi_options: {
        policy: 1 // 周边搜索策略
      }
    })
    
    uni.showToast({
      title: '获取位置成功',
      icon: 'success'
    })
  } catch (err) {
    uni.showToast({
      title: err.message || '获取位置失败',
      icon: 'error'
    })
  }
}

// 重试
const handleRetry = () => {
  handleGetLocation()
}

// 清除位置
const handleClear = () => {
  clearLocation()
  uni.showToast({
    title: '已清除位置',
    icon: 'success'
  })
}
</script>

<style scoped>
.container {
  padding: 40rpx;
}

.loading, .error {
  padding: 40rpx;
  text-align: center;
}

.error {
  color: #e64340;
}

.location-info {
  margin-bottom: 40rpx;
}

.section {
  margin-bottom: 20rpx;
  padding: 20rpx;
  background: #f5f5f5;
  border-radius: 10rpx;
}

.label {
  font-weight: bold;
  color: #333;
  margin-right: 10rpx;
}

.value {
  color: #666;
}

.pois {
  margin-top: 30rpx;
}

.poi-item {
  display: flex;
  justify-content: space-between;
  padding: 15rpx 0;
  border-bottom: 1rpx solid #eee;
}

.distance {
  color: #999;
  font-size: 24rpx;
}

.actions {
  margin-top: 40rpx;
}
</style>

5. 全局配置 manifest.json

html 复制代码
{
  "mp-weixin": {
    "appid": "你的小程序AppID",
    "requiredPrivateInfos": ["getLocation"],
    "permission": {
      "scope.userLocation": {
        "desc": "你的位置信息将用于位置展示"
      }
    }
  }
}

6. 使用注意事项

权限配置

在微信小程序管理后台,需要添加 request 合法域名:

html 复制代码
https://apis.map.qq.com
相关推荐
chéng ௹1 小时前
uniapp vue3 unipush2.0 调用系统通知功能流程
前端·vue.js·uni-app
2501_916008892 小时前
uni-app 上架到 App Store 的项目流程,构建、打包与使用开心上架(Appuploader)上传
android·ios·小程序·https·uni-app·iphone·webview
桐溪漂流2 小时前
微信小程序路由及 `EventChannel` 通信
微信小程序·小程序
00后程序员张2 小时前
怎么在 iOS 上架 App,从构建端到审核端的全流程协作解析
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_915918412 小时前
iOS 开发者工具全景指南,构建高效开发、调试与性能优化的多工具工作体系
android·ios·性能优化·小程序·uni-app·iphone·webview
多看书少吃饭15 小时前
小程序支持HTTP POST 流式接口吗?
网络协议·http·小程序
询问QQ:48773927818 小时前
CDB文件第0x2C位置存放温度阈值
小程序
vx_vxbs6618 小时前
【SSM高校普法系统】(免费领源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案
android·java·python·mysql·小程序·php·idea
吹水一流19 小时前
微信小程序页面栈:从一个 Bug 讲到彻底搞懂
前端·微信小程序