feat: 加载地图

This commit is contained in:
zhanghao 2026-08-05 11:08:44 +08:00
parent d2130ffaf5
commit 3104b34cb0
5 changed files with 108 additions and 55 deletions

1
auto-imports.d.ts vendored
View File

@ -8,6 +8,7 @@ export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const ElMess: typeof import('element-plus/es').ElMess
const ElMessage: typeof import('element-plus/es').ElMessage
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp

View File

@ -1,25 +1,35 @@
import { ElMessage } from 'element-plus'
const request = async (url, params = {}, method = 'GET', headers = { 'Content-Type': 'application/json' }) => {
// 1. 创建控制器
const controller = new AbortController();
const { signal } = controller;
// 2. 设置定时器,超时后调用 abort()
const controller = new AbortController()
const { signal } = controller
const timeoutId = setTimeout(() => {
controller.abort(); // 触发中止信号
controller.abort()
}, 5000)
const res = await fetch(url, {
method: method,
headers: headers,
try {
const res = await fetch(url, {
method,
headers,
body: JSON.stringify(params),
signal
}).finally(() => {
// 4. 无论请求成功还是失败,都要清除定时器,防止内存泄漏
clearTimeout(timeoutId);
});
})
const data = await res.json()
return data
const data = await res.json()
if (!res.ok) {
throw new Error(data.message || data.error || `请求失败(${res.status}`)
}
return data
} catch (error) {
const message = error.name === 'AbortError'
? '请求超时,请稍后重试'
: (error.message || '请求失败,请稍后重试')
ElMessage.error(message)
throw error
} finally {
clearTimeout(timeoutId)
}
}
export default request
export default request

View File

@ -43,6 +43,7 @@ import { useRouter } from 'vue-router'
import { getRuntimeState } from '@/api/agv.js'
import { useRobotStore } from '@/stores/robot'
import { ElMessage } from 'element-plus'
import { ca } from 'element-plus/es/locales.mjs'
const router = useRouter()
@ -104,24 +105,27 @@ const testConnect = async () => {
robotStore.setCameraDeviceId('real_cam1')
}
const result = await getRuntimeState({
ip: robotStore.ip,
deviceId: ruleForm.value.agvDeviceId
})
loading.value = false
if (result.code === 200) {
robotStore.setPosition(result.data.pose)
robotStore.setBattery(result.data.battery)
robotStore.setCurrentMap(result.data.current_map)
router.push({
path: '/home'
try {
const result = await getRuntimeState({
ip: robotStore.ip,
deviceId: robotStore.agvDeviceId
})
} else {
ElMessage.error(result?.message || '连接服务错误')
loading.value = false
if (result.code === 200) {
robotStore.setPosition(result.data.pose)
robotStore.setBattery(result.data.battery)
robotStore.setCurrentMap(result.data.current_map)
router.push({
path: '/home'
})
} else {
ElMessage.error(result?.message || result?.error || '连接服务错误')
}
}
catch (error) {
loading.value = false
ElMessage.error(error?.message || '连接服务错误')
}
router.push({
path: '/home'
})
}
}

View File

@ -348,6 +348,8 @@ onBeforeUnmount(() => {
.map-body {
flex: 1;
min-width: 0;
min-height: 0;
position: relative;
overflow: hidden;
background: $color-background-base;

View File

@ -1,6 +1,6 @@
<template>
<div class="container">
<canvas id="map-canvas"></canvas>
<div ref="containerRef" class="container">
<canvas ref="mapCanvasRef" id="map-canvas"></canvas>
<div id="tooltip">
<div class="tooltip-title"></div>
<div class="tooltip-detail"></div>
@ -9,7 +9,7 @@
</template>
<script setup>
import { onMounted, onUnmounted, watch } from 'vue';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import {
worldToScreen,
@ -90,8 +90,12 @@ const applicationState = {
let mapCanvas; // DOM
let canvasCtx; // 2D
const containerRef = ref(null)
const mapCanvasRef = ref(null)
let resizeObserver
const init = () => {
mapCanvas = document.getElementById('map-canvas');
mapCanvas = mapCanvasRef.value;
canvasCtx = mapCanvas.getContext('2d');
}
@ -100,14 +104,34 @@ const init = () => {
* 在窗口 resize 时自动调用
*/
function resizeCanvasToContainer() {
const containerRect = mapCanvas.parentElement.getBoundingClientRect(); //
devicePixelRatio = window.devicePixelRatio || 1; //
if (!mapCanvas || !containerRef.value) return;
const containerRect = containerRef.value.getBoundingClientRect(); //
if (!containerRect.width || !containerRect.height) return;
applicationState.devicePixelRatio = window.devicePixelRatio || 1; //
applicationState.canvasWidthPx = containerRect.width; //
applicationState.canvasHeightPx = containerRect.height; // = -
mapCanvas.width = applicationState.canvasWidthPx * devicePixelRatio; // Canvas
mapCanvas.height = applicationState.canvasHeightPx * devicePixelRatio; // Canvas
mapCanvas.width = applicationState.canvasWidthPx * applicationState.devicePixelRatio; // Canvas
mapCanvas.height = applicationState.canvasHeightPx * applicationState.devicePixelRatio; // Canvas
mapCanvas.style.width = applicationState.canvasWidthPx + 'px'; // CSS
mapCanvas.style.height = applicationState.canvasHeightPx + 'px'; // CSS
if (applicationState.parsedMap) {
fitMapToCanvas(applicationState.parsedMap)
}
}
function fitMapToCanvas(parsedMap) {
const worldWidthMeters = parsedMap.maxX - parsedMap.originX;
const worldHeightMeters = parsedMap.maxY - parsedMap.originY;
if (!worldWidthMeters || !worldHeightMeters || !applicationState.canvasWidthPx || !applicationState.canvasHeightPx) return;
applicationState.camera.centerX = (parsedMap.originX + parsedMap.maxX) / 2;
applicationState.camera.centerY = (parsedMap.originY + parsedMap.maxY) / 2;
const fitScaleX = applicationState.canvasWidthPx / worldWidthMeters;
const fitScaleY = applicationState.canvasHeightPx / worldHeightMeters;
// Canvas Canvas
applicationState.camera.pixelsPerMeter = Math.max(1, Math.max(fitScaleX, fitScaleY));
}
@ -172,14 +196,7 @@ async function loadMapIntoApplication(parsedMap) {
applicationState.gridOffscreen = buildOccupancyGridImage(parsedMap, applicationState.layerVisibility.edges, applicationState);
//
applicationState.camera.centerX = (parsedMap.originX + parsedMap.maxX) / 2; //
applicationState.camera.centerY = (parsedMap.originY + parsedMap.maxY) / 2; //
// 使
const worldWidthMeters = parsedMap.maxX - parsedMap.originX; //
const worldHeightMeters = parsedMap.maxY - parsedMap.originY; //
const fitScaleX = applicationState.canvasWidthPx / worldWidthMeters; //
const fitScaleY = applicationState.canvasHeightPx / worldHeightMeters; //
applicationState.camera.pixelsPerMeter = Math.max(1, Math.max(fitScaleX, fitScaleY) * 1); //
fitMapToCanvas(parsedMap);
// UI
applicationState.isMapLoaded = true;
@ -379,9 +396,12 @@ const centerCanvasView = () => {
}
const loadSourceMap = async (mapName) => {
await nextTick()
if (!mapCanvas) init()
resizeCanvasToContainer()
const res = await getCurrentMap({
ip: robotStore.ip,
// deviceId: VITE_AGV_DEVICE_ID,
deviceId: robotStore.agvDeviceId,
mapName: mapName
})
@ -419,6 +439,10 @@ const moveRobot = (robot, dx, dy, dtheta = 0) => {
onMounted(() => {
init()
resizeCanvasToContainer()
resizeObserver = new ResizeObserver(resizeCanvasToContainer)
resizeObserver.observe(containerRef.value)
window.addEventListener('resize', resizeCanvasToContainer)
})
watch(() => props.mapName,
@ -427,8 +451,6 @@ watch(() => props.mapName,
await loadSourceMap(newVal)
addRobot(robotStore.ip, robotStore.ip, robotStore.position.x, robotStore.position.y, robotStore.position.theta, getThemeColor('--color-canvas-robot'))
requestAnimationFrame(renderFrame);
window.addEventListener('resize', resizeCanvasToContainer); //
resizeCanvasToContainer(); //
}
}, {
immediate: true
@ -442,15 +464,29 @@ defineExpose({
onUnmounted(() => {
window.removeEventListener('resize', resizeCanvasToContainer)
resizeObserver?.disconnect()
canvasCtx = null
})
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 100%;
position: relative;
position: absolute;
inset: 0;
width: auto;
height: auto;
min-width: 0;
min-height: 0;
overflow: hidden;
box-sizing: border-box;
#map-canvas {
position: absolute;
inset: 0;
display: block;
width: 100% !important;
height: 100% !important;
}
#tooltip {
position: absolute;