Skip to content

Commit cf3f2fd

Browse files
committed
chore: 复制粘贴节点
1 parent a002154 commit cf3f2fd

File tree

5 files changed

+112
-9
lines changed

5 files changed

+112
-9
lines changed

packages/x-flow/src/components/CustomNode/index.less

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
.xflow-node-container {
22
border: 2px solid #fff;
33
border-radius: 14px;
4-
4+
position: relative;
55
.react-flow__edge-path,
66
.react-flow__connection-path {
77
stroke: #d0d5dc;
88
stroke-width: 2px;
99
}
10-
10+
.xflow-node-actions-container {
11+
cursor: pointer;
12+
position: absolute;
13+
top: -4px;
14+
background: #ffffff;
15+
transform: translate(0, -100%);
16+
right: 6px;
17+
padding: 0 2px;
18+
border-radius: 4px;
19+
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, .05);
20+
}
1121
.react-flow__handle {
1222
width: 30px;
1323
height: 30px;

packages/x-flow/src/components/CustomNode/index.tsx

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { MoreOutlined } from '@ant-design/icons';
12
import { Handle, Position, useReactFlow } from '@xyflow/react';
3+
import { Dropdown, message } from "antd";
4+
25
import classNames from 'classnames';
3-
import React, { memo, useContext, useState } from 'react';
6+
import React, { memo, useCallback, useContext, useState } from 'react';
47
import { shallow } from 'zustand/shallow';
58
import { useStore } from '../../hooks/useStore';
69
import { ConfigContext } from '../../models/context';
@@ -15,13 +18,15 @@ export default memo((props: any) => {
1518
widgets[`${capitalize(type)}Node`] || widgets['CommonNode'];
1619
const [isHovered, setIsHovered] = useState(false);
1720
const reactflow = useReactFlow();
18-
const { addNodes, addEdges, mousePosition } = useStore(
21+
const { addNodes, addEdges, copyNode, pasteNode, mousePosition } = useStore(
1922
(state: any) => ({
2023
nodes: state.nodes,
2124
edges: state.edges,
2225
mousePosition: state.mousePosition,
2326
addNodes: state.addNodes,
2427
addEdges: state.addEdges,
28+
copyNode: state.copyNode,
29+
pasteNode: state.pasteNode,
2530
onEdgesChange: state.onEdgesChange,
2631
}),
2732
shallow
@@ -42,15 +47,15 @@ export default memo((props: any) => {
4247
type: 'custom',
4348
data: {
4449
...data,
45-
title: `${title}_${uuid4()}`
50+
title: `${title}_${uuid4()}`,
4651
},
4752
position: { x, y },
4853
};
4954
const newEdges = {
5055
id: uuid(),
5156
source: id,
5257
target: targetId,
53-
...sourceHandle && { sourceHandle }
58+
...(sourceHandle && { sourceHandle }),
5459
};
5560
addNodes(newNodes);
5661
addEdges(newEdges);
@@ -62,6 +67,16 @@ export default memo((props: any) => {
6267
targetPosition = Position.Top;
6368
sourcePosition = Position.Bottom;
6469
}
70+
71+
const handleCopyNode = useCallback(() => {
72+
copyNode(id);
73+
message.success('复制成功');
74+
}, [copyNode]);
75+
76+
const handlePasteNode = useCallback(() => {
77+
pasteNode(id)
78+
}, [pasteNode]);
79+
6580
return (
6681
<div
6782
className={classNames('xflow-node-container', {
@@ -78,6 +93,31 @@ export default memo((props: any) => {
7893
isConnectable={isConnectable}
7994
/>
8095
)}
96+
{selected && (
97+
<Dropdown
98+
menu={{
99+
items: [
100+
{
101+
label: '复制',
102+
key: ' copy',
103+
onClick: handleCopyNode,
104+
},
105+
{
106+
label: '粘贴',
107+
key: 'paste',
108+
onClick: handlePasteNode,
109+
},
110+
],
111+
}}
112+
trigger={['click', 'contextMenu']}
113+
>
114+
<div className="xflow-node-actions-container">
115+
<MoreOutlined
116+
style={{ transform: 'rotateZ(90deg)', fontSize: '20px' }}
117+
></MoreOutlined>
118+
</div>
119+
</Dropdown>
120+
)}
81121
<NodeWidget
82122
id={id}
83123
type={type}

packages/x-flow/src/models/store.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { generateCopyNodes, uuid } from '../utils';
12
import {
23
addEdge,
34
applyEdgeChanges,
@@ -27,6 +28,8 @@ export type FlowState = {
2728
layout?: 'LR' | 'TB';
2829
nodes?: FlowNode[];
2930
edges?: Edge[];
31+
copyNodes: FlowNode[];
32+
copyEdges: Edge[];
3033
panOnDrag?: boolean;
3134
isAddingNode?: boolean;
3235
candidateNode: any;
@@ -36,8 +39,10 @@ export type FlowState = {
3639
onConnect: OnConnect;
3740
setNodes: (nodes: FlowNode[]) => void;
3841
setEdges: (edges: Edge[]) => void;
39-
addNodes: (nodes: FlowNode[]) => void;
40-
addEdges: (edges: Edge[]) => void;
42+
addNodes: (nodes: FlowNode[]| FlowNode) => void;
43+
addEdges: (edges: Edge[] | Edge) => void;
44+
copyNode: (nodeId: string) => void;
45+
pasteNode: (nodeId: string) => void;
4146
setLayout: (layout: 'LR' | 'TB') => void;
4247
setIsAddingNode: (payload: boolean) => void;
4348
setCandidateNode: (candidateNode: any) => void;
@@ -57,6 +62,8 @@ const createStore = (initProps?: Partial<FlowProps>) => {
5762
(set, get) => ({
5863
...DEFAULT_PROPS,
5964
...initProps,
65+
copyNodes: [],
66+
copyEdges: [],
6067
isAddingNode: false,
6168
candidateNode: null,
6269
// nodeMenus: [],
@@ -113,6 +120,28 @@ const createStore = (initProps?: Partial<FlowProps>) => {
113120
}
114121
set({ layout });
115122
},
123+
copyNode: (nodeId) => {
124+
const copyNodes = generateCopyNodes(
125+
get().nodes.find((node) => node.id === nodeId),
126+
);
127+
set({
128+
copyNodes,
129+
});
130+
},
131+
pasteNode: (nodeId) => {
132+
if (get().copyNodes.length > 0) {
133+
const newEdges = {
134+
id: uuid(),
135+
source: nodeId,
136+
target: get().copyNodes[0].id,
137+
};
138+
get().addNodes(get().copyNodes);
139+
get().addEdges(newEdges);
140+
set({
141+
copyNodes: [],
142+
});
143+
}
144+
},
116145
}),
117146
{
118147
// nodes 和 edges 是引用类型,所以使用深比较

packages/x-flow/src/utils/flow.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { uuid } from './';
2+
3+
/**
4+
* 获取所有子节点
5+
*/
6+
export const generateCopyNodes = (parentNode: any) => {
7+
// 1、定义 childNodeIds 数组,用于存储找到的所有节点的 id,默认把 rootNode 添加到数组中
8+
const childNodes: any[] = [];
9+
const rootNode = {
10+
id: uuid(),
11+
type: parentNode.type,
12+
data: {
13+
...parentNode.data,
14+
},
15+
position: { x: 0, y: 0 },
16+
sourceId: parentNode.id,
17+
};
18+
childNodes.push(rootNode);
19+
20+
return childNodes;
21+
};

packages/x-flow/src/utils/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { version as antdVersion } from 'antd';
1+
// import { version as antdVersion } from 'antd';
22
import { customAlphabet } from 'nanoid';
33
export const uuid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 16);
44
export const uuid4 = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 4);
55

66
import { isMatch, some, set, get, cloneDeep, has as _has, merge, mergeWith, isUndefined, omitBy } from 'lodash-es';
77

8+
const antdVersion = "5.22.6"
89
export const _set = set;
910
export const _get = get;
1011
export const _cloneDeep = cloneDeep;
@@ -215,3 +216,5 @@ export function safeJsonStringify(obj: Object) {
215216
return null;
216217
}
217218
}
219+
220+
export * from './flow'

0 commit comments

Comments
 (0)