From 2e148ea65902b6055fd470c9c0f9abc2e6612a92 Mon Sep 17 00:00:00 2001 From: yyy <2605810609@qq.com> Date: Wed, 11 Jun 2025 15:24:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=87=8F=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local | 2 +- .../BatchCopyingDialogForm.vue | 125 ++++++++ .../realTimeMap/components/indexPage.vue | 2 +- src/views/mapPage/realTimeMap/editMap.vue | 268 +++++++++++++++--- 4 files changed, 361 insertions(+), 36 deletions(-) create mode 100644 src/views/mapPage/realTimeMap/components-tool/BatchCopyingDialogForm.vue diff --git a/.env.local b/.env.local index 2d5925a2..835b8acd 100644 --- a/.env.local +++ b/.env.local @@ -5,7 +5,7 @@ VITE_DEV=true # 请求路径 # VITE_BASE_URL='http://192.168.77.50:48080' -# VITE_BASE_URL='http://10.10.100.19:48080' +# VITE_BASE_URL='http://10.10.100.15:48080' VITE_BASE_URL='http://10.10.5.5:48080' # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 diff --git a/src/views/mapPage/realTimeMap/components-tool/BatchCopyingDialogForm.vue b/src/views/mapPage/realTimeMap/components-tool/BatchCopyingDialogForm.vue new file mode 100644 index 00000000..2b56548f --- /dev/null +++ b/src/views/mapPage/realTimeMap/components-tool/BatchCopyingDialogForm.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/views/mapPage/realTimeMap/components/indexPage.vue b/src/views/mapPage/realTimeMap/components/indexPage.vue index 23cf37ea..7fc7a71e 100644 --- a/src/views/mapPage/realTimeMap/components/indexPage.vue +++ b/src/views/mapPage/realTimeMap/components/indexPage.vue @@ -883,7 +883,7 @@ onMounted(() => { // 监听页面可见性变化 document.addEventListener('visibilitychange', () => { if (!document.hidden && robotListTimerRef.value === null) { - robotListTimerRef.value = setInterval(getRobotByFloorAndAreaList, 5000) + robotListTimerRef.value = setInterval(getRobotByFloorAndAreaList, 10000) } }) }) diff --git a/src/views/mapPage/realTimeMap/editMap.vue b/src/views/mapPage/realTimeMap/editMap.vue index d52247ef..94c637f9 100644 --- a/src/views/mapPage/realTimeMap/editMap.vue +++ b/src/views/mapPage/realTimeMap/editMap.vue @@ -284,6 +284,7 @@ toolbarSwitchType === 'createRegion' || toolbarSwitchType === 'drawRoute' || toolbarSwitchType === 'generateLine' || + toolbarSwitchType === 'batchCopying' || toolbarSwitchType === 'bulkDelete')) " > @@ -294,6 +295,7 @@ toolbarSwitchType === 'createRegion' || toolbarSwitchType === 'drawRoute' || toolbarSwitchType === 'generateLine' || + toolbarSwitchType === 'batchCopying' || toolbarSwitchType === 'bulkDelete') " type="danger" @@ -898,6 +900,12 @@ ref="GenerateStraightLinesDialogRef" @generate-straight-lines-submit="GenerateStraightLinesSubmit" /> + + @@ -910,6 +918,7 @@ 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 BatchCopyingDialogForm from './components-tool/BatchCopyingDialogForm.vue' import lineLibrarySettingDialog from './components-tool/lineLibrarySettingDialog.vue' import layerSelectionToolDialog from './components-tool/layerSelectionToolDialog.vue' import itemAreaManagementDialog from './components-tool/itemAreaManagementDialog.vue' @@ -928,6 +937,7 @@ const lineLibraryManagementDialogRef = ref() //线库管理 const itemAreaManagementDialogRef = ref() //区域管理 const lineLibrarySettingDialogRef = ref() //线库设置 const itemAreaSettingDialogRef = ref() //物料区域设置 +const BatchCopyingDialogFormRef = ref() //批量复制 const equipmentToolDialogRef = ref() //设备弹窗 const textFormToolDialogRef = ref() //文字输入弹窗 const editMapRouteDialogRef = ref() //编辑地图路线的弹窗 @@ -1519,6 +1529,13 @@ const state = reactive({ icon: 'ep:delete', isActive: false }, + + { + switchType: 'batchCopying', + name: '批量复制', + icon: 'ep:document-copy', + isActive: false + }, { switchType: 'showSortNum', name: '节点序号', @@ -1566,6 +1583,7 @@ const state = reactive({ drawSelectionAreaBox: { x: 0, y: 0, width: 0, height: 0 }, //绘制区域的位置,长宽 drawSelectionStartPoint: { x: 0, y: 0 }, //开始绘制的点位 drawSelectionPointList: [], //绘制选中的list + drawSelectionRouteList: [], //绘制选中的路线list textFormToolShow: false, //文字表单显示隐藏 showInputBox: false, //输入框显示隐藏 inputBoxStyle: { @@ -1700,7 +1718,8 @@ const toolbarClick = async (item) => { toolbarSwitchType.value === 'generateLine' || toolbarSwitchType.value === 'createLineLibrary' || toolbarSwitchType.value === 'createRegion' || - toolbarSwitchType.value === 'bulkDelete' + toolbarSwitchType.value === 'bulkDelete' || + toolbarSwitchType.value === 'batchCopying' ) { state.cursorStyle = 'crosshair' } else if ( @@ -1734,6 +1753,7 @@ const toolbarClick = async (item) => { toolbarSwitchType.value === 'createLineLibrary' || toolbarSwitchType.value === 'createRegion' || toolbarSwitchType.value === 'bulkDelete' || + toolbarSwitchType.value === 'batchCopying' || toolbarSwitchType.value === 'editRoute' || toolbarSwitchType.value === 'generateLine' ) { @@ -1936,6 +1956,9 @@ const toolbarClick = async (item) => { case 'bulkDelete': // 批量删除 break + case 'batchCopying': + //批量复制 + break case 'showSortNum': // 显示节点序号 //网格 @@ -2280,7 +2303,8 @@ const startDrawSelection = (event) => { toolbarSwitchType.value === 'createRegion' || toolbarSwitchType.value === 'drawRoute' || toolbarSwitchType.value == 'generateLine' || - toolbarSwitchType.value === 'bulkDelete' + toolbarSwitchType.value === 'bulkDelete' || + toolbarSwitchType.value === 'batchCopying' ) { const { x, y } = disposeEventPoints(event) @@ -2301,7 +2325,8 @@ const updateDrawSelection = throttle((event) => { toolbarSwitchType.value === 'createRegion' || toolbarSwitchType.value === 'drawRoute' || toolbarSwitchType.value === 'generateLine' || - toolbarSwitchType.value === 'bulkDelete' + toolbarSwitchType.value === 'bulkDelete' || + toolbarSwitchType.value === 'batchCopying' ) { if (state.drawSelectionAreaShow) { const { x, y } = disposeEventPoints(event) @@ -2405,7 +2430,8 @@ const endDrawSelection = (event) => { toolbarSwitchType.value === 'createRegion' || toolbarSwitchType.value === 'drawRoute' || toolbarSwitchType.value === 'generateLine' || - toolbarSwitchType.value === 'bulkDelete' + toolbarSwitchType.value === 'bulkDelete' || + toolbarSwitchType.value === 'batchCopying' ) { // 使用 requestAnimationFrame 优化渲染 requestAnimationFrame(() => { @@ -2421,26 +2447,64 @@ const endDrawSelection = (event) => { //点击区域 const clickDrawSelectionArea = () => { - let points = state.allMapPointInfo - state.drawSelectionPointList = [] + state.drawSelectionRouteList = [] + + // 用于存储所有框选区域的数据 + const allSelectedPoints = new Map() + const allSelectedRoutes = new Set() + state.allDrawSelectionAreaBox.forEach((box) => { - points.forEach((point) => { + // 筛选点 + state.allMapPointInfo.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) + // 使用位置和类型组合作为唯一标识 + const pointKey = `${point.locationX},${point.locationY},${point.type}` + if (!allSelectedPoints.has(pointKey)) { + allSelectedPoints.set(pointKey, point) + } } }) + + // 筛选路线 + if (toolbarSwitchType.value === 'batchCopying') { + state.mapRouteList.forEach((route) => { + const startPointInBox = + route.startPointX >= box.x && + route.startPointX <= box.x + box.width && + route.startPointY >= box.y && + route.startPointY <= box.y + box.height + + const endPointInBox = + route.endPointX >= box.x && + route.endPointX <= box.x + box.width && + route.endPointY >= box.y && + route.endPointY <= box.y + box.height + + if (startPointInBox && endPointInBox) { + // 使用起点ID和终点ID组合作为唯一标识 + const routeKey = `${route.startingPointId}-${route.endPointId}` + if (!allSelectedRoutes.has(routeKey)) { + allSelectedRoutes.add(routeKey) + } + } + }) + } }) + // 将去重后的数据赋值给状态 + state.drawSelectionPointList = Array.from(allSelectedPoints.values()) + state.drawSelectionRouteList = state.mapRouteList.filter((route) => + allSelectedRoutes.has(`${route.startingPointId}-${route.endPointId}`) + ) + // 清空框选区域 state.allDrawSelectionAreaBox = [] - //去重 - state.drawSelectionPointList = deduplicateArrayById(state.drawSelectionPointList) //只要库位的 let binLocation = state.drawSelectionPointList.filter((item) => item.type === 2) //所以类型的 @@ -2578,7 +2642,120 @@ const clickDrawSelectionArea = () => { state.currentItemIndex = -1 addEditHistory() } + + //批量复制 + if (toolbarSwitchType.value === 'batchCopying') { + if (state.drawSelectionPointList.length > 0) { + let boundaryValue = { + left: Math.min(...state.drawSelectionPointList.map((point) => Number(point.locationX))), + right: Math.max(...state.drawSelectionPointList.map((point) => Number(point.locationX))), + top: Math.min(...state.drawSelectionPointList.map((point) => Number(point.locationY))), + bottom: Math.max(...state.drawSelectionPointList.map((point) => Number(point.locationY))) + } + BatchCopyingDialogFormRef.value.open(boundaryValue) + } else { + message.warning('至少选择一个点') + } + } } + +//批量复制 +const submitBatchCopyingFormSuccess = async (form) => { + let newPoints = JSON.parse(JSON.stringify(state.drawSelectionPointList)) + let newRoutes = JSON.parse(JSON.stringify(state.drawSelectionRouteList)) + + // 创建节点ID映射 + const nodeIdMap = new Map() + + // 处理节点列表 + const newPointList = await Promise.all( + newPoints.map(async (node) => { + const newId = await MapApi.getNodeId() + nodeIdMap.set(node.id, newId) + + const locationX = Number(node.locationX) + Number(form.x) + const locationY = Number(node.locationY) + Number(form.y) + const actualPoint = disposeEventPoint(locationX, locationY) + + const { sortNum, createTime, ...restNode } = node + return { + ...restNode, + id: newId, + locationX, + locationY, + actualLocationX: actualPoint.actualLocationX, + actualLocationY: actualPoint.actualLocationY + } + }) + ) + + // 处理路线列表 + const newRouteList = newRoutes.map((route) => { + const newRoute = { ...route } + + // 更新起点 + if (nodeIdMap.has(route.startingPointId)) { + newRoute.startingPointId = nodeIdMap.get(route.startingPointId) + newRoute.startPointX = (Number(route.startPointX) + Number(form.x)).toString() + newRoute.startPointY = (Number(route.startPointY) + Number(form.y)).toString() + + const actualStartPoint = disposeEventPoint(newRoute.startPointX, newRoute.startPointY) + newRoute.actualStartPointX = actualStartPoint.actualLocationX + newRoute.actualStartPointY = actualStartPoint.actualLocationY + + // 更新控制点 + if (route.method == 1) { + newRoute.beginControlX = (Number(route.beginControlX) + Number(form.x)).toString() + newRoute.beginControlY = (Number(route.beginControlY) + Number(form.y)).toString() + const actualBeginControl = disposeEventPoint(newRoute.beginControlX, newRoute.beginControlY) + newRoute.actualBeginControlX = actualBeginControl.actualLocationX + newRoute.actualBeginControlY = actualBeginControl.actualLocationY + } else { + newRoute.beginControlX = 0 + newRoute.beginControlY = 0 + newRoute.actualBeginControlX = 0 + newRoute.actualBeginControlY = 0 + } + } + + // 更新终点 + if (nodeIdMap.has(route.endPointId)) { + newRoute.endPointId = nodeIdMap.get(route.endPointId) + newRoute.endPointX = (Number(route.endPointX) + Number(form.x)).toString() + newRoute.endPointY = (Number(route.endPointY) + Number(form.y)).toString() + + const actualEndPoint = disposeEventPoint(newRoute.endPointX, newRoute.endPointY) + newRoute.actualEndPointX = actualEndPoint.actualLocationX + newRoute.actualEndPointY = actualEndPoint.actualLocationY + + // 更新控制点 + if (route.method == 1) { + newRoute.endControlX = (Number(route.endControlX) + Number(form.x)).toString() + newRoute.endControlY = (Number(route.endControlY) + Number(form.y)).toString() + const actualEndControl = disposeEventPoint(newRoute.endControlX, newRoute.endControlY) + newRoute.actualEndControlX = actualEndControl.actualLocationX + newRoute.actualEndControlY = actualEndControl.actualLocationY + } else { + newRoute.endControlX = 0 + newRoute.endControlY = 0 + newRoute.actualEndControlX = 0 + newRoute.actualEndControlY = 0 + } + } + + const { id, createTime, startingSortNum, endPointSortNum, ...restRoute } = newRoute + return restRoute + }) + + // 添加到地图 + console.log(newPointList, newRouteList) + + state.allMapPointInfo.push(...newPointList) + state.mapRouteList.push(...newRouteList) + message.success('复制成功') + addEditHistory() +} + //生成直线 选择完成开始点和结束点 const GenerateStraightLinesSubmit = (pointList, form) => { // 使用 requestAnimationFrame 优化渲染 @@ -2630,24 +2807,13 @@ const GenerateStraightLinesSubmit = (pointList, form) => { } return item }) - // 批量更新状态 state.allMapPointInfo = updatedPoints state.mapRouteList = updatedRoutes addEditHistory() }) } -//去重 -const deduplicateArrayById = (arr) => { - const idSet = new Set() - return arr.filter((item) => { - if (idSet.has(item.id)) { - return false - } - idSet.add(item.id) - return true - }) -} + //返回生成线库时 排序相同项的id const findDuplicateLocationIds = (data) => { const locationMap = {} @@ -3341,6 +3507,7 @@ const saveMap = async () => { message.success('保存成功') } catch (error) { loading.close() + message.error(error?.message || '保存失败') } } //节点的保存 @@ -3374,11 +3541,19 @@ const saveNodeList = async () => { item.dataJson = JSON.stringify(item.dataObj) } }) - await MapApi.batchSaveOrEditOrDelMapItem(imgBgObj.positionMapId, list) + try { + await MapApi.batchSaveOrEditOrDelMapItem(imgBgObj.positionMapId, list) + } catch (error) { + throw new Error('节点保存失败:' + (error?.message || '未知错误')) + } } //路线的保存 const saveMapRoute = async () => { - await MapApi.createOrEditOrDelPositionMapLine(imgBgObj.positionMapId, state.mapRouteList) + try { + await MapApi.createOrEditOrDelPositionMapLine(imgBgObj.positionMapId, state.mapRouteList) + } catch (error) { + throw new Error('路线保存失败:' + (error?.message || '未知错误')) + } } //线库新增 要在库位新增线库信息 const submitLineLibraryFormSuccess = (obj) => { @@ -3556,15 +3731,7 @@ const getLineMidArrowPath = (item) => { return `M ${startXArrow} ${startYArrow} L ${endXArrow} ${endYArrow}` } -// 计算贝塞尔曲线中间箭头的路径(简单近似) -const getBezierMidArrowPath = (item) => { - const path = document.createElementNS('http://www.w3.org/2000/svg', 'path') - path.setAttribute('d', item.curvePath) - const length = path.getTotalLength() - const midPoint = path.getPointAtLength(length / 2) - const prevPoint = path.getPointAtLength(length / 2 - 1) - return `M ${prevPoint.x} ${prevPoint.y} L ${midPoint.x} ${midPoint.y}` -} + // 计算贝塞尔曲线中间文字的 x 坐标 const computedCurveTextX = (item) => { return ( @@ -3851,6 +4018,8 @@ const findClosestPoint = (x, y) => { padding: 0 0.75rem; .top-tool-list { + max-width: calc(100vw - 260px); + overflow-x: auto; display: flex; align-items: center; text-align: center; @@ -3896,11 +4065,13 @@ const findClosestPoint = (x, y) => { .right-tool-list { position: absolute; right: 0; - top: 64px; + top: 70px; background-color: #fff; z-index: 999; text-align: center; box-shadow: rgba(0, 0, 0, 0.05) 0rem 0rem 0rem 0.0625rem; + max-height: calc(100vh - 200px); + overflow-y: auto; .tool-item { cursor: pointer; @@ -4008,4 +4179,33 @@ const findClosestPoint = (x, y) => { height: 20px; } } + +// 美化滚动条 +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + width: 6px; + background: rgba(#101f1c, 0.1); + -webkit-border-radius: 2em; + -moz-border-radius: 2em; + border-radius: 2em; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(144, 147, 153, 0.7); + background-clip: padding-box; + min-height: 28px; + -webkit-border-radius: 2em; + -moz-border-radius: 2em; + border-radius: 2em; + transition: background-color 0.3s; + cursor: pointer; +} + +::-webkit-scrollbar-thumb:hover { + background-color: rgba(144, 147, 153, 0.3); +}