diff --git a/src/components/LabCanvas/DiagramCanvas.vue b/src/components/LabCanvas/DiagramCanvas.vue index 7465b2d..5c4a282 100644 --- a/src/components/LabCanvas/DiagramCanvas.vue +++ b/src/components/LabCanvas/DiagramCanvas.vue @@ -40,7 +40,7 @@ ref="canvas" class="diagram-canvas" :style="{ - transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`, + transform: `translate(${componentManager.canvasPosition.x}px, ${componentManager.canvasPosition.y}px) scale(${componentManager.canvasScale.value})`, }" > @@ -167,7 +167,9 @@ class="absolute bottom-4 right-4 bg-base-100 px-3 py-1.5 rounded-md shadow-md z-20" style="opacity: 0.9" > - {{ Math.round(scale * 100) }}% + {{ Math.round(componentManager?.canvasScale.value * 100) }}% @@ -243,8 +245,6 @@ const alertStore = useAlertStore(); // --- 画布状态 --- const canvasContainer = ref(null); const canvas = ref(null); -const position = reactive({ x: 0, y: 0 }); -const scale = ref(1); const isDragging = ref(false); const isMiddleDragging = ref(false); const dragStart = reactive({ x: 0, y: 0 }); @@ -265,11 +265,6 @@ const diagramData = ref({ connections: [], }); -// 组件引用跟踪(保留以便向后兼容) -const componentRefs = computed( - () => componentManager?.componentRefs.value || {}, -); - // 计算属性:从 diagramData 中提取组件列表,并按index属性排序 const diagramParts = computed(() => { // 克隆原始数组以避免直接修改原始数据 @@ -423,25 +418,13 @@ function onZoom(e: WheelEvent) { const mouseX = e.clientX - containerRect.left; const mouseY = e.clientY - containerRect.top; - // 计算鼠标在画布坐标系中的位置 - const mouseXCanvas = (mouseX - position.x) / scale.value; - const mouseYCanvas = (mouseY - position.y) / scale.value; - // 计算缩放值 const zoomFactor = 1.1; // 每次放大/缩小10% const direction = e.deltaY > 0 ? -1 : 1; + const finalZoomFactor = direction > 0 ? zoomFactor : 1 / zoomFactor; - // 计算新的缩放值 - let newScale = - direction > 0 ? scale.value * zoomFactor : scale.value / zoomFactor; - newScale = Math.max(MIN_SCALE, Math.min(newScale, MAX_SCALE)); - - // 计算新的位置,使鼠标指针位置在缩放前后保持不变 - position.x = mouseX - mouseXCanvas * newScale; - position.y = mouseY - mouseYCanvas * newScale; - - // 更新缩放值 - scale.value = newScale; + // 使用componentManager的缩放方法 + componentManager?.zoomAtPosition(mouseX, mouseY, finalZoomFactor); } // --- 画布交互逻辑 --- @@ -472,8 +455,10 @@ function startDrag(e: MouseEvent) { isDragging.value = true; isMiddleDragging.value = false; - dragStart.x = e.clientX - position.x; - dragStart.y = e.clientY - position.y; + const currentPosition = componentManager?.getCanvasPosition(); + if (!currentPosition) return; + dragStart.x = e.clientX - currentPosition.x; + dragStart.y = e.clientY - currentPosition.y; isDragEventActive.value = true; e.preventDefault(); @@ -487,8 +472,10 @@ function startMiddleDrag(e: MouseEvent) { isDragging.value = false; draggingComponentId.value = null; - dragStart.x = e.clientX - position.x; - dragStart.y = e.clientY - position.y; + const currentPosition = componentManager?.getCanvasPosition(); + if (!currentPosition) return; + dragStart.x = e.clientX - currentPosition.x; + dragStart.y = e.clientY - currentPosition.y; isDragEventActive.value = true; e.preventDefault(); @@ -498,8 +485,11 @@ function startMiddleDrag(e: MouseEvent) { function onDrag(e: MouseEvent) { if (!isDragging.value && !isMiddleDragging.value) return; - position.x = e.clientX - dragStart.x; - position.y = e.clientY - dragStart.y; + const newX = e.clientX - dragStart.x; + const newY = e.clientY - dragStart.y; + + // 使用componentManager设置画布位置 + componentManager?.setCanvasPosition(newX, newY); } // 停止拖拽画布 @@ -551,15 +541,16 @@ function startComponentDrag(e: MouseEvent, component: DiagramPart) { if (!canvasContainer.value) return; const containerRect = canvasContainer.value.getBoundingClientRect(); - // 计算鼠标在画布坐标系中的位置 - const mouseX_canvas = - (e.clientX - containerRect.left - position.x) / scale.value; - const mouseY_canvas = - (e.clientY - containerRect.top - position.y) / scale.value; + // 使用componentManager的屏幕坐标转画布坐标方法 + const mouseCanvasPos = componentManager?.screenToCanvas( + e.clientX - containerRect.left, + e.clientY - containerRect.top, + ); + if (!mouseCanvasPos) return; // 计算鼠标相对于组件左上角的偏移量 - componentDragOffset.x = mouseX_canvas - component.x; - componentDragOffset.y = mouseY_canvas - component.y; + componentDragOffset.x = mouseCanvasPos.x - component.x; + componentDragOffset.y = mouseCanvasPos.y - component.y; // 激活组件拖拽事件监听 isComponentDragEventActive.value = true; @@ -575,15 +566,16 @@ function onComponentDrag(e: MouseEvent) { const containerRect = canvasContainer.value.getBoundingClientRect(); - // 计算鼠标在画布坐标系中的位置 - const mouseX_canvas = - (e.clientX - containerRect.left - position.x) / scale.value; - const mouseY_canvas = - (e.clientY - containerRect.top - position.y) / scale.value; + // 使用componentManager的屏幕坐标转画布坐标方法 + const mouseCanvasPos = componentManager?.screenToCanvas( + e.clientX - containerRect.left, + e.clientY - containerRect.top, + ); + if (!mouseCanvasPos) return; // 计算组件新位置 - const newX = mouseX_canvas - componentDragOffset.x; - const newY = mouseY_canvas - componentDragOffset.y; + const newX = mouseCanvasPos.x - componentDragOffset.x; + const newY = mouseCanvasPos.y - componentDragOffset.y; // 获取当前拖动的组件 const draggedComponent = diagramParts.value.find( @@ -671,9 +663,19 @@ function updateComponentProp( function updateMousePosition(e: MouseEvent) { if (!canvasContainer.value) return; const containerRect = canvasContainer.value.getBoundingClientRect(); - mousePosition.x = (e.clientX - containerRect.left - position.x) / scale.value; - mousePosition.y = (e.clientY - containerRect.top - position.y) / scale.value; -} // 处理引脚点击 + + // 使用componentManager的屏幕坐标转画布坐标方法 + const canvasPos = componentManager?.screenToCanvas( + e.clientX - containerRect.left, + e.clientY - containerRect.top, + ); + if (!canvasPos) return; + + mousePosition.x = canvasPos.x; + mousePosition.y = canvasPos.y; +} + +// 处理引脚点击 function handlePinClick(componentId: string, pinInfo: any, event: MouseEvent) { if (!canvasContainer.value) return; updateMousePosition(event); @@ -1029,8 +1031,8 @@ onMounted(async () => { saveDiagramData(data); emit("diagram-updated", data); }, - getCanvasPosition: () => ({ x: position.x, y: position.y }), - getScale: () => scale.value, + getCanvasPosition: () => componentManager.getCanvasPosition(), + getScale: () => componentManager.getCanvasScale(), $el: canvasContainer.value, }; componentManager.setCanvasRef(canvasAPI); @@ -1060,11 +1062,13 @@ onMounted(async () => { } catch (error) { console.error("加载图表数据失败:", error); } - // 初始化中心位置 - if (canvasContainer.value) { + + // 初始化中心位置 - 使用componentManager设置 + if (canvasContainer.value && componentManager) { // 修改为将画布中心点放在容器中心点 - position.x = canvasContainer.value.clientWidth / 2 - 5000; // 画布宽度的一半 - position.y = canvasContainer.value.clientHeight / 2 - 5000; // 画布高度的一半 + const centerX = canvasContainer.value.clientWidth / 2 - 5000; // 画布宽度的一半 + const centerY = canvasContainer.value.clientHeight / 2 - 5000; // 画布高度的一半 + componentManager.setCanvasPosition(centerX, centerY); } }); @@ -1102,40 +1106,6 @@ defineExpose({ // 基本数据操作 getDiagramData: () => diagramData.value, updateDiagramDataDirectly, - setDiagramData: (data: DiagramData) => { - // 检查组件是否仍然挂载 - if (!document.body.contains(canvasContainer.value)) { - return; // 如果组件已经卸载,不执行后续操作 - } - - isLoading.value = true; - - // 使用requestAnimationFrame确保UI更新 - window.requestAnimationFrame(() => { - // 再次检查组件是否仍然挂载 - if (!document.body.contains(canvasContainer.value)) { - return; // 如果组件已经卸载,不执行后续操作 - } - - diagramData.value = data; - saveDiagramData(data); - - // 发出diagram-updated事件 - emit("diagram-updated", data); - - // 短暂延迟后结束加载状态,以便UI能更新 - setTimeout(() => { - // 检查组件是否仍然挂载 - if (document.body.contains(canvasContainer.value)) { - isLoading.value = false; - } - }, 200); - }); - }, - - // 画布状态 - getCanvasPosition: () => ({ x: position.x, y: position.y }), - getScale: () => scale.value, }); // 监视器 - 当图表数据更改时保存 @@ -1241,15 +1211,6 @@ watch( display: none; } -/* 为黑暗模式设置不同的网格线颜色 */ -/* :root[data-theme="dark"] .diagram-container { - background-image: - linear-gradient(to right, rgba(200, 200, 200, 0.1) 1px, transparent 1px), - linear-gradient(to bottom, rgba(200, 200, 200, 0.1) 1px, transparent 1px), - linear-gradient(to right, rgba(180, 180, 180, 0.15) 100px, transparent 100px), - linear-gradient(to bottom, rgba(180, 180, 180, 0.15) 100px, transparent 100px); -} */ - /* 深度选择器 - 默认阻止SVG内部元素的鼠标事件,但允许SVG本身和特定交互元素 */ .component-wrapper :deep(svg) { pointer-events: auto; diff --git a/src/components/LabCanvas/composable/componentManager.ts b/src/components/LabCanvas/composable/componentManager.ts index de7489c..36c0c73 100644 --- a/src/components/LabCanvas/composable/componentManager.ts +++ b/src/components/LabCanvas/composable/componentManager.ts @@ -1,4 +1,4 @@ -import { ref, shallowRef, computed } from "vue"; +import { ref, shallowRef, computed, reactive } from "vue"; import { createInjectionState } from "@vueuse/core"; import type { DiagramData, DiagramPart } from "./diagramManager"; import type { PropertyConfig } from "@/components/equipments/componentConfig"; @@ -27,6 +27,10 @@ const [useProvideComponentManager, useComponentManager] = createInjectionState( const selectedComponentConfig = shallowRef<{ props: PropertyConfig[] } | null>(null); const diagramCanvas = ref(null); const componentRefs = ref>({}); + + // 新增:直接管理canvas的位置和缩放 + const canvasPosition = reactive({ x: 0, y: 0 }); + const canvasScale = ref(1); // 计算当前选中的组件数据 const selectedComponentData = computed(() => { @@ -40,6 +44,104 @@ const [useProvideComponentManager, useComponentManager] = createInjectionState( return null; }); + // --- Canvas 控制方法 --- + + /** + * 设置canvas位置 + */ + function setCanvasPosition(x: number, y: number) { + canvasPosition.x = x; + canvasPosition.y = y; + } + + /** + * 更新canvas位置(相对偏移) + */ + function updateCanvasPosition(deltaX: number, deltaY: number) { + canvasPosition.x += deltaX; + canvasPosition.y += deltaY; + } + + /** + * 设置canvas缩放 + */ + function setCanvasScale(scale: number) { + canvasScale.value = Math.max(0.2, Math.min(scale, 10.0)); + } + + /** + * 获取canvas位置 + */ + function getCanvasPosition() { + return { x: canvasPosition.x, y: canvasPosition.y }; + } + + /** + * 获取canvas缩放 + */ + function getCanvasScale() { + return canvasScale.value; + } + + /** + * 缩放到指定位置(以鼠标位置为中心) + */ + function zoomAtPosition(mouseX: number, mouseY: number, zoomFactor: number) { + // 计算鼠标在画布坐标系中的位置 + const mouseXCanvas = (mouseX - canvasPosition.x) / canvasScale.value; + const mouseYCanvas = (mouseY - canvasPosition.y) / canvasScale.value; + + // 计算新的缩放值 + const newScale = Math.max(0.2, Math.min(canvasScale.value * zoomFactor, 10.0)); + + // 计算新的位置,使鼠标指针位置在缩放前后保持不变 + canvasPosition.x = mouseX - mouseXCanvas * newScale; + canvasPosition.y = mouseY - mouseYCanvas * newScale; + canvasScale.value = newScale; + + return { scale: newScale, position: { x: canvasPosition.x, y: canvasPosition.y } }; + } + + /** + * 将屏幕坐标转换为画布坐标 + */ + function screenToCanvas(screenX: number, screenY: number) { + return { + x: (screenX - canvasPosition.x) / canvasScale.value, + y: (screenY - canvasPosition.y) / canvasScale.value + }; + } + + /** + * 将画布坐标转换为屏幕坐标 + */ + function canvasToScreen(canvasX: number, canvasY: number) { + return { + x: canvasX * canvasScale.value + canvasPosition.x, + y: canvasY * canvasScale.value + canvasPosition.y + }; + } + + /** + * 居中显示指定区域 + */ + function centerView(bounds: { x: number, y: number, width: number, height: number }, containerWidth: number, containerHeight: number) { + // 计算合适的缩放比例 + const scaleX = containerWidth / bounds.width; + const scaleY = containerHeight / bounds.height; + const newScale = Math.min(scaleX, scaleY, 1) * 0.8; // 留一些边距 + + // 计算居中位置 + const centerX = bounds.x + bounds.width / 2; + const centerY = bounds.y + bounds.height / 2; + + canvasScale.value = newScale; + canvasPosition.x = containerWidth / 2 - centerX * newScale; + canvasPosition.y = containerHeight / 2 - centerY * newScale; + + return { scale: newScale, position: { x: canvasPosition.x, y: canvasPosition.y } }; + } + // --- 组件模块管理 --- /** @@ -111,23 +213,17 @@ const [useProvideComponentManager, useComponentManager] = createInjectionState( } console.log(`组件模块加载成功: ${componentData.type}`, componentModule); - // 获取画布位置信息 + // 使用内部管理的位置和缩放信息 let position = { x: 100, y: 100 }; - let scale = 1; try { - if (canvasInstance.getCanvasPosition && canvasInstance.getScale) { - position = canvasInstance.getCanvasPosition(); - scale = canvasInstance.getScale(); + const canvasContainer = canvasInstance.$el as HTMLElement; + if (canvasContainer) { + const viewportWidth = canvasContainer.clientWidth; + const viewportHeight = canvasContainer.clientHeight; - const canvasContainer = canvasInstance.$el as HTMLElement; - if (canvasContainer) { - const viewportWidth = canvasContainer.clientWidth; - const viewportHeight = canvasContainer.clientHeight; - - position.x = (viewportWidth / 2 - position.x) / scale; - position.y = (viewportHeight / 2 - position.y) / scale; - } + position.x = (viewportWidth / 2 - canvasPosition.x) / canvasScale.value; + position.y = (viewportHeight / 2 - canvasPosition.y) / canvasScale.value; } } catch (error) { console.error("获取画布位置时出错:", error); @@ -205,20 +301,16 @@ const [useProvideComponentManager, useComponentManager] = createInjectionState( const idPrefix = `template-${Date.now()}-`; if (templateData.template?.parts) { - // 获取视口中心位置 + // 使用内部管理的位置和缩放信息获取视口中心位置 let viewportCenter = { x: 300, y: 200 }; try { - if (canvasInstance.getCanvasPosition && canvasInstance.getScale) { - const position = canvasInstance.getCanvasPosition(); - const scale = canvasInstance.getScale(); - const canvasContainer = canvasInstance.$el as HTMLElement; - - if (canvasContainer) { - const viewportWidth = canvasContainer.clientWidth; - const viewportHeight = canvasContainer.clientHeight; - viewportCenter.x = (viewportWidth / 2 - position.x) / scale; - viewportCenter.y = (viewportHeight / 2 - position.y) / scale; - } + const canvasContainer = canvasInstance.$el as HTMLElement; + + if (canvasContainer) { + const viewportWidth = canvasContainer.clientWidth; + const viewportHeight = canvasContainer.clientHeight; + viewportCenter.x = (viewportWidth / 2 - canvasPosition.x) / canvasScale.value; + viewportCenter.y = (viewportHeight / 2 - canvasPosition.y) / canvasScale.value; } } catch (error) { console.error("获取视口中心位置时出错:", error); @@ -527,29 +619,6 @@ const [useProvideComponentManager, useComponentManager] = createInjectionState( } } - /** - * 获取画布位置和缩放信息 - */ - function getCanvasInfo() { - const canvasInstance = diagramCanvas.value; - if (!canvasInstance) return { position: { x: 0, y: 0 }, scale: 1 }; - - const position = canvasInstance.getCanvasPosition ? canvasInstance.getCanvasPosition() : { x: 0, y: 0 }; - const scale = canvasInstance.getScale ? canvasInstance.getScale() : 1; - - return { position, scale }; - } - - /** - * 显示通知 - */ - function showToast(message: string, type: "success" | "error" | "info" = "info") { - const canvasInstance = diagramCanvas.value; - if (canvasInstance && canvasInstance.showToast) { - canvasInstance.showToast(message, type); - } - } - /** * 获取组件定义 */ @@ -619,6 +688,10 @@ const [useProvideComponentManager, useComponentManager] = createInjectionState( selectedComponentConfig, componentRefs, + // Canvas控制状态 + canvasPosition, + canvasScale, + // 方法 loadComponentModule, preloadComponentModules, @@ -634,11 +707,20 @@ const [useProvideComponentManager, useComponentManager] = createInjectionState( getComponentRef, getDiagramData, updateDiagramData, - getCanvasInfo, - showToast, getComponentDefinition, prepareComponentProps, initialize, + + // Canvas控制方法 + setCanvasPosition, + updateCanvasPosition, + setCanvasScale, + getCanvasPosition, + getCanvasScale, + zoomAtPosition, + screenToCanvas, + canvasToScreen, + centerView, }; } );