Commit 5a1f1ae031dc48bb6a8b2d912c5452b5a42206d9

Authored by xp.Huang
2 parents 7f7e88ae 84825d99

Merge branch 'feat/rule-chain' into 'main_dev'

Feat/rule chain

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