Commit a0634f38362a7ea0358f3c3492147983389efbdc
Merge branch 'feat/rule-chain' into 'main_dev'
feat: 选择创建规则链 See merge request yunteng/thingskit-front!855
Showing
11 changed files
with
414 additions
and
149 deletions
1 | 1 | import { Ref, toRaw, unref } from 'vue'; |
2 | -import { BasicNodeBindData, NodeData } from '../types/node'; | |
3 | -import { Elements, GraphNode } from '@vue-flow/core'; | |
4 | -import { RuleChainType } from '../types/ruleNode'; | |
2 | +import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; | |
3 | +import { Elements, GraphEdge, GraphNode } from '@vue-flow/core'; | |
4 | +import { ConnectionItemType, RuleChainType } from '../types/ruleNode'; | |
5 | 5 | import { allComponents } from '../packages'; |
6 | 6 | import { RuleNodeTypeEnum } from '../packages/index.type'; |
7 | 7 | import { buildUUID } from '/@/utils/uuid'; |
... | ... | @@ -9,6 +9,9 @@ import { isNullOrUnDef } from '/@/utils/is'; |
9 | 9 | import { useAddNodes } from './useAddNodes'; |
10 | 10 | import { useAddEdges } from './useAddEdges'; |
11 | 11 | import { RuleChainEntityType } from '../enum/entity'; |
12 | +import { EntryCategoryComponentEnum } from '../enum/category'; | |
13 | + | |
14 | +const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; | |
12 | 15 | |
13 | 16 | export function useBasicDataTransform() { |
14 | 17 | const nodeConfigMap = new Map<string, NodeData>(); |
... | ... | @@ -24,7 +27,7 @@ export function useBasicDataTransform() { |
24 | 27 | } |
25 | 28 | |
26 | 29 | function mergeData(data: NodeData['data'], nodeData: NodeData, node: GraphNode) { |
27 | - const { x: layoutX, y: layoutY } = node.computedPosition; | |
30 | + const { x: layoutX, y: layoutY } = node.position; | |
28 | 31 | |
29 | 32 | return { |
30 | 33 | debugMode: !!data?.debugMode, |
... | ... | @@ -172,8 +175,126 @@ export function useBasicDataTransform() { |
172 | 175 | |
173 | 176 | initNodeConfigMap(); |
174 | 177 | |
178 | + /** | |
179 | + * @description 保存连接信息 | |
180 | + */ | |
181 | + function getConnections( | |
182 | + nodesRef: Ref<GraphNode[]> | GraphNode[], | |
183 | + edges: Ref<GraphEdge[]> | GraphEdge[] | |
184 | + ) { | |
185 | + const nodeIndexMap = new Map(); | |
186 | + | |
187 | + const connections: ConnectionItemType[] = []; | |
188 | + | |
189 | + unref(nodesRef).forEach((item, index) => { | |
190 | + nodeIndexMap.set(item.id, index); | |
191 | + }); | |
192 | + | |
193 | + for (const item of unref(edges)) { | |
194 | + const { data, target, source } = item; | |
195 | + const { data: bindData } = data as EdgeData; | |
196 | + const { type } = bindData || {}; | |
197 | + const fromIndex = nodeIndexMap.get(source); | |
198 | + const toIndex = nodeIndexMap.get(target); | |
199 | + type?.forEach((key) => { | |
200 | + connections.push({ fromIndex, toIndex, type: key }); | |
201 | + }); | |
202 | + } | |
203 | + | |
204 | + return connections; | |
205 | + } | |
206 | + | |
207 | + function getNodes(nodesRef: Ref<GraphNode[]> | GraphNode[], removeId: boolean) { | |
208 | + const nodes: BasicNodeBindData[] = []; | |
209 | + | |
210 | + for (const node of unref(nodesRef)) { | |
211 | + const nodeData = node.data as NodeData; | |
212 | + | |
213 | + if (ignoreNodeKeys.includes(nodeData.config?.key as string)) continue; | |
214 | + | |
215 | + const data = nodeData.data; | |
216 | + | |
217 | + nodes.push( | |
218 | + Object.assign( | |
219 | + mergeData(data, nodeData, node), | |
220 | + nodeData.created && !removeId | |
221 | + ? ({ | |
222 | + id: { id: node.id, entityType: RuleChainEntityType.RULE_NODE }, | |
223 | + } as BasicNodeBindData) | |
224 | + : {} | |
225 | + ) | |
226 | + ); | |
227 | + } | |
228 | + | |
229 | + return nodes; | |
230 | + } | |
231 | + | |
232 | + function getFirsetNodeIndex( | |
233 | + nodesRef: Ref<GraphNode[]> | GraphNode[], | |
234 | + edges: Ref<GraphEdge[]> | GraphEdge[] | |
235 | + ) { | |
236 | + const inputNode = unref(edges).find( | |
237 | + (item) => (item.sourceNode.data as NodeData).config?.key === EntryCategoryComponentEnum.INPUT | |
238 | + ); | |
239 | + | |
240 | + if (inputNode) { | |
241 | + const targetId = inputNode.target; | |
242 | + const index = unref(nodesRef).findIndex((item) => item.id === targetId); | |
243 | + return index; | |
244 | + } | |
245 | + } | |
246 | + | |
247 | + function combineData( | |
248 | + nodesRef: Ref<GraphNode[]> | GraphNode[] = [], | |
249 | + edgesRef: Ref<GraphEdge[]> | GraphEdge[] = [], | |
250 | + removeId = false | |
251 | + ) { | |
252 | + const extraIgnoreNodeRef = unref(nodesRef).filter( | |
253 | + (item) => !ignoreNodeKeys.includes((item.data as NodeData).config?.key as string) | |
254 | + ); | |
255 | + | |
256 | + const connections = getConnections(extraIgnoreNodeRef, edgesRef); | |
257 | + | |
258 | + const nodes = getNodes(extraIgnoreNodeRef, removeId); | |
259 | + | |
260 | + const firstNodeIndex = getFirsetNodeIndex(extraIgnoreNodeRef, edgesRef); | |
261 | + | |
262 | + return { connections, nodes, firstNodeIndex }; | |
263 | + } | |
264 | + | |
265 | + function validateCanCreateRuleChain( | |
266 | + nodes: Ref<GraphNode[]> | GraphNode[] = [], | |
267 | + edges: Ref<GraphEdge[]> | GraphEdge[] = [] | |
268 | + ) { | |
269 | + const rootNode: GraphNode[] = []; | |
270 | + | |
271 | + let flag = true; | |
272 | + for (const node of unref(nodes)) { | |
273 | + const list = unref(edges).filter( | |
274 | + (edge) => edge.source === node.id || edge.target === node.id | |
275 | + ); | |
276 | + | |
277 | + if (!list.length) { | |
278 | + flag = false; | |
279 | + break; | |
280 | + } | |
281 | + | |
282 | + if (!list.filter((edge) => edge.target === node.id).length) { | |
283 | + if (!rootNode.length) rootNode.push(node); | |
284 | + else { | |
285 | + flag = false; | |
286 | + break; | |
287 | + } | |
288 | + } | |
289 | + } | |
290 | + | |
291 | + return { flag, firstNode: rootNode[0] || null }; | |
292 | + } | |
293 | + | |
175 | 294 | return { |
176 | 295 | mergeData, |
296 | + combineData, | |
177 | 297 | deconstructionData, |
298 | + validateCanCreateRuleChain, | |
178 | 299 | }; |
179 | 300 | } | ... | ... |
... | ... | @@ -3,11 +3,19 @@ import { getRuleNodeCache, setRuleNodeCache } from './useRuleChainCache'; |
3 | 3 | import { RuleContextMenuEnum } from './useRuleChainContextMenu'; |
4 | 4 | import { useAddNodes } from './useAddNodes'; |
5 | 5 | import { buildUUID } from '/@/utils/uuid'; |
6 | -import { toRaw, unref } from 'vue'; | |
6 | +import { Ref, toRaw, unref } from 'vue'; | |
7 | 7 | import { useSaveAndRedo } from './useSaveAndRedo'; |
8 | 8 | import { isUnDef } from '/@/utils/is'; |
9 | 9 | import { useAddEdges } from './useAddEdges'; |
10 | 10 | import { EdgeData } from '../types/node'; |
11 | +import { CreateNodeModal } from '../src/components/CreateNodeModal'; | |
12 | +import { CreateEdgeModal } from '../src/components/CreateEdgeModal'; | |
13 | +import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer'; | |
14 | +import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer'; | |
15 | +import { CreateRuleChainModal } from '../src/components/CreateRuleChainModal'; | |
16 | +import { useBasicDataTransform } from './useBasicDataTransform'; | |
17 | +import { cloneDeep } from 'lodash-es'; | |
18 | +import { useNewNode } from './useNewNode'; | |
11 | 19 | |
12 | 20 | interface HandleContextMenuActionParamsType { |
13 | 21 | menuType: RuleContextMenuEnum; |
... | ... | @@ -16,18 +24,58 @@ interface HandleContextMenuActionParamsType { |
16 | 24 | node?: GraphNode; |
17 | 25 | edge?: GraphEdge; |
18 | 26 | useSaveAndRedoActionType?: ReturnType<typeof useSaveAndRedo>; |
27 | + modalActionType: { | |
28 | + createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; | |
29 | + createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; | |
30 | + updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; | |
31 | + updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; | |
32 | + createRuleChainModalActionType: Ref<Nullable<InstanceType<typeof CreateRuleChainModal>>>; | |
33 | + }; | |
34 | +} | |
35 | + | |
36 | +export function transformToRuleChain( | |
37 | + nodesRef: Ref<GraphNode[]> | GraphNode[] = [], | |
38 | + edgesRef: Ref<GraphEdge[]> | GraphEdge[] = [] | |
39 | +) { | |
40 | + const nodeMap = new Map<string, GraphNode>(); | |
41 | + const { combineData, validateCanCreateRuleChain } = useBasicDataTransform(); | |
42 | + | |
43 | + nodesRef = cloneDeep(unref(nodesRef)); | |
44 | + edgesRef = cloneDeep(unref(edgesRef)); | |
45 | + | |
46 | + unref(nodesRef).forEach((node) => nodeMap.set(node.id, node)); | |
47 | + const outputEdges = unref(edgesRef).filter((edge) => !nodeMap.has(edge.target)); | |
48 | + const outputEdgesId = outputEdges.map((edge) => edge.id); | |
49 | + | |
50 | + const { getOutputNodeConfig } = useNewNode(); | |
51 | + const outputNode = outputEdges.map((edge) => { | |
52 | + const id = buildUUID(); | |
53 | + const name = (edge.data as EdgeData).data?.type?.join(' / ') || ''; | |
54 | + edge.target = id; | |
55 | + return getOutputNodeConfig(name, edge.targetNode.position, id); | |
56 | + }); | |
57 | + | |
58 | + nodesRef = [...nodesRef, ...(outputNode as GraphNode[])]; | |
59 | + | |
60 | + const { connections, nodes } = combineData(nodesRef, edgesRef, true); | |
61 | + | |
62 | + const { firstNode } = validateCanCreateRuleChain(nodesRef, edgesRef); | |
63 | + | |
64 | + const firstNodeIndex = nodesRef.findIndex((node) => node.id === firstNode.id); | |
65 | + | |
66 | + return { connections, nodes, firstNodeIndex, outputEdgesId }; | |
19 | 67 | } |
20 | 68 | |
21 | 69 | export const NODE_WIDTH = 176; |
22 | 70 | export const NODE_HEIGHT = 48; |
23 | 71 | |
24 | -function getElementsCenter(nodes: GraphNode[]) { | |
72 | +function getElementsCenter(nodes: Ref<GraphNode[]> | GraphNode[] = []) { | |
25 | 73 | let leftTopX: number | undefined; |
26 | 74 | let leftTopY: number | undefined; |
27 | 75 | let rightBottomX: number | undefined; |
28 | 76 | let rightBottomY: number | undefined; |
29 | 77 | |
30 | - for (const node of nodes) { | |
78 | + for (const node of unref(nodes)) { | |
31 | 79 | const { position } = node; |
32 | 80 | const { x, y } = position; |
33 | 81 | if (isUnDef(leftTopX)) { |
... | ... | @@ -200,6 +248,37 @@ export function useContextMenuAction() { |
200 | 248 | useSaveAndRedoActionType?.handleRedoChange(flowActionType!); |
201 | 249 | }; |
202 | 250 | |
251 | + const createRuleChain = async (_params: HandleContextMenuActionParamsType) => { | |
252 | + // const { useSaveAndRedoActionType, modalActionType, flowActionType } = params; | |
253 | + // const { createRuleChainModalActionType } = modalActionType; | |
254 | + // const result = (await unref(createRuleChainModalActionType)?.openCreateRuleChainModal()) as { | |
255 | + // name: string; | |
256 | + // additionalInfo: { description: string }; | |
257 | + // }; | |
258 | + // const ruleChainDetail = await saveRuleChainDetail( | |
259 | + // Object.assign(result, { debugger: false, type: 'CORE' }) as Partial<RuleChainDetail> | |
260 | + // ); | |
261 | + // const selectedNodes = unref(flowActionType?.getSelectedNodes); | |
262 | + // const selectedEdges = unref(flowActionType?.getSelectedEdges); | |
263 | + // const { firstNodeIndex, connections, nodes, outputEdgesId } = transformToRuleChain( | |
264 | + // selectedNodes, | |
265 | + // selectedEdges | |
266 | + // ); | |
267 | + // await saveRuleChainData({ | |
268 | + // firstNodeIndex, | |
269 | + // connections, | |
270 | + // nodes, | |
271 | + // ruleChainId: ruleChainDetail.id, | |
272 | + // }); | |
273 | + // const outputEdges = outputEdgesId.map((id) => flowActionType?.findEdge(id)); | |
274 | + // console.log(getElementsCenter(unref(selectedNodes))); | |
275 | + // const { originX, originY } = getElementsCenter(unref(selectedNodes)); | |
276 | + // const {} = useNewNode(); | |
277 | + // flowActionType?.removeNodes(selectedNodes || []); | |
278 | + // flowActionType?.removeEdges(selectedEdges || []); | |
279 | + // useSaveAndRedoActionType?.triggerChange(); | |
280 | + }; | |
281 | + | |
203 | 282 | const handleContextMenuAction = (params: HandleContextMenuActionParamsType) => { |
204 | 283 | const { menuType } = params; |
205 | 284 | |
... | ... | @@ -213,6 +292,7 @@ export function useContextMenuAction() { |
213 | 292 | [RuleContextMenuEnum.SELECT_COPY]: selectCopy, |
214 | 293 | [RuleContextMenuEnum.APPLY_CHANGE]: applyChange, |
215 | 294 | [RuleContextMenuEnum.UNDO_CHANGE]: undoChange, |
295 | + [RuleContextMenuEnum.CREATE_RULE_CHAIN]: createRuleChain, | |
216 | 296 | }; |
217 | 297 | |
218 | 298 | if (handlerMapping[menuType]) { | ... | ... |
... | ... | @@ -5,6 +5,7 @@ import type { CreateNodeModal } from '../src/components/CreateNodeModal'; |
5 | 5 | import type { CreateEdgeModal } from '../src/components/CreateEdgeModal'; |
6 | 6 | import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer'; |
7 | 7 | import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer'; |
8 | +import { CreateRuleChainModal } from '../src/components/CreateRuleChainModal'; | |
8 | 9 | |
9 | 10 | const SYMBOL = Symbol('flow-context'); |
10 | 11 | |
... | ... | @@ -30,6 +31,11 @@ interface FlowContextOptionsType { |
30 | 31 | updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; |
31 | 32 | |
32 | 33 | /** |
34 | + * @description 创建规则链 actions | |
35 | + */ | |
36 | + createRuleChainModalActionType: Ref<Nullable<InstanceType<typeof CreateRuleChainModal>>>; | |
37 | + | |
38 | + /** | |
33 | 39 | * @description vue flow store |
34 | 40 | */ |
35 | 41 | flowActionType: VueFlowStore; | ... | ... |
src/views/rule/designer/hook/useNewNode.ts
renamed from
src/views/rule/designer/hook/useInputNode.ts
1 | +import { XYPosition } from '@vue-flow/core'; | |
1 | 2 | import { Config as InputConfig } from '../packages/Entry/Input/config'; |
3 | +import { Config as OutputConfig } from '../packages/Flow/Output/config'; | |
4 | +import { Config as RuleChainConfig } from '../packages/Flow/RuleChain/config'; | |
2 | 5 | import { useAddNodes } from './useAddNodes'; |
3 | 6 | |
4 | -export function useInputNode() { | |
7 | +export function useNewNode() { | |
5 | 8 | const getInputNodeConfig = (id?: string) => { |
6 | 9 | const { getAddNodesParams } = useAddNodes(); |
7 | 10 | |
... | ... | @@ -20,5 +23,38 @@ export function useInputNode() { |
20 | 23 | return newNode; |
21 | 24 | }; |
22 | 25 | |
23 | - return { getInputNodeConfig }; | |
26 | + const getOutputNodeConfig = (name: string, position: XYPosition, id?: string) => { | |
27 | + const { getAddNodesParams } = useAddNodes(); | |
28 | + | |
29 | + const newNode = getAddNodesParams( | |
30 | + position, | |
31 | + { | |
32 | + ...new OutputConfig(), | |
33 | + data: { | |
34 | + name, | |
35 | + }, | |
36 | + }, | |
37 | + { id, draggable: false, selectable: false } | |
38 | + ); | |
39 | + | |
40 | + return newNode; | |
41 | + }; | |
42 | + | |
43 | + const getRuleChainNodeConfig = (name: string, position: XYPosition, id?: string) => { | |
44 | + const { getAddNodesParams } = useAddNodes(); | |
45 | + const newNode = getAddNodesParams( | |
46 | + position, | |
47 | + { | |
48 | + ...new RuleChainConfig(), | |
49 | + data: { name }, | |
50 | + }, | |
51 | + { | |
52 | + id, | |
53 | + } | |
54 | + ); | |
55 | + | |
56 | + return newNode; | |
57 | + }; | |
58 | + | |
59 | + return { getInputNodeConfig, getOutputNodeConfig, getRuleChainNodeConfig }; | |
24 | 60 | } | ... | ... |
... | ... | @@ -27,14 +27,19 @@ import { RuleChainDetail } from '../types/ruleNode'; |
27 | 27 | import { useContextMenuAction } from './useContextMenuAction'; |
28 | 28 | import { useSaveAndRedo } from './useSaveAndRedo'; |
29 | 29 | import { EntryCategoryComponentEnum } from '../enum/category'; |
30 | +import { CreateRuleChainModal } from '../src/components/CreateRuleChainModal'; | |
31 | +import { useBasicDataTransform } from './useBasicDataTransform'; | |
30 | 32 | |
31 | 33 | interface UseRuleFlowOptionsType { |
32 | 34 | id: string; |
33 | 35 | ruleChainDetail: Ref<RuleChainDetail | undefined>; |
34 | - createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; | |
35 | - createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; | |
36 | - updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; | |
37 | - updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; | |
36 | + modalActionType: { | |
37 | + createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; | |
38 | + createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; | |
39 | + updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; | |
40 | + updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; | |
41 | + createRuleChainModalActionType: Ref<Nullable<InstanceType<typeof CreateRuleChainModal>>>; | |
42 | + }; | |
38 | 43 | useSaveAndRedoActionType: ReturnType<typeof useSaveAndRedo>; |
39 | 44 | } |
40 | 45 | |
... | ... | @@ -47,14 +52,10 @@ const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => |
47 | 52 | }; |
48 | 53 | |
49 | 54 | export function useRuleFlow(options: UseRuleFlowOptionsType) { |
50 | - const { | |
51 | - id, | |
52 | - ruleChainDetail, | |
53 | - createEdgeModalActionType, | |
54 | - updateEdgeDrawerActionType, | |
55 | - updateNodeDrawerActionType, | |
56 | - useSaveAndRedoActionType, | |
57 | - } = options; | |
55 | + const { id, ruleChainDetail, modalActionType, useSaveAndRedoActionType } = options; | |
56 | + | |
57 | + const { createEdgeModalActionType, updateEdgeDrawerActionType, updateNodeDrawerActionType } = | |
58 | + modalActionType; | |
58 | 59 | |
59 | 60 | const { triggerChange } = useSaveAndRedoActionType; |
60 | 61 | |
... | ... | @@ -162,29 +163,35 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
162 | 163 | |
163 | 164 | const { handleContextMenuAction } = useContextMenuAction(); |
164 | 165 | |
166 | + const { validateCanCreateRuleChain } = useBasicDataTransform(); | |
167 | + | |
165 | 168 | onNodeContextMenu(async (params) => { |
166 | 169 | const menuType = params.node.selected |
167 | 170 | ? await createElementsSelectedContextMenu( |
168 | 171 | params, |
169 | 172 | unref(useSaveAndRedoActionType.changeMarker), |
170 | - validateSelectionElementsCanCreateRuleChain() | |
173 | + validateCanCreateRuleChain( | |
174 | + flowActionType.getSelectedNodes, | |
175 | + flowActionType.getSelectedEdges | |
176 | + ).flag | |
171 | 177 | ) |
172 | 178 | : await createNodeContextMenu(params); |
173 | 179 | |
174 | - if (menuType) { | |
175 | - if (menuType === RuleContextMenuEnum.DETAIL) { | |
176 | - handleUpdateNode(params.node); | |
177 | - return; | |
178 | - } | |
180 | + if (!menuType) return; | |
179 | 181 | |
180 | - handleContextMenuAction({ | |
181 | - menuType, | |
182 | - flowActionType, | |
183 | - event: params.event, | |
184 | - node: params.node, | |
185 | - useSaveAndRedoActionType, | |
186 | - }); | |
182 | + if (menuType === RuleContextMenuEnum.DETAIL) { | |
183 | + handleUpdateNode(params.node); | |
184 | + return; | |
187 | 185 | } |
186 | + | |
187 | + handleContextMenuAction({ | |
188 | + menuType, | |
189 | + flowActionType, | |
190 | + event: params.event, | |
191 | + node: params.node, | |
192 | + useSaveAndRedoActionType, | |
193 | + modalActionType, | |
194 | + }); | |
188 | 195 | }); |
189 | 196 | |
190 | 197 | onEdgeContextMenu(async (params) => { |
... | ... | @@ -207,6 +214,7 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
207 | 214 | event: params.event, |
208 | 215 | useSaveAndRedoActionType, |
209 | 216 | edge: params.edge, |
217 | + modalActionType, | |
210 | 218 | }); |
211 | 219 | } |
212 | 220 | }); |
... | ... | @@ -216,21 +224,25 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
216 | 224 | ? await createElementsSelectedContextMenu( |
217 | 225 | getCreatePanelContextMenuParams(params), |
218 | 226 | unref(useSaveAndRedoActionType.changeMarker), |
219 | - validateSelectionElementsCanCreateRuleChain() | |
227 | + validateCanCreateRuleChain( | |
228 | + flowActionType.getSelectedNodes, | |
229 | + flowActionType.getSelectedEdges | |
230 | + ).flag | |
220 | 231 | ) |
221 | 232 | : await createPanelContextMenu( |
222 | 233 | getCreatePanelContextMenuParams(params), |
223 | 234 | unref(useSaveAndRedoActionType.changeMarker) |
224 | 235 | ); |
225 | 236 | |
226 | - if (menuType) { | |
227 | - handleContextMenuAction({ | |
228 | - menuType, | |
229 | - flowActionType, | |
230 | - event: params, | |
231 | - useSaveAndRedoActionType, | |
232 | - }); | |
233 | - } | |
237 | + if (!menuType) return; | |
238 | + | |
239 | + handleContextMenuAction({ | |
240 | + menuType, | |
241 | + flowActionType, | |
242 | + event: params, | |
243 | + useSaveAndRedoActionType, | |
244 | + modalActionType, | |
245 | + }); | |
234 | 246 | }); |
235 | 247 | |
236 | 248 | /** |
... | ... | @@ -382,17 +394,5 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
382 | 394 | } as NodeMouseEvent; |
383 | 395 | } |
384 | 396 | |
385 | - function validateSelectionElementsCanCreateRuleChain() { | |
386 | - const nodes = unref(flowActionType.getSelectedNodes); | |
387 | - const edges = unref(flowActionType.getSelectedEdges); | |
388 | - | |
389 | - for (const node of nodes) { | |
390 | - const index = edges.findIndex((edge) => edge.target === node.id || edge.source === node.id); | |
391 | - if (!~index) return false; | |
392 | - } | |
393 | - | |
394 | - return true; | |
395 | - } | |
396 | - | |
397 | 397 | return { flowActionType }; |
398 | 398 | } | ... | ... |
1 | -import type { VueFlowStore, Getters, Elements } from '@vue-flow/core'; | |
2 | -import { ComputedRef, computed, ref, unref } from 'vue'; | |
3 | -import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; | |
4 | -import { EntryCategoryComponentEnum } from '../enum/category'; | |
1 | +import type { VueFlowStore, Elements } from '@vue-flow/core'; | |
2 | +import { computed, ref, unref } from 'vue'; | |
3 | +import { BasicNodeBindData, NodeData } from '../types/node'; | |
5 | 4 | import { useBasicDataTransform } from './useBasicDataTransform'; |
6 | 5 | import { |
7 | 6 | getRuleChainData, |
... | ... | @@ -10,15 +9,13 @@ import { |
10 | 9 | saveRuleChainDetail, |
11 | 10 | } from '/@/api/ruleDesigner'; |
12 | 11 | import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode'; |
13 | -import { useInputNode } from './useInputNode'; | |
12 | +import { useNewNode } from './useNewNode'; | |
14 | 13 | import { buildUUID } from '/@/utils/uuid'; |
15 | 14 | import { useRoute, useRouter } from 'vue-router'; |
16 | 15 | import { RuleChainEntityType } from '../enum/entity'; |
17 | 16 | import { PageEnum } from '/@/enums/pageEnum'; |
18 | 17 | import { clearRuleChainImportCache, getRuleChainImportCache } from './useRuleChainCache'; |
19 | 18 | |
20 | -const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; | |
21 | - | |
22 | 19 | export function useSaveAndRedo() { |
23 | 20 | const changeMarker = ref(false); |
24 | 21 | |
... | ... | @@ -38,7 +35,7 @@ export function useSaveAndRedo() { |
38 | 35 | |
39 | 36 | const ruleChainDetail = ref<RuleChainDetail>(); |
40 | 37 | |
41 | - const { mergeData, deconstructionData } = useBasicDataTransform(); | |
38 | + const { combineData, deconstructionData } = useBasicDataTransform(); | |
42 | 39 | |
43 | 40 | const triggerChange = () => { |
44 | 41 | changeMarker.value = true; |
... | ... | @@ -48,90 +45,14 @@ export function useSaveAndRedo() { |
48 | 45 | changeMarker.value = false; |
49 | 46 | }; |
50 | 47 | |
51 | - /** | |
52 | - * @description 保存连接信息 | |
53 | - */ | |
54 | - function getConnections( | |
55 | - nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'], | |
56 | - edges: ComputedRef<Getters['getEdges']> | |
57 | - ) { | |
58 | - const nodeIndexMap = new Map(); | |
59 | - | |
60 | - const connections: ConnectionItemType[] = []; | |
61 | - | |
62 | - unref(nodesRef).forEach((item, index) => { | |
63 | - nodeIndexMap.set(item.id, index); | |
64 | - }); | |
65 | - | |
66 | - for (const item of unref(edges)) { | |
67 | - const { data, target, source } = item; | |
68 | - const { data: bindData } = data as EdgeData; | |
69 | - const { type } = bindData || {}; | |
70 | - const fromIndex = nodeIndexMap.get(source); | |
71 | - const toIndex = nodeIndexMap.get(target); | |
72 | - type?.forEach((key) => { | |
73 | - connections.push({ fromIndex, toIndex, type: key }); | |
74 | - }); | |
75 | - } | |
76 | - | |
77 | - return connections; | |
78 | - } | |
79 | - | |
80 | - function getNodes(nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes']) { | |
81 | - const nodes: BasicNodeBindData[] = []; | |
82 | - | |
83 | - for (const node of unref(nodesRef)) { | |
84 | - const nodeData = node.data as NodeData; | |
85 | - | |
86 | - if (ignoreNodeKeys.includes(nodeData.config?.key as string)) continue; | |
87 | - | |
88 | - const data = nodeData.data; | |
89 | - | |
90 | - nodes.push( | |
91 | - Object.assign( | |
92 | - mergeData(data, nodeData, node), | |
93 | - nodeData.created | |
94 | - ? ({ | |
95 | - id: { id: node.id, entityType: RuleChainEntityType.RULE_NODE }, | |
96 | - } as BasicNodeBindData) | |
97 | - : {} | |
98 | - ) | |
99 | - ); | |
100 | - } | |
101 | - | |
102 | - return nodes; | |
103 | - } | |
104 | - | |
105 | - function getFirsetNodeIndex( | |
106 | - nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'], | |
107 | - edges: ComputedRef<Getters['getEdges']> | |
108 | - ) { | |
109 | - const inputNode = unref(edges).find( | |
110 | - (item) => (item.sourceNode.data as NodeData).config?.key === EntryCategoryComponentEnum.INPUT | |
111 | - ); | |
112 | - | |
113 | - if (inputNode) { | |
114 | - const targetId = inputNode.target; | |
115 | - const index = unref(nodesRef).findIndex((item) => item.id === targetId); | |
116 | - return index; | |
117 | - } | |
118 | - } | |
119 | - | |
120 | 48 | const handleApplyChange = (flowActionType: VueFlowStore) => { |
121 | 49 | if (!unref(changeMarker)) return; |
122 | 50 | |
123 | - const edgesRef = flowActionType.getEdges; | |
124 | - | |
125 | - const extraIgnoreNodeRef = unref(flowActionType.getNodes).filter( | |
126 | - (item) => !ignoreNodeKeys.includes((item.data as NodeData).config?.key as string) | |
51 | + const { connections, nodes, firstNodeIndex } = combineData( | |
52 | + flowActionType.getNodes, | |
53 | + flowActionType.getEdges | |
127 | 54 | ); |
128 | 55 | |
129 | - const connections = getConnections(extraIgnoreNodeRef, edgesRef); | |
130 | - | |
131 | - const nodes = getNodes(extraIgnoreNodeRef); | |
132 | - | |
133 | - const firstNodeIndex = getFirsetNodeIndex(extraIgnoreNodeRef, edgesRef); | |
134 | - | |
135 | 56 | handleSaveRuleChain(connections, nodes, firstNodeIndex); |
136 | 57 | }; |
137 | 58 | |
... | ... | @@ -211,7 +132,7 @@ export function useSaveAndRedo() { |
211 | 132 | function parseRuleChain(ruleChain: RuleChainType) { |
212 | 133 | const inputId = buildUUID(); |
213 | 134 | |
214 | - const { getInputNodeConfig } = useInputNode(); | |
135 | + const { getInputNodeConfig } = useNewNode(); | |
215 | 136 | |
216 | 137 | const value = deconstructionData(ruleChain, inputId); |
217 | 138 | ... | ... |
... | ... | @@ -19,10 +19,11 @@ |
19 | 19 | import { CreateEdgeModal } from './src/components/CreateEdgeModal'; |
20 | 20 | import { useFullScreen } from './hook/useFullScreen'; |
21 | 21 | import { useSaveAndRedo } from './hook/useSaveAndRedo'; |
22 | + import { NodeData } from './types/node'; | |
22 | 23 | import { Icon } from '/@/components/Icon'; |
23 | 24 | import { UpdateNodeDrawer } from './src/components/UpdateNodeDrawer'; |
24 | 25 | import { UpdateEdgeDrawer } from './src/components/UpdateEdgeDrawer'; |
25 | - import { NodeData } from './types/node'; | |
26 | + import { CreateRuleChainModal } from './src/components/CreateRuleChainModal'; | |
26 | 27 | |
27 | 28 | const getId = Number(Math.random().toString().substring(2)).toString(16); |
28 | 29 | |
... | ... | @@ -36,6 +37,9 @@ |
36 | 37 | |
37 | 38 | const updateEdgeDrawerActionType = ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>(null); |
38 | 39 | |
40 | + const createRuleChainModalActionType = | |
41 | + ref<Nullable<InstanceType<typeof CreateRuleChainModal>>>(null); | |
42 | + | |
39 | 43 | const flowElRef = ref<Nullable<FlowElRef>>(null); |
40 | 44 | |
41 | 45 | const elements = ref([]); |
... | ... | @@ -57,11 +61,14 @@ |
57 | 61 | const { flowActionType } = useRuleFlow({ |
58 | 62 | id: getId, |
59 | 63 | ruleChainDetail, |
60 | - createNodeModalActionType, | |
61 | - createEdgeModalActionType, | |
62 | - updateEdgeDrawerActionType, | |
63 | - updateNodeDrawerActionType, | |
64 | 64 | useSaveAndRedoActionType, |
65 | + modalActionType: { | |
66 | + createNodeModalActionType, | |
67 | + createEdgeModalActionType, | |
68 | + updateEdgeDrawerActionType, | |
69 | + updateNodeDrawerActionType, | |
70 | + createRuleChainModalActionType, | |
71 | + }, | |
65 | 72 | }); |
66 | 73 | |
67 | 74 | const { handleOnDragOver, handleOnDrop } = useDragCreate({ |
... | ... | @@ -85,6 +92,8 @@ |
85 | 92 | |
86 | 93 | const handleDeleteSelectionElements = () => { |
87 | 94 | flowActionType.removeNodes(unref(flowActionType.getSelectedNodes)); |
95 | + flowActionType.removeEdges(unref(flowActionType.getSelectedEdges)); | |
96 | + useSaveAndRedoActionType.triggerChange?.(); | |
88 | 97 | }; |
89 | 98 | |
90 | 99 | onMounted(() => { |
... | ... | @@ -97,6 +106,7 @@ |
97 | 106 | createNodeModalActionType, |
98 | 107 | updateEdgeDrawerActionType, |
99 | 108 | updateNodeDrawerActionType, |
109 | + createRuleChainModalActionType, | |
100 | 110 | flowActionType, |
101 | 111 | triggerChange, |
102 | 112 | }); |
... | ... | @@ -177,6 +187,8 @@ |
177 | 187 | |
178 | 188 | <UpdateEdgeDrawer ref="updateEdgeDrawerActionType" /> |
179 | 189 | <UpdateNodeDrawer ref="updateNodeDrawerActionType" /> |
190 | + | |
191 | + <CreateRuleChainModal ref="createRuleChainModalActionType" /> | |
180 | 192 | </main> |
181 | 193 | </template> |
182 | 194 | ... | ... |
1 | +import { FormSchema } from '/@/components/Form'; | |
2 | + | |
3 | +export enum FormFieldsEnum { | |
4 | + NAME = 'name', | |
5 | + DESCRIPTION = 'description', | |
6 | + ADDITIONAL_INFO = 'additionalInfo', | |
7 | +} | |
8 | + | |
9 | +export enum FormFieldsNameEnum { | |
10 | + NAME = '名称', | |
11 | + DESCRIPTION = '说明', | |
12 | +} | |
13 | + | |
14 | +export const formSchemas: FormSchema[] = [ | |
15 | + { | |
16 | + field: FormFieldsEnum.NAME, | |
17 | + label: FormFieldsNameEnum.NAME, | |
18 | + component: 'Input', | |
19 | + required: true, | |
20 | + componentProps: { | |
21 | + placeholder: `请输入${FormFieldsNameEnum.NAME}`, | |
22 | + }, | |
23 | + }, | |
24 | + { | |
25 | + field: FormFieldsEnum.DESCRIPTION, | |
26 | + label: FormFieldsNameEnum.DESCRIPTION, | |
27 | + component: 'InputTextArea', | |
28 | + componentProps: { | |
29 | + placeholder: `请输入${FormFieldsNameEnum.DESCRIPTION}`, | |
30 | + }, | |
31 | + }, | |
32 | +]; | ... | ... |
1 | +export { default as CreateRuleChainModal } from './index.vue'; | ... | ... |
1 | +<script setup lang="ts"> | |
2 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | |
3 | + import { BasicForm, useForm } from '/@/components/Form'; | |
4 | + import { formSchemas, FormFieldsEnum } from './config'; | |
5 | + import { ref } from 'vue'; | |
6 | + | |
7 | + interface SaveParamsType { | |
8 | + name: string; | |
9 | + additional: { description: string }; | |
10 | + } | |
11 | + | |
12 | + const visible = ref(false); | |
13 | + | |
14 | + const [register, { closeModal }] = useModalInner(); | |
15 | + | |
16 | + const [registerForm, { getFieldsValue, validate }] = useForm({ | |
17 | + schemas: formSchemas, | |
18 | + showActionButtonGroup: false, | |
19 | + layout: 'vertical', | |
20 | + }); | |
21 | + | |
22 | + let resolveFn: undefined | ((value: SaveParamsType) => any); | |
23 | + | |
24 | + const openCreateRuleChainModal = async (): Promise<{ | |
25 | + name: string; | |
26 | + additionalInfo: { description: string }; | |
27 | + }> => { | |
28 | + visible.value = true; | |
29 | + return new Promise((resolve) => { | |
30 | + resolveFn = resolve; | |
31 | + }); | |
32 | + }; | |
33 | + | |
34 | + const handleSave = async () => { | |
35 | + await validate(); | |
36 | + const value = getFieldsValue(); | |
37 | + resolveFn?.({ | |
38 | + name: value[FormFieldsEnum.NAME], | |
39 | + additional: { description: value[FormFieldsEnum.DESCRIPTION] || '' }, | |
40 | + }); | |
41 | + closeModal(); | |
42 | + }; | |
43 | + | |
44 | + defineExpose({ openCreateRuleChainModal }); | |
45 | +</script> | |
46 | + | |
47 | +<template> | |
48 | + <BasicModal | |
49 | + v-model:visible="visible" | |
50 | + @register="register" | |
51 | + @ok="handleSave" | |
52 | + title="Create nested rule chain" | |
53 | + > | |
54 | + <BasicForm @register="registerForm" /> | |
55 | + </BasicModal> | |
56 | +</template> | ... | ... |