Showing
69 changed files
with
2878 additions
and
18 deletions
... | ... | @@ -5,14 +5,14 @@ |
5 | 5 | "public/resource/tinymce/langs" |
6 | 6 | ], |
7 | 7 | "cSpell.words": [ |
8 | + "clazz", | |
8 | 9 | "Cmds", |
9 | 10 | "COAP", |
10 | 11 | "echarts", |
11 | 12 | "edrx", |
12 | - "EFENTO", | |
13 | + "EFENTO", | |
13 | 14 | "fingerprintjs", |
14 | 15 | "flvjs", |
15 | - "flvjs", | |
16 | 16 | "inited", |
17 | 17 | "liveui", |
18 | 18 | "MQTT", |
... | ... | @@ -21,6 +21,7 @@ |
21 | 21 | "rtsp", |
22 | 22 | "SCADA", |
23 | 23 | "SNMP", |
24 | + "UNACK", | |
24 | 25 | "unref", |
25 | 26 | "vben", |
26 | 27 | "videojs", | ... | ... |
... | ... | @@ -39,6 +39,10 @@ |
39 | 39 | "@iconify/iconify": "^2.0.3", |
40 | 40 | "@logicflow/core": "^0.6.9", |
41 | 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 | 46 | "@vueuse/core": "^10.1.0", |
43 | 47 | "@zxcvbn-ts/core": "^1.0.0-beta.0", |
44 | 48 | "ace-builds": "^1.4.14", |
... | ... | @@ -68,7 +72,7 @@ |
68 | 72 | "vditor": "^3.8.6", |
69 | 73 | "video.js": "^7.20.3", |
70 | 74 | "videojs-flvjs-es6": "^1.0.1", |
71 | - "vue": "3.2.31", | |
75 | + "vue": "3.3.4", | |
72 | 76 | "vue-i18n": "9.1.7", |
73 | 77 | "vue-json-pretty": "^2.0.4", |
74 | 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 | 3 | import { BasicColumn } from '/@/components/Table'; |
4 | 4 | import moment from 'moment'; |
5 | 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 | 8 | export const alarmSearchSchemas: FormSchema[] = [ |
22 | 9 | { | ... | ... |
1 | -import { AlarmStatus, AlarmStatusMean } from '../config/detail.config'; | |
2 | 1 | import { clearOrAckAlarm, getDeviceAlarm } from '/@/api/device/deviceManager'; |
3 | 2 | import { notification, Button, Tag } from 'ant-design-vue'; |
4 | 3 | import { h, onMounted, onUnmounted } from 'vue'; |
... | ... | @@ -8,6 +7,7 @@ import { RoleEnum } from '/@/enums/roleEnum'; |
8 | 7 | import { usePermission } from '/@/hooks/web/usePermission'; |
9 | 8 | import { useUserStore } from '/@/store/modules/user'; |
10 | 9 | import { useGlobSetting } from '/@/hooks/setting'; |
10 | +import { AlarmStatus, AlarmStatusMean } from '/@/enums/alarmEnum'; | |
11 | 11 | |
12 | 12 | interface UseAlarmNotifyParams { |
13 | 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 | +}; | ... | ... |