223 lines
5.0 KiB
Vue
223 lines
5.0 KiB
Vue
|
|
<template>
|
||
|
|
<div class="chongqing-map-container">
|
||
|
|
<div ref="mapContainer" class="map-container"></div>
|
||
|
|
<div v-if="loading" class="loading-overlay">
|
||
|
|
<div class="loading-spinner"></div>
|
||
|
|
<span class="loading-text">地图加载中...</span>
|
||
|
|
</div>
|
||
|
|
<div v-if="error" class="error-overlay">
|
||
|
|
<span class="error-text">{{ error }}</span>
|
||
|
|
<button class="retry-btn" @click="loadMapData">重试</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||
|
|
|
||
|
|
const mapContainer = ref(null)
|
||
|
|
const loading = ref(false)
|
||
|
|
const error = ref(null)
|
||
|
|
let mapInstance = null
|
||
|
|
|
||
|
|
// 重庆地图GeoJSON API地址
|
||
|
|
const GEOJSON_URL = 'https://geo.datav.aliyun.com/areas_v3/bound/500000_full.json'
|
||
|
|
|
||
|
|
// 加载地图数据
|
||
|
|
const loadMapData = async () => {
|
||
|
|
loading.value = true
|
||
|
|
error.value = null
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch(GEOJSON_URL)
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
const geoJsonData = await response.json()
|
||
|
|
|
||
|
|
// 初始化地图
|
||
|
|
initMap(geoJsonData)
|
||
|
|
|
||
|
|
} catch (err) {
|
||
|
|
console.error('加载地图数据失败:', err)
|
||
|
|
error.value = '地图数据加载失败,请检查网络连接'
|
||
|
|
} finally {
|
||
|
|
loading.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 初始化地图
|
||
|
|
const initMap = (geoJsonData) => {
|
||
|
|
if (!mapContainer.value) return
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 清除旧地图实例
|
||
|
|
if (mapInstance) {
|
||
|
|
mapInstance.remove()
|
||
|
|
}
|
||
|
|
|
||
|
|
// 创建地图实例
|
||
|
|
mapInstance = new window.L.Map(mapContainer.value, {
|
||
|
|
center: [29.563, 106.551], // 重庆中心坐标
|
||
|
|
zoom: 8,
|
||
|
|
minZoom: 7,
|
||
|
|
maxZoom: 18,
|
||
|
|
zoomControl: false,
|
||
|
|
attributionControl: false
|
||
|
|
})
|
||
|
|
|
||
|
|
// 添加瓦片图层
|
||
|
|
const tileLayer = new window.L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||
|
|
})
|
||
|
|
mapInstance.addLayer(tileLayer)
|
||
|
|
|
||
|
|
// 添加GeoJSON图层
|
||
|
|
const geoJsonLayer = new window.L.GeoJSON(geoJsonData, {
|
||
|
|
style: {
|
||
|
|
fillColor: '#1E3A8A',
|
||
|
|
weight: 2,
|
||
|
|
opacity: 0.8,
|
||
|
|
color: '#3B82F6',
|
||
|
|
fillOpacity: 0.3
|
||
|
|
},
|
||
|
|
onEachFeature: (feature, layer) => {
|
||
|
|
if (feature.properties && feature.properties.name) {
|
||
|
|
layer.bindPopup(`<div class="map-popup">
|
||
|
|
<strong>${feature.properties.name}</strong>
|
||
|
|
</div>`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
mapInstance.addLayer(geoJsonLayer)
|
||
|
|
|
||
|
|
// 调整视图以适应重庆边界
|
||
|
|
mapInstance.fitBounds(geoJsonLayer.getBounds())
|
||
|
|
|
||
|
|
} catch (err) {
|
||
|
|
console.error('初始化地图失败:', err)
|
||
|
|
error.value = '地图初始化失败'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 组件挂载时加载地图
|
||
|
|
onMounted(() => {
|
||
|
|
// 检查Leaflet是否已加载
|
||
|
|
if (typeof window.L === 'undefined') {
|
||
|
|
// 动态加载Leaflet CSS和JS
|
||
|
|
const link = document.createElement('link')
|
||
|
|
link.rel = 'stylesheet'
|
||
|
|
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'
|
||
|
|
link.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY='
|
||
|
|
link.crossOrigin = ''
|
||
|
|
document.head.appendChild(link)
|
||
|
|
|
||
|
|
const script = document.createElement('script')
|
||
|
|
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'
|
||
|
|
script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo='
|
||
|
|
script.crossOrigin = ''
|
||
|
|
script.onload = loadMapData
|
||
|
|
document.head.appendChild(script)
|
||
|
|
} else {
|
||
|
|
loadMapData()
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// 组件卸载时清理资源
|
||
|
|
onUnmounted(() => {
|
||
|
|
if (mapInstance) {
|
||
|
|
mapInstance.remove()
|
||
|
|
mapInstance = null
|
||
|
|
}
|
||
|
|
})
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
.chongqing-map-container {
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
.map-container {
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
background: #0f1c2e;
|
||
|
|
}
|
||
|
|
|
||
|
|
.loading-overlay,
|
||
|
|
.error-overlay {
|
||
|
|
position: absolute;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
background: rgba(0, 0, 0, 0.7);
|
||
|
|
z-index: 1000;
|
||
|
|
}
|
||
|
|
|
||
|
|
.loading-spinner {
|
||
|
|
width: 40px;
|
||
|
|
height: 40px;
|
||
|
|
border: 4px solid #3B82F6;
|
||
|
|
border-top: 4px solid transparent;
|
||
|
|
border-radius: 50%;
|
||
|
|
animation: spin 1s linear infinite;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.loading-text {
|
||
|
|
color: #fff;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.error-text {
|
||
|
|
color: #ff6b6b;
|
||
|
|
font-size: 14px;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.retry-btn {
|
||
|
|
background: #3B82F6;
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
padding: 8px 16px;
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
font-size: 12px;
|
||
|
|
|
||
|
|
&:hover {
|
||
|
|
background: #2563EB;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes spin {
|
||
|
|
0% { transform: rotate(0deg); }
|
||
|
|
100% { transform: rotate(360deg); }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Leaflet地图样式覆盖
|
||
|
|
:deep(.leaflet-container) {
|
||
|
|
background: #0f1c2e !important;
|
||
|
|
|
||
|
|
.leaflet-popup-content-wrapper {
|
||
|
|
background: rgba(64, 169, 255, 0.9);
|
||
|
|
border-radius: 4px;
|
||
|
|
|
||
|
|
.map-popup {
|
||
|
|
color: #fff;
|
||
|
|
font-size: 12px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.leaflet-popup-tip {
|
||
|
|
background: rgba(64, 169, 255, 0.9);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|