zn-admin-vue3-wcs/src/views/mapPage/realTimeMap/editMap.vue
2025-03-31 10:02:45 +08:00

3187 lines
102 KiB
Vue

<template>
<div class="edit-map-page" @wheel="handleWheel">
<div class="top-tool">
<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="24" />
<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: 64px; height: 30px; 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="360"
controls-position="right"
/>
</el-form-item>
<div style="text-align: right">
<el-button
size="small"
style="width: 64px; height: 30px; background: #00329f"
color="#00329F"
@click="rotationFormSubmit"
>确认</el-button
>
</div>
</el-form>
</el-popover>
<!-- 线库 -->
<el-popover
placement="bottom"
trigger="click"
v-else-if="item.switchType === 'lineLibrary'"
:popper-style="{ padding: '0px' }"
>
<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="24" />
<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: '0px' }"
>
<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="24" />
<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="24" />
<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: 240px" disabled />
</el-form-item>
<el-form-item label="原节点" v-if="state.markForm.originalNode">
<el-input v-model="state.markForm.originalNode" style="width: 240px" disabled />
</el-form-item>
<div style="text-align: right">
<el-button
size="small"
style="width: 64px; height: 30px; background: #efefef"
@click="markFormCancel()"
>取消</el-button
>
<el-button
size="small"
style="width: 64px; height: 30px; 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="24" />
<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'))
"
></div>
<el-button
v-if="
item.switchType === 'next' &&
(toolbarSwitchType === 'createLineLibrary' ||
toolbarSwitchType === 'createRegion' ||
toolbarSwitchType === 'drawRoute' ||
toolbarSwitchType === 'generateLine')
"
type="danger"
class="selection-area-btn"
@click="clickDrawSelectionArea"
>确定</el-button
>
</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)"
>
<Icon :icon="item.icon" :size="24" />
<div class="name"> {{ item.name }} </div>
</div>
</div>
</div>
<div
class="map-container"
ref="mapContainer"
: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"
:class="state.isShowGrid ? 'grid-show' : ''"
:style="{
width: imgBgObj.width + 'px',
height: imgBgObj.height + 'px'
}"
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>
<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
v-if="item.type === 1 && item.layerSelectionShow"
:style="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
backgroundColor: state.currentItemIndex === index ? '#f48924' : '#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_1739326653035.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/设备点_png_179_1739327151877.png'
"
alt=""
:style="nodeStyle(item, index)"
/>
<!-- 4 停车点 -->
<img
v-if="item.type === 4 && item.layerSelectionShow"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/停车场-01_png_179_1739326933020.png"
alt=""
:style="nodeStyle(item, index)"
/>
<!-- 5 区域变更点 -->
<img
v-if="item.type === 5 && item.layerSelectionShow"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/区域_png_179_1739327151876.png"
alt=""
: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_1739326991439.png"
alt=""
:style="nodeStyle(item, index)"
/>
</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 ? '1px 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="#00329F"
stroke-width="3"
/>
<template v-if="state.mapRouteList.length > 0">
<template v-for="(curve, index) in state.mapRouteList" :key="index">
<!-- 定义箭头 -->
<defs>
<marker
id="forward-arrow"
viewBox="0 0 10 10"
refX="10"
refY="5"
orient="auto"
markerWidth="2"
markerHeight="2"
>
<path d="M 0 0 L 10 5 L 0 10 z" fill="black" />
</marker>
<!-- 反向箭头 -->
<marker
id="backward-arrow"
viewBox="0 0 10 10"
refX="0"
refY="5"
orient="auto"
markerWidth="2"
markerHeight="2"
>
<path d="M 10 0 L 0 5 L 10 10 z" fill="black" />
</marker>
</defs>
<!-- 直线 -->
<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' : '#00329F'"
stroke-width="3"
@click="handleChooseRoute(curve, index)"
@dblclick="handleEditRoute(curve, index)"
/>
<text
: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"
>
{{ calculateRouteLength(curve, 'line') }}米
</text>
<path
v-if="curve.isSelected"
:d="getLineMidArrowPath(curve)"
stroke="none"
fill="black"
stroke-width="3"
:marker-start="curve.direction === 2 ? 'url(#backward-arrow)' : ''"
:marker-end="
curve.direction === 2 ? 'url(#forward-arrow)' : 'url(#forward-arrow)'
"
/>
</template>
<template v-else>
<path
id="curvePath"
:d="getCurvePath(curve)"
:stroke="curve.isSelected ? '#f48924' : '#00329F'"
stroke-width="3"
fill="none"
@click="handleChooseRoute(curve, index)"
@dblclick="handleEditRoute(curve, index)"
/>
<text
:x="computedCurveTextX(curve)"
:y="computedCurveTextY(curve)"
font-size="11"
text-anchor="middle"
fill="black"
v-if="curve.isSelected"
>
{{ calculateRouteLength(curve, 'curve') }}米
</text>
<path
v-if="curve.isSelected"
:d="getBezierMidArrowPath(curve)"
stroke="none"
fill="black"
stroke-width="3"
:marker-start="curve.direction === 2 ? 'url(#backward-arrow)' : ''"
:marker-end="
curve.direction === 2 ? 'url(#forward-arrow)' : 'url(#forward-arrow)'
"
/>
<!-- 第一条控制线 -->
<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' : '#00329F'"
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' : '#00329F'"
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' : '#00329F'"
@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' : '#00329F'"
@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: '2px 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: '2px 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>
<!-- 节点编辑 -->
<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 inputBoxRef = ref() //文字输入框
const message = useMessage() // 消息弹窗
//其他节点的样式
const nodeStyle = (item, index) => {
return {
verticalAlign: 'top',
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border: state.currentItemIndex === index ? '1px dashed #000' : 'none'
}
}
//库位的样式
const binLocationStyle = (item, index) => {
return {
verticalAlign: 'top',
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border:
state.currentItemIndex === index
? '1px dashed #000'
: state.noLocationNumberList.includes(index)
? '1px dashed red'
: '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) => {
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 }
}
//节点编辑
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
//新增节点
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:circle-plus-filled',
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:semi-select',
isActive: false
},
{
switchType: 'generateLine',
name: '生成直线',
icon: 'ep:finished',
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
})
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') &&
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'
) {
state.cursorStyle = 'crosshair'
} else {
state.cursorStyle = `auto`
}
//工具切换 不适用框选的 要把框选的信息都删掉
if (
toolbarSwitchType.value !== 'createLineLibrary' &&
toolbarSwitchType.value !== 'createRegion'
) {
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'
) {
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':
// 生成线库
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
}
}
//复制
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
}
let res = await MapApi.getAGVPointInformation(state.markForm.macAddress)
if (res) {
let content = JSON.parse(res.content)
let point = JSON.parse(content[state.markForm.robotNo]) //标记传过来的数据
let pointPx = convertActualToBrowser(point.x, point.y)
let actualPoint = disposeEventPoint(pointPx.x, pointPx.y)
if (state.currentItemIndex !== -1) {
state.allMapPointInfo[state.currentItemIndex].locationX = pointPx.x
state.allMapPointInfo[state.currentItemIndex].locationY = pointPx.y
state.allMapPointInfo[state.currentItemIndex].actualLocationX = actualPoint.actualLocationX
state.allMapPointInfo[state.currentItemIndex].actualLocationY = actualPoint.actualLocationY
//更改路线里的
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 = actualPoint.actualLocationX
route.actualStartPointY = actualPoint.actualLocationY
}
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 = actualPoint.actualLocationX
route.actualEndPointY = actualPoint.actualLocationY
}
})
addEditHistory()
} else {
//新增一个节点
state.allMapPointInfo.push({
positionMapId: imgBgObj.positionMapId, //地图的id
layerSelectionShow: true,
locationX: pointPx.x,
locationY: pointPx.y,
actualLocationX: actualPoint.actualLocationX,
actualLocationY: actualPoint.actualLocationY,
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: 0 //弧度
})
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'
) {
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'
) {
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'
) {
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 // 终点高
})
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 = []
//只要库位的
let binLocation = state.drawSelectionPointList.filter((item) => item.type === 2)
//只要路径点的
// let routeList = state.drawSelectionPointList.filter((item) => item.type === 1)
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
}
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)
}
}
//生成直线 选择完成开始点和结束点
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 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 = startPoint.locationX - endPoint.locationX
const dy = startPoint.locationY - endPoint.locationY
// 处理垂直直线的情况
if (dx === 0) {
return points.map((point) => {
if (point === endPoint || point === startPoint) {
return point
}
return {
...point,
locationX: endPoint.locationX
}
})
}
const slope = dy / dx
const intercept = endPoint.locationY - slope * endPoint.locationX
return points.map((point) => {
if (point === endPoint || point === startPoint) {
return point
}
const newY = slope * point.locationX + intercept
return {
...point,
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 handleEditRoute = (item, index) => {
state.mapRouteList.forEach((curve, i) => {
curve.isSelected = i === index
})
state.currentDragTarget.index = index
state.selectedCurve = item
removeEventListener() //移除监听
editMapRouteDialogRef.value.open(JSON.parse(JSON.stringify(item)))
}
//单击 选中
const handleChooseRoute = (item, index) => {
state.mapRouteList.forEach((curve, i) => {
curve.isSelected = i === index
})
state.selectedCurve = item
state.currentDragTarget.index = index
//让节点不选中
state.currentItemIndex = -1
}
//编辑路线成功
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).actualLocationX
}
})
//增加一条历史记录
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: '2px',
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: '2px',
borderRadius: '4px',
fontSize: '12px',
color: 'black',
whiteSpace: 'nowrap', // 防止文字换行
pointerEvents: 'none' // 防止文字遮挡点击事件
}
}
return {}
})
// 计算点的样式
const getRangingPointStyle = (point) => ({
position: 'absolute',
left: `${point.x - 4}px`,
top: `${point.y - 4}px`,
width: '8px',
height: '8px',
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 === 5 || 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.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 === 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)
})
}
//获取所有的路线
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.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.areaId = obj.id
item.dataList.forEach((node) => {
node.areaName = obj.areaName
node.areaId = obj.id
})
}
})
}
//线库删除 要在库位中删除线库信息
const lineLibraryManagementDelete = (mapItemIds) => {
state.allMapPointInfo.forEach((item) => {
if (mapItemIds.includes(item.id)) {
item.laneId = undefined
item.dataList.forEach((node) => {
;``
node.laneName = undefined
node.laneId = undefined
})
}
})
}
//区域删除 要在库位删除区域信息
const itemAreaManagementDelete = (mapItemIds) => {
state.allMapPointInfo.forEach((item) => {
if (mapItemIds.includes(item.id)) {
item.areaId = undefined
item.dataList.forEach((node) => {
node.areaName = undefined
node.areaId = undefined
})
}
})
}
//线库编辑 要在库位中编辑线库信息
const lineLibraryManagementEdit = (obj) => {
console.log(obj)
state.allMapPointInfo.forEach((item) => {
if (obj.mapItemIds.includes(item.id)) {
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.areaId = obj.id
item.dataList.forEach((node) => {
node.areaName = obj.areaName
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)
let y = Math.max(y1 - Number(pointY), 0)
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
)
}
document.onmousedown = function (e) {
//左击
if (e.button == 2) {
state.selectedCurve = ''
if (state.currentDragTarget.index !== null) {
state.mapRouteList[state.currentDragTarget.index].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 {
message.warning('不能在放大了')
}
} else {
//缩小
if (state.imageChangeMultiple > 0.2) {
state.imageChangeMultiple -= 0.2
} else {
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: 200px;
}
.edit-map-page {
.map-container {
position: relative;
width: 100%;
overflow: auto;
// height: 85vh;
height: calc(100vh - 140px);
.map-bg {
background-size: contain;
background-repeat: no-repeat;
position: absolute;
// top: 18px;
// left: 18px;
top: 0;
left: 0;
.map-box-inner {
position: absolute;
top: 0;
left: 0;
}
}
}
.top-tool {
margin-top: 5px;
margin-bottom: 3px;
.top-tool-list {
display: flex;
align-items: center;
text-align: center;
background-color: #fff;
padding: 0 12px;
height: 70px;
box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 3px;
.top-tool-item {
display: flex;
align-items: center;
.tool-item {
cursor: pointer;
width: 50px;
height: 70px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.name {
cursor: pointer;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #0d162a;
line-height: 20px;
text-align: center;
font-style: normal;
margin-top: 4px;
}
}
.line {
margin: 0 12px;
width: 1px;
height: 47px;
border: 1px solid #cccccc;
}
}
}
.right-tool-list {
position: absolute;
right: 0;
top: 114px;
background-color: #fff;
z-index: 999;
text-align: center;
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
.tool-item {
cursor: pointer;
padding: 10px;
.name {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #0d162a;
line-height: 20px;
text-align: center;
font-style: normal;
}
}
}
}
.input-box-class {
position: absolute;
border: 1px solid #00329f;
padding: 4px;
outline: none;
}
.selection-area-btn {
width: 80px;
margin-left: 4px;
}
.grid-show {
// background: linear-gradient(-90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
// linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px);
// background-size:
// 20px 20px,
// 20px 20px;
background-image: repeating-linear-gradient(
to right,
rgba(0, 0, 0, 0.1),
rgba(0, 0, 0, 0.1) 1px,
transparent 1px,
transparent 50px
),
repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0.1),
rgba(0, 0, 0, 0.1) 1px,
transparent 1px,
transparent 50px
);
}
.svg-div {
z-index: 99999;
svg {
cursor: crosshair;
path.selected {
stroke: blue;
}
}
}
.node-text {
white-space: nowrap; /* 防止文字自动换行 */
}
}
.tool-active {
background: #ebf1ff;
border-bottom: 2px 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: 11px 0;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #0d162a;
line-height: 20px;
text-align: left;
font-style: normal;
border-bottom: 1px solid #e9e9e9;
}
}
</style>