xlqx/app/components/AMapComponent.vue
2025-10-30 10:24:09 +08:00

350 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>
<button class="retry-btn" @click="retryLoad">重试</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { getAMapScriptUrl, validateKeys } from '~/config/amap'
// 组件属性
const props = defineProps({
// 地图中心点坐标 [经度, 纬度]
center: {
type: Array,
default: () => [120.258419, 27.11402]
},
// 地图缩放级别
zoom: {
type: Number,
default: 17
},
// 视图模式2D 或 3D
viewMode: {
type: String,
default: '2D',
validator: (value) => ['2D', '3D'].includes(value)
},
// 地图高度
height: {
type: String,
default: '342px'
}
})
// 响应式数据
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()
}
// 创建地图实例
map.value = new window.AMap.Map('amap-container', {
viewMode: props.viewMode, // 2D 或 3D 模式
zoom: props.zoom, // 初始化地图层级
center: props.center, // 初始化地图中心点
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('视图模式需要在初始化时设置,当前版本不支持运行时切换')
// }
// })
// 生命周期
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;
}
}
</style>