Showing
8 changed files
with
107 additions
and
46 deletions
1 | -import type { Connection } from '@vue-flow/core'; | |
1 | +import type { Connection, Edge } from '@vue-flow/core'; | |
2 | 2 | import { EdgeTypeEnum } from '../enum'; |
3 | 3 | import type { EdgeData } from '../types/node'; |
4 | 4 | import { buildUUID } from '/@/utils/uuid'; |
5 | 5 | |
6 | 6 | export function useAddEdges() { |
7 | - const getAddedgesParams = (params: Connection, data: string | string[] | any) => { | |
7 | + const getAddedgesParams = (params: Connection | Edge, data: string | string[] | any) => { | |
8 | 8 | return { type: EdgeTypeEnum.CUSTOM, data: { data } as EdgeData, id: buildUUID(), ...params }; |
9 | 9 | }; |
10 | 10 | ... | ... |
1 | -import { GraphEdge, GraphNode, VueFlowStore } from '@vue-flow/core'; | |
1 | +import { GraphEdge, GraphNode, VueFlowStore, pointToRendererPoint } from '@vue-flow/core'; | |
2 | 2 | import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste'; |
3 | 3 | import { RuleContextMenuEnum } from './useRuleChainContextMenu'; |
4 | 4 | import { useAddNodes } from './useAddNodes'; |
... | ... | @@ -65,15 +65,15 @@ export function useContextMenuAction() { |
65 | 65 | const copy = (params: HandleContextMenuActionParamsType) => { |
66 | 66 | const { node } = params; |
67 | 67 | if (!node) return; |
68 | - const { position, data } = node; | |
68 | + const { position, data, id } = node; | |
69 | 69 | const { getAddNodesParams } = useAddNodes(); |
70 | 70 | const { x, y } = position; |
71 | 71 | |
72 | - const value = getAddNodesParams(position, data, { id: buildUUID() }); | |
72 | + const value = getAddNodesParams(position, data, { id }); | |
73 | 73 | |
74 | 74 | setRuleNodeCache({ |
75 | 75 | nodes: [value], |
76 | - originX: x + NODE_WIDTH / 2 + x, | |
76 | + originX: x + NODE_WIDTH / 2, | |
77 | 77 | originY: y + NODE_HEIGHT / 2, |
78 | 78 | }); |
79 | 79 | }; |
... | ... | @@ -81,24 +81,49 @@ export function useContextMenuAction() { |
81 | 81 | const paste = (params: HandleContextMenuActionParamsType) => { |
82 | 82 | const { event, flowActionType, useSaveAndRedoActionType } = params; |
83 | 83 | const { triggerChange } = useSaveAndRedoActionType || {}; |
84 | + | |
84 | 85 | const { getAddNodesParams } = useAddNodes(); |
85 | 86 | const { getAddedgesParams } = useAddEdges(); |
86 | - const clientX = (event as MouseEvent).offsetX; | |
87 | - const clientY = (event as MouseEvent).offsetY; | |
88 | 87 | |
89 | 88 | const { edges = [], nodes = [], originX, originY } = getRuleNodeCache(); |
90 | 89 | |
90 | + const { x: clientX, y: clientY } = pointToRendererPoint( | |
91 | + { x: (event as MouseEvent).offsetX, y: (event as MouseEvent).offsetY }, | |
92 | + flowActionType!.getViewport(), | |
93 | + false, | |
94 | + [25, 25] | |
95 | + ); | |
96 | + | |
97 | + const xAxisMoveDistance = clientX - originX!; | |
98 | + const yAxisMoveDistance = clientY - originY!; | |
99 | + | |
100 | + const newEdges = edges.map((edge) => | |
101 | + getAddedgesParams({ ...edge, id: buildUUID() }, edge.data) | |
102 | + ); | |
103 | + | |
91 | 104 | const newNode = nodes.map((node) => { |
92 | - const { position, data, id } = node; | |
105 | + const { position, data, id: oldId } = node; | |
93 | 106 | const { x, y } = position; |
107 | + const newId = buildUUID(); | |
94 | 108 | |
95 | - const newX = clientX - originX! + x + NODE_WIDTH / 2; | |
96 | - const newY = clientY - originY! + y + NODE_HEIGHT / 2; | |
109 | + const newX = xAxisMoveDistance + x; | |
110 | + const newY = yAxisMoveDistance + y; | |
97 | 111 | |
98 | - return getAddNodesParams({ x: newX, y: newY }, { ...data, created: false }, { id }); | |
99 | - }); | |
112 | + for (const connection of newEdges || []) { | |
113 | + if (connection.source.includes(oldId)) { | |
114 | + connection.source = newId; | |
115 | + connection.sourceHandle = connection.sourceHandle?.replaceAll(oldId, newId); | |
116 | + continue; | |
117 | + } | |
100 | 118 | |
101 | - const newEdges = edges.map((edge) => getAddedgesParams(edge, edge.data)); | |
119 | + if (connection.target.includes(oldId)) { | |
120 | + connection.target = newId; | |
121 | + connection.targetHandle = connection.targetHandle?.replaceAll(oldId, newId); | |
122 | + } | |
123 | + } | |
124 | + | |
125 | + return getAddNodesParams({ x: newX, y: newY }, { ...data, created: false }, { id: newId }); | |
126 | + }); | |
102 | 127 | |
103 | 128 | flowActionType?.addNodes(newNode); |
104 | 129 | flowActionType?.addEdges(newEdges); |
... | ... | @@ -153,23 +178,9 @@ export function useContextMenuAction() { |
153 | 178 | ); |
154 | 179 | |
155 | 180 | const nodes = unref(flowActionType?.getSelectedNodes)?.map((node) => { |
156 | - const { id: oldId } = node; | |
157 | - const newId = buildUUID(); | |
158 | - | |
159 | - for (const connection of edges || []) { | |
160 | - if (connection.source.includes(oldId)) { | |
161 | - connection.source = newId; | |
162 | - connection.sourceHandle = connection.sourceHandle?.replaceAll(oldId, newId); | |
163 | - continue; | |
164 | - } | |
165 | - | |
166 | - if (connection.target.includes(oldId)) { | |
167 | - connection.target = newId; | |
168 | - connection.targetHandle = connection.targetHandle?.replaceAll(oldId, newId); | |
169 | - } | |
170 | - } | |
181 | + const { id } = node; | |
171 | 182 | |
172 | - return getAddNodesParams(node.position, toRaw(unref(node.data)), { id: newId }); | |
183 | + return getAddNodesParams(node.position, toRaw(unref(node.data)), { id }); | |
173 | 184 | }); |
174 | 185 | |
175 | 186 | const originRect = getElementsCenter(unref(flowActionType?.getSelectedNodes) || []); | ... | ... |
... | ... | @@ -51,18 +51,17 @@ export enum RuleChainContextMenuIconEnum { |
51 | 51 | // LINK = 'material-symbols:trending-flat', |
52 | 52 | } |
53 | 53 | |
54 | -export enum RuleChainContextMenuShortcutKeyEnum { | |
55 | - DELETE = 'Ctrl(⌘) X', | |
54 | +export enum RuleChainContextMenuShortcutKeyEnum {} | |
55 | +// DELETE = 'Ctrl(⌘) X', | |
56 | 56 | |
57 | - SELECT_COPY = 'Ctrl(⌘) C', | |
58 | - PASTE = 'Ctrl(⌘) V', | |
59 | - UNSELECTED = 'Esc', | |
60 | - DELETE_SELECT = 'Del', | |
61 | - APPLY_CHANGE = 'Ctrl(⌘) S', | |
62 | - UNDO_CHANGE = 'Ctrl(⌘) Z', | |
57 | +// SELECT_COPY = 'Ctrl(⌘) C', | |
58 | +// PASTE = 'Ctrl(⌘) V', | |
59 | +// UNSELECTED = 'Esc', | |
60 | +// DELETE_SELECT = 'Del', | |
61 | +// APPLY_CHANGE = 'Ctrl(⌘) S', | |
62 | +// UNDO_CHANGE = 'Ctrl(⌘) Z', | |
63 | 63 | |
64 | - SELECT_ALL = 'Ctrl(⌘) A', | |
65 | -} | |
64 | +// SELECT_ALL = 'Ctrl(⌘) A', | |
66 | 65 | |
67 | 66 | const getMenuItem = (key: RuleContextMenuEnum, handler: Fn, disabled = false) => { |
68 | 67 | return { | ... | ... |
... | ... | @@ -92,6 +92,9 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
92 | 92 | |
93 | 93 | if (!validateList.every((item) => item(connection, elements))) return false; |
94 | 94 | |
95 | + if (validateCircularreference(elements.sourceNode!, elements.targetNode, elements.edges)) | |
96 | + return false; | |
97 | + | |
95 | 98 | return true; |
96 | 99 | }, |
97 | 100 | }); |
... | ... | @@ -134,7 +137,6 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
134 | 137 | |
135 | 138 | triggerChange(); |
136 | 139 | }); |
137 | - | |
138 | 140 | onPaneReady(async () => { |
139 | 141 | setViewport({ x: 0, y: 0, zoom: 1 }); |
140 | 142 | }); |
... | ... | @@ -238,6 +240,55 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
238 | 240 | return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; |
239 | 241 | } |
240 | 242 | |
243 | + /** | |
244 | + * @description 验证是否循环引用 | |
245 | + */ | |
246 | + function validateCircularreference( | |
247 | + sourceNode: GraphNode, | |
248 | + targetNode: GraphNode, | |
249 | + edges: GraphEdge[] | |
250 | + ) { | |
251 | + const sourceId = sourceNode.id; | |
252 | + const targetId = targetNode.id; | |
253 | + | |
254 | + const getAllIndex = ( | |
255 | + edges: GraphEdge[], | |
256 | + validate: (item: GraphEdge, index: number) => boolean | |
257 | + ) => { | |
258 | + const indexList: number[] = []; | |
259 | + | |
260 | + for (let i = 0; i < edges.length; i++) { | |
261 | + const item = edges[i]; | |
262 | + if (validate(item, i)) { | |
263 | + indexList.push(i); | |
264 | + } | |
265 | + } | |
266 | + | |
267 | + return indexList; | |
268 | + }; | |
269 | + | |
270 | + const validate = (source: string, startSource: string) => { | |
271 | + const nextNodesIndex = getAllIndex(edges, (item) => item.source === source); | |
272 | + | |
273 | + if (!nextNodesIndex.length) return false; | |
274 | + | |
275 | + for (const nextNodeIndex of nextNodesIndex) { | |
276 | + const nextNode = edges[nextNodeIndex]; | |
277 | + if (nextNode && nextNode.target === startSource) { | |
278 | + return true; | |
279 | + } | |
280 | + return validate(nextNode.target, startSource); | |
281 | + } | |
282 | + }; | |
283 | + | |
284 | + return validate(targetId, sourceId); | |
285 | + } | |
286 | + | |
287 | + /** | |
288 | + * @description 处理最大链接点 | |
289 | + * @param sourceNode | |
290 | + * @returns | |
291 | + */ | |
241 | 292 | function handleMaxConnectionPoint(sourceNode?: GraphNode) { |
242 | 293 | if (!sourceNode) return; |
243 | 294 | ... | ... |
... | ... | @@ -19,11 +19,11 @@ export function useSaveAndRedo() { |
19 | 19 | |
20 | 20 | const redoDataRef = ref<Elements>([]); |
21 | 21 | |
22 | - const route = useRoute(); | |
22 | + const ROUTE = useRoute(); | |
23 | 23 | |
24 | 24 | const debugMarker = ref(false); |
25 | 25 | |
26 | - const getRuleChainId = computed(() => (route.params as Record<'id', string>).id); | |
26 | + const getRuleChainId = computed(() => (ROUTE.params as Record<'id', string>).id); | |
27 | 27 | |
28 | 28 | const ruleChainDetail = ref<RuleChainDetail>(); |
29 | 29 | ... | ... |