zn-admin-vue3-wcs/src/views/mapPage/realTimeMap/editMap.vue
2025-06-19 12:06:47 +08:00

4402 lines
140 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="edit-map-page" @wheel="handleWheel">
<div class="top-tool">
<div class="top-tool-list-container">
<div class="top-tool-list">
<div v-for="item in state.topToolList" :key="item.switchType" class="top-tool-item">
<el-popover
placement="bottom"
:width="170"
trigger="click"
v-if="item.switchType === 'move' || item.switchType === 'revolve'"
:disabled="state.currentItemIndex === -1"
>
<template #reference>
<div
class="tool-item"
:class="
toolbarSwitchType === item.switchType
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<!-- 位置 -->
<el-form :model="state.moveForm" v-if="item.switchType === 'move'" class="mt-2">
<el-form-item label="X">
<el-input-number
v-model="state.moveForm.locationX"
:min="0"
:max="imgBgObj.width"
controls-position="right"
/>
</el-form-item>
<el-form-item label="Y">
<el-input-number
v-model="state.moveForm.locationY"
:min="0"
:max="imgBgObj.height"
controls-position="right"
/>
</el-form-item>
<div style="text-align: right">
<el-button
size="small"
style="width: 4rem; height: 1.875rem; background: #00329f"
color="#00329F"
@click="moveFormSubmit()"
>确认</el-button
>
</div>
</el-form>
<!-- 旋转 -->
<el-form
:model="state.rotationForm"
v-if="item.switchType === 'revolve'"
class="mt-2"
>
<el-form-item label="角度">
<el-input-number
v-model="state.rotationForm.angle"
:min="0"
:max="259"
controls-position="right"
/>
</el-form-item>
<div style="text-align: right">
<el-button
size="small"
style="width: 4rem; height: 1.875rem; background: #00329f"
color="#00329F"
@click="rotationFormSubmit"
>确认</el-button
>
</div>
</el-form>
</el-popover>
<el-popover
placement="bottom"
:width="170"
trigger="click"
v-else-if="item.switchType === 'grid'"
>
<template #reference>
<div
class="tool-item"
:class="
toolbarSwitchType === item.switchType
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<!-- 网格 -->
<el-form :model="state.gridForm" v-if="item.switchType === 'grid'" class="mt-2">
<el-form-item label="网格">
<el-select v-model="state.gridForm.gridNum" placeholder="选择">
<el-option label="0.5米" value="10px" />
<el-option label="1米" value="20px" />
<el-option label="2米" value="40px" />
<el-option label="3米" value="60px" />
</el-select>
</el-form-item>
</el-form>
</el-popover>
<!-- 线库 -->
<el-popover
placement="bottom"
trigger="click"
v-else-if="item.switchType === 'lineLibrary'"
:popper-style="{ padding: '0rem' }"
>
<template #reference>
<div
class="tool-item"
:class="
toolbarSwitchType === 'lineLibrary' ||
toolbarSwitchType === 'createLineLibrary' ||
toolbarSwitchType === 'lineLibraryManage'
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<div v-if="item.switchType === 'lineLibrary'" class="drop-down-menu">
<div
class="drop-down-menu-item"
:class="toolbarSwitchType === 'createLineLibrary' ? 'right-tool-active' : ''"
@click="toolbarClick({ switchType: 'createLineLibrary' })"
>生成线库</div
>
<div
class="drop-down-menu-item"
:class="toolbarSwitchType === 'lineLibraryManage' ? 'right-tool-active' : ''"
@click="toolbarClick({ switchType: 'lineLibraryManage' })"
>线库管理</div
>
</div>
</el-popover>
<!-- 区域 -->
<el-popover
placement="bottom"
trigger="click"
v-else-if="item.switchType === 'region'"
:popper-style="{ padding: '0rem' }"
>
<template #reference>
<div
class="tool-item"
:class="
toolbarSwitchType === 'region' ||
toolbarSwitchType === 'createRegion' ||
toolbarSwitchType === 'regionManage'
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<div v-if="item.switchType === 'region'" class="drop-down-menu">
<div
class="drop-down-menu-item"
:class="toolbarSwitchType === 'createRegion' ? 'right-tool-active' : ''"
@click="toolbarClick({ switchType: 'createRegion' })"
>生成物料区域</div
>
<div
class="drop-down-menu-item"
:class="toolbarSwitchType === 'regionManage' ? 'right-tool-active' : ''"
@click="toolbarClick({ switchType: 'regionManage' })"
>物料区域管理</div
>
</div>
</el-popover>
<!-- 标记 -->
<el-popover
placement="bottom"
trigger="click"
v-else-if="item.switchType === 'marker'"
width="220"
:visible="state.markPopoverVisible"
>
<template #reference>
<div
class="tool-item"
:class="
toolbarSwitchType === item.switchType
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<!-- 位置 -->
<el-form :model="state.markForm" class="mt-2" label-width="68">
<el-form-item label="标记车辆">
<el-select
v-model="state.markForm.macAddress"
placeholder="请选择"
@change="macAddressChange"
>
<el-option
v-for="car in state.mapMarkCarList"
:key="car.id"
:label="car.robotNo"
:value="car.macAddress"
/>
</el-select>
</el-form-item>
<el-form-item label="标记属性" v-if="state.markForm.markProperty">
<el-input v-model="state.markForm.markProperty" style="width: 15rem" disabled />
</el-form-item>
<el-form-item label="原节点" v-if="state.markForm.originalNode">
<el-input v-model="state.markForm.originalNode" style="width: 15rem" disabled />
</el-form-item>
<div style="text-align: right">
<el-button
size="small"
style="width: 4rem; height: 1.875rem; background: #efefef"
@click="markFormCancel()"
>取消</el-button
>
<el-button
size="small"
style="width: 4rem; height: 1.875rem; background: #00329f"
color="#00329F"
@click="markFormSubmit()"
>确认</el-button
>
</div>
</el-form>
</el-popover>
<div
v-else
class="tool-item"
:class="
toolbarSwitchType === item.switchType
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
<!-- 分隔线 -->
<div
class="line"
v-if="
item.switchType === 'save' ||
item.switchType === 'delete' ||
item.switchType === 'grid' ||
(item.switchType === 'next' &&
(toolbarSwitchType === 'createLineLibrary' ||
toolbarSwitchType === 'createRegion' ||
toolbarSwitchType === 'drawRoute' ||
toolbarSwitchType === 'generateLine' ||
toolbarSwitchType === 'batchCopying' ||
toolbarSwitchType === 'bulkDelete'))
"
></div>
<el-button
v-if="
item.switchType === 'next' &&
(toolbarSwitchType === 'createLineLibrary' ||
toolbarSwitchType === 'createRegion' ||
toolbarSwitchType === 'drawRoute' ||
toolbarSwitchType === 'generateLine' ||
toolbarSwitchType === 'batchCopying' ||
toolbarSwitchType === 'bulkDelete')
"
type="danger"
class="selection-area-btn"
@click="clickDrawSelectionArea"
>确定</el-button
>
</div>
</div>
<div class="search-select">
<img
class="search-icon"
v-if="!state.isSearchSelectVisible"
@click="toggleSelect"
src="@/assets/search.png"
/>
<el-select
class="!w-160px"
v-else
v-model="state.searchSelectedOption"
@blur="toggleSelect"
placeholder="请选择节点号"
@change="searchSelectChange"
filterable
>
<el-option
v-for="item in state.haveSortNumMapPointInfo"
:key="item.sortNum"
:label="item.sortNum"
:value="item.sortNum"
/>
</el-select>
</div>
</div>
<div class="right-tool-list" v-if="state.isShowToolbar">
<div
v-for="item in state.rightToolList"
:key="item.switchType"
class="tool-item"
:class="
toolbarSwitchType === item.switchType
? 'tool-active'
: item.isActive
? 'right-tool-active'
: ''
"
@click="toolbarClick(item)"
>
<el-popover
placement="left"
:width="170"
trigger="click"
v-if="item.switchType === 'routeWidth' || item.switchType === 'clickDrawRoute'"
>
<template #reference>
<div @click="toolbarClick(item)">
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<!-- 网格 -->
<el-form
:model="state.routeWidthForm"
v-if="item.switchType === 'routeWidth'"
class="mt-2"
>
<el-form-item label="路线宽度">
<el-select v-model="state.routeWidthForm.routeWidth" placeholder="选择">
<el-option label="1" :value="1" />
<el-option label="2" :value="2" />
<el-option label="3" :value="3" />
<el-option label="4" :value="4" />
<el-option label="5" :value="5" />
</el-select>
</el-form-item>
</el-form>
<!-- 新增路线 -->
<el-form v-if="item.switchType === 'clickDrawRoute'" class="mt-2">
<el-form-item label="路线方向">
<el-select v-model="state.routeDirection" placeholder="选择">
<el-option label="单向" :value="1" />
<el-option label="双向" :value="2" />
</el-select>
</el-form-item>
</el-form>
</el-popover>
<!-- 手动录入实际点位 -->
<el-popover
placement="left"
trigger="click"
v-else-if="item.switchType === 'entryActualNode'"
width="250"
:visible="state.entryNodePopoverVisible"
>
<template #reference>
<div @click="toolbarClick(item)">
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</template>
<!-- 位置 -->
<el-form :model="state.entryActualNodeForm" class="mt-2" label-width="78">
<el-form-item label="实际点位X">
<el-input v-model="state.entryActualNodeForm.x" placeholder="请输入" />
</el-form-item>
<el-form-item label="实际点位Y">
<el-input v-model="state.entryActualNodeForm.y" placeholder="请输入" />
</el-form-item>
<el-form-item label="弧度">
<el-input-number
v-model="state.entryActualNodeForm.locationYaw"
:min="-state.MathPI"
:max="state.MathPI"
placeholder="请输入"
:precision="2"
controls-position="right"
/>
</el-form-item>
<div style="text-align: right">
<el-button
size="small"
style="width: 4rem; height: 1.875rem; background: #efefef"
@click="entryActualNodeFormCancel()"
>取消</el-button
>
<el-button
size="small"
style="width: 4rem; height: 1.875rem; background: #00329f"
color="#00329F"
@click="entryActualNodeFormSubmit()"
>确认</el-button
>
</div>
</el-form>
</el-popover>
<div v-else>
<Icon :icon="item.icon" :size="20" />
<div class="name"> {{ item.name }} </div>
</div>
</div>
</div>
</div>
<div
class="map-container"
ref="mapContainerRef"
:style="{ cursor: state.cursorStyle }"
v-if="imgBgObj.width && imgBgObj.height"
>
<!-- <map-scale-tool :stepLength="50" :width="imgBgObj.width" :height="imgBgObj.height"> -->
<div
class="map-bg"
:style="{
backgroundImage: `url(${imgBgObj.imgUrl})`,
transform: `scale(${state.imageChangeMultiple})`,
transformOrigin: '0 0',
width: `${imgBgObj.width}px`,
height: `${imgBgObj.height}px`
}"
@mousedown="startDrawSelection"
@mousemove="updateDrawSelection"
@mouseup="endDrawSelection"
>
<div
ref="mapBackgroundRef"
class="map-box-inner"
@click="mapClick"
:style="gradientBackground"
v-if="interfaceRefreshed"
>
<VueDragResizeRotate
v-for="(item, index) in state.allMapPointInfo"
:key="index + Number(item.locationX)"
:parent="true"
:x="Number(item.locationX) - Number(item.locationWidePx) / 2"
:y="Number(item.locationY) - Number(item.locationDeepPx) / 2"
:w="Number(item.locationWidePx)"
:h="Number(item.locationDeepPx)"
: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)"
@deactivated="deactivatedHandle"
:draggable="!state.prohibitedOperation && item.draggable"
:resizable="!state.prohibitedOperation && item.resizable"
:rotatable="!state.prohibitedOperation && item.rotatable"
:lock-aspect-ratio="item.lockAspectRatio"
:scaleRatio="state.imageChangeMultiple"
style="border: none; z-index: 999"
@ref-line-params="getRefLineParams"
>
<!-- 节点合集 -->
<div
@mousedown="startFromPoint(index, $event)"
:style="{ width: item.locationWidePx + 'px', height: item.locationDeepPx + 'px' }"
>
<!-- 1 路径点 -->
<el-tooltip effect="dark" placement="top" trigger="click">
<template #content>
<div v-if="item.type === 1">
<div>序号:{{ item.sortNum || '节点未保存' }}</div>
</div>
<div v-else-if="item.type === 2">
<div>序号:{{ item.sortNum || '节点未保存' }}</div>
<div class="item-tooltip-name" v-if="item.laneId && item.laneName">
所属线库:{{ item.laneName }}
</div>
<div class="item-tooltip-name" v-if="item.areaId && item.areaName">
所属区域:{{ item.areaName }}
</div>
<div class="item-tooltip-name" v-if="item.areaId && item.skuInfo">
物料信息:{{ item.skuInfo }}
</div>
</div>
<div v-else-if="item.type === 3">
<div>序号:{{ item.sortNum || '节点未保存' }}</div>
<div class="item-tooltip-name" v-if="item.deviceType">
设备类型:{{ getDeviceTypeName(item.deviceType) }}
</div>
<div class="item-tooltip-name" v-if="item.deviceNo">
设备编号:{{ item.deviceNo }}
</div>
</div>
<div v-else>
<div>序号:{{ item.sortNum || '节点未保存' }}</div>
<div class="item-tooltip-name">
节点类型:{{
item.type == 4
? '停车点'
: item.type == 5
? '区域变更点'
: item.type == 6
? '等待点'
: ''
}}
</div>
</div>
</template>
<div>
<div
v-if="
item.type === 1 &&
item.layerSelectionShow &&
state.isShowSortNum &&
item.sortNum
"
class="sort-num"
:style="getSortNumStyle(item, index)"
>
{{ item.sortNum }}
</div>
<div
v-if="
item.type !== 1 &&
item.layerSelectionShow &&
state.isShowSortNum &&
item.sortNum
"
class="sort-num-location"
:style="getSortNumLocationStyle(item, index)"
>
{{ item.sortNum }}
</div>
<div
class="sort-num-location"
:style="getLocationNumberStyle(item, index)"
v-if="
toolbarSwitchType === 'createLineLibrary' &&
item.type === 2 &&
item.locationNumber
"
>
{{ item.locationNumber }}
</div>
<div
v-if="item.type === 1 && item.layerSelectionShow"
:style="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
backgroundColor: state.currentItemIndex === index ? '#5ecc62' : '#000',
borderRadius: '50%',
zIndex: 999
}"
>
</div>
<!-- 2 库位点 -->
<img
v-if="item.type === 2 && item.layerSelectionShow"
src="@/assets/imgs/indexPage/bin-location.png"
alt=""
:style="binLocationStyle(item, index)"
/>
<!-- 3 设备点 -->
<img
v-if="item.type === 3 && item.layerSelectionShow"
:src="item.mapImageUrl || '@/assets/imgs/indexPage/equipment.png'"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
/>
<!-- 4 停车点 -->
<img
v-if="item.type === 4 && item.layerSelectionShow"
src="@/assets/imgs/indexPage/stop-car.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
/>
<!-- 5 区域变更点 -->
<img
v-if="item.type === 5 && item.layerSelectionShow"
src="@/assets/imgs/indexPage/change-point.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
/>
<!-- 6 等待点 -->
<img
v-if="item.type === 6 && item.layerSelectionShow"
src="@/assets/imgs/indexPage/wait-point.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
/>
</div>
</el-tooltip>
</div>
<div
class="node-text"
v-if="item.type === 7 && item.layerSelectionShow"
:style="{
position: 'absolute',
color: item.fontColor,
fontSize: item.fontSize + 'px',
fontFamily: item.fontFamily,
border: state.currentItemIndex === index ? '.0625rem dashed #000' : 'none'
}"
>
{{ item.text }}
</div>
</VueDragResizeRotate>
<span
class="ref-line v-line"
v-for="(item, index) in state.vLine"
:key="'v_' + index"
v-show="item.display"
:style="{
left: item.position,
top: item.origin,
height: item.lineLength
}"
></span>
<span
class="ref-line h-line"
v-for="(item, index) in state.hLine"
:key="'h_' + index"
:style="{
top: item.position,
left: item.origin,
width: item.lineLength
}"
></span>
<div v-if="imgBgObj.width && imgBgObj.height">
<svg
:width="imgBgObj.width"
:height="imgBgObj.height"
:style="{
position: 'absolute',
top: 0,
left: 0,
zIndex: state.svgRouteZIndex
}"
id="svgId"
>
<!-- 实时绘制当前直线 -->
<line
v-if="state.isDrawing && toolbarSwitchType === 'clickDrawRoute'"
:x1="Number(state.startDrawPoint.locationX)"
:y1="Number(state.startDrawPoint.locationY)"
:x2="Number(state.currentDrawX)"
:y2="Number(state.currentDrawY)"
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="curve.method === 0">
<line
:x1="Number(curve.startPointX)"
:y1="Number(curve.startPointY)"
:x2="Number(curve.endPointX)"
:y2="Number(curve.endPointY)"
:stroke="curve.isSelected ? '#f48924' : '#2d72d9'"
:stroke-width="state.routeWidthForm.routeWidth"
@click="(e) => handleChooseRoute(curve, index, 'line', e)"
/>
<text
style="user-select: none"
:x="(Number(curve.startPointX) + Number(curve.endPointX)) / 2"
:y="(Number(curve.startPointY) + Number(curve.endPointY) - 18) / 2"
font-size="11"
text-anchor="middle"
fill="black"
v-if="curve.isSelected"
@click="(e) => handleChooseRoute(curve, index, 'line', e)"
>
{{ calculateRouteLength(curve, 'line') }}米
</text>
</template>
<template v-else>
<path
id="curvePath"
:d="getCurvePath(curve)"
:stroke="curve.isSelected ? '#f48924' : '#2d72d9'"
:stroke-width="state.routeWidthForm.routeWidth"
fill="none"
@click="handleChooseRoute(curve, index)"
/>
<text
style="user-select: none"
:x="computedCurveTextX(curve)"
:y="computedCurveTextY(curve)"
font-size="11"
text-anchor="middle"
fill="black"
v-if="curve.isSelected"
@click="handleChooseRoute(curve, index)"
>
{{ calculateRouteLength(curve, 'curve') }}米
</text>
<!-- 第一条控制线 -->
<line
v-if="state.currentDragTarget.index == index"
:x1="Number(curve.startPointX)"
:y1="Number(curve.startPointY)"
:x2="curve.beginControlX"
:y2="curve.beginControlY"
:stroke="curve.isSelected ? '#f48924' : '#2d72d9'"
stroke-dasharray="4"
stroke-width="2"
/>
<!-- 第二条控制线 -->
<line
v-if="state.currentDragTarget.index == index"
:x1="Number(curve.endPointX)"
:y1="Number(curve.endPointY)"
:x2="curve.endControlX"
:y2="curve.endControlY"
:stroke="curve.isSelected ? '#f48924' : '#2d72d9'"
stroke-dasharray="4"
stroke-width="2"
/>
<!-- 控制点circle已移至SVG末尾 -->
</template>
</template>
</template>
<!-- 控制点circle统一渲染保证层级最高 -->
<template v-if="state.currentDragTarget.index !== null">
<circle
id="startCircle"
:cx="state.mapRouteList[state.currentDragTarget.index].beginControlX"
:cy="state.mapRouteList[state.currentDragTarget.index].beginControlY"
r="5"
:fill="
state.mapRouteList[state.currentDragTarget.index].isSelected
? '#f48924'
: '#2d72d9'
"
@mousedown="
startDrag(
state.mapRouteList[state.currentDragTarget.index],
state.currentDragTarget.index,
'start'
)
"
/>
<circle
id="endCircle"
:cx="state.mapRouteList[state.currentDragTarget.index].endControlX"
:cy="state.mapRouteList[state.currentDragTarget.index].endControlY"
r="5"
:fill="
state.mapRouteList[state.currentDragTarget.index].isSelected
? '#f48924'
: '#2d72d9'
"
@mousedown="
startDrag(
state.mapRouteList[state.currentDragTarget.index],
state.currentDragTarget.index,
'end'
)
"
/>
</template>
</svg>
</div>
</div>
<!-- 框选区域 -->
<div
v-for="(box, index) in state.allDrawSelectionAreaBox"
:key="index"
:style="{
position: 'absolute',
left: `${box.x}px`,
top: `${box.y}px`,
width: `${box.width}px`,
height: `${box.height}px`,
border: '.125rem dashed #007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
zIndex: 999
}"
></div>
<!-- 当前框选区域 -->
<div
v-if="state.drawSelectionAreaShow"
:style="{
position: 'absolute',
left: `${state.drawSelectionAreaBox.x}px`,
top: `${state.drawSelectionAreaBox.y}px`,
width: `${state.drawSelectionAreaBox.width}px`,
height: `${state.drawSelectionAreaBox.height}px`,
border: '.125rem dashed #007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
zIndex: 9999
}"
></div>
<!-- 测距 绘制点和连线 -->
<template v-if="state.measureDistancesPoints.length > 0">
<!-- 测距的线 -->
<div v-if="state.measureDistancesPoints.length === 2" :style="rangingLineStyle"></div>
<!-- 显示距离信息 -->
<div v-if="state.measureDistancesNum !== null" :style="rangingTextStyle">
{{ state.measureDistancesNum.toFixed(2) }}
</div>
<!-- 测距的点 -->
<div
v-for="(point, index) in state.measureDistancesPoints"
:key="index"
:style="getRangingPointStyle(point)"
></div>
</template>
<!-- 文字输入区域 -->
<input
v-if="state.showInputBox"
ref="inputBoxRef"
v-model="state.inputBoxValue"
@keyup.enter="handleInputEnd"
@blur="handleInputEnd"
:style="{
fontSize: state.inputBoxStyle.fontSize + 'px',
fontFamily: state.inputBoxStyle.fontFamily,
color: state.inputBoxStyle.fontColor,
left: state.inputBoxStyle.locationX + 'px',
top: state.inputBoxStyle.locationY + 'px'
}"
:maxlength="30"
class="input-box-class"
/>
</div>
<!-- </map-scale-tool> -->
</div>
<div class="actual-location" v-if="state.actualLocation.x && state.actualLocation.y">
<div>X{{ state.actualLocation.x }}</div>
<div>Y{{ state.actualLocation.y }}</div>
</div>
<!-- 节点编辑 -->
<editNodeProperties
v-if="imgBgObj.positionMapId"
ref="editNodePropertiesRef"
:positionMapId="imgBgObj.positionMapId"
:imgBgObj="imgBgObj"
@submit-node-success="submitNodeSuccess"
@add-event-listener="addEventListener"
/>
<!-- 文字输入弹窗 -->
<textFormToolDialog
ref="textFormToolDialogRef"
v-if="state.textFormToolShow"
:inputBoxStyle="state.inputBoxStyle"
@text-form-success="textFormSuccess"
/>
<!-- 图层选择 -->
<layerSelectionToolDialog
v-if="state.isShowLayer"
ref="layerSelectionToolDialogRef"
@layer-selection-success="layerSelectionSuccess"
/>
<!-- 设备弹窗选择 -->
<equipmentToolDialog
ref="equipmentToolDialogRef"
:positionMapId="imgBgObj.positionMapId"
@add-event-listener="addEventListener"
/>
<!-- 区域选择 -->
<itemAreaSettingDialog
ref="itemAreaSettingDialogRef"
:positionMapId="imgBgObj.positionMapId"
@add-event-listener="addEventListener"
@item-area-setting-submit-success="itemAreaSettingSubmitSuccess"
/>
<!-- 线库设置 -->
<lineLibrarySettingDialog
ref="lineLibrarySettingDialogRef"
:positionMapId="imgBgObj.positionMapId"
@add-event-listener="addEventListener"
@submit-line-library-form-success="submitLineLibraryFormSuccess"
/>
<!-- 编辑地图路线 -->
<editMapRouteDialog
v-if="imgBgObj.positionMapId"
ref="editMapRouteDialogRef"
@edit-map-route-dialog-submit="editMapRouteDialogSubmit"
@add-event-listener="addEventListener"
:imgBgObj="imgBgObj"
/>
<!-- 线库管理 -->
<lineLibraryManagementDialog
ref="lineLibraryManagementDialogRef"
:positionMapId="imgBgObj.positionMapId"
@add-event-listener="addEventListener"
@line-library-management-delete="lineLibraryManagementDelete"
@line-library-management-edit="lineLibraryManagementEdit"
/>
<!-- 区域管理 -->
<itemAreaManagementDialog
ref="itemAreaManagementDialogRef"
:positionMapId="imgBgObj.positionMapId"
@add-event-listener="addEventListener"
@item-area-management-delete="itemAreaManagementDelete"
@item-area-management-edit="itemAreaManagementEdit"
/>
<!-- 生成直线 选择开始点和结束点 -->
<GenerateStraightLinesDialog
ref="GenerateStraightLinesDialogRef"
@generate-straight-lines-submit="GenerateStraightLinesSubmit"
/>
<!-- 批量复制节点和路线 -->
<BatchCopyingDialogForm
ref="BatchCopyingDialogFormRef"
:imgBgObj="imgBgObj"
@submit-batch-copying-form-success="submitBatchCopyingFormSuccess"
/>
</div>
</template>
<script setup>
import JSONBigInt from 'json-bigint'
import { ElLoading } from 'element-plus'
import { ref, defineComponent, reactive, nextTick, onMounted } from 'vue'
import editMapRouteDialog from './components-tool/editMapRouteDialog.vue'
import editNodeProperties from './components-tool/editNodeProperties.vue'
import textFormToolDialog from './components-tool/textFormToolDialog.vue'
import equipmentToolDialog from './components-tool/equipmentToolDialog.vue'
import itemAreaSettingDialog from './components-tool/itemAreaSettingDialog.vue'
import BatchCopyingDialogForm from './components-tool/BatchCopyingDialogForm.vue'
import lineLibrarySettingDialog from './components-tool/lineLibrarySettingDialog.vue'
import layerSelectionToolDialog from './components-tool/layerSelectionToolDialog.vue'
import itemAreaManagementDialog from './components-tool/itemAreaManagementDialog.vue'
import lineLibraryManagementDialog from './components-tool/lineLibraryManagementDialog.vue'
import GenerateStraightLinesDialog from './components-tool/GenerateStraightLinesDialog.vue'
import mapScaleTool from './components-tool/map-scale-tool.vue'
import * as MapApi from '@/api/map/map'
import cursorCollection from './cursorCollection'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
defineOptions({ name: 'EditMapPageRealTimeMap' })
const GenerateStraightLinesDialogRef = ref() //生成直线的
const lineLibraryManagementDialogRef = ref() //线库管理
const itemAreaManagementDialogRef = ref() //区域管理
const lineLibrarySettingDialogRef = ref() //线库设置
const itemAreaSettingDialogRef = ref() //物料区域设置
const BatchCopyingDialogFormRef = ref() //批量复制
const equipmentToolDialogRef = ref() //设备弹窗
const textFormToolDialogRef = ref() //文字输入弹窗
const editMapRouteDialogRef = ref() //编辑地图路线的弹窗
const mapBackgroundRef = ref()
const mapContainerRef = ref()
const inputBoxRef = ref() //文字输入框
const message = useMessage() // 消息弹窗
//其他节点的样式
const nodeStyle = (item, index) => {
return {
verticalAlign: 'top',
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border: state.currentItemIndex === index ? '.0625rem dashed #000' : 'none',
borderRadius: '.1875rem'
}
}
//sortNum路径点的样式
const getSortNumStyle = (item, index) => {
let leftNum = 0
if (item.sortNum.toString().length === 1) {
leftNum = 3
} else if (item.sortNum.toString().length === 2) {
leftNum = 7
} else if (item.sortNum.toString().length === 3) {
leftNum = 10
} else if (item.sortNum.toString().length === 4) {
leftNum = 14
} else if (item.sortNum.toString().length === 5) {
leftNum = 18
} else if (item.sortNum.toString().length === 6) {
leftNum = 21
} else if (item.sortNum.toString().length === 7) {
leftNum = 25
} else if (item.sortNum.toString().length === 8) {
leftNum = 28
} else if (item.sortNum.toString().length === 9) {
leftNum = 31
}
return {
left: Number(item.locationWidePx) / 2 - leftNum + 'px',
top: 6 + 'px'
}
}
//sortNum非路径点的样式
const getSortNumLocationStyle = (item, index) => {
let leftNum = 0
if (item.sortNum.toString().length === 1) {
leftNum = 3
} else if (item.sortNum.toString().length === 2) {
leftNum = 7
} else if (item.sortNum.toString().length === 3) {
leftNum = 10
} else if (item.sortNum.toString().length === 4) {
leftNum = 14
} else if (item.sortNum.toString().length === 5) {
leftNum = 18
} else if (item.sortNum.toString().length === 6) {
leftNum = 21
} else if (item.sortNum.toString().length === 7) {
leftNum = 25
} else if (item.sortNum.toString().length === 8) {
leftNum = 28
} else if (item.sortNum.toString().length === 9) {
leftNum = 31
}
return {
left: Number(item.locationWidePx) / 2 - leftNum + 'px',
top: Number(item.locationDeepPx) / 2 - 2 + 'px'
}
}
//排序的样式
const getLocationNumberStyle = (item, index) => {
let leftNum = 0
if (item.locationNumber.toString().length === 1) {
leftNum = 3
} else if (item.locationNumber.toString().length === 2) {
leftNum = 7
} else if (item.locationNumber.toString().length === 3) {
leftNum = 10
} else if (item.locationNumber.toString().length === 4) {
leftNum = 14
} else if (item.locationNumber.toString().length === 5) {
leftNum = 18
} else if (item.locationNumber.toString().length === 6) {
leftNum = 21
} else if (item.locationNumber.toString().length === 7) {
leftNum = 25
} else if (item.locationNumber.toString().length === 8) {
leftNum = 28
} else if (item.locationNumber.toString().length === 9) {
leftNum = 31
}
return {
left: Number(item.locationWidePx) / 2 - leftNum + 'px',
top: Number(item.locationDeepPx) / 2 - 2 + 'px'
}
}
//库位的样式
const binLocationStyle = (item, index) => {
let laneId
let areaId
if (state.currentItemIndex !== -1) {
laneId = state.allMapPointInfo[state.currentItemIndex].laneId
areaId = state.allMapPointInfo[state.currentItemIndex].areaId
}
return {
verticalAlign: 'top',
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border:
state.currentItemIndex === index
? '.0625rem dashed #000'
: state.noLocationNumberList.includes(index)
? '2px dashed red'
: laneId && item.laneId === laneId
? '2px dashed #ff6a00'
: areaId && item.areaId === areaId
? '2px dashed #00aeff'
: 'none'
}
}
// 缩放停止
const interfaceRefreshed = ref(true)
const resizeEnd = (locationX, locationY, w, h, item, index) => {
interfaceRefreshed.value = false
nextTick(() => {
let x = Number(locationX) + Number(item.locationWidePx) / 2
let y = Number(locationY) + Number(item.locationDeepPx) / 2
let width = Number(w) * imgBgObj.resolution * 100 //实际的宽高cm
let height = Number(h) * imgBgObj.resolution * 100
let actualPoint = disposeEventPoint(x, y)
state.allMapPointInfo[index].locationX = x
state.allMapPointInfo[index].locationY = y
state.allMapPointInfo[index].locationWide = width
state.allMapPointInfo[index].locationDeep = height
state.allMapPointInfo[index].locationWidePx = w
state.allMapPointInfo[index].locationDeepPx = h
state.allMapPointInfo[index].actualLocationX = actualPoint.actualLocationX
state.allMapPointInfo[index].actualLocationY = actualPoint.actualLocationY
//更改路线里的
state.mapRouteList.forEach((route) => {
if (item.id === route.startingPointId) {
route.startPointX = x
route.startPointY = y
route.actualStartPointX = actualPoint.actualLocationX
route.actualStartPointY = actualPoint.actualLocationY
}
if (item.id === route.endPointId) {
route.endPointX = x
route.endPointY = y
route.actualEndPointX = actualPoint.actualLocationX
route.actualEndPointY = actualPoint.actualLocationY
}
})
addEditHistory()
})
}
// 拖拽停止
const dragEnd = (locationX, locationY, item, index) => {
let x = Number(locationX) + Number(item.locationWidePx) / 2
let y = Number(locationY) + Number(item.locationDeepPx) / 2
if (x === item.locationX && y === item.locationY) return
let actualPoint = disposeEventPoint(x, y)
state.allMapPointInfo[index].locationX = x
state.allMapPointInfo[index].locationY = y
state.allMapPointInfo[index].actualLocationX = actualPoint.actualLocationX
state.allMapPointInfo[index].actualLocationY = actualPoint.actualLocationY
//更改路线里的
state.mapRouteList.forEach((route) => {
if (item.id === route.startingPointId) {
route.startPointX = x
route.startPointY = y
route.actualStartPointX = actualPoint.actualLocationX
route.actualStartPointY = actualPoint.actualLocationY
}
if (item.id === route.endPointId) {
route.endPointX = x
route.endPointY = y
route.actualEndPointX = actualPoint.actualLocationX
route.actualEndPointY = actualPoint.actualLocationY
}
})
addEditHistory()
}
// 旋转
const rotateEnd = (angle, item, index) => {
state.allMapPointInfo[index].angle = angle
addEditHistory()
}
//节点选中
const editNodePropertiesRef = ref()
const activatedHandle = (item, index) => {
if (toolbarSwitchType.value === 'editRoute') return
state.currentItemIndex = index
//让路线不选中
state.selectedCurve = ''
if (state.mapRouteList.length > 0 && state.currentDragTarget.index !== null) {
state.mapRouteList[state.currentDragTarget.index].isSelected = false
state.currentDragTarget = { index: null, type: null }
}
//显示出可走路线
state.isCanTakeRoutes = state.mapRouteList.forEach((route) => {
if (
route.startingPointId === item.id ||
(route.direction === 2 && route.endPointId === item.id)
) {
route.isSelected = true
} else {
route.isSelected = false
}
})
//节点编辑
if (toolbarSwitchType.value === 'editNode' && item.type !== 7) {
let list = state.allMapPointInfo.filter((item) => item.type === 3)
removeEventListener() //移除监听
editNodePropertiesRef.value.open(JSON.parse(JSON.stringify(item)), list)
}
}
//非选中
const deactivatedHandle = () => {}
//添加历史记录
const addEditHistory = () => {
//要判断是不是在最新的记录上修改 如果不是 要把currentIndex之后的记录都删掉 保持当前修改是最新的
if (state.currentIndex < state.allHistoryList.length - 1) {
state.allHistoryList.splice(state.currentIndex + 1)
}
state.allHistoryList.push({
allMapPointInfo: JSON.parse(JSON.stringify(state.allMapPointInfo)),
mapRouteList: JSON.parse(JSON.stringify(state.mapRouteList))
})
state.currentIndex = state.allHistoryList.length - 1
interfaceRefreshed.value = true
}
//上一步
const backPreviousStep = () => {
if (state.currentIndex > 0) {
state.currentIndex--
state.allMapPointInfo.splice(
0,
state.allMapPointInfo.length,
...JSON.parse(JSON.stringify(state.allHistoryList[state.currentIndex]?.allMapPointInfo))
)
state.mapRouteList.splice(
0,
state.mapRouteList.length,
...JSON.parse(JSON.stringify(state.allHistoryList[state.currentIndex]?.mapRouteList))
)
} else {
message.warning('没了老铁')
}
}
//下一步
const backNextStep = () => {
if (state.currentIndex < state.allHistoryList.length - 1) {
state.currentIndex++
state.allMapPointInfo.splice(
0,
state.allMapPointInfo.length,
...JSON.parse(JSON.stringify(state.allHistoryList[state.currentIndex]?.allMapPointInfo))
)
state.mapRouteList.splice(
0,
state.mapRouteList.length,
...JSON.parse(JSON.stringify(state.allHistoryList[state.currentIndex]?.mapRouteList))
)
} else {
message.warning('没了老铁')
}
}
//地图点击
const mapClick = (e) => {
try {
// 提取坐标处理
const { x, y, actualLocationX, actualLocationY } = disposeEventPoints(e)
state.actualLocation.x = actualLocationX
state.actualLocation.y = actualLocationY
//新增节点
if (toolbarSwitchType.value === 'drawNodes') {
// 优化节点检查逻辑,比较一位小数
const isDuplicate = state.allMapPointInfo.some(
(item) =>
Number(item.locationX).toFixed(1) === Number(x).toFixed(1) &&
Number(item.locationY).toFixed(1) === Number(y).toFixed(1)
)
if (!isDuplicate) {
state.allMapPointInfo.push({
positionMapId: imgBgObj.positionMapId,
layerSelectionShow: true,
locationX: x,
locationY: y,
actualLocationX,
actualLocationY,
locationDeep: 40,
locationWide: 40,
locationDeepPx: 8,
locationWidePx: 8,
angle: 0,
draggable: true,
resizable: true,
rotatable: false,
lockAspectRatio: false,
mapImageUrl: '',
locationYaw: 0,
type: 1,
dataList: [],
dataObj: {}
})
addEditHistory()
} else {
message.warning('该点位已经存在节点')
}
}
//文字输入
else if (toolbarSwitchType.value === 'text') {
state.showInputBox = true
state.inputBoxStyle = {
locationX: x,
locationY: y,
actualLocationX,
actualLocationY
}
// 聚焦输入框
setTimeout(() => {
inputBoxRef.value?.focus()
}, 0)
}
//测距
else if (toolbarSwitchType.value === 'ranging') {
measureDistancesClick(e)
}
} catch (error) {
console.error('地图点击处理错误:', error)
}
}
//输入文字样式改变
const textFormSuccess = (form) => {
state.inputBoxStyle.fontSize = `${form.fontSize}`
state.inputBoxStyle.fontFamily = `${form.fontFamily}`
state.inputBoxStyle.fontColor = `${form.fontColor}`
}
//输入结束
const handleInputEnd = () => {
if (state.inputBoxValue) {
state.showInputBox = false
state.allMapPointInfo.push({
type: 7, //类型 7文字
positionMapId: imgBgObj.positionMapId, //地图的id
locationX: state.inputBoxStyle.locationX, //x
locationY: state.inputBoxStyle.locationY, //y
actualLocationX: state.inputBoxStyle.actualLocationX,
actualLocationY: state.inputBoxStyle.actualLocationY,
locationDeep: null, //h
locationWide: null, //w
angle: 0, //旋转角度
draggable: true, //是否可以拖动
resizable: false, //是否可以调整大小
rotatable: true, //是否可以旋转
lockAspectRatio: true, //是否锁定比例
mapImageUrl: '', //图片
text: state.inputBoxValue, //文字内容
fontColor: state.inputBoxStyle.fontColor, //文字颜色
fontFamily: state.inputBoxStyle.fontFamily, //字体类型
fontSize: state.inputBoxStyle.fontSize,
dataObj: {}, //存 设备点 停车点 文字
layerSelectionShow: true,
locationDeep: 20,
locationWide: 20,
locationDeepPx: 4,
locationWidePx: 4
})
addEditHistory()
state.inputBoxValue = ''
}
}
//编辑节点成功
const submitNodeSuccess = (form) => {
let actualPoint = disposeEventPoint(form.locationX, form.locationY)
form.locationDeepPx = Number(form.locationDeep) / 100 / imgBgObj.resolution
form.locationWidePx = Number(form.locationWide) / 100 / imgBgObj.resolution
form.actualLocationX = actualPoint.actualLocationX
form.actualLocationY = actualPoint.actualLocationY
state.allMapPointInfo[state.currentItemIndex] = form
//节点位置改变 通知路径里面的节点也改变
state.mapRouteList.forEach((item) => {
if (form.id === item.startingPointId) {
item.startPointX = form.locationX
item.startPointY = form.locationY
item.actualStartPointX = disposeEventPoint(form.locationX, form.locationY).actualLocationX
item.actualStartPointY = disposeEventPoint(form.locationX, form.locationY).actualLocationY
}
if (form.id === item.endPointId) {
item.endPointX = form.locationX
item.endPointY = form.locationY
item.actualEndPointX = disposeEventPoint(form.locationX, form.locationY).actualLocationX
item.actualEndPointY = disposeEventPoint(form.locationX, form.locationY).actualLocationY
}
})
addEditHistory()
//存在库位未填写排序号的问题
state.noLocationNumberList = state.allMapPointInfo.reduce((invalidIndexes, item, index) => {
if (item.type === 2 && !item.locationNumber) {
invalidIndexes.push(index) // 如果不满足条件,记录索引
}
return invalidIndexes
}, [])
}
//工具栏点击
const toolbarSwitchType = ref('')
const state = reactive({
topToolList: [
// {
// switchType: 'open',
// name: '打开',
// icon: 'ep:folder-add',
// isActive: false
// },
{
switchType: 'save',
name: '保存',
icon: 'ep:folder-checked',
isActive: false
},
// {
// switchType: 'saveAs',
// name: '另存为',
// icon: 'ep:folder-opened',
// isActive: false
// },
{
switchType: 'choose',
name: '选择',
icon: 'ep:position',
isActive: false
},
{
switchType: 'move',
name: '移动',
icon: 'ep:rank',
isActive: false
},
{
switchType: 'revolve',
name: '旋转',
icon: 'ep:refresh-right',
isActive: false
},
{
switchType: 'copy',
name: '复制',
icon: 'ep:document',
isActive: false
},
{
switchType: 'paste',
name: '粘贴',
icon: 'ep:copy-document',
isActive: false
},
{
switchType: 'delete',
name: '删除',
icon: 'ep:delete',
isActive: false
},
{
switchType: 'tools',
name: '工具',
icon: 'ep:briefcase',
isActive: true
},
{
switchType: 'lineLibrary',
name: '线库',
icon: 'ep:message-box',
isActive: false
},
{
switchType: 'region',
name: '区域',
icon: 'ep:full-screen',
isActive: false
},
{
switchType: 'text',
name: '文字',
icon: 'ep:edit-pen',
isActive: false
},
{
switchType: 'equipment',
name: '设备',
icon: 'ep:video-camera-filled',
isActive: false
},
{
switchType: 'ranging',
name: '测距',
icon: 'ep:folder-add',
isActive: false
},
{
switchType: 'layer',
name: '图层',
icon: 'ep:copy-document',
isActive: false
},
{
switchType: 'marker',
name: '标记',
icon: 'ep:map-location',
isActive: false
},
{
switchType: 'grid',
name: '网格',
icon: 'ep:grid',
isActive: false
},
{
switchType: 'larger',
name: '放大',
icon: 'ep:zoom-in',
isActive: false
},
{
switchType: 'smaller',
name: '缩小',
icon: 'ep:zoom-out',
isActive: false
},
{
switchType: 'withdraw',
name: '撤回',
icon: 'ep:top-left',
isActive: false
},
{
switchType: 'next',
name: '重做',
icon: 'ep:top-right',
isActive: false
}
],
rightToolList: [
{
switchType: 'drawNodes',
name: '新增节点',
icon: 'ep:circle-plus-filled',
isActive: false
},
{
switchType: 'editNode',
name: '编辑节点',
icon: 'ep:edit',
isActive: false
},
{
switchType: 'clickDrawRoute',
name: '新增路线',
icon: 'ep:semi-select',
isActive: false
},
// {
// switchType: 'drawRoute',
// name: '框选绘制',
// icon: 'ep:semi-select',
// isActive: false
// },
{
switchType: 'editRoute',
name: '编辑路线',
icon: 'ep:edit-pen',
isActive: false
},
{
switchType: 'generateLine',
name: '生成直线',
icon: 'ep:finished',
isActive: false
},
{
switchType: 'bulkDelete',
name: '批量删除',
icon: 'ep:delete',
isActive: false
},
{
switchType: 'batchCopying',
name: '批量复制',
icon: 'ep:document-copy',
isActive: false
},
{
switchType: 'showSortNum',
name: '节点序号',
icon: 'ep:chat-dot-square',
isActive: false
},
{
switchType: 'routeWidth',
name: '路线宽度',
icon: 'ep:semi-select',
isActive: false
},
{
switchType: 'addPointOnline',
name: '线上加点',
icon: 'ep:add-location',
isActive: false
},
{
switchType: 'entryActualNode',
name: '录入节点',
icon: 'ep:avatar',
isActive: false
},
{
switchType: 'nodePictureHidden',
name: '图片隐藏',
icon: 'ep:hide',
isActive: false
}
],
isShowToolbar: true, //工具栏展示隐藏
isShowGrid: false, //网格展示隐藏
moveForm: {
locationX: 0,
locationY: 0
}, //移动的表单
rotationForm: {
angle: 0
}, //旋转的表单
copyMapItem: '', //复制的值
cursorStyle: 'auto',
drawSelectionAreaShow: false, //绘制框选区域
allDrawSelectionAreaBox: [], // 所有框选区域
drawSelectionAreaBox: { x: 0, y: 0, width: 0, height: 0 }, //绘制区域的位置,长宽
drawSelectionStartPoint: { x: 0, y: 0 }, //开始绘制的点位
drawSelectionPointList: [], //绘制选中的list
drawSelectionRouteList: [], //绘制选中的路线list
textFormToolShow: false, //文字表单显示隐藏
showInputBox: false, //输入框显示隐藏
inputBoxStyle: {
fontFamily: 'SimSun',
fontSize: '14',
fontColor: '#000000',
locationX: 0,
locationY: 0,
actualLocationX: 0,
actualLocationY: 0
}, //输入框的样式
inputBoxValue: '', //输入的值
isShowLayer: false, //图层显示
measureDistancesPoints: [], // 存储点击的点位
measureDistancesNum: 0, // 存储两点之间的距离
imageChangeMultiple: 1, //图片放大缩小的倍数
prohibitedOperation: false, //禁止操作 在框选测距等操作时,禁止所有拖动等操作
currentDragTarget: {
index: null,
type: null
}, //当前拖拽的目标(起点、终点、控制点)
selectedCurve: '', // 当前选中的曲线
startDrawPointIndex: -1, // 起始点的索引
startDrawPoint: null, //新增路线开始的点
isDrawing: false, //正在绘制
currentDrawX: 0, //正在绘制的x轴坐标
currentDrawY: 0, //正在绘制的y轴坐标
allHistoryList: [], //所有的历史记录 [{allMapPointInfo:[],mapRouteList:[]}]
allMapPointInfo: [], //当前页面上的所有节点的列表
mapRouteList: [], //当前页面上所有路线的列表
currentIndex: 0, //当前处在哪条历史记录
currentItemIndex: -1, //当前处在哪个节点上
markForm: {
macAddress: '', // mac地址
markProperty: '', //标记属性
originalNode: '', //原节点
robotNo: '' //车辆编号
}, //标记的表单
mapMarkCarList: [], //标记的车辆列表
markPopoverVisible: false, //标记弹窗
entryNodePopoverVisible: false, //录入点位弹窗
entryActualNodeForm: {
x: '',
y: '',
locationYaw: 0
}, //录入点位的表单
noLocationNumberList: [], //没有排序的库位index
isShowSortNum: false, //是否显示序号
actualLocation: {
x: '',
y: ''
},
gridForm: {
gridNum: 20 + 'px'
},
routeWidthForm: {
routeWidth: 3
},
isSearchSelectVisible: false, //是否显示选择框
searchSelectedOption: '',
haveSortNumMapPointInfo: [],
MathPI: Math.PI,
routeDirection: 2, //1单2双
vLine: [],
hLine: [],
svgRouteZIndex: 9
})
const getRefLineParams = (params) => {
const { vLine, hLine } = params
state.vLine = vLine
state.hLine = hLine
}
//网格的样式
const gradientBackground = computed(() => {
if (!state.isShowGrid)
return {
width: `${imgBgObj.width}px`,
height: `${imgBgObj.height}px`
}
return {
backgroundImage: `
linear-gradient(to right, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 1px, transparent 1px)
`,
backgroundSize: `
${state.gridForm.gridNum} ${state.gridForm.gridNum},
${state.gridForm.gridNum} ${state.gridForm.gridNum}
`,
width: `${imgBgObj.width}px`,
height: `${imgBgObj.height}px`
}
})
const toolbarClick = async (item) => {
let type = item.switchType
if (state.currentItemIndex === -1 && (type === 'move' || type === 'revolve' || type === 'copy')) {
message.warning('请先选择要操作的对象')
return
}
if (
type === 'delete' &&
state.currentDragTarget.index === null &&
state.currentItemIndex === -1
) {
message.warning('请先选择要操作的对象')
return
}
//粘贴
if (type === 'paste' && !state.copyMapItem) {
message.warning('请先复制对象')
return
}
if (
(type === 'tools' ||
type === 'text' ||
type === 'layer' ||
type === 'grid' ||
type === 'showSortNum' ||
type === 'nodePictureHidden') &&
toolbarSwitchType.value === type
) {
toolbarSwitchType.value = ''
} else {
toolbarSwitchType.value = type
}
//鼠标样式的问题
if (toolbarSwitchType.value !== 'text') {
state.cursorStyle = `auto`
state.textFormToolShow = false
state.showInputBox = false
state.inputBoxValue = ''
}
if (
toolbarSwitchType.value === 'generateLine' ||
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'bulkDelete' ||
toolbarSwitchType.value === 'batchCopying'
) {
state.cursorStyle = 'crosshair'
} else if (
toolbarSwitchType.value === 'addPointOnline' ||
toolbarSwitchType.value === 'editRoute' ||
toolbarSwitchType.value === 'drawNodes' ||
toolbarSwitchType.value === 'editNode' ||
toolbarSwitchType.value === 'clickDrawRoute'
) {
state.cursorStyle = 'pointer'
} else {
state.cursorStyle = `auto`
}
//编辑路线的时候更换svg层级
if (toolbarSwitchType.value === 'editRoute') {
state.svgRouteZIndex = 999
} else {
state.svgRouteZIndex = 9
}
//非测距
if (toolbarSwitchType.value !== 'ranging') {
state.measureDistancesPoints = [] // 清空存储点击的点位
state.measureDistancesNum = 0 // 清空存储两点之间的距离
}
//禁止操作 在框选测距等操作时,禁止所有拖动等操作
if (
toolbarSwitchType.value === 'drawNodes' ||
toolbarSwitchType.value === 'editNode' ||
toolbarSwitchType.value === 'clickDrawRoute' ||
toolbarSwitchType.value === 'drawRoute' ||
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'bulkDelete' ||
toolbarSwitchType.value === 'batchCopying' ||
toolbarSwitchType.value === 'editRoute' ||
toolbarSwitchType.value === 'generateLine'
) {
state.prohibitedOperation = true
} else {
state.prohibitedOperation = false
}
//工具切换 不适用框选的 要把框选的信息都删掉
state.drawSelectionAreaShow = false
state.allDrawSelectionAreaBox = []
state.drawSelectionPointList = []
switch (type) {
case 'open':
// 打开
break
case 'save':
//保存
await message.confirm('请确认是否保存?')
saveMap()
break
case 'saveAs':
//另存为 存为json文件 无法直接访问用户的文件系统 不能选择存在那个文件夹里面
const jsonString = JSON.stringify(state.allHistoryList[state.currentIndex], null, 2)
const blob = new Blob([jsonString], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'mapJson.json'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
message.success('下载成功')
break
case 'choose':
//选择
break
case 'move':
//移动
console.log(state.allMapPointInfo[state.currentItemIndex])
state.moveForm.locationX = Number(state.allMapPointInfo[state.currentItemIndex].locationX)
state.moveForm.locationY = Number(state.allMapPointInfo[state.currentItemIndex].locationY)
break
case 'revolve':
state.rotationForm.angle = Number(state.allMapPointInfo[state.currentItemIndex]?.angle) || 0
break
case 'copy':
//复制
replicationNode()
break
case 'paste':
//粘贴
pasteNode()
break
case 'delete':
//删除
if (state.currentItemIndex !== -1) {
let deleteId = state.allMapPointInfo[state.currentItemIndex].id
// 点删除之后 跟点相关的路线都要删除
state.mapRouteList = state.mapRouteList.filter(
(item) => item.startingPointId !== deleteId && item.endPointId !== deleteId
)
state.allMapPointInfo.splice(state.currentItemIndex, 1)
//恢复index
state.currentItemIndex = -1
}
if (state.currentDragTarget.index !== null) {
state.mapRouteList.splice(state.currentDragTarget.index, 1)
state.currentDragTarget.index = null
}
addEditHistory()
setTimeout(() => {
toolbarSwitchType.value = ''
}, 200)
break
case 'tools':
//工具
if (toolbarSwitchType.value === 'tools') {
state.isShowToolbar = true
item.isActive = true
} else {
state.isShowToolbar = false
item.isActive = false
}
break
case 'lineLibrary':
// 线库
break
case 'region':
// 区域
break
case 'createLineLibrary':
// 生成线库
state.isShowSortNum = false
state.rightToolList.forEach((item) => {
if (item.switchType === 'showSortNum') {
item.isActive = false
}
})
break
case 'createRegion':
// 生成区域
break
case 'lineLibraryManage':
// 线库管理
removeEventListener() //移除监听
lineLibraryManagementDialogRef.value.open()
break
case 'regionManage':
removeEventListener() //移除监听
itemAreaManagementDialogRef.value.open()
// 区域管理
break
case 'text':
//文字
if (toolbarSwitchType.value === 'text') {
state.textFormToolShow = true
state.cursorStyle = `url('${cursorCollection.input}'),auto`
} else {
state.cursorStyle = `auto`
state.textFormToolShow = false
state.showInputBox = false
state.inputBoxValue = ''
}
break
case 'ranging':
// 测距
break
case 'layer':
//图层
if (toolbarSwitchType.value === 'layer') {
state.isShowLayer = true
item.isActive = true
} else {
state.isShowLayer = false
item.isActive = false
}
break
case 'marker':
// 标记
mapMark()
break
case 'grid':
//网格
if (toolbarSwitchType.value === 'grid') {
state.isShowGrid = true
item.isActive = true
} else {
state.isShowGrid = false
item.isActive = false
}
break
case 'larger':
//放大
if (state.imageChangeMultiple < 4) {
state.imageChangeMultiple += 0.2
} else {
message.warning('不能在放大了')
}
break
case 'smaller':
//缩小
if (state.imageChangeMultiple > 0.2) {
state.imageChangeMultiple -= 0.2
} else {
message.warning('不能在缩小了')
}
break
case 'withdraw':
//上一步
backPreviousStep()
break
case 'next':
//下一步
backNextStep()
break
case 'drawNodes':
//新增节点
break
case 'editNode':
// 编辑节点
break
case 'drawRoute':
//新增路线
break
case 'editRoute':
// 编辑路线
break
case 'equipment':
// 设备
let equipmentList = state.allMapPointInfo.filter((item) => {
return item.type === 3
})
removeEventListener() //移除监听
equipmentToolDialogRef.value.open(equipmentList)
break
case 'generateLine':
// 生成直线
break
case 'bulkDelete':
// 批量删除
break
case 'batchCopying':
//批量复制
break
case 'showSortNum':
// 显示节点序号
//网格
if (toolbarSwitchType.value === 'showSortNum') {
state.isShowSortNum = true
item.isActive = true
} else {
state.isShowSortNum = false
item.isActive = false
}
break
case 'entryActualNode':
// 录入实际点位
state.entryNodePopoverVisible = true
break
case 'nodePictureHidden':
if (toolbarSwitchType.value === 'nodePictureHidden') {
state.allMapPointInfo.forEach((item) => {
if (item.type === 2) {
item.locationWidePx = 8
item.locationDeepPx = 8
}
})
item.isActive = true
} else {
state.allMapPointInfo.forEach((item) => {
//要将实际的cm改成px
if (item.type === 2) {
let pxObj = cmConversionPx(item.locationWide, item.locationDeep)
item.locationWidePx = pxObj.pWidth
item.locationDeepPx = pxObj.pHeight
}
})
item.isActive = false
}
//图片隐藏
break
}
}
//复制
const replicationNode = () => {
let copyMapItem = state.allMapPointInfo[state.currentItemIndex]
if (copyMapItem.type === 3) {
message.warning('设备不能复制!')
return
}
let locationX = Number(copyMapItem.locationX) + 20
let locationY = Number(copyMapItem.locationY) + 20
let actualPoint = disposeEventPoint(locationX, locationY)
if (copyMapItem.dataList.length > 0) {
copyMapItem.dataList.forEach((item) => {
delete item.id
delete item.locationNo
delete item.mapItemId
})
copyMapItem.dataJson = JSON.stringify(copyMapItem.dataList)
}
if (copyMapItem.dataObj && JSON.stringify(copyMapItem.dataObj) !== '{}') {
delete copyMapItem.dataObj.id
delete copyMapItem.dataObj.positionMapItemId
copyMapItem.dataJson = JSON.stringify(copyMapItem.dataObj)
}
state.copyMapItem = {
positionMapId: copyMapItem.positionMapId,
locationX: locationX,
locationY: locationY,
actualLocationX: actualPoint.actualLocationX,
actualLocationY: actualPoint.actualLocationY,
type: copyMapItem.type,
dataJson: copyMapItem.dataJson || '',
dataObj: copyMapItem.dataObj || {},
dataList: copyMapItem.dataList || [],
locationDeep: copyMapItem.locationDeep,
locationWide: copyMapItem.locationWide,
locationDeepPx: copyMapItem.locationDeepPx,
locationWidePx: copyMapItem.locationWidePx,
draggable: copyMapItem.draggable,
resizable: copyMapItem.resizable,
rotatable: copyMapItem.rotatable,
lockAspectRatio: copyMapItem.lockAspectRatio,
layerSelectionShow: copyMapItem.layerSelectionShow,
mapImageUrl: copyMapItem.mapImageUrl,
locationYaw: copyMapItem.locationYaw,
areaId: null,
locationNumber: null
}
message.success('复制成功')
}
//粘贴
const pasteNode = () => {
let copyObj = JSON.parse(JSON.stringify(state.copyMapItem))
state.allMapPointInfo.push(copyObj)
message.success('粘贴成功')
addEditHistory()
}
//移动工具表单提交
const moveFormSubmit = async () => {
if (state.moveForm.locationX && state.moveForm.locationY) {
let x = Number(state.moveForm.locationX)
let y = Number(state.moveForm.locationY)
let item = state.allMapPointInfo[state.currentItemIndex]
if (x === item.locationX && y === item.locationY) return
let actualPoint = disposeEventPoint(x, y)
item.locationX = x
item.locationY = y
item.actualLocationX = actualPoint.actualLocationX
item.actualLocationY = actualPoint.actualLocationY
//更改路线里的
state.mapRouteList.forEach((route) => {
if (item.id === route.startingPointId) {
route.startPointX = x
route.startPointY = y
route.actualStartPointX = actualPoint.actualLocationX
route.actualStartPointY = actualPoint.actualLocationY
}
if (item.id === route.endPointId) {
route.endPointX = x
route.endPointY = y
route.actualEndPointX = actualPoint.actualLocationX
route.actualEndPointY = actualPoint.actualLocationY
}
})
addEditHistory()
} else {
message.warning('请输入x和y')
}
}
//旋转工具表单提交
const rotationFormSubmit = () => {
state.allMapPointInfo[state.currentItemIndex].angle = state.rotationForm.angle
addEditHistory()
}
//录入实际点位
const entryActualNodeFormCancel = () => {
state.entryNodePopoverVisible = false
state.entryActualNodeForm.x = ''
state.entryActualNodeForm.y = ''
state.entryActualNodeForm.locationYaw = 0
}
const entryActualNodeFormSubmit = () => {
if (!state.entryActualNodeForm.x || !state.entryActualNodeForm.y) {
message.warning('请输入实际点位X和Y')
return
} else {
let pointPx = convertActualToBrowser(state.entryActualNodeForm.x, state.entryActualNodeForm.y)
state.allMapPointInfo.push({
positionMapId: imgBgObj.positionMapId, //地图的id
layerSelectionShow: true,
locationX: pointPx.x,
locationY: pointPx.y,
actualLocationX: state.entryActualNodeForm.x,
actualLocationY: state.entryActualNodeForm.y,
locationDeep: 40,
locationWide: 40,
locationDeepPx: 8,
locationWidePx: 8,
angle: 0,
draggable: true,
resizable: true,
rotatable: false,
lockAspectRatio: false, //横纵比
mapImageUrl: '',
locationYaw: state.entryActualNodeForm.locationYaw, //弧度
type: 1, //默认类型1 路径节点
dataList: [], //存库位的
dataObj: {} //存 设备点 停车点 文字
})
addEditHistory()
state.entryNodePopoverVisible = false
message.success('录入成功')
setTimeout(() => {
state.entryActualNodeForm.x = ''
state.entryActualNodeForm.y = ''
state.entryActualNodeForm.locationYaw = 0
}, 200)
}
}
//标记
const mapMark = async () => {
state.mapMarkCarList = await MapApi.getListByMapId(imgBgObj.positionMapId)
if (state.currentItemIndex != -1) {
let item = state.allMapPointInfo[state.currentItemIndex]
state.markForm.originalNode = `[${item.locationX},${item.locationY}]`
if (item.type == 1) {
state.markForm.markProperty = '路径点'
} else if (item.type == 2) {
state.markForm.markProperty = '库位点'
} else if (item.type == 3) {
state.markForm.markProperty = '设备点'
} else if (item.type == 4) {
state.markForm.markProperty = '停车点'
} else if (item.type == 5) {
state.markForm.markProperty = '区域变更点'
} else if (item.type == 6) {
state.markForm.markProperty = '等待点'
}
} else {
state.markForm.markProperty = ''
state.markForm.originalNode = ''
}
state.markPopoverVisible = true
}
//标记提交
const macAddressChange = (e) => {
const targetItem = state.mapMarkCarList.find((item) => item.macAddress === e)
if (targetItem) {
state.markForm.robotNo = targetItem.robotNo
}
}
//取消标记
const markFormCancel = () => {
state.markPopoverVisible = false
state.markForm.markProperty = ''
state.markForm.originalNode = ''
state.markForm.macAddress = ''
state.markForm.robotNo = ''
}
//确认标记
const markFormSubmit = async () => {
if (!state.markForm.macAddress) {
message.warning('请选择车辆')
return
}
// 格式是 '{"area":"E区","batSoc":"80","floor":"3","robotNo":"100","x":"-1.720683217048645","y":"-14.308184623718262","yaw":"-3.0042707920074463"}'
let res = await MapApi.getAGVPointInformation(state.markForm.macAddress)
if (res) {
//点
let point = JSON.parse(res)
let pointPx = convertActualToBrowser(point.x, point.y)
// 检查节点是否已存在
const isDuplicate = state.allMapPointInfo.some(
(item) =>
Number(item.locationX).toFixed(1) === Number(pointPx.x).toFixed(1) &&
Number(item.locationY).toFixed(1) === Number(pointPx.y).toFixed(1)
)
if (isDuplicate) {
message.warning('该位置已存在节点')
state.markPopoverVisible = false
return
}
if (state.currentItemIndex !== -1) {
state.allMapPointInfo[state.currentItemIndex].locationYaw = point.yaw
state.allMapPointInfo[state.currentItemIndex].locationX = pointPx.x
state.allMapPointInfo[state.currentItemIndex].locationY = pointPx.y
state.allMapPointInfo[state.currentItemIndex].actualLocationX = point.x
state.allMapPointInfo[state.currentItemIndex].actualLocationY = point.y
//更改路线里的
let item = state.allMapPointInfo[state.currentItemIndex]
state.mapRouteList.forEach((route) => {
if (item.id === route.startingPointId) {
route.startPointX = pointPx.x
route.startPointY = pointPx.y
route.actualStartPointX = point.x
route.actualStartPointY = point.y
}
if (item.id === route.endPointId) {
route.endPointX = pointPx.x
route.endPointY = pointPx.y
route.actualEndPointX = point.x
route.actualEndPointY = point.y
}
})
addEditHistory()
} else {
//新增一个节点
state.allMapPointInfo.push({
positionMapId: imgBgObj.positionMapId, //地图的id
layerSelectionShow: true,
locationX: pointPx.x,
locationY: pointPx.y,
actualLocationX: point.x,
actualLocationY: point.y,
locationDeep: 40,
locationWide: 40,
locationDeepPx: 8,
locationWidePx: 8,
angle: 0,
draggable: true,
resizable: true,
rotatable: false,
lockAspectRatio: false, //横纵比
mapImageUrl: '',
type: 1, //默认类型1 路径节点
dataList: [], //存库位的
dataObj: {}, //存 设备点 停车点 文字
locationYaw: point.yaw //弧度
})
message.success('标记成功')
addEditHistory()
}
} else {
message.warning('未采集到该AGV点位信息')
}
state.markPopoverVisible = false
}
//鼠标拖动绘制节点
// 从点开始绘制
const startFromPoint = (index, event) => {
if (toolbarSwitchType.value === 'clickDrawRoute') {
let list = state.allMapPointInfo
const point = list[index]
if (point?.id) {
state.startDrawPoint = { ...point } //创建点位数据副本
state.startDrawPointIndex = index
state.isDrawing = true
event.preventDefault()
} else {
message.warning('选择的节点未保存')
resetDrawState()
}
}
}
// 重置绘制状态
const resetDrawState = () => {
state.startDrawPointIndex = -1
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
}
// 节流函数
const throttle = (fn, delay) => {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= delay) {
lastTime = now
return fn.apply(this, args)
}
}
}
// 优化后的框选相关函数
const startDrawSelection = (event) => {
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute' ||
toolbarSwitchType.value == 'generateLine' ||
toolbarSwitchType.value === 'bulkDelete' ||
toolbarSwitchType.value === 'batchCopying'
) {
const { x, y } = disposeEventPoints(event)
// 确保点击在背景区域内
if (x >= 0 && x <= imgBgObj.width && y >= 0 && y <= imgBgObj.height) {
state.drawSelectionAreaShow = true
state.drawSelectionStartPoint = { x, y }
state.drawSelectionAreaBox = { x, y, width: 0, height: 0 }
}
event.preventDefault()
}
}
// 使用节流优化更新框选区域
const updateDrawSelection = throttle((event) => {
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute' ||
toolbarSwitchType.value === 'generateLine' ||
toolbarSwitchType.value === 'bulkDelete' ||
toolbarSwitchType.value === 'batchCopying'
) {
if (state.drawSelectionAreaShow) {
const { x, y } = disposeEventPoints(event)
// 使用 requestAnimationFrame 优化渲染
requestAnimationFrame(() => {
state.drawSelectionAreaBox = {
x: Math.min(state.drawSelectionStartPoint.x, x),
y: Math.min(state.drawSelectionStartPoint.y, y),
width: Math.abs(x - state.drawSelectionStartPoint.x),
height: Math.abs(y - state.drawSelectionStartPoint.y)
}
})
}
event.preventDefault()
}
if (toolbarSwitchType.value === 'clickDrawRoute') {
if (state.isDrawing) {
const { x, y } = disposeEventPoints(event)
// 使用 requestAnimationFrame 优化渲染
requestAnimationFrame(() => {
state.currentDrawX = x
state.currentDrawY = y
})
}
event.preventDefault()
}
}, 16) // 约60fps的更新频率
// 优化结束框选
const endDrawSelection = (event) => {
if (toolbarSwitchType.value === 'clickDrawRoute') {
if (state.isDrawing) {
const endPointIndex = findClosestPoint(state.currentDrawX, state.currentDrawY)
if (endPointIndex !== null && endPointIndex !== state.startDrawPointIndex) {
let list = state.allMapPointInfo
const endPoint = list[endPointIndex]
if (!endPoint?.id) {
message.warning('选择的节点未保存')
resetDrawState()
return
}
// 检查路线是否重复
if (checkRouteDuplicate(state.startDrawPoint, endPoint, state.mapRouteList)) {
message.warning('此路线已存在')
} else {
// 创建新路线对象
const newRoute = {
isSelected: false,
startingPointId: state.startDrawPoint.id,
endPointId: endPoint.id,
startPointX: state.startDrawPoint.locationX,
startPointY: state.startDrawPoint.locationY,
endPointX: endPoint.locationX,
endPointY: endPoint.locationY,
actualStartPointX: state.startDrawPoint.actualLocationX,
actualStartPointY: state.startDrawPoint.actualLocationY,
actualEndPointX: endPoint.actualLocationX,
actualEndPointY: endPoint.actualLocationY,
actualBeginControlX: '',
actualBeginControlY: '',
actualEndControlX: '',
actualEndControlY: '',
beginControlX: 0,
beginControlY: 0,
endControlX: 0,
endControlY: 0,
expansionZoneFront: 0,
expansionZoneAfter: 0,
expansionZoneLeft: 0,
expansionZoneRight: 0,
method: 0,
direction: state.routeDirection,
forwardSpeedLimit: 1.5,
reverseSpeedLimit: 0.4,
toward: 0,
startingSortNum: state.startDrawPoint.sortNum,
endPointSortNum: endPoint.sortNum
}
// 使用 requestAnimationFrame 优化渲染
requestAnimationFrame(() => {
state.mapRouteList.push(newRoute)
addEditHistory()
})
}
}
resetDrawState()
}
event.preventDefault()
return
}
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute' ||
toolbarSwitchType.value === 'generateLine' ||
toolbarSwitchType.value === 'bulkDelete' ||
toolbarSwitchType.value === 'batchCopying'
) {
// 使用 requestAnimationFrame 优化渲染
requestAnimationFrame(() => {
state.drawSelectionAreaShow = false
if (state.drawSelectionAreaBox.width > 0 && state.drawSelectionAreaBox.height > 0) {
state.allDrawSelectionAreaBox.push({ ...state.drawSelectionAreaBox })
}
state.drawSelectionAreaBox = { x: 0, y: 0, width: 0, height: 0 }
})
event.preventDefault()
}
}
//点击区域
const clickDrawSelectionArea = () => {
state.drawSelectionPointList = []
state.drawSelectionRouteList = []
// 用于存储所有框选区域的数据
const allSelectedPoints = new Map()
const allSelectedRoutes = new Set()
state.allDrawSelectionAreaBox.forEach((box) => {
// 筛选点
state.allMapPointInfo.forEach((point) => {
if (
point.locationX >= box.x &&
point.locationX <= box.x + box.width &&
point.locationY >= box.y &&
point.locationY <= box.y + box.height
) {
// 使用位置和类型组合作为唯一标识
const pointKey = `${point.locationX},${point.locationY},${point.type}`
if (!allSelectedPoints.has(pointKey)) {
allSelectedPoints.set(pointKey, point)
}
}
})
// 筛选路线
if (toolbarSwitchType.value === 'batchCopying') {
state.mapRouteList.forEach((route) => {
const startPointInBox =
route.startPointX >= box.x &&
route.startPointX <= box.x + box.width &&
route.startPointY >= box.y &&
route.startPointY <= box.y + box.height
const endPointInBox =
route.endPointX >= box.x &&
route.endPointX <= box.x + box.width &&
route.endPointY >= box.y &&
route.endPointY <= box.y + box.height
if (startPointInBox && endPointInBox) {
// 使用起点ID和终点ID组合作为唯一标识
const routeKey = `${route.startingPointId}-${route.endPointId}`
if (!allSelectedRoutes.has(routeKey)) {
allSelectedRoutes.add(routeKey)
}
}
})
}
})
// 将去重后的数据赋值给状态
state.drawSelectionPointList = Array.from(allSelectedPoints.values())
state.drawSelectionRouteList = state.mapRouteList.filter((route) =>
allSelectedRoutes.has(`${route.startingPointId}-${route.endPointId}`)
)
// 清空框选区域
state.allDrawSelectionAreaBox = []
//只要库位的
let binLocation = state.drawSelectionPointList.filter((item) => item.type === 2)
//所以类型的
let routeList = state.drawSelectionPointList
let isHaveId = binLocation.every((item) => {
item.id
})
//线库
if (toolbarSwitchType.value === 'createLineLibrary') {
//线库
if (binLocation.length < 2) {
message.warning('至少选择两个库位')
return
}
// if (!isStraightLine(binLocation)) {
// message.warning('您选择的库位不在一条直线上')
// return
// }
let isHaveId = binLocation.every((item) => {
return item.id
})
if (!isHaveId) {
message.warning('您选择的库位存在未保存的')
return
}
//判断线库的排序是不是一样
const locationNumbers = findDuplicateLocationIds(binLocation)
if (locationNumbers.length > 0) {
message.warning('您选择的库位的序号不能重复')
return
}
removeEventListener() //移除监听
lineLibrarySettingDialogRef.value.open(binLocation)
}
//区域
if (toolbarSwitchType.value === 'createRegion') {
if (binLocation.length < 1) {
message.warning('至少选择两个库位')
return
}
let isHaveId = binLocation.every((item) => {
return item.id
})
if (!isHaveId) {
message.warning('您选择的库位存在未保存的')
return
}
removeEventListener() //移除监听
itemAreaSettingDialogRef.value.open(binLocation)
}
//绘制直线
if (toolbarSwitchType.value === 'drawRoute') {
if (routeList.length !== 2) {
message.warning('只能选择两个路径点')
return
}
let isHaveId = routeList.every((item) => {
return item.id
})
if (!isHaveId) {
message.warning('您选择的路径点存在未保存的')
return
}
let curve = {
isSelected: false, //是否选中
startingPointId: routeList[0].id,
endPointId: routeList[1].id,
startPointX: routeList[0].locationX, //开始点
startPointY: routeList[0].locationY, //开始点
endPointX: routeList[1].locationX, //结束点
endPointY: routeList[1].locationY, //结束点
actualStartPointX: routeList[0].actualLocationX, //实际开始点位x轴
actualStartPointY: routeList[0].actualLocationY, //实际开始点位y轴
actualEndPointX: routeList[1].actualLocationX, //实际结束点位x轴
actualEndPointY: routeList[1].actualLocationY, //实际结束点位y轴
actualBeginControlX: '', //实际开始控制点x轴
actualBeginControlY: '', //实际开始控制点y轴
actualEndControlX: '', //实际结束控制点x轴
actualEndControlY: '', //实际结束控制点y轴
beginControlX: 0, //开始控制点x轴
beginControlY: 0, //开始控制点y轴
endControlX: 0, //结束控制点x轴
endControlY: 0, //结束控制点y轴
expansionZoneFront: 0, //膨胀区域前
expansionZoneAfter: 0, //膨胀区域后
expansionZoneLeft: 0, // 膨胀区域左
expansionZoneRight: 0, //膨胀区域右
method: 0, //行走方法 0.直线 1.上左曲线2.上右曲线3.下左曲线 4.下右曲线
direction: 2, //方向 1.单向 2.双向,
forwardSpeedLimit: 1.5, //正向限速
reverseSpeedLimit: 0.4, // 反向限速
toward: 0 // 车头朝向( 0:正正 1:正反 2:反正 3:反反)
}
state.selectedCurve = curve
state.mapRouteList.push(curve)
addEditHistory()
}
//生成直线
if (toolbarSwitchType.value === 'generateLine') {
if (routeList.length < 3) {
message.warning('至少框选三个点')
return
}
let isHaveId = routeList.every((item) => {
return item.id
})
if (!isHaveId) {
message.warning('您选择的路径点存在未保存的')
return
}
GenerateStraightLinesDialogRef.value.open(routeList)
}
//批量删除
if (toolbarSwitchType.value === 'bulkDelete') {
if (routeList.length < 1) {
message.warning('至少选择一个点')
return
}
//删除的节点id合集
const routeIds = routeList.map((route) => route.id)
//在数组中去除这些节点
state.allMapPointInfo = state.allMapPointInfo.filter((item) => {
return !routeIds.includes(item.id)
})
//将路线中的删除
state.mapRouteList = state.mapRouteList.filter((item) => {
return !routeIds.includes(item.startingPointId) && !routeIds.includes(item.endPointId)
})
state.currentItemIndex = -1
addEditHistory()
}
//批量复制
if (toolbarSwitchType.value === 'batchCopying') {
if (state.drawSelectionPointList.length > 0) {
let boundaryValue = {
left: Math.min(...state.drawSelectionPointList.map((point) => Number(point.locationX))),
right: Math.max(...state.drawSelectionPointList.map((point) => Number(point.locationX))),
top: Math.min(...state.drawSelectionPointList.map((point) => Number(point.locationY))),
bottom: Math.max(...state.drawSelectionPointList.map((point) => Number(point.locationY)))
}
BatchCopyingDialogFormRef.value.open(boundaryValue)
} else {
message.warning('至少选择一个点')
}
}
}
//批量复制
const submitBatchCopyingFormSuccess = async (form) => {
let newPoints = JSON.parse(JSON.stringify(state.drawSelectionPointList))
let newRoutes = JSON.parse(JSON.stringify(state.drawSelectionRouteList))
// 创建节点ID映射
const nodeIdMap = new Map()
// 检查节点类型是否可复制
const isValidNodeType = (type) => [1, 2, 4, 6].includes(type)
// 检查路线是否可复制
const isValidRoute = (route) => {
const startNode = newPoints.find((node) => node.id === route.startingPointId)
const endNode = newPoints.find((node) => node.id === route.endPointId)
return startNode && endNode && isValidNodeType(startNode.type) && isValidNodeType(endNode.type)
}
// 过滤可复制的节点和路线
const validNodes = newPoints.filter((node) => isValidNodeType(node.type))
const validRoutes = newRoutes.filter((route) => isValidRoute(route))
// 计算新坐标的辅助函数
const calculateNewCoordinates = (x, y) => {
const newX = (Number(x) + Number(form.x)).toString()
const newY = (Number(y) + Number(form.y)).toString()
const actualPoint = disposeEventPoint(newX, newY)
return { newX, newY, actualPoint }
}
const newPointList = await Promise.all(
validNodes.map(async (node) => {
const newId = await MapApi.getNodeId()
nodeIdMap.set(node.id, newId)
const {
newX: locationX,
newY: locationY,
actualPoint
} = calculateNewCoordinates(node.locationX, node.locationY)
// 检查节点是否重复
const existingNode = state.allMapPointInfo.find(
(item) =>
Number(item.locationX).toFixed(1) === Number(locationX).toFixed(1) &&
Number(item.locationY).toFixed(1) === Number(locationY).toFixed(1)
)
if (existingNode) {
message.warning(`节点(${locationX}, ${locationY})已存在,已跳过`)
// 记录重复节点的映射关系
nodeIdMap.set(node.id, existingNode.id)
return null
}
const { sortNum, createTime, ...restNode } = node
let dataObj = {}
let dataList = []
let dataJson = ''
if (restNode.type === 1 || restNode.type === 6) {
dataObj = {}
dataList = []
dataJson = ''
} else if (restNode.type === 2) {
dataList = restNode.dataList.map(
({ locationDeep, locationStorey, locationWide, positionMapId }) => ({
locationDeep,
locationStorey,
locationWide,
positionMapId
})
)
dataJson = JSON.stringify(dataList)
} else if (restNode.type === 4) {
dataObj = {
locationDeep: restNode.dataObj.locationDeep,
locationWide: restNode.dataObj.locationWide,
positionMapId: restNode.dataObj.positionMapId
}
dataJson = JSON.stringify(dataObj)
}
return {
...restNode,
id: newId,
locationX,
locationY,
actualLocationX: actualPoint.actualLocationX,
actualLocationY: actualPoint.actualLocationY,
dataObj,
dataList,
dataJson
}
})
)
// 过滤掉重复的节点
const filteredPointList = newPointList.filter((point) => point !== null)
// 处理路线列表
const newRouteList = validRoutes.map((route) => {
const newRoute = { ...route }
// 更新起点
if (nodeIdMap.has(route.startingPointId)) {
const { newX, newY, actualPoint } = calculateNewCoordinates(
route.startPointX,
route.startPointY
)
// 使用映射后的ID可能是新ID或已存在节点的ID
newRoute.startingPointId = nodeIdMap.get(route.startingPointId)
newRoute.startPointX = newX
newRoute.startPointY = newY
newRoute.actualStartPointX = actualPoint.actualLocationX
newRoute.actualStartPointY = actualPoint.actualLocationY
// 更新控制点
if (route.method == 1) {
const {
newX: controlX,
newY: controlY,
actualPoint: controlPoint
} = calculateNewCoordinates(route.beginControlX, route.beginControlY)
newRoute.beginControlX = controlX
newRoute.beginControlY = controlY
newRoute.actualBeginControlX = controlPoint.actualLocationX
newRoute.actualBeginControlY = controlPoint.actualLocationY
} else {
newRoute.beginControlX = 0
newRoute.beginControlY = 0
newRoute.actualBeginControlX = 0
newRoute.actualBeginControlY = 0
}
}
// 更新终点
if (nodeIdMap.has(route.endPointId)) {
const { newX, newY, actualPoint } = calculateNewCoordinates(route.endPointX, route.endPointY)
// 使用映射后的ID可能是新ID或已存在节点的ID
newRoute.endPointId = nodeIdMap.get(route.endPointId)
newRoute.endPointX = newX
newRoute.endPointY = newY
newRoute.actualEndPointX = actualPoint.actualLocationX
newRoute.actualEndPointY = actualPoint.actualLocationY
// 更新控制点
if (route.method == 1) {
const {
newX: controlX,
newY: controlY,
actualPoint: controlPoint
} = calculateNewCoordinates(route.endControlX, route.endControlY)
newRoute.endControlX = controlX
newRoute.endControlY = controlY
newRoute.actualEndControlX = controlPoint.actualLocationX
newRoute.actualEndControlY = controlPoint.actualLocationY
} else {
newRoute.endControlX = 0
newRoute.endControlY = 0
newRoute.actualEndControlX = 0
newRoute.actualEndControlY = 0
}
}
const { id, createTime, startingSortNum, endPointSortNum, ...restRoute } = newRoute
return restRoute
})
// 过滤掉重复的路线
const filteredRouteList = newRouteList.filter((newRoute) => {
// 检查路线是否重复
const isDuplicate = state.mapRouteList.some(
(existingRoute) =>
Number(existingRoute.startPointX).toFixed(1) === Number(newRoute.startPointX).toFixed(1) &&
Number(existingRoute.startPointY).toFixed(1) === Number(newRoute.startPointY).toFixed(1) &&
Number(existingRoute.endPointX).toFixed(1) === Number(newRoute.endPointX).toFixed(1) &&
Number(existingRoute.endPointY).toFixed(1) === Number(newRoute.endPointY).toFixed(1)
)
if (isDuplicate) {
message.warning(
`路线(${newRoute.startPointX}, ${newRoute.startPointY}) -> (${newRoute.endPointX}, ${newRoute.endPointY})已存在,已跳过`
)
return false
}
return true
})
console.log(filteredPointList)
console.log(filteredRouteList)
state.allMapPointInfo.push(...filteredPointList)
state.mapRouteList.push(...filteredRouteList)
message.success('复制成功')
addEditHistory()
}
//生成直线 选择完成开始点和结束点
const GenerateStraightLinesSubmit = (pointList, form) => {
// 使用 requestAnimationFrame 优化渲染
requestAnimationFrame(() => {
const list = mapPointsToLine(pointList, form.startPointId, form.endPointId)
const idNameMap = new Map(list.map((item) => [item.id, item]))
// 批量更新点位
const updatedPoints = state.allMapPointInfo.map((item) => {
if (idNameMap.has(item.id)) {
const newPoint = idNameMap.get(item.id)
const actualPoint = disposeEventPoint(newPoint.locationX, newPoint.locationY)
return {
...item,
locationX: newPoint.locationX,
locationY: newPoint.locationY,
actualLocationX: actualPoint.actualLocationX,
actualLocationY: actualPoint.actualLocationY
}
}
return item
})
// 批量更新路线
const updatedRoutes = state.mapRouteList.map((item) => {
const startPoint = idNameMap.get(item.startingPointId)
const endPoint = idNameMap.get(item.endPointId)
if (startPoint || endPoint) {
const result = { ...item }
if (startPoint) {
const actualStartPoint = disposeEventPoint(startPoint.locationX, startPoint.locationY)
result.startPointX = startPoint.locationX
result.startPointY = startPoint.locationY
result.actualStartPointX = actualStartPoint.actualLocationX
result.actualStartPointY = actualStartPoint.actualLocationY
}
if (endPoint) {
const actualEndPoint = disposeEventPoint(endPoint.locationX, endPoint.locationY)
result.endPointX = endPoint.locationX
result.endPointY = endPoint.locationY
result.actualEndPointX = actualEndPoint.actualLocationX
result.actualEndPointY = actualEndPoint.actualLocationY
}
return result
}
return item
})
// 批量更新状态
state.allMapPointInfo = updatedPoints
state.mapRouteList = updatedRoutes
addEditHistory()
})
}
//返回生成线库时 排序相同项的id
const findDuplicateLocationIds = (data) => {
const locationMap = {}
data.forEach((item) => {
if (!locationMap[item.locationNumber]) {
locationMap[item.locationNumber] = []
}
locationMap[item.locationNumber].push(item.id)
})
return Object.values(locationMap).filter((ids) => ids.length > 1)
}
//将一个数组中的点位 按照第一个和最后一个排成一条直线
const mapPointsToLine = (points, startPointId, endPointId) => {
const startPoint = points.find((point) => point.id === startPointId)
const endPoint = points.find((point) => point.id === endPointId)
if (!startPoint || !endPoint) {
message.warning('选择的点位有误')
return
}
const dx = endPoint.locationX - startPoint.locationX
const dy = endPoint.locationY - startPoint.locationY
const length = Math.sqrt(dx * dx + dy * dy)
return points.map((point) => {
if (point.id === startPointId || point.id === endPointId) {
return point
}
const vx = point.locationX - startPoint.locationX
const vy = point.locationY - startPoint.locationY
const dotProduct = vx * dx + vy * dy
const projectionLength = dotProduct / length
const t = Math.max(0, Math.min(1, projectionLength / length))
const newX = startPoint.locationX + t * dx
const newY = startPoint.locationY + t * dy
return {
...point,
locationX: newX,
locationY: newY
}
})
}
//计算是不是在同一条直线的
const isStraightLine = (binLocation) => {
if (binLocation.length <= 2) {
return true // 两个点一定在一条直线上
}
const firstPoint = binLocation[0]
const secondPoint = binLocation[1]
// 检查是否垂直直线(所有 x 相同)
const isVertical = binLocation.every(
(point) => Number(point.locationX) === Number(firstPoint.locationX)
)
if (isVertical) {
return true
}
// 检查是否水平直线(所有 y 相同)
const isHorizontal = binLocation.every(
(point) => Number(point.locationY) === Number(firstPoint.locationY)
)
if (isHorizontal) {
return true
}
// 计算斜率
const slope =
Number(secondPoint.locationY) -
Number(firstPoint.locationY) / (Number(secondPoint.locationX) - Number(firstPoint.locationX))
// 检查所有点是否在同一条斜线上
return binLocation.every((point) => {
const currentSlope =
(Number(point.locationY) - Number(firstPoint.locationY)) /
(Number(point.locationX) - Number(firstPoint.locationX))
return Math.abs(currentSlope - slope) < Number.EPSILON // 处理浮点数精度问题
})
}
//三阶贝塞尔曲线
// 开始拖拽
const startDrag = (item, index, type) => {
state.currentDragTarget = { index, type }
window.addEventListener('mousemove', handleDrag)
window.addEventListener('mouseup', endDrag)
}
// 处理拖动事件
const handleDrag = (event) => {
let x = disposeEventPoints(event).x
let y = disposeEventPoints(event).y
if (state.currentDragTarget.index !== null) {
const curve = state.mapRouteList[state.currentDragTarget.index]
// 确保控制点不超出盒子范围
x = Math.max(0, Math.min(x, imgBgObj.width))
y = Math.max(0, Math.min(y, imgBgObj.height))
if (state.currentDragTarget.type === 'start') {
curve.beginControlX = x
curve.beginControlY = y
} else {
curve.endControlX = x
curve.endControlY = y
}
}
}
// 结束拖动
const endDrag = (event) => {
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.actualEndControlX = actualEndControl.actualLocationX
curve.actualEndControlY = actualEndControl.actualLocationY
addEditHistory()
state.currentDragTarget.type = null
window.removeEventListener('mousemove', handleDrag)
window.removeEventListener('mouseup', endDrag)
}
// 获取曲线的路径
const getCurvePath = (curve) => {
let startPointX = Number(curve.startPointX)
let startPointY = Number(curve.startPointY)
let endPointX = Number(curve.endPointX)
let endPointY = Number(curve.endPointY)
let path = `M ${startPointX} ${startPointY} C ${curve.beginControlX} ${curve.beginControlY}, ${curve.endControlX} ${curve.endControlY}, ${endPointX} ${endPointY}`
curve.curvePath = path
return path
}
// 编辑路线
const handleChooseRoute = async (item, index, type, e) => {
if (toolbarSwitchType.value === 'addPointOnline') {
if (!item.id) {
message.warning('该路线未保存')
return
}
//直线情况下 线上加点
if (type === 'line') {
const id = await MapApi.getNodeId()
const pageX = disposeEventPoints(e).x
const pageY = disposeEventPoints(e).y
const x = mapPointToLineSegment(item, pageX, pageY).newX
const y = mapPointToLineSegment(item, pageX, pageY).newY
const actualLocationX = disposeEventPoint(x, y).actualLocationX
const actualLocationY = disposeEventPoint(x, y).actualLocationY
let newPoint = {
id: id,
positionMapId: imgBgObj.positionMapId, //地图的id
layerSelectionShow: true,
locationX: x,
locationY: y,
actualLocationX: actualLocationX,
actualLocationY: actualLocationY,
locationDeep: 40,
locationWide: 40,
locationDeepPx: 8,
locationWidePx: 8,
angle: 0,
draggable: true,
resizable: true,
rotatable: false,
lockAspectRatio: false, //横纵比
mapImageUrl: '',
locationYaw: 0, //弧度
type: 1, //默认类型1 路径节点
dataList: [], //存库位的
dataObj: {} //存 设备点 停车点 文字
}
let positionMapLineOne = {
isSelected: false, //是否选中
startingPointId: item.startingPointId,
endPointId: id,
startPointX: item.startPointX, //开始点
startPointY: item.startPointY, //开始点
endPointX: x, //结束点
endPointY: y, //结束点
actualStartPointX: item.actualStartPointX, //实际开始点位x轴
actualStartPointY: item.actualStartPointY, //实际开始点位y轴
actualEndPointX: actualLocationX, //实际结束点位x轴
actualEndPointY: actualLocationY, //实际结束点位y轴
actualBeginControlX: '', //实际开始控制点x轴
actualBeginControlY: '', //实际开始控制点y轴
actualEndControlX: '', //实际结束控制点x轴
actualEndControlY: '', //实际结束控制点y轴
beginControlX: 0, //开始控制点x轴
beginControlY: 0, //开始控制点y轴
endControlX: 0, //结束控制点x轴
endControlY: 0, //结束控制点y轴
expansionZoneFront: 0, //膨胀区域前
expansionZoneAfter: 0, //膨胀区域后
expansionZoneLeft: 0, // 膨胀区域左
expansionZoneRight: 0, //膨胀区域右
method: 0, //行走方法 0.直线 1.上左曲线2.上右曲线3.下左曲线 4.下右曲线
direction: 2, //方向 1.单向 2.双向,
forwardSpeedLimit: 1.5, //正向限速
reverseSpeedLimit: 0.4, // 反向限速
toward: 0 // 车头朝向( 0:正正 1:正反 2:反正 3:反反)
}
let positionMapLineTwo = {
isSelected: false, //是否选中
startingPointId: id,
endPointId: item.endPointId,
startPointX: x, //开始点
startPointY: y, //开始点
endPointX: item.endPointX, //结束点
endPointY: item.endPointY, //结束点
actualStartPointX: actualLocationX, //实际开始点位x轴
actualStartPointY: actualLocationY, //实际开始点位y轴
actualEndPointX: item.actualEndPointX, //实际结束点位x轴
actualEndPointY: item.actualEndPointY, //实际结束点位y轴
actualBeginControlX: '', //实际开始控制点x轴
actualBeginControlY: '', //实际开始控制点y轴
actualEndControlX: '', //实际结束控制点x轴
actualEndControlY: '', //实际结束控制点y轴
beginControlX: 0, //开始控制点x轴
beginControlY: 0, //开始控制点y轴
endControlX: 0, //结束控制点x轴
endControlY: 0, //结束控制点y轴
expansionZoneFront: 0, //膨胀区域前
expansionZoneAfter: 0, //膨胀区域后
expansionZoneLeft: 0, // 膨胀区域左
expansionZoneRight: 0, //膨胀区域右
method: 0, //行走方法 0.直线 1.上左曲线2.上右曲线3.下左曲线 4.下右曲线
direction: 2, //方向 1.单向 2.双向,
forwardSpeedLimit: 1.5, //正向限速
reverseSpeedLimit: 0.4, // 反向限速
toward: 0 // 车头朝向( 0:正正 1:正反 2:反正 3:反反)
}
try {
let res = await MapApi.lineAddItem([
{
...newPoint,
oldLineId: item.id,
positionMapLines: [positionMapLineOne, positionMapLineTwo]
}
])
newPoint.sortNum = res?.ITEM?.sortNum || ''
res?.LINE.forEach((lineItem) => {
if (
lineItem.startingPointId === positionMapLineOne.startingPointId &&
lineItem.endPointId === positionMapLineOne.endPointId
) {
positionMapLineOne.startingSortNum = lineItem.startingSortNum
positionMapLineOne.endPointSortNum = lineItem.endPointSortNum
}
if (
lineItem.startingPointId === positionMapLineTwo.startingPointId &&
lineItem.endPointId === positionMapLineTwo.endPointId
) {
positionMapLineTwo.startingSortNum = lineItem.startingSortNum
positionMapLineTwo.endPointSortNum = lineItem.endPointSortNum
}
})
state.allMapPointInfo.push(newPoint)
state.mapRouteList.push(positionMapLineOne)
state.mapRouteList.push(positionMapLineTwo)
state.mapRouteList.splice(index, 1)
state.allHistoryList[0] = {
allMapPointInfo: JSON.parse(JSON.stringify(state.allMapPointInfo)),
mapRouteList: JSON.parse(JSON.stringify(state.mapRouteList))
}
state.currentIndex = 0
message.success('添加成功')
} catch (e) {
message.error(e)
}
}
} else {
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(JSON.parse(JSON.stringify(item)))
}
}
}
const mapPointToLineSegment = (linePoint, x, y) => {
const dx = Number(linePoint.endPointX) - Number(linePoint.startPointX)
const dy = Number(linePoint.endPointY) - Number(linePoint.startPointY)
const length = Math.sqrt(dx * dx + dy * dy)
const vx = x - Number(linePoint.startPointX)
const vy = y - Number(linePoint.startPointY)
const dotProduct = vx * dx + vy * dy
const projectionLength = dotProduct / length
const t = Math.max(0, Math.min(1, projectionLength / length))
const newX = Number(linePoint.startPointX) + t * dx
const newY = Number(linePoint.startPointY) + t * dy
return { newX, newY }
}
//编辑路线成功
const editMapRouteDialogSubmit = (form) => {
state.mapRouteList[state.currentDragTarget.index] = form
//路线位置改变 通知路径里面的节点也改变
state.mapRouteList.forEach((item) => {
if (form.startingPointId === item.startingPointId) {
item.startPointX = form.startPointX
item.startPointY = form.startPointY
item.actualStartPointX = disposeEventPoint(form.startPointX, form.startPointY).actualLocationX
item.actualStartPointY = disposeEventPoint(form.startPointX, form.startPointY).actualLocationY
}
if (form.endPointId === item.endPointId) {
item.endPointX = form.endPointX
item.endPointY = form.endPointY
item.actualEndPointX = disposeEventPoint(form.endPointX, form.endPointY).actualLocationX
item.actualEndPointY = disposeEventPoint(form.endPointX, form.endPointY).actualLocationY
}
})
state.allMapPointInfo.forEach((item) => {
if (item.id === form.startingPointId) {
item.locationX = form.startPointX
item.locationY = form.startPointY
item.actualLocationX = disposeEventPoint(form.startPointX, form.startPointY).actualLocationX
item.actualLocationY = disposeEventPoint(form.startPointX, form.startPointY).actualLocationY
}
if (item.id === form.endPointId) {
item.locationX = form.endPointX
item.locationY = form.endPointY
item.actualLocationX = disposeEventPoint(form.endPointX, form.endPointY).actualLocationX
item.actualLocationY = disposeEventPoint(form.endPointX, form.endPointY).actualLocationY
}
})
//增加一条历史记录
addEditHistory() //重新监听键盘事件
}
//测距相关
// 计算连线的样式
const rangingLineStyle = computed(() => {
if (state.measureDistancesPoints.length === 2) {
const [point1, point2] = state.measureDistancesPoints
const length = Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2))
const angle = (Math.atan2(point2.y - point1.y, point2.x - point1.x) * 180) / Math.PI
return {
position: 'absolute',
left: `${point1.x}px`,
top: `${point1.y}px`,
width: `${length}px`,
height: '.125rem',
backgroundColor: 'red',
transform: `rotate(${angle}deg)`,
transformOrigin: '0 0'
}
}
return {}
})
// 计算距离信息的样式(显示在连线中间,并根据角度调整文字方向)
const rangingTextStyle = computed(() => {
if (state.measureDistancesPoints.length === 2) {
const [point1, point2] = state.measureDistancesPoints
const midX = (point1.x + point2.x) / 2
const midY = (point1.y + point2.y) / 2
const angle = (Math.atan2(point2.y - point1.y, point2.x - point1.x) * 180) / Math.PI
// 调整文字方向,使其始终易于阅读
let textRotation = 0
if (angle > 90 || angle < -90) {
textRotation = angle + 180 // 翻转文字方向
} else {
textRotation = angle
}
return {
position: 'absolute',
left: `${midX}px`,
top: `${midY}px`,
transform: `translate(-50%, -50%) rotate(${textRotation}deg)`,
backgroundColor: 'rgba(255, 255, 255, 0.8)',
padding: '.125rem',
borderRadius: '.25rem',
fontSize: '.75rem',
color: 'black',
whiteSpace: 'nowrap', // 防止文字换行
pointerEvents: 'none' // 防止文字遮挡点击事件
}
}
return {}
})
// 计算点的样式
const getRangingPointStyle = (point) => ({
position: 'absolute',
left: `${point.x - 4}px`,
top: `${point.y - 4}px`,
width: '.5rem',
height: '.5rem',
backgroundColor: 'blue',
borderRadius: '50%'
})
//计算路线的距离
const calculateRouteLength = (item, type) => {
const {
startPointX,
startPointY,
endPointX,
endPointY,
beginControlX,
beginControlY,
endControlX,
endControlY
} = item
if (type == 'line') {
const dx = startPointX - endPointX
const dy = startPointY - endPointY
const length = Math.sqrt(dx * dx + dy * dy) * Number(imgBgObj.resolution)
return length.toFixed(2)
} else {
const steps = 100 // 离散化的步数,步数越多越精确
let length = 0
let prevPoint = null
for (let i = 0; i <= steps; i++) {
const t = i / steps
// 三阶贝塞尔曲线公式
const x =
(1 - t) ** 3 * startPointX +
3 * t * (1 - t) ** 2 * beginControlX +
3 * t ** 2 * (1 - t) * endControlX +
t ** 3 * endPointX
const y =
(1 - t) ** 3 * startPointY +
3 * t * (1 - t) ** 2 * beginControlY +
3 * t ** 2 * (1 - t) * endControlY +
t ** 3 * endPointY
const currentPoint = [x, y]
if (prevPoint) {
const dx = currentPoint[0] - prevPoint[0]
const dy = currentPoint[1] - prevPoint[1]
length += Math.sqrt(dx * dx + dy * dy)
}
prevPoint = currentPoint
}
length = (length * Number(imgBgObj.resolution)).toFixed(2)
return length
}
}
// 处理点击事件
const measureDistancesClick = (event) => {
// 获取点击点相对于整个页面的坐标
const x = disposeEventPoints(event).x
const y = disposeEventPoints(event).y
// 检查点击是否发生在背景区域内
if (state.measureDistancesPoints.length === 2) {
// 如果已经有两个点,清空信息
state.measureDistancesPoints = []
state.measureDistancesNum = null
} else {
// 记录点击的点位(相对于背景区域的坐标)
const offsetX = x
const offsetY = y
state.measureDistancesPoints.push({ x: offsetX, y: offsetY })
if (state.measureDistancesPoints.length === 2) {
// 计算两点之间的距离
const [point1, point2] = state.measureDistancesPoints
let distancesNum = Math.sqrt(
Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)
)
state.measureDistancesNum = distancesNum * Number(imgBgObj.resolution)
}
}
}
//获取扫描图 地图背景相关的信息
const imgBgObj = reactive({
imgUrl: '',
positionMapId: '',
width: '',
height: '',
floor: '',
area: '',
resolution: 0,
origin: null
})
//接收参数
const { query } = useRoute() // 查询参数
//获取地图点位
const getMapList = async () => {
if (!query.mapId) return
let res = await MapApi.getPositionMap(query.mapId)
let yamlJson = JSON.parse(res.yamlJson)
imgBgObj.positionMapId = res.id
imgBgObj.floor = res.floor
imgBgObj.area = res.area
imgBgObj.width = yamlJson.width
imgBgObj.height = yamlJson.height
imgBgObj.origin = yamlJson.origin
imgBgObj.resolution = yamlJson.resolution
// 调转换成png的接口
getMapData(imgBgObj)
}
//调转换成png的接口
const getMapData = async (mapInfo) => {
let data = await MapApi.getPositionMapdDwnloadPngBase64({
floor: mapInfo.floor,
area: mapInfo.area
})
imgBgObj.imgUrl = data
//获取节点 路径等信息
await getAllNodeList()
await getAllMapRoute()
}
//获取所有的点位 处理七种类型的
const getAllNodeList = async () => {
let list = await MapApi.getPositionMapItemList({
positionMapId: imgBgObj.positionMapId
})
state.allMapPointInfo = []
state.currentIndex = 0
list.forEach((item) => {
item.layerSelectionShow = true //用于图层
item.locationX = Number(item.locationX)
item.locationY = Number(item.locationY)
if (item.type === 1) {
item.dataObj = {}
item.dataList = []
item.locationDeep = 40
item.locationWide = 40
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 6) {
item.dataObj = {}
item.dataList = []
item.locationDeep = 150
item.locationWide = 150
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 2) {
//库位点
item.dataList = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.locationDeep = item.dataList[0].locationDeep
item.locationWide = item.dataList[0].locationWide
item.areaName = item.dataList[0].areaName || undefined
item.laneName = item.dataList[0].laneName || undefined
item.skuInfo = item.dataList[0].skuInfo || undefined
item.draggable = true
item.resizable = true
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 3) {
item.dataObj = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.dataList = []
item.locationDeep = item.dataObj.locationDeep
item.locationWide = item.dataObj.locationWide
item.deviceId = item.dataObj.id
item.deviceNo = item.dataObj.deviceNo
item.deviceType = item.dataObj.deviceType
item.mapImageUrl = item.dataObj.mapImageUrl
item.draggable = true
item.resizable = true
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 4) {
item.dataObj = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.dataList = []
item.locationDeep = item.dataObj.locationDeep
item.locationWide = item.dataObj.locationWide
item.draggable = true
item.resizable = true
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 5) {
item.dataObj = item.dataJson ? JSONBigInt({ storeAsString: true }).parse(item.dataJson) : {}
item.dataList = []
item.locationDeep = 150
item.locationWide = 150
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 7) {
item.dataObj = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.text = item.dataObj.text
item.fontColor = item.dataObj.fontColor
item.fontFamily = item.dataObj.fontFamily
item.fontSize = item.dataObj.fontSize
item.angle = item.dataObj.angle
item.draggable = true
item.resizable = false
item.rotatable = true
item.lockAspectRatio = true
item.locationDeep = 20
item.locationWide = 20
}
//要将实际的cm改成px
if (item.locationWide && item.locationDeep) {
let pxObj = cmConversionPx(item.locationWide, item.locationDeep)
item.locationWidePx = pxObj.pWidth
item.locationDeepPx = pxObj.pHeight
}
state.allMapPointInfo.push(item)
})
//包含sortNumber的
state.haveSortNumMapPointInfo = state.allMapPointInfo.filter((item) => {
return item.sortNum
})
}
//获取所有的路线
const getAllMapRoute = async () => {
state.mapRouteList = await MapApi.getPositionMapLineByPositionMapId(imgBgObj.positionMapId)
state.allHistoryList = []
state.allHistoryList[0] = {
allMapPointInfo: JSON.parse(JSON.stringify(state.allMapPointInfo)),
mapRouteList: JSON.parse(JSON.stringify(state.mapRouteList))
}
}
//获取设备类型
const getDeviceTypeName = (deviceType) => {
let list = getIntDictOptions(DICT_TYPE.DEVICE_TYPE)
let deviceItem = list.find((item) => {
return item.value == deviceType
})
return deviceItem.label
}
//保存地图按钮
const saveMap = async () => {
//判断是否存在库位未填写排序号
state.noLocationNumberList = state.allMapPointInfo.reduce((invalidIndexes, item, index) => {
if (item.type === 2 && !item.locationNumber) {
invalidIndexes.push(index) // 如果不满足条件,记录索引
}
return invalidIndexes
}, [])
if (state.noLocationNumberList.length !== 0) {
state.currentItemIndex = -1
message.error('存在库位未填写排序号')
return
}
const loading = ElLoading.service({
lock: true,
text: '保存中',
background: 'rgba(255, 255, 255, 0.7)'
})
try {
//节点的保存
await saveNodeList()
//路线的保存
await saveMapRoute()
//获取新的数据
await getAllNodeList()
await getAllMapRoute()
//关闭loading
loading.close()
message.success('保存成功')
} catch (error) {
loading.close()
message.error(error?.message || '保存失败')
}
}
//节点的保存
const saveNodeList = async () => {
let list = state.allMapPointInfo
list.forEach((item) => {
if (item.type === 2) {
// 库位点 类型为数组
item.dataList.forEach((node) => {
node.locationDeep = item.locationDeep
node.locationWide = item.locationWide
})
item.dataJson = JSON.stringify(item.dataList)
} else if (item.type === 3 || item.type === 4) {
//设备类型
item.dataObj.locationWide = item.locationWide
item.dataObj.locationDeep = item.locationDeep
item.dataJson = JSON.stringify(item.dataObj)
} else if (item.type === 7) {
//文字类型
item.dataObj.positionMapId = imgBgObj.positionMapId
item.dataObj.text = item.text
item.dataObj.fontColor = item.fontColor
item.dataObj.fontType = item.fontType
item.dataObj.fontFamily = item.fontFamily
item.dataObj.fontSize = item.fontSize
item.dataObj.rotatable = item.rotatable
item.dataObj.angle = item.angle
//dataJson数据
item.dataJson = JSON.stringify(item.dataObj)
}
})
try {
await MapApi.batchSaveOrEditOrDelMapItem(imgBgObj.positionMapId, list)
} catch (error) {
throw new Error('节点保存失败:' + (error?.message || '未知错误'))
}
}
//路线的保存
const saveMapRoute = async () => {
try {
await MapApi.createOrEditOrDelPositionMapLine(imgBgObj.positionMapId, state.mapRouteList)
} catch (error) {
throw new Error('路线保存失败:' + (error?.message || '未知错误'))
}
}
//线库新增 要在库位新增线库信息
const submitLineLibraryFormSuccess = (obj) => {
state.allMapPointInfo.forEach((item) => {
if (obj.mapItemIds.includes(item.id)) {
item.laneName = obj.laneName
item.laneId = obj.id
item.dataList.forEach((node) => {
node.laneName = obj.laneName
node.laneId = obj.id
})
}
})
}
//区域新增 要在库位新增区域信息
const itemAreaSettingSubmitSuccess = (obj) => {
state.allMapPointInfo.forEach((item) => {
if (obj.mapItemIds.includes(item.id)) {
item.areaName = obj.areaName
item.skuInfo = obj.skuInfo
item.areaId = obj.id
item.dataList.forEach((node) => {
node.areaName = obj.areaName
node.skuInfo = obj.skuInfo
node.areaId = obj.id
})
}
})
}
//线库删除 要在库位中删除线库信息
const lineLibraryManagementDelete = (mapItemIds) => {
state.allMapPointInfo.forEach((item) => {
if (mapItemIds.includes(item.id)) {
item.laneId = undefined
item.laneName = undefined
item.dataList.forEach((node) => {
node.laneName = undefined
node.laneId = undefined
})
}
})
}
//区域删除 要在库位删除区域信息
const itemAreaManagementDelete = (mapItemIds) => {
state.allMapPointInfo.forEach((item) => {
if (mapItemIds.includes(item.id)) {
item.areaName = undefined
item.skuInfo = undefined
item.areaId = undefined
item.dataList.forEach((node) => {
node.areaName = undefined
node.skuInfo = undefined
node.areaId = undefined
})
}
})
}
//线库编辑 要在库位中编辑线库信息
const lineLibraryManagementEdit = (obj) => {
console.log(obj)
state.allMapPointInfo.forEach((item) => {
if (obj.mapItemIds.includes(item.id)) {
item.laneName = obj.laneName
item.laneId = obj.id
item.dataList.forEach((node) => {
node.laneName = obj.laneName
node.laneId = obj.id
})
}
})
}
//区域编辑 要在库位编辑区域信息
const itemAreaManagementEdit = (obj) => {
console.log(obj)
state.allMapPointInfo.forEach((item) => {
if (obj.mapItemIds.includes(item.id)) {
item.areaName = obj.areaName
item.skuInfo = obj.skuInfo
item.areaId = obj.id
item.dataList.forEach((node) => {
node.areaName = obj.areaName
node.skuInfo = obj.skuInfo
node.areaId = obj.id
})
}
})
}
//筛选图层
const layerSelectionSuccess = (typeList) => {
state.allMapPointInfo.forEach((item) => {
// 如果 type 存在于第一个数组中,则将 layerSelectionShow 设置为 false
if (typeList.includes(item.type)) {
item.layerSelectionShow = false
} else {
item.layerSelectionShow = true
}
})
}
//处理数据 考虑滚动条,缩放等各种情况的的坐标数据
const disposeEventPoints = (event) => {
const rect = mapBackgroundRef.value.getBoundingClientRect()
const scrollLeft = mapBackgroundRef.value.scrollLeft // 水平滚动条偏移量
const scrollTop = mapBackgroundRef.value.scrollTop // 垂直滚动条偏移量
// const devicePixelRatio = window.devicePixelRatio || 1 //浏览器缩放
const devicePixelRatio = 1
// 计算页面坐标(考虑设备像素比和滚动条偏移量)
const x =
Math.round(
((event.clientX - rect.left + scrollLeft) / state.imageChangeMultiple / devicePixelRatio) *
1000
) / 1000
const y =
Math.round(
((event.clientY - rect.top + scrollTop) / state.imageChangeMultiple / devicePixelRatio) * 1000
) / 1000
// 转换为实际坐标
const actualLocationX = Math.round((imgBgObj.origin[0] + x * imgBgObj.resolution) * 1000) / 1000
const actualLocationY =
Math.round((imgBgObj.origin[1] + (imgBgObj.height - y) * imgBgObj.resolution) * 1000) / 1000
return {
x,
y,
actualLocationX,
actualLocationY
}
}
//实际坐标 传入x y轴坐标
const disposeEventPoint = (x, y) => {
const actualLocationX = Number(imgBgObj.origin[0]) + Number(x) * Number(imgBgObj.resolution)
const actualLocationY =
Number(imgBgObj.origin[1]) + (Number(imgBgObj.height) - Number(y)) * Number(imgBgObj.resolution)
return {
actualLocationX,
actualLocationY
}
}
// 传入实际现场的数据,转换成浏览器坐标的数据
const convertActualToBrowser = (pointX, pointY) => {
const y1 = Number(imgBgObj.origin[1]) + Number(imgBgObj.height) * Number(imgBgObj.resolution)
let x = Math.max(Number(pointX) - Number(imgBgObj.origin[0]), 0) / Number(imgBgObj.resolution)
let y = Math.max(y1 - Number(pointY), 0) / Number(imgBgObj.resolution)
return {
x,
y
}
}
//将节点实际宽高cm转换成px
const cmConversionPx = (cWidth, cHeight) => {
let pWidth = Number(cWidth) / imgBgObj.resolution / 100
let pHeight = Number(cHeight) / imgBgObj.resolution / 100
return {
pWidth,
pHeight
}
}
// 计算贝塞尔曲线中间文字的 x 坐标
const computedCurveTextX = (item) => {
return (
(Number(item.startPointX) +
Number(item.beginControlX) +
Number(item.endControlX) +
Number(item.endPointX)) /
4
)
}
// 计算贝塞尔曲线中间文字的 y 坐标
const computedCurveTextY = (item) => {
return (
(Number(item.startPointY) +
Number(item.beginControlY) +
Number(item.endControlY) +
Number(item.endPointY)) /
4
)
}
//搜索
const toggleSelect = () => {
state.isSearchSelectVisible = !state.isSearchSelectVisible
state.searchSelectedOption = ''
}
const searchSelectChange = (sortNum) => {
const currentIndex = state.allMapPointInfo.findIndex((item) => item.sortNum === sortNum)
const currentItem = state.allMapPointInfo.find((item) => item.sortNum === sortNum)
// 计算并限制边界 滚动到指定位置
const rect = mapContainerRef.value
const maxScrollLeft = rect.scrollWidth - rect.clientWidth
const maxScrollTop = rect.scrollHeight - rect.clientHeight
const scrollToX = Math.max(
0,
Math.min(currentItem.locationX - rect.clientWidth / 2, maxScrollLeft)
)
const scrollToY = Math.max(
0,
Math.min(currentItem.locationY - rect.clientHeight / 2, maxScrollTop)
)
rect.scrollTo({
left: scrollToX,
top: scrollToY,
behavior: 'smooth'
})
state.currentItemIndex = currentIndex
}
document.onmousedown = function (e) {
//右击
if (e.button == 2) {
state.selectedCurve = ''
//清除路线的选中
state.mapRouteList.forEach((item) => {
item.isSelected = false
})
state.currentDragTarget = { index: null, type: null }
state.currentItemIndex = -1
}
}
// 阻止默认菜单弹出
window.document.oncontextmenu = function () {
return false
}
//监听键盘事件
const handleKeyDown = (event) => {
if (event.ctrlKey) {
if (event.key === 'c') {
//复制
if (state.currentItemIndex === -1) {
message.warning('请先选择要操作的对象')
return
}
replicationNode()
} else if (event.key === 'v') {
//粘贴
if (!state.copyMapItem) {
message.warning('请先复制对象')
return
}
pasteNode()
} else if (event.key === 'z') {
//撤回
backPreviousStep()
} else if (event.key === 'y') {
//重写
backNextStep()
}
}
}
//鼠标滚轮
const handleWheel = (event) => {
// 判断 Ctrl 键是否被按下
if (event.ctrlKey) {
// 阻止默认的滚动行为
event.preventDefault()
// 根据滚轮滚动方向调整缩放比例
if (event.deltaY < 0) {
// 向上滚动,放大
//放大
if (state.imageChangeMultiple < 4) {
state.imageChangeMultiple += 0.2
} else {
state.imageChangeMultiple = 3.8
message.warning('不能在放大了')
}
} else {
//缩小
if (state.imageChangeMultiple > 0.2) {
state.imageChangeMultiple -= 0.1
} else {
state.imageChangeMultiple = 0.1
message.warning('不能在缩小了')
}
}
}
}
const addEventListener = () => {
window.addEventListener('keydown', handleKeyDown)
}
const removeEventListener = () => {
window.removeEventListener('keydown', handleKeyDown)
}
const router = useRouter()
onBeforeRouteLeave((to, from) => {
if (to.path == '/mapPage/realTimeMap' && to.query.mapId != imgBgObj.positionMapId) {
router.replace({
name: 'MapPageRealTimeMap',
query: {
mapId: imgBgObj.positionMapId
}
})
}
})
onMounted(() => {
getMapList()
window.addEventListener('keydown', handleKeyDown)
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown)
})
// 检查路线是否重复的函数
const checkRouteDuplicate = (startPoint, endPoint, routeList) => {
if (!startPoint?.id || !endPoint?.id) return false
return routeList.some(
(route) =>
(route.startingPointId === startPoint.id && route.endPointId === endPoint.id) ||
(route.startingPointId === endPoint.id && route.endPointId === startPoint.id)
)
}
// 空间分区优化
const GRID_SIZE = 200 // 网格大小
const gridMap = new Map() // 网格映射
// 获取点所在的网格坐标
const getGridKey = (x, y) => {
const gridX = Math.floor(x / GRID_SIZE)
const gridY = Math.floor(y / GRID_SIZE)
return `${gridX},${gridY}`
}
// 更新网格数据
const updateGridMap = (points) => {
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, [])
}
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)
}
}
}
return nearbyPoints
}
// 优化后的findClosestPoint函数
const findClosestPoint = (x, y) => {
const list = state.allMapPointInfo
if (!Array.isArray(list) || list.length === 0) return null
// 更新网格数据
updateGridMap(list)
const searchRadius = 100
let minDistance = Infinity
let closestIndex = null
// 获取附近的点
const nearbyPoints = getNearbyPoints(x, y)
// 在附近的点中找最近的
for (const { point, index } of nearbyPoints) {
const dx = point.locationX - x
const dy = point.locationY - y
const distance = Math.sqrt(dx * dx + dy * dy)
const captureRadius = point.locationWide || 10
if (distance < minDistance && distance < captureRadius) {
minDistance = distance
closestIndex = index
}
}
return closestIndex
}
</script>
<style lang="scss" scoped>
.item-tooltip-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 12.5rem;
}
.edit-map-page {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.map-container {
position: relative;
width: 100%;
overflow: auto;
height: calc(100vh - 120px);
.map-bg {
background-size: contain;
background-repeat: no-repeat;
position: absolute;
top: 0;
left: 0;
.map-box-inner {
position: absolute;
top: 0;
left: 0;
}
}
}
.top-tool {
margin-bottom: 2px;
.top-tool-list-container {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
box-shadow: rgba(0, 0, 0, 0.06) 0rem 0.125rem 0.1875rem;
height: 60px;
padding: 0 0 0 4px;
.top-tool-list {
max-width: calc(100vw - 260px);
overflow-x: auto;
display: flex;
align-items: center;
text-align: center;
position: relative;
.top-tool-item {
display: flex;
align-items: center;
.tool-item {
cursor: pointer;
width: 42px;
height: 60px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.name {
cursor: pointer;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 13px;
color: #0d162a;
line-height: 1.25rem;
text-align: center;
font-style: normal;
margin-top: 0.25rem;
}
}
.line {
margin: 0 4px;
width: 0.0625rem;
height: 2.9375rem;
border: 0.0625rem solid #cccccc;
}
}
}
}
.right-tool-list {
position: absolute;
right: 0;
top: 70px;
background-color: #fff;
z-index: 999;
text-align: center;
box-shadow: rgba(0, 0, 0, 0.05) 0rem 0rem 0rem 0.0625rem;
max-height: calc(100vh - 200px);
overflow-y: auto;
.tool-item {
cursor: pointer;
padding: 6px 10px;
.name {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 12px;
color: #0d162a;
text-align: center;
font-style: normal;
}
}
}
}
.input-box-class {
position: absolute;
border: 0.0625rem solid #00329f;
padding: 0.25rem;
outline: none;
}
.selection-area-btn {
width: 5rem;
margin-left: 0.25rem;
}
.svg-div {
z-index: 99999;
svg {
cursor: crosshair;
path.selected {
stroke: blue;
}
}
}
.node-text {
white-space: nowrap; /* 防止文字自动换行 */
}
}
.tool-active {
background: #ebf1ff;
border-bottom: 0.125rem solid #1677ff;
}
.right-tool-active {
background: #ebf1ff !important;
}
.drop-down-menu {
.drop-down-menu-item {
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 0.6875rem 0;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 0.875rem;
color: #0d162a;
line-height: 1.25rem;
text-align: left;
font-style: normal;
border-bottom: 0.0625rem solid #e9e9e9;
}
}
.sort-num {
position: absolute;
font-size: 0.75rem;
user-select: none;
color: #000;
}
.sort-num-location {
position: absolute;
font-size: 0.75rem;
user-select: none;
color: #000;
}
.actual-location {
position: fixed;
bottom: 0.625rem;
right: 1rem;
background-color: #f48924;
color: #fff;
padding: 0.375rem;
border-radius: 0.125rem;
font-size: 0.875rem;
user-select: none;
}
.search-select {
.search-icon {
widows: 20px;
height: 20px;
}
}
// 美化滚动条
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
width: 6px;
background: rgba(#101f1c, 0.1);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.7);
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
transition: background-color 0.3s;
cursor: pointer;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(144, 147, 153, 0.3);
}
.ref-line {
position: absolute;
background-color: rgb(219, 89, 110);
z-index: 9999;
}
.v-line {
width: 1px;
}
.h-line {
height: 1px;
}
</style>