diff --git a/src/views/inspection/cockpit/components/MapCanvas.vue b/src/views/inspection/cockpit/components/MapCanvas.vue index fc29cc2..0d943d6 100644 --- a/src/views/inspection/cockpit/components/MapCanvas.vue +++ b/src/views/inspection/cockpit/components/MapCanvas.vue @@ -73,7 +73,9 @@ const applicationState = { let mapCanvas; // 主渲染画布 DOM 元素 let canvasCtx; // 2D 渲染上下文 +// 保存当前动画帧 ID,组件卸载时用于停止 Canvas 渲染循环。 let animationFrameId; +// 地图请求版本号:当多个地图请求并发时,只允许最后一次请求更新画布。 let mapLoadVersion = 0; const init = () => { @@ -352,24 +354,39 @@ const centerCanvasView = () => { inspectionStore.camera.centerY = (map.originY + map.maxY) / 2; } +/** + * 从机器人列表中选出地图数据来源。 + * 当前沿用原有规则:使用第一台已连接的机器人获取地图。 + */ const getConnectedRobot = (robotList = props.robotList) => { return robotList.find(item => item?.connectStatus == 1) } +/** + * 生成地图来源的稳定标识。 + * 机器人轮询时位置等字段会不断变化,但机器人 ID 和地图名称不变, + * 因此监听该标识可以避免位置更新触发底图重新加载。 + */ const getMapIdentity = (robotList = props.robotList) => { const robot = getConnectedRobot(robotList) return robot ? `${robot.id ?? ''}:${robot.robotMapName ?? ''}` : '' } +/** + * 请求并加载机器人当前使用的地图。 + * @param {Object} connectedRobot 用于提供地图的已连接机器人 + */ 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 { @@ -382,6 +399,10 @@ const loadSourceMap = async (connectedRobot = getConnectedRobot()) => { } }; +/** + * 将接口返回的 "x,y,angle" 字符串转换为数值坐标。 + * 无位置或包含非法数值时返回 null,避免无效机器人进入绘制列表。 + */ const parseRobotPosition = (currentPosition) => { if (!currentPosition) return null const values = String(currentPosition).split(',').map(Number) @@ -389,7 +410,15 @@ const parseRobotPosition = (currentPosition) => { return values } +/** + * 将最新机器人列表同步到 Canvas 使用的机器人实例中。 + * + * 已存在的机器人会复用原 MobileRobot 实例,仅修改位置和名称;新增机器人 + * 创建实例,接口中已移除或没有有效位置的机器人不会进入 nextRobots。 + * 该函数不会加载地图或重置相机,因此可安全用于高频轮询。 + */ const syncRobots = (robotList) => { + // 用 ID 建立索引,避免为每台机器人遍历整个旧列表。 const previousRobots = new Map(applicationState.robots.map(robot => [robot.id, robot])); const nextRobots = []; @@ -400,6 +429,7 @@ const syncRobots = (robotList) => { const [x, y, angle] = position const robot = previousRobots.get(item.id) if (robot) { + // 保留原实例和颜色,只更新轮询产生的动态信息。 robot.name = item.robotName robot.x = x robot.y = y @@ -408,6 +438,7 @@ const syncRobots = (robotList) => { return } + // 列表中首次出现的机器人需要创建绘制实例并分配颜色。 nextRobots.push(new MobileRobot( item.id, item.robotName, @@ -418,6 +449,7 @@ const syncRobots = (robotList) => { )) }) + // 整体替换绘制列表,同时自然移除本轮接口中不存在的机器人。 applicationState.robots = nextRobots } @@ -451,6 +483,7 @@ defineExpose({ onUnmounted(() => { window.removeEventListener('resize', resizeCanvasToContainer) + // 停止渲染循环,并使仍在进行中的地图请求全部失效。 cancelAnimationFrame(animationFrameId) mapLoadVersion += 1 canvasCtx = null