This commit is contained in:
2026-07-30 11:24:40 +08:00
parent 42da6fc74e
commit 0eb2835215
15 changed files with 899 additions and 509 deletions
+185
View File
@@ -0,0 +1,185 @@
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
const mapContainer = ref(null)
let map = null
let markerLayer = null
let currentMarkers = []
let radiusCircle = null
const emit = defineEmits(['marker-click', 'map-click'])
// 高德地图瓦片
const amapTileUrl = 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
const amapSubdomains = ['1', '2', '3', '4']
onMounted(() => {
map = L.map(mapContainer.value, {
center: [39.9042, 116.4074], // 北京
zoom: 12,
zoomControl: true,
})
// 高德地图瓦片层
L.tileLayer(amapTileUrl, {
subdomains: amapSubdomains,
attribution: '&copy; 高德地图',
maxZoom: 18,
}).addTo(map)
markerLayer = L.layerGroup().addTo(map)
// 地图点击 → 设置中心点
map.on('click', (e) => {
emit('map-click', { lat: e.latlng.lat, lng: e.latlng.lng })
})
})
onUnmounted(() => {
if (map) {
map.remove()
map = null
}
})
function clearMarkers() {
markerLayer.clearLayers()
currentMarkers = []
}
function addMarkers(pois) {
clearMarkers()
if (!map || !pois.length) return
const bounds = L.latLngBounds()
const pinIcon = L.divIcon({
className: '',
html: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 42" width="32" height="42">
<path d="M16 2C8.268 2 2 8.268 2 16c0 9.678 14 24 14 24s14-14.322 14-24C30 8.268 23.732 2 16 2z" fill="#1a73e8" stroke="#fff" stroke-width="2"/>
<circle cx="16" cy="15" r="6" fill="#fff"/>
</svg>`,
iconSize: [32, 42],
iconAnchor: [16, 42],
popupAnchor: [0, -42],
})
pois.forEach((poi) => {
const lat = parseFloat(poi.latitude)
const lng = parseFloat(poi.longitude)
if (isNaN(lat) || isNaN(lng)) return
const marker = L.marker([lat, lng], { icon: pinIcon }).addTo(markerLayer)
marker.bindPopup(`
<div class="map-popup">
<h4>${poi.name}</h4>
<p class="popup-phone">${poi.phone ? `<a href="tel:${poi.phone}">${poi.phone}</a>` : '暂无电话'}</p>
<p class="popup-addr">${poi.address || ''}</p>
${poi.businessArea ? `<p class="popup-area">商圈:${poi.businessArea}</p>` : ''}
</div>
`)
marker.on('click', () => {
emit('marker-click', poi)
})
currentMarkers.push(marker)
bounds.extend([lat, lng])
})
// 自动调整视野
if (pois.length === 1) {
map.setView(bounds.getCenter(), 15)
} else {
map.fitBounds(bounds, { padding: [40, 40], maxZoom: 16 })
}
}
function focusMarker(poi) {
const lat = parseFloat(poi.latitude)
const lng = parseFloat(poi.longitude)
if (isNaN(lat) || isNaN(lng)) return
map.setView([lat, lng], 16)
for (const marker of currentMarkers) {
const pos = marker.getLatLng()
if (Math.abs(pos.lat - lat) < 0.001 && Math.abs(pos.lng - lng) < 0.001) {
marker.openPopup()
break
}
}
}
// 地图跳转到指定坐标
function flyTo(lat, lng, zoom) {
if (!map) return
map.setView([lat, lng], zoom || 12)
}
// 绘制半径圆圈
function drawRadiusCircle(lat, lng, radiusMeters) {
clearRadiusCircle()
if (!map) return
radiusCircle = L.circle([lat, lng], {
radius: radiusMeters,
color: '#1a73e8',
fillColor: '#1a73e8',
fillOpacity: 0.08,
weight: 2,
dashArray: '6, 6',
}).addTo(map)
map.fitBounds(radiusCircle.getBounds(), { padding: [40, 40] })
}
function clearRadiusCircle() {
if (radiusCircle) {
map.removeLayer(radiusCircle)
radiusCircle = null
}
}
defineExpose({ addMarkers, clearMarkers, focusMarker, drawRadiusCircle, clearRadiusCircle, flyTo })
</script>
<style scoped>
.map-container {
width: 100%;
height: 100%;
min-height: 400px;
}
:deep(.map-popup h4) {
margin: 0 0 4px;
font-size: 14px;
color: #333;
}
:deep(.popup-phone) {
margin: 2px 0;
font-size: 15px;
font-weight: 600;
color: #1a73e8;
}
:deep(.popup-phone a) {
color: #1a73e8;
text-decoration: none;
}
:deep(.popup-addr) {
margin: 2px 0;
font-size: 12px;
color: #888;
}
:deep(.popup-area) {
margin: 2px 0;
font-size: 12px;
color: #999;
}
</style>