feat(map): 优化地图缩放逻辑及点位渲染
- 将设备管理地图最大缩放比例提升至15,支持更大范围缩放 - 修改地图缩放增量为按倍率缩放,提升缩放体验平滑度 - 新增根据地图宽高比计算默认缩放,消除宽扁地图显示过小问题 - 自动缩放时聚焦地图原点,提升用户定位便利性 - 调整地图点位渲染样式,统一圆圈大小,简化视觉效果,只用描边色区分 - 移除点位标签背景框绘制,减少视觉干扰 - 鼠标滚轮缩放时以光标为中心,缩放过程视野自动平移保持焦点 - 集成中心页面地图缩放支持按倍率调整和平滑过渡 - 优化保存巡检任务时进度条逻辑,取消模拟递增,调用完成立即完成进度 - 增强地图缩放动画过渡时长,从0.18秒提升至0.45秒 - 修复集成中心页面真实点位显示优化,起点终点显示标签,途经点隐藏标签
This commit is contained in:
@@ -269,8 +269,7 @@ const mapStageRef = ref<HTMLElement | null>(null);
|
|||||||
const mapCanvasRef = ref<HTMLCanvasElement | null>(null);
|
const mapCanvasRef = ref<HTMLCanvasElement | null>(null);
|
||||||
const suppressStageClick = ref(false);
|
const suppressStageClick = ref(false);
|
||||||
const minMapZoom = 0.6;
|
const minMapZoom = 0.6;
|
||||||
const maxMapZoom = 3;
|
const maxMapZoom = 15;
|
||||||
const mapZoomStep = 0.2;
|
|
||||||
const mapZoom = ref(1);
|
const mapZoom = ref(1);
|
||||||
const mapZoomPercent = computed(() => Math.round(mapZoom.value * 100));
|
const mapZoomPercent = computed(() => Math.round(mapZoom.value * 100));
|
||||||
|
|
||||||
@@ -338,29 +337,21 @@ const mapPointPalette: Record<
|
|||||||
{
|
{
|
||||||
stroke: string;
|
stroke: string;
|
||||||
glow: string;
|
glow: string;
|
||||||
labelBackground: string;
|
|
||||||
labelBorder: string;
|
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
业务点: {
|
业务点: {
|
||||||
stroke: "#66d7ff",
|
stroke: "#66d7ff",
|
||||||
glow: "rgba(102, 215, 255, 0.28)",
|
glow: "rgba(102, 215, 255, 0.28)",
|
||||||
labelBackground: "rgba(16, 74, 118, 0.92)",
|
|
||||||
labelBorder: "rgba(91, 208, 255, 0.18)",
|
|
||||||
},
|
},
|
||||||
途经点: {
|
途经点: {
|
||||||
stroke: "#9af7c8",
|
stroke: "#9af7c8",
|
||||||
glow: "rgba(154, 247, 200, 0.3)",
|
glow: "rgba(154, 247, 200, 0.3)",
|
||||||
labelBackground: "rgba(18, 90, 66, 0.94)",
|
|
||||||
labelBorder: "rgba(91, 208, 255, 0.18)",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedPointHighlight = {
|
const selectedPointHighlight = {
|
||||||
stroke: "#ffb26e",
|
stroke: "#ffb26e",
|
||||||
glow: "rgba(255, 178, 110, 0.32)",
|
glow: "rgba(255, 178, 110, 0.32)",
|
||||||
labelBackground: "rgba(122, 72, 18, 0.94)",
|
|
||||||
labelBorder: "rgba(255, 196, 118, 0.72)",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRosMapMeta: RosMapMetaState = {
|
const defaultRosMapMeta: RosMapMetaState = {
|
||||||
@@ -382,6 +373,10 @@ const mapConfigInputRef = ref<HTMLInputElement | null>(null);
|
|||||||
rosMapImage.onload = () => {
|
rosMapImage.onload = () => {
|
||||||
rosMapMeta.width = rosMapImage.naturalWidth || rosMapMeta.width;
|
rosMapMeta.width = rosMapImage.naturalWidth || rosMapMeta.width;
|
||||||
rosMapMeta.height = rosMapImage.naturalHeight || rosMapMeta.height;
|
rosMapMeta.height = rosMapImage.naturalHeight || rosMapMeta.height;
|
||||||
|
// 图片尺寸就绪后按实际宽高比重算默认缩放,避免宽扁地图默认显示过小
|
||||||
|
if (configDialogVisible.value) {
|
||||||
|
resetMapView();
|
||||||
|
}
|
||||||
scheduleMapCanvasRender();
|
scheduleMapCanvasRender();
|
||||||
};
|
};
|
||||||
rosMapImage.src = rosMapMeta.imageUrl;
|
rosMapImage.src = rosMapMeta.imageUrl;
|
||||||
@@ -1322,7 +1317,6 @@ async function openConfigDialog(device: DeviceRecord) {
|
|||||||
configuringDeviceId.value = device.id;
|
configuringDeviceId.value = device.id;
|
||||||
configDialogVisible.value = true;
|
configDialogVisible.value = true;
|
||||||
mapEditorMode.value = "normal";
|
mapEditorMode.value = "normal";
|
||||||
resetMapView();
|
|
||||||
|
|
||||||
if (!config.plans.some((plan) => plan.id === config.activePlanId)) {
|
if (!config.plans.some((plan) => plan.id === config.activePlanId)) {
|
||||||
config.activePlanId = config.plans[0]?.id ?? "";
|
config.activePlanId = config.plans[0]?.id ?? "";
|
||||||
@@ -1346,6 +1340,7 @@ async function openConfigDialog(device: DeviceRecord) {
|
|||||||
resetPlanForm();
|
resetPlanForm();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
attachMapResizeObserver();
|
attachMapResizeObserver();
|
||||||
|
resetMapView();
|
||||||
scheduleMapCanvasRender();
|
scheduleMapCanvasRender();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1518,27 +1513,80 @@ function setMapZoom(value: number) {
|
|||||||
scheduleMapCanvasRender();
|
scheduleMapCanvasRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按固定步长放大地图
|
// 按固定倍率放大地图,倍率越高放大步进越大
|
||||||
function zoomInMap() {
|
function zoomInMap() {
|
||||||
setMapZoom(mapZoom.value + mapZoomStep);
|
setMapZoom(mapZoom.value * 1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按固定步长缩小地图
|
// 按固定倍率缩小地图
|
||||||
function zoomOutMap() {
|
function zoomOutMap() {
|
||||||
setMapZoom(mapZoom.value - mapZoomStep);
|
setMapZoom(mapZoom.value / 1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将地图缩放比例和平移位置恢复为默认状态
|
// 将地图缩放比例和平移位置恢复为默认状态:宽高比自适应缩放并聚焦原点
|
||||||
function resetMapView() {
|
function resetMapView() {
|
||||||
mapPan.x = 0;
|
mapPan.x = 0;
|
||||||
mapPan.y = 0;
|
mapPan.y = 0;
|
||||||
setMapZoom(1);
|
setMapZoom(computeDefaultMapZoom());
|
||||||
|
focusMapOnOrigin();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理鼠标滚轮缩放地图
|
// 处理鼠标滚轮缩放地图
|
||||||
function handleMapWheel(event: WheelEvent) {
|
function handleMapWheel(event: WheelEvent) {
|
||||||
const zoomDirection = event.deltaY > 0 ? -1 : 1;
|
const zoomFactor = event.deltaY > 0 ? 1 / 1.1 : 1.1;
|
||||||
setMapZoom(mapZoom.value + zoomDirection * mapZoomStep);
|
setMapZoom(mapZoom.value * zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据地图宽高比计算默认缩放:常规比例地图保持完整显示,宽扁/高瘦地图放大让短边占满画布
|
||||||
|
function computeDefaultMapZoom() {
|
||||||
|
const rect = getMapStageRect();
|
||||||
|
|
||||||
|
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageRatio = rosMapImage.naturalWidth && rosMapImage.naturalHeight
|
||||||
|
? rosMapImage.naturalWidth / rosMapImage.naturalHeight
|
||||||
|
: 178 / 50;
|
||||||
|
const padding = 30;
|
||||||
|
const maxWidth = rect.width - padding * 2;
|
||||||
|
const maxHeight = rect.height - padding * 2;
|
||||||
|
|
||||||
|
if (maxWidth <= 0 || maxHeight <= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与 getRosMapFrame 的 fit 逻辑保持一致:完整显示时短边占比过小才放大到铺满画布
|
||||||
|
const fitHeight = maxWidth / imageRatio;
|
||||||
|
const fitWidth = maxHeight * imageRatio;
|
||||||
|
const shortSideRatio = Math.min(fitWidth / maxWidth, fitHeight / maxHeight);
|
||||||
|
|
||||||
|
if (shortSideRatio >= 0.6) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultZoom = fitHeight <= maxHeight
|
||||||
|
? maxHeight / fitHeight
|
||||||
|
: maxWidth / fitWidth;
|
||||||
|
|
||||||
|
return Number(defaultZoom.toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将地图视野平移到原点位置,使原点位于画布中心
|
||||||
|
function focusMapOnOrigin() {
|
||||||
|
const rect = getMapStageRect();
|
||||||
|
|
||||||
|
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originRatioX = (0 - rosMapMeta.origin[0]) / rosMapMeta.resolution / rosMapMeta.width;
|
||||||
|
const originRatioY = 1 - (0 - rosMapMeta.origin[1]) / rosMapMeta.resolution / rosMapMeta.height;
|
||||||
|
const frame = getRosMapFrame(rect);
|
||||||
|
|
||||||
|
// 原点位于画布中心时,平移量等于地图尺寸的一半减去原点偏移
|
||||||
|
mapPan.x = frame.width * (0.5 - originRatioX);
|
||||||
|
mapPan.y = frame.height * (0.5 - originRatioY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据 ROS 地图图片比例计算画布中的实际地图显示区域
|
// 根据 ROS 地图图片比例计算画布中的实际地图显示区域
|
||||||
@@ -1577,28 +1625,6 @@ function percentToStagePixels(x: number, y: number, rect: DOMRect) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function estimatePointLabelWidth(name: string) {
|
|
||||||
return Math.max(72, name.length * 12 + 18);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPointLabelFrame(point: PatrolPoint, rect: DOMRect) {
|
|
||||||
const pixelPoint = percentToStagePixels(point.x, point.y, rect);
|
|
||||||
const labelWidth = estimatePointLabelWidth(point.name);
|
|
||||||
const labelHeight = 24;
|
|
||||||
let left = pixelPoint.x + 18;
|
|
||||||
|
|
||||||
if (left + labelWidth > rect.width - 10) {
|
|
||||||
left = pixelPoint.x - labelWidth - 18;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
left,
|
|
||||||
top: pixelPoint.y - labelHeight / 2,
|
|
||||||
width: labelWidth,
|
|
||||||
height: labelHeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function findHitPoint(event: MouseEvent) {
|
function findHitPoint(event: MouseEvent) {
|
||||||
const rect = getMapStageRect();
|
const rect = getMapStageRect();
|
||||||
if (!rect) {
|
if (!rect) {
|
||||||
@@ -1617,45 +1643,11 @@ function findHitPoint(event: MouseEvent) {
|
|||||||
if (Math.hypot(deltaX, deltaY) <= 18) {
|
if (Math.hypot(deltaX, deltaY) <= 18) {
|
||||||
return point;
|
return point;
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelFrame = getPointLabelFrame(point, rect);
|
|
||||||
|
|
||||||
if (
|
|
||||||
pointerX >= labelFrame.left &&
|
|
||||||
pointerX <= labelFrame.left + labelFrame.width &&
|
|
||||||
pointerY >= labelFrame.top &&
|
|
||||||
pointerY <= labelFrame.top + labelFrame.height
|
|
||||||
) {
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawRoundRect(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
radius: number,
|
|
||||||
) {
|
|
||||||
const clampedRadius = Math.min(radius, width / 2, height / 2);
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(x + clampedRadius, y);
|
|
||||||
ctx.lineTo(x + width - clampedRadius, y);
|
|
||||||
ctx.quadraticCurveTo(x + width, y, x + width, y + clampedRadius);
|
|
||||||
ctx.lineTo(x + width, y + height - clampedRadius);
|
|
||||||
ctx.quadraticCurveTo(x + width, y + height, x + width - clampedRadius, y + height);
|
|
||||||
ctx.lineTo(x + clampedRadius, y + height);
|
|
||||||
ctx.quadraticCurveTo(x, y + height, x, y + height - clampedRadius);
|
|
||||||
ctx.lineTo(x, y + clampedRadius);
|
|
||||||
ctx.quadraticCurveTo(x, y, x + clampedRadius, y);
|
|
||||||
ctx.closePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMapCanvas() {
|
function renderMapCanvas() {
|
||||||
const canvas = mapCanvasRef.value;
|
const canvas = mapCanvasRef.value;
|
||||||
const rect = getMapStageRect();
|
const rect = getMapStageRect();
|
||||||
@@ -1770,17 +1762,6 @@ function renderMapCanvas() {
|
|||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.arc(originPoint.x, originPoint.y, 4, 0, Math.PI * 2);
|
context.arc(originPoint.x, originPoint.y, 4, 0, Math.PI * 2);
|
||||||
context.fill();
|
context.fill();
|
||||||
drawRoundRect(context, originPoint.x + 12, originPoint.y - 16, 126, 32, 8);
|
|
||||||
context.fillStyle = "rgba(72, 42, 13, 0.92)";
|
|
||||||
context.strokeStyle = "rgba(255, 196, 118, 0.72)";
|
|
||||||
context.lineWidth = 1;
|
|
||||||
context.fill();
|
|
||||||
context.stroke();
|
|
||||||
context.fillStyle = "#fff5df";
|
|
||||||
context.font = '600 12px "Microsoft YaHei", sans-serif';
|
|
||||||
context.textAlign = "left";
|
|
||||||
context.textBaseline = "middle";
|
|
||||||
context.fillText("原点 (0.00, 0.00)", originPoint.x + 22, originPoint.y);
|
|
||||||
context.restore();
|
context.restore();
|
||||||
|
|
||||||
if (activeRoundPoints.value.length > 1) {
|
if (activeRoundPoints.value.length > 1) {
|
||||||
@@ -1813,44 +1794,28 @@ function renderMapCanvas() {
|
|||||||
const palette = mapPointPalette[point.type];
|
const palette = mapPointPalette[point.type];
|
||||||
const selected = currentConfig.value?.selectedPointIds.includes(point.id) ?? false;
|
const selected = currentConfig.value?.selectedPointIds.includes(point.id) ?? false;
|
||||||
const highlightPalette = selected ? selectedPointHighlight : palette;
|
const highlightPalette = selected ? selectedPointHighlight : palette;
|
||||||
const labelFrame = getPointLabelFrame(point, rect);
|
|
||||||
|
|
||||||
context.save();
|
context.save();
|
||||||
context.fillStyle = selected ? highlightPalette.glow : "rgba(255, 255, 255, 0.02)";
|
context.fillStyle = selected ? highlightPalette.glow : "rgba(255, 255, 255, 0.02)";
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.arc(pixelPoint.x, pixelPoint.y, selected ? 18 : 14, 0, Math.PI * 2);
|
context.arc(pixelPoint.x, pixelPoint.y, selected ? 14 : 10, 0, Math.PI * 2);
|
||||||
context.fill();
|
context.fill();
|
||||||
|
|
||||||
context.fillStyle = "rgba(5, 18, 38, 0.94)";
|
context.fillStyle = "rgba(5, 18, 38, 0.94)";
|
||||||
context.strokeStyle = highlightPalette.stroke;
|
context.strokeStyle = highlightPalette.stroke;
|
||||||
context.lineWidth = selected ? 3 : 2;
|
context.lineWidth = selected ? 3 : 2;
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.arc(pixelPoint.x, pixelPoint.y, 11, 0, Math.PI * 2);
|
context.arc(pixelPoint.x, pixelPoint.y, 8, 0, Math.PI * 2);
|
||||||
context.fill();
|
context.fill();
|
||||||
context.shadowColor = highlightPalette.glow;
|
context.shadowColor = highlightPalette.glow;
|
||||||
context.shadowBlur = selected ? 18 : 12;
|
context.shadowBlur = selected ? 18 : 12;
|
||||||
context.stroke();
|
context.stroke();
|
||||||
|
|
||||||
drawRoundRect(context, labelFrame.left, labelFrame.top, labelFrame.width, labelFrame.height, 8);
|
// 所有点位统一圆圈大小,仅以描边颜色区分类型,内部标注序号
|
||||||
context.fillStyle = selected
|
|
||||||
? highlightPalette.labelBackground
|
|
||||||
: "rgba(4, 19, 39, 0.82)";
|
|
||||||
context.strokeStyle = selected
|
|
||||||
? highlightPalette.labelBorder
|
|
||||||
: "rgba(91, 208, 255, 0.18)";
|
|
||||||
context.lineWidth = 1;
|
|
||||||
context.shadowBlur = 0;
|
|
||||||
context.fill();
|
|
||||||
context.stroke();
|
|
||||||
|
|
||||||
context.fillStyle = "#eefcff";
|
context.fillStyle = "#eefcff";
|
||||||
context.font = '700 11px "Microsoft YaHei", sans-serif';
|
context.font = '700 10px "Microsoft YaHei", sans-serif';
|
||||||
context.textAlign = "center";
|
context.textAlign = "center";
|
||||||
context.fillText(String(index + 1), pixelPoint.x, pixelPoint.y + 0.5);
|
context.fillText(String(index + 1), pixelPoint.x, pixelPoint.y + 0.5);
|
||||||
|
|
||||||
context.font = '500 12px "Microsoft YaHei", sans-serif';
|
|
||||||
context.textAlign = "left";
|
|
||||||
context.fillText(point.name, labelFrame.left + 8, labelFrame.top + labelFrame.height / 2 + 0.5);
|
|
||||||
context.restore();
|
context.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2462,14 +2427,8 @@ async function saveConfigDialog() {
|
|||||||
buildInspectionTaskPayload(currentConfiguredDevice.value, currentConfig.value),
|
buildInspectionTaskPayload(currentConfiguredDevice.value, currentConfig.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
saveProgressTimer = window.setInterval(() => {
|
// 保存接口真实调用完成后,进度条立即走完,不再模拟递增动画
|
||||||
const step = saveProgressValue.value < 72 ? 12 : saveProgressValue.value < 90 ? 6 : 3;
|
|
||||||
saveProgressValue.value = Math.min(100, saveProgressValue.value + step);
|
|
||||||
|
|
||||||
if (saveProgressValue.value >= 100) {
|
|
||||||
finishSaveProgress();
|
finishSaveProgress();
|
||||||
}
|
|
||||||
}, 180);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
stopSaveProgressTimers();
|
stopSaveProgressTimers();
|
||||||
@@ -3800,7 +3759,7 @@ watch(
|
|||||||
rgba(24, 102, 180, 0.92) 0%,
|
rgba(24, 102, 180, 0.92) 0%,
|
||||||
rgba(35, 158, 215, 0.9) 100%);
|
rgba(35, 158, 215, 0.9) 100%);
|
||||||
box-shadow: 0 0 18px rgba(52, 177, 255, 0.26);
|
box-shadow: 0 0 18px rgba(52, 177, 255, 0.26);
|
||||||
transition: width 0.18s ease;
|
transition: width 0.45s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-progress-value {
|
.save-progress-value {
|
||||||
|
|||||||
@@ -668,7 +668,7 @@ const realRoutePath = computed(() => routeMapPoints.value
|
|||||||
.join(" "));
|
.join(" "));
|
||||||
const realCheckPoints = computed(() => routeMapPoints.value.map((point, index) => ({
|
const realCheckPoints = computed(() => routeMapPoints.value.map((point, index) => ({
|
||||||
...point,
|
...point,
|
||||||
label: index === 0 ? "起点" : index === routeMapPoints.value.length - 1 ? "终点" : "途经点",
|
label: index === 0 ? "起点" : index === routeMapPoints.value.length - 1 ? "终点" : "",
|
||||||
})));
|
})));
|
||||||
const inspectionRunning = computed(() => currentInspection.value?.status === 0);
|
const inspectionRunning = computed(() => currentInspection.value?.status === 0);
|
||||||
const inspectionStatusText = computed(() => inspectionRunning.value ? "巡检中" : "空闲");
|
const inspectionStatusText = computed(() => inspectionRunning.value ? "巡检中" : "空闲");
|
||||||
@@ -1270,9 +1270,10 @@ function switchDriveMode(mode: DriveMode) {
|
|||||||
activeDriveMode.value = mode;
|
activeDriveMode.value = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调整真实 ROS 地图整体缩放,不影响底部状态浮层
|
// 调整真实 ROS 地图整体缩放,不影响底部状态浮层,倍率越高放大步进越大
|
||||||
function changeMapScale(delta: number) {
|
function changeMapScale(delta: number) {
|
||||||
mapScale.value = Math.min(3, Math.max(0.55, Number((mapScale.value + delta).toFixed(2))));
|
const factor = delta > 0 ? 1.1 : 1 / 1.1;
|
||||||
|
mapScale.value = Math.min(15, Math.max(0.55, Number((mapScale.value * factor).toFixed(2))));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetMapScale() {
|
function resetMapScale() {
|
||||||
@@ -1280,10 +1281,34 @@ function resetMapScale() {
|
|||||||
mapOffset.value = { x: 0, y: 0 };
|
mapOffset.value = { x: 0, y: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鼠标滚轮缩放地图内容,按住拖拽平移地图视野
|
// 鼠标滚轮以光标为缩放中心缩放地图内容,按住拖拽平移地图视野
|
||||||
function handleMapWheel(event: WheelEvent) {
|
function handleMapWheel(event: WheelEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
changeMapScale(event.deltaY > 0 ? -0.05 : 0.05);
|
|
||||||
|
if (!rosMap.value || !navMapSvgRef.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const factor = event.deltaY > 0 ? 1 / 1.1 : 1.1;
|
||||||
|
const nextScale = Math.min(15, Math.max(0.55, Number((mapScale.value * factor).toFixed(2))));
|
||||||
|
|
||||||
|
if (nextScale === mapScale.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缩放前后保持光标下的地图点位置不动,需要同步平移视野
|
||||||
|
const rect = navMapSvgRef.value.getBoundingClientRect();
|
||||||
|
const pointerX = (event.clientX - rect.left) * (rosMap.value.width / rect.width);
|
||||||
|
const pointerY = (event.clientY - rect.top) * (rosMap.value.height / rect.height);
|
||||||
|
const centerX = rosMap.value.width / 2;
|
||||||
|
const centerY = rosMap.value.height / 2;
|
||||||
|
const keepRatio = 1 - nextScale / mapScale.value;
|
||||||
|
|
||||||
|
mapOffset.value = {
|
||||||
|
x: mapOffset.value.x + (pointerX - centerX) * keepRatio,
|
||||||
|
y: mapOffset.value.y + (pointerY - centerY) * keepRatio,
|
||||||
|
};
|
||||||
|
mapScale.value = nextScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startMapDrag(event: PointerEvent) {
|
function startMapDrag(event: PointerEvent) {
|
||||||
@@ -1539,10 +1564,10 @@ function getStatusClass(status: DeviceItem["status"]) {
|
|||||||
<path v-if="realRoutePath" class="route-glow" :d="realRoutePath" />
|
<path v-if="realRoutePath" class="route-glow" :d="realRoutePath" />
|
||||||
<path v-if="realRoutePath" class="route-line" :d="realRoutePath" />
|
<path v-if="realRoutePath" class="route-line" :d="realRoutePath" />
|
||||||
<g class="checkpoint-layer">
|
<g class="checkpoint-layer">
|
||||||
<g v-for="point in realCheckPoints" :key="`${point.label}-${point.x}-${point.y}`" class="checkpoint"
|
<g v-for="(point, index) in realCheckPoints" :key="index" class="checkpoint"
|
||||||
:transform="`translate(${point.x}, ${point.y})`">
|
:transform="`translate(${point.x}, ${point.y})`">
|
||||||
<circle r="3"></circle>
|
<circle r="3"></circle>
|
||||||
<text x="7" y="2.5">{{ point.label }}</text>
|
<text v-if="point.label" x="7" y="2.5">{{ point.label }}</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g v-if="robotMapPoint" class="robot-layer real-robot-layer"
|
<g v-if="robotMapPoint" class="robot-layer real-robot-layer"
|
||||||
|
|||||||
Reference in New Issue
Block a user