140 lines
3.1 KiB
Vue
140 lines
3.1 KiB
Vue
|
<template>
|
||
|
<div ref="mapContainer" class="map-container"></div>
|
||
|
</template>
|
||
|
|
||
|
<script setup>
|
||
|
import { ref, onMounted, watch } from 'vue'
|
||
|
import * as d3 from 'd3'
|
||
|
import chinaData from './newJson.json'
|
||
|
|
||
|
const props = defineProps({
|
||
|
coordinates: {
|
||
|
type: Array,
|
||
|
default: () => [
|
||
|
[116.405285, 39.904989], // 北京
|
||
|
[121.472644, 31.231706] // 上海
|
||
|
]
|
||
|
}
|
||
|
})
|
||
|
|
||
|
const mapContainer = ref(null)
|
||
|
let svg, mapGroup, markersGroup, zoom
|
||
|
|
||
|
const initMap = () => {
|
||
|
const width = 1000
|
||
|
const height = 800
|
||
|
|
||
|
const projection = d3.geoMercator()
|
||
|
.center([104, 37])
|
||
|
.scale(600)
|
||
|
.translate([width / 2, height / 2])
|
||
|
|
||
|
const path = d3.geoPath().projection(projection)
|
||
|
|
||
|
if (svg) svg.selectAll("*").remove()
|
||
|
|
||
|
svg = d3.select(mapContainer.value)
|
||
|
.append('svg')
|
||
|
.attr('width', width)
|
||
|
.attr('height', height)
|
||
|
|
||
|
// 修改分组结构
|
||
|
const mainGroup = svg.append('g')
|
||
|
mapGroup = mainGroup.append('g')
|
||
|
markersGroup = mainGroup.append('g') // 标记层放入主缩放组
|
||
|
|
||
|
mapGroup.append('g')
|
||
|
.selectAll('path')
|
||
|
.data(chinaData.features)
|
||
|
.enter()
|
||
|
.append('path')
|
||
|
.attr('d', path)
|
||
|
.attr('fill', '#e7e7e7')
|
||
|
.attr('stroke', '#fff')
|
||
|
|
||
|
zoom = d3.zoom()
|
||
|
.scaleExtent([0.2, 8])
|
||
|
.on('zoom', (event) => {
|
||
|
mainGroup.attr('transform', event.transform)
|
||
|
})
|
||
|
svg.call(zoom)
|
||
|
}
|
||
|
|
||
|
const updateRoute = () => {
|
||
|
const projection = d3.geoMercator()
|
||
|
.center([104, 37])
|
||
|
.scale(600)
|
||
|
.translate([1000 / 2, 800 / 2])
|
||
|
|
||
|
mapGroup.selectAll('.route-path').remove()
|
||
|
markersGroup.selectAll('*').remove()
|
||
|
|
||
|
const routeData = {
|
||
|
type: "LineString",
|
||
|
coordinates: props.coordinates
|
||
|
}
|
||
|
|
||
|
mapGroup.append('path')
|
||
|
.datum(routeData)
|
||
|
.attr('class', 'route-path')
|
||
|
.attr('d', d3.geoPath().projection(projection))
|
||
|
.attr('fill', 'none')
|
||
|
.attr('stroke', '#f00')
|
||
|
.attr('stroke-width', 2)
|
||
|
.attr('stroke-dasharray', function() {
|
||
|
return this.getTotalLength()
|
||
|
})
|
||
|
.attr('stroke-dashoffset', function() {
|
||
|
return this.getTotalLength()
|
||
|
})
|
||
|
.transition()
|
||
|
.duration(2000)
|
||
|
.attr('stroke-dashoffset', 0)
|
||
|
|
||
|
// 修改标记点绘制逻辑
|
||
|
props.coordinates.forEach((coord, i) => {
|
||
|
if (!Array.isArray(coord) || coord.length !== 2) return
|
||
|
|
||
|
const lon = Number(coord[0])
|
||
|
const lat = Number(coord[1])
|
||
|
|
||
|
if (isNaN(lon) || isNaN(lat)) return
|
||
|
|
||
|
const [x, y] = projection([lon, lat])
|
||
|
|
||
|
// 添加缩放补偿逻辑
|
||
|
const currentTransform = d3.zoomTransform(svg.node())
|
||
|
const baseRadius = 8
|
||
|
|
||
|
markersGroup.append('circle')
|
||
|
.attr('cx', x)
|
||
|
.attr('cy', y)
|
||
|
.attr('r', baseRadius / currentTransform.k) // 根据缩放级别调整半径
|
||
|
.attr('fill', '#ff4757')
|
||
|
.attr('stroke', 'white')
|
||
|
.attr('stroke-width', 2 / currentTransform.k) // 同步调整描边宽度
|
||
|
})
|
||
|
}
|
||
|
|
||
|
onMounted(() => {
|
||
|
if(props.coordinates){
|
||
|
initMap()
|
||
|
updateRoute()
|
||
|
}
|
||
|
|
||
|
})
|
||
|
|
||
|
watch(() => props.coordinates, () => {
|
||
|
updateRoute()
|
||
|
}, { deep: true })
|
||
|
</script>
|
||
|
|
||
|
<style>
|
||
|
/* 保持原有样式不变 */
|
||
|
.map-container {
|
||
|
background-color: #f0f8ff;
|
||
|
margin: 20px;
|
||
|
overflow: hidden;
|
||
|
touch-action: none;
|
||
|
}
|
||
|
</style>
|