From 2159b7f22814098a919670d4ddc6d7060a2cdf49 Mon Sep 17 00:00:00 2001 From: zhanghao <774378400@qq.com> Date: Thu, 6 Aug 2026 11:52:30 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E5=8A=A0=E8=BD=BD=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ManualTakeover/index.vue | 38 +++++++++++++++---- .../cockpit/components/MapCanvas.vue | 10 +++++ .../inspection/cockpit/components/Monitor.vue | 31 +++++++++++++-- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/views/inspection/cockpit/components/ManualTakeover/index.vue b/src/views/inspection/cockpit/components/ManualTakeover/index.vue index 4cbb293..d82338a 100644 --- a/src/views/inspection/cockpit/components/ManualTakeover/index.vue +++ b/src/views/inspection/cockpit/components/ManualTakeover/index.vue @@ -11,7 +11,7 @@
{{ item.robotName }}
-
{{ agvRuntimeStatusLabel(item.status) }}
+
{{ agvRuntimeStatusLabel(item.status) }}
@@ -100,7 +100,7 @@
- +
@@ -134,7 +134,7 @@ import { getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, clea import { Remove, CirclePlus } from '@element-plus/icons-vue' import VoiceConversation from "./VoiceConversation.vue"; import IPlayer from "@/components/IPlayer/index.vue"; -import { agvRuntimeStatusLabel } from '@/utils/agvRuntimeStatus' +import { agvRuntimeStatusClass, agvRuntimeStatusLabel } from '@/utils/agvRuntimeStatus' const robotList = ref([]) const robotDevices = ref([]) @@ -310,7 +310,7 @@ onMounted(() => { fetchRobotList() robotRefreshTimer = window.setInterval(() => { if (!document.hidden) fetchRobotList(false) - }, 5000) + }, 500) }) onUnmounted(() => { @@ -353,10 +353,10 @@ onUnmounted(() => { background: #0F1E38; height: 56px; border-radius: 8px; - padding: 16px; + padding: 12px; display: flex; align-items: center; - gap: 20px; + gap: 12px; margin-bottom: 16px; cursor: pointer; border: 1px solid transparent; @@ -367,10 +367,34 @@ onUnmounted(() => { } .bot-local { - color: #00d4ff; font-size: 11px; margin-top: 4px; } + + .online { + color: #0f8; + } + + .unknown, + .offline, + .stopped { + color: #cbd5e1; + } + + .manual, + .running { + color: #00d4ff; + } + + .charging, + .paused { + color: #ffb300; + } + + .fault, + .emergency { + color: #ff6b6d; + } } .active { diff --git a/src/views/inspection/cockpit/components/MapCanvas.vue b/src/views/inspection/cockpit/components/MapCanvas.vue index b33de48..7d630e4 100644 --- a/src/views/inspection/cockpit/components/MapCanvas.vue +++ b/src/views/inspection/cockpit/components/MapCanvas.vue @@ -28,11 +28,16 @@ import { import { getMapJson } from '@/api/inspection/robot' import { useInspectionStore } from "@/store/modules/inspection"; import { ElMessage } from 'element-plus'; +import { clear } from 'ace-builds/src-noconflict/ext-code_lens'; const props = defineProps({ robotList: { type: Array, default: () => [] + }, + clearMap: { + type: Boolean, + default: false } }) @@ -419,6 +424,11 @@ const colorList = ['#00D4FF', '#FFB300', '#00FF88', '#ff5100', '#fff', '#15ff00' watch(() => props.robotList, (newVal) => { if (newVal.length > 0) { + if (props.clearMap) { + applicationState.robots = [] + } + loadSourceMap() + const ids = applicationState.robots.map(r => r.id); props.robotList.forEach(item => { const position = parseRobotPosition(item.currentPosition) diff --git a/src/views/inspection/cockpit/components/Monitor.vue b/src/views/inspection/cockpit/components/Monitor.vue index ee1ab54..345aa9b 100644 --- a/src/views/inspection/cockpit/components/Monitor.vue +++ b/src/views/inspection/cockpit/components/Monitor.vue @@ -13,8 +13,8 @@
-
机器人#0{{ index + 1}}
-
厂区A-在线
+
{{ item.robotName }}
+
{{ agvRuntimeStatusLabel(item.status) }}
@@ -42,6 +42,7 @@ import SvgIcon from "@/components/SvgIcon"; import IPlayer from "@/components/IPlayer/index.vue"; import { getRobotList } from '@/api/inspection/robot' +import { agvRuntimeStatusClass, agvRuntimeStatusLabel } from '@/utils/agvRuntimeStatus' const videoUrl = ref('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8') @@ -112,10 +113,34 @@ onMounted(() => { } .bot-local { - color: #00d4ff; font-size: 11px; margin-top: 4px; } + + .online { + color: #0f8; + } + + .unknown, + .offline, + .stopped { + color: #cbd5e1; + } + + .manual, + .running { + color: #00d4ff; + } + + .charging, + .paused { + color: #ffb300; + } + + .fault, + .emergency { + color: #ff6b6d; + } } .active { From f5b1b70274af91b493264c2152e571ba939dd1f9 Mon Sep 17 00:00:00 2001 From: zhanghao <774378400@qq.com> Date: Thu, 6 Aug 2026 13:46:08 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E5=A4=84=E7=90=86=E8=BD=AE?= =?UTF-8?q?=E8=AF=A2=E6=97=B6=E5=9C=B0=E5=9B=BE=E5=88=B7=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ManualTakeover/index.vue | 2 +- .../cockpit/components/MapCanvas.vue | 131 +++++++++--------- 2 files changed, 64 insertions(+), 69 deletions(-) diff --git a/src/views/inspection/cockpit/components/ManualTakeover/index.vue b/src/views/inspection/cockpit/components/ManualTakeover/index.vue index d82338a..d78e55e 100644 --- a/src/views/inspection/cockpit/components/ManualTakeover/index.vue +++ b/src/views/inspection/cockpit/components/ManualTakeover/index.vue @@ -100,7 +100,7 @@
- +
diff --git a/src/views/inspection/cockpit/components/MapCanvas.vue b/src/views/inspection/cockpit/components/MapCanvas.vue index 7d630e4..fc29cc2 100644 --- a/src/views/inspection/cockpit/components/MapCanvas.vue +++ b/src/views/inspection/cockpit/components/MapCanvas.vue @@ -28,16 +28,11 @@ import { import { getMapJson } from '@/api/inspection/robot' import { useInspectionStore } from "@/store/modules/inspection"; import { ElMessage } from 'element-plus'; -import { clear } from 'ace-builds/src-noconflict/ext-code_lens'; const props = defineProps({ robotList: { type: Array, default: () => [] - }, - clearMap: { - type: Boolean, - default: false } }) @@ -78,6 +73,9 @@ const applicationState = { let mapCanvas; // 主渲染画布 DOM 元素 let canvasCtx; // 2D 渲染上下文 +let animationFrameId; +let mapLoadVersion = 0; + const init = () => { mapCanvas = document.getElementById('map-canvas'); canvasCtx = mapCanvas.getContext('2d'); @@ -191,7 +189,7 @@ function renderFrame(timestamp) { // 如果地图未加载,跳过绘制 if (!applicationState.isMapLoaded) { - requestAnimationFrame(renderFrame); // 继续请求下一帧 + animationFrameId = requestAnimationFrame(renderFrame); // 继续请求下一帧 return; } @@ -241,7 +239,7 @@ function renderFrame(timestamp) { drawMapBoundary(canvasCtx, map); // 请求下一帧渲染 - requestAnimationFrame(renderFrame); + animationFrameId = requestAnimationFrame(renderFrame); } const drawAllRobots = (ctx) => { @@ -354,14 +352,25 @@ const centerCanvasView = () => { inspectionStore.camera.centerY = (map.originY + map.maxY) / 2; } -const loadSourceMap = async () => { - const connectedRobot = props.robotList.find(item => item.connectStatus == 1); +const getConnectedRobot = (robotList = props.robotList) => { + return robotList.find(item => item?.connectStatus == 1) +} + +const getMapIdentity = (robotList = props.robotList) => { + const robot = getConnectedRobot(robotList) + return robot ? `${robot.id ?? ''}:${robot.robotMapName ?? ''}` : '' +} + +const loadSourceMap = async (connectedRobot = getConnectedRobot()) => { if (!connectedRobot) return; + const currentLoadVersion = ++mapLoadVersion; + const res = await getMapJson({ robotId: connectedRobot.id, mapName: connectedRobot.robotMapName }); + if (currentLoadVersion !== mapLoadVersion) return; if (res.code === 200) { try { const rawJsonObject = JSON.parse(res.data); // 解析 JSON @@ -373,20 +382,6 @@ const loadSourceMap = async () => { } }; -/** - * - * @param id 机器人编号 - * @param name 机器人名称 - * @param angleRad 角度 - * @param color 颜色 - */ -const addRobot = (id, name,x, y, angleRad, color) => { - const map = applicationState.parsedMap; - // 放置于地图中央附近 - const robot = new MobileRobot(id, name, x, y, angleRad, color); - applicationState.robots.push(robot); -} - const parseRobotPosition = (currentPosition) => { if (!currentPosition) return null const values = String(currentPosition).split(',').map(Number) @@ -394,60 +389,58 @@ const parseRobotPosition = (currentPosition) => { return values } -const moveRobot = (robot, dx, dy, dtheta = 0) => { - robot.x += dx; - robot.y += dy; - robot.angle += dtheta; +const syncRobots = (robotList) => { + const previousRobots = new Map(applicationState.robots.map(robot => [robot.id, robot])); + const nextRobots = []; + + robotList.forEach(item => { + const position = parseRobotPosition(item.currentPosition) + if (!position) return + + const [x, y, angle] = position + const robot = previousRobots.get(item.id) + if (robot) { + robot.name = item.robotName + robot.x = x + robot.y = y + robot.angle = angle + nextRobots.push(robot) + return + } + + nextRobots.push(new MobileRobot( + item.id, + item.robotName, + x, + y, + angle, + colorList[nextRobots.length % colorList.length] + )) + }) + + applicationState.robots = nextRobots } onMounted(() => { init() - if (props.robotList.length > 0) { - loadSourceMap() - requestAnimationFrame(renderFrame); - window.addEventListener('resize', resizeCanvasToContainer); // 监听窗口大小变化 - resizeCanvasToContainer(); // 初始调用一次 - setTimeout(() => { - bindEvent() - props.robotList.forEach(item => { - const position = parseRobotPosition(item.currentPosition) - if (!position) return - const [x, y, angle] = position - addRobot(item.id, item.robotName, x, y, angle, colorList[applicationState.robots.length % colorList.length]) - }) - }, 300) - } + window.addEventListener('resize', resizeCanvasToContainer); + resizeCanvasToContainer(); + bindEvent() + syncRobots(props.robotList) + loadSourceMap() + animationFrameId = requestAnimationFrame(renderFrame); }) const colorList = ['#00D4FF', '#FFB300', '#00FF88', '#ff5100', '#fff', '#15ff00', '#ff00d4', '#00d4ff'] -watch(() => props.robotList, - (newVal) => { - if (newVal.length > 0) { - if (props.clearMap) { - applicationState.robots = [] - } - loadSourceMap() +// 轮询只同步机器人实例,不重新请求、解析或重置地图。 +watch(() => props.robotList, syncRobots) - const ids = applicationState.robots.map(r => r.id); - props.robotList.forEach(item => { - const position = parseRobotPosition(item.currentPosition) - if (!position) return - const [x, y, angle] = position - if (!ids.includes(item.id)) { - addRobot(item.id, item.robotName, x, y, angle, colorList[applicationState.robots.length % colorList.length]) - } else { - applicationState.robots.forEach(robot => { - if (robot.id === item.id) { - // moveRobot(robot, parseFloat(x) - robot.x, parseFloat(y) - robot.y, parseFloat(angle) - robot.angle) - robot.x = x - robot.y = y - robot.angle = angle - } - }) - } - }) - } +// 只有用于加载地图的机器人或地图名称改变时,才重新加载底图。 +watch( + () => getMapIdentity(props.robotList), + (newIdentity, oldIdentity) => { + if (newIdentity && newIdentity !== oldIdentity) loadSourceMap() } ) @@ -458,6 +451,8 @@ defineExpose({ onUnmounted(() => { window.removeEventListener('resize', resizeCanvasToContainer) + cancelAnimationFrame(animationFrameId) + mapLoadVersion += 1 canvasCtx = null }) From 544711cd73e041fe2726908fe0acfeb0d2b48d6e Mon Sep 17 00:00:00 2001 From: zhanghao <774378400@qq.com> Date: Thu, 6 Aug 2026 14:48:28 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E8=AF=95=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E4=B8=8D=E9=9C=80=E8=A6=81=E8=A6=81=E6=9C=89=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=BA=E8=BF=9E=E6=8E=A5=E7=9A=84=EF=BC=8C?= =?UTF-8?q?=E5=9B=A0=E4=B8=BA=E5=8F=AF=E4=BB=A5=E9=80=89=E6=8B=A9=E6=9C=AA?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E7=9A=84=E6=9C=BA=E5=99=A8=E4=BA=BA=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E8=AF=95=E8=BF=90=E8=A1=8C=E6=9D=A5=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/flow/components/TestRun.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/views/flow/components/TestRun.vue b/src/views/flow/components/TestRun.vue index dc5b567..47daa2c 100644 --- a/src/views/flow/components/TestRun.vue +++ b/src/views/flow/components/TestRun.vue @@ -209,7 +209,9 @@ const handleRobotChange = async (robotId) => { /** 获取在线机器人列表 */ const loadRobotOptions = async () => { - const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' }) + // 试运行不需要要有连接状态为连接的,因为可以选择未连接的机器人进行试运行来保存流程数据 + // , connectStatus: '1' + const res = await getRobotList({ pageNum: 1, pageSize: 1000 }) if (res.code === 200) { robotIdOptions.value = res.rows?.map((item) => { return { From 5fb3bf4e2007a07315c0011b8b7e5372c8f0c395 Mon Sep 17 00:00:00 2001 From: zhanghao <774378400@qq.com> Date: Thu, 6 Aug 2026 15:10:47 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E5=85=8B=E9=9A=86=E8=8A=82?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/test/configs/index.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/test/configs/index.vue b/src/views/test/configs/index.vue index ad0264b..769e606 100644 --- a/src/views/test/configs/index.vue +++ b/src/views/test/configs/index.vue @@ -227,13 +227,15 @@
待检测项
+