3187 lines
102 KiB
Vue
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>
|