From 570d4b62368ae64ecba14de8b9940724c2deea99 Mon Sep 17 00:00:00 2001 From: jile1997 <1154416769@qq.com> Date: Fri, 21 Nov 2025 13:44:46 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E8=B7=A8=E7=94=BB=E5=B8=83?= =?UTF-8?q?=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/x-flow/src/XFlow.tsx | 7 ++----- packages/x-flow/src/hooks/useFlow.ts | 29 +++++++++++++++++++++++++--- packages/x-flow/src/utils/index.ts | 15 ++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/packages/x-flow/src/XFlow.tsx b/packages/x-flow/src/XFlow.tsx index 62c620001..884cf85d2 100644 --- a/packages/x-flow/src/XFlow.tsx +++ b/packages/x-flow/src/XFlow.tsx @@ -164,11 +164,8 @@ const XFlow: FC = memo(props => { copyNode(selectedNode.id); } } else if ((e.key === 'v' || e.key === 'V') && (e.ctrlKey || e.metaKey)) { - const { copyNodes } = storeApi.getState(); - if (copyNodes?.length > 0) { - e.preventDefault(); - pasteNodeSimple(); - } + e.preventDefault(); + pasteNodeSimple(); } else if (e.key === 'Escape') { setOpenPanel(false); workflowContainerRef.current?.focus(); diff --git a/packages/x-flow/src/hooks/useFlow.ts b/packages/x-flow/src/hooks/useFlow.ts index 14455810c..b0e5a7f08 100644 --- a/packages/x-flow/src/hooks/useFlow.ts +++ b/packages/x-flow/src/hooks/useFlow.ts @@ -6,7 +6,7 @@ import { FlowNode } from '../models/store'; import { useStoreApi } from './useStore'; import { useTemporalStore } from './useTemporalStore'; import autoLayoutNodes from '../utils/autoLayoutNodes'; -import { generateCopyNodes, uuid } from '../utils'; +import { generateCopyNodes, safeJSONParse, safeJsonStringify, uuid } from '../utils'; import { message } from 'antd'; // useFlow 维护原则 @@ -113,6 +113,11 @@ export const useFlow = () => { storeApi.setState({ copyTimeoutId: null }); }, 30000); + navigator.clipboard + .writeText(safeJsonStringify(copyNodes)) + .catch((err) => { + message.error('复制失败~'); + }) storeApi.setState({ copyNodes, copyTimeoutId: timeoutId, @@ -148,8 +153,9 @@ export const useFlow = () => { } }); - const pasteNodeSimple = useMemoizedFn(() => { + const pasteNodeSimple = useMemoizedFn(async() => { const mousePosition = storeApi.getState().mousePosition; + const text = await navigator.clipboard.readText().catch(()=>null) if (storeApi.getState().copyNodes.length > 0) { // 清除超时定时器 if (storeApi.getState().copyTimeoutId) { @@ -178,7 +184,24 @@ export const useFlow = () => { copyTimeoutId: null, }); }); - } else { + } else if(text){ + const copyNodeData = safeJSONParse(text,[]) + const flowPos = screenToFlowPosition({ + x: mousePosition.elementX, + y: mousePosition.elementY, + }); + const copyNodes = copyNodeData.map(node => ({ + ...node, + id:uuid(), + position: { + x: flowPos.x, + y: flowPos.y, + }, + })); + record(() => { + storeApi.getState().addNodes(copyNodes, false); + }); + }else{ // message.warning('请先复制节点!'); } }); diff --git a/packages/x-flow/src/utils/index.ts b/packages/x-flow/src/utils/index.ts index 32c5f23cc..2086cb0e6 100644 --- a/packages/x-flow/src/utils/index.ts +++ b/packages/x-flow/src/utils/index.ts @@ -281,6 +281,21 @@ export function safeJsonStringify(obj: Object) { } } +export function safeJSONParse( + jsonString: string | null, + defaultVal: T = {} as any, +): T { + if (typeof jsonString === 'string') { + try { + return JSON.parse(jsonString); + } catch (e) { + return defaultVal; // 或返回默认值 {} + } + } else { + return defaultVal; + } +} + export * from './flow'; // 内置节点状态 From d3eebd39eb277a179446f3827426045481cf22d1 Mon Sep 17 00:00:00 2001 From: jile1997 <1154416769@qq.com> Date: Fri, 28 Nov 2025 13:59:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E9=BB=98=E8=AE=A4=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=BA=BF=E7=9A=84=E7=B2=97=E7=BB=86=E5=8F=AF=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/x-flow/src/XFlow.tsx | 3 ++- packages/x-flow/src/types.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/x-flow/src/XFlow.tsx b/packages/x-flow/src/XFlow.tsx index 884cf85d2..1e4b9d278 100644 --- a/packages/x-flow/src/XFlow.tsx +++ b/packages/x-flow/src/XFlow.tsx @@ -324,6 +324,7 @@ const XFlow: FC = memo(props => { }, [activeNode?.id]); const deletable = globalConfig?.edge?.deletable ?? true; + const strokeWidth = globalConfig?.edge?.strokeWidth ?? 1.5 const panelonClose = globalConfig?.nodePanel?.onClose; return ( @@ -349,7 +350,7 @@ const XFlow: FC = memo(props => { defaultEdgeOptions={{ type: 'buttonedge', style: { - strokeWidth: 1.5, // 线粗细 + strokeWidth, // 线粗细 }, markerEnd: { type: MarkerType.ArrowClosed, // 箭头 diff --git a/packages/x-flow/src/types.ts b/packages/x-flow/src/types.ts index 174d03e2b..91445e419 100644 --- a/packages/x-flow/src/types.ts +++ b/packages/x-flow/src/types.ts @@ -125,6 +125,8 @@ export interface TEdge { hideEdgeAddBtn?: boolean; // 是否隐藏两个节点之间,连线上的增加节点按钮 hideEdgeDelBtn?: boolean; // 是否隐藏两个节点之间,连线上的删除节点按钮 deletable?: boolean; // 是否允许删除线条 初始化的edges不受此项影响 + + strokeWidth?:number } export interface TControl{ From c99f49f879a891e1c04eb2938d30674e2a4a4897 Mon Sep 17 00:00:00 2001 From: jile1997 <1154416769@qq.com> Date: Mon, 1 Dec 2025 14:19:49 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E4=BC=A0=E9=80=92nodeId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/x-flow/src/components/NodeEditor/index.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/x-flow/src/components/NodeEditor/index.tsx b/packages/x-flow/src/components/NodeEditor/index.tsx index 472c087a4..6701e0242 100644 --- a/packages/x-flow/src/components/NodeEditor/index.tsx +++ b/packages/x-flow/src/components/NodeEditor/index.tsx @@ -152,6 +152,7 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { setCustomVal(values); handleNodeValueChange({ ...values }); }} + nodeId={id} readOnly={readOnly} ref={nodeWidgetRef} /> @@ -163,6 +164,9 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { form={form} widgets={widgets} watch={watch} + globalProps={{ + nodeId:id + }} readOnly={readOnly} onMount={() => { const initialValues = form.getValues(); @@ -184,6 +188,9 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { widgets={widgets} watch={watch} size={'small'} + globalProps={{ + nodeId:id + }} readOnly={readOnly} configProvider={{ getPopupContainer: triggerNode => triggerNode.parentElement, @@ -198,6 +205,7 @@ const NodeEditor: FC = forwardRef((props, ref: any) => { handleNodeValueChange({ ...val }); }} value={data} + nodeId={id} readOnly={readOnly} /> );