1
This commit is contained in:
parent
0494d9477e
commit
a82253089c
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="edit-map-page">
|
||||
<div class="edit-map-page" @wheel="handleWheel">
|
||||
<div class="top-tool">
|
||||
<div class="top-tool-list">
|
||||
<div v-for="item in state.topToolList" :key="item.switchType" class="top-tool-item">
|
||||
@ -155,6 +155,62 @@
|
||||
>
|
||||
</div>
|
||||
</el-popover>
|
||||
<!-- 标记 -->
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
v-else-if="item.switchType === 'marker'"
|
||||
width="220"
|
||||
>
|
||||
<template #reference>
|
||||
<div
|
||||
class="tool-item"
|
||||
:class="
|
||||
toolbarSwitchType === item.switchType
|
||||
? 'tool-active'
|
||||
: item.isActive
|
||||
? 'right-tool-active'
|
||||
: ''
|
||||
"
|
||||
@click="toolbarClick(item)"
|
||||
>
|
||||
<Icon :icon="item.icon" :size="24" />
|
||||
<div class="name"> {{ item.name }} </div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 位置 -->
|
||||
<el-form :model="state.markForm" class="mt-2" label-width="68">
|
||||
<el-form-item label="标记车辆">
|
||||
<el-select
|
||||
v-model="state.markForm.macAddress"
|
||||
placeholder="请选择"
|
||||
@change="macAddressChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in state.mapMarkCarList"
|
||||
:key="item.id"
|
||||
:label="item.robotNo"
|
||||
:value="item.macAddress"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标记属性" v-if="state.markForm.markProperty">
|
||||
<el-input v-model="state.markForm.markProperty" style="width: 240px" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="原节点" v-if="state.markForm.originalNode">
|
||||
<el-input v-model="state.markForm.originalNode" style="width: 240px" disabled />
|
||||
</el-form-item>
|
||||
<div style="text-align: right">
|
||||
<el-button
|
||||
size="small"
|
||||
style="width: 64px; height: 30px; background: #00329f"
|
||||
color="#00329F"
|
||||
@click="markFormSubmit()"
|
||||
>确认</el-button
|
||||
>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-popover>
|
||||
<div
|
||||
v-else
|
||||
class="tool-item"
|
||||
@ -180,7 +236,8 @@
|
||||
(item.switchType === 'next' &&
|
||||
(toolbarSwitchType === 'createLineLibrary' ||
|
||||
toolbarSwitchType === 'createRegion' ||
|
||||
toolbarSwitchType === 'drawRoute'))
|
||||
toolbarSwitchType === 'drawRoute' ||
|
||||
toolbarSwitchType === 'generateLine'))
|
||||
"
|
||||
></div>
|
||||
<el-button
|
||||
@ -188,7 +245,8 @@
|
||||
item.switchType === 'next' &&
|
||||
(toolbarSwitchType === 'createLineLibrary' ||
|
||||
toolbarSwitchType === 'createRegion' ||
|
||||
toolbarSwitchType === 'drawRoute')
|
||||
toolbarSwitchType === 'drawRoute' ||
|
||||
toolbarSwitchType === 'generateLine')
|
||||
"
|
||||
type="danger"
|
||||
class="selection-area-btn"
|
||||
@ -269,7 +327,10 @@
|
||||
style="border: none; z-index: 999"
|
||||
>
|
||||
<!-- 节点合集 -->
|
||||
<div @mousedown="startFromPoint(index, $event)">
|
||||
<div
|
||||
@mousedown="startFromPoint(index, $event)"
|
||||
:style="{ width: item.locationWidePx + 'px', height: item.locationDeepPx + 'px' }"
|
||||
>
|
||||
<!-- 1 路径点 -->
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
@ -361,17 +422,33 @@
|
||||
:x2="Number(state.currentDrawX)"
|
||||
:y2="Number(state.currentDrawY)"
|
||||
stroke="#00329F"
|
||||
stroke-width="4"
|
||||
stroke-width="3"
|
||||
/>
|
||||
<template v-if="state.mapRouteList.length > 0">
|
||||
<template v-for="(curve, index) in state.mapRouteList" :key="index">
|
||||
<!-- 定义箭头 -->
|
||||
<defs>
|
||||
<marker id="forward-arrow" viewBox="0 0 9 9" refX="10" refY="5" orient="auto">
|
||||
<marker
|
||||
id="forward-arrow"
|
||||
viewBox="0 0 10 10"
|
||||
refX="10"
|
||||
refY="5"
|
||||
orient="auto"
|
||||
markerWidth="2"
|
||||
markerHeight="2"
|
||||
>
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="black" />
|
||||
</marker>
|
||||
<!-- 反向箭头 -->
|
||||
<marker id="backward-arrow" viewBox="0 0 9 9" refX="0" refY="5" orient="auto">
|
||||
<marker
|
||||
id="backward-arrow"
|
||||
viewBox="0 0 10 10"
|
||||
refX="0"
|
||||
refY="5"
|
||||
orient="auto"
|
||||
markerWidth="2"
|
||||
markerHeight="2"
|
||||
>
|
||||
<path d="M 10 0 L 0 5 L 10 10 z" fill="black" />
|
||||
</marker>
|
||||
</defs>
|
||||
@ -824,10 +901,10 @@ const mapClick = (e) => {
|
||||
locationY: y,
|
||||
actualLocationX: actualLocationX,
|
||||
actualLocationY: actualLocationY,
|
||||
locationDeep: 50,
|
||||
locationWide: 50,
|
||||
locationDeepPx: 10,
|
||||
locationWidePx: 10,
|
||||
locationDeep: 40,
|
||||
locationWide: 40,
|
||||
locationDeepPx: 8,
|
||||
locationWidePx: 8,
|
||||
angle: 0,
|
||||
draggable: true,
|
||||
resizable: true,
|
||||
@ -1078,17 +1155,23 @@ const state = reactive({
|
||||
icon: 'ep:semi-select',
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
switchType: 'drawRoute',
|
||||
name: '框选绘制',
|
||||
icon: 'ep:semi-select',
|
||||
isActive: false
|
||||
},
|
||||
// {
|
||||
// switchType: 'drawRoute',
|
||||
// name: '框选绘制',
|
||||
// icon: 'ep:semi-select',
|
||||
// isActive: false
|
||||
// },
|
||||
{
|
||||
switchType: 'editRoute',
|
||||
name: '编辑路线',
|
||||
icon: 'ep:semi-select',
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
switchType: 'generateLine',
|
||||
name: '生成直线',
|
||||
icon: 'ep:finished',
|
||||
isActive: false
|
||||
}
|
||||
],
|
||||
isShowToolbar: true, //工具栏展示隐藏
|
||||
@ -1138,7 +1221,14 @@ const state = reactive({
|
||||
allMapPointInfo: [], //当前页面上的所有节点的列表
|
||||
mapRouteList: [], //当前页面上所有路线的列表
|
||||
currentIndex: 0, //当前处在哪条历史记录
|
||||
currentItemIndex: -1 //当前处在哪个工具
|
||||
currentItemIndex: -1, //当前处在哪个工具
|
||||
markForm: {
|
||||
macAddress: '', // mac地址
|
||||
markProperty: '', //标记属性
|
||||
originalNode: '', //原节点
|
||||
robotNo: '' //AGV编号
|
||||
}, //标记的表单
|
||||
mapMarkCarList: [] //标记的车辆列表
|
||||
})
|
||||
|
||||
const toolbarClick = async (item) => {
|
||||
@ -1329,6 +1419,7 @@ const toolbarClick = async (item) => {
|
||||
break
|
||||
case 'marker':
|
||||
// 标记
|
||||
mapMark()
|
||||
break
|
||||
case 'grid':
|
||||
//网格
|
||||
@ -1457,6 +1548,108 @@ const rotationFormSubmit = () => {
|
||||
addEditHistory()
|
||||
}
|
||||
|
||||
//标记
|
||||
const mapMark = async () => {
|
||||
state.mapMarkCarList = await MapApi.getListByMapId(imgBgObj.positionMapId)
|
||||
|
||||
if (state.currentItemIndex != -1) {
|
||||
let item = state.allMapPointInfo[state.currentItemIndex]
|
||||
state.markForm.originalNode = `[${item.locationX},${item.locationY}]`
|
||||
if (item.type == 1) {
|
||||
state.markForm.markProperty = '路径点'
|
||||
} else if (item.type == 2) {
|
||||
state.markForm.markProperty = '库位点'
|
||||
} else if (item.type == 3) {
|
||||
state.markForm.markProperty = '设备点'
|
||||
} else if (item.type == 4) {
|
||||
state.markForm.markProperty = '停车点'
|
||||
} else if (item.type == 5) {
|
||||
state.markForm.markProperty = '区域变更点'
|
||||
} else if (item.type == 6) {
|
||||
state.markForm.markProperty = '等待点'
|
||||
}
|
||||
} else {
|
||||
state.markForm.markProperty = ''
|
||||
state.markForm.originalNode = ''
|
||||
}
|
||||
}
|
||||
//标记提交
|
||||
const macAddressChange = (e) => {
|
||||
const targetItem = state.mapMarkCarList.find((item) => item.macAddress === e)
|
||||
if (targetItem) {
|
||||
state.markForm.robotNo = targetItem.robotNo
|
||||
}
|
||||
}
|
||||
const markFormSubmit = async () => {
|
||||
if (!state.markForm.macAddress) {
|
||||
message.warning('请选择车辆')
|
||||
return
|
||||
}
|
||||
let res = await MapApi.getAGVPointInformation(state.markForm.macAddress)
|
||||
if (res) {
|
||||
let content = JSON.parse(res.content)
|
||||
let point = JSON.parse(content[state.markForm.robotNo]) //标记传过来的数据
|
||||
let pointPx = convertActualToBrowser(point.x, point.y)
|
||||
let actualPoint = disposeEventPoint(pointPx.x, pointPx.y)
|
||||
|
||||
if (state.currentItemIndex !== -1) {
|
||||
state.allMapPointInfo[state.currentItemIndex].locationX = pointPx.x
|
||||
state.allMapPointInfo[state.currentItemIndex].locationY = pointPx.y
|
||||
state.allMapPointInfo[state.currentItemIndex].actualLocationX = actualPoint.actualLocationX
|
||||
state.allMapPointInfo[state.currentItemIndex].actualLocationY = actualPoint.actualLocationY
|
||||
|
||||
//更改路线里的
|
||||
let item = state.allMapPointInfo[state.currentItemIndex]
|
||||
state.mapRouteList.forEach((route) => {
|
||||
if (item.id === route.startingPointId) {
|
||||
route.startPointX = pointPx.x
|
||||
route.startPointY = pointPx.y
|
||||
route.beginHigh = Number(item.locationDeepPx)
|
||||
route.beginWidth = Number(item.locationWidePx)
|
||||
route.actualStartPointX = actualPoint.actualLocationX
|
||||
route.actualStartPointY = actualPoint.actualLocationY
|
||||
}
|
||||
if (item.id === route.endPointId) {
|
||||
route.endPointX = pointPx.x
|
||||
route.endPointY = pointPx.y
|
||||
route.endHigh = Number(item.locationDeepPx)
|
||||
route.endWidth = Number(item.locationWidePx)
|
||||
route.actualEndPointX = actualPoint.actualLocationX
|
||||
route.actualEndPointY = actualPoint.actualLocationY
|
||||
}
|
||||
})
|
||||
|
||||
addEditHistory()
|
||||
} else {
|
||||
message.warning('未采集到该AGV点位信息')
|
||||
}
|
||||
} else {
|
||||
//新增一个节点
|
||||
state.allMapPointInfo.push({
|
||||
positionMapId: imgBgObj.positionMapId, //地图的id
|
||||
layerSelectionShow: true,
|
||||
locationX: pointPx.x,
|
||||
locationY: pointPx.y,
|
||||
actualLocationX: actualPoint.actualLocationX,
|
||||
actualLocationY: actualPoint.actualLocationY,
|
||||
locationDeep: 40,
|
||||
locationWide: 40,
|
||||
locationDeepPx: 8,
|
||||
locationWidePx: 8,
|
||||
angle: 0,
|
||||
draggable: true,
|
||||
resizable: true,
|
||||
rotatable: false,
|
||||
lockAspectRatio: false, //横纵比
|
||||
mapImageUrl: '',
|
||||
type: 1, //默认类型1 路径节点
|
||||
dataList: [], //存库位的
|
||||
dataObj: {} //存 设备点 停车点 文字
|
||||
})
|
||||
addEditHistory()
|
||||
}
|
||||
}
|
||||
|
||||
//鼠标拖动绘制节点
|
||||
// 从点开始绘制
|
||||
const startFromPoint = (index, event) => {
|
||||
@ -1482,32 +1675,33 @@ const startFromPoint = (index, event) => {
|
||||
//开始框选绘制
|
||||
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() // 阻止默认行为(避免选中图片或文本)
|
||||
toolbarSwitchType.value !== 'createLineLibrary' &&
|
||||
toolbarSwitchType.value !== 'createRegion' &&
|
||||
toolbarSwitchType.value !== 'drawRoute' &&
|
||||
toolbarSwitchType.value !== 'generateLine'
|
||||
)
|
||||
return
|
||||
|
||||
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
|
||||
|
||||
const x = disposeEventPoints(event).x
|
||||
const y = disposeEventPoints(event).y
|
||||
|
||||
// 确保点击在背景区域内
|
||||
if (x >= 0 && x <= backgroundRect.width && y >= 0 && y <= backgroundRect.height) {
|
||||
state.drawSelectionAreaShow = true
|
||||
state.drawSelectionStartPoint = { x: x, y: y }
|
||||
state.drawSelectionAreaBox = { x: x, y: y, width: 0, height: 0 }
|
||||
}
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
}
|
||||
// 更新框选区域
|
||||
const updateDrawSelection = (event) => {
|
||||
if (
|
||||
toolbarSwitchType.value === 'createLineLibrary' ||
|
||||
toolbarSwitchType.value === 'createRegion' ||
|
||||
toolbarSwitchType.value === 'drawRoute'
|
||||
toolbarSwitchType.value === 'drawRoute' ||
|
||||
toolbarSwitchType.value === 'generateLine'
|
||||
) {
|
||||
if (state.drawSelectionAreaShow) {
|
||||
const x = disposeEventPoints(event).x
|
||||
@ -1532,16 +1726,17 @@ const updateDrawSelection = (event) => {
|
||||
state.currentDrawX = x
|
||||
state.currentDrawY = y
|
||||
}
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
//结束框选绘制
|
||||
const endDrawSelection = (event) => {
|
||||
if (
|
||||
toolbarSwitchType.value === 'createLineLibrary' ||
|
||||
toolbarSwitchType.value === 'createRegion' ||
|
||||
toolbarSwitchType.value === 'drawRoute'
|
||||
toolbarSwitchType.value === 'drawRoute' ||
|
||||
toolbarSwitchType.value === 'generateLine'
|
||||
) {
|
||||
state.drawSelectionAreaShow = false
|
||||
state.allDrawSelectionAreaBox.push({ ...state.drawSelectionAreaBox })
|
||||
@ -1636,7 +1831,6 @@ const endDrawSelection = (event) => {
|
||||
state.currentDrawX = 0
|
||||
state.currentDrawY = 0
|
||||
}
|
||||
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
@ -1686,6 +1880,7 @@ const clickDrawSelectionArea = () => {
|
||||
item.id
|
||||
})
|
||||
|
||||
//线库
|
||||
if (toolbarSwitchType.value === 'createLineLibrary') {
|
||||
//线库
|
||||
if (binLocation.length < 2) {
|
||||
@ -1722,7 +1917,7 @@ const clickDrawSelectionArea = () => {
|
||||
removeEventListener() //移除监听
|
||||
itemAreaSettingDialogRef.value.open(binLocation)
|
||||
}
|
||||
|
||||
//绘制直线
|
||||
if (toolbarSwitchType.value === 'drawRoute') {
|
||||
if (routeList.length !== 2) {
|
||||
message.warning('只能选择两个路径点')
|
||||
@ -1773,6 +1968,105 @@ const clickDrawSelectionArea = () => {
|
||||
state.mapRouteList.push(curve)
|
||||
addEditHistory()
|
||||
}
|
||||
//生成直线
|
||||
if (toolbarSwitchType.value === 'generateLine') {
|
||||
if (routeList.length < 3) {
|
||||
message.warning('至少框选三个点')
|
||||
return
|
||||
}
|
||||
let isHaveId = routeList.every((item) => {
|
||||
return item.id
|
||||
})
|
||||
if (!isHaveId) {
|
||||
message.warning('您选择的路径点存在未保存的')
|
||||
return
|
||||
}
|
||||
const list = mapPointsToLine(routeList)
|
||||
const idNameMap = {}
|
||||
list.forEach((item) => {
|
||||
idNameMap[item.id] = item
|
||||
})
|
||||
// 遍历第二个数组,更新 name
|
||||
state.allMapPointInfo.forEach((item) => {
|
||||
if (idNameMap[item.id]) {
|
||||
let actualPoint = disposeEventPoint(
|
||||
idNameMap[item.id].locationX,
|
||||
idNameMap[item.id].locationY
|
||||
)
|
||||
item.locationX = idNameMap[item.id].locationX
|
||||
item.locationY = idNameMap[item.id].locationY
|
||||
item.actualLocationX = actualPoint.actualLocationX
|
||||
item.actualLocationY = actualPoint.actualLocationY
|
||||
}
|
||||
})
|
||||
console.log(state.allMapPointInfo)
|
||||
state.mapRouteList.forEach((item) => {
|
||||
if (idNameMap[item.startingPointId]) {
|
||||
let actualPoint = disposeEventPoint(
|
||||
idNameMap[item.startingPointId].locationX,
|
||||
idNameMap[item.startingPointId].locationY
|
||||
)
|
||||
item.startPointX = idNameMap[item.startingPointId].locationX
|
||||
item.startPointY = idNameMap[item.startingPointId].locationY
|
||||
item.actualStartPointX = actualPoint.actualLocationX
|
||||
item.actualStartPointY = actualPoint.actualLocationY
|
||||
}
|
||||
if (idNameMap[item.endPointId]) {
|
||||
let actualPoint = disposeEventPoint(
|
||||
idNameMap[item.endPointId].locationX,
|
||||
idNameMap[item.endPointId].locationY
|
||||
)
|
||||
item.endPointX = idNameMap[item.endPointId].locationX
|
||||
item.endPointY = idNameMap[item.endPointId].locationY
|
||||
item.actualEndPointX = actualPoint.actualLocationX
|
||||
item.actualEndPointY = actualPoint.actualLocationY
|
||||
}
|
||||
})
|
||||
addEditHistory()
|
||||
}
|
||||
}
|
||||
//将一个数组中的点位 按照第一个和最后一个排成一条直线
|
||||
const mapPointsToLine = (points) => {
|
||||
if (points.length < 2) {
|
||||
return points
|
||||
}
|
||||
// 取数组的第一项和最后一项
|
||||
const firstPoint = points[0]
|
||||
const lastPoint = points[points.length - 1]
|
||||
|
||||
// 计算直线的斜率
|
||||
const dx = lastPoint.locationX - firstPoint.locationX
|
||||
const dy = lastPoint.locationY - firstPoint.locationY
|
||||
|
||||
if (dx === 0) {
|
||||
// 垂直直线的情况
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
return point
|
||||
}
|
||||
return {
|
||||
...point,
|
||||
locationX: firstPoint.locationX,
|
||||
locationY: point.locationY
|
||||
}
|
||||
})
|
||||
}
|
||||
const slope = dy / dx
|
||||
// 计算直线的截距
|
||||
const intercept = firstPoint.locationY - slope * firstPoint.locationX
|
||||
|
||||
// 映射其他点到直线上
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
return point
|
||||
}
|
||||
const newY = slope * point.locationX + intercept
|
||||
// 考虑精度问题,这里保留三位小数
|
||||
return {
|
||||
...point,
|
||||
locationY: newY
|
||||
}
|
||||
})
|
||||
}
|
||||
//计算是不是在同一条直线的
|
||||
const isStraightLine = (binLocation) => {
|
||||
@ -2002,7 +2296,6 @@ const getMapList = async () => {
|
||||
imgBgObj.positionMapId = res.id
|
||||
imgBgObj.floor = res.floor
|
||||
imgBgObj.area = res.area
|
||||
|
||||
imgBgObj.width = yamlJson.width
|
||||
imgBgObj.height = yamlJson.height
|
||||
imgBgObj.origin = yamlJson.origin
|
||||
@ -2037,8 +2330,8 @@ const getAllNodeList = async () => {
|
||||
if (item.type === 1) {
|
||||
item.dataObj = {}
|
||||
item.dataList = []
|
||||
item.locationDeep = 50
|
||||
item.locationWide = 50
|
||||
item.locationDeep = 40
|
||||
item.locationWide = 40
|
||||
item.draggable = true
|
||||
item.resizable = false
|
||||
item.rotatable = false
|
||||
@ -2301,6 +2594,33 @@ const handleKeyDown = (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
//鼠标滚轮
|
||||
const handleWheel = (event) => {
|
||||
// 判断 Ctrl 键是否被按下
|
||||
if (event.ctrlKey) {
|
||||
// 阻止默认的滚动行为
|
||||
event.preventDefault()
|
||||
|
||||
// 根据滚轮滚动方向调整缩放比例
|
||||
if (event.deltaY < 0) {
|
||||
// 向上滚动,放大
|
||||
//放大
|
||||
if (state.imageChangeMultiple < 4) {
|
||||
state.imageChangeMultiple += 0.2
|
||||
} else {
|
||||
message.warning('不能在放大了')
|
||||
}
|
||||
} else {
|
||||
//缩小
|
||||
if (state.imageChangeMultiple > 0.2) {
|
||||
state.imageChangeMultiple -= 0.2
|
||||
} else {
|
||||
message.warning('不能在缩小了')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addEventListener = () => {
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
@ -2450,8 +2770,6 @@ onUnmounted(() => {
|
||||
// 20px 20px,
|
||||
// 20px 20px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: repeating-linear-gradient(
|
||||
to right,
|
||||
rgba(0, 0, 0, 0.1),
|
||||
|
94
src/views/mapPage/example/排成直线.vue
Normal file
94
src/views/mapPage/example/排成直线.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div style="width: 600px; height: 300px; background: #dbeede; position: relative">
|
||||
<div v-for="(item, index) in points" :key="index">
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
backgroundColor: '#000',
|
||||
borderRadius: '50%',
|
||||
zIndex: 999,
|
||||
left: item.x + 'px',
|
||||
top: item.y + 'px'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 600px; height: 300px; background: #b3dcff; position: relative">
|
||||
<div v-for="(item, index) in mappedPoints" :key="index">
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
backgroundColor: '#000',
|
||||
borderRadius: '50%',
|
||||
zIndex: 999,
|
||||
left: item.x + 'px',
|
||||
top: item.y + 'px'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const points = ref([
|
||||
{
|
||||
x: 123.567,
|
||||
y: 178.123
|
||||
},
|
||||
{
|
||||
x: 255.567,
|
||||
y: 79.123
|
||||
},
|
||||
{
|
||||
x: 382.567,
|
||||
y: 178.123
|
||||
},
|
||||
{
|
||||
x: 481.567,
|
||||
y: 238.123
|
||||
}
|
||||
])
|
||||
|
||||
const mapPointsToLine = (points) => {
|
||||
if (points.length < 2) {
|
||||
return points
|
||||
}
|
||||
// 取数组的第一项和最后一项
|
||||
const firstPoint = points[0]
|
||||
const lastPoint = points[points.length - 1]
|
||||
|
||||
// 计算直线的斜率
|
||||
const dx = lastPoint.x - firstPoint.x
|
||||
const dy = lastPoint.y - firstPoint.y
|
||||
if (dx === 0) {
|
||||
// 垂直直线的情况
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
return point
|
||||
}
|
||||
return { x: firstPoint.x, y: point.y }
|
||||
})
|
||||
}
|
||||
const slope = dy / dx
|
||||
// 计算直线的截距
|
||||
const intercept = firstPoint.y - slope * firstPoint.x
|
||||
|
||||
// 映射其他点到直线上
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
return point
|
||||
}
|
||||
const newY = slope * point.x + intercept
|
||||
return { x: point.x, y: newY }
|
||||
})
|
||||
}
|
||||
|
||||
const mappedPoints = mapPointsToLine(points.value)
|
||||
console.log(mappedPoints)
|
||||
</script>
|
@ -23,6 +23,14 @@
|
||||
>
|
||||
<el-input type="number" v-model="form.locationY" placeholder="请输入" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="弧度"
|
||||
prop="locationYaw"
|
||||
required
|
||||
:rules="{ required: true, message: '请输入弧度', trigger: 'change' }"
|
||||
>
|
||||
<el-input type="number" v-model="form.locationYaw" placeholder="请输入" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type" required>
|
||||
<el-select v-model="form.type" placeholder="请选择类型" @change="typeChange">
|
||||
<el-option label="路径点位" :value="1" />
|
||||
@ -33,6 +41,7 @@
|
||||
<el-option label="等待点" :value="6" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="form.type === 2 || form.type === 3 || form.type === 4">
|
||||
<el-form-item
|
||||
label="层数"
|
||||
@ -172,12 +181,14 @@ const form = ref({
|
||||
dataList: [], //存库位的
|
||||
dataObj: {}, //存 设备点 停车点 文字
|
||||
positionMapId: undefined,
|
||||
deviceId: undefined
|
||||
deviceId: undefined, //设备id
|
||||
locationYaw: undefined //弧度
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
locationX: [{ required: true, message: '请输入X', trigger: 'blur' }],
|
||||
locationY: [{ required: true, message: '请输入Y', trigger: 'blur' }],
|
||||
locationYaw: [{ required: true, message: '请输入弧度', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择类型', trigger: 'blur' }],
|
||||
layersNumber: [{ required: true, message: '请输入层数', trigger: 'blur' }],
|
||||
locationNumber: [{ required: true, message: '请输入排序', trigger: 'blur' }]
|
||||
@ -256,6 +267,7 @@ const dialogClose = () => {
|
||||
|
||||
const equipmentList = ref([]) //用过的设备列表
|
||||
const open = (item, list) => {
|
||||
console.log(item)
|
||||
form.value = item
|
||||
form.value.layersNumber = item.dataList?.length || ''
|
||||
form.value.deviceId = item?.deviceId || item?.dataObj?.id || ''
|
||||
|
@ -130,13 +130,33 @@
|
||||
}"
|
||||
>
|
||||
<!-- 库位 2-->
|
||||
<div v-if="item.type == 2" style="width: 100%; height: 100%">
|
||||
<div
|
||||
v-if="item.type == 2"
|
||||
:style="{
|
||||
width:
|
||||
(item.showData.locationWide / nowObject.showYamlJson.resolution / 100) * radio +
|
||||
'px',
|
||||
height:
|
||||
(item.showData.locationDeep / nowObject.showYamlJson.resolution / 100) * radio +
|
||||
'px'
|
||||
}"
|
||||
>
|
||||
<el-popover placement="top-start" trigger="hover" width="auto">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="item.imgUrl"
|
||||
alt=""
|
||||
style="width: 100%; height: 100%"
|
||||
:style="{
|
||||
width:
|
||||
(item.showData.locationWide / nowObject.showYamlJson.resolution / 100) *
|
||||
radio +
|
||||
'px',
|
||||
height:
|
||||
(item.showData.locationDeep / nowObject.showYamlJson.resolution / 100) *
|
||||
radio +
|
||||
'px',
|
||||
verticalAlign: 'top'
|
||||
}"
|
||||
@dblclick="storeClick(item)"
|
||||
/>
|
||||
</template>
|
||||
|
@ -422,7 +422,7 @@
|
||||
:x2="Number(state.currentDrawX)"
|
||||
:y2="Number(state.currentDrawY)"
|
||||
stroke="#00329F"
|
||||
stroke-width="3"
|
||||
stroke-width="4"
|
||||
/>
|
||||
<template v-if="state.mapRouteList.length > 0">
|
||||
<template v-for="(curve, index) in state.mapRouteList" :key="index">
|
||||
@ -1155,12 +1155,12 @@ const state = reactive({
|
||||
icon: 'ep:semi-select',
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
switchType: 'drawRoute',
|
||||
name: '框选绘制',
|
||||
icon: 'ep:semi-select',
|
||||
isActive: false
|
||||
},
|
||||
// {
|
||||
// switchType: 'drawRoute',
|
||||
// name: '框选绘制',
|
||||
// icon: 'ep:semi-select',
|
||||
// isActive: false
|
||||
// },
|
||||
{
|
||||
switchType: 'editRoute',
|
||||
name: '编辑路线',
|
||||
@ -2026,45 +2026,45 @@ const clickDrawSelectionArea = () => {
|
||||
}
|
||||
//将一个数组中的点位 按照第一个和最后一个排成一条直线
|
||||
const mapPointsToLine = (points) => {
|
||||
if (points.length < 2) {
|
||||
return points
|
||||
}
|
||||
// 取数组的第一项和最后一项
|
||||
const firstPoint = points[0]
|
||||
const lastPoint = points[points.length - 1]
|
||||
// 找出 locationX 最大和最小的项
|
||||
let minXPoint = points[0]
|
||||
let maxXPoint = points[0]
|
||||
points.forEach((point) => {
|
||||
if (point.locationX < minXPoint.locationX) {
|
||||
minXPoint = point
|
||||
}
|
||||
if (point.locationX > maxXPoint.locationX) {
|
||||
maxXPoint = point
|
||||
}
|
||||
})
|
||||
|
||||
// 计算直线的斜率
|
||||
const dx = lastPoint.locationX - firstPoint.locationX
|
||||
const dy = lastPoint.locationY - firstPoint.locationY
|
||||
const dx = maxXPoint.locationX - minXPoint.locationX
|
||||
const dy = maxXPoint.locationY - minXPoint.locationY
|
||||
|
||||
// 处理垂直直线的情况
|
||||
if (dx === 0) {
|
||||
// 垂直直线的情况
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
return points.map((point) => {
|
||||
if (point === minXPoint || point === maxXPoint) {
|
||||
return point
|
||||
}
|
||||
return {
|
||||
...point,
|
||||
locationX: firstPoint.locationX,
|
||||
locationY: point.locationY
|
||||
locationX: minXPoint.locationX
|
||||
}
|
||||
})
|
||||
}
|
||||
const slope = dy / dx
|
||||
// 计算直线的截距
|
||||
const intercept = firstPoint.locationY - slope * firstPoint.locationX
|
||||
|
||||
// 映射其他点到直线上
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
const slope = dy / dx
|
||||
const intercept = minXPoint.locationY - slope * minXPoint.locationX
|
||||
|
||||
return points.map((point) => {
|
||||
if (point === minXPoint || point === maxXPoint) {
|
||||
return point
|
||||
}
|
||||
const newY = slope * point.locationX + intercept
|
||||
// 考虑精度问题,这里保留三位小数
|
||||
const roundedY = parseFloat(newY.toFixed(8))
|
||||
return {
|
||||
...point,
|
||||
locationY: roundedY
|
||||
locationY: newY
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,94 +1,130 @@
|
||||
<template>
|
||||
<div style="width: 600px; height: 300px; background: #dbeede; position: relative">
|
||||
<div v-for="(item, index) in points" :key="index">
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
backgroundColor: '#000',
|
||||
borderRadius: '50%',
|
||||
zIndex: 999,
|
||||
left: item.x + 'px',
|
||||
top: item.y + 'px'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 600px; height: 300px; background: #b3dcff; position: relative">
|
||||
<div v-for="(item, index) in mappedPoints" :key="index">
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
backgroundColor: '#000',
|
||||
borderRadius: '50%',
|
||||
zIndex: 999,
|
||||
left: item.x + 'px',
|
||||
top: item.y + 'px'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
@mousedown="startSelection"
|
||||
@mousemove="updateSelection"
|
||||
@mouseup="endSelection"
|
||||
style="position: relative; width: 100vw; height: 100vh"
|
||||
>
|
||||
<!-- 绘制所有框选区域 -->
|
||||
<div
|
||||
v-for="(rect, index) in selectionRects"
|
||||
:key="index"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: `${rect.left}px`,
|
||||
top: `${rect.top}px`,
|
||||
width: `${rect.width}px`,
|
||||
height: `${rect.height}px`,
|
||||
backgroundColor: 'rgba(0, 0, 255, 0.2)',
|
||||
border: '1px solid blue'
|
||||
}"
|
||||
></div>
|
||||
|
||||
<!-- 绘制点位 -->
|
||||
<div
|
||||
v-for="(point, index) in points"
|
||||
:key="index"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: `${point.x}px`,
|
||||
top: `${point.y}px`,
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
backgroundColor: selectedPoints.includes(point) ? 'red' : 'green',
|
||||
borderRadius: '50%'
|
||||
}"
|
||||
></div>
|
||||
|
||||
<!-- 确认按钮 -->
|
||||
<button @click="confirmSelection" style="position: fixed; bottom: 20px; right: 20px">
|
||||
确认
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const points = ref([
|
||||
{
|
||||
x: 123.567,
|
||||
y: 178.123
|
||||
},
|
||||
{
|
||||
x: 255.567,
|
||||
y: 79.123
|
||||
},
|
||||
{
|
||||
x: 382.567,
|
||||
y: 178.123
|
||||
},
|
||||
{
|
||||
x: 481.567,
|
||||
y: 238.123
|
||||
}
|
||||
])
|
||||
export default {
|
||||
setup() {
|
||||
const points = ref([
|
||||
{ x: 10, y: 10 },
|
||||
{ x: 30, y: 100 },
|
||||
{ x: 40, y: 40 },
|
||||
{ x: 230, y: 400 },
|
||||
{ x: 750, y: 640 }
|
||||
])
|
||||
|
||||
const mapPointsToLine = (points) => {
|
||||
if (points.length < 2) {
|
||||
return points
|
||||
}
|
||||
// 取数组的第一项和最后一项
|
||||
const firstPoint = points[0]
|
||||
const lastPoint = points[points.length - 1]
|
||||
const isSelecting = ref(false)
|
||||
const startPos = ref({ x: 0, y: 0 })
|
||||
const currentRect = ref({ left: 0, top: 0, width: 0, height: 0 })
|
||||
const selectionRects = ref([]) // 存储所有框选区域
|
||||
const selectedPoints = ref([]) // 存储选中的点位
|
||||
const selectedPointsInOrder = ref([]) // 按照框选顺序存储选中的点位
|
||||
|
||||
// 计算直线的斜率
|
||||
const dx = lastPoint.x - firstPoint.x
|
||||
const dy = lastPoint.y - firstPoint.y
|
||||
if (dx === 0) {
|
||||
// 垂直直线的情况
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
return point
|
||||
}
|
||||
return { x: firstPoint.x, y: point.y }
|
||||
})
|
||||
}
|
||||
const slope = dy / dx
|
||||
// 计算直线的截距
|
||||
const intercept = firstPoint.y - slope * firstPoint.x
|
||||
|
||||
// 映射其他点到直线上
|
||||
return points.map((point, index) => {
|
||||
if (index === 0 || index === points.length - 1) {
|
||||
return point
|
||||
// 开始框选
|
||||
const startSelection = (event) => {
|
||||
isSelecting.value = true
|
||||
startPos.value = { x: event.clientX, y: event.clientY }
|
||||
currentRect.value = { left: event.clientX, top: event.clientY, width: 0, height: 0 }
|
||||
}
|
||||
const newY = slope * point.x + intercept
|
||||
return { x: point.x, y: newY }
|
||||
})
|
||||
}
|
||||
|
||||
const mappedPoints = mapPointsToLine(points.value)
|
||||
console.log(mappedPoints)
|
||||
// 更新框选区域
|
||||
const updateSelection = (event) => {
|
||||
if (isSelecting.value) {
|
||||
currentRect.value = {
|
||||
left: Math.min(startPos.value.x, event.clientX),
|
||||
top: Math.min(startPos.value.y, event.clientY),
|
||||
width: Math.abs(event.clientX - startPos.value.x),
|
||||
height: Math.abs(event.clientY - startPos.value.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束框选
|
||||
const endSelection = () => {
|
||||
if (isSelecting.value) {
|
||||
isSelecting.value = false
|
||||
selectionRects.value.push({ ...currentRect.value }) // 保存当前框选区域
|
||||
checkPointsInSelection(currentRect.value) // 检查当前框选区域内的点位
|
||||
}
|
||||
}
|
||||
|
||||
// 检查点位是否在框选区域内
|
||||
const checkPointsInSelection = (rect) => {
|
||||
points.value.forEach((point) => {
|
||||
if (
|
||||
point.x >= rect.left &&
|
||||
point.x <= rect.left + rect.width &&
|
||||
point.y >= rect.top &&
|
||||
point.y <= rect.top + rect.height &&
|
||||
!selectedPoints.value.includes(point) // 避免重复添加
|
||||
) {
|
||||
selectedPoints.value.push(point)
|
||||
selectedPointsInOrder.value.push(point) // 按照框选顺序添加
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
const confirmSelection = () => {
|
||||
console.log('Selected Points in Order:', selectedPointsInOrder.value)
|
||||
// 清除框选区域和选中的点位
|
||||
selectionRects.value = []
|
||||
selectedPoints.value = []
|
||||
selectedPointsInOrder.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
points,
|
||||
isSelecting,
|
||||
selectionRects,
|
||||
selectedPoints,
|
||||
startSelection,
|
||||
updateSelection,
|
||||
endSelection,
|
||||
confirmSelection
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -551,13 +551,13 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div style="width: 100%; display: flex; align-items: center">
|
||||
<el-form-item
|
||||
required
|
||||
label="放货位置"
|
||||
:prop="`taskDetailList[${index}].releaseId`"
|
||||
:rules="{ required: true, message: '放货位置不能为空', trigger: 'change' }"
|
||||
>
|
||||
<el-form-item
|
||||
required
|
||||
label="放货位置"
|
||||
:prop="`taskDetailList[${index}].releaseId`"
|
||||
:rules="{ required: true, message: '放货位置不能为空', trigger: 'change' }"
|
||||
>
|
||||
<div style="width: 100%; display: flex; align-items: center">
|
||||
<el-select
|
||||
:disabled="!detailItem.releaseType"
|
||||
v-model="detailItem.releaseId"
|
||||
@ -579,15 +579,15 @@
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-icon
|
||||
class="ml-2"
|
||||
size="20"
|
||||
color="#00329F"
|
||||
@click="chooseLocation('release', detailItem, index)"
|
||||
><Location />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-icon
|
||||
class="ml-2"
|
||||
size="20"
|
||||
color="#00329F"
|
||||
@click="chooseLocation('release', detailItem, index)"
|
||||
><Location />
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
|
Loading…
Reference in New Issue
Block a user