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