修改显示

main
suixy 3 months ago
parent ec01f39fa0
commit 52a8d2598e

@ -0,0 +1,410 @@
<template>
<div ref="wrapperRef" class="map-wrapper">
<!-- 中心区域 -->
<div
class="map-content"
:style="{ transformOrigin: `${transformOriginX}px ${transformOriginY}px`,transform: ` scale(${scale}) translate(${0}px, ${0}px)` }"
>
<div class="map-area"></div>
<div
v-if="lineCoordinates"
class="connection-line"
:style="{
left: lineCoordinates.startX + 'px',
bottom: lineCoordinates.startY + 'px',
width: lineCoordinates.width + 'px',
transform: `translateY(-50%) rotate(${lineCoordinates.angle}deg) scaleY(${1/scale})`,
transformOrigin: 'left center'
}"
></div>
<!-- Box元素直接用内容坐标不再额外 *scale/+translate -->
<div
v-if="boxPos"
class="box"
:style="{
left: boxContentX + 'px',
bottom: boxContentY + 'px',
transform: `translate(-50%, 50%) rotate(${boxPos.rotate}deg) scale(${1 / scale})`
}"
>
<div class="scan"></div>
</div>
<div
v-for="(dot, index) in dots"
:key="index"
class="dot"
:style="{
left: `${lonToPixel(dot.lon)}px`,
bottom: `${latToPixel(dot.lat)}px`,
transform: `translate(-50%, 50%) scale(${1 / scale})`
}"
></div>
</div>
<!-- 左侧刻度尺 -->
<div class="y-ruler">
<div
v-for="tick in yTicks"
:key="tick.value"
class="tick"
:style="{ bottom: tick.pixel + 'px' }"
>
<div class="line"></div>
<div class="label">{{ tick.display }}</div>
</div>
</div>
<!-- 下方刻度尺 -->
<div class="x-ruler">
<div
v-for="tick in xTicks"
:key="tick.value"
class="tick"
:style="{ left: tick.pixel + 'px' }"
>
<div class="line"></div>
<div class="label">{{ tick.display }}</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, computed, onMounted} from "vue"
const props = defineProps({
minlon: Number,
maxlon: Number,
minlat: Number,
maxlat: Number,
boxPos: Object,// { x, y, rotate }
dots: Array,
dotIndex: Number
})
setInterval(() => {
// props.boxPos.x += (-0.001 + (Math.random() * 0.002))
// props.boxPos.y += (-0.001 + (Math.random() * 0.002))
props.boxPos.rotate += (-0 + (Math.random() * 50))
}, 1000)
const wrapperRef = ref(null)
const parentWidth = ref(0)
const parentHeight = ref(0)
//
const rulerWidth = 40
const rulerHeight = 20
//
const zoomLevel = ref(1)
const scale = computed(() =>
zoomLevel.value === 1 ? 1 : zoomLevel.value === 2 ? 10 : 100
)
const tickValue = computed(() =>
zoomLevel.value === 1 ? 100 : zoomLevel.value === 2 ? 10 : 1
)
const translateX = ref(0)
const translateY = ref(0)
//
const updateSize = () => {
if (wrapperRef.value) {
const rect = wrapperRef.value.getBoundingClientRect()
parentWidth.value = rect.width
parentHeight.value = rect.height
}
}
onMounted(updateSize)
window.addEventListener("resize", updateSize)
//
const contentWidth = computed(() => parentWidth.value - rulerWidth)
const contentHeight = computed(() => parentHeight.value - rulerHeight)
//
function lonToPixel(lon) {
return ((lon - props.minlon) / (props.maxlon - props.minlon)) * contentWidth.value
}
function latToPixel(lat) {
return ((lat - props.minlat) / (props.maxlat - props.minlat)) * contentHeight.value
}
const transformOriginX = ref(0)
const transformOriginY = ref(0)
//
function setZoom(center, level) {
zoomLevel.value = level
const contentX = lonToPixel(center.lon)
const contentY = latToPixel(center.lat)
const scaledX = (contentX + translateX.value) * scale.value
const scaledY = (contentY + translateY.value) * scale.value
transformOriginX.value = contentX
transformOriginY.value = contentHeight.value - contentY
const cx = lonToPixel(center.lon)
const cy = latToPixel(center.lat)
const targetX = contentWidth.value / 2
const targetY = contentHeight.value / 2
translateX.value = targetX - cx * scale.value
translateY.value = targetY - cy * scale.value
}
//
function formatTick(valueMeters) {
if (zoomLevel.value === 1) return Math.round(valueMeters / 100) * 100
if (zoomLevel.value === 2) return Math.round(valueMeters / 10) * 10
return Math.round(valueMeters)
}
// X
const xTicks = computed(() => {
const ticks = []
const pixelsPerLon = contentWidth.value / (props.maxlon - props.minlon)
const startLon = props.minlon - translateX.value / (pixelsPerLon * scale.value)
let val = startLon
while (val <= props.maxlon) {
const contentX = lonToPixel(val)
const pixel = contentX * scale.value + translateX.value
ticks.push({
value: val,
pixel,
display: formatTick((val - props.minlon) * 111000)
})
val += tickValue.value / 111000
}
return ticks
})
// Y
const yTicks = computed(() => {
const ticks = []
const pixelsPerLat = contentHeight.value / (props.maxlat - props.minlat)
const startLat = props.minlat - translateY.value / (pixelsPerLat * scale.value)
let val = startLat
while (val <= props.maxlat) {
const contentY = latToPixel(val)
const pixel = contentY * scale.value + translateY.value
ticks.push({
value: val,
pixel,
display: formatTick((val - props.minlat) * 111000)
})
val += tickValue.value / 111000
}
return ticks
})
// Box/
const boxContentX = computed(() => {
if (!props.boxPos) return 0
return lonToPixel(props.boxPos.x)
})
const boxContentY = computed(() => {
if (!props.boxPos) return 0
return latToPixel(props.boxPos.y)
})
const BOX_SIZE = 30 // .box width/height
const DOT_SIZE = 10 // .dot width/height
const lineCoordinates = computed(() => {
if (!props.boxPos || !props.dots || props.dotIndex == null) return null
const startDot = props.dots[props.dotIndex]
if (!startDot) return null
// dot center
const startX = lonToPixel(startDot.lon)
const startY = latToPixel(startDot.lat) - (DOT_SIZE / 2) * (1 / scale.value)
// box
const boxBottomX = lonToPixel(props.boxPos.x)
const boxBottomY = latToPixel(props.boxPos.y) - 15
// box
const centerX = boxBottomX
const centerY = boxBottomY + (BOX_SIZE / 2) * (1 / scale.value)
const carHeadOffset = BOX_SIZE * 0.5
const localAttachX = 0
const localAttachY = carHeadOffset
const ox = localAttachX * (1 / scale.value)
const oy = localAttachY * (1 / scale.value)
const rotDeg = props.boxPos.rotate || 0
const rad = -rotDeg * Math.PI / 180
const rotatedX = ox * Math.cos(rad) - oy * Math.sin(rad)
const rotatedY = ox * Math.sin(rad) + oy * Math.cos(rad)
const endX = centerX + rotatedX
const endY = centerY + rotatedY
const dx = endX - startX
const dy = endY - startY
const width = Math.hypot(dx, dy)
const angle = -Math.atan2(dy, dx) * 180 / Math.PI
return {startX, startY, width, angle, endX, endY}
})
defineExpose({setZoom})
//
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 2)
// }, 3000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 3)
// }, 5000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 2)
// }, 8000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 1)
// }, 10000)
</script>
<style scoped>
.map-wrapper {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
}
.map-content {
position: absolute;
left: 40px; /* 左侧刻度尺宽度 */
bottom: 20px; /* 下方刻度尺高度 */
width: calc(100% - 40px);
height: calc(100% - 20px);
transform-origin: 0 0;
}
.map-area {
width: 100%;
height: 100%;
background: #333;
}
/* 左侧刻度尺 */
.y-ruler {
position: absolute;
left: 0;
top: 0;
bottom: 20px;
width: 40px;
background: #000;
}
.y-ruler .tick {
position: absolute;
width: 100%;
height: 1px;
}
.y-ruler .line {
position: absolute;
right: 0;
width: 10px;
height: 1px;
background: #fff;
}
.y-ruler .label {
position: absolute;
right: 12px;
top: -6px;
color: #fff;
font-size: 10px;
text-align: right;
}
/* 下方刻度尺 */
.x-ruler {
position: absolute;
left: 40px;
right: 0;
bottom: 0;
height: 20px;
background: #000;
}
.x-ruler .tick {
position: absolute;
height: 100%;
width: 1px;
}
.x-ruler .line {
position: absolute;
top: 0;
width: 1px;
height: 5px;
background: #fff;
}
.x-ruler .label {
position: absolute;
top: 5px;
left: -5px;
color: #fff;
font-size: 10px;
}
/* Box元素 */
.box {
position: absolute;
width: 30px;
height: 30px;
background-image: url("../assets/car.png");
background-repeat: no-repeat;
background-size: 100% 100%;
transform-origin: center;
}
.box .scan {
position: absolute;
background-image: url("../assets/scan.gif");
background-size: 100% 100%;
width: 100px;
height: 100px;
top: 50%;
right: 15px;
transform: translateY(-50%);
}
.dot {
width: 10px;
height: 10px;
position: absolute;
background: radial-gradient(circle at center,
#ea1212 0%, rgba(228, 116, 116, 0.2) 100%);
border-radius: 50%;
}
.connection-line {
position: absolute;
height: 2px;
background-color: transparent; /* 背景透明 */
border-top: 1px dashed #fff; /* 虚线样式 */
transform-origin: left center;
z-index: 1;
}
</style>

@ -13,7 +13,7 @@
left: lineCoordinates.startX + 'px',
bottom: lineCoordinates.startY + 'px',
width: lineCoordinates.width + 'px',
transform: `translateY(50%) rotate(${lineCoordinates.angle}deg) scaleY(${1/scale})`,
transform: `translateY(-50%) rotate(${lineCoordinates.angle}deg) scaleY(${1/scale})`,
transformOrigin: 'left center'
}"
></div>
@ -82,9 +82,9 @@ const props = defineProps({
dotIndex: Number
})
setInterval(() => {
props.boxPos.x += (-0.001 + (Math.random() * 0.002))
props.boxPos.y += (-0.001 + (Math.random() * 0.002))
props.boxPos.rotate += (-25 + (Math.random() * 50))
// props.boxPos.x += (-0.001 + (Math.random() * 0.002))
// props.boxPos.y += (-0.001 + (Math.random() * 0.002))
props.boxPos.rotate += (-0 + (Math.random() * 50))
}, 1000)
const wrapperRef = ref(null)
const parentWidth = ref(0)
@ -215,37 +215,62 @@ const boxContentY = computed(() => {
if (!props.boxPos) return 0
return latToPixel(props.boxPos.y)
})
const lineCoordinates = computed(() => {
if (!props.boxPos || !props.dots || props.dotIndex === undefined) return null
const BOX_SIZE = 30 // .box width/height
const DOT_SIZE = 10 // .dot width/height
const lineCoordinates = computed(() => {
if (!props.boxPos || !props.dots || props.dotIndex == null) return null
const startDot = props.dots[props.dotIndex]
if (!startDot) return null
// dot center
const startX = lonToPixel(startDot.lon)
const startY = latToPixel(startDot.lat)
const startY = latToPixel(startDot.lat) - (DOT_SIZE / 2) * (1 / scale.value)
// box
const boxBottomX = lonToPixel(props.boxPos.x)
const boxBottomY = latToPixel(props.boxPos.y) - 15
// box
const centerX = boxBottomX
const centerY = boxBottomY + (BOX_SIZE / 2) * (1 / scale.value)
const carHeadOffset = BOX_SIZE * 0.5
const localAttachX = 0
const localAttachY = carHeadOffset
const ox = localAttachX * (1 / scale.value)
const oy = localAttachY * (1 / scale.value)
const rotDeg = props.boxPos.rotate || 0
const rad = -rotDeg * Math.PI / 180
const rotatedX = ox * Math.cos(rad) - oy * Math.sin(rad)
const rotatedY = ox * Math.sin(rad) + oy * Math.cos(rad)
const endX = centerX + rotatedX
const endY = centerY + rotatedY
const endX = lonToPixel(props.boxPos.x)
const endY = latToPixel(props.boxPos.y) + 15
const dx = endX - startX
const dy = endY - startY
const width = Math.hypot(dx, dy)
const angle = Math.atan2(dx, dy) * 180 / Math.PI - 90
const angle = -Math.atan2(dy, dx) * 180 / Math.PI
return {startX, startY, width, angle}
return {startX, startY, width, angle, endX, endY}
})
defineExpose({setZoom})
//
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 2)
// }, 3000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 3)
// }, 5000)
setTimeout(() => {
setZoom({lon: 120.005, lat: 30.005}, 2)
}, 3000)
setTimeout(() => {
setZoom({lon: 120.005, lat: 30.005}, 3)
}, 5000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 2)
// }, 8000)

@ -76,12 +76,14 @@ const LEFT = defineComponent({
ref={rulerRef}
index={dotIndex.value}
dots={dots.value}
dotIndex={3}
dotIndex={1}
boxPos={boxPos.value}
minlon={120}
maxlon={120.01}
minlat={30}
maxlat={30.01}
rightBottomLocation={{lon: 120.01, lat: 30.01}}
bottomScaleStep={10}
/>
</div>
)

Loading…
Cancel
Save