Showing
69 changed files
with
2878 additions
and
18 deletions
@@ -5,14 +5,14 @@ | @@ -5,14 +5,14 @@ | ||
5 | "public/resource/tinymce/langs" | 5 | "public/resource/tinymce/langs" |
6 | ], | 6 | ], |
7 | "cSpell.words": [ | 7 | "cSpell.words": [ |
8 | + "clazz", | ||
8 | "Cmds", | 9 | "Cmds", |
9 | "COAP", | 10 | "COAP", |
10 | "echarts", | 11 | "echarts", |
11 | "edrx", | 12 | "edrx", |
12 | - "EFENTO", | 13 | + "EFENTO", |
13 | "fingerprintjs", | 14 | "fingerprintjs", |
14 | "flvjs", | 15 | "flvjs", |
15 | - "flvjs", | ||
16 | "inited", | 16 | "inited", |
17 | "liveui", | 17 | "liveui", |
18 | "MQTT", | 18 | "MQTT", |
@@ -21,6 +21,7 @@ | @@ -21,6 +21,7 @@ | ||
21 | "rtsp", | 21 | "rtsp", |
22 | "SCADA", | 22 | "SCADA", |
23 | "SNMP", | 23 | "SNMP", |
24 | + "UNACK", | ||
24 | "unref", | 25 | "unref", |
25 | "vben", | 26 | "vben", |
26 | "videojs", | 27 | "videojs", |
@@ -39,6 +39,10 @@ | @@ -39,6 +39,10 @@ | ||
39 | "@iconify/iconify": "^2.0.3", | 39 | "@iconify/iconify": "^2.0.3", |
40 | "@logicflow/core": "^0.6.9", | 40 | "@logicflow/core": "^0.6.9", |
41 | "@logicflow/extension": "^0.6.9", | 41 | "@logicflow/extension": "^0.6.9", |
42 | + "@vue-flow/background": "^1.2.0", | ||
43 | + "@vue-flow/controls": "^1.1.0", | ||
44 | + "@vue-flow/core": "^1.22.1", | ||
45 | + "@vue-flow/node-toolbar": "^1.1.0", | ||
42 | "@vueuse/core": "^10.1.0", | 46 | "@vueuse/core": "^10.1.0", |
43 | "@zxcvbn-ts/core": "^1.0.0-beta.0", | 47 | "@zxcvbn-ts/core": "^1.0.0-beta.0", |
44 | "ace-builds": "^1.4.14", | 48 | "ace-builds": "^1.4.14", |
@@ -68,7 +72,7 @@ | @@ -68,7 +72,7 @@ | ||
68 | "vditor": "^3.8.6", | 72 | "vditor": "^3.8.6", |
69 | "video.js": "^7.20.3", | 73 | "video.js": "^7.20.3", |
70 | "videojs-flvjs-es6": "^1.0.1", | 74 | "videojs-flvjs-es6": "^1.0.1", |
71 | - "vue": "3.2.31", | 75 | + "vue": "3.3.4", |
72 | "vue-i18n": "9.1.7", | 76 | "vue-i18n": "9.1.7", |
73 | "vue-json-pretty": "^2.0.4", | 77 | "vue-json-pretty": "^2.0.4", |
74 | "vue-router": "^4.0.11", | 78 | "vue-router": "^4.0.11", |
src/api/ruleDesigner/index.ts
0 → 100644
1 | +import { defHttp } from '/@/utils/http/axios'; | ||
2 | +import { RuleChainType } from '/@/views/rule/designer/types/ruleNode'; | ||
3 | + | ||
4 | +enum Api { | ||
5 | + SAVE = '/ruleChain/metadata', | ||
6 | +} | ||
7 | + | ||
8 | +export const getRuleChainData = (id: string) => { | ||
9 | + return defHttp.get<RuleChainType>( | ||
10 | + { | ||
11 | + url: `/ruleChain/${id}/metadata`, | ||
12 | + }, | ||
13 | + { joinPrefix: false } | ||
14 | + ); | ||
15 | +}; | ||
16 | + | ||
17 | +export const saveRuleChainData = (data: RuleChainType) => { | ||
18 | + return defHttp.post( | ||
19 | + { | ||
20 | + url: Api.SAVE, | ||
21 | + data, | ||
22 | + }, | ||
23 | + { joinPrefix: false } | ||
24 | + ); | ||
25 | +}; |
src/api/ruleDesigner/model/type.ts
0 → 100644
src/enums/alarmEnum.ts
0 → 100644
1 | +export enum AlarmStatus { | ||
2 | + CLEARED_UN_ACK = 'CLEARED_UNACK', | ||
3 | + ACTIVE_UN_ACK = 'ACTIVE_UNACK', | ||
4 | + CLEARED_ACK = 'CLEARED_ACK', | ||
5 | + ACTIVE_ACK = 'ACTIVE_ACK', | ||
6 | +} | ||
7 | + | ||
8 | +export enum AlarmStatusMean { | ||
9 | + CLEARED_UNACK = '清除未确认', | ||
10 | + ACTIVE_UNACK = '激活未确认', | ||
11 | + CLEARED_ACK = '清除已确认', | ||
12 | + ACTIVE_ACK = '激活已确认', | ||
13 | +} |
@@ -3,20 +3,7 @@ import { FormSchema } from '/@/components/Form'; | @@ -3,20 +3,7 @@ import { FormSchema } from '/@/components/Form'; | ||
3 | import { BasicColumn } from '/@/components/Table'; | 3 | import { BasicColumn } from '/@/components/Table'; |
4 | import moment from 'moment'; | 4 | import moment from 'moment'; |
5 | import { findDictItemByCode } from '/@/api/system/dict'; | 5 | import { findDictItemByCode } from '/@/api/system/dict'; |
6 | - | ||
7 | -export enum AlarmStatus { | ||
8 | - CLEARED_UN_ACK = 'CLEARED_UNACK', | ||
9 | - ACTIVE_UN_ACK = 'ACTIVE_UNACK', | ||
10 | - CLEARED_ACK = 'CLEARED_ACK', | ||
11 | - ACTIVE_ACK = 'ACTIVE_ACK', | ||
12 | -} | ||
13 | - | ||
14 | -export enum AlarmStatusMean { | ||
15 | - CLEARED_UNACK = '清除未确认', | ||
16 | - ACTIVE_UNACK = '激活未确认', | ||
17 | - CLEARED_ACK = '清除已确认', | ||
18 | - ACTIVE_ACK = '激活已确认', | ||
19 | -} | 6 | +import { AlarmStatus, AlarmStatusMean } from '/@/enums/alarmEnum'; |
20 | 7 | ||
21 | export const alarmSearchSchemas: FormSchema[] = [ | 8 | export const alarmSearchSchemas: FormSchema[] = [ |
22 | { | 9 | { |
1 | -import { AlarmStatus, AlarmStatusMean } from '../config/detail.config'; | ||
2 | import { clearOrAckAlarm, getDeviceAlarm } from '/@/api/device/deviceManager'; | 1 | import { clearOrAckAlarm, getDeviceAlarm } from '/@/api/device/deviceManager'; |
3 | import { notification, Button, Tag } from 'ant-design-vue'; | 2 | import { notification, Button, Tag } from 'ant-design-vue'; |
4 | import { h, onMounted, onUnmounted } from 'vue'; | 3 | import { h, onMounted, onUnmounted } from 'vue'; |
@@ -8,6 +7,7 @@ import { RoleEnum } from '/@/enums/roleEnum'; | @@ -8,6 +7,7 @@ import { RoleEnum } from '/@/enums/roleEnum'; | ||
8 | import { usePermission } from '/@/hooks/web/usePermission'; | 7 | import { usePermission } from '/@/hooks/web/usePermission'; |
9 | import { useUserStore } from '/@/store/modules/user'; | 8 | import { useUserStore } from '/@/store/modules/user'; |
10 | import { useGlobSetting } from '/@/hooks/setting'; | 9 | import { useGlobSetting } from '/@/hooks/setting'; |
10 | +import { AlarmStatus, AlarmStatusMean } from '/@/enums/alarmEnum'; | ||
11 | 11 | ||
12 | interface UseAlarmNotifyParams { | 12 | interface UseAlarmNotifyParams { |
13 | alarmNotifyStatus?: AlarmStatus; | 13 | alarmNotifyStatus?: AlarmStatus; |
src/views/rule/designer/enum/category.ts
0 → 100644
src/views/rule/designer/enum/index.ts
0 → 100644
1 | +export enum HandleTypeEnum { | ||
2 | + SOURCE = 'source', | ||
3 | + TARGET = 'target', | ||
4 | +} | ||
5 | + | ||
6 | +export enum NodeTypeEnum { | ||
7 | + CUSTOM = 'custom', | ||
8 | +} | ||
9 | + | ||
10 | +export enum EdgeTypeEnum { | ||
11 | + CUSTOM = 'custom', | ||
12 | +} | ||
13 | + | ||
14 | +export enum BasicConnectionModalEnum { | ||
15 | + BASIC = 'BASIC', | ||
16 | + CUSTOM = 'CUSTOM', | ||
17 | +} | ||
18 | + | ||
19 | +export enum MarkerArrowEnum { | ||
20 | + BASIC_ARROW = 'basicArrow', | ||
21 | + BASIC_ARROW_SELECTED = 'basicArrowSelected', | ||
22 | +} | ||
23 | + | ||
24 | +export enum ElementsTypeEnum { | ||
25 | + NODE = 'NODE', | ||
26 | + EDGE = 'EDGE', | ||
27 | +} |
src/views/rule/designer/enum/node.ts
0 → 100644
1 | +export enum FetchNodeComFlagTypeENum { | ||
2 | + CONNECTION_MODAL = 'CONNECTION_MODAL', | ||
3 | + CREATE_MODAL = 'CREATE_MODAL', | ||
4 | +} | ||
5 | + | ||
6 | +export enum NodeBindDataFieldEnum { | ||
7 | + NAME = 'name', | ||
8 | + ALARM_STATUS_LIST = 'alarmStatusList', | ||
9 | + DESCRIPTION = 'description', | ||
10 | + DEBUG_MODE = 'debugMode', | ||
11 | +} | ||
12 | + | ||
13 | +export enum NodeBindDataFieldNameEnum { | ||
14 | + NAME = '名称', | ||
15 | + ALARM_STATUS_LIST = 'Alarm status filter', | ||
16 | + DESCRIPTION = '说明', | ||
17 | + DEBUG_MODE = '调试模式', | ||
18 | +} | ||
19 | + | ||
20 | +export enum EdgeBindDataFieldEnum { | ||
21 | + TYPE = 'type', | ||
22 | +} | ||
23 | + | ||
24 | +export enum EdgeBindDataFieldNameEnum { | ||
25 | + TYPE = '链接标签', | ||
26 | +} |
src/views/rule/designer/hook/useAddEdges.ts
0 → 100644
1 | +import type { Connection } from '@vue-flow/core'; | ||
2 | +import { EdgeTypeEnum } from '../enum'; | ||
3 | +import type { EdgeData } from '../types/node'; | ||
4 | +import { buildUUID } from '/@/utils/uuid'; | ||
5 | + | ||
6 | +export function useAddEdges() { | ||
7 | + const getAddedgesParams = (params: Connection, data: string | string[] | any) => { | ||
8 | + return { type: EdgeTypeEnum.CUSTOM, data: { data } as EdgeData, id: buildUUID(), ...params }; | ||
9 | + }; | ||
10 | + | ||
11 | + return { getAddedgesParams }; | ||
12 | +} |
src/views/rule/designer/hook/useAddNodes.ts
0 → 100644
1 | +import { Node } from '@vue-flow/core'; | ||
2 | +import { NodeTypeEnum } from '../enum'; | ||
3 | +import { buildUUID } from '/@/utils/uuid'; | ||
4 | + | ||
5 | +export const useAddNodes = () => { | ||
6 | + const getAddNodesParams = ( | ||
7 | + position: Node['position'], | ||
8 | + data: object, | ||
9 | + options?: Partial<Node> | ||
10 | + ): Node => { | ||
11 | + return { | ||
12 | + id: buildUUID(), | ||
13 | + type: NodeTypeEnum.CUSTOM, | ||
14 | + position, | ||
15 | + data, | ||
16 | + ...options, | ||
17 | + }; | ||
18 | + }; | ||
19 | + | ||
20 | + return { getAddNodesParams }; | ||
21 | +}; |
1 | +import { Component, computed, ref, shallowRef, toRaw, unref } from 'vue'; | ||
2 | +import { fetchConnectionComponent, fetchCreateComponent } from '../packages'; | ||
3 | +import { CreateModalDefineExposeType, AwaitPopupWindowReturnDataType } from '../types'; | ||
4 | +import { EdgeData, NodeData } from '../types/node'; | ||
5 | +import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
6 | +import { ElementsTypeEnum } from '../enum'; | ||
7 | + | ||
8 | +interface OptionsType { | ||
9 | + mode: DataActionModeEnum; | ||
10 | + type: ElementsTypeEnum; | ||
11 | +} | ||
12 | + | ||
13 | +export function useAwaitPopupWindowBindData( | ||
14 | + options: OptionsType = { mode: DataActionModeEnum.CREATE, type: ElementsTypeEnum.NODE } | ||
15 | +) { | ||
16 | + const { type, mode } = options; | ||
17 | + | ||
18 | + const visible = ref(false); | ||
19 | + | ||
20 | + const nodeData = ref<NodeData>(); | ||
21 | + | ||
22 | + const edgeData = ref<EdgeData>(); | ||
23 | + | ||
24 | + const spinning = ref(false); | ||
25 | + | ||
26 | + const resolveFn = ref<(options: AwaitPopupWindowReturnDataType) => void>(); | ||
27 | + | ||
28 | + const createComponentEl = ref<Nullable<CreateModalDefineExposeType>>(); | ||
29 | + | ||
30 | + const shadowComponent = shallowRef<Nullable<Component>>(); | ||
31 | + | ||
32 | + const getNodeSetValue = computed(() => { | ||
33 | + return unref(mode) === DataActionModeEnum.CREATE | ||
34 | + ? unref(nodeData)?.config?.configurationDescriptor.nodeDefinition.defaultConfiguration | ||
35 | + : unref(nodeData)?.data?.configuration; | ||
36 | + }); | ||
37 | + | ||
38 | + const getEdgeSetValue = computed(() => { | ||
39 | + return { | ||
40 | + type: | ||
41 | + unref(mode) === DataActionModeEnum.CREATE | ||
42 | + ? unref(nodeData)?.config?.configurationDescriptor?.nodeDefinition.relationTypes | ||
43 | + : unref(edgeData)?.data, | ||
44 | + }; | ||
45 | + }); | ||
46 | + | ||
47 | + const getSetValue = computed(() => | ||
48 | + unref(type) === ElementsTypeEnum.EDGE ? unref(getEdgeSetValue) : unref(getNodeSetValue) | ||
49 | + ); | ||
50 | + | ||
51 | + const getComponentKey = computed(() => unref(nodeData)?.config?.key); | ||
52 | + | ||
53 | + const handleFetchComponent = async (nodeOptions: NodeData, edgeOptions?: EdgeData) => { | ||
54 | + nodeData.value = nodeOptions; | ||
55 | + edgeData.value = edgeOptions; | ||
56 | + | ||
57 | + spinning.value = true; | ||
58 | + shadowComponent.value = null; | ||
59 | + const modules = | ||
60 | + ElementsTypeEnum.NODE === type | ||
61 | + ? await fetchCreateComponent(nodeOptions.config!) | ||
62 | + : await fetchConnectionComponent(nodeOptions.config!); | ||
63 | + | ||
64 | + const component = (await modules?.()) as Record<'default', Component>; | ||
65 | + shadowComponent.value = component?.default; | ||
66 | + spinning.value = false; | ||
67 | + }; | ||
68 | + | ||
69 | + const open = async ( | ||
70 | + nodeData: NodeData, | ||
71 | + edgeData?: EdgeData | ||
72 | + ): Promise<AwaitPopupWindowReturnDataType> => { | ||
73 | + await handleFetchComponent(nodeData, edgeData); | ||
74 | + return new Promise((_resolve) => { | ||
75 | + visible.value = true; | ||
76 | + resolveFn.value = _resolve; | ||
77 | + }); | ||
78 | + }; | ||
79 | + | ||
80 | + const handleOnMounted = () => { | ||
81 | + unref(createComponentEl)?.setFieldsValue?.(toRaw(unref(getSetValue)), toRaw(unref(nodeData))); | ||
82 | + }; | ||
83 | + | ||
84 | + const validate = async () => { | ||
85 | + return (await unref(createComponentEl)?.validate?.()) || { flag: true }; | ||
86 | + }; | ||
87 | + | ||
88 | + const handleSubmitData = (extraData: Recordable = {}, value: Recordable = {}) => { | ||
89 | + return Object.assign( | ||
90 | + {}, | ||
91 | + extraData, | ||
92 | + unref(type) === ElementsTypeEnum.NODE ? { configuration: toRaw(unref(value)) } : value | ||
93 | + ); | ||
94 | + }; | ||
95 | + | ||
96 | + const handleSubmit = async (extraData: Recordable = {}) => { | ||
97 | + const { flag } = await validate(); | ||
98 | + if (!flag) return; | ||
99 | + const value = await unref(createComponentEl)?.getFieldsValue?.(); | ||
100 | + unref(resolveFn)?.({ | ||
101 | + flag: true, | ||
102 | + data: handleSubmitData(extraData, value), | ||
103 | + } as AwaitPopupWindowReturnDataType); | ||
104 | + visible.value = false; | ||
105 | + }; | ||
106 | + | ||
107 | + const handleCancel = () => { | ||
108 | + unref(resolveFn)?.({ flag: false, data: null } as AwaitPopupWindowReturnDataType); | ||
109 | + visible.value = false; | ||
110 | + }; | ||
111 | + | ||
112 | + return { | ||
113 | + visible, | ||
114 | + nodeData, | ||
115 | + spinning, | ||
116 | + shadowComponent, | ||
117 | + createComponentEl, | ||
118 | + getComponentKey, | ||
119 | + open, | ||
120 | + handleOnMounted, | ||
121 | + handleSubmit, | ||
122 | + handleCancel, | ||
123 | + }; | ||
124 | +} |
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'; | ||
5 | +import { allComponents } from '../packages'; | ||
6 | +import { RuleNodeTypeEnum } from '../packages/index.type'; | ||
7 | +import { buildUUID } from '/@/utils/uuid'; | ||
8 | +import { isNullOrUnDef } from '/@/utils/is'; | ||
9 | +import { useAddNodes } from './useAddNodes'; | ||
10 | +import { useAddEdges } from './useAddEdges'; | ||
11 | + | ||
12 | +export function useBasicDataTransform() { | ||
13 | + const nodeConfigMap = new Map<string, NodeData>(); | ||
14 | + | ||
15 | + function initNodeConfigMap() { | ||
16 | + for (const key of Object.keys(allComponents)) { | ||
17 | + const category = allComponents[key as RuleNodeTypeEnum]; | ||
18 | + for (const nodeConfig of category.components) { | ||
19 | + const { clazz } = nodeConfig; | ||
20 | + nodeConfigMap.set(clazz, { config: nodeConfig, category: category.category }); | ||
21 | + } | ||
22 | + } | ||
23 | + } | ||
24 | + | ||
25 | + function mergeData(data: NodeData['data'], nodeData: NodeData, node: GraphNode) { | ||
26 | + const { x: layoutX, y: layoutY } = node.computedPosition; | ||
27 | + | ||
28 | + return { | ||
29 | + debugMode: !!data?.debugMode, | ||
30 | + name: data?.name, | ||
31 | + type: nodeData.config?.clazz, | ||
32 | + configuration: toRaw(unref(data?.configuration)), | ||
33 | + additionalInfo: { | ||
34 | + description: data?.description, | ||
35 | + layoutX, | ||
36 | + layoutY, | ||
37 | + }, | ||
38 | + }; | ||
39 | + } | ||
40 | + | ||
41 | + function deconstructionConnection( | ||
42 | + ruleChain: Ref<RuleChainType> | RuleChainType, | ||
43 | + inputNodeId: string | ||
44 | + ) { | ||
45 | + const { connections, nodes, firstNodeIndex } = unref(ruleChain); | ||
46 | + const indexMap = new Map<number, BasicNodeBindData>(); | ||
47 | + const groupByConnections = new Map<string, string[]>(); | ||
48 | + const SOURCE_HANDLE = '__handle-right'; | ||
49 | + const TARGET_HANDLE = '__handle-left'; | ||
50 | + const SEPARATOR = ','; | ||
51 | + const edges: Elements = []; | ||
52 | + const { getAddedgesParams } = useAddEdges(); | ||
53 | + | ||
54 | + nodes.forEach((item, index) => indexMap.set(index, item)); | ||
55 | + | ||
56 | + connections.forEach((item) => { | ||
57 | + const { fromIndex, toIndex, type } = item; | ||
58 | + const key = `${fromIndex}${SEPARATOR}${toIndex}`; | ||
59 | + if (!groupByConnections.has(key)) groupByConnections.set(key, []); | ||
60 | + | ||
61 | + const types = groupByConnections.get(key)!; | ||
62 | + types.push(type); | ||
63 | + }); | ||
64 | + | ||
65 | + for (const [key, types] of Array.from(groupByConnections.entries())) { | ||
66 | + const [fromIndex, toIndex] = key.split(SEPARATOR); | ||
67 | + | ||
68 | + const sourceNode = indexMap.get(Number(fromIndex)); | ||
69 | + const targetNode = indexMap.get(Number(toIndex)); | ||
70 | + const source = sourceNode!.id!.id; | ||
71 | + const target = targetNode!.id!.id; | ||
72 | + const sourceHandle = `${source}${SOURCE_HANDLE}`; | ||
73 | + const targetHandle = `${target}${TARGET_HANDLE}`; | ||
74 | + | ||
75 | + edges.push( | ||
76 | + getAddedgesParams( | ||
77 | + { | ||
78 | + source, | ||
79 | + target, | ||
80 | + sourceHandle, | ||
81 | + targetHandle, | ||
82 | + }, | ||
83 | + { type: types } | ||
84 | + ) | ||
85 | + ); | ||
86 | + } | ||
87 | + | ||
88 | + if (!isNullOrUnDef(firstNodeIndex)) { | ||
89 | + const targetNode = indexMap.get(firstNodeIndex); | ||
90 | + const source = inputNodeId; | ||
91 | + const target = targetNode!.id!.id; | ||
92 | + const sourceHandle = `${source}$${SOURCE_HANDLE}`; | ||
93 | + const targetHandle = `${target}${TARGET_HANDLE}`; | ||
94 | + edges.push( | ||
95 | + getAddedgesParams( | ||
96 | + { | ||
97 | + source, | ||
98 | + target, | ||
99 | + sourceHandle, | ||
100 | + targetHandle, | ||
101 | + }, | ||
102 | + {} | ||
103 | + ) | ||
104 | + ); | ||
105 | + } | ||
106 | + | ||
107 | + return edges; | ||
108 | + } | ||
109 | + | ||
110 | + function genNewNodeByData(node: BasicNodeBindData, config: NodeData) { | ||
111 | + const { additionalInfo, configuration, debugMode, name, id } = node; | ||
112 | + const { layoutX, layoutY, description } = additionalInfo || {}; | ||
113 | + const { getAddNodesParams } = useAddNodes(); | ||
114 | + | ||
115 | + return getAddNodesParams( | ||
116 | + { x: layoutX!, y: layoutY! }, | ||
117 | + { | ||
118 | + ...config, | ||
119 | + data: { | ||
120 | + configuration, | ||
121 | + debugMode, | ||
122 | + description, | ||
123 | + name, | ||
124 | + }, | ||
125 | + }, | ||
126 | + { | ||
127 | + id: id?.id || buildUUID(), | ||
128 | + } | ||
129 | + ); | ||
130 | + } | ||
131 | + | ||
132 | + function deconstructionNode(nodes: RuleChainType['nodes']) { | ||
133 | + const addNodes: Elements = []; | ||
134 | + for (const node of unref(nodes)) { | ||
135 | + const { type } = node; | ||
136 | + if (!type) continue; | ||
137 | + const nodeConfig = nodeConfigMap.get(type); | ||
138 | + | ||
139 | + if (!nodeConfig) { | ||
140 | + throw `No component configuration of type '${type}' was found`; | ||
141 | + } | ||
142 | + | ||
143 | + const newNode = genNewNodeByData(node, nodeConfig); | ||
144 | + | ||
145 | + addNodes.push(newNode); | ||
146 | + } | ||
147 | + return addNodes; | ||
148 | + } | ||
149 | + | ||
150 | + function deconstructionData( | ||
151 | + ruleChain: RuleChainType | Ref<RuleChainType | undefined>, | ||
152 | + inputNodeId: string | ||
153 | + ) { | ||
154 | + if (!ruleChain || !unref(ruleChain)) return; | ||
155 | + ruleChain = toRaw(unref(ruleChain))!; | ||
156 | + | ||
157 | + const nodes = deconstructionNode(ruleChain?.nodes || []); | ||
158 | + | ||
159 | + const edges = deconstructionConnection(ruleChain!, inputNodeId); | ||
160 | + | ||
161 | + return { | ||
162 | + nodes, | ||
163 | + edges, | ||
164 | + }; | ||
165 | + } | ||
166 | + | ||
167 | + initNodeConfigMap(); | ||
168 | + | ||
169 | + return { | ||
170 | + mergeData, | ||
171 | + deconstructionData, | ||
172 | + }; | ||
173 | +} |
1 | +import { EdgeProps, useEdge } from '@vue-flow/core'; | ||
2 | +import { computed, unref } from 'vue'; | ||
3 | +import { MarkerArrowEnum } from '../enum'; | ||
4 | + | ||
5 | +export const useConnectionFocus = (props: EdgeProps) => { | ||
6 | + const { edge } = useEdge(props.id); | ||
7 | + | ||
8 | + const getSelected = computed(() => unref(edge)?.selected); | ||
9 | + | ||
10 | + const getMarkerEnd = computed( | ||
11 | + () => | ||
12 | + `url(#${ | ||
13 | + unref(getSelected) ? MarkerArrowEnum.BASIC_ARROW_SELECTED : MarkerArrowEnum.BASIC_ARROW | ||
14 | + })` | ||
15 | + ); | ||
16 | + | ||
17 | + return { | ||
18 | + getMarkerEnd, | ||
19 | + getSelected, | ||
20 | + }; | ||
21 | +}; |
src/views/rule/designer/hook/useDataTool.ts
0 → 100644
1 | +import { Ref, toRaw, unref } from 'vue'; | ||
2 | +import { NodeData } from '../types/node'; | ||
3 | + | ||
4 | +export function useDataTool() { | ||
5 | + /** | ||
6 | + * @description 通过NodeData获取默认配置信息 | ||
7 | + * @param nodeData | ||
8 | + * @returns | ||
9 | + */ | ||
10 | + function getDefaultConfigurationByNodeData<T = any>(nodeData: Ref<NodeData> | NodeData) { | ||
11 | + nodeData = toRaw(unref(nodeData)); | ||
12 | + | ||
13 | + const { nodeDefinition } = nodeData.config?.configurationDescriptor || {}; | ||
14 | + const { defaultConfiguration } = nodeDefinition || {}; | ||
15 | + | ||
16 | + return { defaultConfiguration: defaultConfiguration as T }; | ||
17 | + } | ||
18 | + | ||
19 | + /** | ||
20 | + * @description 通过NodeData获取节点绑定信息 | ||
21 | + * @param nodeData | ||
22 | + * @returns | ||
23 | + */ | ||
24 | + function getBindDataByNodeData<T = any>(nodeData: Ref<NodeData> | NodeData) { | ||
25 | + nodeData = toRaw(unref(nodeData)); | ||
26 | + | ||
27 | + const data = nodeData.data as T; | ||
28 | + | ||
29 | + return { data }; | ||
30 | + } | ||
31 | + | ||
32 | + return { getDefaultConfigurationByNodeData, getBindDataByNodeData }; | ||
33 | +} |
1 | +import { Ref, toRaw, unref } from 'vue'; | ||
2 | +import type { VueFlowStore } from '@vue-flow/core'; | ||
3 | +import type { FlowElRef } from '../types/flow'; | ||
4 | +import type { CreateNodeModal } from '../src/components/CreateNodeModal'; | ||
5 | +import type { DragTransferData } from '../types/node'; | ||
6 | +import { useAddNodes } from './useAddNodes'; | ||
7 | + | ||
8 | +type EffectSymbol = DataTransfer['dropEffect']; | ||
9 | + | ||
10 | +export const TRANSFER_DATA_KEY = 'NODE_INFO'; | ||
11 | +export const EFFECT_SYMBOL: EffectSymbol = 'move'; | ||
12 | + | ||
13 | +interface UseDragCreateOptionsType { | ||
14 | + el: Ref<Nullable<FlowElRef>>; | ||
15 | + createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; | ||
16 | + flowActionType: VueFlowStore; | ||
17 | + triggerChange: () => void; | ||
18 | +} | ||
19 | + | ||
20 | +export function useDragCreate(options: UseDragCreateOptionsType) { | ||
21 | + const { el, createNodeModalActionType, flowActionType, triggerChange } = options; | ||
22 | + const { project, addNodes } = flowActionType; | ||
23 | + const { getAddNodesParams } = useAddNodes(); | ||
24 | + | ||
25 | + const handleOnDrop = async (event: DragEvent) => { | ||
26 | + const value = event.dataTransfer?.getData(TRANSFER_DATA_KEY); | ||
27 | + const transferData: DragTransferData = JSON.parse(value || ''); | ||
28 | + const { options: nodeData, offsetX, offsetY } = transferData; | ||
29 | + const { flag, data } = (await unref(createNodeModalActionType)?.open(nodeData)) || {}; | ||
30 | + if (!flag) return; | ||
31 | + | ||
32 | + const flowBounds = unref(el)?.$el.getBoundingClientRect() as DOMRect; | ||
33 | + | ||
34 | + const position = project({ | ||
35 | + x: event.clientX - flowBounds.left - offsetX, | ||
36 | + y: event.clientY - flowBounds.top - offsetY, | ||
37 | + }); | ||
38 | + | ||
39 | + const newNode = getAddNodesParams(position, { ...toRaw(unref(nodeData)), data }); | ||
40 | + addNodes(newNode); | ||
41 | + | ||
42 | + triggerChange(); | ||
43 | + }; | ||
44 | + | ||
45 | + const handleOnDragOver = (event: DragEvent) => { | ||
46 | + event.preventDefault(); | ||
47 | + | ||
48 | + if (event.dataTransfer) event.dataTransfer.dropEffect = EFFECT_SYMBOL; | ||
49 | + }; | ||
50 | + | ||
51 | + const handleOnDragStart = (event: DragEvent, options: object) => { | ||
52 | + if (event.dataTransfer) { | ||
53 | + event.dataTransfer.setData(TRANSFER_DATA_KEY, JSON.stringify(options)); | ||
54 | + event.dataTransfer.effectAllowed = EFFECT_SYMBOL; | ||
55 | + } | ||
56 | + }; | ||
57 | + | ||
58 | + return { | ||
59 | + handleOnDrop, | ||
60 | + handleOnDragOver, | ||
61 | + handleOnDragStart, | ||
62 | + }; | ||
63 | +} |
1 | +import type { Ref } from 'vue'; | ||
2 | +import { inject, provide } from 'vue'; | ||
3 | +import type { VueFlowStore } from '@vue-flow/core'; | ||
4 | +import type { CreateNodeModal } from '../src/components/CreateNodeModal'; | ||
5 | +import type { CreateEdgeModal } from '../src/components/CreateEdgeModal'; | ||
6 | +import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer'; | ||
7 | +import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer'; | ||
8 | + | ||
9 | +const SYMBOL = Symbol('flow-context'); | ||
10 | + | ||
11 | +interface FlowContextOptionsType { | ||
12 | + /** | ||
13 | + * @description 节点 actions | ||
14 | + */ | ||
15 | + createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; | ||
16 | + | ||
17 | + /** | ||
18 | + * @description 连接线 actions | ||
19 | + */ | ||
20 | + createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; | ||
21 | + | ||
22 | + /** | ||
23 | + * @description 节点更新 actions | ||
24 | + */ | ||
25 | + updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; | ||
26 | + | ||
27 | + /** | ||
28 | + * @description 连接线更新 actions | ||
29 | + */ | ||
30 | + updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; | ||
31 | + | ||
32 | + /** | ||
33 | + * @description vue flow store | ||
34 | + */ | ||
35 | + flowActionType: VueFlowStore; | ||
36 | + | ||
37 | + /** | ||
38 | + * @description 更新变化 | ||
39 | + */ | ||
40 | + triggerChange: () => void; | ||
41 | +} | ||
42 | + | ||
43 | +export const createFlowContext = (options: FlowContextOptionsType) => { | ||
44 | + provide(SYMBOL, options); | ||
45 | +}; | ||
46 | + | ||
47 | +export const useFlowContext = () => { | ||
48 | + return inject(SYMBOL) as FlowContextOptionsType; | ||
49 | +}; |
1 | +import { useFullscreen } from '@vueuse/core'; | ||
2 | +import type { Ref } from 'vue'; | ||
3 | +import { computed, unref } from 'vue'; | ||
4 | + | ||
5 | +export function useFullScreen(el: Ref<Nullable<HTMLDivElement>>) { | ||
6 | + const { toggle, isFullscreen } = useFullscreen(el); | ||
7 | + | ||
8 | + const getFullScreenIcon = computed(() => | ||
9 | + unref(isFullscreen) ? 'bx:exit-fullscreen' : 'mdi:fullscreen' | ||
10 | + ); | ||
11 | + | ||
12 | + return { | ||
13 | + getFullScreenIcon, | ||
14 | + handleFullScreen: toggle, | ||
15 | + }; | ||
16 | +} |
src/views/rule/designer/hook/useInputNode.ts
0 → 100644
1 | +import { Config as InputConfig } from '../packages/Entry/Input/config'; | ||
2 | +import { useAddNodes } from './useAddNodes'; | ||
3 | + | ||
4 | +export function useInputNode() { | ||
5 | + const getInputNodeConfig = (id?: string) => { | ||
6 | + const { getAddNodesParams } = useAddNodes(); | ||
7 | + | ||
8 | + const newNode = getAddNodesParams( | ||
9 | + { x: 80, y: 50 }, | ||
10 | + { | ||
11 | + ...new InputConfig(), | ||
12 | + data: { | ||
13 | + name: '输入', | ||
14 | + description: '规则链的逻辑输入,将传入消息转发到下一个相关规则节点。', | ||
15 | + }, | ||
16 | + }, | ||
17 | + { id, draggable: false } | ||
18 | + ); | ||
19 | + | ||
20 | + return newNode; | ||
21 | + }; | ||
22 | + | ||
23 | + return { getInputNodeConfig }; | ||
24 | +} |
src/views/rule/designer/hook/useRuleFlow.ts
0 → 100644
1 | +import type { | ||
2 | + Connection, | ||
3 | + EdgeComponent, | ||
4 | + NodeComponent, | ||
5 | + ValidConnectionFunc, | ||
6 | + GraphNode, | ||
7 | +} from '@vue-flow/core'; | ||
8 | +import { ConnectionLineType, SelectionMode, useVueFlow } from '@vue-flow/core'; | ||
9 | +import type { Ref } from 'vue'; | ||
10 | +import { markRaw, toRaw, unref } from 'vue'; | ||
11 | +import { isFunction } from 'lodash-es'; | ||
12 | +import type { CreateNodeModal } from '../src/components/CreateNodeModal'; | ||
13 | +import { EdgeTypeEnum, NodeTypeEnum } from '../enum'; | ||
14 | +import { BasicEdge, BasicNode } from '../src/components'; | ||
15 | +import type { EdgeData, NodeData } from '../types/node'; | ||
16 | +import type { CreateEdgeModal } from '../src/components/CreateEdgeModal'; | ||
17 | +import { isInputHandle, isOutputHandle } from '../utils'; | ||
18 | +import { useAddEdges } from './useAddEdges'; | ||
19 | +import { UpdateNodeDrawer } from '../src/components/UpdateNodeDrawer'; | ||
20 | +import { UpdateEdgeDrawer } from '../src/components/UpdateEdgeDrawer'; | ||
21 | +import { isNumber } from '/@/utils/is'; | ||
22 | + | ||
23 | +interface UseRuleFlowOptionsType { | ||
24 | + id: string; | ||
25 | + createNodeModalActionType: Ref<Nullable<InstanceType<typeof CreateNodeModal>>>; | ||
26 | + createEdgeModalActionType: Ref<Nullable<InstanceType<typeof CreateEdgeModal>>>; | ||
27 | + updateNodeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>; | ||
28 | + updateEdgeDrawerActionType: Ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>; | ||
29 | + triggerChange: () => void; | ||
30 | +} | ||
31 | + | ||
32 | +const validateInputAndOutput: ValidConnectionFunc = (connection: Connection) => { | ||
33 | + const { sourceHandle, targetHandle, source, target } = connection; | ||
34 | + | ||
35 | + return ( | ||
36 | + isOutputHandle(sourceHandle || '') && isInputHandle(targetHandle || '') && source !== target | ||
37 | + ); | ||
38 | +}; | ||
39 | + | ||
40 | +export function useRuleFlow(options: UseRuleFlowOptionsType) { | ||
41 | + const { | ||
42 | + id, | ||
43 | + createEdgeModalActionType, | ||
44 | + updateEdgeDrawerActionType, | ||
45 | + updateNodeDrawerActionType, | ||
46 | + triggerChange, | ||
47 | + } = options; | ||
48 | + | ||
49 | + const flowActionType = useVueFlow({ | ||
50 | + id, | ||
51 | + maxZoom: 1, | ||
52 | + minZoom: 1, | ||
53 | + panOnScroll: true, | ||
54 | + selectionMode: SelectionMode.Partial, | ||
55 | + nodeTypes: { | ||
56 | + [NodeTypeEnum.CUSTOM]: markRaw(BasicNode) as NodeComponent, | ||
57 | + }, | ||
58 | + edgeTypes: { | ||
59 | + [EdgeTypeEnum.CUSTOM]: markRaw(BasicEdge) as EdgeComponent, | ||
60 | + }, | ||
61 | + connectionLineOptions: { | ||
62 | + type: ConnectionLineType.Bezier, | ||
63 | + }, | ||
64 | + defaultViewport: { | ||
65 | + x: 0, | ||
66 | + y: 0, | ||
67 | + }, | ||
68 | + isValidConnection(connection, elements) { | ||
69 | + const validateList = [validateInputAndOutput]; | ||
70 | + const targetData = elements.targetNode.data as NodeData; | ||
71 | + | ||
72 | + if ( | ||
73 | + targetData.category?.validateConnection && | ||
74 | + isFunction(targetData.category.validateConnection) | ||
75 | + ) | ||
76 | + validateList.push(targetData.category?.validateConnection); | ||
77 | + | ||
78 | + if (targetData.config?.validateConnection && isFunction(targetData.config.validateConnection)) | ||
79 | + validateList.push(targetData.config.validateConnection); | ||
80 | + | ||
81 | + if (!validateList.every((item) => item(connection, elements))) return false; | ||
82 | + | ||
83 | + return true; | ||
84 | + }, | ||
85 | + }); | ||
86 | + | ||
87 | + const { | ||
88 | + getEdges, | ||
89 | + addEdges, | ||
90 | + findEdge, | ||
91 | + findNode, | ||
92 | + setViewport, | ||
93 | + removeEdges, | ||
94 | + onConnect, | ||
95 | + onPaneReady, | ||
96 | + onNodeDoubleClick, | ||
97 | + onEdgeDoubleClick, | ||
98 | + onNodeDragStop, | ||
99 | + } = flowActionType; | ||
100 | + | ||
101 | + const { getAddedgesParams } = useAddEdges(); | ||
102 | + | ||
103 | + onConnect(async (params) => { | ||
104 | + const { source } = params; | ||
105 | + const sourceNode = findNode(source); | ||
106 | + const sourceData = sourceNode?.data as NodeData; | ||
107 | + | ||
108 | + let types: string[] = []; | ||
109 | + | ||
110 | + if (sourceData && validateHasLabelConnection(sourceData)) { | ||
111 | + const { flag, data } = (await unref(createEdgeModalActionType)?.open(sourceData)) || {}; | ||
112 | + if (!flag) return; | ||
113 | + types = toRaw(unref(data)); | ||
114 | + } | ||
115 | + | ||
116 | + handleMaxConnectionPoint(sourceNode); | ||
117 | + | ||
118 | + addEdges(getAddedgesParams(params, types)); | ||
119 | + | ||
120 | + triggerChange(); | ||
121 | + }); | ||
122 | + | ||
123 | + onPaneReady(async () => { | ||
124 | + setViewport({ x: 0, y: 0, zoom: 1 }); | ||
125 | + }); | ||
126 | + | ||
127 | + onNodeDoubleClick(async ({ node }) => { | ||
128 | + const { flag, data } = | ||
129 | + (await unref(updateNodeDrawerActionType)?.open( | ||
130 | + toRaw((node as NodeData)?.data as unknown as NodeData) | ||
131 | + )) || {}; | ||
132 | + | ||
133 | + if (!flag) return; | ||
134 | + | ||
135 | + const currentNode = findNode(node.id); | ||
136 | + | ||
137 | + (currentNode!.data as NodeData).data = data; | ||
138 | + }); | ||
139 | + | ||
140 | + onEdgeDoubleClick(async ({ edge }) => { | ||
141 | + if (!validateHasLabelConnection(edge.sourceNode.data)) return; | ||
142 | + | ||
143 | + const { flag, data } = | ||
144 | + (await unref(updateEdgeDrawerActionType)?.open( | ||
145 | + toRaw(unref(edge.sourceNode?.data as unknown as NodeData)), | ||
146 | + toRaw(unref(edge.data as EdgeData)) | ||
147 | + )) || {}; | ||
148 | + | ||
149 | + if (!flag) return; | ||
150 | + | ||
151 | + const currentEdge = findEdge(edge.id); | ||
152 | + | ||
153 | + (currentEdge!.data as EdgeData).data = toRaw(unref(data)); | ||
154 | + }); | ||
155 | + | ||
156 | + onNodeDragStop(() => { | ||
157 | + triggerChange(); | ||
158 | + }); | ||
159 | + | ||
160 | + /** | ||
161 | + * @description 验证是否有连接label | ||
162 | + * @param sourceData | ||
163 | + * @returns | ||
164 | + */ | ||
165 | + function validateHasLabelConnection(sourceData: NodeData) { | ||
166 | + return !!sourceData?.config?.configurationDescriptor.nodeDefinition?.relationTypes?.length; | ||
167 | + } | ||
168 | + | ||
169 | + function handleMaxConnectionPoint(sourceNode?: GraphNode) { | ||
170 | + if (!sourceNode) return; | ||
171 | + | ||
172 | + const maxConnectionPoint = unref(sourceNode).data?.config?.maxConnectionPoint; | ||
173 | + | ||
174 | + if (!maxConnectionPoint || !isNumber(maxConnectionPoint)) return; | ||
175 | + | ||
176 | + const sourceId = sourceNode.id; | ||
177 | + const connectionPool = unref(getEdges).filter((item) => item.source === sourceId); | ||
178 | + if (connectionPool.length >= maxConnectionPoint && connectionPool[0]) { | ||
179 | + removeEdges(connectionPool[0].id); | ||
180 | + } | ||
181 | + } | ||
182 | + | ||
183 | + return { flowActionType }; | ||
184 | +} |
1 | +import type { VueFlowStore, Getters, Elements } from '@vue-flow/core'; | ||
2 | +import { ComputedRef, ref, unref } from 'vue'; | ||
3 | +import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; | ||
4 | +import { EntryComponentEnum } from '../enum/category'; | ||
5 | +import { useBasicDataTransform } from './useBasicDataTransform'; | ||
6 | +import { getRuleChainData, saveRuleChainData } from '/@/api/ruleDesigner'; | ||
7 | +import { ConnectionItemType, RuleChainType } from '../types/ruleNode'; | ||
8 | +import { useInputNode } from './useInputNode'; | ||
9 | +import { buildUUID } from '/@/utils/uuid'; | ||
10 | + | ||
11 | +const ignoreNodeKeys: string[] = [EntryComponentEnum.INPUT]; | ||
12 | + | ||
13 | +export function useSaveAndRedo() { | ||
14 | + const changeMarker = ref(false); | ||
15 | + | ||
16 | + const loading = ref(false); | ||
17 | + | ||
18 | + const redoDataRef = ref<Elements>([]); | ||
19 | + | ||
20 | + const { mergeData, deconstructionData } = useBasicDataTransform(); | ||
21 | + | ||
22 | + const triggerChange = () => { | ||
23 | + changeMarker.value = true; | ||
24 | + }; | ||
25 | + | ||
26 | + const resetChange = () => { | ||
27 | + changeMarker.value = false; | ||
28 | + }; | ||
29 | + | ||
30 | + /** | ||
31 | + * @description 保存连接信息 | ||
32 | + */ | ||
33 | + function getConnections( | ||
34 | + nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'], | ||
35 | + edges: ComputedRef<Getters['getEdges']> | ||
36 | + ) { | ||
37 | + const nodeIndexMap = new Map(); | ||
38 | + | ||
39 | + const connections: ConnectionItemType[] = []; | ||
40 | + | ||
41 | + unref(nodesRef).forEach((item, index) => { | ||
42 | + nodeIndexMap.set(item.id, index); | ||
43 | + }); | ||
44 | + | ||
45 | + for (const item of unref(edges)) { | ||
46 | + const { data, target, source } = item; | ||
47 | + const { data: bindData } = data as EdgeData; | ||
48 | + const { type } = bindData || {}; | ||
49 | + const fromIndex = nodeIndexMap.get(source); | ||
50 | + const toIndex = nodeIndexMap.get(target); | ||
51 | + type?.forEach((key) => { | ||
52 | + connections.push({ fromIndex, toIndex, type: key }); | ||
53 | + }); | ||
54 | + } | ||
55 | + | ||
56 | + return connections; | ||
57 | + } | ||
58 | + | ||
59 | + function getNodes(nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes']) { | ||
60 | + const nodes: BasicNodeBindData[] = []; | ||
61 | + | ||
62 | + for (const node of unref(nodesRef)) { | ||
63 | + const nodeData = node.data as NodeData; | ||
64 | + | ||
65 | + if (ignoreNodeKeys.includes(nodeData.config?.key as string)) continue; | ||
66 | + | ||
67 | + const data = nodeData.data; | ||
68 | + | ||
69 | + nodes.push(mergeData(data, nodeData, node)); | ||
70 | + } | ||
71 | + | ||
72 | + return nodes; | ||
73 | + } | ||
74 | + | ||
75 | + function getFirsetNodeIndex( | ||
76 | + nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'], | ||
77 | + edges: ComputedRef<Getters['getEdges']> | ||
78 | + ) { | ||
79 | + const inputNode = unref(edges).find( | ||
80 | + (item) => (item.sourceNode.data as NodeData).config?.key === EntryComponentEnum.INPUT | ||
81 | + ); | ||
82 | + | ||
83 | + if (inputNode) { | ||
84 | + const targetId = inputNode.target; | ||
85 | + const index = unref(nodesRef).findIndex((item) => item.id === targetId); | ||
86 | + return index; | ||
87 | + } | ||
88 | + } | ||
89 | + | ||
90 | + const handleApplyChange = (flowActionType: VueFlowStore) => { | ||
91 | + if (!unref(changeMarker)) return; | ||
92 | + | ||
93 | + const edgesRef = flowActionType.getEdges; | ||
94 | + | ||
95 | + const extraIgnoreNodeRef = unref(flowActionType.getNodes).filter( | ||
96 | + (item) => !ignoreNodeKeys.includes((item.data as NodeData).config?.key as string) | ||
97 | + ); | ||
98 | + | ||
99 | + const connections = getConnections(extraIgnoreNodeRef, edgesRef); | ||
100 | + | ||
101 | + const nodes = getNodes(extraIgnoreNodeRef); | ||
102 | + | ||
103 | + const firstNodeIndex = getFirsetNodeIndex(extraIgnoreNodeRef, edgesRef); | ||
104 | + | ||
105 | + handleSaveRuleChain(connections, nodes, firstNodeIndex); | ||
106 | + }; | ||
107 | + | ||
108 | + const handleRedoChange = (flowActionType: VueFlowStore) => { | ||
109 | + if (!unref(changeMarker)) return; | ||
110 | + flowActionType.setElements(unref(redoDataRef)); | ||
111 | + resetChange(); | ||
112 | + }; | ||
113 | + | ||
114 | + async function handleSaveRuleChain( | ||
115 | + connections: ConnectionItemType[], | ||
116 | + nodes: BasicNodeBindData[], | ||
117 | + firstNodeIndex?: number | ||
118 | + ) { | ||
119 | + try { | ||
120 | + loading.value = true; | ||
121 | + const data = await saveRuleChainData({ | ||
122 | + connections, | ||
123 | + nodes, | ||
124 | + firstNodeIndex, | ||
125 | + ruleChainId: { | ||
126 | + entityType: 'RULE_CHAIN', | ||
127 | + id: 'd6ddea70-25da-11ee-bc6b-47e715464e68', | ||
128 | + }, | ||
129 | + }); | ||
130 | + | ||
131 | + parseRuleChain(data); | ||
132 | + | ||
133 | + resetChange(); | ||
134 | + } finally { | ||
135 | + loading.value = false; | ||
136 | + } | ||
137 | + } | ||
138 | + | ||
139 | + async function getCurrentPageMetaData(flowActionType: VueFlowStore) { | ||
140 | + try { | ||
141 | + loading.value = true; | ||
142 | + | ||
143 | + const id = 'd6ddea70-25da-11ee-bc6b-47e715464e68'; | ||
144 | + | ||
145 | + const data = await getRuleChainData(id); | ||
146 | + | ||
147 | + const elements = parseRuleChain(data); | ||
148 | + | ||
149 | + flowActionType.setElements(elements); | ||
150 | + | ||
151 | + resetChange(); | ||
152 | + } finally { | ||
153 | + loading.value = false; | ||
154 | + } | ||
155 | + } | ||
156 | + | ||
157 | + function parseRuleChain(ruleChain: RuleChainType) { | ||
158 | + const inputId = buildUUID(); | ||
159 | + | ||
160 | + const { getInputNodeConfig } = useInputNode(); | ||
161 | + | ||
162 | + const value = deconstructionData(ruleChain, inputId); | ||
163 | + | ||
164 | + const { nodes = [], edges = [] } = value || {}; | ||
165 | + | ||
166 | + const inputNode = getInputNodeConfig(inputId); | ||
167 | + | ||
168 | + const elements = [inputNode, ...nodes, ...edges]; | ||
169 | + | ||
170 | + redoDataRef.value = elements; | ||
171 | + | ||
172 | + return elements; | ||
173 | + } | ||
174 | + | ||
175 | + return { | ||
176 | + loading, | ||
177 | + changeMarker, | ||
178 | + triggerChange, | ||
179 | + handleApplyChange, | ||
180 | + handleRedoChange, | ||
181 | + getCurrentPageMetaData, | ||
182 | + }; | ||
183 | +} |
src/views/rule/designer/hook/useTextWidth.ts
0 → 100644
1 | +export function useTextWidth(text: string, fontSize: number) { | ||
2 | + const LetterMap = { | ||
3 | + ' ': 0.3329986572265625, | ||
4 | + a: 0.5589996337890625, | ||
5 | + A: 0.6569992065429687, | ||
6 | + b: 0.58599853515625, | ||
7 | + B: 0.6769989013671875, | ||
8 | + c: 0.5469985961914062, | ||
9 | + C: 0.7279998779296875, | ||
10 | + d: 0.58599853515625, | ||
11 | + D: 0.705999755859375, | ||
12 | + e: 0.554998779296875, | ||
13 | + E: 0.63699951171875, | ||
14 | + f: 0.37299957275390627, | ||
15 | + F: 0.5769989013671875, | ||
16 | + g: 0.5909988403320312, | ||
17 | + G: 0.7479995727539063, | ||
18 | + h: 0.555999755859375, | ||
19 | + H: 0.7199996948242188, | ||
20 | + i: 0.255999755859375, | ||
21 | + I: 0.23699951171875, | ||
22 | + j: 0.26699981689453123, | ||
23 | + J: 0.5169998168945312, | ||
24 | + k: 0.5289993286132812, | ||
25 | + K: 0.6899993896484375, | ||
26 | + l: 0.23499908447265624, | ||
27 | + L: 0.5879989624023437, | ||
28 | + m: 0.854998779296875, | ||
29 | + M: 0.8819992065429687, | ||
30 | + n: 0.5589996337890625, | ||
31 | + N: 0.7189987182617188, | ||
32 | + o: 0.58599853515625, | ||
33 | + O: 0.7669998168945312, | ||
34 | + p: 0.58599853515625, | ||
35 | + P: 0.6419998168945312, | ||
36 | + q: 0.58599853515625, | ||
37 | + Q: 0.7669998168945312, | ||
38 | + r: 0.3649993896484375, | ||
39 | + R: 0.6759994506835938, | ||
40 | + s: 0.504998779296875, | ||
41 | + S: 0.6319992065429687, | ||
42 | + t: 0.354998779296875, | ||
43 | + T: 0.6189987182617187, | ||
44 | + u: 0.5599990844726562, | ||
45 | + U: 0.7139999389648437, | ||
46 | + v: 0.48199920654296874, | ||
47 | + V: 0.6389999389648438, | ||
48 | + w: 0.754998779296875, | ||
49 | + W: 0.929998779296875, | ||
50 | + x: 0.5089996337890625, | ||
51 | + X: 0.63699951171875, | ||
52 | + y: 0.4959991455078125, | ||
53 | + Y: 0.66199951171875, | ||
54 | + z: 0.48699951171875, | ||
55 | + Z: 0.6239990234375, | ||
56 | + 0: 0.6, | ||
57 | + 1: 0.40099945068359377, | ||
58 | + 2: 0.6, | ||
59 | + 3: 0.6, | ||
60 | + 4: 0.6, | ||
61 | + 5: 0.6, | ||
62 | + 6: 0.6, | ||
63 | + 7: 0.5469985961914062, | ||
64 | + 8: 0.6, | ||
65 | + 9: 0.6, | ||
66 | + '[': 0.3329986572265625, | ||
67 | + ']': 0.3329986572265625, | ||
68 | + ',': 0.26399993896484375, | ||
69 | + '.': 0.26399993896484375, | ||
70 | + ';': 0.26399993896484375, | ||
71 | + ':': 0.26399993896484375, | ||
72 | + '{': 0.3329986572265625, | ||
73 | + '}': 0.3329986572265625, | ||
74 | + '\\': 0.5, | ||
75 | + '|': 0.19499969482421875, | ||
76 | + '=': 0.604998779296875, | ||
77 | + '+': 0.604998779296875, | ||
78 | + '-': 0.604998779296875, | ||
79 | + _: 0.5, | ||
80 | + '`': 0.3329986572265625, | ||
81 | + ' ~': 0.8329986572265625, | ||
82 | + '!': 0.3329986572265625, | ||
83 | + '@': 0.8579986572265625, | ||
84 | + '#': 0.6, | ||
85 | + $: 0.6, | ||
86 | + '%': 0.9699996948242188, | ||
87 | + '^': 0.517999267578125, | ||
88 | + '&': 0.7259994506835937, | ||
89 | + '*': 0.505999755859375, | ||
90 | + '(': 0.3329986572265625, | ||
91 | + ')': 0.3329986572265625, | ||
92 | + '<': 0.604998779296875, | ||
93 | + '>': 0.604998779296875, | ||
94 | + '/': 0.5, | ||
95 | + '?': 0.53699951171875, | ||
96 | + '"': 0.33699951171875, | ||
97 | + }; | ||
98 | + | ||
99 | + // 计算非中文字符宽度 | ||
100 | + const getLetterWidth = (letter: string, fontSize: number) => fontSize * (LetterMap[letter] || 1); | ||
101 | + | ||
102 | + // 计算文本宽度 | ||
103 | + const getTextWidth = (text: string, fontSize: number) => { | ||
104 | + // 中文匹配正则 | ||
105 | + const pattern = new RegExp('[\u4E00-\u9FA5]+'); | ||
106 | + // 文本宽度 | ||
107 | + const textWidth = text.split('').reduce((pre, curLetter) => { | ||
108 | + // 单个字符宽度 | ||
109 | + const letterWidth = pattern.test(curLetter) ? fontSize : getLetterWidth(curLetter, fontSize); | ||
110 | + return pre + letterWidth; | ||
111 | + }, 0); | ||
112 | + return textWidth; | ||
113 | + }; | ||
114 | + | ||
115 | + return getTextWidth(text, fontSize); | ||
116 | +} |
src/views/rule/designer/index.ts
0 → 100644
1 | +export { default as RuleChainsDesigner } from './index.vue'; |
src/views/rule/designer/index.vue
0 → 100644
1 | +<script setup lang="ts"> | ||
2 | + import { Background, BackgroundVariant } from '@vue-flow/background'; | ||
3 | + import { Controls } from '@vue-flow/controls'; | ||
4 | + import { Panel, PanelPosition, VueFlow } from '@vue-flow/core'; | ||
5 | + import { computed, onMounted, Ref, ref, unref } from 'vue'; | ||
6 | + import './style'; | ||
7 | + import { BasicConnectionArrow, BasicConnectionLine, BasicEdge, Sidebar } from './src/components'; | ||
8 | + import type { FlowElRef } from './types/flow'; | ||
9 | + import { useDragCreate } from './hook/useDragCreate'; | ||
10 | + import { createFlowContext } from './hook/useFlowContext'; | ||
11 | + import { CreateNodeModal } from './src/components/CreateNodeModal'; | ||
12 | + import { useRuleFlow } from './hook/useRuleFlow'; | ||
13 | + import { CreateEdgeModal } from './src/components/CreateEdgeModal'; | ||
14 | + import { useFullScreen } from './hook/useFullScreen'; | ||
15 | + import { useSaveAndRedo } from './hook/useSaveAndRedo'; | ||
16 | + import { Icon } from '/@/components/Icon'; | ||
17 | + import { UpdateNodeDrawer } from './src/components/UpdateNodeDrawer'; | ||
18 | + import { UpdateEdgeDrawer } from './src/components/UpdateEdgeDrawer'; | ||
19 | + | ||
20 | + const getId = Number(Math.random().toString().substring(2)).toString(16); | ||
21 | + | ||
22 | + const rootElRef = ref<Nullable<HTMLDivElement>>(null); | ||
23 | + | ||
24 | + const createNodeModalActionType = ref<Nullable<InstanceType<typeof CreateNodeModal>>>(null); | ||
25 | + | ||
26 | + const createEdgeModalActionType = ref<Nullable<InstanceType<typeof CreateEdgeModal>>>(null); | ||
27 | + | ||
28 | + const updateNodeDrawerActionType = ref<Nullable<InstanceType<typeof UpdateNodeDrawer>>>(null); | ||
29 | + | ||
30 | + const updateEdgeDrawerActionType = ref<Nullable<InstanceType<typeof UpdateEdgeDrawer>>>(null); | ||
31 | + | ||
32 | + const flowElRef = ref<Nullable<FlowElRef>>(null); | ||
33 | + | ||
34 | + const elements = ref([]); | ||
35 | + | ||
36 | + const { | ||
37 | + changeMarker, | ||
38 | + getCurrentPageMetaData, | ||
39 | + triggerChange, | ||
40 | + handleApplyChange, | ||
41 | + handleRedoChange, | ||
42 | + } = useSaveAndRedo(); | ||
43 | + | ||
44 | + const { flowActionType } = useRuleFlow({ | ||
45 | + id: getId, | ||
46 | + createNodeModalActionType, | ||
47 | + createEdgeModalActionType, | ||
48 | + updateEdgeDrawerActionType, | ||
49 | + updateNodeDrawerActionType, | ||
50 | + triggerChange, | ||
51 | + }); | ||
52 | + | ||
53 | + const { handleOnDragOver, handleOnDrop } = useDragCreate({ | ||
54 | + el: flowElRef, | ||
55 | + createNodeModalActionType, | ||
56 | + flowActionType, | ||
57 | + triggerChange, | ||
58 | + }); | ||
59 | + | ||
60 | + const { handleFullScreen, getFullScreenIcon } = useFullScreen( | ||
61 | + rootElRef as unknown as Ref<Nullable<HTMLDivElement>> | ||
62 | + ); | ||
63 | + | ||
64 | + const handleGetContainer = () => unref(rootElRef)!; | ||
65 | + | ||
66 | + const getDeleteDisplayState = computed(() => unref(flowActionType.getSelectedElements).length); | ||
67 | + | ||
68 | + const handleDeleteSelectionElements = () => { | ||
69 | + flowActionType.removeSelectedElements(); | ||
70 | + }; | ||
71 | + | ||
72 | + onMounted(() => { | ||
73 | + getCurrentPageMetaData(flowActionType); | ||
74 | + }); | ||
75 | + | ||
76 | + createFlowContext({ | ||
77 | + createEdgeModalActionType, | ||
78 | + createNodeModalActionType, | ||
79 | + updateEdgeDrawerActionType, | ||
80 | + updateNodeDrawerActionType, | ||
81 | + flowActionType, | ||
82 | + triggerChange, | ||
83 | + }); | ||
84 | +</script> | ||
85 | + | ||
86 | +<template> | ||
87 | + <main ref="rootElRef" class="w-full h-full flex relative" @drop="handleOnDrop"> | ||
88 | + <Sidebar /> | ||
89 | + | ||
90 | + <VueFlow | ||
91 | + :id="getId" | ||
92 | + ref="flowElRef" | ||
93 | + v-model="elements" | ||
94 | + class="w-full h-full" | ||
95 | + @dragover="handleOnDragOver" | ||
96 | + > | ||
97 | + <template #connection-line="props"> | ||
98 | + <BasicConnectionLine v-bind="props" /> | ||
99 | + </template> | ||
100 | + <template #edge-custom="props"> | ||
101 | + <BasicEdge v-bind="props" /> | ||
102 | + </template> | ||
103 | + | ||
104 | + <BasicConnectionArrow /> | ||
105 | + | ||
106 | + <Background :variant="BackgroundVariant.Lines" :gap="25" pattern-color="#cfcfcf" /> | ||
107 | + | ||
108 | + <Controls :position="PanelPosition.BottomLeft" /> | ||
109 | + | ||
110 | + <Panel position="bottom-right" class="controls"> | ||
111 | + <section class="flex gap-4"> | ||
112 | + <button | ||
113 | + :style="{ transform: `translateY(${getDeleteDisplayState ? 0 : '72px'})` }" | ||
114 | + class="button-box-shadow w-14 h-14 flex justify-center items-center bg-orange-600 rounded-full transition-transform transform" | ||
115 | + @click="handleDeleteSelectionElements" | ||
116 | + > | ||
117 | + <Icon class="!text-3xl !text-light-50" icon="mdi:delete" /> | ||
118 | + </button> | ||
119 | + <button | ||
120 | + class="button-box-shadow w-14 h-14 flex justify-center items-center bg-gray-400 rounded-full opacity-50" | ||
121 | + > | ||
122 | + <Icon class="!text-3xl !text-light-50" icon="carbon:debug" /> | ||
123 | + </button> | ||
124 | + <button | ||
125 | + :class="changeMarker ? '!bg-orange-600 !opacity-100' : 'opacity-50'" | ||
126 | + class="button-box-shadow w-14 h-14 flex justify-center items-center bg-gray-400 rounded-full" | ||
127 | + @click="handleApplyChange(flowActionType)" | ||
128 | + > | ||
129 | + <Icon class="!text-3xl !text-light-50" icon="mdi:tick" /> | ||
130 | + </button> | ||
131 | + <button | ||
132 | + :class="changeMarker ? '!bg-orange-600 !opacity-100' : 'opacity-50'" | ||
133 | + class="button-box-shadow w-14 h-14 flex justify-center items-center bg-gray-400 rounded-full" | ||
134 | + @click="handleRedoChange(flowActionType)" | ||
135 | + > | ||
136 | + <Icon class="!text-3xl !text-light-50" icon="ic:baseline-close" /> | ||
137 | + </button> | ||
138 | + </section> | ||
139 | + </Panel> | ||
140 | + | ||
141 | + <Panel position="top-right"> | ||
142 | + <button | ||
143 | + class="w-10 h-10 bg-gray-300 flex justify-center items-center rounded-full" | ||
144 | + @click="handleFullScreen" | ||
145 | + > | ||
146 | + <Icon class="!text-2xl" :icon="getFullScreenIcon" /> | ||
147 | + </button> | ||
148 | + </Panel> | ||
149 | + </VueFlow> | ||
150 | + | ||
151 | + <CreateNodeModal ref="createNodeModalActionType" :get-container="handleGetContainer" /> | ||
152 | + <CreateEdgeModal ref="createEdgeModalActionType" :get-container="handleGetContainer" /> | ||
153 | + | ||
154 | + <UpdateEdgeDrawer ref="updateEdgeDrawerActionType" /> | ||
155 | + <UpdateNodeDrawer ref="updateNodeDrawerActionType" /> | ||
156 | + </main> | ||
157 | +</template> | ||
158 | + | ||
159 | +<style scoped> | ||
160 | + .button-box-shadow { | ||
161 | + box-shadow: 0 3px 5px -1px #0003, 0 6px 10px 0 #00000024, 0 1px 18px 0 #0000001f; | ||
162 | + } | ||
163 | +</style> |
1 | +import { cloneDeep } from 'lodash-es'; | ||
2 | +import { PublicNodeItemClass } from '../../../types/node'; | ||
3 | +import type { | ||
4 | + CategoryConfigType, | ||
5 | + CreateComponentType, | ||
6 | + NodeItemConfigType, | ||
7 | +} from '../../../types/node'; | ||
8 | +import { InputConfig } from '.'; | ||
9 | +import { EntryCategoryConfig } from '..'; | ||
10 | + | ||
11 | +export class Config extends PublicNodeItemClass implements CreateComponentType { | ||
12 | + public config: NodeItemConfigType = cloneDeep(InputConfig); | ||
13 | + | ||
14 | + public categoryConfig: CategoryConfigType = cloneDeep(EntryCategoryConfig); | ||
15 | + | ||
16 | + constructor() { | ||
17 | + super(); | ||
18 | + } | ||
19 | +} |
1 | +import { EntryComponentEnum } from '../../../enum/category'; | ||
2 | +import { useCreateNodeKey } from '../../../hook/useCreateNodeKey'; | ||
3 | +import type { NodeItemConfigType } from '../../../types/node'; | ||
4 | +import { RuleNodeTypeEnum } from '../../index.type'; | ||
5 | + | ||
6 | +const keys = useCreateNodeKey(EntryComponentEnum.INPUT); | ||
7 | + | ||
8 | +export const InputConfig: NodeItemConfigType = { | ||
9 | + ...keys, | ||
10 | + categoryType: RuleNodeTypeEnum.ENTRY, | ||
11 | + clazz: EntryComponentEnum.INPUT, | ||
12 | + maxConnectionPoint: 1, | ||
13 | + backgroundColor: '#95E898', | ||
14 | + configurationDescriptor: { | ||
15 | + nodeDefinition: { | ||
16 | + icon: 'material-symbols:input-sharp', | ||
17 | + outEnabled: true, | ||
18 | + }, | ||
19 | + }, | ||
20 | +}; |
1 | +import type { CategoryConfigType } from '../../types/node'; | ||
2 | +import { RuleNodeTypeEnum } from '../index.type'; | ||
3 | + | ||
4 | +export const EntryCategoryConfig: CategoryConfigType = { | ||
5 | + category: RuleNodeTypeEnum.ENTRY, | ||
6 | + backgroundColor: '#95E898', | ||
7 | + title: '入口', | ||
8 | + icon: 'material-symbols:input-sharp', | ||
9 | + description: '使用配置条件筛选传入消息', | ||
10 | +}; |
1 | +import { cloneDeep } from 'lodash-es'; | ||
2 | +import { PublicNodeItemClass } from '../../../types/node'; | ||
3 | +import type { | ||
4 | + CategoryConfigType, | ||
5 | + CreateComponentType, | ||
6 | + NodeItemConfigType, | ||
7 | +} from '../../../types/node'; | ||
8 | +import { FilterCategoryConfig } from '..'; | ||
9 | +import { CheckAlarmStatusConfig } from '.'; | ||
10 | + | ||
11 | +export class Config extends PublicNodeItemClass implements CreateComponentType { | ||
12 | + public config: NodeItemConfigType = cloneDeep(CheckAlarmStatusConfig); | ||
13 | + | ||
14 | + public categoryConfig: CategoryConfigType = cloneDeep(FilterCategoryConfig); | ||
15 | + | ||
16 | + constructor() { | ||
17 | + super(); | ||
18 | + } | ||
19 | +} |
1 | +import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | ||
2 | +import { FormSchema } from '/@/components/Form'; | ||
3 | +import { AlarmStatus, AlarmStatusMean } from '/@/enums/alarmEnum'; | ||
4 | + | ||
5 | +export const formSchemas: FormSchema[] = [ | ||
6 | + { | ||
7 | + field: NodeBindDataFieldEnum.ALARM_STATUS_LIST, | ||
8 | + component: 'Select', | ||
9 | + label: NodeBindDataFieldNameEnum.ALARM_STATUS_LIST, | ||
10 | + required: true, | ||
11 | + componentProps: { | ||
12 | + mode: 'multiple', | ||
13 | + getPopupContainer: () => document.body, | ||
14 | + placeholder: `请选择${NodeBindDataFieldNameEnum.ALARM_STATUS_LIST}`, | ||
15 | + options: [ | ||
16 | + { | ||
17 | + label: AlarmStatusMean[AlarmStatus.CLEARED_UN_ACK], | ||
18 | + value: AlarmStatus.CLEARED_UN_ACK, | ||
19 | + }, | ||
20 | + { | ||
21 | + label: AlarmStatusMean[AlarmStatus.ACTIVE_UN_ACK], | ||
22 | + value: AlarmStatus.ACTIVE_UN_ACK, | ||
23 | + }, | ||
24 | + { | ||
25 | + label: AlarmStatusMean[AlarmStatus.CLEARED_ACK], | ||
26 | + value: AlarmStatus.CLEARED_ACK, | ||
27 | + }, | ||
28 | + { | ||
29 | + label: AlarmStatusMean[AlarmStatus.ACTIVE_ACK], | ||
30 | + value: AlarmStatus.ACTIVE_ACK, | ||
31 | + }, | ||
32 | + ], | ||
33 | + }, | ||
34 | + }, | ||
35 | +]; |
1 | +<script lang="ts" setup> | ||
2 | + import type { CreateModalDefineExposeType } from '../../../types'; | ||
3 | + import { BasicForm, useForm } from '/@/components/Form'; | ||
4 | + import { formSchemas } from './create.config'; | ||
5 | + import { NodeData } from '../../../types/node'; | ||
6 | + | ||
7 | + defineProps<{ | ||
8 | + config: NodeData; | ||
9 | + }>(); | ||
10 | + | ||
11 | + const [register, { validate, getFieldsValue, setFieldsValue, resetFields }] = useForm({ | ||
12 | + schemas: formSchemas, | ||
13 | + showActionButtonGroup: false, | ||
14 | + }); | ||
15 | + | ||
16 | + const getValue: CreateModalDefineExposeType['getFieldsValue'] = async () => { | ||
17 | + await validate(); | ||
18 | + const value = getFieldsValue() || {}; | ||
19 | + return value; | ||
20 | + }; | ||
21 | + | ||
22 | + const setValue: CreateModalDefineExposeType['setFieldsValue'] = (value) => { | ||
23 | + resetFields(); | ||
24 | + setFieldsValue(value); | ||
25 | + }; | ||
26 | + | ||
27 | + defineExpose({ | ||
28 | + setFieldsValue: setValue, | ||
29 | + getFieldsValue: getValue, | ||
30 | + } as CreateModalDefineExposeType); | ||
31 | +</script> | ||
32 | + | ||
33 | +<template> | ||
34 | + <BasicForm @register="register" /> | ||
35 | +</template> |
1 | +import { FilterCategoryComponentEnum } from '../../../enum/category'; | ||
2 | +import { useCreateNodeKey } from '../../../hook/useCreateNodeKey'; | ||
3 | +import type { NodeItemConfigType } from '../../../types/node'; | ||
4 | +import { RuleNodeTypeEnum } from '../../index.type'; | ||
5 | +import { AlarmStatus } from '/@/enums/alarmEnum'; | ||
6 | + | ||
7 | +const keys = useCreateNodeKey(FilterCategoryComponentEnum.CHECK_ALARM_STATUS); | ||
8 | + | ||
9 | +export interface CheckAlarmStatusDataType { | ||
10 | + alarmStatusList: AlarmStatus[]; | ||
11 | +} | ||
12 | + | ||
13 | +export const CheckAlarmStatusConfig: NodeItemConfigType = { | ||
14 | + ...keys, | ||
15 | + clazz: 'org.thingsboard.rule.engine.filter.TbCheckAlarmStatusNode', | ||
16 | + categoryType: RuleNodeTypeEnum.FILTER, | ||
17 | + name: 'check alarm status', | ||
18 | + backgroundColor: '#ede550', | ||
19 | + configurationDescriptor: { | ||
20 | + nodeDefinition: { | ||
21 | + icon: 'tabler:circuit-ground', | ||
22 | + details: | ||
23 | + 'If the alarm status matches the specified one - msg is success if does not match - msg is failure.', | ||
24 | + description: 'Checks alarm status.', | ||
25 | + inEnabled: true, | ||
26 | + outEnabled: true, | ||
27 | + relationTypes: ['True', 'False', 'Failure'], | ||
28 | + customRelations: false, | ||
29 | + ruleChainNode: false, | ||
30 | + defaultConfiguration: { | ||
31 | + alarmStatusList: [AlarmStatus.ACTIVE_ACK, AlarmStatus.ACTIVE_UN_ACK], | ||
32 | + }, | ||
33 | + }, | ||
34 | + }, | ||
35 | +}; |
1 | +import type { CategoryConfigType, NodeItemConfigType } from '../../types/node'; | ||
2 | +import { RuleNodeTypeEnum } from '../index.type'; | ||
3 | +import { CheckAlarmStatusConfig } from './CheckAlarmStatus'; | ||
4 | + | ||
5 | +export const FilterCategoryConfig: CategoryConfigType = { | ||
6 | + category: RuleNodeTypeEnum.FILTER, | ||
7 | + backgroundColor: '#ede550', | ||
8 | + title: '筛选器', | ||
9 | + icon: 'tabler:circuit-ground', | ||
10 | + description: '使用配置条件筛选传入消息', | ||
11 | +}; | ||
12 | + | ||
13 | +export const FilterComponents: NodeItemConfigType[] = [CheckAlarmStatusConfig]; |
src/views/rule/designer/packages/index.ts
0 → 100644
1 | +import { BasicConnectionModalEnum } from '../enum'; | ||
2 | +import { FetchNodeComFlagTypeENum } from '../enum/node'; | ||
3 | +import type { CategoryConfigType, NodeItemConfigType } from '../types/node'; | ||
4 | +import { FilterCategoryConfig, FilterComponents } from './Filter'; | ||
5 | +import { RuleNodeTypeEnum } from './index.type'; | ||
6 | + | ||
7 | +const createModules = import.meta.glob('../packages/**/create.vue'); | ||
8 | +const connectionModules = import.meta.glob('../packages/**/connection.vue'); | ||
9 | + | ||
10 | +const BasicConnectionComponent = { | ||
11 | + [BasicConnectionModalEnum.BASIC]: () => | ||
12 | + import('../src/components/CreateEdgeModal/BasicRelations.vue'), | ||
13 | + [BasicConnectionModalEnum.CUSTOM]: () => | ||
14 | + import('../src/components/CreateEdgeModal/CustomRelations.vue'), | ||
15 | +}; | ||
16 | + | ||
17 | +export const fetchNodeComponent = (key: string, flag: FetchNodeComFlagTypeENum) => { | ||
18 | + const modules = | ||
19 | + flag === FetchNodeComFlagTypeENum.CONNECTION_MODAL ? connectionModules : createModules; | ||
20 | + const modulesName = Object.keys(modules); | ||
21 | + const path = modulesName.find((item) => { | ||
22 | + const temp = item.split('/'); | ||
23 | + return temp[temp.length - 2] === key; | ||
24 | + }); | ||
25 | + return path ? modules[path] : null; | ||
26 | +}; | ||
27 | + | ||
28 | +export const fetchConnectionComponent = async (config: NodeItemConfigType) => { | ||
29 | + const { configurationDescriptor } = config; | ||
30 | + const { nodeDefinition } = configurationDescriptor; | ||
31 | + const { customRelations } = nodeDefinition; | ||
32 | + return customRelations | ||
33 | + ? BasicConnectionComponent[BasicConnectionModalEnum.CUSTOM] | ||
34 | + : BasicConnectionComponent[BasicConnectionModalEnum.BASIC]; | ||
35 | +}; | ||
36 | + | ||
37 | +export const fetchCreateComponent = async (config: NodeItemConfigType) => { | ||
38 | + const { key } = config; | ||
39 | + return fetchNodeComponent(key, FetchNodeComFlagTypeENum.CREATE_MODAL); | ||
40 | +}; | ||
41 | + | ||
42 | +export const allComponents: Record< | ||
43 | + RuleNodeTypeEnum, | ||
44 | + { category: CategoryConfigType; components: NodeItemConfigType[] } | ||
45 | +> = { | ||
46 | + [RuleNodeTypeEnum.FILTER]: { | ||
47 | + category: FilterCategoryConfig, | ||
48 | + components: FilterComponents, | ||
49 | + }, | ||
50 | +}; |
1 | +<script lang="ts" setup> | ||
2 | + import { MarkerArrowEnum } from '../../../enum'; | ||
3 | +</script> | ||
4 | + | ||
5 | +<template> | ||
6 | + <svg> | ||
7 | + <defs> | ||
8 | + <marker | ||
9 | + :id="MarkerArrowEnum.BASIC_ARROW" | ||
10 | + markerWidth="5" | ||
11 | + markerHeight="5" | ||
12 | + viewBox="-6 -6 12 12" | ||
13 | + refX="5" | ||
14 | + refY="0" | ||
15 | + markerUnits="strokeWidth" | ||
16 | + orient="auto" | ||
17 | + > | ||
18 | + <polygon points="-2,0 -5,5 5,0 -5,-5" stroke="gray" fill="gray" stroke-width="1px" /> | ||
19 | + </marker> | ||
20 | + <marker | ||
21 | + :id="MarkerArrowEnum.BASIC_ARROW_SELECTED" | ||
22 | + markerWidth="5" | ||
23 | + markerHeight="5" | ||
24 | + viewBox="-6 -6 12 12" | ||
25 | + refX="5" | ||
26 | + refY="0" | ||
27 | + markerUnits="strokeWidth" | ||
28 | + orient="auto" | ||
29 | + > | ||
30 | + <polygon points="-2,0 -5,5 5,0 -5,-5" stroke="red" fill="red" stroke-width="1px" /> | ||
31 | + </marker> | ||
32 | + </defs> | ||
33 | + </svg> | ||
34 | +</template> |
1 | +<script setup lang="ts"> | ||
2 | + import { BaseEdge, ConnectionLineProps, getBezierPath } from '@vue-flow/core'; | ||
3 | + import { computed } from 'vue'; | ||
4 | + | ||
5 | + type CustomConnectionLineProps = Partial<ConnectionLineProps>; | ||
6 | + | ||
7 | + const props = defineProps<CustomConnectionLineProps>(); | ||
8 | + | ||
9 | + const path = computed(() => getBezierPath(props as unknown as Required<ConnectionLineProps>)); | ||
10 | +</script> | ||
11 | + | ||
12 | +<template> | ||
13 | + <BaseEdge v-bind="props" :path="path[0]" :marker-end="markerEnd" /> | ||
14 | +</template> |
1 | +<script setup lang="ts"> | ||
2 | + import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@vue-flow/core'; | ||
3 | + import type { EdgeProps } from '@vue-flow/core'; | ||
4 | + import { computed, toRaw, unref } from 'vue'; | ||
5 | + import type { EdgeData, NodeData } from '../../../types/node'; | ||
6 | + import { useConnectionFocus } from '../../../hook/useConnectionFocus'; | ||
7 | + import { useFlowContext } from '../../../hook/useFlowContext'; | ||
8 | + import { useTextWidth } from '../../../hook/useTextWidth'; | ||
9 | + | ||
10 | + const props = defineProps<EdgeProps>(); | ||
11 | + | ||
12 | + const { flowActionType, updateEdgeDrawerActionType } = useFlowContext(); | ||
13 | + | ||
14 | + const { getMarkerEnd, getSelected } = useConnectionFocus(props); | ||
15 | + | ||
16 | + const getData = computed(() => props.data as EdgeData); | ||
17 | + | ||
18 | + const getLabel = computed(() => { | ||
19 | + let { data } = unref(getData); | ||
20 | + const type = data?.type || []; | ||
21 | + return type.join(' / '); | ||
22 | + }); | ||
23 | + | ||
24 | + const getLabelWidth = computed(() => useTextWidth(unref(getLabel), 14) + 10); | ||
25 | + | ||
26 | + const path = computed(() => getBezierPath(props)); | ||
27 | + | ||
28 | + const handleOpenEdit = async () => { | ||
29 | + const { flag, data } = | ||
30 | + (await unref(updateEdgeDrawerActionType)?.open( | ||
31 | + toRaw(unref(props.sourceNode?.data as unknown as NodeData)), | ||
32 | + toRaw(unref(props.data as EdgeData)) | ||
33 | + )) || {}; | ||
34 | + | ||
35 | + if (!flag) return; | ||
36 | + | ||
37 | + const currentEdge = flowActionType.findEdge(props.id); | ||
38 | + | ||
39 | + (currentEdge!.data as EdgeData).data = data; | ||
40 | + | ||
41 | + console.log(props); | ||
42 | + }; | ||
43 | + | ||
44 | + const handleDelete = () => { | ||
45 | + flowActionType?.removeEdges(props.id); | ||
46 | + }; | ||
47 | +</script> | ||
48 | + | ||
49 | +<template> | ||
50 | + <BaseEdge | ||
51 | + v-bind="props" | ||
52 | + :path="path[0]" | ||
53 | + :marker-end="getMarkerEnd" | ||
54 | + :label="getLabel" | ||
55 | + :label-x="path[1]" | ||
56 | + :label-y="path[2]" | ||
57 | + :label-style="{ fill: '#003a79', fontWeight: 700, fontSize: 14 }" | ||
58 | + :label-show-bg="true" | ||
59 | + :label-bg-style="{ fill: '#fff', stroke: '#003a79', strokeWidth: 2, width: getLabelWidth }" | ||
60 | + :label-bg-padding="[5, 5]" | ||
61 | + :label-bg-border-radius="10" | ||
62 | + /> | ||
63 | + <EdgeLabelRenderer> | ||
64 | + <section | ||
65 | + :style="{ | ||
66 | + pointerEvents: 'all', | ||
67 | + position: 'absolute', | ||
68 | + transform: `translate(0, -3rem) translate(${path[1]}px,${path[2]}px)`, | ||
69 | + }" | ||
70 | + class="nodrag nopan label-container relative" | ||
71 | + > | ||
72 | + <div | ||
73 | + v-if="getSelected" | ||
74 | + class="actions flex absolute gap-1 -top-full transform translate-y-1 right w-16 h-7 justify-center" | ||
75 | + > | ||
76 | + <div | ||
77 | + class="border-2 rounded-1 border-light-50 bg-red-500 w-7 h-7 flex justify-center items-center cursor-pointer" | ||
78 | + @click="handleOpenEdit" | ||
79 | + > | ||
80 | + <Icon icon="mdi:lead-pencil" color="#fff" /> | ||
81 | + </div> | ||
82 | + <div | ||
83 | + class="border-2 rounded-1 border-light-50 bg-red-500 w-7 h-7 flex justify-center items-center cursor-pointer" | ||
84 | + @click="handleDelete" | ||
85 | + > | ||
86 | + <Icon icon="mdi:close" color="#fff" /> | ||
87 | + </div> | ||
88 | + </div> | ||
89 | + </section> | ||
90 | + </EdgeLabelRenderer> | ||
91 | +</template> |
1 | +<script lang="ts" setup> | ||
2 | + import type { NodeProps } from '@vue-flow/core'; | ||
3 | + import { Handle, Position } from '@vue-flow/core'; | ||
4 | + import { computed, unref } from 'vue'; | ||
5 | + import { Tooltip } from 'ant-design-vue'; | ||
6 | + import { Icon } from '/@/components/Icon'; | ||
7 | + import type { NodeData, NodeDefinition } from '../../../types/node'; | ||
8 | + import BasicToolbar from './BasicToolbar.vue'; | ||
9 | + | ||
10 | + const props = defineProps<NodeProps>(); | ||
11 | + | ||
12 | + const getData = computed(() => props.data as NodeData); | ||
13 | + | ||
14 | + const getNodeDefinition = computed<NodeDefinition>(() => { | ||
15 | + const { config } = unref(getData); | ||
16 | + const { configurationDescriptor } = config || {}; | ||
17 | + const { nodeDefinition } = configurationDescriptor || {}; | ||
18 | + return nodeDefinition || {}; | ||
19 | + }); | ||
20 | + | ||
21 | + const getHasInEnabled = computed(() => unref(getNodeDefinition).inEnabled); | ||
22 | + | ||
23 | + const getHasOutEnabled = computed(() => unref(getNodeDefinition).outEnabled); | ||
24 | + | ||
25 | + const getLabel = computed(() => { | ||
26 | + const { config } = unref(getData); | ||
27 | + | ||
28 | + return config?.name; | ||
29 | + }); | ||
30 | + | ||
31 | + const getIcon = computed(() => { | ||
32 | + const { icon } = unref(getNodeDefinition); | ||
33 | + const { category } = unref(getData); | ||
34 | + const { icon: categoryIcon } = category || {}; | ||
35 | + return icon || categoryIcon || 'tabler:circuit-ground'; | ||
36 | + }); | ||
37 | + | ||
38 | + const getName = computed(() => { | ||
39 | + const { data } = unref(getData); | ||
40 | + return data?.name; | ||
41 | + }); | ||
42 | + | ||
43 | + const getBackgroundColor = computed(() => { | ||
44 | + const { config, category } = unref(getData); | ||
45 | + const { backgroundColor: categoryBackgroundColor } = category || {}; | ||
46 | + const { backgroundColor } = config || {}; | ||
47 | + return backgroundColor || categoryBackgroundColor; | ||
48 | + }); | ||
49 | +</script> | ||
50 | + | ||
51 | +<template> | ||
52 | + <Tooltip color="#fff" :mouse-enter-delay="0.5" placement="right"> | ||
53 | + <template #title> | ||
54 | + <section class="text-dark-900 text-xs"> | ||
55 | + <p class="mb-0 font-bold"> {{ getData?.data?.name }} </p> | ||
56 | + <p class="mb-0 mt-2 text-gray-500 italic"> | ||
57 | + {{ getData.category?.title }} | ||
58 | + {{ getData?.category?.title && getData.config?.name ? '-' : '' }} | ||
59 | + {{ getData.config?.name }} | ||
60 | + </p> | ||
61 | + <p class="mt-1 mb-0 text-gray-500">{{ getData.data?.description }}</p> | ||
62 | + </section> | ||
63 | + </template> | ||
64 | + <main | ||
65 | + class="basic-node-hover flex items-center w-44 h-12 rounded border px-4 py-2 border-gray-700" | ||
66 | + :style="{ backgroundColor: getBackgroundColor }" | ||
67 | + > | ||
68 | + <div> | ||
69 | + <Icon class="text-2xl" :icon="getIcon" /> | ||
70 | + </div> | ||
71 | + <BasicToolbar v-bind="$props" /> | ||
72 | + <div class="flex text-xs flex-col ml-2 text-left truncate"> | ||
73 | + <span class="text-gray-700 w-full truncate">{{ getLabel }}</span> | ||
74 | + <span class="w-full truncate">{{ getName }}</span> | ||
75 | + </div> | ||
76 | + <Handle v-if="getHasInEnabled" type="source" :position="Position.Left" /> | ||
77 | + <Handle v-if="getHasOutEnabled" type="source" :position="Position.Right" /> | ||
78 | + </main> | ||
79 | + </Tooltip> | ||
80 | +</template> | ||
81 | + | ||
82 | +<style lang="css" scoped> | ||
83 | + .basic-node-hover::before { | ||
84 | + content: ''; | ||
85 | + position: absolute; | ||
86 | + width: 100%; | ||
87 | + height: 100%; | ||
88 | + left: 0; | ||
89 | + display: none; | ||
90 | + background-color: #00000027; | ||
91 | + } | ||
92 | + | ||
93 | + .basic-node-hover:hover::before { | ||
94 | + display: block; | ||
95 | + } | ||
96 | +</style> |
1 | +<script lang="ts" setup> | ||
2 | + import { Icon } from '/@/components/Icon'; | ||
3 | + import { NodeProps, Position } from '@vue-flow/core'; | ||
4 | + import { NodeToolbar } from '@vue-flow/node-toolbar'; | ||
5 | + import { computed, toRaw, unref } from 'vue'; | ||
6 | + import { useFlowContext } from '../../../hook/useFlowContext'; | ||
7 | + import type { NodeData } from '../../../types/node'; | ||
8 | + | ||
9 | + const props = defineProps<NodeProps>(); | ||
10 | + | ||
11 | + const getData = computed(() => props.data as NodeData); | ||
12 | + | ||
13 | + const { updateNodeDrawerActionType, flowActionType } = useFlowContext(); | ||
14 | + | ||
15 | + const handleOpenEdit = async () => { | ||
16 | + const { flag, data } = | ||
17 | + (await unref(updateNodeDrawerActionType)?.open(toRaw(unref(getData)))) || {}; | ||
18 | + | ||
19 | + if (!flag) return; | ||
20 | + | ||
21 | + const currentNode = flowActionType?.findNode(props.id); | ||
22 | + | ||
23 | + (currentNode!.data as NodeData).data = data; | ||
24 | + }; | ||
25 | + | ||
26 | + const handleDelete = () => { | ||
27 | + flowActionType?.removeNodes(props.id); | ||
28 | + }; | ||
29 | +</script> | ||
30 | + | ||
31 | +<template> | ||
32 | + <NodeToolbar :is-visible="data.toolbarVisible" :position="Position.Top" align="end" :offset="2"> | ||
33 | + <section class="flex gap-1 transform translate-y-1"> | ||
34 | + <div | ||
35 | + class="border-2 rounded-1 border-light-50 bg-red-500 w-7 h-7 flex justify-center items-center cursor-pointer" | ||
36 | + @click="handleOpenEdit" | ||
37 | + > | ||
38 | + <Icon icon="mdi:lead-pencil" color="#fff" /> | ||
39 | + </div> | ||
40 | + <div | ||
41 | + class="border-2 rounded-1 border-light-50 bg-red-500 w-7 h-7 flex justify-center items-center cursor-pointer" | ||
42 | + @click="handleDelete" | ||
43 | + > | ||
44 | + <Icon icon="mdi:close" color="#fff" /> | ||
45 | + </div> | ||
46 | + </section> | ||
47 | + </NodeToolbar> | ||
48 | +</template> |
1 | +<script lang="ts" setup> | ||
2 | + import { unref } from 'vue'; | ||
3 | + import { EdgeBindDataFieldEnum, EdgeBindDataFieldNameEnum } from '../../../enum/node'; | ||
4 | + import { ConnectionModalDefineExposeType } from '../../../types'; | ||
5 | + import { NodeData } from '../../../types/node'; | ||
6 | + import { BasicForm, FormSchema, useForm } from '/@/components/Form'; | ||
7 | + | ||
8 | + defineProps<{ | ||
9 | + config: NodeData; | ||
10 | + }>(); | ||
11 | + | ||
12 | + const getSchemas = (options: string[] = []): FormSchema[] => { | ||
13 | + return [ | ||
14 | + { | ||
15 | + field: EdgeBindDataFieldEnum.TYPE, | ||
16 | + component: 'Select', | ||
17 | + label: EdgeBindDataFieldNameEnum.TYPE, | ||
18 | + componentProps: { | ||
19 | + getPopupContainer: () => document.body, | ||
20 | + options: unref(options).map((item) => ({ label: item, value: item })), | ||
21 | + mode: 'multiple', | ||
22 | + placeholder: '请选择链接标签', | ||
23 | + }, | ||
24 | + }, | ||
25 | + ]; | ||
26 | + }; | ||
27 | + | ||
28 | + const [register, { updateSchema, setFieldsValue, getFieldsValue }] = useForm({ | ||
29 | + showActionButtonGroup: false, | ||
30 | + schemas: getSchemas(), | ||
31 | + }); | ||
32 | + | ||
33 | + const getValue: ConnectionModalDefineExposeType['getFieldsValue'] = async () => { | ||
34 | + return getFieldsValue(); | ||
35 | + }; | ||
36 | + | ||
37 | + const setValue: ConnectionModalDefineExposeType['setFieldsValue'] = async (value, nodeData) => { | ||
38 | + const { type } = value || {}; | ||
39 | + const relationTypes = | ||
40 | + unref(nodeData)?.config?.configurationDescriptor.nodeDefinition.relationTypes; | ||
41 | + await updateSchema(getSchemas(relationTypes)); | ||
42 | + setFieldsValue(type); | ||
43 | + }; | ||
44 | + | ||
45 | + defineExpose({ | ||
46 | + getFieldsValue: getValue, | ||
47 | + setFieldsValue: setValue, | ||
48 | + } as ConnectionModalDefineExposeType); | ||
49 | +</script> | ||
50 | + | ||
51 | +<template> | ||
52 | + <BasicForm @register="register" /> | ||
53 | +</template> |
1 | +<script lang="ts" setup> | ||
2 | + import { Spin, Empty } from 'ant-design-vue'; | ||
3 | + import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
4 | + import { BasicModal } from '/@/components/Modal'; | ||
5 | + import { useAwaitPopupWindowBindData } from '../../../hook/useAwaitPopupWindowBindData'; | ||
6 | + import { ElementsTypeEnum } from '../../../enum'; | ||
7 | + | ||
8 | + defineProps<{ | ||
9 | + getContainer: () => any; | ||
10 | + }>(); | ||
11 | + | ||
12 | + const { | ||
13 | + visible, | ||
14 | + nodeData, | ||
15 | + spinning, | ||
16 | + shadowComponent, | ||
17 | + getComponentKey, | ||
18 | + createComponentEl, | ||
19 | + open, | ||
20 | + handleSubmit, | ||
21 | + handleCancel, | ||
22 | + handleOnMounted, | ||
23 | + } = useAwaitPopupWindowBindData({ type: ElementsTypeEnum.EDGE, mode: DataActionModeEnum.CREATE }); | ||
24 | + | ||
25 | + defineExpose({ open }); | ||
26 | +</script> | ||
27 | + | ||
28 | +<template> | ||
29 | + <BasicModal | ||
30 | + v-model:visible="visible" | ||
31 | + title="添加链接" | ||
32 | + :get-container="getContainer" | ||
33 | + @ok="handleSubmit" | ||
34 | + @cancel="handleCancel" | ||
35 | + > | ||
36 | + <Spin :spinning="spinning"> | ||
37 | + <component | ||
38 | + :is="shadowComponent" | ||
39 | + v-if="shadowComponent" | ||
40 | + ref="createComponentEl" | ||
41 | + :config="nodeData" | ||
42 | + :key="getComponentKey" | ||
43 | + @vue:mounted="handleOnMounted" | ||
44 | + /> | ||
45 | + <Empty v-if="!shadowComponent" description="未找到链接组件" /> | ||
46 | + </Spin> | ||
47 | + </BasicModal> | ||
48 | +</template> |
1 | +import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | ||
2 | +import { FormSchema } from '/@/components/Form'; | ||
3 | + | ||
4 | +export const TopFormSchemas: FormSchema[] = [ | ||
5 | + { | ||
6 | + field: NodeBindDataFieldEnum.NAME, | ||
7 | + component: 'Input', | ||
8 | + label: NodeBindDataFieldNameEnum.NAME, | ||
9 | + required: true, | ||
10 | + colProps: { | ||
11 | + span: 16, | ||
12 | + }, | ||
13 | + componentProps: { | ||
14 | + placeholder: `请输入${NodeBindDataFieldNameEnum.NAME}`, | ||
15 | + }, | ||
16 | + }, | ||
17 | + { | ||
18 | + field: NodeBindDataFieldEnum.DEBUG_MODE, | ||
19 | + component: 'Checkbox', | ||
20 | + label: NodeBindDataFieldNameEnum.DEBUG_MODE, | ||
21 | + colProps: { | ||
22 | + offset: 2, | ||
23 | + span: 6, | ||
24 | + }, | ||
25 | + itemProps: { | ||
26 | + labelCol: { span: 14 }, | ||
27 | + wrapperCol: { span: 10 }, | ||
28 | + }, | ||
29 | + componentProps: { | ||
30 | + placeholder: `请输入${NodeBindDataFieldNameEnum.NAME}`, | ||
31 | + }, | ||
32 | + }, | ||
33 | +]; | ||
34 | + | ||
35 | +export const BottomFormSchemas: FormSchema[] = [ | ||
36 | + { | ||
37 | + field: NodeBindDataFieldEnum.DESCRIPTION, | ||
38 | + component: 'InputTextArea', | ||
39 | + label: NodeBindDataFieldNameEnum.DESCRIPTION, | ||
40 | + componentProps: { | ||
41 | + placeholder: `请输入${NodeBindDataFieldNameEnum.DESCRIPTION}`, | ||
42 | + autoSize: true, | ||
43 | + }, | ||
44 | + }, | ||
45 | +]; |
1 | +export { default as CreateNodeModal } from './index.vue'; |
1 | +<script lang="ts" setup> | ||
2 | + import { Empty, Spin } from 'ant-design-vue'; | ||
3 | + import { BasicModal } from '/@/components/Modal'; | ||
4 | + import { useAwaitPopupWindowBindData } from '../../../hook/useAwaitPopupWindowBindData'; | ||
5 | + import { ElementsTypeEnum } from '../../../enum'; | ||
6 | + import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
7 | + import { useForm, BasicForm } from '/@/components/Form'; | ||
8 | + import { BottomFormSchemas, TopFormSchemas } from './config'; | ||
9 | + | ||
10 | + defineProps<{ | ||
11 | + getContainer: () => any; | ||
12 | + }>(); | ||
13 | + | ||
14 | + const [topFormRegister, topFormActionType] = useForm({ | ||
15 | + schemas: TopFormSchemas, | ||
16 | + showActionButtonGroup: false, | ||
17 | + }); | ||
18 | + | ||
19 | + const [bottomFormRegister, bottomFormActionType] = useForm({ | ||
20 | + schemas: BottomFormSchemas, | ||
21 | + showActionButtonGroup: false, | ||
22 | + }); | ||
23 | + | ||
24 | + const { | ||
25 | + visible, | ||
26 | + spinning, | ||
27 | + nodeData, | ||
28 | + shadowComponent, | ||
29 | + getComponentKey, | ||
30 | + createComponentEl, | ||
31 | + open, | ||
32 | + handleSubmit, | ||
33 | + handleCancel, | ||
34 | + handleOnMounted, | ||
35 | + } = useAwaitPopupWindowBindData({ type: ElementsTypeEnum.NODE, mode: DataActionModeEnum.CREATE }); | ||
36 | + | ||
37 | + const validate = async () => { | ||
38 | + await topFormActionType.validate(); | ||
39 | + await bottomFormActionType.validate(); | ||
40 | + }; | ||
41 | + | ||
42 | + const getFieldsValue = () => { | ||
43 | + const topValue = topFormActionType.getFieldsValue() || {}; | ||
44 | + const bottomValue = bottomFormActionType.getFieldsValue() || {}; | ||
45 | + return { | ||
46 | + ...topValue, | ||
47 | + ...bottomValue, | ||
48 | + }; | ||
49 | + }; | ||
50 | + | ||
51 | + const resetFieldsValue = () => { | ||
52 | + topFormActionType.resetFields(); | ||
53 | + bottomFormActionType.resetFields(); | ||
54 | + }; | ||
55 | + | ||
56 | + const handleModalOk = async () => { | ||
57 | + await validate(); | ||
58 | + const data = getFieldsValue(); | ||
59 | + await handleSubmit(data); | ||
60 | + resetFieldsValue(); | ||
61 | + }; | ||
62 | + | ||
63 | + defineExpose({ open }); | ||
64 | +</script> | ||
65 | + | ||
66 | +<template> | ||
67 | + <BasicModal | ||
68 | + v-model:visible="visible" | ||
69 | + :title="`添加规则节点: ${nodeData?.config?.name}`" | ||
70 | + :get-container="getContainer" | ||
71 | + @ok="handleModalOk" | ||
72 | + @cancel="handleCancel" | ||
73 | + > | ||
74 | + <Spin :spinning="spinning"> | ||
75 | + <section v-if="shadowComponent" class="w-full h-full"> | ||
76 | + <BasicForm @register="topFormRegister" /> | ||
77 | + <component | ||
78 | + :is="shadowComponent" | ||
79 | + ref="createComponentEl" | ||
80 | + :key="getComponentKey" | ||
81 | + :config="nodeData" | ||
82 | + @vue:mounted="handleOnMounted" | ||
83 | + /> | ||
84 | + <BasicForm @register="bottomFormRegister" /> | ||
85 | + </section> | ||
86 | + <Empty v-if="!shadowComponent" description="未找到创建组件" /> | ||
87 | + </Spin> | ||
88 | + </BasicModal> | ||
89 | +</template> |
1 | +<script lang="ts" setup> | ||
2 | + import { Collapse, Tooltip } from 'ant-design-vue'; | ||
3 | + import { computed, ref } from 'vue'; | ||
4 | + import { allComponents } from '../../../packages'; | ||
5 | + import { RuleNodeTypeEnum } from '../../../packages/index.type'; | ||
6 | + import ItemPanel from './ItemPanel.vue'; | ||
7 | + import { Icon } from '/@/components/Icon'; | ||
8 | + | ||
9 | + const props = defineProps<{ | ||
10 | + searchText?: string; | ||
11 | + }>(); | ||
12 | + | ||
13 | + const activeKey = ref(Object.values(RuleNodeTypeEnum)); | ||
14 | + | ||
15 | + const getCategory = computed(() => { | ||
16 | + const { searchText } = props; | ||
17 | + let components = Object.values(allComponents); | ||
18 | + searchText && | ||
19 | + (components = components.map((item) => ({ | ||
20 | + ...item, | ||
21 | + components: item.components.filter((temp) => temp.name.includes(searchText)), | ||
22 | + }))); | ||
23 | + return components; | ||
24 | + }); | ||
25 | +</script> | ||
26 | + | ||
27 | +<template> | ||
28 | + <Collapse v-model:activeKey="activeKey" class="rule-chain-sidebar"> | ||
29 | + <Collapse.Panel v-for="item in getCategory" :key="item.category.category" :show-arrow="false"> | ||
30 | + <template #header> | ||
31 | + <Tooltip placement="right" color="#fff"> | ||
32 | + <template #title> | ||
33 | + <p class="text-dark-900 font-bold text-xs m-0 py-1"> | ||
34 | + {{ item.category.title }} | ||
35 | + </p> | ||
36 | + <p class="text-gray-500 text-xs m-0 py-1"> | ||
37 | + {{ item.category.description }} | ||
38 | + </p> | ||
39 | + </template> | ||
40 | + <div class="flex items-center"> | ||
41 | + <Icon class="text-2xl" icon="tabler:circuit-ground" /> | ||
42 | + <div class="flex-1 ml-2 select-none"> | ||
43 | + {{ item.category.title }} | ||
44 | + </div> | ||
45 | + <Icon | ||
46 | + class="text-2xl transform transition-transform" | ||
47 | + icon="ep:arrow-down" | ||
48 | + :class="activeKey.includes(item.category.category!) ? '' : 'rotate-180'" | ||
49 | + /> | ||
50 | + </div> | ||
51 | + </Tooltip> | ||
52 | + </template> | ||
53 | + <ItemPanel :components="item.components" :category="item.category" /> | ||
54 | + </Collapse.Panel> | ||
55 | + </Collapse> | ||
56 | +</template> | ||
57 | + | ||
58 | +<style scoped> | ||
59 | + .rule-chain-sidebar { | ||
60 | + background-color: transparent !important; | ||
61 | + } | ||
62 | + | ||
63 | + .rule-chain-sidebar:deep(.ant-collapse-content-box) { | ||
64 | + padding: 0; | ||
65 | + } | ||
66 | + | ||
67 | + .rule-chain-sidebar:deep(.ant-collapse-header) { | ||
68 | + background-color: #e6e6e6 !important; | ||
69 | + } | ||
70 | + | ||
71 | + .rule-chain-sidebar:deep(.ant-collapse-content) { | ||
72 | + background-color: transparent; | ||
73 | + } | ||
74 | +</style> |
1 | +<script lang="ts" setup> | ||
2 | + import type { CategoryConfigType, NodeItemConfigType } from '../../../types/node'; | ||
3 | + import SidebarNodeItem from './SidebarNodeItem.vue'; | ||
4 | + | ||
5 | + defineProps<{ | ||
6 | + category?: CategoryConfigType; | ||
7 | + components?: NodeItemConfigType[]; | ||
8 | + }>(); | ||
9 | +</script> | ||
10 | + | ||
11 | +<template> | ||
12 | + <section | ||
13 | + class="rule-designer-collapse-container w-full flex items-center flex-col gap-2 relative" | ||
14 | + :class="components?.length ? 'p-4' : 'p-0'" | ||
15 | + > | ||
16 | + <SidebarNodeItem | ||
17 | + v-for="item in components" | ||
18 | + :key="item.key" | ||
19 | + :config="item" | ||
20 | + :category="category" | ||
21 | + /> | ||
22 | + </section> | ||
23 | +</template> | ||
24 | + | ||
25 | +<style scoped> | ||
26 | + .rule-designer-collapse-container::after { | ||
27 | + position: absolute; | ||
28 | + content: ''; | ||
29 | + width: 100%; | ||
30 | + height: 100%; | ||
31 | + z-index: 1; | ||
32 | + top: 0; | ||
33 | + background-color: #fff; | ||
34 | + } | ||
35 | +</style> |
1 | +<script lang="ts" setup> | ||
2 | + import { Icon } from '/@/components/Icon'; | ||
3 | + import { computed, ref, toRaw, unref } from 'vue'; | ||
4 | + import { Tooltip } from 'ant-design-vue'; | ||
5 | + import type { NodeData } from '../../../types/node'; | ||
6 | + import { EFFECT_SYMBOL, TRANSFER_DATA_KEY } from '../../../hook/useDragCreate'; | ||
7 | + | ||
8 | + const props = defineProps<NodeData>(); | ||
9 | + | ||
10 | + const visible = ref(false); | ||
11 | + | ||
12 | + const getNodeDefinition = computed(() => { | ||
13 | + const { config } = props; | ||
14 | + const { configurationDescriptor } = config || {}; | ||
15 | + const { nodeDefinition } = configurationDescriptor || {}; | ||
16 | + return nodeDefinition || {}; | ||
17 | + }); | ||
18 | + | ||
19 | + const getIcon = computed(() => { | ||
20 | + const { icon } = unref(getNodeDefinition); | ||
21 | + const { icon: categoryIcon } = props.category || {}; | ||
22 | + return icon || categoryIcon || 'tabler:circuit-ground'; | ||
23 | + }); | ||
24 | + | ||
25 | + const getBackgroundColor = computed(() => { | ||
26 | + const { config, category } = props; | ||
27 | + const { backgroundColor } = config || {}; | ||
28 | + const { backgroundColor: categoryBackgroundColor } = category || {}; | ||
29 | + return backgroundColor || categoryBackgroundColor; | ||
30 | + }); | ||
31 | + | ||
32 | + const handleOnDragStart = (event: DragEvent, options: object) => { | ||
33 | + if (event.dataTransfer) { | ||
34 | + event.dataTransfer.setData( | ||
35 | + TRANSFER_DATA_KEY, | ||
36 | + JSON.stringify({ offsetX: event.offsetX, offsetY: event.offsetY, options }) | ||
37 | + ); | ||
38 | + event.dataTransfer.effectAllowed = EFFECT_SYMBOL; | ||
39 | + } | ||
40 | + }; | ||
41 | + | ||
42 | + const handleDragStart = (event: DragEvent) => { | ||
43 | + visible.value = false; | ||
44 | + handleOnDragStart(event, toRaw(unref(props))); | ||
45 | + }; | ||
46 | +</script> | ||
47 | + | ||
48 | +<template> | ||
49 | + <Tooltip | ||
50 | + v-model:visible="visible" | ||
51 | + placement="left" | ||
52 | + color="#fff" | ||
53 | + trigger="hover" | ||
54 | + :mouse-enter-delay="0.3" | ||
55 | + > | ||
56 | + <template #title> | ||
57 | + <section class="text-dark-900"> | ||
58 | + <h1 class="font-bold"> | ||
59 | + {{ config?.name }} | ||
60 | + </h1> | ||
61 | + <p class="italic text-gray-600"> | ||
62 | + {{ getNodeDefinition.description }} | ||
63 | + </p> | ||
64 | + <p>{{ getNodeDefinition.details }}</p> | ||
65 | + </section> | ||
66 | + </template> | ||
67 | + <main | ||
68 | + :draggable="true" | ||
69 | + class="sidebar-basic-node flex items-center cursor-pointer w-44 h-12 rounded border relative px-4 py-2 border-gray-700 z-10" | ||
70 | + :style="{ backgroundColor: getBackgroundColor }" | ||
71 | + @dragstart="handleDragStart" | ||
72 | + > | ||
73 | + <div> | ||
74 | + <Icon class="text-2xl" :icon="getIcon" /> | ||
75 | + </div> | ||
76 | + <div class="flex text-xs flex-col ml-2 text-left"> | ||
77 | + <span class="text-gray-700 w-full truncate">{{ config?.name }}</span> | ||
78 | + <!-- <span class="w-full truncate">name</span> --> | ||
79 | + </div> | ||
80 | + <div class="w-4 h-4 bg-dark-50 rounded-1 border absolute -left-2 border-light-50"></div> | ||
81 | + <div class="w-4 h-4 bg-dark-50 rounded-1 border absolute -right-2 border-light-50"></div> | ||
82 | + </main> | ||
83 | + </Tooltip> | ||
84 | +</template> | ||
85 | + | ||
86 | +<style lang="css" scoped> | ||
87 | + .sidebar-basic-node::before { | ||
88 | + content: ''; | ||
89 | + position: absolute; | ||
90 | + width: 100%; | ||
91 | + height: 100%; | ||
92 | + left: 0; | ||
93 | + display: none; | ||
94 | + background-color: #00000027; | ||
95 | + } | ||
96 | + | ||
97 | + .sidebar-basic-node:hover::before { | ||
98 | + display: block; | ||
99 | + } | ||
100 | +</style> |
1 | +export { default as Sidebar } from './index.vue'; |
1 | +<script lang="ts" setup> | ||
2 | + import { Icon } from '/@/components/Icon'; | ||
3 | + import type { CSSProperties } from 'vue'; | ||
4 | + import { computed, ref, unref } from 'vue'; | ||
5 | + import { Tooltip, Input } from 'ant-design-vue'; | ||
6 | + import CategoryPanel from './CategoryPanel.vue'; | ||
7 | + | ||
8 | + const expand = ref(true); | ||
9 | + | ||
10 | + const searchText = ref(''); | ||
11 | + | ||
12 | + const getSidebarStyle = computed<CSSProperties>(() => { | ||
13 | + return { | ||
14 | + flex: `0 0 ${unref(expand) ? '250px' : 0}`, | ||
15 | + width: unref(expand) ? '250px' : 0, | ||
16 | + }; | ||
17 | + }); | ||
18 | + | ||
19 | + const handleExpandMenu = () => { | ||
20 | + expand.value = !unref(expand); | ||
21 | + }; | ||
22 | +</script> | ||
23 | + | ||
24 | +<template> | ||
25 | + <main | ||
26 | + class="rule-designer-sidebar z-50 shadow shadow-lg shadow-dark-600 transition-all overflow-hidden relative" | ||
27 | + :style="getSidebarStyle" | ||
28 | + > | ||
29 | + <section class="bg-sky-900 text-light-50 px-2 h-12 flex items-center"> | ||
30 | + <Tooltip title="查找节点"> | ||
31 | + <div | ||
32 | + class="w-8 h-8 cursor-pointer flex justify-center items-center rounded-1 hover:bg-opacity-90" | ||
33 | + > | ||
34 | + <Icon icon="ion:search" class="text-xl" /> | ||
35 | + </div> | ||
36 | + </Tooltip> | ||
37 | + <div> | ||
38 | + <Input | ||
39 | + v-model:value="searchText" | ||
40 | + placeholder="查找节点" | ||
41 | + style="border-bottom: 1px solid #fff !important" | ||
42 | + class="!border-none !border-light-50 !outline-none !bg-transparent !focus:border-none !focus:shadow-none !text-light-50" | ||
43 | + /> | ||
44 | + </div> | ||
45 | + <Tooltip title="关闭"> | ||
46 | + <div | ||
47 | + class="w-8 h-8 cursor-pointer flex justify-center items-center rounded-1 hover:bg-opacity-90" | ||
48 | + > | ||
49 | + <Icon icon="uiw:left" class="text-2xl" @click="handleExpandMenu" /> | ||
50 | + </div> | ||
51 | + </Tooltip> | ||
52 | + </section> | ||
53 | + <CategoryPanel :search-text="searchText" /> | ||
54 | + </main> | ||
55 | + <Tooltip title="打开节点库"> | ||
56 | + <div | ||
57 | + class="absolute top-2 left-2 z-20 bg-sky-800 w-10 h-10 cursor-pointer flex justify-center items-center rounded-1 opacity-50" | ||
58 | + @click="handleExpandMenu" | ||
59 | + > | ||
60 | + <Icon icon="ion:menu-sharp" class="!text-2xl !text-light-50" /> | ||
61 | + </div> | ||
62 | + </Tooltip> | ||
63 | +</template> | ||
64 | + | ||
65 | +<style scoped> | ||
66 | + .rule-designer-sidebar::after { | ||
67 | + content: ''; | ||
68 | + position: absolute; | ||
69 | + width: 100%; | ||
70 | + height: 100%; | ||
71 | + background: #fff; | ||
72 | + } | ||
73 | +</style> |
1 | +export { default as UpdateEdgeDrawer } from './index.vue'; |
1 | +<script lang="ts" setup> | ||
2 | + import { BasicDrawer } from '/@/components/Drawer'; | ||
3 | + import { Spin, Empty } from 'ant-design-vue'; | ||
4 | + import { useAwaitPopupWindowBindData } from '../../../hook/useAwaitPopupWindowBindData'; | ||
5 | + import { ElementsTypeEnum } from '../../../enum'; | ||
6 | + import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
7 | + | ||
8 | + const { | ||
9 | + visible, | ||
10 | + spinning, | ||
11 | + nodeData, | ||
12 | + createComponentEl, | ||
13 | + shadowComponent, | ||
14 | + getComponentKey, | ||
15 | + open, | ||
16 | + handleCancel, | ||
17 | + handleSubmit, | ||
18 | + handleOnMounted, | ||
19 | + } = useAwaitPopupWindowBindData({ mode: DataActionModeEnum.UPDATE, type: ElementsTypeEnum.EDGE }); | ||
20 | + | ||
21 | + defineExpose({ | ||
22 | + open, | ||
23 | + }); | ||
24 | +</script> | ||
25 | + | ||
26 | +<template> | ||
27 | + <BasicDrawer | ||
28 | + v-model:visible="visible" | ||
29 | + width="700px" | ||
30 | + showFooter | ||
31 | + showOkBtn | ||
32 | + showCancelBtn | ||
33 | + @ok="handleSubmit" | ||
34 | + @close="handleCancel" | ||
35 | + > | ||
36 | + <template #title> | ||
37 | + <h2 class="font-bold text-2xl truncate">{{ nodeData?.data?.name }}</h2> | ||
38 | + <p class="mb-0 text-gray-700">规则节点详情</p> | ||
39 | + </template> | ||
40 | + <Spin :spinning="spinning"> | ||
41 | + <component | ||
42 | + :is="shadowComponent" | ||
43 | + v-if="shadowComponent" | ||
44 | + ref="createComponentEl" | ||
45 | + :config="nodeData" | ||
46 | + :key="getComponentKey" | ||
47 | + @vue:mounted="handleOnMounted" | ||
48 | + /> | ||
49 | + <Empty v-if="!shadowComponent" description="未找到链接组件" /> | ||
50 | + </Spin> | ||
51 | + </BasicDrawer> | ||
52 | +</template> |
1 | +<script lang="ts" setup> | ||
2 | + import { NodeData } from '../../../types/node'; | ||
3 | + | ||
4 | + defineProps<{ nodeData?: NodeData }>(); | ||
5 | +</script> | ||
6 | + | ||
7 | +<template> | ||
8 | + <section> | ||
9 | + <h1 class="font-bold text-lg">{{ nodeData?.config?.name }}</h1> | ||
10 | + <p class="text-gray-600 italic"> | ||
11 | + {{ nodeData?.config?.configurationDescriptor.nodeDefinition.description }}</p | ||
12 | + > | ||
13 | + <p>{{ nodeData?.config?.configurationDescriptor.nodeDefinition.details }}</p> | ||
14 | + </section> | ||
15 | +</template> |
1 | +export { default as UpdateNodeDrawer } from './index.vue'; |
1 | +<script lang="ts" setup> | ||
2 | + import { BasicDrawer } from '/@/components/Drawer'; | ||
3 | + import { Tabs, Spin, Empty } from 'ant-design-vue'; | ||
4 | + import { TabsPanelEnum, TabsPanelNameEnum } from './config'; | ||
5 | + import { useAwaitPopupWindowBindData } from '../../../hook/useAwaitPopupWindowBindData'; | ||
6 | + import HelpMessage from './HelpMessage.vue'; | ||
7 | + import EventTotal from './EventTotal.vue'; | ||
8 | + import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
9 | + import { ElementsTypeEnum } from '../../../enum'; | ||
10 | + import { useForm, BasicForm } from '/@/components/Form'; | ||
11 | + import { BottomFormSchemas, TopFormSchemas } from '../CreateNodeModal/config'; | ||
12 | + import { toRaw, unref } from 'vue-demi'; | ||
13 | + | ||
14 | + const [topFormRegister, topFormActionType] = useForm({ | ||
15 | + schemas: TopFormSchemas, | ||
16 | + showActionButtonGroup: false, | ||
17 | + }); | ||
18 | + | ||
19 | + const [bottomFormRegister, bottomFormActionType] = useForm({ | ||
20 | + schemas: BottomFormSchemas, | ||
21 | + showActionButtonGroup: false, | ||
22 | + }); | ||
23 | + | ||
24 | + const { | ||
25 | + visible, | ||
26 | + spinning, | ||
27 | + nodeData, | ||
28 | + getComponentKey, | ||
29 | + shadowComponent, | ||
30 | + createComponentEl, | ||
31 | + open, | ||
32 | + handleCancel, | ||
33 | + handleSubmit, | ||
34 | + handleOnMounted, | ||
35 | + } = useAwaitPopupWindowBindData({ mode: DataActionModeEnum.UPDATE, type: ElementsTypeEnum.NODE }); | ||
36 | + | ||
37 | + const handleTopFormMounted = () => { | ||
38 | + const data = unref(nodeData)?.data || {}; | ||
39 | + topFormActionType.setFieldsValue(toRaw(unref(data))); | ||
40 | + }; | ||
41 | + | ||
42 | + const handleBottomFormMounted = () => { | ||
43 | + const data = unref(nodeData)?.data || {}; | ||
44 | + bottomFormActionType.setFieldsValue(toRaw(unref(data))); | ||
45 | + }; | ||
46 | + | ||
47 | + const validate = async () => { | ||
48 | + await topFormActionType.validate(); | ||
49 | + await bottomFormActionType.validate(); | ||
50 | + }; | ||
51 | + | ||
52 | + const getFieldsValue = () => { | ||
53 | + const topValue = topFormActionType.getFieldsValue() || {}; | ||
54 | + const bottomValue = bottomFormActionType.getFieldsValue() || {}; | ||
55 | + return { | ||
56 | + ...topValue, | ||
57 | + ...bottomValue, | ||
58 | + }; | ||
59 | + }; | ||
60 | + | ||
61 | + const resetFieldsValue = () => { | ||
62 | + topFormActionType.resetFields(); | ||
63 | + bottomFormActionType.resetFields(); | ||
64 | + }; | ||
65 | + | ||
66 | + const handleModalOk = async () => { | ||
67 | + await validate(); | ||
68 | + const data = getFieldsValue(); | ||
69 | + await handleSubmit(data); | ||
70 | + resetFieldsValue(); | ||
71 | + }; | ||
72 | + | ||
73 | + defineExpose({ | ||
74 | + open, | ||
75 | + }); | ||
76 | +</script> | ||
77 | + | ||
78 | +<template> | ||
79 | + <BasicDrawer | ||
80 | + v-model:visible="visible" | ||
81 | + width="700px" | ||
82 | + showFooter | ||
83 | + showCancelBtn | ||
84 | + showOkBtn | ||
85 | + @ok="handleModalOk" | ||
86 | + @close="handleCancel" | ||
87 | + > | ||
88 | + <template #title> | ||
89 | + <h2 class="font-bold text-2xl truncate">{{ nodeData?.data?.name }}</h2> | ||
90 | + <p class="mb-0 text-gray-700"> | ||
91 | + <span> {{ nodeData?.category?.title }}</span> | ||
92 | + <span class="mx-1">-</span> | ||
93 | + <span>{{ nodeData?.config?.name }}</span> | ||
94 | + </p> | ||
95 | + </template> | ||
96 | + <Tabs> | ||
97 | + <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.DETAIL]" :key="TabsPanelEnum.DETAIL"> | ||
98 | + <Spin :spinning="spinning"> | ||
99 | + <section v-if="shadowComponent" class="w-full h-full"> | ||
100 | + <BasicForm @register="topFormRegister" @vue:mounted="handleTopFormMounted" /> | ||
101 | + <component | ||
102 | + :is="shadowComponent" | ||
103 | + ref="createComponentEl" | ||
104 | + :config="nodeData" | ||
105 | + :key="getComponentKey" | ||
106 | + @vue:mounted="handleOnMounted" | ||
107 | + /> | ||
108 | + <BasicForm @register="bottomFormRegister" @vue:mounted="handleBottomFormMounted" /> | ||
109 | + </section> | ||
110 | + <Empty v-if="!shadowComponent" description="未找到链接组件" /> | ||
111 | + </Spin> | ||
112 | + </Tabs.TabPane> | ||
113 | + <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.EVENT]" :key="TabsPanelEnum.EVENT"> | ||
114 | + <EventTotal /> | ||
115 | + </Tabs.TabPane> | ||
116 | + <Tabs.TabPane :tab="TabsPanelNameEnum[TabsPanelEnum.HELP]" :key="TabsPanelEnum.HELP"> | ||
117 | + <HelpMessage :nodeData="nodeData" /> | ||
118 | + </Tabs.TabPane> | ||
119 | + </Tabs> | ||
120 | + </BasicDrawer> | ||
121 | +</template> |
src/views/rule/designer/style/index.ts
0 → 100644
src/views/rule/designer/style/override.css
0 → 100644
1 | +.vue-flow__handle { | ||
2 | + width: 16px; | ||
3 | + height: 16px; | ||
4 | +} | ||
5 | + | ||
6 | +.vue-flow__handle-right { | ||
7 | + right: -8px; | ||
8 | +} | ||
9 | + | ||
10 | +.vue-flow__handle-left { | ||
11 | + left: -8px; | ||
12 | +} | ||
13 | + | ||
14 | +.vue-flow__background.vue-flow__container { | ||
15 | + background-color: #e9e9e9; | ||
16 | +} | ||
17 | + | ||
18 | +.vue-flow__edge-path { | ||
19 | + stroke-width: 4; | ||
20 | + stroke: gray; | ||
21 | + transition: stroke-width 0.3s; | ||
22 | +} | ||
23 | + | ||
24 | +.connection-focus { | ||
25 | + stroke-width: 6; | ||
26 | +} | ||
27 | + | ||
28 | +/* 连接线选中时 颜色改变 */ | ||
29 | +.vue-flow__edge.selected > .vue-flow__edge-path { | ||
30 | + stroke: red !important; | ||
31 | +} | ||
32 | + | ||
33 | +/* 连接线移入放大效果 */ | ||
34 | +.vue-flow__edge:hover > .vue-flow__edge-path { | ||
35 | + stroke-width: 6 !important; | ||
36 | +} | ||
37 | + | ||
38 | +.vue-flow__edge-textbg, | ||
39 | +.vue-flow__edge-text { | ||
40 | + transition: transform 0.2s; | ||
41 | +} | ||
42 | + | ||
43 | +.vue-flow__edge:hover .vue-flow__edge-textbg { | ||
44 | + transform: scale(1.1) | ||
45 | +} | ||
46 | + | ||
47 | +.vue-flow__edge:hover .vue-flow__edge-text{ | ||
48 | + transform: scale(1.1) | ||
49 | +} | ||
50 | + | ||
51 | +/* selection 选择框 */ | ||
52 | +.vue-flow__nodesselection-rect, .vue-flow__selection{ | ||
53 | + border-width: 3px; | ||
54 | +} |
src/views/rule/designer/types/flow.ts
0 → 100644
src/views/rule/designer/types/index.ts
0 → 100644
1 | +import type { RuleError } from 'ant-design-vue/lib/form/interface'; | ||
2 | +import { DefaultEdgeConfig, DefaultNodeConfig, NodeData } from './node'; | ||
3 | + | ||
4 | +export interface AwaitPopupWindowReturnDataType<T = any> { | ||
5 | + flag: boolean; | ||
6 | + data: Nullable<T>; | ||
7 | +} | ||
8 | + | ||
9 | +export interface BasicSetFieldValueType<D = DefaultNodeConfig, C = Recordable> { | ||
10 | + defaultConfig: D; | ||
11 | + config: C; | ||
12 | +} | ||
13 | + | ||
14 | +export interface CreateModalDefineExposeType { | ||
15 | + validate?: () => Promise<{ flag: boolean; error?: RuleError }>; | ||
16 | + getFieldsValue?: () => Promise<Recordable>; | ||
17 | + setFieldsValue?: (value?: DefaultNodeConfig, nodeData?: NodeData) => void; | ||
18 | +} | ||
19 | + | ||
20 | +export interface CreateModalDefineEmitType { | ||
21 | + (eventName: 'mounted'): void; | ||
22 | +} | ||
23 | + | ||
24 | +export interface ConnectionModalReturnDataType<T = string | string[]> { | ||
25 | + flag: boolean; | ||
26 | + data: Nullable<T>; | ||
27 | +} | ||
28 | + | ||
29 | +export interface ConnectionModalDefineExposeType { | ||
30 | + validate?: () => Promise<{ flag: boolean; error?: RuleError }>; | ||
31 | + getFieldsValue?: () => Promise<Recordable>; | ||
32 | + setFieldsValue?: (value: DefaultEdgeConfig, nodeData?: NodeData) => void; | ||
33 | +} |
src/views/rule/designer/types/node.ts
0 → 100644
1 | +import type { ValidConnectionFunc } from '@vue-flow/core'; | ||
2 | +import type { RuleNodeTypeEnum } from '../packages/index.type'; | ||
3 | + | ||
4 | +export interface NodeItemConfigType { | ||
5 | + /** | ||
6 | + * @description 组件key | ||
7 | + */ | ||
8 | + key: string; | ||
9 | + | ||
10 | + /** | ||
11 | + * @description 创建组件 | ||
12 | + */ | ||
13 | + createComponentKey?: string; | ||
14 | + | ||
15 | + /** | ||
16 | + * @description 连接组件 | ||
17 | + */ | ||
18 | + connectionComponentKey?: string; | ||
19 | + | ||
20 | + /** | ||
21 | + * @description 背景颜色 | ||
22 | + */ | ||
23 | + backgroundColor?: string; | ||
24 | + | ||
25 | + /** | ||
26 | + * @description 连接校验 | ||
27 | + */ | ||
28 | + validateConnection?: ValidConnectionFunc; | ||
29 | + | ||
30 | + /** | ||
31 | + * @description 类别 | ||
32 | + */ | ||
33 | + categoryType: string; | ||
34 | + | ||
35 | + type: string; | ||
36 | + | ||
37 | + /** | ||
38 | + * @description 归属 | ||
39 | + */ | ||
40 | + scope?: string; | ||
41 | + | ||
42 | + /** | ||
43 | + * @description 节点名称 | ||
44 | + */ | ||
45 | + name: string; | ||
46 | + | ||
47 | + clazz: string; | ||
48 | + | ||
49 | + /** | ||
50 | + * @description 最大连接点 | ||
51 | + */ | ||
52 | + maxConnectionPoint?: number; | ||
53 | + | ||
54 | + /** | ||
55 | + * @description 配置描述 | ||
56 | + */ | ||
57 | + configurationDescriptor: ConfigurationDescriptor; | ||
58 | +} | ||
59 | + | ||
60 | +export interface CategoryConfigType { | ||
61 | + /** | ||
62 | + * @description 类别 | ||
63 | + */ | ||
64 | + category?: RuleNodeTypeEnum; | ||
65 | + | ||
66 | + /** | ||
67 | + * @description 背景颜色 | ||
68 | + */ | ||
69 | + backgroundColor?: string; | ||
70 | + | ||
71 | + /** | ||
72 | + * @description icon | ||
73 | + */ | ||
74 | + icon?: string; | ||
75 | + | ||
76 | + /** | ||
77 | + * @description 标题 | ||
78 | + */ | ||
79 | + title?: string; | ||
80 | + | ||
81 | + /** | ||
82 | + * @description 连接校验 | ||
83 | + */ | ||
84 | + validateConnection?: ValidConnectionFunc; | ||
85 | + | ||
86 | + /** | ||
87 | + * @description 描述 | ||
88 | + */ | ||
89 | + description?: string; | ||
90 | +} | ||
91 | + | ||
92 | +export interface PublicNodeItemType { | ||
93 | + id?: string; | ||
94 | +} | ||
95 | + | ||
96 | +export class PublicNodeItemClass implements PublicNodeItemType { | ||
97 | + constructor() {} | ||
98 | +} | ||
99 | + | ||
100 | +export interface CreateComponentType { | ||
101 | + config: NodeItemConfigType; | ||
102 | + categoryConfig: CategoryConfigType; | ||
103 | +} | ||
104 | + | ||
105 | +export interface ConfigurationDescriptor { | ||
106 | + nodeDefinition: NodeDefinition; | ||
107 | +} | ||
108 | + | ||
109 | +export interface NodeDefinition<T = object> { | ||
110 | + details?: string; | ||
111 | + description?: string; | ||
112 | + inEnabled?: boolean; | ||
113 | + outEnabled?: boolean; | ||
114 | + relationTypes?: string[]; | ||
115 | + customRelations?: boolean; | ||
116 | + ruleChainNode?: boolean; | ||
117 | + defaultConfiguration?: T; | ||
118 | + uiResources?: string[]; | ||
119 | + configDirective?: string; | ||
120 | + icon?: string; | ||
121 | + iconUrl?: string; | ||
122 | + docUrl?: string; | ||
123 | +} | ||
124 | + | ||
125 | +export interface DragTransferData { | ||
126 | + options: NodeData; | ||
127 | + offsetX: number; | ||
128 | + offsetY: number; | ||
129 | +} | ||
130 | + | ||
131 | +export interface NodeData<T = BasicNodeFormData> { | ||
132 | + category?: CategoryConfigType; | ||
133 | + config?: NodeItemConfigType; | ||
134 | + data?: T; | ||
135 | +} | ||
136 | + | ||
137 | +export interface EdgeData { | ||
138 | + config: DefaultEdgeConfig; | ||
139 | + data?: BasicEdgeBindData; | ||
140 | +} | ||
141 | + | ||
142 | +export interface BasicNodeBindData { | ||
143 | + id?: { entityType: string; id: string }; | ||
144 | + ruleChainId?: { entityType: string; id: string }; | ||
145 | + additionalInfo?: AdditionalInfo; | ||
146 | + type?: string; | ||
147 | + name?: string; | ||
148 | + debugMode?: boolean; | ||
149 | + configuration?: Configuration; | ||
150 | +} | ||
151 | + | ||
152 | +export interface AdditionalInfo { | ||
153 | + description?: string; | ||
154 | + layoutX: number; | ||
155 | + layoutY: number; | ||
156 | +} | ||
157 | + | ||
158 | +export interface Configuration { | ||
159 | + alarmStatusList?: string[]; | ||
160 | + checkForSingleEntity?: boolean; | ||
161 | + direction?: string; | ||
162 | + entityId?: string; | ||
163 | + entityType?: string; | ||
164 | + relationType?: string; | ||
165 | +} | ||
166 | + | ||
167 | +export interface DefaultNodeConfig { | ||
168 | + alarmStatusList?: string[]; | ||
169 | +} | ||
170 | + | ||
171 | +export interface DefaultEdgeConfig { | ||
172 | + type?: string[]; | ||
173 | +} | ||
174 | + | ||
175 | +export interface BasicEdgeBindData { | ||
176 | + type?: string[]; | ||
177 | +} | ||
178 | + | ||
179 | +export interface BasicNodeFormData { | ||
180 | + configuration?: Recordable; | ||
181 | + description?: string; | ||
182 | + name?: string; | ||
183 | + debugMode?: boolean; | ||
184 | +} |
src/views/rule/designer/types/ruleNode.ts
0 → 100644
1 | +import { BasicNodeBindData } from './node'; | ||
2 | + | ||
3 | +export interface RuleChainType { | ||
4 | + ruleChainId: Id; | ||
5 | + firstNodeIndex?: number; | ||
6 | + nodes: BasicNodeBindData[]; | ||
7 | + connections: ConnectionItemType[]; | ||
8 | + ruleChainConnections?: any; | ||
9 | +} | ||
10 | + | ||
11 | +export interface Id { | ||
12 | + entityType: string; | ||
13 | + id: string; | ||
14 | +} | ||
15 | + | ||
16 | +export interface ConnectionItemType { | ||
17 | + fromIndex: number; | ||
18 | + toIndex: number; | ||
19 | + type: string; | ||
20 | +} |
src/views/rule/designer/utils/index.ts
0 → 100644
1 | +import { FormProps } from '/@/components/Form'; | ||
2 | + | ||
3 | +export const isOutputHandle = (string: string) => { | ||
4 | + const reg = /.*handle-right/; | ||
5 | + return reg.test(string); | ||
6 | +}; | ||
7 | + | ||
8 | +export const isInputHandle = (string: string) => { | ||
9 | + const reg = /.*handle-left/; | ||
10 | + return reg.test(string); | ||
11 | +}; | ||
12 | + | ||
13 | +export const basicNodeFormConfiguration = (): FormProps => { | ||
14 | + return { | ||
15 | + layout: 'vertical', | ||
16 | + showActionButtonGroup: false, | ||
17 | + }; | ||
18 | +}; |