From 2a84a5a8f2322ec3d7c3190d1e4f9151066683b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E6=A2=A6?= Date: Fri, 4 Jul 2025 10:19:26 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=95=B0=E6=8D=AE=E4=B8=8D=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/x-flow/src/XFlow.tsx | 46 ++++++++-------- .../src/components/NodeEditor/index.tsx | 55 ++++++++++++------- .../src/components/NodeLogPanel/index.tsx | 2 +- 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/packages/x-flow/src/XFlow.tsx b/packages/x-flow/src/XFlow.tsx index 9bab32500..9323d4f45 100644 --- a/packages/x-flow/src/XFlow.tsx +++ b/packages/x-flow/src/XFlow.tsx @@ -202,17 +202,27 @@ const XFlow: FC = memo(props => { setEdges(newEdges); }); - const handleNodeValueChange = debounce((data: any) => { - for (let node of nodes) { - if (node.id === data.id) { - node.data = { - ...node?.data, - ...data?.values, - }; - break; - } + const handleNodeValueChange = debounce((data: any, id: string) => { + // for (let node of nodes) { + // if (node.id === data.id) { + // node.data = { + // ...node?.data, + // ...data?.values, + // }; + // break; + // } + // } + // setNodes([...nodes], false); + // 同时更新 activeNode 状态,确保面板数据同步 + if (activeNode && activeNode.id === id) { + setActiveNode({ + ...activeNode, + values: { + ...activeNode.values, + ...data, + }, + }); } - setNodes([...nodes], false); }, 200); const nodeTypes = useMemo(() => { @@ -252,6 +262,7 @@ const XFlow: FC = memo(props => { }, [layout]); const NodeEditorWrap = useMemo(() => { + return ( = memo(props => { /> ); + // JSON.stringify(activeNode) }, [activeNode?.id]); const NodeLogWrap = useMemo(() => { return ( = memo(props => { const deletable = globalConfig?.edge?.deletable ?? true; const panelonClose = globalConfig?.nodePanel?.onClose; - const getNodesJ = nodes => { - const result = nodes.map(item => { - const { data, ...rest } = item; - const { _nodeType, ...restData } = data; - return { - ...rest, - data: restData, - type: _nodeType, - }; - }); - return result; - }; return (
diff --git a/packages/x-flow/src/components/NodeEditor/index.tsx b/packages/x-flow/src/components/NodeEditor/index.tsx index 413b04400..460d58b02 100644 --- a/packages/x-flow/src/components/NodeEditor/index.tsx +++ b/packages/x-flow/src/components/NodeEditor/index.tsx @@ -17,10 +17,11 @@ import { safeJsonStringify, uuid } from '../../utils'; interface INodeEditorProps { data: any; - onChange: (data: any) => void; + onChange: (data: any, id?: string) => void; nodeType: string; id: string; ref?: React.Ref; // 添加 ref 属性 + // activeNode?: any; } const NodeEditor: FC = forwardRef((props, ref: any) => { @@ -35,6 +36,20 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { const getSettingSchema = nodeSetting['getSettingSchema']; const [asyncSchema, setAsyncSchema] = useState({}); const nodeWidgetRef = useRef(null); + const { nodes, setNodes } = useStore( + (state: any) => ({ + nodes: state.nodes, + setNodes: state.setNodes, + }), + shallow + ); + const [internalData, setInternalData] = useState(); + + useEffect(() => { + const activeNode = nodes.find(node => node.id === id); + const { _nodeType, _status, ...restData } = activeNode?.data || {}; + setInternalData(restData); + }, []); useImperativeHandle(ref, () => ({ validateForm: async () => { @@ -51,7 +66,10 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { .catch(err => { return false; }); - } else if (nodeSetting?.settingWidget && nodeWidgetRef.current?.validateForm) { + } else if ( + nodeSetting?.settingWidget && + nodeWidgetRef.current?.validateForm + ) { result = await nodeWidgetRef.current.validateForm(); } return result; @@ -68,20 +86,13 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { ).catch(() => ({})); setAsyncSchema(shema); } + useEffect(() => { if (isFunction(getSettingSchema)) { getSchema(); } }, []); - const { nodes, setNodes } = useStore( - (state: any) => ({ - nodes: state.nodes, - setNodes: state.setNodes, - }), - shallow - ); - useEffect(() => { if (nodeSetting?.settingSchema) { // 自定义Schema @@ -115,14 +126,14 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { if (item?._id) { return item; } else { - if (node?.data?.list?.length && node?.data?.list[index]?._id) { - return { - ...item, - _id: node?.data?.list[index]?._id, - }; - } else { - return { ...item, _id: `id_${uuid()}` }; - } + // if (node?.data?.list?.length && node?.data?.list[index]?._id) { + // return { + // ...item, + // _id: node?.data?.list[index]?._id, + // }; + // } else { + return { ...item, _id: `id_${uuid()}` }; + // } } }); } @@ -131,6 +142,11 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { } }); setNodes(newNodes, false); + + // if (onChange) { + // onChange(data, id); + // } + setInternalData(data); }, 100); const watch = { @@ -193,7 +209,8 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { onChange={val => { handleNodeValueChange({ ...val }); }} - value={data} + value={internalData} // data + // value={data} readOnly={readOnly} /> ); diff --git a/packages/x-flow/src/components/NodeLogPanel/index.tsx b/packages/x-flow/src/components/NodeLogPanel/index.tsx index a3039ebf5..bd5eca624 100644 --- a/packages/x-flow/src/components/NodeLogPanel/index.tsx +++ b/packages/x-flow/src/components/NodeLogPanel/index.tsx @@ -8,7 +8,7 @@ import './index.less'; interface INodeEditorProps { data: any; - onChange: (data: any) => void; + onChange?: (data: any) => void; nodeType: string; id: string; node: any; From c5570710b8a2940476ce930afc7c669d1e0cdfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E6=A2=A6?= Date: Fri, 4 Jul 2025 14:28:04 +0800 Subject: [PATCH 2/7] =?UTF-8?q?fix:1.=E6=B2=A1=E6=9C=89=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=9A=84=E8=8A=82=E7=82=B9=E6=B2=A1=E6=9C=89=E5=8F=B3=E4=BE=A7?= =?UTF-8?q?=E7=9A=84=E6=A0=87=E6=B3=A82.=E4=BF=AE=E5=A4=8D=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E7=82=B9=E5=83=8F=E7=B4=A0=E5=81=8F=E5=B7=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CustomNode/index.less | 12 +++- .../src/components/CustomNode/index.tsx | 38 +++++++++-- .../components/CustomNode/sourceHandle.tsx | 68 +++++++++++-------- .../ParallelBuildInNodeWidget.tsx | 4 ++ .../x-flow/src/nodes/node-parallel/index.less | 2 +- .../node-switch/SwitchBuildInNodeWidget.tsx | 8 +++ .../x-flow/src/nodes/node-switch/index.less | 2 +- 7 files changed, 98 insertions(+), 36 deletions(-) diff --git a/packages/x-flow/src/components/CustomNode/index.less b/packages/x-flow/src/components/CustomNode/index.less index 18d0845ee..8e432b721 100644 --- a/packages/x-flow/src/components/CustomNode/index.less +++ b/packages/x-flow/src/components/CustomNode/index.less @@ -40,7 +40,17 @@ width: 2px; height: 10px; display: block; - margin: 11px 0 8px 15px; + margin: 11px 0 8px 17px; + } + + .handle-connected-target::after{ + margin: 11px 0 8px 11px; + } + + .handle-disconnected { + &::after { + background-color: transparent !important; // 未连接时透明 + } } .react-flow__handle:hover { diff --git a/packages/x-flow/src/components/CustomNode/index.tsx b/packages/x-flow/src/components/CustomNode/index.tsx index 7549e1c1c..70fbfe9c3 100644 --- a/packages/x-flow/src/components/CustomNode/index.tsx +++ b/packages/x-flow/src/components/CustomNode/index.tsx @@ -1,7 +1,6 @@ import { MoreOutlined } from '@ant-design/icons'; import { Handle, Position, useReactFlow } from '@xyflow/react'; import { Dropdown, Menu, message } from 'antd'; - import { ItemType } from 'antd/es/menu/interface'; import classNames from 'classnames'; import { isFunction } from 'lodash'; @@ -43,7 +42,7 @@ export default memo((props: any) => { widgets[`${capitalize(type)}Node`] || widgets['CommonNode']; const [isHovered, setIsHovered] = useState(false); const reactflow = useReactFlow(); - const { addEdges, mousePosition } = + const { edges,nodes,addEdges, mousePosition } = useStore( (state: any) => ({ nodes: state.nodes, @@ -61,6 +60,30 @@ export default memo((props: any) => { const connectable = readOnly ? false : isConnectable; const nodeSetting = settingMap[type] || {}; const nodeClassName = nodeSetting?.className || ''; + // 判断左侧Handle是否已连接 + const isTargetHandleConnected = useMemo(() => { + return (edges || [])?.some(edge => edge.target === id); + }, [edges, id]); + + const isSourceHandleConnected = useMemo(() => { + if (isSwitchNode) { + // 对于Switch节点,需要检查每个sourceHandle是否已连接 + if (type === 'Switch' && Array.isArray(data.list)) { + return data.list.some(item => + edges.some(edge => edge.source === id && edge.sourceHandle === item._id) + ); + } + // 对于Parallel节点,需要检查每个sourceHandle是否已连接 + if (type === 'Parallel' && Array.isArray(data.list)) { + return data.list.some(item => + edges.some(edge => edge.source === id && edge.sourceHandle === item._id) + ); + } + return false; + } + // 对于普通节点,检查是否有从该节点出发的边 + return edges.some(edge => edge.source === id); + }, [edges, id, type, data.list, isSwitchNode]); // 增加节点并进行联系 const handleAddNode = (data: any, sourceHandle?: string) => { @@ -248,6 +271,7 @@ export default memo((props: any) => { overlay: menu, }; }, [menuItem, isEnd]); + return (
{ type="target" position={targetPosition} isConnectable={connectable} + className={classNames({ + 'handle-connected': isTargetHandleConnected, + 'handle-disconnected': !isTargetHandleConnected, + "handle-connected-target":true + })} // isConnectableStart={isConnectableStart} // isConnectableEnd={isConnectableEnd} /> @@ -303,8 +332,9 @@ export default memo((props: any) => { selected={selected} isHovered={isHovered} handleAddNode={handleAddNode} - // isConnectableStart={isConnectableStart} - // isConnectableEnd={isConnectableEnd} + isConnected={isSourceHandleConnected} + // isConnectableStart={isConnectableStart} + // isConnectableEnd={isConnectableEnd} /> )} diff --git a/packages/x-flow/src/components/CustomNode/sourceHandle.tsx b/packages/x-flow/src/components/CustomNode/sourceHandle.tsx index 403cc79da..2bd5c8827 100644 --- a/packages/x-flow/src/components/CustomNode/sourceHandle.tsx +++ b/packages/x-flow/src/components/CustomNode/sourceHandle.tsx @@ -1,10 +1,11 @@ import { PlusOutlined } from '@ant-design/icons'; import { Handle } from '@xyflow/react'; import { Tooltip } from 'antd'; +import classNames from 'classnames'; import React, { memo, useContext, useMemo, useRef, useState } from 'react'; -import NodeSelectPopover from '../NodesPopover'; import { ConfigContext } from '../../models/context'; - +import NodeSelectPopover from '../NodesPopover'; +import './index.less'; export default memo((props: any) => { const { @@ -14,13 +15,14 @@ export default memo((props: any) => { isHovered, handleAddNode, switchTitle, + isConnected, // 是否有连接的节点 ...rest } = props; const [isShowTooltip, setIsShowTooltip] = useState(false); const [openNodeSelectPopover, setOpenNodeSelectPopover] = useState(false); const popoverRef = useRef(null); - const { antdVersion,globalConfig } = useContext(ConfigContext); - const handleProps = globalConfig?.handle || {} + const { antdVersion, globalConfig } = useContext(ConfigContext); + const handleProps = globalConfig?.handle || {}; const toolTipVersionProps = useMemo(() => { if (antdVersion === 'V5') { @@ -49,37 +51,45 @@ export default memo((props: any) => { setOpenNodeSelectPopover(true); }} {...rest} + className={classNames( + { + 'handle-disconnected': !isConnected, + }, + rest.className + )} > - {(selected || isHovered || openNodeSelectPopover ) && ( + {(selected || isHovered || openNodeSelectPopover) && ( <> {switchTitle && (
{switchTitle}
)} - {isConnectable &&
- setOpenNodeSelectPopover(val)} - > - - document.getElementById('xflow-container') as HTMLElement - } + {isConnectable && ( +
+ setOpenNodeSelectPopover(val)} > - - - -
} + + document.getElementById('xflow-container') as HTMLElement + } + > + + +
+
+ )} )} diff --git a/packages/x-flow/src/nodes/node-parallel/ParallelBuildInNodeWidget.tsx b/packages/x-flow/src/nodes/node-parallel/ParallelBuildInNodeWidget.tsx index cad691365..544f5d1f4 100644 --- a/packages/x-flow/src/nodes/node-parallel/ParallelBuildInNodeWidget.tsx +++ b/packages/x-flow/src/nodes/node-parallel/ParallelBuildInNodeWidget.tsx @@ -65,6 +65,10 @@ export default memo((props: any) => { }} id={item?._id} className="item-handle" + isConnected={ + (edges || [])?.filter(flow => flow?.sourceHandle === item?._id) + ?.length > 0 + } />
); diff --git a/packages/x-flow/src/nodes/node-parallel/index.less b/packages/x-flow/src/nodes/node-parallel/index.less index f61165af9..94d69f05d 100644 --- a/packages/x-flow/src/nodes/node-parallel/index.less +++ b/packages/x-flow/src/nodes/node-parallel/index.less @@ -43,7 +43,7 @@ } .item-handle { - right: -11px; + right: -12px; top: 9px; .xflow-node-add-box { diff --git a/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx b/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx index 22564fc7f..7a5d0d7d0 100644 --- a/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx +++ b/packages/x-flow/src/nodes/node-switch/SwitchBuildInNodeWidget.tsx @@ -51,6 +51,10 @@ export default memo((props: any) => { }} id={item?._id} className="item-handle" + isConnected={ + (edges || [])?.filter(flow => flow?.sourceHandle === item?._id) + ?.length > 0 + } />
); @@ -123,6 +127,10 @@ export default memo((props: any) => { }} className="item-handle" id={'id_else'} + isConnected={ + (edges || [])?.filter(flow => flow?.sourceHandle === 'id_else') + ?.length > 0 + } /> diff --git a/packages/x-flow/src/nodes/node-switch/index.less b/packages/x-flow/src/nodes/node-switch/index.less index d1404858f..688781024 100644 --- a/packages/x-flow/src/nodes/node-switch/index.less +++ b/packages/x-flow/src/nodes/node-switch/index.less @@ -38,7 +38,7 @@ } .item-handle { - right: -15px; + right: -12px; } } From e48b5e6715885732627343a76ac69012f790fd2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E6=A2=A6?= Date: Fri, 4 Jul 2025 15:05:04 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix:=E6=8A=BD=E5=B1=89=E9=98=B4=E5=BD=B1?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E5=8F=98=E6=B5=85+=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=8A=BD=E5=B1=89=E4=B8=8A=E8=BE=B9=E8=B7=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/x-flow/src/components/PanelContainer/index.less | 5 +++-- packages/x-flow/src/components/PanelContainer/index.tsx | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/x-flow/src/components/PanelContainer/index.less b/packages/x-flow/src/components/PanelContainer/index.less index 2b195e639..33ca87853 100644 --- a/packages/x-flow/src/components/PanelContainer/index.less +++ b/packages/x-flow/src/components/PanelContainer/index.less @@ -5,7 +5,8 @@ right: 12px; border-radius: 20px; height: auto; // 兼容antd4 - + box-shadow: 0px 4px 6px -2px rgba(16, 24, 40, 0.03), + 0px 12px 16px -4px rgba(16, 24, 40, 0.08); .ant-drawer-header-title, .ant-drawer-title { width: 100%; @@ -20,7 +21,7 @@ } .ant-drawer-header { - padding: 24px 16px 0 16px; + padding: 16px 16px 0 16px; } .ant-drawer-content { diff --git a/packages/x-flow/src/components/PanelContainer/index.tsx b/packages/x-flow/src/components/PanelContainer/index.tsx index 7a2473b73..ae50cbcd7 100644 --- a/packages/x-flow/src/components/PanelContainer/index.tsx +++ b/packages/x-flow/src/components/PanelContainer/index.tsx @@ -149,6 +149,7 @@ const Panel: FC = (props: IPanelProps) => { setTitleVal(e.target.value); handleNodeValueChange({ title: e.target.value }); }} + size="small" /> )} From 0de7d7ebffef8dd7343c8ce16e4feb6f6d80b299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E6=A2=A6?= Date: Fri, 4 Jul 2025 16:31:46 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=A0=8F=E9=85=8D=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/xflow/api.md | 6 + .../x-flow/src/operator/Control/index.less | 1 - .../x-flow/src/operator/Control/index.tsx | 171 ++++++++++-------- .../x-flow/src/operator/UndoRedo/index.less | 2 +- .../x-flow/src/operator/ZoomInOut/index.less | 1 + packages/x-flow/src/operator/index.tsx | 6 +- packages/x-flow/src/types.ts | 7 +- 7 files changed, 111 insertions(+), 83 deletions(-) diff --git a/docs/xflow/api.md b/docs/xflow/api.md index 3fbf3404d..81d229fc4 100644 --- a/docs/xflow/api.md +++ b/docs/xflow/api.md @@ -74,6 +74,12 @@ group: | hideAddNode | 是否隐藏增加节点功能 | `boolean` | false | | hideAnnotate | 是否隐藏注释节点功能 | `boolean` | false | | hideUndoRedoBtns | 是否隐藏撤销和重做按钮 | `boolean` | false | +| hideZoomInOutBtns | 是否隐藏缩放按钮 | `boolean` | false | +| hideControlBtns | 是否隐藏所有控制按钮 | `boolean` | false | +| hideAutoLayout | 是否隐藏整理画布功能 | `boolean` | false | +| hideFullscreen | 是否隐藏全屏功能 | `boolean` | false | +| hideInteractionMode | 是否隐藏指针和手形工具切换功能 | `boolean` | false | + ## THandle diff --git a/packages/x-flow/src/operator/Control/index.less b/packages/x-flow/src/operator/Control/index.less index 350354ecb..3e4a889c9 100644 --- a/packages/x-flow/src/operator/Control/index.less +++ b/packages/x-flow/src/operator/Control/index.less @@ -7,7 +7,6 @@ background-color: #fff; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); color: #667085; - margin-left: 10px; .ant-btn { display: inline-flex; diff --git a/packages/x-flow/src/operator/Control/index.tsx b/packages/x-flow/src/operator/Control/index.tsx index d3c56b30d..6aeaafc3a 100644 --- a/packages/x-flow/src/operator/Control/index.tsx +++ b/packages/x-flow/src/operator/Control/index.tsx @@ -6,7 +6,6 @@ import NodeSelectPopover from '../../components/NodesPopover'; import { useStore, useStoreApi } from '../../hooks/useStore'; import { ConfigContext } from '../../models/context'; import { useEventEmitterContextContext } from '../../models/event-emitter'; - import { useFullscreen } from 'ahooks'; import './index.less'; @@ -17,6 +16,10 @@ const Control = (props: any) => { const hideAddNode = globalConfig?.controls?.hideAddNode ?? false; const hideAnnotate = globalConfig?.controls?.hideAnnotate ?? false; + const hideAutoLayout = globalConfig?.controls?.hideAutoLayout ?? false; + const hideFullscreen = globalConfig?.controls?.hideFullscreen ?? false; + const hideInteractionMode = + globalConfig?.controls?.hideInteractionMode ?? false; const { setIsAddingNode, panOnDrag } = useStore(s => ({ setIsAddingNode: s.setIsAddingNode, @@ -69,86 +72,98 @@ const Control = (props: any) => { /> )} - {!(hideAddNode && hideAnnotate) && !readOnly &&
} - - document.getElementById('xflow-container') as HTMLElement - } - > -