Commit 5a1f1ae031dc48bb6a8b2d912c5452b5a42206d9
Merge branch 'feat/rule-chain' into 'main_dev'
Feat/rule chain See merge request yunteng/thingskit-front!851
Showing
17 changed files
with
280 additions
and
66 deletions
1 | -import { DeviceInfoItemType, DeviceTypeItem, PageParams } from './model'; | 1 | +import { DeviceInfoItemType, DeviceTypeItem, PageParams, ScriptTestParams } from './model'; |
2 | import { TBPaginationResult } from '/#/axios'; | 2 | import { TBPaginationResult } from '/#/axios'; |
3 | import { defHttp } from '/@/utils/http/axios'; | 3 | import { defHttp } from '/@/utils/http/axios'; |
4 | 4 | ||
@@ -6,6 +6,7 @@ enum Api { | @@ -6,6 +6,7 @@ enum Api { | ||
6 | GET_DEVICE_INFOS = '/tenant/deviceInfos', | 6 | GET_DEVICE_INFOS = '/tenant/deviceInfos', |
7 | GET_DEVICE_TYPE = '/device/types', | 7 | GET_DEVICE_TYPE = '/device/types', |
8 | TENANT_QUEUE = '/tenant/queues', | 8 | TENANT_QUEUE = '/tenant/queues', |
9 | + TEST_SCRIPT = '/ruleChain/testScript', | ||
9 | } | 10 | } |
10 | 11 | ||
11 | enum Entity { | 12 | enum Entity { |
@@ -142,3 +143,13 @@ export const getEntityEdge = (params: PageParams) => { | @@ -142,3 +143,13 @@ export const getEntityEdge = (params: PageParams) => { | ||
142 | } | 143 | } |
143 | ); | 144 | ); |
144 | }; | 145 | }; |
146 | + | ||
147 | +export const testScript = (data: ScriptTestParams) => { | ||
148 | + return defHttp.post<Record<'error' | 'output', string>>( | ||
149 | + { | ||
150 | + url: Api.TEST_SCRIPT, | ||
151 | + data, | ||
152 | + }, | ||
153 | + { joinPrefix: false } | ||
154 | + ); | ||
155 | +}; |
@@ -52,3 +52,12 @@ export interface PageParams { | @@ -52,3 +52,12 @@ export interface PageParams { | ||
52 | sortProperty?: string; | 52 | sortProperty?: string; |
53 | sortOrder?: string; | 53 | sortOrder?: string; |
54 | } | 54 | } |
55 | + | ||
56 | +export interface ScriptTestParams { | ||
57 | + argNames?: string[]; | ||
58 | + metadata?: Recordable; | ||
59 | + msg?: string; | ||
60 | + msgType?: string; | ||
61 | + script?: string; | ||
62 | + scriptType?: string; | ||
63 | +} |
@@ -4,12 +4,23 @@ import { defHttp } from '/@/utils/http/axios'; | @@ -4,12 +4,23 @@ import { defHttp } from '/@/utils/http/axios'; | ||
4 | import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode'; | 4 | import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode'; |
5 | 5 | ||
6 | enum Api { | 6 | enum Api { |
7 | + SAVE_RULE_CHAINS = '/ruleChain', | ||
7 | GET_RULE_CHAINS_DETAIL = '/ruleChain', | 8 | GET_RULE_CHAINS_DETAIL = '/ruleChain', |
8 | SAVE = '/ruleChain/metadata', | 9 | SAVE = '/ruleChain/metadata', |
9 | GET_RULE_CHAINES = '/ruleChains', | 10 | GET_RULE_CHAINES = '/ruleChains', |
10 | GET_RULE_NODE_EVENTS = '/events/RULE_NODE', | 11 | GET_RULE_NODE_EVENTS = '/events/RULE_NODE', |
11 | } | 12 | } |
12 | 13 | ||
14 | +export const saveRuleChainDetail = (params: Partial<RuleChainDetail>) => { | ||
15 | + return defHttp.post<RuleChainDetail>( | ||
16 | + { | ||
17 | + url: Api.SAVE_RULE_CHAINS, | ||
18 | + data: params, | ||
19 | + }, | ||
20 | + { joinPrefix: false } | ||
21 | + ); | ||
22 | +}; | ||
23 | + | ||
13 | export const getRuleChainDetail = (id: string) => { | 24 | export const getRuleChainDetail = (id: string) => { |
14 | return defHttp.get<RuleChainDetail>( | 25 | return defHttp.get<RuleChainDetail>( |
15 | { | 26 | { |
@@ -41,8 +41,12 @@ export const MENU_LIST = 'MENU_LIST'; | @@ -41,8 +41,12 @@ export const MENU_LIST = 'MENU_LIST'; | ||
41 | 41 | ||
42 | export const RULE_NODE_LOCAL_CACHE_KEY = 'RULE__NODE__KEY__'; | 42 | export const RULE_NODE_LOCAL_CACHE_KEY = 'RULE__NODE__KEY__'; |
43 | 43 | ||
44 | +export const RULE_CHAIN_IMPORT_LOCAL_CACHE_KEY = 'RULE__CHAIN__IMPORT__KEY__'; | ||
45 | + | ||
44 | export const RULE_NODE_KEY = 'RULE_NODE'; | 46 | export const RULE_NODE_KEY = 'RULE_NODE'; |
45 | 47 | ||
48 | +export const RULE_CHAIN_KEY = 'RULE_CHAIN'; | ||
49 | + | ||
46 | export enum CacheTypeEnum { | 50 | export enum CacheTypeEnum { |
47 | SESSION, | 51 | SESSION, |
48 | LOCAL, | 52 | LOCAL, |
@@ -18,4 +18,6 @@ export const PageEnum = { | @@ -18,4 +18,6 @@ export const PageEnum = { | ||
18 | SHARE_PAGE: '/share/:viewType/:id/:publicId', | 18 | SHARE_PAGE: '/share/:viewType/:id/:publicId', |
19 | 19 | ||
20 | RULE_CHAIN_DETAIL: '/rule/chain/:id', | 20 | RULE_CHAIN_DETAIL: '/rule/chain/:id', |
21 | + | ||
22 | + RULE_CHAIN_DETAIL_IMPORT: '/rule/chain/import', | ||
21 | }; | 23 | }; |
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 |
@@ -8,6 +8,7 @@ import { buildUUID } from '/@/utils/uuid'; | @@ -8,6 +8,7 @@ import { buildUUID } from '/@/utils/uuid'; | ||
8 | import { isNullOrUnDef } from '/@/utils/is'; | 8 | import { isNullOrUnDef } from '/@/utils/is'; |
9 | import { useAddNodes } from './useAddNodes'; | 9 | import { useAddNodes } from './useAddNodes'; |
10 | import { useAddEdges } from './useAddEdges'; | 10 | import { useAddEdges } from './useAddEdges'; |
11 | +import { RuleChainEntityType } from '../enum/entity'; | ||
11 | 12 | ||
12 | export function useBasicDataTransform() { | 13 | export function useBasicDataTransform() { |
13 | const nodeConfigMap = new Map<string, NodeData>(); | 14 | const nodeConfigMap = new Map<string, NodeData>(); |
@@ -67,8 +68,8 @@ export function useBasicDataTransform() { | @@ -67,8 +68,8 @@ export function useBasicDataTransform() { | ||
67 | 68 | ||
68 | const sourceNode = indexMap.get(Number(fromIndex)); | 69 | const sourceNode = indexMap.get(Number(fromIndex)); |
69 | const targetNode = indexMap.get(Number(toIndex)); | 70 | const targetNode = indexMap.get(Number(toIndex)); |
70 | - const source = sourceNode!.id!.id; | ||
71 | - const target = targetNode!.id!.id; | 71 | + const source = sourceNode?.id?.id || buildUUID(); |
72 | + const target = targetNode?.id?.id || buildUUID(); | ||
72 | const sourceHandle = `${source}${SOURCE_HANDLE}`; | 73 | const sourceHandle = `${source}${SOURCE_HANDLE}`; |
73 | const targetHandle = `${target}${TARGET_HANDLE}`; | 74 | const targetHandle = `${target}${TARGET_HANDLE}`; |
74 | 75 | ||
@@ -88,7 +89,7 @@ export function useBasicDataTransform() { | @@ -88,7 +89,7 @@ export function useBasicDataTransform() { | ||
88 | if (!isNullOrUnDef(firstNodeIndex)) { | 89 | if (!isNullOrUnDef(firstNodeIndex)) { |
89 | const targetNode = indexMap.get(firstNodeIndex); | 90 | const targetNode = indexMap.get(firstNodeIndex); |
90 | const source = inputNodeId; | 91 | const source = inputNodeId; |
91 | - const target = targetNode!.id!.id; | 92 | + const target = targetNode?.id?.id || buildUUID(); |
92 | const sourceHandle = `${source}$${SOURCE_HANDLE}`; | 93 | const sourceHandle = `${source}$${SOURCE_HANDLE}`; |
93 | const targetHandle = `${target}${TARGET_HANDLE}`; | 94 | const targetHandle = `${target}${TARGET_HANDLE}`; |
94 | edges.push( | 95 | edges.push( |
@@ -112,7 +113,7 @@ export function useBasicDataTransform() { | @@ -112,7 +113,7 @@ export function useBasicDataTransform() { | ||
112 | const { layoutX, layoutY, description } = additionalInfo || {}; | 113 | const { layoutX, layoutY, description } = additionalInfo || {}; |
113 | const { getAddNodesParams } = useAddNodes(); | 114 | const { getAddNodesParams } = useAddNodes(); |
114 | 115 | ||
115 | - return getAddNodesParams( | 116 | + const value = getAddNodesParams( |
116 | { x: layoutX!, y: layoutY! }, | 117 | { x: layoutX!, y: layoutY! }, |
117 | { | 118 | { |
118 | ...config, | 119 | ...config, |
@@ -128,6 +129,10 @@ export function useBasicDataTransform() { | @@ -128,6 +129,10 @@ export function useBasicDataTransform() { | ||
128 | id: id?.id || buildUUID(), | 129 | id: id?.id || buildUUID(), |
129 | } | 130 | } |
130 | ); | 131 | ); |
132 | + | ||
133 | + if (!id?.id) node.id = { id: value.id, entityType: RuleChainEntityType.RULE_NODE }; | ||
134 | + | ||
135 | + return value; | ||
131 | } | 136 | } |
132 | 137 | ||
133 | function deconstructionNode(nodes: RuleChainType['nodes']) { | 138 | function deconstructionNode(nodes: RuleChainType['nodes']) { |
1 | -import { GraphEdge, GraphNode, VueFlowStore } from '@vue-flow/core'; | ||
2 | -import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste'; | 1 | +import { GraphEdge, GraphNode, VueFlowStore, pointToRendererPoint } from '@vue-flow/core'; |
2 | +import { getRuleNodeCache, setRuleNodeCache } from './useRuleChainCache'; | ||
3 | import { RuleContextMenuEnum } from './useRuleChainContextMenu'; | 3 | import { RuleContextMenuEnum } from './useRuleChainContextMenu'; |
4 | import { useAddNodes } from './useAddNodes'; | 4 | import { useAddNodes } from './useAddNodes'; |
5 | import { buildUUID } from '/@/utils/uuid'; | 5 | import { buildUUID } from '/@/utils/uuid'; |
@@ -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: { |
src/views/rule/designer/hook/useRuleChainCache.ts
renamed from
src/views/rule/designer/hook/useRuleCopyPaste.ts
1 | import { Edge, Node } from '@vue-flow/core'; | 1 | import { Edge, Node } from '@vue-flow/core'; |
2 | -import { RULE_NODE_KEY, RULE_NODE_LOCAL_CACHE_KEY } from '/@/enums/cacheEnum'; | 2 | +import { |
3 | + RULE_NODE_KEY, | ||
4 | + RULE_CHAIN_KEY, | ||
5 | + RULE_NODE_LOCAL_CACHE_KEY, | ||
6 | + RULE_CHAIN_IMPORT_LOCAL_CACHE_KEY, | ||
7 | +} from '/@/enums/cacheEnum'; | ||
3 | import { createLocalStorage } from '/@/utils/cache'; | 8 | import { createLocalStorage } from '/@/utils/cache'; |
9 | +import { RuleChainDetail, RuleChainType } from '../types/ruleNode'; | ||
4 | 10 | ||
5 | const ruleNodeStorage = createLocalStorage({ prefixKey: RULE_NODE_LOCAL_CACHE_KEY }); | 11 | const ruleNodeStorage = createLocalStorage({ prefixKey: RULE_NODE_LOCAL_CACHE_KEY }); |
12 | +const ruleChainStorage = createLocalStorage({ prefixKey: RULE_CHAIN_IMPORT_LOCAL_CACHE_KEY }); | ||
6 | 13 | ||
7 | interface RuleNodeCacheType { | 14 | interface RuleNodeCacheType { |
8 | nodes?: Node[]; | 15 | nodes?: Node[]; |
@@ -11,6 +18,11 @@ interface RuleNodeCacheType { | @@ -11,6 +18,11 @@ interface RuleNodeCacheType { | ||
11 | originY?: number; | 18 | originY?: number; |
12 | } | 19 | } |
13 | 20 | ||
21 | +export interface RuleChainCacheType { | ||
22 | + ruleChain: Partial<RuleChainDetail>; | ||
23 | + metadata: RuleChainType; | ||
24 | +} | ||
25 | + | ||
14 | export const setRuleNodeCache = ({ | 26 | export const setRuleNodeCache = ({ |
15 | nodes = [], | 27 | nodes = [], |
16 | edges = [], | 28 | edges = [], |
@@ -29,6 +41,14 @@ export const getRuleNodeCache = (): RuleNodeCacheType => ruleNodeStorage.get(RUL | @@ -29,6 +41,14 @@ export const getRuleNodeCache = (): RuleNodeCacheType => ruleNodeStorage.get(RUL | ||
29 | 41 | ||
30 | export const checkHasCacheRuleNode = () => !!getRuleNodeCache(); | 42 | export const checkHasCacheRuleNode = () => !!getRuleNodeCache(); |
31 | 43 | ||
44 | +export const getRuleChainImportCache = (): RuleChainCacheType => | ||
45 | + ruleChainStorage.get(RULE_CHAIN_KEY) || {}; | ||
46 | + | ||
47 | +export const setRuleChainImportCache = (value: RuleChainCacheType) => | ||
48 | + ruleChainStorage.set(RULE_CHAIN_KEY, value); | ||
49 | + | ||
50 | +export const clearRuleChainImportCache = () => ruleChainStorage.remove(RULE_CHAIN_KEY); | ||
51 | + | ||
32 | function initRuleNodeStorage() { | 52 | function initRuleNodeStorage() { |
33 | const value = ruleNodeStorage.get(RULE_NODE_KEY); | 53 | const value = ruleNodeStorage.get(RULE_NODE_KEY); |
34 | value && ruleNodeStorage.set(RULE_NODE_KEY, value); | 54 | value && ruleNodeStorage.set(RULE_NODE_KEY, value); |
@@ -2,7 +2,7 @@ import { NodeMouseEvent } from '@vue-flow/core'; | @@ -2,7 +2,7 @@ import { NodeMouseEvent } from '@vue-flow/core'; | ||
2 | import { useContextMenu } from '../src/components/RuleChainContextMenu'; | 2 | import { useContextMenu } from '../src/components/RuleChainContextMenu'; |
3 | import { RuleChainContextMenuItemType } from '../src/components/RuleChainContextMenu/index.type'; | 3 | import { RuleChainContextMenuItemType } from '../src/components/RuleChainContextMenu/index.type'; |
4 | import { ElementsTypeEnum } from '../enum'; | 4 | import { ElementsTypeEnum } from '../enum'; |
5 | -import { checkHasCacheRuleNode } from './useRuleCopyPaste'; | 5 | +import { checkHasCacheRuleNode } from './useRuleChainCache'; |
6 | 6 | ||
7 | export enum RuleContextMenuEnum { | 7 | export enum RuleContextMenuEnum { |
8 | DETAIL = 'DETAIL', | 8 | DETAIL = 'DETAIL', |
@@ -17,6 +17,8 @@ export enum RuleContextMenuEnum { | @@ -17,6 +17,8 @@ export enum RuleContextMenuEnum { | ||
17 | UNDO_CHANGE = 'UNDO_CHANGE', | 17 | UNDO_CHANGE = 'UNDO_CHANGE', |
18 | 18 | ||
19 | SELECT_ALL = 'SELECT_ALL', | 19 | SELECT_ALL = 'SELECT_ALL', |
20 | + | ||
21 | + CREATE_RULE_CHAIN = 'CREATE_RULE_CHAIN', | ||
20 | } | 22 | } |
21 | 23 | ||
22 | export enum RuleContextMenuNameEnum { | 24 | export enum RuleContextMenuNameEnum { |
@@ -32,6 +34,8 @@ export enum RuleContextMenuNameEnum { | @@ -32,6 +34,8 @@ export enum RuleContextMenuNameEnum { | ||
32 | UNDO_CHANGE = '撤销更改', | 34 | UNDO_CHANGE = '撤销更改', |
33 | 35 | ||
34 | SELECT_ALL = '选择全部', | 36 | SELECT_ALL = '选择全部', |
37 | + | ||
38 | + CREATE_RULE_CHAIN = '创建规则链', | ||
35 | } | 39 | } |
36 | 40 | ||
37 | export enum RuleChainContextMenuIconEnum { | 41 | export enum RuleChainContextMenuIconEnum { |
@@ -48,21 +52,22 @@ export enum RuleChainContextMenuIconEnum { | @@ -48,21 +52,22 @@ export enum RuleChainContextMenuIconEnum { | ||
48 | 52 | ||
49 | SELECT_ALL = 'material-symbols:select-all', | 53 | SELECT_ALL = 'material-symbols:select-all', |
50 | 54 | ||
55 | + CREATE_RULE_CHAIN = 'material-symbols:settings-ethernet', | ||
56 | + | ||
51 | // LINK = 'material-symbols:trending-flat', | 57 | // LINK = 'material-symbols:trending-flat', |
52 | } | 58 | } |
53 | 59 | ||
54 | -export enum RuleChainContextMenuShortcutKeyEnum { | ||
55 | - DELETE = 'Ctrl(⌘) X', | 60 | +export enum RuleChainContextMenuShortcutKeyEnum {} |
61 | +// DELETE = 'Ctrl(⌘) X', | ||
56 | 62 | ||
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 | +// SELECT_COPY = 'Ctrl(⌘) C', |
64 | +// PASTE = 'Ctrl(⌘) V', | ||
65 | +// UNSELECTED = 'Esc', | ||
66 | +// DELETE_SELECT = 'Del', | ||
67 | +// APPLY_CHANGE = 'Ctrl(⌘) S', | ||
68 | +// UNDO_CHANGE = 'Ctrl(⌘) Z', | ||
63 | 69 | ||
64 | - SELECT_ALL = 'Ctrl(⌘) A', | ||
65 | -} | 70 | +// SELECT_ALL = 'Ctrl(⌘) A', |
66 | 71 | ||
67 | const getMenuItem = (key: RuleContextMenuEnum, handler: Fn, disabled = false) => { | 72 | const getMenuItem = (key: RuleContextMenuEnum, handler: Fn, disabled = false) => { |
68 | return { | 73 | return { |
@@ -96,6 +101,7 @@ export function useCreateRuleChainContextMenu() { | @@ -96,6 +101,7 @@ export function useCreateRuleChainContextMenu() { | ||
96 | const createElementsSelectedContextMenu = ( | 101 | const createElementsSelectedContextMenu = ( |
97 | params: NodeMouseEvent, | 102 | params: NodeMouseEvent, |
98 | changeMarker: boolean, | 103 | changeMarker: boolean, |
104 | + hasCreateChainMenuItem = false, | ||
99 | elementsType: ElementsTypeEnum.NODE = ElementsTypeEnum.NODE | 105 | elementsType: ElementsTypeEnum.NODE = ElementsTypeEnum.NODE |
100 | ): Promise<RuleContextMenuEnum | ''> => { | 106 | ): Promise<RuleContextMenuEnum | ''> => { |
101 | return new Promise(async (resolve) => { | 107 | return new Promise(async (resolve) => { |
@@ -107,6 +113,9 @@ export function useCreateRuleChainContextMenu() { | @@ -107,6 +113,9 @@ export function useCreateRuleChainContextMenu() { | ||
107 | getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()), | 113 | getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()), |
108 | getDivider(), | 114 | getDivider(), |
109 | getMenuItem(RuleContextMenuEnum.UNSELECTED, resolve), | 115 | getMenuItem(RuleContextMenuEnum.UNSELECTED, resolve), |
116 | + ...(hasCreateChainMenuItem | ||
117 | + ? [getMenuItem(RuleContextMenuEnum.CREATE_RULE_CHAIN, resolve)] | ||
118 | + : []), | ||
110 | getMenuItem(RuleContextMenuEnum.DELETE_SELECT, resolve), | 119 | getMenuItem(RuleContextMenuEnum.DELETE_SELECT, resolve), |
111 | getDivider(), | 120 | getDivider(), |
112 | getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker), | 121 | getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker), |
@@ -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 | }); |
@@ -164,7 +166,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -164,7 +166,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
164 | const menuType = params.node.selected | 166 | const menuType = params.node.selected |
165 | ? await createElementsSelectedContextMenu( | 167 | ? await createElementsSelectedContextMenu( |
166 | params, | 168 | params, |
167 | - unref(useSaveAndRedoActionType.changeMarker) | 169 | + unref(useSaveAndRedoActionType.changeMarker), |
170 | + validateSelectionElementsCanCreateRuleChain() | ||
168 | ) | 171 | ) |
169 | : await createNodeContextMenu(params); | 172 | : await createNodeContextMenu(params); |
170 | 173 | ||
@@ -212,7 +215,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -212,7 +215,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
212 | const menuType = unref(flowActionType.getSelectedElements).length | 215 | const menuType = unref(flowActionType.getSelectedElements).length |
213 | ? await createElementsSelectedContextMenu( | 216 | ? await createElementsSelectedContextMenu( |
214 | getCreatePanelContextMenuParams(params), | 217 | getCreatePanelContextMenuParams(params), |
215 | - unref(useSaveAndRedoActionType.changeMarker) | 218 | + unref(useSaveAndRedoActionType.changeMarker), |
219 | + validateSelectionElementsCanCreateRuleChain() | ||
216 | ) | 220 | ) |
217 | : await createPanelContextMenu( | 221 | : await createPanelContextMenu( |
218 | getCreatePanelContextMenuParams(params), | 222 | getCreatePanelContextMenuParams(params), |
@@ -238,6 +242,55 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -238,6 +242,55 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
238 | return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; | 242 | return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; |
239 | } | 243 | } |
240 | 244 | ||
245 | + /** | ||
246 | + * @description 验证是否循环引用 | ||
247 | + */ | ||
248 | + function validateCircularreference( | ||
249 | + sourceNode: GraphNode, | ||
250 | + targetNode: GraphNode, | ||
251 | + edges: GraphEdge[] | ||
252 | + ) { | ||
253 | + const sourceId = sourceNode.id; | ||
254 | + const targetId = targetNode.id; | ||
255 | + | ||
256 | + const getAllIndex = ( | ||
257 | + edges: GraphEdge[], | ||
258 | + validate: (item: GraphEdge, index: number) => boolean | ||
259 | + ) => { | ||
260 | + const indexList: number[] = []; | ||
261 | + | ||
262 | + for (let i = 0; i < edges.length; i++) { | ||
263 | + const item = edges[i]; | ||
264 | + if (validate(item, i)) { | ||
265 | + indexList.push(i); | ||
266 | + } | ||
267 | + } | ||
268 | + | ||
269 | + return indexList; | ||
270 | + }; | ||
271 | + | ||
272 | + const validate = (source: string, startSource: string) => { | ||
273 | + const nextNodesIndex = getAllIndex(edges, (item) => item.source === source); | ||
274 | + | ||
275 | + if (!nextNodesIndex.length) return false; | ||
276 | + | ||
277 | + for (const nextNodeIndex of nextNodesIndex) { | ||
278 | + const nextNode = edges[nextNodeIndex]; | ||
279 | + if (nextNode && nextNode.target === startSource) { | ||
280 | + return true; | ||
281 | + } | ||
282 | + return validate(nextNode.target, startSource); | ||
283 | + } | ||
284 | + }; | ||
285 | + | ||
286 | + return validate(targetId, sourceId); | ||
287 | + } | ||
288 | + | ||
289 | + /** | ||
290 | + * @description 处理最大链接点 | ||
291 | + * @param sourceNode | ||
292 | + * @returns | ||
293 | + */ | ||
241 | function handleMaxConnectionPoint(sourceNode?: GraphNode) { | 294 | function handleMaxConnectionPoint(sourceNode?: GraphNode) { |
242 | if (!sourceNode) return; | 295 | if (!sourceNode) return; |
243 | 296 | ||
@@ -329,5 +382,17 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | @@ -329,5 +382,17 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
329 | } as NodeMouseEvent; | 382 | } as NodeMouseEvent; |
330 | } | 383 | } |
331 | 384 | ||
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 | + | ||
332 | return { flowActionType }; | 397 | return { flowActionType }; |
333 | } | 398 | } |
@@ -3,12 +3,19 @@ import { ComputedRef, computed, ref, unref } from 'vue'; | @@ -3,12 +3,19 @@ import { ComputedRef, computed, ref, unref } from 'vue'; | ||
3 | import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; | 3 | import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; |
4 | import { EntryCategoryComponentEnum } from '../enum/category'; | 4 | import { EntryCategoryComponentEnum } from '../enum/category'; |
5 | import { useBasicDataTransform } from './useBasicDataTransform'; | 5 | import { useBasicDataTransform } from './useBasicDataTransform'; |
6 | -import { getRuleChainData, getRuleChainDetail, saveRuleChainData } from '/@/api/ruleDesigner'; | 6 | +import { |
7 | + getRuleChainData, | ||
8 | + getRuleChainDetail, | ||
9 | + saveRuleChainData, | ||
10 | + saveRuleChainDetail, | ||
11 | +} from '/@/api/ruleDesigner'; | ||
7 | import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode'; | 12 | import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode'; |
8 | import { useInputNode } from './useInputNode'; | 13 | import { useInputNode } from './useInputNode'; |
9 | import { buildUUID } from '/@/utils/uuid'; | 14 | import { buildUUID } from '/@/utils/uuid'; |
10 | -import { useRoute } from 'vue-router'; | 15 | +import { useRoute, useRouter } from 'vue-router'; |
11 | import { RuleChainEntityType } from '../enum/entity'; | 16 | import { RuleChainEntityType } from '../enum/entity'; |
17 | +import { PageEnum } from '/@/enums/pageEnum'; | ||
18 | +import { clearRuleChainImportCache, getRuleChainImportCache } from './useRuleChainCache'; | ||
12 | 19 | ||
13 | const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; | 20 | const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; |
14 | 21 | ||
@@ -19,11 +26,15 @@ export function useSaveAndRedo() { | @@ -19,11 +26,15 @@ export function useSaveAndRedo() { | ||
19 | 26 | ||
20 | const redoDataRef = ref<Elements>([]); | 27 | const redoDataRef = ref<Elements>([]); |
21 | 28 | ||
22 | - const route = useRoute(); | 29 | + const ROUTE = useRoute(); |
30 | + | ||
31 | + const ROUTER = useRouter(); | ||
23 | 32 | ||
24 | const debugMarker = ref(false); | 33 | const debugMarker = ref(false); |
25 | 34 | ||
26 | - const getRuleChainId = computed(() => (route.params as Record<'id', string>).id); | 35 | + const getRuleChainId = computed(() => (ROUTE.params as Record<'id', string>).id); |
36 | + | ||
37 | + const getIsImportFlag = computed(() => ROUTE.fullPath === PageEnum.RULE_CHAIN_DETAIL_IMPORT); | ||
27 | 38 | ||
28 | const ruleChainDetail = ref<RuleChainDetail>(); | 39 | const ruleChainDetail = ref<RuleChainDetail>(); |
29 | 40 | ||
@@ -131,7 +142,9 @@ export function useSaveAndRedo() { | @@ -131,7 +142,9 @@ export function useSaveAndRedo() { | ||
131 | }; | 142 | }; |
132 | 143 | ||
133 | async function getCurrentRuleChainDetail() { | 144 | async function getCurrentRuleChainDetail() { |
134 | - ruleChainDetail.value = await getRuleChainDetail(unref(getRuleChainId)); | 145 | + ruleChainDetail.value = unref(getIsImportFlag) |
146 | + ? (getImportChainDetail() as RuleChainDetail) | ||
147 | + : await getRuleChainDetail(unref(getRuleChainId)); | ||
135 | } | 148 | } |
136 | 149 | ||
137 | async function handleSaveRuleChain( | 150 | async function handleSaveRuleChain( |
@@ -142,19 +155,35 @@ export function useSaveAndRedo() { | @@ -142,19 +155,35 @@ export function useSaveAndRedo() { | ||
142 | try { | 155 | try { |
143 | loading.value = true; | 156 | loading.value = true; |
144 | 157 | ||
158 | + let ruleChainId = unref(getRuleChainId); | ||
159 | + if (unref(getIsImportFlag)) { | ||
160 | + const detail = await saveRuleChainDetail(unref(ruleChainDetail)!); | ||
161 | + ruleChainDetail.value = detail; | ||
162 | + ruleChainId = detail.id.id; | ||
163 | + } | ||
164 | + | ||
145 | const data = await saveRuleChainData({ | 165 | const data = await saveRuleChainData({ |
146 | connections, | 166 | connections, |
147 | nodes, | 167 | nodes, |
148 | firstNodeIndex, | 168 | firstNodeIndex, |
149 | ruleChainId: { | 169 | ruleChainId: { |
150 | entityType: RuleChainEntityType.RULE_CHAIN, | 170 | entityType: RuleChainEntityType.RULE_CHAIN, |
151 | - id: unref(getRuleChainId), | 171 | + id: ruleChainId, |
152 | }, | 172 | }, |
153 | }); | 173 | }); |
154 | 174 | ||
155 | parseRuleChain(data); | 175 | parseRuleChain(data); |
156 | 176 | ||
157 | resetChange(); | 177 | resetChange(); |
178 | + | ||
179 | + if (unref(getIsImportFlag)) { | ||
180 | + clearRuleChainImportCache(); | ||
181 | + | ||
182 | + ROUTER.replace({ | ||
183 | + path: `/rule/chain/${ruleChainId}`, | ||
184 | + replace: true, | ||
185 | + }); | ||
186 | + } | ||
158 | } finally { | 187 | } finally { |
159 | loading.value = false; | 188 | loading.value = false; |
160 | } | 189 | } |
@@ -164,13 +193,16 @@ export function useSaveAndRedo() { | @@ -164,13 +193,16 @@ export function useSaveAndRedo() { | ||
164 | try { | 193 | try { |
165 | loading.value = true; | 194 | loading.value = true; |
166 | 195 | ||
167 | - const data = await getRuleChainData(unref(getRuleChainId)); | 196 | + const data = unref(getIsImportFlag) |
197 | + ? await getImportMetadata() | ||
198 | + : await getRuleChainData(unref(getRuleChainId)); | ||
199 | + if (!data) return; | ||
168 | 200 | ||
169 | const elements = parseRuleChain(data); | 201 | const elements = parseRuleChain(data); |
170 | 202 | ||
171 | flowActionType.setElements(elements); | 203 | flowActionType.setElements(elements); |
172 | 204 | ||
173 | - resetChange(); | 205 | + unref(getIsImportFlag) ? triggerChange() : resetChange(); |
174 | } finally { | 206 | } finally { |
175 | loading.value = false; | 207 | loading.value = false; |
176 | } | 208 | } |
@@ -201,6 +233,12 @@ export function useSaveAndRedo() { | @@ -201,6 +233,12 @@ export function useSaveAndRedo() { | ||
201 | triggerChange(); | 233 | triggerChange(); |
202 | }; | 234 | }; |
203 | 235 | ||
236 | + const getImportMetadata = () => { | ||
237 | + return getRuleChainImportCache().metadata; | ||
238 | + }; | ||
239 | + | ||
240 | + const getImportChainDetail = () => getRuleChainImportCache().ruleChain; | ||
241 | + | ||
204 | return { | 242 | return { |
205 | loading, | 243 | loading, |
206 | debugMarker, | 244 | debugMarker, |
@@ -77,13 +77,33 @@ | @@ -77,13 +77,33 @@ | ||
77 | return { msg, msgType, metadata: toRaw(unref(metadata)), javascriptFunction }; | 77 | return { msg, msgType, metadata: toRaw(unref(metadata)), javascriptFunction }; |
78 | }; | 78 | }; |
79 | 79 | ||
80 | + function executeTestScript() { | ||
81 | + try { | ||
82 | + const { javaScriptEditorProps } = props; | ||
83 | + const { paramsName } = javaScriptEditorProps; | ||
84 | + const fn = new Function(...(paramsName || [])!, unref(scriptContent)); | ||
85 | + const value = getValue(); | ||
86 | + const executeParams = paramsName!.map((key) => value[key]); | ||
87 | + | ||
88 | + return fn(...executeParams); | ||
89 | + } catch (error) { | ||
90 | + return error; | ||
91 | + } | ||
92 | + } | ||
93 | + | ||
80 | const handleTestScript = async () => { | 94 | const handleTestScript = async () => { |
81 | const flag = await handleValidate(); | 95 | const flag = await handleValidate(); |
96 | + | ||
82 | flag && emit('test', getValue()); | 97 | flag && emit('test', getValue()); |
98 | + | ||
99 | + const result = executeTestScript(); | ||
100 | + | ||
101 | + outputContent.value = result; | ||
83 | }; | 102 | }; |
84 | 103 | ||
85 | const handleSave = async () => { | 104 | const handleSave = async () => { |
86 | const flag = await handleValidate(); | 105 | const flag = await handleValidate(); |
106 | + | ||
87 | flag && emit('save', getValue()); | 107 | flag && emit('save', getValue()); |
88 | }; | 108 | }; |
89 | 109 |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | + import { Badge } from 'ant-design-vue'; | ||
2 | import { cloneDeep } from 'lodash'; | 3 | import { cloneDeep } from 'lodash'; |
3 | import { computed, ref, unref } from 'vue'; | 4 | import { computed, ref, unref } from 'vue'; |
4 | import { allComponents } from '../../../packages'; | 5 | import { allComponents } from '../../../packages'; |
@@ -27,13 +28,21 @@ | @@ -27,13 +28,21 @@ | ||
27 | <template> | 28 | <template> |
28 | <section class="absolute top-11 w-full flex flex-auto h-[calc(100%-2.75rem)] bg-neutral-100"> | 29 | <section class="absolute top-11 w-full flex flex-auto h-[calc(100%-2.75rem)] bg-neutral-100"> |
29 | <nav class="w-20 min-w-20 p-2 border-t border-light-50"> | 30 | <nav class="w-20 min-w-20 p-2 border-t border-light-50"> |
30 | - <CategoryItem | 31 | + <Badge |
31 | v-for="category in allComponents" | 32 | v-for="category in allComponents" |
32 | :key="category.category.category" | 33 | :key="category.category.category" |
33 | - v-model:activeKey="activeKey" | ||
34 | - :category="category" | ||
35 | - /> | 34 | + :dot="!!searchText && |
35 | + category.components.some((item) => | ||
36 | + item.name.toUpperCase().includes(searchText!.toUpperCase()) | ||
37 | + ) | ||
38 | + " | ||
39 | + :offset="[-10, 10]" | ||
40 | + class="!block" | ||
41 | + > | ||
42 | + <CategoryItem v-model:activeKey="activeKey" :category="category" /> | ||
43 | + </Badge> | ||
36 | </nav> | 44 | </nav> |
45 | + | ||
37 | <body class="p-4 w-full flex flex-col gap-2 items-center overflow-x-hidden overflow-y-auto"> | 46 | <body class="p-4 w-full flex flex-col gap-2 items-center overflow-x-hidden overflow-y-auto"> |
38 | <NodeItem | 47 | <NodeItem |
39 | v-for="config in getCurrentCategoryNode.components" | 48 | v-for="config in getCurrentCategoryNode.components" |