<think>我们正在使用 RuoYi Vue3 版本(基于 Vue3 和 Vite)集成腾讯地图(QQMap)。根据用户提供的引用片段,我们看到在 uniapp 中配置了腾讯地图的 key,但我们的场景是 RuoYi Vue3 的 Web 应用,因此需要按照 Web 的方式集成。
步骤概述:
1. 引入腾讯地图 JavaScript API SDK
2. 创建地图组件
3. 在需要的地方使用地图组件(例如在表单中选择位置)
4. 处理坐标和地址的反解析(逆地理编码)
详细步骤:
1. **申请腾讯地图 Key**
前往腾讯位置服务(https://lbs.qq.com/)申请开发者密钥(Key)。申请时注意设置安全域名(如本地开发用localhost,生产环境用你的域名)。
2. **在项目中引入 SDK**
在 `public/index.html` 中通过 script 标签引入腾讯地图 SDK(注意:Vue3 项目通常使用 Vite,也可以使用 npm 包,但腾讯地图官方推荐直接引入脚本)。
在 `public/index.html` 的 `<head>` 或 `<body>` 中添加:
```html
<script src="https://map.qq.com/api/gljs?v=1.exp&key=YOUR_KEY"></script>
```
将 `YOUR_KEY` 替换为你的实际密钥。
3. **创建地图组件**
在 `src/components` 目录下创建 `QQMap.vue` 组件。
```vue
<template>
<div class="map-container">
<div id="map" ref="mapContainer"></div>
<div class="map-controls">
<el-button type="primary" @click="confirmLocation">确认位置</el-button>
<el-button @click="closeMap">关闭</el-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, defineEmits, defineProps } from 'vue';
const props = defineProps({
latitude: Number,
longitude: Number,
address: String
});
const emit = defineEmits(['selectMap', 'closeMap']);
const mapContainer = ref(null);
let map = null;
let marker = null;
let selectedLocation = ref(null);
onMounted(() => {
initMap();
});
const initMap = () => {
// 确保 TMap 已经加载(全局变量 TMap)
if (window.TMap) {
// 如果有传入的经纬度,则使用传入的,否则使用默认(北京)
const center = props.latitude && props.longitude ?
new TMap.LatLng(props.latitude, props.longitude) :
new TMap.LatLng(39.908823, 116.397470);
// 初始化地图
map = new TMap.Map(mapContainer.value, {
center: center,
zoom: 12
});
// 初始化标记
marker = new TMap.MultiMarker({
map: map,
styles: {
marker: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 12.5, y: 35 }
})
},
geometries: [{
position: center
}]
});
// 地图点击事件
map.on('click', (evt) => {
selectedLocation.value = evt.latLng;
marker.setGeometries([{ position: evt.latLng }]);
});
} else {
console.error('TMap SDK 尚未加载完成,请稍后再试');
}
};
// 确认位置
const confirmLocation = async () => {
if (selectedLocation.value) {
// 获取地址(逆地理编码)
try {
const address = await getAddress(selectedLocation.value);
emit('selectMap', {
latitude: selectedLocation.value.lat,
longitude: selectedLocation.value.lng,
address: address
});
} catch (error) {
console.error('逆地理编码失败', error);
// 如果逆地理编码失败,至少返回坐标
emit('selectMap', {
latitude: selectedLocation.value.lat,
longitude: selectedLocation.value.lng,
address: '未知地址'
});
}
}
};
// 逆地理编码
const getAddress = (latLng) => {
return new Promise((resolve, reject) => {
const geocoder = new TMap.service.Geocoder();
geocoder.getAddress({ location: latLng }, (err, res) => {
if (err || !res || !res.result) {
reject(err || new Error('逆地理编码失败'));
} else {
resolve(res.result.address);
}
});
});
};
// 关闭地图
const closeMap = () => {
emit('closeMap');
};
</script>
<style scoped>
.map-container {
position: relative;
height: 500px;
}
#map {
width: 100%;
height: 100%;
}
.map-controls {
position: absolute;
bottom: 20px;
right: 20px;
z-index: 1000;
}
</style>
```
4. **在业务页面中使用地图组件**
例如,我们想在用户管理页面中添加一个地址选择功能。
```vue
<template>
<div class="app-container">
<!-- 其他表单内容 -->
<el-form :model="form" label-width="80px">
<el-form-item label="地址">
<el-input
v-model="form.address"
placeholder="点击选择位置"
@click="openMap"
readonly>
</el-input>
</el-form-item>
</el-form>
<el-dialog
title="选择位置"
v-model="mapVisible"
width="80%"
append-to-body>
<QQMap
v-if="mapVisible"
:latitude="form.latitude"
:longitude="form.longitude"
:address="form.address"
@selectMap="handleSelectMap"
@closeMap="mapVisible = false"
/>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import QQMap from '@/components/QQMap.vue';
const form = ref({
latitude: null,
longitude: null,
address: ''
});
const mapVisible = ref(false);
const openMap = () => {
mapVisible.value = true;
};
const handleSelectMap = (location) => {
form.value = {
...form.value,
latitude: location.latitude,
longitude: location.longitude,
address: location.address
};
mapVisible.value = false;
};
</script>
```
5. **注意事项**
- **密钥安全**:不要将密钥直接暴露在代码中,生产环境建议通过环境变量注入。在 RuoYi Vue3 中,可以在 `.env.production` 等环境文件中设置,然后在构建时替换。
- **坐标系**:腾讯地图使用 GCJ-02 坐标系(火星坐标系),如果后台使用其他坐标系(如 WGS-84),需要进行转换。可以在前端使用转换库(如 gcoord)进行转换,或者在后端转换。
- **性能**:地图组件在打开时才渲染,使用 `v-if` 控制。
- **移动端适配**:如果需要适配移动端,注意地图容器的高度和触摸事件。
6. **环境变量配置(可选)**
在 `.env.development` 和 `.env.production` 中添加:
```
VITE_QQMAP_KEY=你的密钥
```
然后在 `public/index.html` 中通过环境变量注入:
```html
<script src="https://map.qq.com/api/gljs?v=1.exp&key=%VITE_QQMAP_KEY%"></script>
```
注意:Vite 在构建时不会替换 public 目录下的文件中的环境变量,因此我们需要使用一个插件(如 vite-plugin-html)来支持。或者,我们可以在入口文件中动态注入,但这样可能会影响地图的加载顺序。
另一种方法:不在 public/index.html 中引入,而是在组件中动态加载:
```js
// 在组件中
const loadScript = (src) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
};
onMounted(async () => {
if (!window.TMap) {
await loadScript(`https://map.qq.com/api/gljs?v=1.exp&key=${import.meta.env.VITE_QQMAP_KEY}`);
}
initMap();
});
```
这样我们就可以使用环境变量了。但注意,这种方式需要确保地图组件的加载顺序。
根据以上步骤,我们可以在 RuoYi Vue3 项目中集成腾讯地图,并实现位置选择功能。
相关问题:
1. 如何在 RuoYi Vue3 中安全地管理腾讯地图的密钥?
2. 腾讯地图的坐标如何与后台的坐标系进行转换?
3. 如何优化地图组件在 RuoYi 项目中的加载速度?
4. 在 RuoYi 中,如何将地图选点功能封装成表单控件?</think>### RuoYi Vue3 集成腾讯地图完整指南
#### 一、前期准备
1. **申请腾讯地图 Key**
- 访问[腾讯位置服务官网](https://lbs.qq.com/)
- 创建应用 → 启用 WebService API 和 JavaScript API
- 配置安全域名(本地开发用 `localhost`,生产环境用实际域名)
2. **配置环境变量**
在 `.env.production` 和 `.env.development` 中添加:
```ini
VITE_QQMAP_KEY = "你的腾讯地图KEY"
```
#### 二、集成步骤
##### 1. 引入地图 SDK
在 `index.html` 中动态加载 SDK:
```html
<head>
<script>
window.QQMapKey = "<%= VITE_QQMAP_KEY %>";
</script>
<script
src="https://map.qq.com/api/gljs?v=1.exp&key=<%= VITE_QQMAP_KEY %>"
defer>
</script>
</head>
```
##### 2. 创建地图组件 (`src/components/map/QQMap.vue`)
```vue
<template>
<div class="map-container">
<div id="qq-map" ref="mapContainer"></div>
<div class="map-controls">
<el-button type="primary" @click="confirmLocation">
<i class="ri-check-line"></i> 确认位置
</el-button>
<el-button @click="$emit('close')">
<i class="ri-close-line"></i> 关闭
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
lat: { type: Number, default: 39.908823 },
lng: { type: Number, default: 116.397470 },
address: String
})
const emit = defineEmits(['confirm', 'close'])
const mapContainer = ref(null)
const map = ref(null)
const marker = ref(null)
const selectedLocation = ref(null)
// 初始化地图
const initMap = async () => {
if (!window.TMap) {
ElMessage.error('地图SDK加载失败,请刷新重试')
return
}
const center = new TMap.LatLng(props.lat, props.lng)
map.value = new TMap.Map(mapContainer.value, {
center: center,
zoom: 13,
viewMode: '2D'
})
// 添加标记
marker.value = new TMap.MultiMarker({
map: map.value,
styles: {
marker: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 12, y: 35 }
})
},
geometries: [{ position: center }]
})
// 点击事件
map.value.on('click', async (evt) => {
selectedLocation.value = evt.latLng
marker.value.setGeometries([{ position: evt.latLng }])
})
}
// 获取地址信息
const getAddress = (latLng) => {
return new Promise((resolve) => {
new TMap.service.Geocoder().getAddress({ location: latLng }, (err, res) => {
resolve(err ? '未知地址' : res.result.address)
})
})
}
// 确认位置
const confirmLocation = async () => {
if (!selectedLocation.value) {
ElMessage.warning('请先在地图上选择位置')
return
}
const address = await getAddress(selectedLocation.value)
emit('confirm', {
lat: selectedLocation.value.lat,
lng: selectedLocation.value.lng,
address
})
}
onMounted(() => {
// 确保SDK加载完成
if (window.TMap) {
initMap()
} else {
window.addEventListener('qqmap-loaded', initMap)
}
})
</script>
<style scoped>
.map-container {
position: relative;
height: 70vh;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
#qq-map {
width: 100%;
height: 100%;
}
.map-controls {
position: absolute;
bottom: 20px;
right: 20px;
z-index: 1000;
display: flex;
gap: 10px;
}
</style>
```
##### 3. 在业务页面中使用 (`src/views/system/user.vue`)
```vue
<template>
<div class="app-container">
<el-form :model="form" label-width="100px">
<el-form-item label="所在位置">
<el-input
v-model="form.address"
placeholder="点击选择位置"
readonly
@click="mapVisible = true">
<template #append>
<el-button :icon="MapLocation" @click="mapVisible = true" />
</template>
</el-input>
</el-form-item>
</el-form>
<!-- 地图弹窗 -->
<el-dialog
v-model="mapVisible"
title="位置选择"
width="80%"
destroy-on-close
append-to-body>
<QQMap
:lat="form.lat"
:lng="form.lng"
@confirm="handleMapConfirm"
@close="mapVisible = false"
/>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { MapLocation } from '@element-plus/icons-vue'
import QQMap from '@/components/map/QQMap.vue'
const mapVisible = ref(false)
const form = ref({
lat: null,
lng: null,
address: ''
})
const handleMapConfirm = (location) => {
form.value = { ...location }
mapVisible.value = false
// 这里可添加数据保存逻辑
}
</script>
```
#### 三、关键配置说明
1. **坐标系处理**:
- 腾讯地图使用 **GCJ-02** 坐标系
- 若后端使用 WGS-84 坐标系,需进行转换:
```javascript
// 安装转换库
npm install gcoord
// 在提交数据时转换
import gcoord from 'gcoord'
const wgsPoint = gcoord.transform(
[form.value.lng, form.value.lat],
gcoord.GCJ02,
gcoord.WGS84
)
```
2. **性能优化**:
```vue
<QQMap v-if="mapVisible" ... />
```
使用 `v-if` 确保地图组件在需要时加载
3. **移动端适配**:
```css
@media (max-width: 768px) {
.map-container {
height: 60vh;
}
.map-controls {
bottom: 10px;
right: 10px;
}
}
```
#### 四、常见问题解决
1. **地图不显示**:
- 检查 Key 的域名白名单是否包含本地地址
- 确保 SDK 加载完成后再初始化地图
```javascript
window.addEventListener('qqmap-loaded', initMap)
```
2. **逆地理编码失败**:
- 添加错误重试机制
```javascript
const getAddress = async (latLng, retry = 3) => {
try {
// ...逆地理编码逻辑
} catch (err) {
if (retry > 0) return getAddress(latLng, retry - 1)
return '地址解析失败'
}
}
```
3. **内存泄漏**:
```vue
<script setup>
onBeforeUnmount(() => {
if (map.value) {
map.value.destroy()
map.value = null
}
})
</script>
```
> 实际部署时建议将地图密钥存储在环境变量中,避免硬编码暴露安全风险[^1]。