Commit f00aade95472d176bc9c0264d2f65dcb96316c43

Authored by ww
1 parent ae4f2379

fix: 修复规则链可以形成循环链

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) || []);
... ...
... ... @@ -6,7 +6,7 @@ export function useInputNode() {
6 6 const { getAddNodesParams } = useAddNodes();
7 7
8 8 const newNode = getAddNodesParams(
9   - { x: 80, y: 50 },
  9 + { x: 75, y: 50 },
10 10 {
11 11 ...new InputConfig(),
12 12 data: {
... ...
... ... @@ -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
... ...
... ... @@ -26,7 +26,7 @@
26 26 <template>
27 27 <BasicDrawer
28 28 v-model:visible="visible"
29   - width="700px"
  29 + width="40%"
30 30 showFooter
31 31 showOkBtn
32 32 showCancelBtn
... ...
... ... @@ -79,7 +79,7 @@
79 79 <template>
80 80 <BasicDrawer
81 81 v-model:visible="visible"
82   - width="700px"
  82 + width="40%"
83 83 showFooter
84 84 showCancelBtn
85 85 showOkBtn
... ...