1、视口渲染优化:只渲染当前可见区域的节点和路线,在滚动和缩放时更新视口信息,添加了边界扩展确保拖动时不会出现闪烁。
2、空间索引优化:使用网格式空间索引将地图划分为网格单元,根据坐标将点位分配到对应网格,搜索时只检查相关网格中的点位。 3、改进鼠标滚轮放大缩小地图功能,改为保持鼠标指向点不变
This commit is contained in:
parent
7ff85039ba
commit
b5785e88e1
@ -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">
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user