1、视口渲染优化:只渲染当前可见区域的节点和路线,在滚动和缩放时更新视口信息,添加了边界扩展确保拖动时不会出现闪烁。

2、空间索引优化:使用网格式空间索引将地图划分为网格单元,根据坐标将点位分配到对应网格,搜索时只检查相关网格中的点位。
3、改进鼠标滚轮放大缩小地图功能,改为保持鼠标指向点不变
This commit is contained in:
yyy 2025-07-15 11:39:40 +08:00
parent 7ff85039ba
commit b5785e88e1
2 changed files with 525 additions and 143 deletions

View File

@ -271,8 +271,9 @@ const getAllNodeList = async () => {
positionMapId: imgBgObj.positionMapId
})
allMapPointInfo.value = []
list.forEach((item) => {
item.locationX = convertActualToBrowser(item.actualLocationX, item.actualLocationY).x
item.locationY = convertActualToBrowser(item.actualLocationX, item.actualLocationY).y
//
if (locationTypeNumber.value == 1 && item.type === 2) {
item.locationX = Number(item.locationX) * (imgBgObj.showWidth / imgBgObj.width)
@ -420,6 +421,23 @@ const handleWheel = (event) => {
const emit = defineEmits(['locationSelectionDialogSuccess'])
defineExpose({ open }) // open
//
const convertActualToBrowser = (pointX, pointY) => {
const y1 = Number(imgBgObj.origin[1]) + Number(imgBgObj.height) * Number(imgBgObj.resolution)
let x =
Math.round(
(Math.max(Number(pointX) - Number(imgBgObj.origin[0]), 0) / Number(imgBgObj.resolution)) *
10000
) / 10000
let y =
Math.round((Math.max(y1 - Number(pointY), 0) / Number(imgBgObj.resolution)) * 10000) / 10000
return {
x,
y
}
}
</script>
<style lang="scss">

View File

@ -476,6 +476,7 @@
ref="mapContainerRef"
:style="{ cursor: state.cursorStyle }"
v-if="imgBgObj.width && imgBgObj.height"
@scroll="updateViewport"
>
<!-- <map-scale-tool :stepLength="50" :width="imgBgObj.width" :height="imgBgObj.height"> -->
<!-- transform: `scale(${state.imageChangeMultiple}) rotate(-5deg)`, -->
@ -500,8 +501,8 @@
v-if="interfaceRefreshed"
>
<VueDragResizeRotate
v-for="(item, index) in state.allMapPointInfo"
:key="index + Number(item.locationX)"
v-for="(item, i) in visibleMapPoints"
:key="item.id || visibleMapPointIndices[i] + Number(item.locationX)"
:parent="true"
:x="Number(item.locationX) - Number(item.locationWidePx) / 2"
:y="Number(item.locationY) - Number(item.locationDeepPx) / 2"
@ -510,10 +511,13 @@
:r="item.angle"
:snap="true"
:snapTolerance="10"
@rotatestop="(degree) => rotateEnd(degree, item, index)"
@dragstop="(x, y) => dragEnd(x, y, item, index)"
@resizestop="(x, y, width, height) => resizeEnd(x, y, width, height, item, index)"
@activated="() => activatedHandle(item, index)"
@rotatestop="(degree) => rotateEnd(degree, item, visibleMapPointIndices[i])"
@dragstop="(x, y) => dragEnd(x, y, item, visibleMapPointIndices[i])"
@resizestop="
(x, y, width, height) =>
resizeEnd(x, y, width, height, item, visibleMapPointIndices[i])
"
@activated="() => activatedHandle(item, visibleMapPointIndices[i])"
@deactivated="deactivatedHandle"
:draggable="!state.prohibitedOperation && item.draggable"
:resizable="!state.prohibitedOperation && item.resizable"
@ -525,7 +529,7 @@
>
<!-- 节点合集 -->
<div
@mousedown="startFromPoint(index, $event)"
@mousedown="startFromPoint(visibleMapPointIndices[i], $event)"
@click.stop="handleNodeClick(item)"
:style="{ width: item.locationWidePx + 'px', height: item.locationDeepPx + 'px' }"
>
@ -571,7 +575,7 @@
</div>
</div>
</template>
<div @contextmenu="handleContextMenu(item, index, $event)">
<div @contextmenu="handleContextMenu(item, visibleMapPointIndices[i], $event)">
<div
v-if="
item.type === 1 &&
@ -580,7 +584,7 @@
item.sortNum
"
class="sort-num"
:style="getSortNumStyle(item, index)"
:style="getSortNumStyle(item, visibleMapPointIndices[i])"
>
{{ item.sortNum }}
</div>
@ -592,13 +596,13 @@
item.sortNum
"
class="sort-num-location"
:style="getSortNumLocationStyle(item, index)"
:style="getSortNumLocationStyle(item, visibleMapPointIndices[i])"
>
{{ item.sortNum }}
</div>
<div
class="sort-num-location"
:style="getLocationNumberStyle(item, index)"
:style="getLocationNumberStyle(item, visibleMapPointIndices[i])"
v-if="
toolbarSwitchType === 'createLineLibrary' &&
item.type === 2 &&
@ -612,7 +616,8 @@
:style="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
backgroundColor: state.currentItemIndex === index ? '#5ecc62' : '#000',
backgroundColor:
state.currentItemIndex === visibleMapPointIndices[i] ? '#5ecc62' : '#000',
borderRadius: '50%',
zIndex: 999
}"
@ -623,7 +628,7 @@
v-if="item.type === 2 && item.layerSelectionShow"
src="@/assets/imgs/indexPage/bin-location.png"
alt=""
:style="binLocationStyle(item, index)"
:style="binLocationStyle(item, visibleMapPointIndices[i])"
/>
<!-- 3 设备点 -->
<img
@ -631,7 +636,7 @@
:src="item.mapImageUrl || '@/assets/imgs/indexPage/equipment.png'"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
:style="nodeStyle(item, visibleMapPointIndices[i])"
/>
<!-- 4 停车点 -->
<img
@ -639,7 +644,7 @@
src="@/assets/imgs/indexPage/stop-car.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
:style="nodeStyle(item, visibleMapPointIndices[i])"
/>
<!-- 5 区域变更点 -->
<img
@ -647,7 +652,7 @@
src="@/assets/imgs/indexPage/change-point.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
:style="nodeStyle(item, visibleMapPointIndices[i])"
/>
<!-- 6 等待点 -->
<img
@ -655,7 +660,7 @@
src="@/assets/imgs/indexPage/wait-point.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
:style="nodeStyle(item, visibleMapPointIndices[i])"
/>
</div>
</el-tooltip>
@ -668,7 +673,10 @@
color: item.fontColor,
fontSize: item.fontSize + 'px',
fontFamily: item.fontFamily,
border: state.currentItemIndex === index ? '.0625rem dashed #000' : 'none'
border:
state.currentItemIndex === visibleMapPointIndices[i]
? '.0625rem dashed #000'
: 'none'
}"
>
{{ item.text }}
@ -718,8 +726,8 @@
stroke="#2d72d9"
:stroke-width="state.routeWidthForm.routeWidth"
/>
<template v-if="state.mapRouteList.length > 0">
<template v-for="(curve, index) in state.mapRouteList" :key="index">
<template v-if="visibleMapRoutes && visibleMapRoutes.length > 0">
<template v-for="(curve, i) in visibleMapRoutes" :key="visibleMapRouteIndices[i]">
<!-- 直线 -->
<template v-if="curve.method === 0">
<line
@ -729,8 +737,10 @@
:y2="Number(curve.endPointY)"
:stroke="curve.isSelected ? '#f48924' : '#2d72d9'"
:stroke-width="state.routeWidthForm.routeWidth"
@click="(e) => handleChooseRoute(curve, index, 'line', e)"
@contextmenu="handleCurveContextMenu(curve, index, $event)"
@click="(e) => handleChooseRoute(curve, visibleMapRouteIndices[i], 'line', e)"
@contextmenu="
handleCurveContextMenu(curve, visibleMapRouteIndices[i], $event)
"
/>
<text
style="user-select: none"
@ -740,7 +750,7 @@
text-anchor="middle"
fill="black"
v-if="curve.isSelected"
@click="(e) => handleChooseRoute(curve, index, 'line', e)"
@click="(e) => handleChooseRoute(curve, visibleMapRouteIndices[i], 'line', e)"
>
{{ calculateRouteLength(curve, 'line') }}
</text>
@ -754,8 +764,10 @@
:stroke="curve.isSelected ? '#f48924' : '#2d72d9'"
:stroke-width="state.routeWidthForm.routeWidth"
fill="none"
@click="handleChooseRoute(curve, index)"
@contextmenu="handleCurveContextMenu(curve, index, $event)"
@click="handleChooseRoute(curve, visibleMapRouteIndices[i])"
@contextmenu="
handleCurveContextMenu(curve, visibleMapRouteIndices[i], $event)
"
/>
<text
style="user-select: none"
@ -765,14 +777,14 @@
text-anchor="middle"
fill="black"
v-if="curve.isSelected"
@click="handleChooseRoute(curve, index)"
@click="handleChooseRoute(curve, visibleMapRouteIndices[i])"
>
{{ calculateRouteLength(curve, 'curve') }}
</text>
<!-- 第一条控制线 -->
<line
v-if="state.currentDragTarget.index == index"
v-if="state.currentDragTarget.index == visibleMapRouteIndices[i]"
:x1="Number(curve.startPointX)"
:y1="Number(curve.startPointY)"
:x2="curve.beginControlX"
@ -783,7 +795,7 @@
/>
<!-- 第二条控制线 -->
<line
v-if="state.currentDragTarget.index == index"
v-if="state.currentDragTarget.index == visibleMapRouteIndices[i]"
:x1="Number(curve.endPointX)"
:y1="Number(curve.endPointY)"
:x2="curve.endControlX"
@ -801,6 +813,7 @@
<template
v-if="
state.currentDragTarget.index !== null &&
state.currentDragTarget.index < state.mapRouteList.length &&
state.mapRouteList[state.currentDragTarget.index].method !== 0
"
>
@ -1068,7 +1081,8 @@
<script setup>
import JSONBigInt from 'json-bigint'
import { ElLoading } from 'element-plus'
import { ref, defineComponent, reactive, nextTick, onMounted } from 'vue'
import { ref, defineComponent, reactive, nextTick, onMounted, onUnmounted, computed } from 'vue'
import editMapRouteDialog from './components-tool/editMapRouteDialog.vue'
import editNodeProperties from './components-tool/editNodeProperties.vue'
import textFormToolDialog from './components-tool/textFormToolDialog.vue'
@ -1092,6 +1106,204 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
defineOptions({ name: 'EditMapPageRealTimeMap' })
// -
const throttle = (fn, delay) => {
let lastTime = 0
let timeoutId = null
return function (...args) {
const now = Date.now()
if (now - lastTime >= delay) {
lastTime = now
return fn.apply(this, args)
} else {
//
if (timeoutId) clearTimeout(timeoutId)
//
timeoutId = setTimeout(
() => {
lastTime = Date.now()
fn.apply(this, args)
},
delay - (now - lastTime)
)
}
}
}
//
const viewport = reactive({
left: 0,
top: 0,
width: 0,
height: 0,
scale: 1,
lastUpdateTime: 0
})
//
const updateViewport = throttle(() => {
performanceMonitor.startRender()
const container = mapContainerRef.value
if (!container) return
viewport.left = container.scrollLeft / state.imageChangeMultiple
viewport.top = container.scrollTop / state.imageChangeMultiple
viewport.width = container.clientWidth / state.imageChangeMultiple
viewport.height = container.clientHeight / state.imageChangeMultiple
viewport.scale = state.imageChangeMultiple
viewport.lastUpdateTime = Date.now()
//
nextTick(() => {
performanceMonitor.endRender()
})
}, 16)
//
const visibleMapPointsWithIndex = computed(() => {
try {
//
const padding = 300 //
const left = viewport.left - padding
const top = viewport.top - padding
const right = viewport.left + viewport.width + padding
const bottom = viewport.top + viewport.height + padding
// 使
const visibleGridKeys = new Set()
//
const startGridX = Math.floor(left / GRID_SIZE)
const startGridY = Math.floor(top / GRID_SIZE)
const endGridX = Math.ceil(right / GRID_SIZE)
const endGridY = Math.ceil(bottom / GRID_SIZE)
//
for (let gridX = startGridX; gridX <= endGridX; gridX++) {
for (let gridY = startGridY; gridY <= endGridY; gridY++) {
visibleGridKeys.add(`${gridX},${gridY}`)
}
}
//
updateGridMap(state.allMapPointInfo)
//
const visiblePoints = []
visibleGridKeys.forEach((key) => {
const points = gridMap.get(key)
if (points) {
points.forEach(({ point, index }) => {
if (
point.layerSelectionShow &&
Number(point.locationX) >= left &&
Number(point.locationX) <= right &&
Number(point.locationY) >= top &&
Number(point.locationY) <= bottom
) {
visiblePoints.push({ point, index })
}
})
}
})
return visiblePoints
} catch (error) {
console.error('Error calculating visible points:', error)
return []
}
})
//
const visibleMapPoints = computed(() => visibleMapPointsWithIndex.value.map((item) => item.point))
const visibleMapPointIndices = computed(() =>
visibleMapPointsWithIndex.value.map((item) => item.index)
)
// 线
const visibleMapRoutesWithIndex = computed(() => {
try {
if (!state.isCurveDisplay) return []
//
const padding = 300 //
const left = viewport.left - padding
const top = viewport.top - padding
const right = viewport.left + viewport.width + padding
const bottom = viewport.top + viewport.height + padding
// 使线
// 线
const pointToRouteMap = new Map()
state.mapRouteList.forEach((route, index) => {
const startKey = getGridKey(route.startPointX, route.startPointY)
const endKey = getGridKey(route.endPointX, route.endPointY)
if (!pointToRouteMap.has(startKey)) pointToRouteMap.set(startKey, [])
if (!pointToRouteMap.has(endKey)) pointToRouteMap.set(endKey, [])
pointToRouteMap.get(startKey).push({ route, index })
if (startKey !== endKey) {
pointToRouteMap.get(endKey).push({ route, index })
}
})
//
const startGridX = Math.floor(left / GRID_SIZE)
const startGridY = Math.floor(top / GRID_SIZE)
const endGridX = Math.ceil(right / GRID_SIZE)
const endGridY = Math.ceil(bottom / GRID_SIZE)
// 使Set
const visibleRouteSet = new Set()
//
for (let gridX = startGridX; gridX <= endGridX; gridX++) {
for (let gridY = startGridY; gridY <= endGridY; gridY++) {
const key = `${gridX},${gridY}`
const routes = pointToRouteMap.get(key)
if (routes) {
routes.forEach(({ route, index }) => {
// 线
if (
(Number(route.startPointX) >= left &&
Number(route.startPointX) <= right &&
Number(route.startPointY) >= top &&
Number(route.startPointY) <= bottom) ||
(Number(route.endPointX) >= left &&
Number(route.endPointX) <= right &&
Number(route.endPointY) >= top &&
Number(route.endPointY) <= bottom)
) {
// 使
visibleRouteSet.add(index)
}
})
}
}
}
//
return Array.from(visibleRouteSet).map((index) => ({
route: state.mapRouteList[index],
index
}))
} catch (error) {
console.error('Error calculating visible routes:', error)
return []
}
})
// 线
const visibleMapRoutes = computed(() => visibleMapRoutesWithIndex.value.map((item) => item.route))
const visibleMapRouteIndices = computed(() =>
visibleMapRoutesWithIndex.value.map((item) => item.index)
)
const BatchEditRoutePropertiesDialogRef = ref() //线
const BatchEditNodePropertiesDialogRef = ref() //
const GenerateStraightLinesDialogRef = ref() //线
@ -1445,9 +1657,8 @@ const mapClick = (e) => {
} else {
message.warning('该点位已经存在节点')
}
}
//
else if (toolbarSwitchType.value === 'text') {
} else if (toolbarSwitchType.value === 'text') {
//
state.showInputBox = true
state.inputBoxStyle = {
locationX: x,
@ -1460,9 +1671,8 @@ const mapClick = (e) => {
setTimeout(() => {
inputBoxRef.value?.focus()
}, 0)
}
//
else if (toolbarSwitchType.value === 'ranging') {
} else if (toolbarSwitchType.value === 'ranging') {
//
measureDistancesClick(e)
}
} catch (error) {
@ -2680,33 +2890,6 @@ const resetDrawState = () => {
state.currentDrawY = 0
}
// -
const throttle = (fn, delay) => {
let lastTime = 0
let timeoutId = null
return function (...args) {
const now = Date.now()
if (now - lastTime >= delay) {
lastTime = now
return fn.apply(this, args)
} else {
//
if (timeoutId) clearTimeout(timeoutId)
//
timeoutId = setTimeout(
() => {
lastTime = Date.now()
fn.apply(this, args)
},
delay - (now - lastTime)
)
}
}
}
//
const startDrawSelection = (event) => {
if (
@ -3593,7 +3776,10 @@ const handleDrag = (event) => {
let x = disposeEventPoints(event).x
let y = disposeEventPoints(event).y
if (state.currentDragTarget.index !== null) {
if (
state.currentDragTarget.index !== null &&
state.currentDragTarget.index < state.mapRouteList.length
) {
const curve = state.mapRouteList[state.currentDragTarget.index]
//
@ -3611,17 +3797,22 @@ const handleDrag = (event) => {
}
//
const endDrag = (event) => {
const curve = state.mapRouteList[state.currentDragTarget.index]
let actualBeginControl = disposeEventPoint(curve.beginControlX, curve.beginControlY)
let actualEndControl = disposeEventPoint(curve.endControlX, curve.endControlY)
if (
state.currentDragTarget.index !== null &&
state.currentDragTarget.index < state.mapRouteList.length
) {
const curve = state.mapRouteList[state.currentDragTarget.index]
let actualBeginControl = disposeEventPoint(curve.beginControlX, curve.beginControlY)
let actualEndControl = disposeEventPoint(curve.endControlX, curve.endControlY)
curve.actualBeginControlX = actualBeginControl.actualLocationX
curve.actualBeginControlY = actualBeginControl.actualLocationY
curve.actualBeginControlX = actualBeginControl.actualLocationX
curve.actualBeginControlY = actualBeginControl.actualLocationY
curve.actualEndControlX = actualEndControl.actualLocationX
curve.actualEndControlY = actualEndControl.actualLocationY
curve.actualEndControlX = actualEndControl.actualLocationX
curve.actualEndControlY = actualEndControl.actualLocationY
addEditHistory()
addEditHistory()
}
state.currentDragTarget.type = null
window.removeEventListener('mousemove', handleDrag)
@ -3774,7 +3965,12 @@ const handleChooseRoute = async (item, index, type, e) => {
state.allMapPointInfo.push(newPoint)
state.mapRouteList.push(positionMapLineOne)
state.mapRouteList.push(positionMapLineTwo)
state.mapRouteList.splice(index, 1)
//
if (index >= 0 && index < state.mapRouteList.length) {
state.mapRouteList.splice(index, 1)
}
state.allHistoryList[0] = {
allMapPointInfo: cloneMapData(state.allMapPointInfo),
mapRouteList: cloneMapData(state.mapRouteList)
@ -3786,17 +3982,20 @@ const handleChooseRoute = async (item, index, type, e) => {
}
}
} else {
state.mapRouteList.forEach((curve, i) => {
curve.isSelected = i === index
})
state.selectedCurve = item
state.currentDragTarget.index = index
//
state.currentItemIndex = -1
//
if (index >= 0 && index < state.mapRouteList.length) {
state.mapRouteList.forEach((curve, i) => {
curve.isSelected = i === index
})
state.selectedCurve = item
state.currentDragTarget.index = index
//
state.currentItemIndex = -1
if (toolbarSwitchType.value === 'editRoute') {
removeEventListener() //
editMapRouteDialogRef.value.open(deepClone(item))
if (toolbarSwitchType.value === 'editRoute') {
removeEventListener() //
editMapRouteDialogRef.value.open(deepClone(item))
}
}
}
}
@ -3970,25 +4169,41 @@ const calculateRouteLength = (item, type) => {
// -
const measureDistancesClick = (event) => {
const { x, y } = disposeEventPoints(event)
const points = state.measureDistancesPoints
try {
const { x, y, actualLocationX, actualLocationY } = disposeEventPoints(event)
if (x === undefined || y === undefined || isNaN(x) || isNaN(y)) {
console.error('Invalid coordinates in measureDistancesClick')
return
}
if (points.length === 2) {
//
const points = state.measureDistancesPoints
if (points.length === 2) {
//
state.measureDistancesPoints = []
state.measureDistancesNum = null
} else {
//
state.measureDistancesPoints.push({
x: Number(x),
y: Number(y),
actualX: Number(actualLocationX),
actualY: Number(actualLocationY)
})
if (points.length === 2) {
//
const [point1, point2] = state.measureDistancesPoints
const dx = point2.x - point1.x
const dy = point2.y - point1.y
const distancesNum = Math.sqrt(dx * dx + dy * dy)
state.measureDistancesNum = distancesNum * Number(imgBgObj.resolution)
}
}
} catch (error) {
console.error('Error in measureDistancesClick:', error)
state.measureDistancesPoints = []
state.measureDistancesNum = null
} else {
//
state.measureDistancesPoints.push({ x, y })
if (points.length === 1) {
//
const [point1, point2] = state.measureDistancesPoints
const dx = point2.x - point1.x
const dy = point2.y - point1.y
const distancesNum = Math.sqrt(dx * dx + dy * dy)
state.measureDistancesNum = distancesNum * Number(imgBgObj.resolution)
}
}
}
//
@ -4545,25 +4760,61 @@ const handleWheel = (event) => {
//
event.preventDefault()
//
const rect = mapContainerRef.value.getBoundingClientRect()
const mouseX = event.clientX - rect.left
const mouseY = event.clientY - rect.top
//
const scrollLeft = mapContainerRef.value.scrollLeft
const scrollTop = mapContainerRef.value.scrollTop
const mouseMapX = mouseX + scrollLeft
const mouseMapY = mouseY + scrollTop
//
const oldScale = state.imageChangeMultiple
//
if (event.deltaY < 0) {
//
//
if (state.imageChangeMultiple < 4) {
state.imageChangeMultiple += 0.2
} else {
state.imageChangeMultiple = 3.8
message.warning('不能在放大了')
state.imageChangeMultiple = 4
message.warning('不能放大了')
}
} else {
//
//
if (state.imageChangeMultiple > 0.2) {
state.imageChangeMultiple -= 0.1
} else {
state.imageChangeMultiple = 0.1
message.warning('不能缩小了')
state.imageChangeMultiple = 0.2
message.warning('不能缩小了')
}
}
//
const newScale = state.imageChangeMultiple
const scaleFactor = newScale / oldScale
// 使
nextTick(() => {
try {
mapContainerRef.value.scrollLeft = mouseMapX * scaleFactor - mouseX
mapContainerRef.value.scrollTop = mouseMapY * scaleFactor - mouseY
//
viewport.scale = state.imageChangeMultiple
updateViewport()
} catch (error) {
console.error('Error adjusting scroll position after zoom:', error)
}
})
} else {
//
nextTick(() => {
updateViewport()
})
}
}
@ -4604,10 +4855,28 @@ onMounted(async () => {
}, 1000)
}
window.addEventListener('keydown', handleKeyDown)
//
const container = mapContainerRef.value
if (container) {
container.addEventListener('scroll', updateViewport)
window.addEventListener('resize', updateViewport)
}
//
nextTick(() => {
updateViewport()
})
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown)
const container = mapContainerRef.value
if (container) {
container.removeEventListener('scroll', updateViewport)
window.removeEventListener('resize', updateViewport)
}
})
// 线
@ -4627,72 +4896,127 @@ const gridMap = new Map() // 网格映射
//
const getGridKey = (x, y) => {
const gridX = Math.floor(x / GRID_SIZE)
const gridY = Math.floor(y / GRID_SIZE)
if (x === undefined || y === undefined || isNaN(x) || isNaN(y)) {
return '0,0' //
}
const gridX = Math.floor(Number(x) / GRID_SIZE)
const gridY = Math.floor(Number(y) / GRID_SIZE)
return `${gridX},${gridY}`
}
//
const updateGridMap = (points) => {
if (!Array.isArray(points)) return
gridMap.clear()
points.forEach((point, index) => {
if (!point?.locationX || !point?.locationY) return
const key = getGridKey(point.locationX, point.locationY)
if (!gridMap.has(key)) {
gridMap.set(key, [])
try {
const key = getGridKey(point.locationX, point.locationY)
if (!gridMap.has(key)) {
gridMap.set(key, [])
}
gridMap.get(key).push({ point, index })
} catch (error) {
console.error('Error adding point to grid:', error)
}
gridMap.get(key).push({ point, index })
})
}
//
const getNearbyPoints = (x, y) => {
const centerKey = getGridKey(x, y)
const [centerX, centerY] = centerKey.split(',').map(Number)
const nearbyPoints = []
//
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
const key = `${centerX + dx},${centerY + dy}`
const points = gridMap.get(key)
if (points) {
nearbyPoints.push(...points)
}
}
if (x === undefined || y === undefined || isNaN(x) || isNaN(y)) {
return []
}
return nearbyPoints
try {
const centerKey = getGridKey(x, y)
const [centerX, centerY] = centerKey.split(',').map(Number)
const nearbyPoints = []
//
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
const key = `${centerX + dx},${centerY + dy}`
const points = gridMap.get(key)
if (points) {
nearbyPoints.push(...points)
}
}
}
return nearbyPoints
} catch (error) {
console.error('Error getting nearby points:', error)
return []
}
}
// findClosestPoint
const findClosestPoint = (x, y) => {
if (x === undefined || y === undefined || isNaN(x) || isNaN(y)) {
return null
}
const list = state.allMapPointInfo
if (!Array.isArray(list) || list.length === 0) return null
//
updateGridMap(list)
try {
//
updateGridMap(list)
let minDistance = Infinity
let closestIndex = null
let minDistance = Infinity
let closestIndex = null
//
const nearbyPoints = getNearbyPoints(x, y)
//
const nearbyPoints = getNearbyPoints(x, y)
//
for (const { point, index } of nearbyPoints) {
const dx = point.locationX - x
const dy = point.locationY - y
const distance = dx * dx + dy * dy // 使
const captureRadius = (point.locationWide || 10) ** 2
//
if (nearbyPoints.length === 0) {
const extendedSearchRadius = 2 //
for (let dx = -extendedSearchRadius; dx <= extendedSearchRadius; dx++) {
for (let dy = -extendedSearchRadius; dy <= extendedSearchRadius; dy++) {
//
if (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) continue
if (distance < minDistance && distance < captureRadius) {
minDistance = distance
closestIndex = index
const centerKey = getGridKey(x, y)
const [centerX, centerY] = centerKey.split(',').map(Number)
const key = `${centerX + dx},${centerY + dy}`
const points = gridMap.get(key)
if (points) {
nearbyPoints.push(...points)
}
}
}
}
}
return closestIndex
//
for (const { point, index } of nearbyPoints) {
if (!point.layerSelectionShow) continue //
const dx = Number(point.locationX) - Number(x)
const dy = Number(point.locationY) - Number(y)
const distance = dx * dx + dy * dy // 使
//
const pointSize = Math.max(
Number(point.locationWidePx) || 0,
Number(point.locationDeepPx) || 0
)
const captureRadius = Math.max(pointSize * pointSize, 100) //
if (distance < minDistance && distance < captureRadius) {
minDistance = distance
closestIndex = index
}
}
return closestIndex
} catch (error) {
console.error('Error finding closest point:', error)
return null
}
}
// 线
@ -4703,7 +5027,11 @@ const handleCurveContextMenu = (curve, index, event) => {
state.curveContextMenu.y = event.clientY
state.curveContextMenu.currentCurve = curve
state.curveContextMenu.currentIndex = index
state.currentDragTarget.index = index
//
if (index >= 0 && index < state.mapRouteList.length) {
state.currentDragTarget.index = index
}
}
// 线
@ -4850,6 +5178,42 @@ function handleNodeClick(item) {
state.actualLocation.x = actualLocationX ? Number(actualLocationX).toFixed(3) : ''
state.actualLocation.y = actualLocationY ? Number(actualLocationY).toFixed(3) : ''
}
//
const performanceMonitor = {
lastRenderTime: 0,
renderCount: 0,
renderTimes: [],
startRender() {
this.lastRenderTime = performance.now()
},
endRender() {
if (this.lastRenderTime === 0) return
const renderTime = performance.now() - this.lastRenderTime
this.renderTimes.push(renderTime)
this.renderCount++
// 100
if (this.renderTimes.length > 100) {
this.renderTimes.shift()
}
// 20
if (this.renderCount % 20 === 0) {
const avg = this.renderTimes.reduce((sum, time) => sum + time, 0) / this.renderTimes.length
const max = Math.max(...this.renderTimes)
const min = Math.min(...this.renderTimes)
console.log(
`渲染性能 - 平均: ${avg.toFixed(2)}ms, 最大: ${max.toFixed(2)}ms, 最小: ${min.toFixed(2)}ms, 可见点: ${visibleMapPoints?.value?.length || 0}, 可见路线: ${visibleMapRoutes?.value?.length || 0}`
)
}
this.lastRenderTime = 0
}
}
</script>
<style lang="scss" scoped>