zn-admin-vue3-wcs/src/views/mapPage/example/第一版编辑地图.vue
2025-02-15 17:15:48 +08:00

1414 lines
41 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="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="80"
trigger="click"
v-if="item.switchType === 'move' || item.switchType === 'revolve'"
:disabled="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'">
<el-form-item label="X">
<el-input v-model="state.moveForm.locationX" placeholder="请输入" />
</el-form-item>
<el-form-item label="Y">
<el-input v-model="state.moveForm.y" placeholder="请输入" />
</el-form-item>
<div style="text-align: right">
<el-button size="small" color="#00329F" @click="moveFormSubmit">确认</el-button>
</div>
</el-form>
<!-- 旋转 -->
<el-form :model="state.rotationForm" v-if="item.switchType === 'revolve'">
<el-form-item label="角度">
<el-input v-model="state.rotationForm.angle" placeholder="请输入" />
</el-form-item>
<div style="text-align: right">
<el-button size="small" color="#00329F" @click="rotationFormSubmit">确认</el-button>
</div>
</el-form>
<!-- 字体 -->
<el-form :model="state.rotationForm" v-if="item.switchType === 'text'">
<el-form-item label="角度">
<el-input v-model="state.rotationForm.angle" placeholder="请输入" />
</el-form-item>
<div style="text-align: right">
<el-button size="small" color="#00329F" @click="rotationFormSubmit">确认</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 === 'saveAs' ||
item.switchType === 'delete' ||
item.switchType === 'grid' ||
(item.switchType === 'next' &&
(toolbarSwitchType === 'lineLibrary' || toolbarSwitchType === 'region'))
"
></div>
<el-button
v-if="
item.switchType === 'next' &&
(toolbarSwitchType === 'lineLibrary' || toolbarSwitchType === 'region')
"
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-box" :style="{ cursor: state.cursorStyle }">
<div
class="map-box-inner"
@mousedown.stop="startDrawSelection"
@mousemove.stop="updateDrawSelection"
@mouseup.stop="endDrawSelection"
>
<img
:src="imgBgObj.imgUrl"
:style="{
width: Number(imgBgObj.width) * state.imageChangeMultiple + 'px',
height: Number(imgBgObj.height) * state.imageChangeMultiple + 'px'
}"
id="mapBg"
/>
<div
ref="mapBackgroundRef"
class="map-box-inner-dot"
@click="mapClick"
:class="state.isShowGrid ? 'grid-show' : ''"
:style="{
width: Number(imgBgObj.width) * state.imageChangeMultiple + 'px',
height: Number(imgBgObj.height) * state.imageChangeMultiple + 'px'
}"
v-if="interfaceRefreshed"
>
<VueDragResizeRotate
v-for="(item, index) in allHistoryList[currentIndex]"
:key="item.locationX"
:parent="true"
:x="Number(item.locationX) * state.imageChangeMultiple"
:y="Number(item.locationY) * state.imageChangeMultiple"
:w="Number(item.locationWide) * state.imageChangeMultiple"
:h="Number(item.locationDeep) * state.imageChangeMultiple"
: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="item.draggable"
:resizable="item.resizable"
:rotatable="item.rotatable"
:lock-aspect-ratio="item.lockAspectRatio"
style="border: none"
>
<!-- 1 路径点 -->
<div
v-if="item.type === 1 && item.layerSelectionShow"
:style="{
width: Number(item.locationWide) * state.imageChangeMultiple + 'px',
height: Number(item.locationDeep) * state.imageChangeMultiple + 'px',
border: ' 1px dashed #000',
borderRadius: '50%',
backgroundColor: '#000'
}"
>
</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: Number(item.locationWide) * state.imageChangeMultiple + 'px',
height: Number(item.locationDeep) * state.imageChangeMultiple + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<!-- 3 设备点 -->
<img
v-if="item.type === 3 && item.layerSelectionShow"
src="https://api.znkjfw.com/admin-api/infra/file/4/get/设备点_png_179_1739327151877.png"
alt=""
:style="{
width: Number(item.locationWide) * state.imageChangeMultiple + 'px',
height: Number(item.locationDeep) * state.imageChangeMultiple + 'px',
border: 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: Number(item.locationWide) * state.imageChangeMultiple + 'px',
height: Number(item.locationDeep) * state.imageChangeMultiple + 'px',
border: 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: Number(item.locationWide) * state.imageChangeMultiple + 'px',
height: Number(item.locationDeep) * state.imageChangeMultiple + 'px',
border: 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: Number(item.locationWide) * state.imageChangeMultiple + 'px',
height: Number(item.locationDeep) * state.imageChangeMultiple + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
/>
<div
v-if="item.type === 7 && item.layerSelectionShow"
:style="{
color: item.fontColor,
fontSize: item.fontSize + 'px',
fontFamily: item.fontFamily,
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
>
{{ item.text }}
</div>
</VueDragResizeRotate>
</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>
</div>
<!-- 节点编辑 -->
<editNodeProperties
ref="editNodePropertiesRef"
:positionMapId="imgBgObj.positionMapId"
@submitNodeSuccess="submitNodeSuccess"
/>
<!-- 文字输入弹窗 -->
<textFormToolDialog
ref="textFormToolDialogRef"
v-if="state.textFormToolShow"
:inputBoxStyle="inputBoxStyle"
@textFormSuccess="textFormSuccess"
/>
<!-- 图层选择 -->
<layerSelectionToolDialog
v-if="state.isShowLayer"
ref="layerSelectionToolDialogRef"
@layerSelectionSuccess="layerSelectionSuccess"
/>
<!-- 设备弹窗选择 -->
<equipmentToolDialog ref="equipmentToolDialogRef" :positionMapId="imgBgObj.positionMapId" />
<!-- 区域选择 -->
<itemAreaSettingDialog ref="itemAreaSettingDialogRef" :positionMapId="imgBgObj.positionMapId" />
<!-- 线库设置 -->
<lineLibrarySettingDialog
ref="lineLibrarySettingDialogRef"
:positionMapId="imgBgObj.positionMapId"
/>
</template>
<script setup>
import JSONBigInt from 'json-bigint'
import { ref, defineComponent, reactive, nextTick, onMounted } from 'vue'
import editNodeProperties from '../realTimeMap/components-tool/editNodeProperties.vue'
import textFormToolDialog from '../realTimeMap/components-tool/textFormToolDialog.vue'
import equipmentToolDialog from '../realTimeMap/components-tool/equipmentToolDialog.vue'
import itemAreaSettingDialog from '../realTimeMap/components-tool/itemAreaSettingDialog.vue'
import lineLibrarySettingDialog from '../realTimeMap/components-tool/lineLibrarySettingDialog.vue'
import layerSelectionToolDialog from '../realTimeMap/components-tool/layerSelectionToolDialog.vue'
import * as MapApi from '@/api/map/map'
import cursorCollection from '../realTimeMap/cursorCollection'
defineOptions({ name: 'editMapPageRealTimeMap' })
const lineLibrarySettingDialogRef = ref() //线库设置
const itemAreaSettingDialogRef = ref() //物料区域设置
const equipmentToolDialogRef = ref() //设备弹窗
const textFormToolDialogRef = ref() //文字输入弹窗
const mapBackgroundRef = ref()
const inputBoxRef = ref() //文字输入框
const message = useMessage() // 消息弹窗
const formLoading = ref(false)
const allHistoryList = ref([]) //全部的历史记录
const currentIndex = ref(0) //用于记录是哪条历史记录的
const currentItemIndex = ref(-1) //用于记录是在编辑那个具体的节点、图标等
const allMapPointInfo = ref([]) //所有的图标的列表
// 缩放停止
const interfaceRefreshed = ref(true)
const resizeEnd = (x, y, w, h, item, index) => {
interfaceRefreshed.value = false
console.log('缩放', w, h)
nextTick(() => {
allMapPointInfo.value[index].locationX = x
allMapPointInfo.value[index].locationY = y
allMapPointInfo.value[index].locationWide = w
allMapPointInfo.value[index].locationDeep = h
addEditHistory()
})
}
// 拖拽停止
const dragEnd = (x, y, item, index) => {
console.log('拖拽')
if (x === item.locationX && y === item.locationY) return
allMapPointInfo.value[index].locationX = x
allMapPointInfo.value[index].locationY = y
addEditHistory()
}
// 旋转
const rotateEnd = (angle, item, index) => {
console.log('旋转')
allMapPointInfo.value[index].angle = angle
addEditHistory()
}
//选中
const editNodePropertiesRef = ref()
const activatedHandle = (item, index) => {
currentItemIndex.value = index
//节点编辑
if (toolbarSwitchType.value === 'editNode' && item.type !== 7) {
editNodePropertiesRef.value.open(JSON.parse(JSON.stringify(item)))
}
}
//非选中
const deactivatedHandle = () => {
console.log('取消选中')
}
//添加历史记录
const addEditHistory = () => {
//要判断是不是在最新的记录上修改 如果不是 要把currentIndex之后的记录都删掉 保持当前修改是最新的
if (currentIndex.value !== allHistoryList.value.length - 1) {
allHistoryList.value = allHistoryList.value.splice(0, currentIndex.value)
currentIndex.value = allHistoryList.value.length
allHistoryList.value.push(JSON.parse(JSON.stringify(allMapPointInfo.value)))
} else {
allHistoryList.value.push(JSON.parse(JSON.stringify(allMapPointInfo.value)))
currentIndex.value++
}
interfaceRefreshed.value = true
console.log(allHistoryList.value, 'allHistoryList')
}
//上一步
const backPreviousStep = () => {
if (currentIndex.value > 0) {
currentIndex.value--
allMapPointInfo.value = allHistoryList.value[currentIndex.value]
console.log('撤销', allHistoryList.value[currentIndex.value])
} else {
message.warning('没了老铁')
}
}
//下一步
const backNextStep = () => {
if (currentIndex.value < allHistoryList.value.length - 1) {
currentIndex.value++
allMapPointInfo.value = allHistoryList.value[currentIndex.value]
console.log('重做', allHistoryList.value[currentIndex.value])
} else {
message.warning('没了老铁')
}
}
//地图点击
const mapClick = (e) => {
//节点
if (toolbarSwitchType.value === 'drawNodes') {
//绘制节点
allMapPointInfo.value.push({
positionMapId: imgBgObj.positionMapId, //地图的id
layerSelectionShow: true,
locationX: e.offsetX,
locationY: e.offsetY,
locationDeep: 16,
locationWide: 16,
angle: 0,
draggable: false,
resizable: false,
rotatable: false,
lockAspectRatio: false, //横纵比
img: '',
type: 1, //默认类型1 路径节点
dataList: [], //存库位的
dataObj: {} //存 设备点 停车点 文字
})
currentIndex.value++
allHistoryList.value.push(JSON.parse(JSON.stringify(allMapPointInfo.value)))
}
//文字输入
if (toolbarSwitchType.value === 'text') {
state.showInputBox = true
state.inputBoxStyle.locationX = e.offsetX
state.inputBoxStyle.locationY = e.offsetY
// 聚焦输入框
setTimeout(() => {
inputBoxRef.value.focus()
}, 0)
}
//测距
if (toolbarSwitchType.value === 'ranging') {
measureDistancesClick(e)
}
}
//输入文字样式改变
const textFormSuccess = (form) => {
state.inputBoxStyle = {
fontSize: `${form.fontSize}`,
fontFamily: `${form.fontFamily}`,
fontColor: `${form.fontColor}`
}
}
//输入结束
const handleInputEnd = () => {
if (state.inputBoxValue) {
state.showInputBox = false
allMapPointInfo.value.push({
type: 7, //类型 7文字
positionMapId: imgBgObj.positionMapId, //地图的id
locationX: state.inputBoxStyle.locationX, //x
locationY: state.inputBoxStyle.locationY, //y
locationDeep: '', //h
locationWide: '', //w
angle: 0, //旋转角度
draggable: true, //是否可以拖动
resizable: false, //是否可以调整大小
rotatable: false, //是否可以旋转
lockAspectRatio: true, //是否锁定比例
img: '', //图片
text: state.inputBoxValue, //文字内容
fontColor: state.inputBoxStyle.fontColor, //文字颜色
fontFamily: state.inputBoxStyle.fontFamily, //字体类型
fontSize: state.inputBoxStyle.fontSize,
dataObj: {}, //存 设备点 停车点 文字
layerSelectionShow: true
})
addEditHistory()
state.inputBoxValue = ''
}
}
//编辑节点成功
const submitNodeSuccess = (item) => {
allMapPointInfo.value[currentItemIndex.value] = item
allHistoryList.value[currentIndex.value][currentItemIndex.value] = item
}
//工具栏点击
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: 'drawRoute',
name: '绘制路线',
icon: 'ep:semi-select',
isActive: false
},
{
switchType: 'editRoute',
name: '编辑路线',
icon: 'ep:semi-select',
isActive: false
}
],
isShowToolbar: true, //工具栏展示隐藏
isShowGrid: true, //网格展示隐藏
moveForm: {
locationX: '',
locationY: ''
}, //移动的表单
rotationForm: {
angle: ''
}, //旋转的表单
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
}, //输入框的样式
inputBoxValue: '', //输入的值
isShowLayer: false, //图层显示
measureDistancesPoints: [], // 存储点击的点位
measureDistancesNum: 0, // 存储两点之间的距离
imageChangeMultiple: 1 //图片放大缩小的倍数
})
const toolbarClick = (item) => {
let type = item.switchType
if (
currentItemIndex.value === -1 &&
(type === 'move' || type === 'revolve' || type === 'copy' || type === 'delete')
) {
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 !== 'lineLibrary' && toolbarSwitchType.value !== 'region') {
state.drawSelectionAreaShow = false
state.allDrawSelectionAreaBox = []
state.drawSelectionPointList = []
}
//非测距
if (toolbarSwitchType.value !== 'ranging') {
state.measureDistancesPoints = [] // 清空存储点击的点位
state.measureDistancesNum = 0 // 清空存储两点之间的距离
}
switch (type) {
case 'open':
// 打开
break
case 'save':
//保存
saveMap()
break
case 'saveAs':
//另存为 存为json文件 无法直接访问用户的文件系统 不能选择存在那个文件夹里面
const jsonString = JSON.stringify(allHistoryList.value[currentIndex.value], 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':
//移动
break
case 'revolve':
break
case 'copy':
//复制
let copyMapItem = allHistoryList.value[currentIndex.value][currentItemIndex.value]
state.copyMapItem = {
positionMapId: copyMapItem.positionMapId,
locationX: copyMapItem.locationX,
locationY: copyMapItem.locationY,
type: copyMapItem.type,
dataJson: copyMapItem.dataJson || '',
dataObj: copyMapItem.dataObj || {},
dataList: copyMapItem.dataList || [],
locationDeep: copyMapItem.locationDeep,
locationWide: copyMapItem.locationWide,
draggable: copyMapItem.draggable,
resizable: copyMapItem.resizable,
rotatable: copyMapItem.rotatable,
lockAspectRatio: copyMapItem.lockAspectRatio
}
message.success('复制成功')
break
case 'paste':
//粘贴
let copyObj = JSON.parse(JSON.stringify(state.copyMapItem))
copyObj.locationX = Number(copyObj.locationX) + 50
copyObj.locationY = Number(copyObj.locationY) + 50
allMapPointInfo.value.push(copyObj)
addEditHistory()
break
case 'delete':
//删除
allMapPointInfo.value.splice(currentItemIndex.value, 1)
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 '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 (imgBgObj.width < 10000) {
state.imageChangeMultiple *= 1.2
} else {
message.warning('不能在放大了')
}
break
case 'smaller':
//缩小
if (imgBgObj.width > 500) {
state.imageChangeMultiple *= 0.8
} 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':
// 设备
equipmentToolDialogRef.value.open()
break
}
}
//移动工具表单提交
const moveFormSubmit = () => {
allHistoryList.value[currentIndex.value][currentItemIndex.value].locationX =
state.moveForm.locationX
allHistoryList.value[currentIndex.value][currentItemIndex.value].locationY =
state.moveForm.locationY
}
//旋转工具表单提交
const rotationFormSubmit = () => {
allHistoryList.value[currentIndex.value][currentItemIndex.value].angle = state.rotationForm.angle
}
//开始框选绘制
const startDrawSelection = (event) => {
if (toolbarSwitchType.value !== 'lineLibrary' && toolbarSwitchType.value !== 'region') return
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
const x = Number(event.clientX) - backgroundRect.left
const y = event.clientY - backgroundRect.top
// 确保点击在背景区域内
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 !== 'lineLibrary' && toolbarSwitchType.value !== 'region') return
if (state.drawSelectionAreaShow) {
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
const x = event.clientX - backgroundRect.left
const y = event.clientY - backgroundRect.top
state.drawSelectionAreaBox.width = Number(x) - Number(state.drawSelectionStartPoint.x)
state.drawSelectionAreaBox.height = Number(y) - Number(state.drawSelectionStartPoint.y)
}
// 阻止默认行为(避免选中图片或文本)
event.preventDefault()
}
//结束框选绘制
const endDrawSelection = () => {
if (toolbarSwitchType.value !== 'lineLibrary' && toolbarSwitchType.value !== 'region') return
state.drawSelectionAreaShow = false
state.allDrawSelectionAreaBox.push({ ...state.drawSelectionAreaBox })
state.drawSelectionAreaBox = { x: 0, y: 0, width: 0, height: 0 }
}
//点击区域
const clickDrawSelectionArea = () => {
let points = allHistoryList.value[currentIndex.value]
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)
}
})
})
console.log(state.drawSelectionPointList, '选中的')
// 清空框选区域
state.allDrawSelectionAreaBox = []
//只要库位的
let binLocation = state.drawSelectionPointList.filter((item) => item.type === 2)
if (toolbarSwitchType.value === 'lineLibrary') {
//线库
if (binLocation.length < 2) {
message.warning('至少选择两个库位')
return
}
if (!isStraightLine(binLocation)) {
message.warning('您选择的库位不在一条直线上')
return
}
lineLibrarySettingDialogRef.value.open(binLocation)
}
if (toolbarSwitchType.value === 'region') {
//区域
if (binLocation.length < 1) {
message.warning('至少选择两个库位')
} else {
itemAreaSettingDialogRef.value.open(binLocation)
}
}
}
//计算是不是在同一条直线的
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 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 = event.clientX
const y = event.clientY
// 检查点击是否发生在背景区域内
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
if (
x >= backgroundRect.left &&
x <= backgroundRect.right &&
y >= backgroundRect.top &&
y <= backgroundRect.bottom
) {
if (state.measureDistancesPoints.length === 2) {
// 如果已经有两个点,清空信息
state.measureDistancesPoints = []
state.measureDistancesNum = null
} else {
// 记录点击的点位(相对于背景区域的坐标)
const offsetX = x - backgroundRect.left
const offsetY = y - backgroundRect.top
state.measureDistancesPoints.push({ x: offsetX, y: offsetY })
if (state.measureDistancesPoints.length === 2) {
// 计算两点之间的距离
state.measureDistancesNum = calculateDistance(
state.measureDistancesPoints[0],
state.measureDistancesPoints[1]
)
}
}
}
}
//获取扫描图 地图背景相关的信息
const imgBgObj = reactive({
imgUrl: '',
positionMapId: '',
width: '',
height: '',
floor: '',
area: ''
})
//接收参数
const { query } = useRoute() // 查询参数
//获取地图点位
const getMapList = async () => {
if (query.mapId) {
imgBgObj.positionMapId = query.mapId
imgBgObj.floor = query.floor
imgBgObj.area = query.area
//调转换成png的接口
getMapData(imgBgObj)
}
}
//调转换成png的接口
const getMapData = async (mapInfo) => {
let data = await MapApi.getPositionMapdDwnloadPngBase64({
floor: mapInfo.floor,
area: mapInfo.area
})
imgBgObj.imgUrl = data
//获取一下图片的宽高
const img = new Image()
img.src = imgBgObj.imgUrl
//加载图片成功
img.onload = () => {
imgBgObj.width = img.naturalWidth
imgBgObj.height = img.naturalHeight
getAllNodeList()
}
//加载图片失败
img.onerror = () => {
console.error('图片加载失败')
}
}
//获取所有的点位 处理七种类型的
const getAllNodeList = async () => {
let list = await MapApi.getPositionMapItemList({
positionMapId: imgBgObj.positionMapId
})
list.forEach((item) => {
item.layerSelectionShow = true //用于图层
item.locationX = Number(item.locationX)
item.locationY = Number(item.locationY)
if (item.type === 1 || item.type === 5 || item.type === 6) {
item.dataObj = {}
item.dataList = []
item.locationDeep = 16
item.locationWide = 16
item.draggable = false
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 = false
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 3 || 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 = false
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 7) {
item.dataObj = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.text = item.dataObj.text
item.fontColor = item.dataObj.fontColor
item.fontFamily = item.dataObj.fontFamily
item.fontSize = item.dataObj.fontSize
item.angle = item.dataObj.angle
item.draggable = true
item.resizable = false
item.rotatable = true
item.lockAspectRatio = true
}
allMapPointInfo.value.push(item)
})
allHistoryList.value[0] = JSON.parse(JSON.stringify(allMapPointInfo.value))
}
//保存地图按钮
const saveMap = async () => {
//节点的保存
await saveNodeList()
//路线的保存
}
//节点的保存
const saveNodeList = async () => {
formLoading.value = true
let list = allHistoryList.value[currentIndex.value]
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.rotatable = item.rotatable
item.dataObj.angle = item.angle
//dataJson数据
item.dataJson = JSON.stringify(item.dataObj)
}
})
try {
await MapApi.batchSaveOrEditOrDelMapItem(imgBgObj.positionMapId, list)
message.success('创建成功')
} finally {
formLoading.value = false
}
}
//筛选图层
const layerSelectionSuccess = (typeList) => {
allHistoryList.value[currentIndex.value].forEach((item) => {
// 如果 type 存在于第一个数组中,则将 layerSelectionShow 设置为 false
if (typeList.includes(item.type)) {
item.layerSelectionShow = false
} else {
item.layerSelectionShow = true
}
})
}
onMounted(() => {
getMapList()
})
</script>
<style lang="scss" scoped>
.map-box {
width: 100%;
position: relative;
.map-box-inner {
position: relative;
width: 100%;
.map-box-inner-dot {
position: absolute;
top: 0;
left: 0;
}
.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:
10px 10px,
10px 10px;
}
}
}
.top-tool {
.top-tool-list {
display: flex;
align-items: center;
text-align: center;
background-color: #fff;
margin-top: -15px;
margin-left: -20px;
margin-right: -20px;
padding: 0 12px;
margin-bottom: 20px;
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
height: 70px;
.top-tool-item {
display: flex;
align-items: center;
.tool-item {
cursor: pointer;
width: 49px;
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;
}
}
.line {
margin: 0 12px;
width: 1px;
height: 47px;
border: 1px solid #cccccc;
}
}
}
.right-tool-list {
position: absolute;
right: 0;
top: 94px;
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;
}
}
}
.tool-active {
background: #ebf1ff;
border-bottom: 2px solid #1677ff;
}
.right-tool-active {
background: #ebf1ff;
}
}
.input-box-class {
position: absolute;
border: 1px solid #00329f;
padding: 4px;
outline: none;
}
.selection-area-btn {
width: 80px;
margin-left: 4px;
}
</style>