CMVR-IOT-UI/src/views/inspection/map/index.vue

579 lines
22 KiB
Vue
Raw Normal View History

2026-05-26 14:45:49 +08:00
<template>
2026-05-29 13:54:45 +08:00
<div class="app-container">
<div ref="topContainerRef">
2026-06-01 11:04:18 +08:00
<el-form :model="queryParams" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="地图名称" prop="mapName">
<el-input v-model="queryParams.mapName" placeholder="请输入地图名称" clearable @keyup.enter="handleQuery" />
2026-05-29 13:54:45 +08:00
</el-form-item>
2026-06-01 11:04:18 +08:00
<el-form-item label="地图描述" prop="mapDesc">
<el-input v-model="queryParams.mapDesc" placeholder="请输入地图描述" clearable @keyup.enter="handleQuery" />
2026-05-29 13:54:45 +08:00
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</div>
<div :style="containerHeight">
<el-table v-loading="loading" height="100%" style="width: 100%" :data="tableDataList">
2026-06-01 11:04:18 +08:00
<el-table-column label="地图名称" align="center" prop="mapName" />
<el-table-column label="地图描述" align="center" prop="mapDesc" min-width="160" show-overflow-tooltip />
<el-table-column label="创建人" align="center" prop="createBy" />
<el-table-column label="创建时间" align="center" prop="createTime" />
2026-05-29 13:54:45 +08:00
<el-table-column label="操作" class-name="small-padding fixed-width" width="200" fixed="right">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
<!-- 添加或修改语料库对话框 -->
<el-dialog :title="title" v-model="open" width="580px" append-to-body>
2026-06-01 11:04:18 +08:00
<el-form ref="mapRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="地图名称" prop="mapName">
<el-input v-model="form.mapName" placeholder="请输入地图名称" />
2026-05-29 13:54:45 +08:00
</el-form-item>
2026-06-01 11:04:18 +08:00
<el-form-item label="地图描述" prop="mapDesc">
<el-input v-model="form.mapDesc" type="textarea" placeholder="请输入地图描述" />
2026-05-29 13:54:45 +08:00
</el-form-item>
2026-06-01 11:04:18 +08:00
<el-form-item label="选择机器人地图">
2026-05-29 13:54:45 +08:00
<el-col :span="11">
2026-06-01 11:04:18 +08:00
<el-form-item prop="mapSourceRobotId">
<el-select v-model="form.mapSourceRobotId" placeholder="请选择机器人" clearable style="width: 100%"
@change="robotChange">
<el-option v-for="robot in robotList" :key="robot.value" :label="robot.label" :value="robot.value" />
2026-05-29 13:54:45 +08:00
</el-select>
</el-form-item>
</el-col>
<el-col class="text-center" :span="2">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
2026-06-01 11:04:18 +08:00
<el-form-item prop="mapSourceName">
<el-select v-model="form.mapSourceName" placeholder="请选择地图" clearable style="width: 100%"
@change="mapChange">
<el-option v-for="map in mapList" :key="map.value" :label="map.label" :value="map.value" />
2026-05-29 13:54:45 +08:00
</el-select>
</el-form-item>
</el-col>
</el-form-item>
<div class="map-container">
<canvas id="mapCanvas"></canvas>
</div>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
2026-05-26 14:45:49 +08:00
</template>
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
<script setup name="Map">
2026-05-29 13:54:45 +08:00
import { useContainerHeight } from "@/hooks/tableHeight";
2026-06-01 11:04:18 +08:00
import { addMap, updateMap, deleteMap, getMapList } from "../../../api/inspection/map";
import { getRobotList } from "../../../api/inspection/robot";
2026-05-29 13:54:45 +08:00
import { onMounted } from "vue";
import {
2026-06-01 11:04:18 +08:00
worldToScreen,
screenToWorld,
drawGrid,
drawGridlines,
drawAdvancedAreaList,
drawAdvancedLineList,
drawNormalLines,
drawAdvancedCurveList,
drawAdvancedPointList,
drawMapBoundary,
buildOccupancyGridImage,
MobileRobot
2026-05-29 13:54:45 +08:00
} from '../cockpit/canvasUtils';
2026-06-01 11:04:18 +08:00
import { de } from "element-plus/es/locale/index.mjs";
import { ElMessage, ElMessageBox } from "element-plus";
2026-05-29 13:54:45 +08:00
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
2026-06-01 11:04:18 +08:00
const mapRef = ref();
const isNew = ref(true);
2026-05-29 13:54:45 +08:00
const tableDataList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const title = ref("");
const data = reactive({
form: {
2026-06-01 11:04:18 +08:00
mapName: "",
mapDesc: "",
mapSourceRobotId: "",
mapSourceName: "",
2026-05-29 13:54:45 +08:00
},
queryParams: {
pageNum: 1,
pageSize: 10,
2026-06-01 11:04:18 +08:00
mapDesc: null,
mapName: null,
2026-05-29 13:54:45 +08:00
},
rules: {
2026-06-01 11:04:18 +08:00
mapName: [
{ required: true, message: "请输入地图名称", trigger: "change" },
2026-05-29 13:54:45 +08:00
],
2026-06-01 11:04:18 +08:00
mapDesc: [
{ required: true, message: "请输入地图描述", trigger: "blur" },
]
2026-05-29 13:54:45 +08:00
},
});
const { queryParams, form, rules } = toRefs(data);
/** 查询列表 */
function getList() {
loading.value = true;
2026-06-01 11:04:18 +08:00
getMapList(queryParams.value).then((response) => {
tableDataList.value = response.rows;
total.value = response.total;
loading.value = false;
}).catch(() => {
loading.value = false;
}).finally(() => {
loading.value = false;
});
2026-05-29 13:54:45 +08:00
}
// 取消按钮
function cancel() {
open.value = false;
reset();
if (animationId) {
2026-06-01 11:04:18 +08:00
cancelAnimationFrame(animationId);
animationId = null;
2026-05-29 13:54:45 +08:00
}
window.removeEventListener('resize', resizeCanvasToContainer)
canvasCtx = null
}
// 表单重置
function reset() {
2026-06-01 11:04:18 +08:00
mapRef.value.resetFields();
2026-05-29 13:54:45 +08:00
form.value = {
2026-06-01 11:04:18 +08:00
mapName: "",
mapDesc: "",
mapSourceRobotId: "",
mapSourceName: "",
}
2026-05-29 13:54:45 +08:00
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
2026-06-01 11:04:18 +08:00
queryParams.value.mapName = null;
queryParams.value.mapDesc = null;
2026-05-29 13:54:45 +08:00
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
open.value = true;
2026-06-01 11:04:18 +08:00
isNew.value = true;
2026-05-29 13:54:45 +08:00
title.value = "添加地图";
setTimeout(() => {
init();
2026-06-01 11:04:18 +08:00
}, 300);
2026-05-29 13:54:45 +08:00
}
/** 修改按钮操作 */
function handleUpdate(row) {
2026-06-01 11:04:18 +08:00
form.value = { ...row };
isNew.value = false;
open.value = true;
title.value = "修改地图";
setTimeout(() => {
init();
if (form.value.mapSourceName) {
mapChange()
}
}, 300);
2026-05-29 13:54:45 +08:00
}
/** 提交按钮 */
function submitForm() {
2026-06-01 11:04:18 +08:00
mapRef.value.validate((valid) => {
if (valid) {
if (isNew.value) {
// 调用新增接口
addMap(form.value).then(() => {
ElMessage.success("地图添加成功!");
getList();
reset();
open.value = false;
});
} else {
// 调用修改接口
updateMap(form.value).then(() => {
ElMessage.success("地图修改成功!");
getList();
reset();
open.value = false;
});
}
}
});
2026-05-29 13:54:45 +08:00
}
/** 删除按钮操作 */
function handleDelete(row) {
2026-06-01 11:04:18 +08:00
ElMessageBox.confirm('确定要删除该地图吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
deleteMap(row.id).then(() => {
ElMessage.success("地图删除成功!");
getList();
});
}).catch(() => {
// 用户取消删除
});
2026-05-29 13:54:45 +08:00
}
const loadSourceMap = async (url) => {
2026-06-01 11:04:18 +08:00
const response = await fetch(url);
if (!response.ok) throw new Error(`加载失败:${response.status}`);
return response.text();
2026-05-29 13:54:45 +08:00
};
function parseSmapJson(rawJson) {
2026-06-01 11:04:18 +08:00
// ── 解析 header ──
const header = rawJson.header || rawJson; // 兼容 header 嵌套或平铺
const minimumPos = header.minPos || { x: 0, y: 0 }; // 地图左下角(世界坐标)
const maximumPos = header.maxPos || { x: 1, y: 1 }; // 地图右上角(世界坐标)
const resolution = header.resolution || 0.05; // 每像素代表的世界长度(米)
// ── 计算栅格尺寸 ──
const worldWidth = maximumPos.x - minimumPos.x; // 地图世界宽度(米)
const worldHeight = maximumPos.y - minimumPos.y; // 地图世界高度(米)
const gridWidthPx = Math.ceil(worldWidth / resolution) + 1; // 栅格像素宽度
const gridHeightPx = Math.ceil(worldHeight / resolution) + 1;// 栅格像素高度
// ── 构建统一数据结构 ──
const parsedMap = {
name: header.mapName || 'Untitled', // 地图名称
version: header.version || '', // 版本号
mapType: header.mapType || '', // 地图类型(如 "2D-Map"
resolution: resolution, // 分辨率(米/像素)
originX: minimumPos.x, // 地图左下角 X
originY: minimumPos.y, // 地图左下角 Y
maxX: maximumPos.x, // 地图右上角 X
maxY: maximumPos.y, // 地图右上角 Y
gridWidth: gridWidthPx, // 栅格宽度(像素)
gridHeight: gridHeightPx, // 栅格高度(像素)
// normalPosList: 自由空间点,直接拷贝
normalPositions: rawJson.normalPosList || [],
// normalLineList: 直接拷贝,每项含 {startPos:{x,y}, endPos:{x,y}}
normalLines: rawJson.normalLineList || [],
// advancedPointList: 直接拷贝,每项含 {className, instanceName, pos:{x,y}, dir, desc, property:[]}
advancedPoints: rawJson.advancedPointList || [],
// advancedLineList: 直接拷贝,每项含 {className, instanceName, line:{startPos, endPos}}
advancedLines: rawJson.advancedLineList || [],
// advancedCurveList: 直接拷贝,每项含 {className, instanceName, controlPos1, controlPos2, startPos, endPos}
advancedCurves: rawJson.advancedCurveList || [],
// advancedAreaList: 直接拷贝,每项含 {className, instanceName, dir, posGroup:[], attribute:{}}
advancedAreas: rawJson.advancedAreaList || [],
};
return parsedMap;
2026-05-29 13:54:45 +08:00
}
const applicationState = {
2026-06-01 11:04:18 +08:00
parsedMap: null, // 解析后的完整地图数据
gridOffscreen: null, // 栅格图像离屏Canvas
hoveredElement: null, // 当前悬停的元素 {kind, index}
animationTimeSeconds: 0, // 全局动画时间戳(秒),用于脉冲动画效果
camera: { // 相机参数
centerX: 0, // 相机中心的世界坐标 X
centerY: 0, // 相机中心的世界坐标 Y
pixelsPerMeter: 20, // 缩放:每米对应的屏幕像素数
},
canvasWidthPx: 0, // Canvas CSS 逻辑宽度(像素)
canvasHeightPx: 0, // Canvas CSS 逻辑高度(像素)
devicePixelRatio: 1, // 设备像素比高DPI屏幕 > 1
mouseState: { // 鼠标交互状态
screenX: 0, // 鼠标在 Canvas 上的像素 X
screenY: 0, // 鼠标在 Canvas 上的像素 Y
worldX: 0, // 鼠标对应的世界坐标 X
worldY: 0, // 鼠标对应的世界坐标 Y
isDragging: false, // 是否正在拖拽平移
},
isMapLoaded: false, // 地图加载完成标志,
layerVisibility: { // 各图层是否可见
grid: true, // 占据栅格底图
edges: true, // 墙壁边缘发光
gridlines: true, // 1米网格辅助线
normalLines: true, // normalLineList
advLines: true, // advancedLineList
curves: true, // advancedCurveList
points: true, // advancedPointList
areas: true, // advancedAreaList
}
2026-05-29 13:54:45 +08:00
};
let mapCanvas; // 主渲染画布 DOM 元素
let canvasCtx; // 2D 渲染上下文
const init = () => {
mapCanvas = document.getElementById('mapCanvas');
canvasCtx = mapCanvas.getContext('2d');
}
/**
* 根据容器尺寸和设备像素比调整 Canvas 大小
* 在窗口 resize 时自动调用
*/
function resizeCanvasToContainer() {
2026-06-01 11:04:18 +08:00
const containerRect = mapCanvas.parentElement.getBoundingClientRect(); // 获取容器实际尺寸
console.log('containerRect', containerRect)
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.style.width = applicationState.canvasWidthPx + 'px'; // CSS 显示宽度
mapCanvas.style.height = applicationState.canvasHeightPx + 'px'; // CSS 显示高度
2026-05-29 13:54:45 +08:00
}
async function loadMapIntoApplication(parsedMap) {
2026-06-01 11:04:18 +08:00
await new Promise(resolve => setTimeout(resolve, 30)); // 短暂等待让 UI 有机会刷新
// 保存地图数据到全局状态
applicationState.parsedMap = parsedMap;
// 构建栅格图像(带边缘高亮)
applicationState.gridOffscreen = buildOccupancyGridImage(parsedMap, applicationState.layerVisibility.edges);
// ── 设置相机初始位置(地图中心)──
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.min(fitScaleX, fitScaleY)); // 取较小值并留12%边距
// 标记加载完成并更新UI
applicationState.isMapLoaded = true;
2026-05-29 13:54:45 +08:00
}
const bindEvent = () => {
2026-06-01 11:04:18 +08:00
// ── 鼠标移动:更新坐标、拖拽平移、悬停检测 ──
mapCanvas.addEventListener('mousemove', (mouseEvent) => {
const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置
applicationState.mouseState.screenX = mouseEvent.clientX - canvasRect.left; // 鼠标在 Canvas 上的 X
applicationState.mouseState.screenY = mouseEvent.clientY - canvasRect.top; // 鼠标在 Canvas 上的 Y
// 反算世界坐标
const worldPos = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState);
applicationState.mouseState.worldX = worldPos.x;
applicationState.mouseState.worldY = worldPos.y;
// 如果正在拖拽,平移相机
if (applicationState.mouseState.isDragging) {
applicationState.camera.centerX -= mouseEvent.movementX / applicationState.camera.pixelsPerMeter; // 水平平移
applicationState.camera.centerY += mouseEvent.movementY / applicationState.camera.pixelsPerMeter; // 垂直平移Y翻转
}
});
// ── 鼠标按下:开始拖拽 ──
mapCanvas.addEventListener('mousedown', () => {
applicationState.mouseState.isDragging = true; // 标记拖拽开始
});
// ── 鼠标抬起:结束拖拽 ──
mapCanvas.addEventListener('mouseup', () => {
applicationState.mouseState.isDragging = false; // 标记拖拽结束
});
// ── 鼠标离开画布:结束拖拽并隐藏 Tooltip ──
mapCanvas.addEventListener('mouseleave', () => {
applicationState.mouseState.isDragging = false;
});
// ── 滚轮:缩放(向鼠标位置缩放)──
mapCanvas.addEventListener('wheel', (wheelEvent) => {
wheelEvent.preventDefault(); // 阻止页面滚动
const zoomFactor = wheelEvent.deltaY > 0 ? 0.88 : 1.12; // 向下滚动=缩小,向上滚动=放大
const mouseWorldBefore = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState); // 缩放前鼠标世界坐标
applicationState.camera.pixelsPerMeter = Math.max(0.5, Math.min(2000, applicationState.camera.pixelsPerMeter * zoomFactor)); // 应用缩放并钳制范围
const mouseWorldAfter = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState); // 缩放后鼠标世界坐标
// 调整相机中心,使鼠标指向的世界坐标保持不变
applicationState.camera.centerX += mouseWorldBefore.x - mouseWorldAfter.x;
applicationState.camera.centerY += mouseWorldBefore.y - mouseWorldAfter.y;
}, { passive: false }); // passive:false 允许 preventDefault
2026-05-29 13:54:45 +08:00
}
/**
* 加载 smap JSON 文件
*/
const loadUserFile = async () => {
2026-06-01 11:04:18 +08:00
try {
const filemapDesc = await loadSourceMap('/3.smap');
const rawJsonObject = JSON.parse(filemapDesc); // 解析 JSON
const parsedMap = parseSmapJson(rawJsonObject); // 转换为内部结构
await loadMapIntoApplication(parsedMap); // 加载到应用
} catch (parseError) {
alert('Parse error: ' + parseError.message); // 显示错误信息
}
2026-05-29 13:54:45 +08:00
}
// 声明一个变量保存 requestAnimationFrame 的 ID
let animationId = null;
function renderFrame(timestamp) {
2026-06-01 11:04:18 +08:00
// 更新动画时间
applicationState.animationTimeSeconds = (timestamp || 0) / 1000;
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 设置 Canvas 变换矩阵(考虑设备像素比)
canvasCtx.setTransform(applicationState.devicePixelRatio, 0, 0, applicationState.devicePixelRatio, 0, 0);
canvasCtx.clearRect(0, 0, applicationState.canvasWidthPx, applicationState.canvasHeightPx); // 清空画布
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 如果地图未加载,跳过绘制
if (!applicationState.isMapLoaded) {
animationId = requestAnimationFrame(renderFrame); // 继续请求下一帧
return;
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
const map = applicationState.parsedMap; // 当前地图数据
const layers = applicationState.layerVisibility; // 图层可见性
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 图层1: 占据栅格底图
if (layers.grid && applicationState.gridOffscreen) {
drawGrid(canvasCtx, map, applicationState);
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 图层2: 网格辅助线每1米一条
if (layers.gridlines) {
drawGridlines(canvasCtx, applicationState)
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 图层3: advancedAreaList半透明多边形区域
if (layers.areas) {
drawAdvancedAreaList(canvasCtx, map, applicationState)
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 图层4: normalLineList基础直线青色 #00e5ff
if (layers.normalLines) {
drawNormalLines(canvasCtx, map, applicationState);
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 图层5: advancedLineList高级线如 ForbiddenLine
if (layers.advLines) {
drawAdvancedLineList(canvasCtx, map, applicationState);
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 图层6: advancedCurveList贝塞尔曲线路径酸绿色 #76ff03
if (layers.curves) {
drawAdvancedCurveList(canvasCtx, map, applicationState);
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 图层7: advancedPointList高级点标记菱形
if (layers.points) {
drawAdvancedPointList(canvasCtx, map, applicationState);
}
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 地图边界虚线框
drawMapBoundary(canvasCtx, map, applicationState);
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
// 请求下一帧渲染
animationId = requestAnimationFrame(renderFrame);
}
const robotList = ref([])
const mapList = ref([])
const getRobotArr = () => {
getRobotList({
pageNum: 1,
pageSize: 10000,
}).then((response) => {
robotList.value = response.rows.map(item => ({
label: item.robotName,
value: item.id
}));
});
}
const getMapArr = (robotId) => {
// TODO: 这里可以根据 robotId 来过滤地图列表,目前接口没有提供这个功能,所以暂时获取全部地图
mapList.value = [
{ label: "地图1", value: "map1" },
{ label: "地图2", value: "map2" },
{ label: "地图3", value: "map3" },
]
}
const robotChange = (value) => {
form.value.mapSourceRobotId = value; // 更新选中的机器人ID
form.value.mapSourceName = ""; // 清空地图选择
if (canvasCtx) {
canvasCtx.clearRect(0, 0, applicationState.canvasWidthPx, applicationState.canvasHeightPx); // 清空画布
}
getMapArr(value);
2026-05-29 13:54:45 +08:00
}
const mapChange = () => {
2026-06-01 11:04:18 +08:00
loadUserFile();
2026-05-29 13:54:45 +08:00
2026-06-01 11:04:18 +08:00
if (animationId) {
cancelAnimationFrame(animationId);
2026-05-29 13:54:45 +08:00
}
2026-06-01 11:04:18 +08:00
animationId = requestAnimationFrame(renderFrame);
window.addEventListener('resize', resizeCanvasToContainer); // 监听窗口大小变化
resizeCanvasToContainer();
setTimeout(() => {
bindEvent()
}, 100)
}
2026-05-29 13:54:45 +08:00
onMounted(() => {
2026-06-01 11:04:18 +08:00
getList();
getRobotArr();
2026-05-29 13:54:45 +08:00
});
</script>
<style lang="scss" scoped>
.map-container {
width: 100%;
height: 300px;
border: 1px solid #ccc;
}
</style>