Przeglądaj źródła

fix: 修改节点自身连接

jiaxing.liao 1 miesiąc temu
rodzic
commit
2953971909

+ 48 - 6
packages/workflow/src/components/Canvas.vue

@@ -6,7 +6,14 @@ import type {
 	CanvasNodeMoveEvent,
 	IWorkflowNode
 } from '../Interface'
-import type { NodeMouseEvent, Connection, NodeDragEvent } from '@vue-flow/core'
+import type {
+	NodeMouseEvent,
+	Connection,
+	NodeDragEvent,
+	CoordinateExtent,
+	CoordinateExtentRange,
+	ValidConnectionFunc
+} from '@vue-flow/core'
 
 import { ref, computed, toRef } from 'vue'
 import { VueFlow, useVueFlow, MarkerType } from '@vue-flow/core'
@@ -87,13 +94,16 @@ const props = withDefaults(
 		id?: string
 		nodes: IWorkflow['nodes']
 		edges: IWorkflow['edges']
+		hideChildNode?: boolean
 		// 是否隐藏带parentId的节点
 		hideChildNode?: boolean
 		readOnly?: boolean
 		nodeMap: Record<string, any>
 		showControlBar?: boolean
 		zoomToFit?: boolean
-	}>(),
+		translateExtent?: CoordinateExtent
+		nodeExtent?: CoordinateExtent | CoordinateExtentRange
+		}>(),
 	{
 		id: 'canvas',
 		readOnly: false,
@@ -108,6 +118,32 @@ const getNodes = computed(() =>
 	props.hideChildNode ? props.nodes.filter((node) => !node?.parentId) : props.nodes
 )
 const defaultViewport = { x: 0, y: 0, zoom: 1 }
+const projectExtent = computed<CoordinateExtent | undefined>(() => {
+	return Array.isArray(props.translateExtent) ? props.translateExtent : undefined
+})
+const isValidConnection: ValidConnectionFunc = (connection) => {
+	if (!connection.source || !connection.target) {
+		return true
+	}
+
+	return connection.source !== connection.target
+}
+
+const clampPositionToExtent = (
+	position: XYPosition,
+	extent?: CoordinateExtent
+): XYPosition => {
+	if (!extent) {
+		return position
+	}
+
+	const [[minX, minY], [maxX, maxY]] = extent
+
+	return {
+		x: Math.min(Math.max(position.x, minX), maxX),
+		y: Math.min(Math.max(position.y, minY), maxY)
+	}
+}
 
 const showMinimap = ref(false)
 const vueFlow = useVueFlow(props.id)
@@ -240,10 +276,13 @@ function getProjectedPosition(event?: MouseEvent | TouchEvent) {
 	const bounds = viewportRef.value?.getBoundingClientRect() ?? { left: 0, top: 0 }
 	const { x, y } = event ? getMousePosition(event) : { x: 0, y: 0 }
 
-	return project({
-		x: x - bounds.left,
-		y: y - bounds.top
-	})
+	return clampPositionToExtent(
+		project({
+			x: x - bounds.left,
+			y: y - bounds.top
+		}),
+		projectExtent.value
+	)
 }
 
 /**
@@ -399,6 +438,9 @@ defineExpose({
 		:edges="normalizedEdges"
 		:default-viewport="defaultViewport"
 		:fit-view-on-init="zoomToFit"
+		:is-valid-connection="isValidConnection"
+		:translate-extent="translateExtent"
+		:node-extent="nodeExtent"
 		:connection-line-options="{ markerEnd: MarkerType.ArrowClosed }"
 		:connection-radius="60"
 		snap-to-grid

+ 18 - 2
packages/workflow/src/components/elements/nodes/render-types/NodeLoop.vue

@@ -3,9 +3,10 @@ import { computed, ref } from 'vue'
 import { Icon } from '@repo/ui'
 import { NodeResizer } from '@vue-flow/node-resizer'
 import type { OnResize } from '@vue-flow/node-resizer'
+import { useElementSize } from '@vueuse/core'
 import Canvas from '../../../Canvas.vue'
 
-import type { XYPosition, Connection } from '@vue-flow/core'
+import type { XYPosition, Connection, CoordinateExtent } from '@vue-flow/core'
 import type { CanvasNodeMoveEvent, ConnectStartEvent } from '../../../../Interface'
 import { useCanvasNodeContext } from '../../../../hooks/useCanvasNodeContext'
 import { useVueFlowContext } from '../../../../hooks/useVueFlowContext'
@@ -58,6 +59,18 @@ const isReadOnly = computed(() => node.props.value.readOnly ?? false)
 const width = computed(() => Number(nodeData.value?.width) || 424)
 const height = computed(() => Number(nodeData.value?.height) || 244)
 const executionStatus = computed(() => getNodeExecutionStatus(node.props.value.node))
+const innerCanvasRef = ref<HTMLDivElement>()
+const { width: innerCanvasWidth, height: innerCanvasHeight } = useElementSize(innerCanvasRef)
+const innerCanvasExtent = computed<CoordinateExtent | undefined>(() => {
+	if (innerCanvasWidth.value <= 0 || innerCanvasHeight.value <= 0) {
+		return undefined
+	}
+
+	return [
+		[0, 0],
+		[Math.max(innerCanvasWidth.value - 1, 0), Math.max(innerCanvasHeight.value - 1, 0)]
+	]
+})
 
 const nodeClass = computed(() => {
 	const classes: string[] = []
@@ -124,7 +137,8 @@ function onAddEdge(connection: Connection) {
 		<!-- 内部画布区域:虚线网格 + 占位内容 -->
 		<div class="loop-body flex-1 min-h-0 relative">
 			<div
-				class="absolute top-16px right-16px bottom-16px left-16px z-1 rounded-8px border-1 border-dashed border-#d9d9d9 nopan"
+				ref="innerCanvasRef"
+				class="absolute top-16px right-16px bottom-16px left-16px z-1 rounded-8px border-1 border-dashed border-#d9d9d9 nopan overflow-hidden"
 				@click.stop
 				@dblclick.stop
 			>
@@ -141,6 +155,8 @@ function onAddEdge(connection: Connection) {
 					:min-zoom="1"
 					:pan-on-drag="false"
 					:auto-pan-on-node-drag="false"
+					:translate-extent="innerCanvasExtent"
+					:node-extent="innerCanvasExtent"
 					@click:node="(id, position) => emit('click:node', id, position)"
 					@dblclick:node="(id, position) => emit('dblclick:node', id, position)"
 					@click:node:add="emit('click:node:add', $event)"