Showing
21 changed files
with
900 additions
and
46 deletions
1 | 1 | import { RuleChainPaginationItemType } from './model/type'; |
2 | 2 | import { TBPaginationResult } from '/#/axios'; |
3 | 3 | import { defHttp } from '/@/utils/http/axios'; |
4 | -import { RuleChainType } from '/@/views/rule/designer/types/ruleNode'; | |
4 | +import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode'; | |
5 | 5 | |
6 | 6 | enum Api { |
7 | + GET_RULE_CHAINS_DETAIL = '/ruleChain', | |
7 | 8 | SAVE = '/ruleChain/metadata', |
8 | 9 | GET_RULE_CHAINES = '/ruleChains', |
9 | 10 | GET_RULE_NODE_EVENTS = '/events/RULE_NODE', |
10 | 11 | } |
11 | 12 | |
13 | +export const getRuleChainDetail = (id: string) => { | |
14 | + return defHttp.get<RuleChainDetail>( | |
15 | + { | |
16 | + url: `${Api.GET_RULE_CHAINS_DETAIL}/${id}`, | |
17 | + }, | |
18 | + { joinPrefix: false } | |
19 | + ); | |
20 | +}; | |
21 | + | |
12 | 22 | export const getRuleChainData = (id: string) => { |
13 | 23 | return defHttp.get<RuleChainType>( |
14 | 24 | { | ... | ... |
... | ... | @@ -38,6 +38,11 @@ export const PLATFORM = 'PLATFORM'; |
38 | 38 | export const PLATFORM_INFO_CACHE_KEY = 'PLATFORM_INFO'; |
39 | 39 | |
40 | 40 | export const MENU_LIST = 'MENU_LIST'; |
41 | + | |
42 | +export const RULE_NODE_LOCAL_CACHE_KEY = 'RULE__NODE__KEY__'; | |
43 | + | |
44 | +export const RULE_NODE_KEY = 'RULE_NODE'; | |
45 | + | |
41 | 46 | export enum CacheTypeEnum { |
42 | 47 | SESSION, |
43 | 48 | LOCAL, | ... | ... |
src/views/rule/designer/enum/entity.ts
0 → 100644
1 | 1 | import { Node } from '@vue-flow/core'; |
2 | 2 | import { NodeTypeEnum } from '../enum'; |
3 | 3 | import { buildUUID } from '/@/utils/uuid'; |
4 | +import { NodeData } from '../types/node'; | |
4 | 5 | |
5 | 6 | export const useAddNodes = () => { |
6 | 7 | const getAddNodesParams = ( |
7 | 8 | position: Node['position'], |
8 | - data: object, | |
9 | + data: NodeData, | |
9 | 10 | options?: Partial<Node> |
10 | 11 | ): Node => { |
11 | 12 | return { | ... | ... |
1 | +import { GraphEdge, GraphNode, VueFlowStore } from '@vue-flow/core'; | |
2 | +import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste'; | |
3 | +import { RuleContextMenuEnum } from './useRuleChainContextMenu'; | |
4 | +import { useAddNodes } from './useAddNodes'; | |
5 | +import { buildUUID } from '/@/utils/uuid'; | |
6 | +import { toRaw, unref } from 'vue'; | |
7 | +import { useSaveAndRedo } from './useSaveAndRedo'; | |
8 | +import { isUnDef } from '/@/utils/is'; | |
9 | +import { useAddEdges } from './useAddEdges'; | |
10 | +import { EdgeData } from '../types/node'; | |
11 | + | |
12 | +interface HandleContextMenuActionParamsType { | |
13 | + menuType: RuleContextMenuEnum; | |
14 | + event?: Event; | |
15 | + flowActionType?: VueFlowStore; | |
16 | + node?: GraphNode; | |
17 | + edge?: GraphEdge; | |
18 | + useSaveAndRedoActionType?: ReturnType<typeof useSaveAndRedo>; | |
19 | +} | |
20 | + | |
21 | +export const NODE_WIDTH = 176; | |
22 | +export const NODE_HEIGHT = 48; | |
23 | + | |
24 | +function getElementsCenter(nodes: GraphNode[]) { | |
25 | + let leftTopX: number | undefined; | |
26 | + let leftTopY: number | undefined; | |
27 | + let rightBottomX: number | undefined; | |
28 | + let rightBottomY: number | undefined; | |
29 | + | |
30 | + for (const node of nodes) { | |
31 | + const { position } = node; | |
32 | + const { x, y } = position; | |
33 | + if (isUnDef(leftTopX)) { | |
34 | + leftTopX = x; | |
35 | + leftTopY = y; | |
36 | + rightBottomX = x + NODE_WIDTH; | |
37 | + rightBottomY = y + NODE_HEIGHT; | |
38 | + | |
39 | + continue; | |
40 | + } | |
41 | + | |
42 | + if (x < leftTopX!) { | |
43 | + leftTopX = x; | |
44 | + if (y < leftTopY!) { | |
45 | + leftTopY = y; | |
46 | + } | |
47 | + continue; | |
48 | + } | |
49 | + | |
50 | + if (x + NODE_WIDTH > rightBottomX!) { | |
51 | + rightBottomX = x + NODE_WIDTH; | |
52 | + if (y + NODE_HEIGHT > rightBottomY!) { | |
53 | + rightBottomY = y + NODE_HEIGHT; | |
54 | + } | |
55 | + } | |
56 | + } | |
57 | + | |
58 | + return { | |
59 | + originX: (rightBottomX! - leftTopX!) / 2 + leftTopX!, | |
60 | + originY: (rightBottomY! - leftTopY!) / 2 + leftTopY!, | |
61 | + }; | |
62 | +} | |
63 | + | |
64 | +export function useContextMenuAction() { | |
65 | + const copy = (params: HandleContextMenuActionParamsType) => { | |
66 | + const { node } = params; | |
67 | + if (!node) return; | |
68 | + const { position, data } = node; | |
69 | + const { getAddNodesParams } = useAddNodes(); | |
70 | + const { x, y } = position; | |
71 | + | |
72 | + const value = getAddNodesParams(position, data, { id: buildUUID() }); | |
73 | + | |
74 | + setRuleNodeCache({ | |
75 | + nodes: [value], | |
76 | + originX: x + NODE_WIDTH / 2 + x, | |
77 | + originY: y + NODE_HEIGHT / 2, | |
78 | + }); | |
79 | + }; | |
80 | + | |
81 | + const paste = (params: HandleContextMenuActionParamsType) => { | |
82 | + const { event, flowActionType, useSaveAndRedoActionType } = params; | |
83 | + const { triggerChange } = useSaveAndRedoActionType || {}; | |
84 | + const { getAddNodesParams } = useAddNodes(); | |
85 | + const { getAddedgesParams } = useAddEdges(); | |
86 | + const clientX = (event as MouseEvent).offsetX; | |
87 | + const clientY = (event as MouseEvent).offsetY; | |
88 | + | |
89 | + const { edges = [], nodes = [], originX, originY } = getRuleNodeCache(); | |
90 | + | |
91 | + const newNode = nodes.map((node) => { | |
92 | + const { position, data, id } = node; | |
93 | + const { x, y } = position; | |
94 | + | |
95 | + const newX = clientX - originX! + x + NODE_WIDTH / 2; | |
96 | + const newY = clientY - originY! + y + NODE_HEIGHT / 2; | |
97 | + | |
98 | + return getAddNodesParams({ x: newX, y: newY }, { ...data, created: false }, { id }); | |
99 | + }); | |
100 | + | |
101 | + const newEdges = edges.map((edge) => getAddedgesParams(edge, edge.data)); | |
102 | + | |
103 | + flowActionType?.addNodes(newNode); | |
104 | + flowActionType?.addEdges(newEdges); | |
105 | + | |
106 | + triggerChange?.(); | |
107 | + | |
108 | + flowActionType?.removeSelectedElements(); | |
109 | + }; | |
110 | + | |
111 | + const selectAll = (params: HandleContextMenuActionParamsType) => { | |
112 | + const { flowActionType } = params; | |
113 | + flowActionType?.addSelectedElements(unref(flowActionType.getElements)); | |
114 | + }; | |
115 | + | |
116 | + const unselect = (params: HandleContextMenuActionParamsType) => { | |
117 | + const { flowActionType } = params; | |
118 | + flowActionType?.removeSelectedElements(); | |
119 | + }; | |
120 | + | |
121 | + const deleteElement = (parmas: HandleContextMenuActionParamsType) => { | |
122 | + const { useSaveAndRedoActionType, flowActionType, node, edge } = parmas; | |
123 | + const { triggerChange } = useSaveAndRedoActionType || {}; | |
124 | + | |
125 | + node && flowActionType?.removeNodes(node); | |
126 | + edge && flowActionType?.removeEdges(edge); | |
127 | + | |
128 | + triggerChange?.(); | |
129 | + }; | |
130 | + | |
131 | + const deleteElements = (params: HandleContextMenuActionParamsType) => { | |
132 | + const { flowActionType, useSaveAndRedoActionType } = params; | |
133 | + flowActionType?.removeNodes(unref(flowActionType.getSelectedNodes)); | |
134 | + | |
135 | + useSaveAndRedoActionType?.triggerChange?.(); | |
136 | + }; | |
137 | + | |
138 | + const selectCopy = (params: HandleContextMenuActionParamsType) => { | |
139 | + const { flowActionType } = params; | |
140 | + const { getAddNodesParams } = useAddNodes(); | |
141 | + const { getAddedgesParams } = useAddEdges(); | |
142 | + | |
143 | + const edges = unref(flowActionType?.getSelectedEdges)?.map((edge) => | |
144 | + getAddedgesParams( | |
145 | + { | |
146 | + source: edge.source, | |
147 | + target: edge.target, | |
148 | + sourceHandle: edge.sourceHandle, | |
149 | + targetHandle: edge.targetHandle, | |
150 | + }, | |
151 | + toRaw(unref(edge.data as EdgeData)?.data) | |
152 | + ) | |
153 | + ); | |
154 | + | |
155 | + 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 | + } | |
171 | + | |
172 | + return getAddNodesParams(node.position, toRaw(unref(node.data)), { id: newId }); | |
173 | + }); | |
174 | + | |
175 | + const originRect = getElementsCenter(unref(flowActionType?.getSelectedNodes) || []); | |
176 | + | |
177 | + setRuleNodeCache({ nodes, edges, ...originRect }); | |
178 | + }; | |
179 | + | |
180 | + const applyChange = (params: HandleContextMenuActionParamsType) => { | |
181 | + const { useSaveAndRedoActionType, flowActionType } = params; | |
182 | + | |
183 | + useSaveAndRedoActionType?.handleApplyChange(flowActionType!); | |
184 | + }; | |
185 | + | |
186 | + const undoChange = (params: HandleContextMenuActionParamsType) => { | |
187 | + const { useSaveAndRedoActionType, flowActionType } = params; | |
188 | + | |
189 | + useSaveAndRedoActionType?.handleRedoChange(flowActionType!); | |
190 | + }; | |
191 | + | |
192 | + const handleContextMenuAction = (params: HandleContextMenuActionParamsType) => { | |
193 | + const { menuType } = params; | |
194 | + | |
195 | + const handlerMapping = { | |
196 | + [RuleContextMenuEnum.COPY]: copy, | |
197 | + [RuleContextMenuEnum.PASTE]: paste, | |
198 | + [RuleContextMenuEnum.SELECT_ALL]: selectAll, | |
199 | + [RuleContextMenuEnum.UNSELECTED]: unselect, | |
200 | + [RuleContextMenuEnum.DELETE]: deleteElement, | |
201 | + [RuleContextMenuEnum.DELETE_SELECT]: deleteElements, | |
202 | + [RuleContextMenuEnum.SELECT_COPY]: selectCopy, | |
203 | + [RuleContextMenuEnum.APPLY_CHANGE]: applyChange, | |
204 | + [RuleContextMenuEnum.UNDO_CHANGE]: undoChange, | |
205 | + }; | |
206 | + | |
207 | + if (handlerMapping[menuType]) { | |
208 | + handlerMapping[menuType]?.(params); | |
209 | + } | |
210 | + }; | |
211 | + | |
212 | + return { | |
213 | + handleContextMenuAction, | |
214 | + }; | |
215 | +} | ... | ... |
1 | +import { NodeMouseEvent } from '@vue-flow/core'; | |
2 | +import { useContextMenu } from '../src/components/RuleChainContextMenu'; | |
3 | +import { RuleChainContextMenuItemType } from '../src/components/RuleChainContextMenu/index.type'; | |
4 | +import { ElementsTypeEnum } from '../enum'; | |
5 | +import { checkHasCacheRuleNode } from './useRuleCopyPaste'; | |
6 | + | |
7 | +export enum RuleContextMenuEnum { | |
8 | + DETAIL = 'DETAIL', | |
9 | + COPY = 'COPY', | |
10 | + DELETE = 'DELETE', | |
11 | + | |
12 | + SELECT_COPY = 'SELECT_COPY', | |
13 | + PASTE = 'PASTE', | |
14 | + UNSELECTED = 'UNSELECTED', | |
15 | + DELETE_SELECT = 'DELETE_SELECT', | |
16 | + APPLY_CHANGE = 'APPLY_CHANGE', | |
17 | + UNDO_CHANGE = 'UNDO_CHANGE', | |
18 | + | |
19 | + SELECT_ALL = 'SELECT_ALL', | |
20 | +} | |
21 | + | |
22 | +export enum RuleContextMenuNameEnum { | |
23 | + DETAIL = '详情', | |
24 | + COPY = '复制', | |
25 | + DELETE = '删除', | |
26 | + | |
27 | + SELECT_COPY = '选择副本', | |
28 | + PASTE = '粘贴', | |
29 | + UNSELECTED = '取消选择', | |
30 | + DELETE_SELECT = '删除选定', | |
31 | + APPLY_CHANGE = '应用更改', | |
32 | + UNDO_CHANGE = '撤销更改', | |
33 | + | |
34 | + SELECT_ALL = '选择全部', | |
35 | +} | |
36 | + | |
37 | +export enum RuleChainContextMenuIconEnum { | |
38 | + DETAIL = 'material-symbols:menu', | |
39 | + COPY = 'material-symbols:content-copy', | |
40 | + DELETE = 'material-symbols:delete', | |
41 | + | |
42 | + SELECT_COPY = 'material-symbols:content-copy', | |
43 | + PASTE = 'material-symbols:content-paste', | |
44 | + UNSELECTED = 'material-symbols:tab-unselected', | |
45 | + DELETE_SELECT = 'material-symbols:close', | |
46 | + APPLY_CHANGE = 'material-symbols:done', | |
47 | + UNDO_CHANGE = 'material-symbols:close', | |
48 | + | |
49 | + SELECT_ALL = 'material-symbols:select-all', | |
50 | + | |
51 | + // LINK = 'material-symbols:trending-flat', | |
52 | +} | |
53 | + | |
54 | +export enum RuleChainContextMenuShortcutKeyEnum { | |
55 | + DELETE = 'Ctrl(⌘) X', | |
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', | |
63 | + | |
64 | + SELECT_ALL = 'Ctrl(⌘) A', | |
65 | +} | |
66 | + | |
67 | +const getMenuItem = (key: RuleContextMenuEnum, handler: Fn, disabled = false) => { | |
68 | + return { | |
69 | + key, | |
70 | + label: RuleContextMenuNameEnum[key], | |
71 | + icon: RuleChainContextMenuIconEnum[key], | |
72 | + shortcutKey: RuleChainContextMenuShortcutKeyEnum[key], | |
73 | + handler: () => handler?.(key), | |
74 | + disabled, | |
75 | + } as RuleChainContextMenuItemType; | |
76 | +}; | |
77 | + | |
78 | +const getDivider = (): RuleChainContextMenuItemType => ({ divider: true }); | |
79 | + | |
80 | +export function useCreateRuleChainContextMenu() { | |
81 | + const [createContextMenu] = useContextMenu(); | |
82 | + | |
83 | + const createNodeContextMenu = (params: NodeMouseEvent): Promise<RuleContextMenuEnum | ''> => { | |
84 | + return new Promise(async (resolve) => { | |
85 | + await createContextMenu(params, { | |
86 | + items: [ | |
87 | + getMenuItem(RuleContextMenuEnum.DETAIL, resolve), | |
88 | + getMenuItem(RuleContextMenuEnum.COPY, resolve), | |
89 | + getMenuItem(RuleContextMenuEnum.DELETE, resolve), | |
90 | + ], | |
91 | + }); | |
92 | + resolve(''); | |
93 | + }); | |
94 | + }; | |
95 | + | |
96 | + const createElementsSelectedContextMenu = ( | |
97 | + params: NodeMouseEvent, | |
98 | + changeMarker: boolean, | |
99 | + elementsType: ElementsTypeEnum.NODE = ElementsTypeEnum.NODE | |
100 | + ): Promise<RuleContextMenuEnum | ''> => { | |
101 | + return new Promise(async (resolve) => { | |
102 | + await createContextMenu(params, { | |
103 | + items: [ | |
104 | + ...(elementsType === ElementsTypeEnum.NODE | |
105 | + ? [getMenuItem(RuleContextMenuEnum.SELECT_COPY, resolve)] | |
106 | + : []), | |
107 | + getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()), | |
108 | + getDivider(), | |
109 | + getMenuItem(RuleContextMenuEnum.UNSELECTED, resolve), | |
110 | + getMenuItem(RuleContextMenuEnum.DELETE_SELECT, resolve), | |
111 | + getDivider(), | |
112 | + getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker), | |
113 | + getMenuItem(RuleContextMenuEnum.UNDO_CHANGE, resolve, !changeMarker), | |
114 | + ], | |
115 | + }); | |
116 | + resolve(''); | |
117 | + }); | |
118 | + }; | |
119 | + | |
120 | + const createPanelContextMenu = ( | |
121 | + params: NodeMouseEvent, | |
122 | + changeMarker: boolean | |
123 | + ): Promise<RuleContextMenuEnum | ''> => { | |
124 | + return new Promise(async (resolve) => { | |
125 | + await createContextMenu(params, { | |
126 | + items: [ | |
127 | + getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()), | |
128 | + getMenuItem(RuleContextMenuEnum.SELECT_ALL, resolve), | |
129 | + getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker), | |
130 | + getMenuItem(RuleContextMenuEnum.UNDO_CHANGE, resolve, !changeMarker), | |
131 | + ], | |
132 | + }); | |
133 | + resolve(''); | |
134 | + }); | |
135 | + }; | |
136 | + | |
137 | + const createEdgeContextMenu = ( | |
138 | + params: NodeMouseEvent, | |
139 | + isInput = false | |
140 | + ): Promise<RuleContextMenuEnum | ''> => { | |
141 | + return new Promise(async (resolve) => { | |
142 | + await createContextMenu(params, { | |
143 | + items: [ | |
144 | + ...(isInput ? [] : [getMenuItem(RuleContextMenuEnum.DETAIL, resolve)]), | |
145 | + getMenuItem(RuleContextMenuEnum.DELETE, resolve), | |
146 | + ], | |
147 | + }); | |
148 | + resolve(''); | |
149 | + }); | |
150 | + }; | |
151 | + return { | |
152 | + createNodeContextMenu, | |
153 | + createElementsSelectedContextMenu, | |
154 | + createPanelContextMenu, | |
155 | + createEdgeContextMenu, | |
156 | + }; | |
157 | +} | ... | ... |
1 | +import { Edge, Node } from '@vue-flow/core'; | |
2 | +import { RULE_NODE_KEY, RULE_NODE_LOCAL_CACHE_KEY } from '/@/enums/cacheEnum'; | |
3 | +import { createLocalStorage } from '/@/utils/cache'; | |
4 | + | |
5 | +const ruleNodeStorage = createLocalStorage({ prefixKey: RULE_NODE_LOCAL_CACHE_KEY }); | |
6 | + | |
7 | +interface RuleNodeCacheType { | |
8 | + nodes?: Node[]; | |
9 | + edges?: Edge[]; | |
10 | + originX?: number; | |
11 | + originY?: number; | |
12 | +} | |
13 | + | |
14 | +export const setRuleNodeCache = ({ | |
15 | + nodes = [], | |
16 | + edges = [], | |
17 | + originX, | |
18 | + originY, | |
19 | +}: RuleNodeCacheType) => { | |
20 | + ruleNodeStorage.set(RULE_NODE_KEY, { | |
21 | + nodes, | |
22 | + edges, | |
23 | + originX, | |
24 | + originY, | |
25 | + }); | |
26 | +}; | |
27 | + | |
28 | +export const getRuleNodeCache = (): RuleNodeCacheType => ruleNodeStorage.get(RULE_NODE_KEY); | |
29 | + | |
30 | +export const checkHasCacheRuleNode = () => !!getRuleNodeCache(); | |
31 | + | |
32 | +function initRuleNodeStorage() { | |
33 | + const value = ruleNodeStorage.get(RULE_NODE_KEY); | |
34 | + value && ruleNodeStorage.set(RULE_NODE_KEY, value); | |
35 | +} | |
36 | + | |
37 | +initRuleNodeStorage(); | ... | ... |
... | ... | @@ -4,29 +4,38 @@ import type { |
4 | 4 | NodeComponent, |
5 | 5 | ValidConnectionFunc, |
6 | 6 | GraphNode, |
7 | + NodeMouseEvent, | |
8 | + GraphEdge, | |
9 | + EdgeMouseEvent, | |
7 | 10 | } from '@vue-flow/core'; |
8 | -import { ConnectionLineType, SelectionMode, useVueFlow } from '@vue-flow/core'; | |
9 | 11 | import type { Ref } from 'vue'; |
10 | -import { markRaw, toRaw, unref } from 'vue'; | |
11 | -import { isFunction } from 'lodash-es'; | |
12 | 12 | import type { CreateNodeModal } from '../src/components/CreateNodeModal'; |
13 | -import { EdgeTypeEnum, ElementsTypeEnum, NodeTypeEnum } from '../enum'; | |
14 | -import { BasicEdge, BasicNode } from '../src/components'; | |
15 | 13 | import type { EdgeData, NodeData } from '../types/node'; |
16 | 14 | import type { CreateEdgeModal } from '../src/components/CreateEdgeModal'; |
15 | +import { BasicEdge, BasicNode } from '../src/components'; | |
16 | +import { EdgeTypeEnum, ElementsTypeEnum, NodeTypeEnum } from '../enum'; | |
17 | +import { markRaw, toRaw, unref } from 'vue'; | |
18 | +import { isFunction } from 'lodash-es'; | |
19 | +import { ConnectionLineType, SelectionMode, useVueFlow } from '@vue-flow/core'; | |
17 | 20 | import { isInputHandle, isOutputHandle } from '../utils'; |
18 | 21 | import { useAddEdges } from './useAddEdges'; |
19 | 22 | import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer'; |
20 | 23 | import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer'; |
21 | 24 | import { isNumber } from '/@/utils/is'; |
25 | +import { RuleContextMenuEnum, useCreateRuleChainContextMenu } from './useRuleChainContextMenu'; | |
26 | +import { RuleChainDetail } from '../types/ruleNode'; | |
27 | +import { useContextMenuAction } from './useContextMenuAction'; | |
28 | +import { useSaveAndRedo } from './useSaveAndRedo'; | |
29 | +import { EntryCategoryComponentEnum } from '../enum/category'; | |
22 | 30 | |
23 | 31 | interface UseRuleFlowOptionsType { |
24 | 32 | id: string; |
33 | + ruleChainDetail: Ref<RuleChainDetail | undefined>; | |
25 | 34 | createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; |
26 | 35 | createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; |
27 | 36 | updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; |
28 | 37 | updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; |
29 | - triggerChange: () => void; | |
38 | + useSaveAndRedoActionType: ReturnType<typeof useSaveAndRedo>; | |
30 | 39 | } |
31 | 40 | |
32 | 41 | const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => { |
... | ... | @@ -40,12 +49,15 @@ const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => |
40 | 49 | export function useRuleFlow(options: UseRuleFlowOptionsType) { |
41 | 50 | const { |
42 | 51 | id, |
52 | + ruleChainDetail, | |
43 | 53 | createEdgeModalActionType, |
44 | 54 | updateEdgeDrawerActionType, |
45 | 55 | updateNodeDrawerActionType, |
46 | - triggerChange, | |
56 | + useSaveAndRedoActionType, | |
47 | 57 | } = options; |
48 | 58 | |
59 | + const { triggerChange } = useSaveAndRedoActionType; | |
60 | + | |
49 | 61 | const flowActionType = useVueFlow({ |
50 | 62 | id, |
51 | 63 | maxZoom: 1, |
... | ... | @@ -96,6 +108,9 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
96 | 108 | onNodeDoubleClick, |
97 | 109 | onEdgeDoubleClick, |
98 | 110 | onNodeDragStop, |
111 | + onNodeContextMenu, | |
112 | + onEdgeContextMenu, | |
113 | + onPaneContextMenu, | |
99 | 114 | } = flowActionType; |
100 | 115 | |
101 | 116 | const { getAddedgesParams } = useAddEdges(); |
... | ... | @@ -125,6 +140,119 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
125 | 140 | }); |
126 | 141 | |
127 | 142 | onNodeDoubleClick(async ({ node }) => { |
143 | + handleUpdateNode(node); | |
144 | + }); | |
145 | + | |
146 | + onEdgeDoubleClick(async ({ edge }) => { | |
147 | + handleUpdateEdge(edge); | |
148 | + }); | |
149 | + | |
150 | + onNodeDragStop(() => { | |
151 | + triggerChange(); | |
152 | + }); | |
153 | + | |
154 | + const { | |
155 | + createNodeContextMenu, | |
156 | + createElementsSelectedContextMenu, | |
157 | + createEdgeContextMenu, | |
158 | + createPanelContextMenu, | |
159 | + } = useCreateRuleChainContextMenu(); | |
160 | + | |
161 | + const { handleContextMenuAction } = useContextMenuAction(); | |
162 | + | |
163 | + onNodeContextMenu(async (params) => { | |
164 | + const menuType = params.node.selected | |
165 | + ? await createElementsSelectedContextMenu( | |
166 | + params, | |
167 | + unref(useSaveAndRedoActionType.changeMarker) | |
168 | + ) | |
169 | + : await createNodeContextMenu(params); | |
170 | + | |
171 | + if (menuType) { | |
172 | + if (menuType === RuleContextMenuEnum.DETAIL) { | |
173 | + handleUpdateNode(params.node); | |
174 | + return; | |
175 | + } | |
176 | + | |
177 | + handleContextMenuAction({ | |
178 | + menuType, | |
179 | + flowActionType, | |
180 | + event: params.event, | |
181 | + node: params.node, | |
182 | + useSaveAndRedoActionType, | |
183 | + }); | |
184 | + } | |
185 | + }); | |
186 | + | |
187 | + onEdgeContextMenu(async (params) => { | |
188 | + const isInputNode = | |
189 | + (params.edge.sourceNode.data as NodeData).config?.key === EntryCategoryComponentEnum.INPUT; | |
190 | + const menuType = await createEdgeContextMenu( | |
191 | + getCreateEdgeContextMenuParams(params), | |
192 | + isInputNode | |
193 | + ); | |
194 | + | |
195 | + if (menuType) { | |
196 | + if (menuType === RuleContextMenuEnum.DETAIL) { | |
197 | + handleUpdateEdge(params.edge); | |
198 | + return; | |
199 | + } | |
200 | + | |
201 | + handleContextMenuAction({ | |
202 | + menuType, | |
203 | + flowActionType, | |
204 | + event: params.event, | |
205 | + useSaveAndRedoActionType, | |
206 | + edge: params.edge, | |
207 | + }); | |
208 | + } | |
209 | + }); | |
210 | + | |
211 | + onPaneContextMenu(async (params) => { | |
212 | + const menuType = unref(flowActionType.getSelectedElements).length | |
213 | + ? await createElementsSelectedContextMenu( | |
214 | + getCreatePanelContextMenuParams(params), | |
215 | + unref(useSaveAndRedoActionType.changeMarker) | |
216 | + ) | |
217 | + : await createPanelContextMenu( | |
218 | + getCreatePanelContextMenuParams(params), | |
219 | + unref(useSaveAndRedoActionType.changeMarker) | |
220 | + ); | |
221 | + | |
222 | + if (menuType) { | |
223 | + handleContextMenuAction({ | |
224 | + menuType, | |
225 | + flowActionType, | |
226 | + event: params, | |
227 | + useSaveAndRedoActionType, | |
228 | + }); | |
229 | + } | |
230 | + }); | |
231 | + | |
232 | + /** | |
233 | + * @description 验证是否有连接label | |
234 | + * @param sourceData | |
235 | + * @returns | |
236 | + */ | |
237 | + function validateHasLabelConnection(sourceData: NodeData) { | |
238 | + return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; | |
239 | + } | |
240 | + | |
241 | + function handleMaxConnectionPoint(sourceNode?: GraphNode) { | |
242 | + if (!sourceNode) return; | |
243 | + | |
244 | + const maxConnectionPoint = unref(sourceNode).data?.config?.maxConnectionPoint; | |
245 | + | |
246 | + if (!maxConnectionPoint || !isNumber(maxConnectionPoint)) return; | |
247 | + | |
248 | + const sourceId = sourceNode.id; | |
249 | + const connectionPool = unref(getEdges).filter((item) => item.source === sourceId); | |
250 | + if (connectionPool.length >= maxConnectionPoint && connectionPool[0]) { | |
251 | + removeEdges(connectionPool[0].id); | |
252 | + } | |
253 | + } | |
254 | + | |
255 | + async function handleUpdateNode(node: GraphNode) { | |
128 | 256 | if ((node.data as NodeData).config?.disableAction) return; |
129 | 257 | const { flag, data } = |
130 | 258 | (await unref(updateNodeDrawerActionType)?.open( |
... | ... | @@ -137,9 +265,10 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
137 | 265 | |
138 | 266 | const currentNode = findNode(node.id); |
139 | 267 | (currentNode!.data as NodeData).data = data; |
140 | - }); | |
268 | + triggerChange(); | |
269 | + } | |
141 | 270 | |
142 | - onEdgeDoubleClick(async ({ edge }) => { | |
271 | + async function handleUpdateEdge(edge: GraphEdge) { | |
143 | 272 | if (!validateHasLabelConnection(edge.sourceNode.data)) return; |
144 | 273 | |
145 | 274 | if ((edge.sourceNode.data as NodeData).config?.disableAction) return; |
... | ... | @@ -156,33 +285,48 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { |
156 | 285 | const currentEdge = findEdge(edge.id); |
157 | 286 | |
158 | 287 | (currentEdge!.data as EdgeData).data = toRaw(unref(data)); |
159 | - }); | |
160 | - | |
161 | - onNodeDragStop(() => { | |
162 | - triggerChange(); | |
163 | - }); | |
164 | 288 | |
165 | - /** | |
166 | - * @description 验证是否有连接label | |
167 | - * @param sourceData | |
168 | - * @returns | |
169 | - */ | |
170 | - function validateHasLabelConnection(sourceData: NodeData) { | |
171 | - return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; | |
289 | + triggerChange?.(); | |
172 | 290 | } |
173 | 291 | |
174 | - function handleMaxConnectionPoint(sourceNode?: GraphNode) { | |
175 | - if (!sourceNode) return; | |
176 | - | |
177 | - const maxConnectionPoint = unref(sourceNode).data?.config?.maxConnectionPoint; | |
178 | - | |
179 | - if (!maxConnectionPoint || !isNumber(maxConnectionPoint)) return; | |
292 | + function getCreatePanelContextMenuParams(params: Event) { | |
293 | + return { | |
294 | + event: params as MouseEvent, | |
295 | + node: { | |
296 | + data: { | |
297 | + data: { name: '规则链' }, | |
298 | + config: { | |
299 | + name: unref(ruleChainDetail)?.name, | |
300 | + backgroundColor: '#aac7e4', | |
301 | + configurationDescriptor: { | |
302 | + nodeDefinition: { | |
303 | + icon: 'material-symbols:settings-ethernet', | |
304 | + }, | |
305 | + }, | |
306 | + }, | |
307 | + }, | |
308 | + }, | |
309 | + } as NodeMouseEvent; | |
310 | + } | |
180 | 311 | |
181 | - const sourceId = sourceNode.id; | |
182 | - const connectionPool = unref(getEdges).filter((item) => item.source === sourceId); | |
183 | - if (connectionPool.length >= maxConnectionPoint && connectionPool[0]) { | |
184 | - removeEdges(connectionPool[0].id); | |
185 | - } | |
312 | + function getCreateEdgeContextMenuParams(params: EdgeMouseEvent) { | |
313 | + return { | |
314 | + event: params.event as MouseEvent, | |
315 | + node: { | |
316 | + data: { | |
317 | + data: { name: '链接' }, | |
318 | + config: { | |
319 | + name: unref(params.edge.data as EdgeData)?.data?.type?.join(' / '), | |
320 | + backgroundColor: '#aac7e4', | |
321 | + configurationDescriptor: { | |
322 | + nodeDefinition: { | |
323 | + icon: 'material-symbols:trending-flat', | |
324 | + }, | |
325 | + }, | |
326 | + }, | |
327 | + }, | |
328 | + }, | |
329 | + } as NodeMouseEvent; | |
186 | 330 | } |
187 | 331 | |
188 | 332 | return { flowActionType }; | ... | ... |
... | ... | @@ -3,11 +3,12 @@ import { ComputedRef, computed, ref, unref } from 'vue'; |
3 | 3 | import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; |
4 | 4 | import { EntryCategoryComponentEnum } from '../enum/category'; |
5 | 5 | import { useBasicDataTransform } from './useBasicDataTransform'; |
6 | -import { getRuleChainData, saveRuleChainData } from '/@/api/ruleDesigner'; | |
7 | -import { ConnectionItemType, RuleChainType } from '../types/ruleNode'; | |
6 | +import { getRuleChainData, getRuleChainDetail, saveRuleChainData } from '/@/api/ruleDesigner'; | |
7 | +import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode'; | |
8 | 8 | import { useInputNode } from './useInputNode'; |
9 | 9 | import { buildUUID } from '/@/utils/uuid'; |
10 | 10 | import { useRoute } from 'vue-router'; |
11 | +import { RuleChainEntityType } from '../enum/entity'; | |
11 | 12 | |
12 | 13 | const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; |
13 | 14 | |
... | ... | @@ -24,6 +25,8 @@ export function useSaveAndRedo() { |
24 | 25 | |
25 | 26 | const getRuleChainId = computed(() => (route.params as Record<'id', string>).id); |
26 | 27 | |
28 | + const ruleChainDetail = ref<RuleChainDetail>(); | |
29 | + | |
27 | 30 | const { mergeData, deconstructionData } = useBasicDataTransform(); |
28 | 31 | |
29 | 32 | const triggerChange = () => { |
... | ... | @@ -73,7 +76,16 @@ export function useSaveAndRedo() { |
73 | 76 | |
74 | 77 | const data = nodeData.data; |
75 | 78 | |
76 | - nodes.push(mergeData(data, nodeData, node)); | |
79 | + nodes.push( | |
80 | + Object.assign( | |
81 | + mergeData(data, nodeData, node), | |
82 | + nodeData.created | |
83 | + ? ({ | |
84 | + id: { id: node.id, entityType: RuleChainEntityType.RULE_NODE }, | |
85 | + } as BasicNodeBindData) | |
86 | + : {} | |
87 | + ) | |
88 | + ); | |
77 | 89 | } |
78 | 90 | |
79 | 91 | return nodes; |
... | ... | @@ -118,6 +130,10 @@ export function useSaveAndRedo() { |
118 | 130 | resetChange(); |
119 | 131 | }; |
120 | 132 | |
133 | + async function getCurrentRuleChainDetail() { | |
134 | + ruleChainDetail.value = await getRuleChainDetail(unref(getRuleChainId)); | |
135 | + } | |
136 | + | |
121 | 137 | async function handleSaveRuleChain( |
122 | 138 | connections: ConnectionItemType[], |
123 | 139 | nodes: BasicNodeBindData[], |
... | ... | @@ -125,12 +141,13 @@ export function useSaveAndRedo() { |
125 | 141 | ) { |
126 | 142 | try { |
127 | 143 | loading.value = true; |
144 | + | |
128 | 145 | const data = await saveRuleChainData({ |
129 | 146 | connections, |
130 | 147 | nodes, |
131 | 148 | firstNodeIndex, |
132 | 149 | ruleChainId: { |
133 | - entityType: 'RULE_CHAIN', | |
150 | + entityType: RuleChainEntityType.RULE_CHAIN, | |
134 | 151 | id: unref(getRuleChainId), |
135 | 152 | }, |
136 | 153 | }); |
... | ... | @@ -188,10 +205,12 @@ export function useSaveAndRedo() { |
188 | 205 | loading, |
189 | 206 | debugMarker, |
190 | 207 | changeMarker, |
208 | + ruleChainDetail, | |
191 | 209 | triggerChange, |
192 | 210 | handleApplyChange, |
193 | 211 | handleRedoChange, |
194 | 212 | handleRemoveDebug, |
195 | 213 | getCurrentPageMetaData, |
214 | + getCurrentRuleChainDetail, | |
196 | 215 | }; |
197 | 216 | } | ... | ... |
... | ... | @@ -40,23 +40,28 @@ |
40 | 40 | |
41 | 41 | const elements = ref([]); |
42 | 42 | |
43 | + const useSaveAndRedoActionType = useSaveAndRedo(); | |
44 | + | |
43 | 45 | const { |
44 | 46 | loading, |
45 | 47 | changeMarker, |
48 | + ruleChainDetail, | |
46 | 49 | getCurrentPageMetaData, |
47 | 50 | triggerChange, |
48 | 51 | handleApplyChange, |
49 | 52 | handleRedoChange, |
50 | 53 | handleRemoveDebug, |
51 | - } = useSaveAndRedo(); | |
54 | + getCurrentRuleChainDetail, | |
55 | + } = useSaveAndRedoActionType; | |
52 | 56 | |
53 | 57 | const { flowActionType } = useRuleFlow({ |
54 | 58 | id: getId, |
59 | + ruleChainDetail, | |
55 | 60 | createNodeModalActionType, |
56 | 61 | createEdgeModalActionType, |
57 | 62 | updateEdgeDrawerActionType, |
58 | 63 | updateNodeDrawerActionType, |
59 | - triggerChange, | |
64 | + useSaveAndRedoActionType, | |
60 | 65 | }); |
61 | 66 | |
62 | 67 | const { handleOnDragOver, handleOnDrop } = useDragCreate({ |
... | ... | @@ -79,12 +84,12 @@ |
79 | 84 | ); |
80 | 85 | |
81 | 86 | const handleDeleteSelectionElements = () => { |
82 | - flowActionType.removeEdges(unref(flowActionType.getSelectedEdges)); | |
83 | 87 | flowActionType.removeNodes(unref(flowActionType.getSelectedNodes)); |
84 | 88 | }; |
85 | 89 | |
86 | 90 | onMounted(() => { |
87 | 91 | getCurrentPageMetaData(flowActionType); |
92 | + getCurrentRuleChainDetail(); | |
88 | 93 | }); |
89 | 94 | |
90 | 95 | createFlowContext({ | ... | ... |
... | ... | @@ -12,7 +12,6 @@ |
12 | 12 | const ROUTER = useRouter(); |
13 | 13 | |
14 | 14 | const handleClick = () => { |
15 | - console.log(props); | |
16 | 15 | const { data } = props.nodeProps?.data || ({} as NodeData); |
17 | 16 | const { configuration } = (data || {}) as { configuration: Record<'ruleChainId', string> }; |
18 | 17 | if (configuration.ruleChainId) { |
... | ... | @@ -22,12 +21,13 @@ |
22 | 21 | </script> |
23 | 22 | |
24 | 23 | <template> |
25 | - <div class="w-full h-6 flex justify-end" @click="handleClick"> | |
24 | + <div class="w-full h-6 flex justify-end"> | |
26 | 25 | <Tooltip color="#fff"> |
27 | 26 | <template #title> |
28 | 27 | <span class="text-slate-500 italic">打开规则链</span> |
29 | 28 | </template> |
30 | 29 | <Icon |
30 | + @click="handleClick" | |
31 | 31 | icon="material-symbols:login" |
32 | 32 | class="cursor-pointer svg:text-lg svg:text-light-50 border-1 border-light-50 bg-purple-400 hover:bg-purple-500 rounded" |
33 | 33 | /> | ... | ... |
... | ... | @@ -29,6 +29,11 @@ |
29 | 29 | return config?.name; |
30 | 30 | }); |
31 | 31 | |
32 | + const getIconUrl = computed(() => { | |
33 | + const { iconUrl } = unref(getNodeDefinition); | |
34 | + return iconUrl; | |
35 | + }); | |
36 | + | |
32 | 37 | const getIcon = computed(() => { |
33 | 38 | const { icon } = unref(getNodeDefinition); |
34 | 39 | const { category } = unref(getData); |
... | ... | @@ -64,10 +69,11 @@ |
64 | 69 | </template> |
65 | 70 | <main |
66 | 71 | class="basic-node-hover flex items-center w-44 h-12 rounded border px-4 py-2 border-gray-700 dark:text-light-50" |
67 | - :style="{ backgroundColor: getBackgroundColor }" | |
72 | + :style="{ backgroundColor: getBackgroundColor, outline: selected ? '3px solid red' : 'none' }" | |
68 | 73 | > |
69 | 74 | <div> |
70 | - <Icon class="text-2xl dark:text-light-50" :icon="getIcon" /> | |
75 | + <Icon v-if="!getIconUrl" class="text-2xl dark:text-light-50" :icon="getIcon" /> | |
76 | + <img v-if="getIconUrl" :src="getIconUrl" class="w-4 h-4" /> | |
71 | 77 | </div> |
72 | 78 | <BasicToolbar v-if="!getData.config?.disableAction" v-bind="$props" /> |
73 | 79 | <div class="flex text-xs flex-col ml-2 text-left truncate"> | ... | ... |
1 | +import { NodeMouseEvent } from '@vue-flow/core'; | |
2 | +import ContextMenuVue from './index.vue'; | |
3 | +import { isClient } from '/@/utils/is'; | |
4 | +import { createVNode, render, getCurrentInstance, onUnmounted, toRaw, unref } from 'vue'; | |
5 | + | |
6 | +const menuManager: { | |
7 | + domList: Element[]; | |
8 | + resolve: Fn; | |
9 | +} = { | |
10 | + domList: [], | |
11 | + resolve: () => {}, | |
12 | +}; | |
13 | + | |
14 | +const createContextMenu = function ( | |
15 | + options: NodeMouseEvent, | |
16 | + params?: InstanceType<typeof ContextMenuVue>['$props'] | |
17 | +) { | |
18 | + const { event } = options || {}; | |
19 | + | |
20 | + if (!(params?.items && params?.items.length)) return; | |
21 | + | |
22 | + event && event?.preventDefault(); | |
23 | + | |
24 | + if (!isClient) { | |
25 | + return; | |
26 | + } | |
27 | + | |
28 | + return new Promise((resolve) => { | |
29 | + const body = document.body; | |
30 | + | |
31 | + const container = document.createElement('div'); | |
32 | + const propsData: Partial<InstanceType<typeof ContextMenuVue>['$props']> = { | |
33 | + ...params, | |
34 | + }; | |
35 | + | |
36 | + if (options.event) { | |
37 | + const { clientX, clientY } = options.event as MouseEvent; | |
38 | + propsData.axis = { x: clientX, y: clientY }; | |
39 | + } | |
40 | + | |
41 | + if (options.node) { | |
42 | + propsData.nodeData = toRaw(unref(options.node.data)); | |
43 | + } | |
44 | + | |
45 | + const vm = createVNode(ContextMenuVue, propsData); | |
46 | + render(vm, container); | |
47 | + | |
48 | + const handleClick = function () { | |
49 | + menuManager.resolve(''); | |
50 | + }; | |
51 | + | |
52 | + menuManager.domList.push(container); | |
53 | + | |
54 | + const remove = function () { | |
55 | + menuManager.domList.forEach((dom: Element) => { | |
56 | + try { | |
57 | + dom && body.removeChild(dom); | |
58 | + } catch (error) {} | |
59 | + }); | |
60 | + body.removeEventListener('click', handleClick); | |
61 | + body.removeEventListener('scroll', handleClick); | |
62 | + }; | |
63 | + | |
64 | + menuManager.resolve = function (arg) { | |
65 | + remove(); | |
66 | + resolve(arg); | |
67 | + }; | |
68 | + remove(); | |
69 | + body.appendChild(container); | |
70 | + body.addEventListener('click', handleClick); | |
71 | + body.addEventListener('scroll', handleClick); | |
72 | + }); | |
73 | +}; | |
74 | + | |
75 | +const destroyContextMenu = function () { | |
76 | + if (menuManager) { | |
77 | + menuManager.resolve(''); | |
78 | + menuManager.domList = []; | |
79 | + } | |
80 | +}; | |
81 | + | |
82 | +export function useContextMenu(authRemove = true) { | |
83 | + if (getCurrentInstance() && authRemove) { | |
84 | + onUnmounted(() => { | |
85 | + destroyContextMenu(); | |
86 | + }); | |
87 | + } | |
88 | + return [createContextMenu, destroyContextMenu]; | |
89 | +} | ... | ... |
1 | +<script setup lang="ts"> | |
2 | + import { computed, CSSProperties, unref } from 'vue'; | |
3 | + import { NodeData } from '../../../types/node'; | |
4 | + import { RuleChainContextMenuItemType } from './index.type'; | |
5 | + import { Icon } from '/@/components/Icon'; | |
6 | + import { Divider } from 'ant-design-vue'; | |
7 | + | |
8 | + const props = withDefaults( | |
9 | + defineProps<{ | |
10 | + width?: number; | |
11 | + itemHeight?: number; | |
12 | + styles?: CSSProperties; | |
13 | + axis?: Record<'x' | 'y', number>; | |
14 | + items?: RuleChainContextMenuItemType[]; | |
15 | + nodeData?: NodeData; | |
16 | + }>(), | |
17 | + { | |
18 | + width: 320, | |
19 | + itemHeight: 48, | |
20 | + axis: () => ({ x: 0, y: 0 }), | |
21 | + items: () => [], | |
22 | + nodeData: () => ({}), | |
23 | + } | |
24 | + ); | |
25 | + | |
26 | + const getMenuHeight = computed(() => { | |
27 | + const { items, itemHeight } = props; | |
28 | + let dividerNumber = 0; | |
29 | + let menuNumber = 1; | |
30 | + for (const item of items) { | |
31 | + if (item.divider) dividerNumber++; | |
32 | + else menuNumber++; | |
33 | + } | |
34 | + | |
35 | + return itemHeight * menuNumber + dividerNumber + 8; | |
36 | + }); | |
37 | + | |
38 | + const getStyle = computed((): CSSProperties => { | |
39 | + const { axis, styles, width } = props; | |
40 | + const { x, y } = axis; | |
41 | + const menuHeight = unref(getMenuHeight); | |
42 | + const menuWidth = width; | |
43 | + const body = document.body; | |
44 | + | |
45 | + const left = body.clientWidth < x + menuWidth ? x - menuWidth : x; | |
46 | + const top = body.clientHeight < y + menuHeight ? y - menuHeight : y; | |
47 | + | |
48 | + return { | |
49 | + ...styles, | |
50 | + position: 'absolute', | |
51 | + width: `${width}px`, | |
52 | + left: `${left + 1}px`, | |
53 | + top: `${top + 1}px`, | |
54 | + }; | |
55 | + }); | |
56 | + | |
57 | + const getNodeIconUrl = computed(() => { | |
58 | + const { config } = props.nodeData; | |
59 | + const { configurationDescriptor } = config || {}; | |
60 | + const { nodeDefinition } = configurationDescriptor || {}; | |
61 | + const { iconUrl } = nodeDefinition || {}; | |
62 | + return iconUrl; | |
63 | + }); | |
64 | + | |
65 | + const getNodeIcon = computed(() => { | |
66 | + const { nodeData } = props; | |
67 | + const { category, config } = nodeData; | |
68 | + const { icon: categoryIcon } = category || {}; | |
69 | + const { configurationDescriptor } = config || {}; | |
70 | + const { nodeDefinition } = configurationDescriptor || {}; | |
71 | + const { icon } = nodeDefinition || {}; | |
72 | + | |
73 | + return categoryIcon || icon; | |
74 | + }); | |
75 | + | |
76 | + const getTitleBackgroundColor = computed(() => { | |
77 | + const { category, config } = props.nodeData; | |
78 | + const { backgroundColor: categoryBackgroundColor } = category || {}; | |
79 | + const { backgroundColor } = config || {}; | |
80 | + return categoryBackgroundColor || backgroundColor; | |
81 | + }); | |
82 | +</script> | |
83 | + | |
84 | +<template> | |
85 | + <div | |
86 | + :style="getStyle" | |
87 | + class="bg-light-50 shadow-lg shadow-dark-50 z-50 rounded-md overflow-hidden pb-2" | |
88 | + > | |
89 | + <div | |
90 | + v-if="nodeData" | |
91 | + :style="{ backgroundColor: getTitleBackgroundColor, height: `${itemHeight}px` }" | |
92 | + class="flex items-center p-2" | |
93 | + > | |
94 | + <Icon v-if="!getNodeIconUrl" :icon="getNodeIcon" class="svg:text-2xl" /> | |
95 | + <img v-if="getNodeIconUrl" :src="getNodeIconUrl" class="w-6 h-6" /> | |
96 | + <div class="ml-4"> | |
97 | + <div class="font-medium">{{ nodeData.config?.name }}</div> | |
98 | + <div class="text-xs">{{ nodeData.data?.name }}</div> | |
99 | + </div> | |
100 | + </div> | |
101 | + <div> | |
102 | + <template v-for="item in items" :key="item.key"> | |
103 | + <div | |
104 | + v-if="!item.divider" | |
105 | + :style="{ height: `${itemHeight}px` }" | |
106 | + class="px-4 flex items-center cursor-pointer hover:bg-neutral-100" | |
107 | + :class="item.disabled && 'disables'" | |
108 | + @click="(event) => !item.disabled && item?.handler?.(event)" | |
109 | + > | |
110 | + <Icon :icon="item.icon" class="svg:text-2xl" /> | |
111 | + <div class="flex-auto px-4">{{ item.label }}</div> | |
112 | + <div class="flex items-center"> {{ item.shortcutKey }} </div> | |
113 | + </div> | |
114 | + <Divider v-if="item.divider" class="!m-0" /> | |
115 | + </template> | |
116 | + </div> | |
117 | + </div> | |
118 | +</template> | |
119 | + | |
120 | +<style lang="less" scoped> | |
121 | + .disables { | |
122 | + @apply pointer-events-none text-gray-300; | |
123 | + } | |
124 | +</style> | ... | ... |
... | ... | @@ -113,7 +113,11 @@ |
113 | 113 | <Empty v-if="!shadowComponent" description="未找到链接组件" /> |
114 | 114 | </Spin> |
115 | 115 | </Tabs.TabPane> |
116 | - <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.EVENT]" :key="TabsPanelEnum.EVENT"> | |
116 | + <Tabs.TabPane | |
117 | + v-if="nodeData?.created" | |
118 | + :tab="TabsPanelNameEnum[TabsPanelEnum.EVENT]" | |
119 | + :key="TabsPanelEnum.EVENT" | |
120 | + > | |
117 | 121 | <BasicEvents :elementInfo="elementInfo" /> |
118 | 122 | </Tabs.TabPane> |
119 | 123 | <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.HELP]" :key="TabsPanelEnum.HELP"> | ... | ... |
... | ... | @@ -18,3 +18,20 @@ export interface ConnectionItemType { |
18 | 18 | toIndex: number; |
19 | 19 | type: string; |
20 | 20 | } |
21 | + | |
22 | +export interface RuleChainDetail { | |
23 | + id: Id; | |
24 | + createdTime: number; | |
25 | + additionalInfo: AdditionalInfo; | |
26 | + tenantId: Id; | |
27 | + name: Id; | |
28 | + type: string; | |
29 | + firstRuleNodeId: Id; | |
30 | + root: boolean; | |
31 | + debugMode: boolean; | |
32 | + configuration: any; | |
33 | +} | |
34 | + | |
35 | +export interface AdditionalInfo { | |
36 | + description: string; | |
37 | +} | ... | ... |