xlqx/app/components/AMapComponent.vue

350 lines
7.4 KiB
Vue
Raw Normal View History

2025-10-29 14:44:18 +08:00
<template>
<div class="map-container">
<div
ref="mapContainer"
id="amap-container"
class="map-wrapper"
></div>
<!-- 加载状态 -->
<div v-if="isLoading" class="loading-overlay">
<div class="loading-spinner"></div>
<p>地图加载中...</p>
</div>
<!-- 错误状态 -->
<div v-if="error" class="error-overlay">
<div class="error-content">
<h3>地图加载失败</h3>
<p>{{ error }}</p>
2025-10-29 18:01:30 +08:00
<button class="retry-btn" @click="retryLoad">重试</button>
2025-10-29 14:44:18 +08:00
</div>
</div>
</div>
</template>
<script setup>
2025-10-29 15:40:24 +08:00
import { ref, onMounted, onUnmounted, watch } from 'vue'
2025-10-30 09:39:22 +08:00
import { getAMapScriptUrl, validateKeys } from '~/config/amap'
2025-10-29 14:44:18 +08:00
// 组件属性
const props = defineProps({
// 地图中心点坐标 [经度, 纬度]
center: {
type: Array,
2025-10-29 15:40:24 +08:00
default: () => [120.258419, 27.11402]
2025-10-29 14:44:18 +08:00
},
// 地图缩放级别
zoom: {
type: Number,
2025-10-29 18:01:30 +08:00
default: 17
2025-10-29 14:44:18 +08:00
},
// 视图模式2D 或 3D
viewMode: {
type: String,
default: '2D',
validator: (value) => ['2D', '3D'].includes(value)
},
// 地图高度
height: {
type: String,
2025-10-29 18:01:30 +08:00
default: '342px'
2025-10-29 14:44:18 +08:00
}
})
// 响应式数据
const mapContainer = ref(null)
const map = ref(null)
const isLoading = ref(true)
const error = ref(null)
// 动态加载高德地图脚本
const loadAMapScript = () => {
return new Promise((resolve, reject) => {
if (window.AMap) {
resolve()
return
}
const script = document.createElement('script')
script.src = getAMapScriptUrl()
script.async = true
script.onload = () => {
console.log('高德地图API加载成功')
resolve()
}
script.onerror = () => {
console.error('高德地图API加载失败')
reject(new Error('高德地图API加载失败'))
}
document.head.appendChild(script)
})
}
// 初始化地图
const initMap = async () => {
if (!mapContainer.value) return
try {
isLoading.value = true
error.value = null
// 验证密钥配置
const keyValidation = validateKeys()
if (!keyValidation.isValid) {
error.value = '请配置高德地图API密钥和安全密钥'
isLoading.value = false
return
}
// 动态导入高德地图API
if (!window.AMap) {
await loadAMapScript()
}
2025-10-30 09:39:22 +08:00
2025-10-29 14:44:18 +08:00
// 创建地图实例
map.value = new window.AMap.Map('amap-container', {
viewMode: props.viewMode, // 2D 或 3D 模式
zoom: props.zoom, // 初始化地图层级
center: props.center, // 初始化地图中心点
2025-10-29 15:40:24 +08:00
2025-10-29 14:44:18 +08:00
features: ['bg', 'road', 'building', 'point'], // 显示要素
showLabel: true, // 显示标注
resizeEnable: true, // 是否监控地图容器尺寸变化
rotateEnable: true, // 是否允许旋转
pitchEnable: true, // 是否允许倾斜
zoomEnable: true, // 是否允许缩放
dragEnable: true, // 是否允许拖拽
keyboardEnable: true, // 是否允许键盘操作
doubleClickZoom: true, // 是否允许双击缩放
scrollWheel: true, // 是否允许滚轮缩放
touchZoom: true, // 是否允许触摸缩放
})
// 地图加载完成事件
map.value.on('complete', () => {
console.log('地图加载完成')
isLoading.value = false
// 触发地图加载完成事件
emit('mapReady', map.value)
})
// 地图点击事件
map.value.on('click', (e) => {
emit('mapClick', {
lng: e.lnglat.getLng(),
lat: e.lnglat.getLat(),
pixel: e.pixel
})
})
// 地图缩放事件
map.value.on('zoomchange', () => {
emit('zoomChange', map.value.getZoom())
})
// 地图移动事件
map.value.on('moveend', () => {
const center = map.value.getCenter()
emit('centerChange', {
lng: center.getLng(),
lat: center.getLat()
})
})
} catch (err) {
console.error('地图初始化失败:', err)
error.value = err.message || '地图初始化失败'
isLoading.value = false
}
}
// 重试加载
const retryLoad = () => {
initMap()
}
// 组件事件
const emit = defineEmits(['mapReady', 'mapClick', 'zoomChange', 'centerChange'])
// 暴露地图实例给父组件
defineExpose({
map: map,
getMap: () => map.value,
setCenter: (center) => {
if (map.value) {
map.value.setCenter(center)
}
},
setZoom: (zoom) => {
if (map.value) {
map.value.setZoom(zoom)
}
},
addMarker: (position, options = {}) => {
if (map.value) {
return new window.AMap.Marker({
position: position,
map: map.value,
...options
})
}
}
})
// // 监听props变化更新地图
// watch(() => props.center, (newCenter) => {
// if (map.value && newCenter) {
// console.log('地图中心点更新:', newCenter)
// map.value.setCenter(newCenter)
// }
// }, { deep: true })
//
// watch(() => props.zoom, (newZoom) => {
// if (map.value && newZoom) {
// console.log('地图缩放级别更新:', newZoom)
// map.value.setZoom(newZoom)
// }
// })
//
// watch(() => props.viewMode, (newViewMode) => {
// if (map.value && newViewMode) {
// console.log('地图视图模式更新:', newViewMode)
// // 高德地图的视图模式需要在初始化时设置,运行时无法直接切换
// // 如果需要切换视图模式,需要重新创建地图实例
// console.warn('视图模式需要在初始化时设置,当前版本不支持运行时切换')
// }
// })
2025-10-29 15:40:24 +08:00
2025-10-29 14:44:18 +08:00
// 生命周期
onMounted(() => {
initMap()
})
onUnmounted(() => {
if (map.value) {
map.value.destroy()
}
})
</script>
<style scoped>
.map-container {
position: relative;
width: 100%;
height: v-bind(height);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.map-wrapper {
width: 100%;
height: 100%;
background-color: #f5f5f5;
}
/* 加载状态样式 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-overlay p {
color: #666;
font-size: 14px;
margin: 0;
}
/* 错误状态样式 */
.error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.error-content {
text-align: center;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
max-width: 300px;
}
.error-content h3 {
color: #dc3545;
margin: 0 0 12px 0;
font-size: 16px;
}
.error-content p {
color: #666;
margin: 0 0 16px 0;
font-size: 14px;
line-height: 1.4;
}
.retry-btn {
background-color: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.retry-btn:hover {
background-color: #0056b3;
}
/* 响应式设计 */
@media (max-width: 768px) {
.map-container {
height: 300px;
}
.error-content {
margin: 0 16px;
}
}
2025-10-29 18:01:30 +08:00
</style>