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 | import { EdgeTypeEnum } from '../enum'; | 2 | import { EdgeTypeEnum } from '../enum'; |
3 | import type { EdgeData } from '../types/node'; | 3 | import type { EdgeData } from '../types/node'; |
4 | import { buildUUID } from '/@/utils/uuid'; | 4 | import { buildUUID } from '/@/utils/uuid'; |
5 | 5 | ||
6 | export function useAddEdges() { | 6 | export function useAddEdges() { |
7 | - const getAddedgesParams = (params: Connection, data: string | string[] | any) => { | 7 | + const getAddedgesParams = (params: Connection | Edge, data: string | string[] | any) => { |
8 | return { type: EdgeTypeEnum.CUSTOM, data: { data } as EdgeData, id: buildUUID(), ...params }; | 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 | import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste'; | 2 | import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste'; |
3 | import { RuleContextMenuEnum } from './useRuleChainContextMenu'; | 3 | import { RuleContextMenuEnum } from './useRuleChainContextMenu'; |
4 | import { useAddNodes } from './useAddNodes'; | 4 | import { useAddNodes } from './useAddNodes'; |
@@ -65,15 +65,15 @@ export function useContextMenuAction() { | @@ -65,15 +65,15 @@ export function useContextMenuAction() { | ||
65 | const copy = (params: HandleContextMenuActionParamsType) => { | 65 | const copy = (params: HandleContextMenuActionParamsType) => { |
66 | const { node } = params; | 66 | const { node } = params; |
67 | if (!node) return; | 67 | if (!node) return; |
68 | - const { position, data } = node; | 68 | + const { position, data, id } = node; |
69 | const { getAddNodesParams } = useAddNodes(); | 69 | const { getAddNodesParams } = useAddNodes(); |
70 | const { x, y } = position; | 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 | setRuleNodeCache({ | 74 | setRuleNodeCache({ |
75 | nodes: [value], | 75 | nodes: [value], |
76 | - originX: x + NODE_WIDTH / 2 + x, | 76 | + originX: x + NODE_WIDTH / 2, |
77 | originY: y + NODE_HEIGHT / 2, | 77 | originY: y + NODE_HEIGHT / 2, |
78 | }); | 78 | }); |
79 | }; | 79 | }; |
@@ -81,24 +81,49 @@ export function useContextMenuAction() { | @@ -81,24 +81,49 @@ export function useContextMenuAction() { | ||
81 | const paste = (params: HandleContextMenuActionParamsType) => { | 81 | const paste = (params: HandleContextMenuActionParamsType) => { |
82 | const { event, flowActionType, useSaveAndRedoActionType } = params; | 82 | const { event, flowActionType, useSaveAndRedoActionType } = params; |
83 | const { triggerChange } = useSaveAndRedoActionType || {}; | 83 | const { triggerChange } = useSaveAndRedoActionType || {}; |
84 | + | ||
84 | const { getAddNodesParams } = useAddNodes(); | 85 | const { getAddNodesParams } = useAddNodes(); |
85 | const { getAddedgesParams } = useAddEdges(); | 86 | const { getAddedgesParams } = useAddEdges(); |
86 | - const clientX = (event as MouseEvent).offsetX; | ||
87 | - const clientY = (event as MouseEvent).offsetY; | ||
88 | 87 | ||
89 | const { edges = [], nodes = [], originX, originY } = getRuleNodeCache(); | 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 | const newNode = nodes.map((node) => { | 104 | const newNode = nodes.map((node) => { |
92 | - const { position, data, id } = node; | 105 | + const { position, data, id: oldId } = node; |
93 | const { x, y } = position; | 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 | flowActionType?.addNodes(newNode); | 128 | flowActionType?.addNodes(newNode); |
104 | flowActionType?.addEdges(newEdges); | 129 | flowActionType?.addEdges(newEdges); |
@@ -153,23 +178,9 @@ export function useContextMenuAction() { | @@ -153,23 +178,9 @@ export function useContextMenuAction() { | ||
153 | ); | 178 | ); |
154 | 179 | ||
155 | const nodes = unref(flowActionType?.getSelectedNodes)?.map((node) => { | 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 | const originRect = getElementsCenter(unref(flowActionType?.getSelectedNodes) || []); | 186 | const originRect = getElementsCenter(unref(flowActionType?.getSelectedNodes) || []); |
@@ -6,7 +6,7 @@ export function useInputNode() { | @@ -6,7 +6,7 @@ export function useInputNode() { | ||
6 | const { getAddNodesParams } = useAddNodes(); | 6 | const { getAddNodesParams } = useAddNodes(); |
7 | 7 | ||
8 | const newNode = getAddNodesParams( | 8 | const newNode = getAddNodesParams( |
9 | - { x: 80, y: 50 }, | 9 | + { x: 75, y: 50 }, |
10 | { | 10 | { |
11 | ...new InputConfig(), | 11 | ...new InputConfig(), |
12 | data: { | 12 | data: { |
@@ -51,18 +51,17 @@ export enum RuleChainContextMenuIconEnum { | @@ -51,18 +51,17 @@ export enum RuleChainContextMenuIconEnum { | ||
51 | // LINK = 'material-symbols:trending-flat', | 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 | const getMenuItem = (key: RuleContextMenuEnum, handler: Fn, disabled = false) => { | 66 | const getMenuItem = (key: RuleContextMenuEnum, handler: Fn, disabled = false) => { |
68 | return { | 67 | return { |
@@ -92,6 +92,9 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -92,6 +92,9 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
92 | 92 | ||
93 | if (!validateList.every((item) => item(connection, elements))) return false; | 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 | return true; | 98 | return true; |
96 | }, | 99 | }, |
97 | }); | 100 | }); |
@@ -134,7 +137,6 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -134,7 +137,6 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
134 | 137 | ||
135 | triggerChange(); | 138 | triggerChange(); |
136 | }); | 139 | }); |
137 | - | ||
138 | onPaneReady(async () => { | 140 | onPaneReady(async () => { |
139 | setViewport({ x: 0, y: 0, zoom: 1 }); | 141 | setViewport({ x: 0, y: 0, zoom: 1 }); |
140 | }); | 142 | }); |
@@ -238,6 +240,55 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -238,6 +240,55 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
238 | return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; | 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 | function handleMaxConnectionPoint(sourceNode?: GraphNode) { | 292 | function handleMaxConnectionPoint(sourceNode?: GraphNode) { |
242 | if (!sourceNode) return; | 293 | if (!sourceNode) return; |
243 | 294 |
@@ -19,11 +19,11 @@ export function useSaveAndRedo() { | @@ -19,11 +19,11 @@ export function useSaveAndRedo() { | ||
19 | 19 | ||
20 | const redoDataRef = ref<Elements>([]); | 20 | const redoDataRef = ref<Elements>([]); |
21 | 21 | ||
22 | - const route = useRoute(); | 22 | + const ROUTE = useRoute(); |
23 | 23 | ||
24 | const debugMarker = ref(false); | 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 | const ruleChainDetail = ref<RuleChainDetail>(); | 28 | const ruleChainDetail = ref<RuleChainDetail>(); |
29 | 29 |