概述
本文档介绍如何在vue3+ts的芋道源码后台管理项目中集成和使用腾讯地图组件,包括位置选择、位置查看、静态地图显示等功能。
腾讯地图标记组件的隐藏参数: showDownload
: 是否显示下载按钮 (0-否,1-是),此参数为隐藏参数,不在文档里显示,referer设为dianping也会隐藏下载按钮。
环境配置
1. 获取腾讯地图API密钥
-
访问 腾讯位置服务控制台
-
创建应用并获取API密钥
-
在系统配置中设置
tencentLbsKey
2. 域名配置
在小程序或Web应用中,需要将以下域名添加到白名单:
-
apis.map.qq.com
-
lbs.qq.com
基础用法
1. 位置选择组件
用于让用户在地图上选择位置并获取经纬度坐标。
xml
<template>
<el-dialog v-model="mapDialogVisible" title="选择位置" append-to-body>
<IFrame class="h-609px" :src="locPickerUrl" />
</el-dialog>
</template>
<script setup>
import * as ConfigApi from '@/api/mall/trade/config'
const mapDialogVisible = ref(false)
const locPickerUrl = ref('')
const selectedLocation = ref({
latitude: null,
longitude: null,
address: ''
})
// 初始化选点组件
const initLocationPicker = async () => {
const data = await ConfigApi.getTradeConfig()
const key = data.tencentLbsKey
locPickerUrl.value = `https://apis.map.qq.com/tools/locpicker?type=1&key=${key}&referer=myapp`
}
// 处理位置选择结果
const selectAddress = (loc) => {
if (loc.poiname === '我的位置') {
ElMessage.warning('请移动地图选择位置')
return
}
selectedLocation.value = {
latitude: loc.latlng?.lat,
longitude: loc.latlng?.lng,
address: loc.poiname
}
mapDialogVisible.value = false
}
// 监听地图消息
onMounted(() => {
window.selectAddress = selectAddress
window.addEventListener('message', (event) => {
const loc = event.data
if (loc && loc.module === 'locationPicker') {
window.parent.selectAddress(loc)
}
}, false)
initLocationPicker()
})
</script>
2. 位置查看组件
用于显示已知位置的标记点。
xml
<template>
<el-dialog v-model="mapDialogVisible" title="查看位置" append-to-body>
<IFrame class="h-609px" :src="poiMarkerUrl" />
</el-dialog>
</template>
<script setup>
const props = defineProps({
latitude: Number,
longitude: Number,
title: String,
address: String
})
const mapDialogVisible = ref(false)
const poiMarkerUrl = ref('')
// 生成标记地图URL
const generatePoiMarkerUrl = async () => {
if (!props.latitude || !props.longitude) return
const data = await ConfigApi.getTradeConfig()
const key = data.tencentLbsKey
poiMarkerUrl.value = `https://apis.map.qq.com/tools/poimarker?type=0&marker=coord:${props.latitude},${props.longitude};title:${encodeURIComponent(props.title || '位置')};addr:${encodeURIComponent(props.address || '地址')};&key=${key}&referer=myapp`
}
// 打开地图
const openMap = () => {
if (!props.latitude || !props.longitude) {
ElMessage.warning('暂无位置信息')
return
}
generatePoiMarkerUrl()
mapDialogVisible.value = true
}
defineExpose({ openMap })
</script>
3. 静态地图组件
用于显示静态地图图片,无交互功能。
xml
<template>
<div class="static-map">
<img
:src="staticMapUrl"
alt="位置地图"
class="w-full h-400px object-cover rounded"
@error="handleMapError"
/>
<div class="map-info">
<p><strong>地址:</strong>{{ address }}</p>
<p><strong>坐标:</strong>{{ longitude }}, {{ latitude }}</p>
</div>
</div>
</template>
<script setup>
const props = defineProps({
latitude: Number,
longitude: Number,
address: String
})
const staticMapUrl = ref('')
// 生成静态地图URL
const generateStaticMapUrl = async () => {
if (!props.latitude || !props.longitude) return
const data = await ConfigApi.getTradeConfig()
const key = data.tencentLbsKey
const markers = `markers=size:large|color:red|${props.latitude},${props.longitude}`
const center = `center=${props.latitude},${props.longitude}`
const zoom = 'zoom=16'
const size = 'size=750x400'
const format = 'format=png'
staticMapUrl.value = `https://apis.map.qq.com/ws/staticmap/v2/?${center}&${zoom}&${size}&${markers}&${format}&key=${key}`
}
const handleMapError = () => {
ElMessage.error('地图加载失败')
}
watch([() => props.latitude, () => props.longitude], () => {
generateStaticMapUrl()
}, { immediate: true })
</script>
<style scoped>
.map-info {
margin-top: 16px;
padding: 16px;
background-color: #f8f9fa;
border-radius: 8px;
}
.map-info p {
margin: 8px 0;
font-size: 14px;
color: #606266;
}
</style>
API参考
腾讯地图API端点
1. 选点组件 (LocationPicker)
官网链接:地图组件 | 腾讯位置服务
apis.map.qq.com/tools/locpi...
参数说明:
-
type
: 组件类型,1为选点组件 -
key
: 腾讯地图API密钥 -
referer
: 调用来源标识,可填写任意字符串 (myapp) -
coord_type
: 坐标类型(可选) -
policy
: 显示策略(可选)
编辑
示例:
bash
https://apis.map.qq.com/tools/locpicker?type=1&key=YOUR_KEY&referer=myapp
2. 标记组件 (PoiMarker)
官网链接:地图组件 | 腾讯位置服务
apis.map.qq.com/tools/poima...
参数说明:
-
type
: 组件类型,0为标记组件 -
marker
: 标记点信息,格式:coord:纬度,经度;title:标题;addr:地址
-
key
: 腾讯地图API密钥 -
referer
: 调用来源标识,建议为myapp(dianping、meituan、qunhuodong、wexinmp_profile、parking、myapp)
- showDownload
: 是否显示下载按钮 (0-否,1-是),此参数为隐藏参数,不在文档里显示,referer设为dianping也会隐藏下载按钮。
如果想隐藏去这里跟查看周边按钮,只需要用白色元素覆盖就行
编辑
示例:
ini
https://apis.map.qq.com/tools/poimarker?type=0&marker=coord:39.908823,116.39747;title:天安门;addr:北京市东城区&key=YOUR_KEY&referer=myapp
3. 静态地图 (StaticMap)
apis.map.qq.com/ws/staticma...
参数说明:
-
center
: 地图中心点,格式:纬度,经度
-
zoom
: 缩放级别,1-18 -
size
: 图片尺寸,格式:宽x高
-
markers
: 标记点,格式:size:large|color:red|纬度,经度
-
format
: 图片格式,png/jpg -
key
: 腾讯地图API密钥
编辑
示例:
ruby
https://apis.map.qq.com/ws/staticmap/v2/?center=39.908823,116.39747&zoom=16&size=750x400&markers=size:large|color:red|39.908823,116.39747&format=png&key=YOUR_KEY
组件示例
完整的位置管理组件
以下是一个完整的位置管理组件示例,包含选择、查看、编辑等功能:
xml
<template>
<div class="location-manager">
<!-- 位置信息显示 -->
<el-form-item label="商户位置" prop="address">
<el-input
v-model="formData.address"
:disabled="isDetail"
placeholder="请输入详细地址"
class="w-120! mr-2"
/>
<el-button
type="primary"
v-if="!isDetail"
@click="openLocationPicker"
>
获取经纬度
</el-button>
<el-button
type="primary"
v-else
@click="openLocationViewer"
:disabled="!hasLocation"
>
查看位置
</el-button>
</el-form-item>
<!-- 经纬度信息显示 -->
<el-form-item label="经纬度信息" v-if="hasLocation">
<div class="location-info">
<div class="location-item">
<span class="label">经度:</span>
<span class="value">{{ formData.longitude }}</span>
</div>
<div class="location-item">
<span class="label">纬度:</span>
<span class="value">{{ formData.latitude }}</span>
</div>
<div class="location-actions">
<el-button type="text" size="small" @click="copyLocation">
复制坐标
</el-button>
<el-button type="text" size="small" @click="openExternalMap">
外部地图查看
</el-button>
</div>
</div>
</el-form-item>
<!-- 地图弹窗 -->
<el-dialog
v-model="mapDialogVisible"
:title="dialogTitle"
append-to-body
width="800px"
>
<div class="map-wrapper">
<IFrame class="h-609px" :src="mapUrl" />
<!-- 按钮遮罩层 -->
<div class="map-overlay">
<div class="button-mask bottom-right"></div>
<div class="button-mask top-right"></div>
<div class="button-mask bottom-left"></div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import * as ConfigApi from '@/api/mall/trade/config'
const props = defineProps({
modelValue: {
type: Object,
default: () => ({
address: '',
latitude: null,
longitude: null
})
},
isDetail: {
type: Boolean,
default: false
},
title: {
type: String,
default: '位置'
}
})
const emit = defineEmits(['update:modelValue'])
const formData = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const mapDialogVisible = ref(false)
const mapUrl = ref('')
const tencentKey = ref('')
// 计算属性
const hasLocation = computed(() =>
formData.value.latitude && formData.value.longitude
)
const dialogTitle = computed(() =>
props.isDetail ? '查看位置' : '选择位置'
)
// 初始化腾讯地图配置
const initTencentMap = async () => {
const data = await ConfigApi.getTradeConfig()
tencentKey.value = data.tencentLbsKey
}
// 打开位置选择器
const openLocationPicker = async () => {
mapUrl.value = `https://apis.map.qq.com/tools/locpicker?type=1&key=${tencentKey.value}&referer=myapp`
mapDialogVisible.value = true
}
// 打开位置查看器
const openLocationViewer = async () => {
if (!hasLocation.value) {
ElMessage.warning('暂无位置信息')
return
}
const { latitude, longitude, address } = formData.value
mapUrl.value = `https://apis.map.qq.com/tools/poimarker?type=0&marker=coord:${latitude},${longitude};title:${encodeURIComponent(props.title)};addr:${encodeURIComponent(address)};&key=${tencentKey.value}&referer=myapp`
mapDialogVisible.value = true
}
// 处理位置选择结果
const selectAddress = (loc) => {
if (loc.poiname === '我的位置') {
ElMessage.warning('请移动地图选择位置')
return
}
formData.value = {
...formData.value,
latitude: loc.latlng?.lat,
longitude: loc.latlng?.lng,
address: loc.poiname
}
mapDialogVisible.value = false
ElMessage.success('位置选择成功')
}
// 复制坐标
const copyLocation = async () => {
const locationText = `${formData.value.longitude},${formData.value.latitude}`
try {
await navigator.clipboard.writeText(locationText)
ElMessage.success('坐标已复制到剪贴板')
} catch (err) {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = locationText
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
ElMessage.success('坐标已复制到剪贴板')
}
}
// 在外部地图中查看
const openExternalMap = () => {
const { longitude, latitude, address } = formData.value
const url = `https://uri.amap.com/marker?position=${longitude},${latitude}&name=${encodeURIComponent(address || props.title)}`
window.open(url, '_blank')
}
// 初始化
onMounted(() => {
// 设置全局回调函数
window.selectAddress = selectAddress
// 监听地图消息
window.addEventListener('message', (event) => {
const loc = event.data
if (loc && loc.module === 'locationPicker') {
window.parent.selectAddress(loc)
}
}, false)
initTencentMap()
})
</script>
<style scoped>
.location-info {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
background-color: #f8f9fa;
border-radius: 6px;
border: 1px solid #e4e7ed;
}
.location-item {
display: flex;
align-items: center;
font-size: 14px;
}
.location-item .label {
font-weight: 500;
color: #606266;
min-width: 50px;
}
.location-item .value {
color: #303133;
font-family: 'Courier New', monospace;
background-color: #fff;
padding: 2px 6px;
border-radius: 3px;
border: 1px solid #dcdfe6;
}
.location-actions {
display: flex;
gap: 8px;
margin-top: 4px;
}
.map-wrapper {
position: relative;
width: 100%;
height: 609px;
overflow: hidden;
}
.map-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 10;
}
.button-mask {
position: absolute;
background-color: rgba(255, 255, 255, 0.9);
pointer-events: auto;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.button-mask.bottom-right {
bottom: 20px;
right: 20px;
width: 80px;
height: 40px;
}
.button-mask.top-right {
top: 20px;
right: 20px;
width: 120px;
height: 40px;
}
.button-mask.bottom-left {
bottom: 20px;
left: 20px;
width: 60px;
height: 40px;
}
</style>
常见问题
1. 地图无法加载
问题描述: 地图组件显示空白或加载失败
解决方案:
-
检查API密钥是否正确配置
-
确认域名是否已添加到白名单
-
检查网络连接是否正常
-
验证API密钥是否有足够的调用配额
javascript
// 检查API密钥配置
const checkApiKey = async () => {
try {
const data = await ConfigApi.getTradeConfig()
if (!data.tencentLbsKey) {
console.error('腾讯地图API密钥未配置')
return false
}
return true
} catch (error) {
console.error('获取API密钥失败:', error)
return false
}
}
2. 位置选择回调不触发
问题描述: 用户选择位置后,回调函数没有执行
解决方案:
-
确保全局回调函数正确设置
-
检查消息监听器是否正确配置
-
验证iframe通信是否被浏览器安全策略阻止
javascript
// 正确的回调函数设置
onMounted(() => {
// 设置全局回调函数
window.selectAddress = selectAddress
// 监听消息事件
window.addEventListener('message', (event) => {
const loc = event.data
if (loc && loc.module === 'locationPicker') {
// 确保调用父窗口的回调函数
if (window.parent && window.parent.selectAddress) {
window.parent.selectAddress(loc)
}
}
}, false)
})
3. 地图显示位置不准确
问题描述: 地图显示的位置与实际位置不符
解决方案:
-
检查经纬度数据是否正确
-
确认坐标系统是否一致(WGS84、GCJ02等)
-
验证经纬度的顺序是否正确
javascript
// 坐标验证函数
const validateCoordinates = (lat, lng) => {
// 检查纬度范围 (-90 到 90)
if (lat < -90 || lat > 90) {
console.error('纬度超出有效范围:', lat)
return false
}
// 检查经度范围 (-180 到 180)
if (lng < -180 || lng > 180) {
console.error('经度超出有效范围:', lng)
return false
}
return true
}
4. 按钮遮罩不生效
问题描述: 地图上的"去这里"等按钮仍然可见
解决方案:
-
调整遮罩层的位置和大小
-
确保z-index层级正确
-
检查CSS样式是否被覆盖
css
/* 调整遮罩位置 */
.button-mask.bottom-right {
bottom: 15px; /* 根据实际按钮位置调整 */
right: 15px;
width: 90px; /* 根据按钮大小调整 */
height: 45px;
z-index: 999; /* 确保层级足够高 */
}
最佳实践
1. 错误处理
始终为地图操作添加错误处理机制:
javascript
const handleMapOperation = async () => {
try {
// 地图操作代码
await initTencentMap()
} catch (error) {
console.error('地图操作失败:', error)
ElMessage.error('地图服务暂时不可用,请稍后重试')
// 提供降级方案
showManualInput()
}
}
const showManualInput = () => {
ElMessageBox.prompt('请手动输入地址', '位置信息', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(({ value }) => {
formData.value.address = value
})
}
2. 性能优化
避免频繁的API调用和DOM操作:
javascript
// 使用防抖优化地图URL更新
import { debounce } from 'lodash-es'
const updateMapUrl = debounce(async () => {
// 更新地图URL的逻辑
}, 300)
// 监听数据变化时使用防抖
watch([() => formData.value.latitude, () => formData.value.longitude], () => {
updateMapUrl()
})
3. 用户体验优化
提供清晰的状态反馈和操作指引:
vbnet
const openLocationPicker = async () => {
// 显示加载状态
const loading = ElLoading.service({
lock: true,
text: '正在加载地图...',
spinner: 'el-icon-loading'
})
try {
await initMapUrl()
mapDialogVisible.value = true
} catch (error) {
ElMessage.error('地图加载失败')
} finally {
loading.close()
}
}
4. 数据验证
对位置数据进行严格验证:
kotlin
const validateLocationData = (data) => {
const errors = []
if (!data.address || data.address.trim() === '') {
errors.push('地址不能为空')
}
if (!data.latitude || !data.longitude) {
errors.push('经纬度信息不完整')
}
if (!validateCoordinates(data.latitude, data.longitude)) {
errors.push('经纬度格式不正确')
}
return {
isValid: errors.length === 0,
errors
}
}
5. 安全考虑
保护API密钥和用户数据:
javascript
// 不要在前端直接暴露API密钥
// 通过后端接口获取配置信息
const getMapConfig = async () => {
try {
const response = await fetch('/api/map/config', {
headers: {
'Authorization': `Bearer ${getToken()}`
}
})
return await response.json()
} catch (error) {
console.error('获取地图配置失败:', error)
throw error
}
}
总结
腾讯地图组件为应用提供了强大的位置服务功能。通过合理的配置和使用,可以为用户提供优秀的位置选择和查看体验。在实际使用中,请注意:
-
正确配置API密钥:确保密钥有效且有足够配额
-
处理异常情况:提供完善的错误处理和降级方案
-
优化用户体验:提供清晰的操作指引和状态反馈
-
保护用户隐私:合理使用位置信息,遵守相关法规
-
性能优化:避免不必要的API调用和DOM操作
更多详细信息请参考 腾讯位置服务官方文档。