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

2527 lines
81 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="edit-map-page">
<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>
<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'))
"
></div>
<el-button
v-if="
item.switchType === 'next' &&
(toolbarSwitchType === 'createLineLibrary' ||
toolbarSwitchType === 'createRegion' ||
toolbarSwitchType === 'drawRoute')
"
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"
: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"
style="border: none; z-index: 999"
>
<!-- 节点合集 -->
<div @mousedown="startFromPoint(index, $event)">
<!-- 1 路径点 -->
<el-tooltip
class="box-item"
effect="dark"
:content="item.sortNum || '节点未保存'"
placement="top"
trigger="click"
>
<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="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border: state.currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 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="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border: state.currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 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="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border: state.currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 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="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border: state.currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 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="{
width: item.locationWidePx + 'px',
height: item.locationDeepPx + 'px',
border: state.currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
</el-tooltip>
</div>
<div
class="node-text"
v-if="item.type === 7 && item.layerSelectionShow"
:style="{
position: 'absolute',
left: item.locationX + 'px',
top: item.locationY + 'px',
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="5"
/>
<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 9 9" refX="10" refY="5" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="black" />
</marker>
<!-- 反向箭头 -->
<marker id="backward-arrow" viewBox="0 0 9 9" refX="0" refY="5" orient="auto">
<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'"
:marker-start="curve.direction === 2 ? 'url(#double-arrow-start)' : ''"
marker-end="url(#single-arrow)"
stroke-width="5"
@click="handleChooseRoute(curve, index)"
@dblclick="handleEditRoute(curve, index)"
/>
<path
v-if="curve.isSelected"
:d="getLineMidArrowPath(curve)"
stroke="none"
fill="black"
stroke-width="4"
:marker-start="curve.direction === 2 ? 'url(#backward-arrow)' : ''"
:marker-end="
curve.direction === 2 ? 'url(#forward-arrow)' : 'url(#forward-arrow)'
"
/>
</template>
<template v-else>
<path
:d="getCurvePath(curve)"
:stroke="curve.isSelected ? '#f48924' : '#00329F'"
stroke-width="5"
fill="none"
:marker-start="curve.direction === 2 ? 'url(#double-arrow-start)' : ''"
marker-end="url(#single-arrow)"
@click="handleChooseRoute(curve, index)"
@dblclick="handleEditRoute(curve, index)"
/>
<path
v-if="curve.isSelected"
:d="getBezierMidArrowPath(curve)"
stroke="none"
fill="black"
stroke-width="4"
: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-for="(point, index) in state.measureDistancesPoints"
:key="index"
:style="{
position: 'absolute',
left: `${point.x}px`,
top: `${point.y}px`,
width: '10px',
height: '10px',
backgroundColor: '#00329F',
borderRadius: '50%'
}"
></div>
<div
v-if="state.measureDistancesPoints.length === 2"
:style="{
position: 'absolute',
left: `${state.measureDistancesPoints[0].x + 5}px`,
top: `${state.measureDistancesPoints[0].y + 5}px`,
width: `${computedLineWidth}px`,
height: '2px',
backgroundColor: '#00329F',
transform: `rotate(${computedLineAngle}deg)`,
transformOrigin: '0 0',
textAlign: 'center'
}"
>
距离{{ state.measureDistancesNum.toFixed(2) }}</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
ref="editNodePropertiesRef"
:positionMapId="imgBgObj.positionMapId"
@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"
/>
<!-- 线库设置 -->
<lineLibrarySettingDialog
ref="lineLibrarySettingDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
/>
<!-- 编辑地图路线 -->
<editMapRouteDialog
ref="editMapRouteDialogRef"
@editMapRouteDialogSubmit="editMapRouteDialogSubmit"
@addEventListener="addEventListener"
:imgBgObj="imgBgObj"
/>
<!-- 线库管理 -->
<lineLibraryManagementDialog
ref="lineLibraryManagementDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
/>
<!-- 区域管理 -->
<itemAreaManagementDialog
ref="itemAreaManagementDialogRef"
:positionMapId="imgBgObj.positionMapId"
@addEventListener="addEventListener"
/>
</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 mapScaleTool from './components-tool/map-scale-tool.vue'
import * as MapApi from '@/api/map/map'
import cursorCollection from './cursorCollection'
defineOptions({ name: 'editMapPageRealTimeMap' })
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 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 actualPoint = disposeEventPoint(x, y)
state.allMapPointInfo[index].locationX = x
state.allMapPointInfo[index].locationY = y
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.locationDeep)
route.endWidth = Number(item.locationDeep)
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: 50,
locationWide: 50,
locationDeepPx: 10,
locationWidePx: 10,
angle: 0,
draggable: true,
resizable: true,
rotatable: false,
lockAspectRatio: false, //横纵比
mapImageUrl: '',
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
})
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()
}
//工具栏点击
const toolbarSwitchType = ref('')
const state = reactive({
topToolList: [
// {
// switchType: 'open',
// name: '打开',
// icon: 'ep:folder-add',
// isActive: false
// },
{
switchType: 'save',
name: '保存',
icon: 'ep:folder-checked',
isActive: false
},
// {
// switchType: 'saveAs',
// name: '另存为',
// icon: 'ep:folder-opened',
// isActive: false
// },
{
switchType: 'choose',
name: '选择',
icon: 'ep:position',
isActive: false
},
{
switchType: 'move',
name: '移动',
icon: 'ep:rank',
isActive: false
},
{
switchType: 'revolve',
name: '旋转',
icon: 'ep:refresh-right',
isActive: false
},
{
switchType: 'copy',
name: '复制',
icon: 'ep:document',
isActive: false
},
{
switchType: 'paste',
name: '粘贴',
icon: 'ep:copy-document',
isActive: false
},
{
switchType: 'delete',
name: '删除',
icon: 'ep:delete',
isActive: false
},
{
switchType: 'tools',
name: '工具',
icon: 'ep:briefcase',
isActive: true
},
{
switchType: 'lineLibrary',
name: '线库',
icon: 'ep:message-box',
isActive: false
},
{
switchType: 'region',
name: '区域',
icon: 'ep:full-screen',
isActive: false
},
{
switchType: 'text',
name: '文字',
icon: 'ep:edit-pen',
isActive: false
},
{
switchType: 'equipment',
name: '设备',
icon: 'ep:video-camera-filled',
isActive: false
},
{
switchType: 'ranging',
name: '测距',
icon: 'ep:folder-add',
isActive: false
},
{
switchType: 'layer',
name: '图层',
icon: 'ep:copy-document',
isActive: false
},
{
switchType: 'marker',
name: '标记',
icon: 'ep:map-location',
isActive: false
},
{
switchType: 'grid',
name: '网格',
icon: 'ep:grid',
isActive: true
},
{
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
}
],
isShowToolbar: true, //工具栏展示隐藏
isShowGrid: true, //网格展示隐藏
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 //当前处在哪个工具
})
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 !== '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':
//移动
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()
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':
// 标记
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 < 3) {
state.imageChangeMultiple += 0.2
} else {
message.warning('不能在放大了')
}
break
case 'smaller':
//缩小
if (state.imageChangeMultiple > 0.3) {
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
}
}
//复制
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,
areaId: null,
locationNumber: copyMapItem.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 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'
) {
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() // 阻止默认行为(避免选中图片或文本)
return
}
}
// 更新框选区域
const updateDrawSelection = (event) => {
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute'
) {
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() // 阻止默认行为(避免选中图片或文本)
return
}
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() // 阻止默认行为(避免选中图片或文本)
return
}
//结束框选绘制
const endDrawSelection = (event) => {
if (
toolbarSwitchType.value === 'createLineLibrary' ||
toolbarSwitchType.value === 'createRegion' ||
toolbarSwitchType.value === 'drawRoute'
) {
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()
}
}
//计算是不是在同一条直线的
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 calculateDistance = (point1, point2) => {
const dx = point2.x - point1.x
const dy = point2.y - point1.y
return Math.sqrt(dx * dx + dy * dy)
}
// 计算连线的角度
const computedLineAngle = computed(() => {
if (state.measureDistancesPoints.length === 2) {
const dx = state.measureDistancesPoints[1].x - state.measureDistancesPoints[0].x
const dy = state.measureDistancesPoints[1].y - state.measureDistancesPoints[0].y
return Math.atan2(dy, dx) * (180 / Math.PI)
}
return 0
})
// 计算连线的长度
const computedLineWidth = computed(() => {
if (state.measureDistancesPoints.length === 2) {
return calculateDistance(state.measureDistancesPoints[0], state.measureDistancesPoints[1])
}
return 0
})
// 处理点击事件
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) {
// 计算两点之间的距离
let distancesNum = calculateDistance(
state.measureDistancesPoints[0],
state.measureDistancesPoints[1]
)
state.measureDistancesNum = distancesNum * 0.05
}
}
}
//获取扫描图 地图背景相关的信息
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 = 50
item.locationWide = 50
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.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.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
}
//要将实际的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 saveMap = async () => {
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 === 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 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 = (x, y) => {
let browserX = Math.max(Number(x) - imgBgObj.origin[0], 0)
let browserY = Math.max(
imgBgObj.origin[1] + Number(imgBgObj.height) * imgBgObj.resolution - Number(y),
0
)
console.log(browserX, browserY)
}
//将节点实际宽高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}`
}
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 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>
.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;
width: 100%;
height: 100%;
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>