zn-admin-vue3-wcs/src/views/mapPage/realTimeMap/editMap.vue
2025-05-28 11:25:47 +08:00

3778 lines
122 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.popoverVisible"
>
<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="item in state.mapMarkCarList"
:key="item.id"
:label="item.robotNo"
:value="item.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 === 'bulkDelete'))
"
></div>
<el-button
v-if="
item.switchType === 'next' &&
(toolbarSwitchType === 'createLineLibrary' ||
toolbarSwitchType === 'createRegion' ||
toolbarSwitchType === 'drawRoute' ||
toolbarSwitchType === 'generateLine' ||
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="bottom"
:width="170"
trigger="click"
v-if="item.switchType === 'routeWidth'"
>
<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-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"
@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"
>
<!-- 节点合集 -->
<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="https://api.znkjfw.com/admin-api/infra/file/4/get/库位库存_png_179_1744098544821.png"
alt=""
:style="binLocationStyle(item, index)"
/>
<!-- 3 设备点 -->
<img
v-if="item.type === 3 && item.layerSelectionShow"
:src="
item.mapImageUrl ||
'https://api.znkjfw.com/admin-api/infra/file/4/get/设备 (4)_png_179_1744102025024.png'
"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
/>
<!-- 4 停车点 -->
<img
v-if="item.type === 4 && item.layerSelectionShow"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/停车位_png_179_1744098982069.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
/>
<!-- 5 区域变更点 -->
<img
v-if="item.type === 5 && item.layerSelectionShow"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/区域变更 (1)_png_179_1744100605191.png"
alt=""
style="background: #fff"
:style="nodeStyle(item, index)"
/>
<!-- 6 等待点 -->
<img
v-if="item.type === 6 && item.layerSelectionShow"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/等待_png_179_1744102070670.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>
<div v-if="imgBgObj.width && imgBgObj.height">
<svg
:width="imgBgObj.width"
:height="imgBgObj.height"
style="position: absolute; top: 0; left: 0; z-index: 9"
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
v-if="state.currentDragTarget.index == index"
id="startCircle"
:cx="curve.beginControlX"
:cy="curve.beginControlY"
r="5"
:fill="curve.isSelected ? '#f48924' : '#2d72d9'"
@mousedown="startDrag(curve, index, 'start')"
/>
<circle
v-if="state.currentDragTarget.index == index"
id="endCircle"
:cx="curve.endControlX"
:cy="curve.endControlY"
r="5"
:fill="curve.isSelected ? '#f48924' : '#2d72d9'"
@mousedown="startDrag(curve, index, 'end')"
/>
</template>
</template>
</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"
@submitNodeSuccess="submitNodeSuccess"
@addEventListener="addEventListener"
/>
<!-- 文字输入弹窗 -->
<textFormToolDialog
ref="textFormToolDialogRef"
v-if="state.textFormToolShow"
:inputBoxStyle="state.inputBoxStyle"
@textFormSuccess="textFormSuccess"
/>
<!-- 图层选择 -->
<layerSelectionToolDialog
v-if="state.isShowLayer"
ref="layerSelectionToolDialogRef"
@layerSelectionSuccess="layerSelectionSuccess"
/>
<!-- 设备弹窗选择 -->
<equipmentToolDialog
ref="equipmentToolDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
/>
<!-- 区域选择 -->
<itemAreaSettingDialog
ref="itemAreaSettingDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
@itemAreaSettingSubmitSuccess="itemAreaSettingSubmitSuccess"
/>
<!-- 线库设置 -->
<lineLibrarySettingDialog
ref="lineLibrarySettingDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
@submitLineLibraryFormSuccess="submitLineLibraryFormSuccess"
/>
<!-- 编辑地图路线 -->
<editMapRouteDialog
v-if="imgBgObj.positionMapId"
ref="editMapRouteDialogRef"
@editMapRouteDialogSubmit="editMapRouteDialogSubmit"
@addEventListener="addEventListener"
:imgBgObj="imgBgObj"
/>
<!-- 线库管理 -->
<lineLibraryManagementDialog
ref="lineLibraryManagementDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
@lineLibraryManagementDelete="lineLibraryManagementDelete"
@lineLibraryManagementEdit="lineLibraryManagementEdit"
/>
<!-- 区域管理 -->
<itemAreaManagementDialog
ref="itemAreaManagementDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
@itemAreaManagementDelete="itemAreaManagementDelete"
@itemAreaManagementEdit="itemAreaManagementEdit"
/>
<!-- 生成直线 选择开始点和结束点 -->
<GenerateStraightLinesDialog
ref="GenerateStraightLinesDialogRef"
@GenerateStraightLinesSubmit="GenerateStraightLinesSubmit"
/>
</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 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 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.beginHigh = Number(item.locationDeepPx)
route.beginWidth = Number(item.locationWidePx)
route.actualStartPointX = actualPoint.actualLocationX
route.actualStartPointY = actualPoint.actualLocationY
}
if (item.id === route.endPointId) {
route.endPointX = x
route.endPointY = y
route.endHigh = Number(item.locationDeepPx)
route.endWidth = Number(item.locationWidePx)
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.beginHigh = Number(item.locationDeepPx)
route.beginWidth = Number(item.locationWidePx)
route.actualStartPointX = actualPoint.actualLocationX
route.actualStartPointY = actualPoint.actualLocationY
}
if (item.id === route.endPointId) {
route.endPointX = x
route.endPointY = y
route.endHigh = Number(item.locationDeepPx)
route.endWidth = Number(item.locationWidePx)
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) => {
const x = disposeEventPoints(e).x
const y = disposeEventPoints(e).y
const actualLocationX = disposeEventPoints(e).actualLocationX
const actualLocationY = disposeEventPoints(e).actualLocationY
state.actualLocation.x = actualLocationX
state.actualLocation.y = actualLocationY
//新增节点
if (toolbarSwitchType.value === 'drawNodes') {
state.allMapPointInfo.push({
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: {} //存 设备点 停车点 文字
})
addEditHistory()
}
//文字输入
if (toolbarSwitchType.value === 'text') {
state.showInputBox = true
state.inputBoxStyle.locationX = x
state.inputBoxStyle.locationY = y
state.inputBoxStyle.actualLocationX = actualLocationX
state.inputBoxStyle.actualLocationY = actualLocationY
// 聚焦输入框
setTimeout(() => {
inputBoxRef.value.focus()
}, 0)
}
//测距
if (toolbarSwitchType.value === 'ranging') {
measureDistancesClick(e)
}
}
//输入文字样式改变
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) => {
form.locationDeepPx = Number(form.locationDeep) / 100 / imgBgObj.resolution
form.locationWidePx = Number(form.locationWide) / 100 / imgBgObj.resolution
state.allMapPointInfo[state.currentItemIndex] = form
//节点位置改变 通知路径里面的节点也改变
state.mapRouteList.forEach((item) => {
if (form.id === item.startingPointId) {
item.startPointX = form.locationX
item.startPointY = form.locationY
item.beginHigh = Number(form.locationDeepPx)
item.beginWidth = Number(form.locationWidePx)
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.endHigh = Number(form.locationDeepPx)
item.endWidth = Number(form.locationWidePx)
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: false
},
{
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: '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
}
],
isShowToolbar: false, //工具栏展示隐藏
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
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: [], //标记的车辆列表
popoverVisible: false, //标记弹窗
noLocationNumberList: [], //没有排序的库位index
isShowSortNum: false, //是否显示序号
actualLocation: {
x: '',
y: ''
},
gridForm: {
gridNum: 20 + 'px'
},
routeWidthForm: {
routeWidth: 3
},
isSearchSelectVisible: false, //是否显示选择框
searchSelectedOption: '',
haveSortNumMapPointInfo: []
})
//网格的样式
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') &&
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'
) {
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`
//工具切换 不适用框选的 要把框选的信息都删掉
state.drawSelectionAreaShow = false
state.allDrawSelectionAreaBox = []
state.drawSelectionPointList = []
}
//非测距
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 === 'editRoute' ||
toolbarSwitchType.value === 'generateLine'
) {
state.prohibitedOperation = true
} else {
state.prohibitedOperation = false
}
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 'showSortNum':
// 显示节点序号
//网格
if (toolbarSwitchType.value === 'showSortNum') {
state.isShowSortNum = true
item.isActive = true
} else {
state.isShowSortNum = false
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 () => {
state.allMapPointInfo[state.currentItemIndex].locationX = Number(state.moveForm.locationX)
state.allMapPointInfo[state.currentItemIndex].locationY = Number(state.moveForm.locationY)
addEditHistory()
}
//旋转工具表单提交
const rotationFormSubmit = () => {
state.allMapPointInfo[state.currentItemIndex].angle = state.rotationForm.angle
addEditHistory()
}
//标记
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.popoverVisible = true
}
//标记提交
const macAddressChange = (e) => {
const targetItem = state.mapMarkCarList.find((item) => item.macAddress === e)
if (targetItem) {
state.markForm.robotNo = targetItem.robotNo
}
}
//取消标记
const markFormCancel = () => {
state.popoverVisible = 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)
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.beginHigh = Number(item.locationDeepPx)
route.beginWidth = Number(item.locationWidePx)
route.actualStartPointX = point.x
route.actualStartPointY = point.y
}
if (item.id === route.endPointId) {
route.endPointX = pointPx.x
route.endPointY = pointPx.y
route.endHigh = Number(item.locationDeepPx)
route.endWidth = Number(item.locationWidePx)
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.popoverVisible = 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('选择的节点未保存')
// 重置状态
state.startDrawPointIndex = -1 // 起始点的索引
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
}
}
}
//开始框选绘制
const startDrawSelection = (event) => {
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute' ||
toolbarSwitchType.value == 'generateLine' ||
toolbarSwitchType.value === 'bulkDelete'
) {
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
const x = disposeEventPoints(event).x
const y = disposeEventPoints(event).y
// 确保点击在背景区域内
if (x >= 0 && x <= backgroundRect.width && y >= 0 && y <= backgroundRect.height) {
state.drawSelectionAreaShow = true
state.drawSelectionStartPoint = { x: x, y: y }
state.drawSelectionAreaBox = { x: x, y: y, width: 0, height: 0 }
}
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
}
}
// 更新框选区域
const updateDrawSelection = (event) => {
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute' ||
toolbarSwitchType.value === 'generateLine' ||
toolbarSwitchType.value === 'bulkDelete'
) {
if (state.drawSelectionAreaShow) {
const x = disposeEventPoints(event).x
const y = disposeEventPoints(event).y
state.drawSelectionAreaBox = {
x: Math.min(state.drawSelectionStartPoint.x, x),
y: Math.min(state.drawSelectionStartPoint.y, y),
width: Math.abs(Number(x) - Number(state.drawSelectionStartPoint.x)),
height: Math.abs(Number(y) - Number(state.drawSelectionStartPoint.y))
}
}
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
}
if (toolbarSwitchType.value === 'clickDrawRoute') {
// 实时绘制
if (state.isDrawing) {
const x = disposeEventPoints(event).x
const y = disposeEventPoints(event).y
state.currentDrawX = x
state.currentDrawY = y
}
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
}
}
//结束框选绘制
const endDrawSelection = (event) => {
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute' ||
toolbarSwitchType.value === 'generateLine' ||
toolbarSwitchType.value === 'bulkDelete'
) {
state.drawSelectionAreaShow = false
state.allDrawSelectionAreaBox.push({ ...state.drawSelectionAreaBox })
state.drawSelectionAreaBox = { x: 0, y: 0, width: 0, height: 0 }
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
return
}
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('选择的节点未保存')
// 重置状态
state.startDrawPointIndex = -1 // 起始点的索引
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
return
}
const newLine = {
startPointX: state.startDrawPoint.locationX,
startPointY: state.startDrawPoint.locationY,
endPointX: endPoint.locationX,
endPointY: endPoint.locationY
}
// 检查是否已存在相同的直线
const isDuplicate = state.mapRouteList.some(
(line) =>
(line.startPointX === newLine.startPointX &&
line.startPointY === newLine.startPointY &&
line.endPointX === newLine.endPointX &&
line.endPointY === newLine.endPointY) ||
(line.startPointX === newLine.endPointX &&
line.startPointY === newLine.endPointY &&
line.endPointX === newLine.startPointX &&
line.endPointY === newLine.startPointY)
)
if (isDuplicate) {
message.warning('此路线已存在')
} else {
// 保存当前直线
state.mapRouteList.push({
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, //实际开始点位x轴
actualStartPointY: state.startDrawPoint.actualLocationY, //实际开始点位y轴
actualEndPointX: endPoint.actualLocationX, //实际结束点位x轴
actualEndPointY: endPoint.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:反反)
beginWidth: state.startDrawPoint.locationWidePx, //起点宽
beginHigh: state.startDrawPoint.locationDeepPx, // 起点高
endWidth: endPoint.locationWidePx, // 终点宽
endHigh: endPoint.locationDeepPx, // 终点高
startingSortNum: state.startDrawPoint.sortNum,
endPointSortNum: endPoint.sortNum
})
addEditHistory()
}
}
// 重置状态
state.startDrawPointIndex = -1 // 起始点的索引
state.startDrawPoint = null
state.isDrawing = false
state.currentDrawX = 0
state.currentDrawY = 0
}
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
return
}
}
// 找到最近的点
const findClosestPoint = (x, y) => {
let minDistance = Infinity
let closestIndex = null
let list = state.allMapPointInfo
list.forEach((point, index) => {
const distance = Math.sqrt((point.locationX - x) ** 2 + (point.locationY - y) ** 2)
if (distance < minDistance && distance < point.locationWide) {
// 10 是点的捕捉范围
minDistance = distance
closestIndex = index
}
})
return closestIndex
}
//点击区域
const clickDrawSelectionArea = () => {
let points = state.allMapPointInfo
state.drawSelectionPointList = []
state.allDrawSelectionAreaBox.forEach((box) => {
points.forEach((point) => {
if (
point.locationX >= box.x &&
point.locationX <= box.x + box.width &&
point.locationY >= box.y &&
point.locationY <= box.y + box.height
) {
state.drawSelectionPointList.push(point)
}
})
})
// 清空框选区域
state.allDrawSelectionAreaBox = []
//去重
state.drawSelectionPointList = deduplicateArrayById(state.drawSelectionPointList)
//只要库位的
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:反反)
beginWidth: routeList[0].locationWidePx, //起点宽
beginHigh: routeList[0].locationDeepPx, // 起点高
endWidth: routeList[1].locationWidePx, // 终点宽
endHigh: routeList[1].locationDeepPx // 终点高
}
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()
}
}
//生成直线 选择完成开始点和结束点
const GenerateStraightLinesSubmit = (pointList, form) => {
const list = mapPointsToLine(pointList, form.startPointId, form.endPointId)
const idNameMap = {}
list.forEach((item) => {
idNameMap[item.id] = item
})
// 遍历第二个数组,更新 name
state.allMapPointInfo.forEach((item) => {
if (idNameMap[item.id]) {
let actualPoint = disposeEventPoint(
idNameMap[item.id].locationX,
idNameMap[item.id].locationY
)
item.locationX = idNameMap[item.id].locationX
item.locationY = idNameMap[item.id].locationY
item.actualLocationX = actualPoint.actualLocationX
item.actualLocationY = actualPoint.actualLocationY
}
})
state.mapRouteList.forEach((item) => {
if (idNameMap[item.startingPointId]) {
let actualPoint = disposeEventPoint(
idNameMap[item.startingPointId].locationX,
idNameMap[item.startingPointId].locationY
)
item.startPointX = idNameMap[item.startingPointId].locationX
item.startPointY = idNameMap[item.startingPointId].locationY
item.actualStartPointX = actualPoint.actualLocationX
item.actualStartPointY = actualPoint.actualLocationY
}
if (idNameMap[item.endPointId]) {
let actualPoint = disposeEventPoint(
idNameMap[item.endPointId].locationX,
idNameMap[item.endPointId].locationY
)
item.endPointX = idNameMap[item.endPointId].locationX
item.endPointY = idNameMap[item.endPointId].locationY
item.actualEndPointX = actualPoint.actualLocationX
item.actualEndPointY = actualPoint.actualLocationY
}
})
addEditHistory()
}
//去重
const deduplicateArrayById = (arr) => {
const idSet = new Set()
return arr.filter((item) => {
if (idSet.has(item.id)) {
return false
}
idSet.add(item.id)
return true
})
}
//返回生成线库时 排序相同项的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:反反)
beginWidth: 8, //起点宽
beginHigh: 8, // 起点高
endWidth: 8, // 终点宽
endHigh: 8 // 终点高
}
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:反反)
beginWidth: 8, //起点宽
beginHigh: 8, // 起点高
endWidth: 8, // 终点宽
endHigh: 8 // 终点高
}
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.beginHigh = Number(form.beginHigh)
item.beginWidth = Number(form.beginWidth)
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.endHigh = Number(form.endHigh)
item.endWidth = Number(form.endWidth)
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()
}
}
//节点的保存
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)
}
})
await MapApi.batchSaveOrEditOrDelMapItem(imgBgObj.positionMapId, list)
}
//路线的保存
const saveMapRoute = async () => {
await MapApi.createOrEditOrDelPositionMapLine(imgBgObj.positionMapId, state.mapRouteList)
}
//线库新增 要在库位新增线库信息
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 = (event.clientX - rect.left + scrollLeft) / state.imageChangeMultiple / devicePixelRatio
const y = (event.clientY - rect.top + scrollTop) / state.imageChangeMultiple / devicePixelRatio
// 转换为实际坐标
const actualLocationX = imgBgObj.origin[0] + x * imgBgObj.resolution
const actualLocationY = imgBgObj.origin[1] + (imgBgObj.height - y) * imgBgObj.resolution
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
}
}
// 计算直线中间箭头的路径
const getLineMidArrowPath = (item) => {
const midX = (Number(item.startPointX) + Number(item.endPointX)) / 2
const midY = (Number(item.startPointY) + Number(item.endPointY)) / 2
let dx = item.endPointX - item.startPointX
let dy = item.endPointY - item.startPointY
let length = Math.sqrt(dx * dx + dy * dy)
if (length === 0) {
return `M ${midX} ${midY} L ${midX} ${midY}`
}
const unitDx = dx / length
const unitDy = dy / length
const arrowLength = 1
const startXArrow = midX - unitDx * arrowLength
const startYArrow = midY - unitDy * arrowLength
const endXArrow = midX
const endYArrow = midY
return `M ${startXArrow} ${startYArrow} L ${endXArrow} ${endYArrow}`
}
// 计算贝塞尔曲线中间箭头的路径(简单近似)
const getBezierMidArrowPath = (item) => {
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
path.setAttribute('d', item.curvePath)
const length = path.getTotalLength()
const midPoint = path.getPointAtLength(length / 2)
const prevPoint = path.getPointAtLength(length / 2 - 1)
return `M ${prevPoint.x} ${prevPoint.y} L ${midPoint.x} ${midPoint.y}`
}
// 计算贝塞尔曲线中间文字的 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)
})
</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.75rem;
.top-tool-list {
display: flex;
align-items: center;
text-align: center;
position: relative;
.top-tool-item {
display: flex;
align-items: center;
.tool-item {
cursor: pointer;
width: 44px;
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: 80px;
background-color: #fff;
z-index: 999;
text-align: center;
box-shadow: rgba(0, 0, 0, 0.05) 0rem 0rem 0rem 0.0625rem;
.tool-item {
cursor: pointer;
padding: 8px;
.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;
}
}
</style>