This commit is contained in:
dengqichen 2025-10-20 13:53:13 +08:00
parent 91db34fbb1
commit 6dc5a00d34
17 changed files with 835 additions and 258 deletions

View File

@ -27,8 +27,6 @@
"@formily/core": "^2.3.2", "@formily/core": "^2.3.2",
"@formily/react": "^2.3.2", "@formily/react": "^2.3.2",
"@hookform/resolvers": "^3.9.1", "@hookform/resolvers": "^3.9.1",
"@logicflow/core": "^2.0.9",
"@logicflow/extension": "^2.0.13",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-avatar": "^1.1.2",

View File

@ -59,12 +59,6 @@ importers:
'@hookform/resolvers': '@hookform/resolvers':
specifier: ^3.9.1 specifier: ^3.9.1
version: 3.9.1(react-hook-form@7.54.2(react@18.3.1)) version: 3.9.1(react-hook-form@7.54.2(react@18.3.1))
'@logicflow/core':
specifier: ^2.0.9
version: 2.0.9
'@logicflow/extension':
specifier: ^2.0.13
version: 2.0.13(@logicflow/core@2.0.9)
'@monaco-editor/react': '@monaco-editor/react':
specifier: ^4.6.0 specifier: ^4.6.0
version: 4.6.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 4.6.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -393,9 +387,6 @@ packages:
'@antv/graphlib@2.0.4': '@antv/graphlib@2.0.4':
resolution: {integrity: sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==} resolution: {integrity: sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==}
'@antv/hierarchy@0.6.14':
resolution: {integrity: sha512-V3uknf7bhynOqQDw2sg+9r9DwZ9pc6k/EcqyTFdfXB1+ydr7urisP0MipIuimucvQKN+Qkd+d6w601r1UIroqQ==}
'@antv/layout@1.2.14-beta.8': '@antv/layout@1.2.14-beta.8':
resolution: {integrity: sha512-/zP8pRz28ahYSVk4fsfhl3T1X3FvCAi7Q3YQ8H2LZEvTojYYrJtO2AXFsojehU6HGVnIAq+QIjd3He6ot8+gvA==} resolution: {integrity: sha512-/zP8pRz28ahYSVk4fsfhl3T1X3FvCAi7Q3YQ8H2LZEvTojYYrJtO2AXFsojehU6HGVnIAq+QIjd3He6ot8+gvA==}
@ -904,14 +895,6 @@ packages:
'@juggle/resize-observer@3.4.0': '@juggle/resize-observer@3.4.0':
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
'@logicflow/core@2.0.9':
resolution: {integrity: sha512-HRc3W+XbcXbq9E3wElFNkaxNBja7Ga+FK6LYbsoOGWDWzZ7gSBCaYTpKPUlQsfUK0EvztqzCuer2DRJQQ77Ylg==}
'@logicflow/extension@2.0.13':
resolution: {integrity: sha512-1csZP2RYyGItvOMxVSpzrP7MguPrMRQYV+PUbm+8jZLrFbt3LcOQCNdHsEFU0cfuaiBF6Nx4qBdBeo0kog0eCw==}
peerDependencies:
'@logicflow/core': 2.0.9
'@monaco-editor/loader@1.4.0': '@monaco-editor/loader@1.4.0':
resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==}
peerDependencies: peerDependencies:
@ -1789,9 +1772,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@sphinxxxx/color-conversion@2.2.2':
resolution: {integrity: sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==}
'@types/babel__core@7.20.5': '@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@ -2641,9 +2621,6 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
hoist-non-react-statics@2.5.5:
resolution: {integrity: sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==}
hoist-non-react-statics@3.3.2: hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
@ -2843,9 +2820,6 @@ packages:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'} engines: {node: '>=6'}
medium-editor@5.23.3:
resolution: {integrity: sha512-he9/TdjX8f8MGdXGfCs8AllrYnqXJJvjNkDKmPg3aPW/uoIrlRqtkFthrwvmd+u4QyzEiadhCCM0EwTiRdUCJw==}
memoize-one@6.0.0: memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
@ -2900,20 +2874,6 @@ packages:
ml-matrix@6.12.0: ml-matrix@6.12.0:
resolution: {integrity: sha512-AGfR+pWaC0GmzjUnB6BfwhndPEUGz0i7QUYdqNuw1zhTov/vSRJ9pP2hs6BoGpaSbtXgrKjZz2zjD1M0xuur6A==} resolution: {integrity: sha512-AGfR+pWaC0GmzjUnB6BfwhndPEUGz0i7QUYdqNuw1zhTov/vSRJ9pP2hs6BoGpaSbtXgrKjZz2zjD1M0xuur6A==}
mobx-preact@3.0.0:
resolution: {integrity: sha512-ijan/cBs3WmRye87E5+3JmoFBB00KDAwNA3pm7bMwYLPHBAXlN86aC3gdrXw8aKzM5RI8V3a993PphzPv6P4FA==}
peerDependencies:
mobx: 5.x
preact: '>=8'
mobx-utils@5.6.2:
resolution: {integrity: sha512-a/WlXyGkp6F12b01sTarENpxbmlRgPHFyR1Xv2bsSjQBm5dcOtd16ONb40/vOqck8L99NHpI+C9MXQ+SZ8f+yw==}
peerDependencies:
mobx: ^4.13.1 || ^5.13.1
mobx@5.15.7:
resolution: {integrity: sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==}
monaco-editor@0.52.2: monaco-editor@0.52.2:
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
@ -3093,9 +3053,6 @@ packages:
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
preact@10.25.1:
resolution: {integrity: sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og==}
prelude-ls@1.2.1: prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -3122,9 +3079,6 @@ packages:
randombytes@2.1.0: randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
rangy@1.3.2:
resolution: {integrity: sha512-fS1C4MOyk8T+ZJZdLcgrukPWxkyDXa+Hd2Kj+Zg4wIK71yrWgmjzHubzPMY1G+WD9EgGxMp3fIL0zQ1ickmSWA==}
rc-align@2.4.5: rc-align@2.4.5:
resolution: {integrity: sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==} resolution: {integrity: sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==}
@ -3863,13 +3817,6 @@ packages:
resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
vanilla-picker@2.12.3:
resolution: {integrity: sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==}
victory-vendor@36.9.2: victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
@ -4289,8 +4236,6 @@ snapshots:
dependencies: dependencies:
'@antv/event-emitter': 0.1.3 '@antv/event-emitter': 0.1.3
'@antv/hierarchy@0.6.14': {}
'@antv/layout@1.2.14-beta.8(workerize-loader@2.0.2(webpack@5.97.1))': '@antv/layout@1.2.14-beta.8(workerize-loader@2.0.2(webpack@5.97.1))':
dependencies: dependencies:
'@antv/event-emitter': 0.1.3 '@antv/event-emitter': 0.1.3
@ -4826,29 +4771,6 @@ snapshots:
'@juggle/resize-observer@3.4.0': {} '@juggle/resize-observer@3.4.0': {}
'@logicflow/core@2.0.9':
dependencies:
classnames: 2.5.1
lodash-es: 4.17.21
mobx: 5.15.7
mobx-preact: 3.0.0(mobx@5.15.7)(preact@10.25.1)
mobx-utils: 5.6.2(mobx@5.15.7)
mousetrap: 1.6.5
preact: 10.25.1
uuid: 9.0.1
'@logicflow/extension@2.0.13(@logicflow/core@2.0.9)':
dependencies:
'@antv/hierarchy': 0.6.14
'@logicflow/core': 2.0.9
classnames: 2.5.1
lodash-es: 4.17.21
medium-editor: 5.23.3
mobx: 5.15.7
preact: 10.25.1
rangy: 1.3.2
vanilla-picker: 2.12.3
'@monaco-editor/loader@1.4.0(monaco-editor@0.52.2)': '@monaco-editor/loader@1.4.0(monaco-editor@0.52.2)':
dependencies: dependencies:
monaco-editor: 0.52.2 monaco-editor: 0.52.2
@ -5670,8 +5592,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.28.1': '@rollup/rollup-win32-x64-msvc@4.28.1':
optional: true optional: true
'@sphinxxxx/color-conversion@2.2.2': {}
'@types/babel__core@7.20.5': '@types/babel__core@7.20.5':
dependencies: dependencies:
'@babel/parser': 7.26.3 '@babel/parser': 7.26.3
@ -6689,8 +6609,6 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
hoist-non-react-statics@2.5.5: {}
hoist-non-react-statics@3.3.2: hoist-non-react-statics@3.3.2:
dependencies: dependencies:
react-is: 16.13.1 react-is: 16.13.1
@ -6870,8 +6788,6 @@ snapshots:
semver: 5.7.2 semver: 5.7.2
optional: true optional: true
medium-editor@5.23.3: {}
memoize-one@6.0.0: {} memoize-one@6.0.0: {}
merge-stream@2.0.0: {} merge-stream@2.0.0: {}
@ -6925,18 +6841,6 @@ snapshots:
is-any-array: 2.0.1 is-any-array: 2.0.1
ml-array-rescale: 1.3.7 ml-array-rescale: 1.3.7
mobx-preact@3.0.0(mobx@5.15.7)(preact@10.25.1):
dependencies:
hoist-non-react-statics: 2.5.5
mobx: 5.15.7
preact: 10.25.1
mobx-utils@5.6.2(mobx@5.15.7):
dependencies:
mobx: 5.15.7
mobx@5.15.7: {}
monaco-editor@0.52.2: {} monaco-editor@0.52.2: {}
mousetrap@1.6.5: {} mousetrap@1.6.5: {}
@ -7091,8 +6995,6 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
preact@10.25.1: {}
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
prop-types@15.8.1: prop-types@15.8.1:
@ -7118,8 +7020,6 @@ snapshots:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
rangy@1.3.2: {}
rc-align@2.4.5: rc-align@2.4.5:
dependencies: dependencies:
babel-runtime: 6.26.0 babel-runtime: 6.26.0
@ -7973,12 +7873,6 @@ snapshots:
utility-types@3.11.0: {} utility-types@3.11.0: {}
uuid@9.0.1: {}
vanilla-picker@2.12.3:
dependencies:
'@sphinxxxx/color-conversion': 2.2.2
victory-vendor@36.9.2: victory-vendor@36.9.2:
dependencies: dependencies:
'@types/d3-array': 3.2.1 '@types/d3-array': 3.2.1

View File

@ -163,7 +163,6 @@ export const getCurrentUserMenus = async () => {
// version: 0, // version: 0,
// name: "X6测试", // name: "X6测试",
// path: "/x6-test", // path: "/x6-test",
// component: "/X6Test/index",
// icon: "experiment", // icon: "experiment",
// type: MenuTypeEnum.MENU, // type: MenuTypeEnum.MENU,
// parentId: 0, // parentId: 0,

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import {Tabs, TabsProps} from 'antd'; import {Tabs, TabsProps} from 'antd';
import {Cell} from '@antv/x6'; import {Cell} from '@antv/x6';
import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types"; import type {NodeDefinitionResponse} from "@/workflow/nodes/nodeService";
import { import {
Sheet, Sheet,
SheetContent, SheetContent,

View File

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import {Card, Collapse, Tooltip, message} from 'antd'; import {Card, Collapse, Tooltip, message} from 'antd';
import type {NodeDefinition, NodeCategory} from '../types'; import type {NodeCategory} from '../types';
import { import {
PlayCircleOutlined, PlayCircleOutlined,
StopOutlined, StopOutlined,
@ -13,9 +13,9 @@ import {
BranchesOutlined BranchesOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import {getNodeDefinitionList} from "@/pages/Workflow/Definition/Design/service"; import {getNodeDefinitionList} from "@/pages/Workflow/Definition/Design/service";
import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types"; import type {NodeDefinitionResponse} from "@/workflow/nodes/nodeService";
const {Panel} = Collapse; // 使用 Collapse 组件,不需要解构 Panel
// 图标映射配置 // 图标映射配置
const iconMap: Record<string, any> = { const iconMap: Record<string, any> = {
@ -24,6 +24,7 @@ const iconMap: Record<string, any> = {
'user': UserOutlined, 'user': UserOutlined,
'api': ApiOutlined, 'api': ApiOutlined,
'code': CodeOutlined, 'code': CodeOutlined,
'build': CodeOutlined, // 构建任务图标
'fork': NodeIndexOutlined, 'fork': NodeIndexOutlined,
'branches': SplitCellsOutlined, 'branches': SplitCellsOutlined,
'apartment': AppstoreOutlined 'apartment': AppstoreOutlined
@ -36,6 +37,7 @@ const typeIconMap: Record<string, any> = {
'USER_TASK': UserOutlined, 'USER_TASK': UserOutlined,
'SERVICE_TASK': ApiOutlined, 'SERVICE_TASK': ApiOutlined,
'SCRIPT_TASK': CodeOutlined, 'SCRIPT_TASK': CodeOutlined,
'DEPLOY_NODE': CodeOutlined, // 构建任务节点
'EXCLUSIVE_GATEWAY': NodeIndexOutlined, 'EXCLUSIVE_GATEWAY': NodeIndexOutlined,
'PARALLEL_GATEWAY': SplitCellsOutlined, 'PARALLEL_GATEWAY': SplitCellsOutlined,
'SUB_PROCESS': AppstoreOutlined, 'SUB_PROCESS': AppstoreOutlined,
@ -167,8 +169,10 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
<div style={{fontSize: '14px', fontWeight: 500}}>{node.description}</div> <div style={{fontSize: '14px', fontWeight: 500}}>{node.description}</div>
</div> </div>
} }
overlayStyle={tooltipStyle} styles={{
overlayInnerStyle={tooltipOverlayInnerStyle} root: tooltipStyle,
body: tooltipOverlayInnerStyle
}}
placement="right" placement="right"
arrow={false} arrow={false}
> >

View File

@ -34,7 +34,7 @@ import NodeConfigDrawer from './components/NodeConfigModal';
import {validateWorkflow} from './utils/validator'; import {validateWorkflow} from './utils/validator';
import {addNodeToGraph} from './utils/nodeUtils'; import {addNodeToGraph} from './utils/nodeUtils';
import './index.less'; import './index.less';
import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types"; import type {NodeDefinitionResponse} from "@/workflow/nodes/nodeService";
import ExpressionModal from './components/ExpressionModal'; import ExpressionModal from './components/ExpressionModal';
import {EdgeCondition} from './types'; import {EdgeCondition} from './types';
@ -651,7 +651,7 @@ const WorkflowDesign: React.FC = () => {
onClick: () => { onClick: () => {
closeMenu(); closeMenu();
setSelectedNode(cell); setSelectedNode(cell);
setSelectedNodeDefinition(nodeDefinitions.find(def => def.type === cell.getProp('type'))); setSelectedNodeDefinition(nodeDefinitions.find(def => def.nodeType === cell.getProp('nodeType')));
setConfigModalVisible(true); setConfigModalVisible(true);
} }
}, },
@ -1044,13 +1044,26 @@ const WorkflowDesign: React.FC = () => {
return; return;
} }
// 确保节点定义已加载
if (!nodeDefinitions || nodeDefinitions.length === 0) {
console.error('节点定义未加载,无法还原工作流');
return;
}
// 清空画布 // 清空画布
graphInstance.clearCells(); graphInstance.clearCells();
const nodeMap = new Map(); const nodeMap = new Map();
// 创建节点 // 创建节点
response.graph?.nodes?.forEach((existingNode: any) => { response.graph?.nodes?.forEach((existingNode: any) => {
console.log('正在还原节点:', existingNode.nodeType, existingNode);
const node = addNodeToGraph(false, graphInstance, existingNode, nodeDefinitions); const node = addNodeToGraph(false, graphInstance, existingNode, nodeDefinitions);
if (!node) {
console.error('节点创建失败:', existingNode);
return;
}
// 只设置 graph 属性 // 只设置 graph 属性
node.setProp('graph', { node.setProp('graph', {
uiVariables: existingNode.uiVariables, uiVariables: existingNode.uiVariables,

View File

@ -1,11 +1,7 @@
// 该文件已不再需要,可以删除 // 工作流设计器相关服务
import request from '@/utils/request'; // 使用新的节点定义系统不再依赖后端API
import { getNodeDefinitionList } from '@/workflow/nodes/nodeService';
const NODE_DEFINITION_URL = '/api/v1/workflow/node-definition'; // 导出节点定义获取函数
export { getNodeDefinitionList };
/**
*
*/
export const getNodeDefinitionList = () =>
request.get(`${NODE_DEFINITION_URL}/list`);

View File

@ -18,7 +18,19 @@ export const addNodeToGraph = (
position?: { x: number; y: number } position?: { x: number; y: number }
) => { ) => {
let nodeDefinition = allNodeDefinitions.find(def => def.nodeType === currentNodeDefinition.nodeType); let nodeDefinition = allNodeDefinitions.find(def => def.nodeType === currentNodeDefinition.nodeType);
let uiVariables = isNew ? nodeDefinition.uiVariables : currentNodeDefinition.uiVariables;
if (!nodeDefinition) {
console.error('找不到节点定义:', currentNodeDefinition.nodeType);
return null;
}
// 合并UI变量使用节点定义中的完整配置但保留保存数据中的位置信息
let uiVariables = isNew
? nodeDefinition.uiVariables
: {
...nodeDefinition.uiVariables, // 完整的UI配置
...currentNodeDefinition.uiVariables // 保存的位置信息(如果有的话)
};
// 根据形状类型设置正确的 shape // 根据形状类型设置正确的 shape
let shape = 'rect'; // 默认使用矩形 let shape = 'rect'; // 默认使用矩形
if (uiVariables.shape === 'circle') { if (uiVariables.shape === 'circle') {
@ -53,5 +65,13 @@ export const addNodeToGraph = (
if (nodePosition) { if (nodePosition) {
Object.assign(nodeConfig, nodePosition); Object.assign(nodeConfig, nodePosition);
} }
return graph.addNode(nodeConfig);
// 为还原的节点设置ID保持与保存数据的一致性
if (!isNew && currentNodeDefinition.id) {
nodeConfig.id = currentNodeDefinition.id;
}
const node = graph.addNode(nodeConfig);
return node;
}; };

View File

@ -1,102 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { Graph } from '@antv/x6';
import { Card } from 'antd';
import { IX6GraphRef } from './types';
const X6TestPage: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<IX6GraphRef>({ graph: null });
useEffect(() => {
if (containerRef.current) {
// 初始化画布
const graph = new Graph({
container: containerRef.current,
width: 800,
height: 600,
grid: true,
background: {
color: '#F5F5F5',
},
connecting: {
snap: true,
allowBlank: false,
allowLoop: false,
highlight: true,
},
});
// 保存graph实例
graphRef.current.graph = graph;
// 创建示例节点
const rect1 = graph.addNode({
x: 100,
y: 100,
width: 100,
height: 40,
label: '节点 1',
attrs: {
body: {
fill: '#fff',
stroke: '#1890ff',
strokeWidth: 1,
},
label: {
fill: '#000',
},
},
});
const rect2 = graph.addNode({
x: 300,
y: 100,
width: 100,
height: 40,
label: '节点 2',
attrs: {
body: {
fill: '#fff',
stroke: '#1890ff',
strokeWidth: 1,
},
label: {
fill: '#000',
},
},
});
// 创建连线
graph.addEdge({
source: rect1,
target: rect2,
attrs: {
line: {
stroke: '#1890ff',
strokeWidth: 1,
},
},
});
// 清理函数
return () => {
graph.dispose();
};
}
}, []);
return (
<Card title="X6 图形编辑器测试">
<div
ref={containerRef}
style={{
width: '100%',
height: '600px',
border: '1px solid #ddd'
}}
/>
</Card>
);
};
export default X6TestPage;

View File

@ -1,19 +0,0 @@
import { Graph } from '@antv/x6';
export interface IX6Node {
id: string;
label: string;
x: number;
y: number;
}
export interface IX6Edge {
id: string;
source: string;
target: string;
label?: string;
}
export interface IX6GraphRef {
graph: Graph | null;
}

View File

@ -30,7 +30,6 @@ const User = lazy(() => import('../pages/System/User'));
const Role = lazy(() => import('../pages/System/Role')); const Role = lazy(() => import('../pages/System/Role'));
const Menu = lazy(() => import('../pages/System/Menu')); const Menu = lazy(() => import('../pages/System/Menu'));
const Department = lazy(() => import('../pages/System/Department')); const Department = lazy(() => import('../pages/System/Department'));
const X6Test = lazy(() => import('../pages/X6Test'));
const WorkflowDefinitionList = lazy(() => import('../pages/Workflow/Definition')); const WorkflowDefinitionList = lazy(() => import('../pages/Workflow/Definition'));
const WorkflowDesign = lazy(() => import('../pages/Workflow/Definition/Design')); const WorkflowDesign = lazy(() => import('../pages/Workflow/Definition/Design'));
const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance')); const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance'));
@ -146,14 +145,6 @@ const router = createBrowserRouter([
} }
] ]
}, },
// {
// path: 'x6-test',
// element: (
// <Suspense fallback={<LoadingComponent/>}>
// <X6Test/>
// </Suspense>
// )
// },
{ {
path: 'workflow', path: 'workflow',
children: [ children: [

View File

@ -0,0 +1,144 @@
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
/**
*
*
*/
export const DeployNode: WorkflowNodeDefinition = {
nodeCode: "DEPLOY_NODE",
nodeName: "构建任务",
nodeType: NodeType.DEPLOY_NODE,
category: NodeCategory.TASK,
description: "执行应用构建和部署任务,支持多种构建工具和部署环境",
// 面板变量配置 - 用户在节点配置面板中可以设置的参数
panelVariablesSchema: {
type: "object",
title: "构建任务配置",
description: "配置构建任务的基本信息和参数",
properties: {
delegate: {
type: "string",
title: "委派者",
description: "执行构建任务的委派者标识",
readOnly: true,
default: "${deployNodeDelegate}"
},
code: {
type: "string",
title: "节点编码",
description: "工作流节点的唯一编码标识",
minLength: 2,
maxLength: 50,
pattern: "^[A-Z0-9_]+$"
},
name: {
type: "string",
title: "节点名称",
description: "工作流节点的显示名称",
minLength: 1,
maxLength: 100
},
description: {
type: "string",
title: "节点描述",
description: "详细描述该节点的功能和用途",
maxLength: 500
},
buildType: {
type: "string",
title: "构建类型",
description: "选择构建工具类型",
enum: ["MAVEN", "GRADLE", "NPM", "DOCKER"],
enumNames: ["Maven", "Gradle", "NPM", "Docker"],
default: "MAVEN"
},
timeout: {
type: "number",
title: "超时时间(分钟)",
description: "构建任务的超时时间,超过该时间将自动终止",
minimum: 1,
maximum: 180,
default: 30
}
},
required: ["code", "name"]
},
// 本地变量配置 - 节点执行过程中的内部变量
localVariablesSchema: {
type: "object",
title: "本地变量",
description: "节点执行过程中使用的内部变量",
properties: {
buildStartTime: {
type: "string",
title: "构建开始时间",
description: "记录构建任务开始的时间戳"
},
buildEndTime: {
type: "string",
title: "构建结束时间",
description: "记录构建任务结束的时间戳"
},
buildResult: {
type: "string",
title: "构建结果",
description: "构建任务的执行结果",
enum: ["SUCCESS", "FAILED", "TIMEOUT"],
enumNames: ["成功", "失败", "超时"]
},
buildLog: {
type: "string",
title: "构建日志",
description: "构建过程的详细日志信息"
}
}
},
// 表单变量配置 - 用户触发流程时需要填写的表单字段
formVariablesSchema: null,
// UI 变量配置 - 节点在画布上的显示样式
uiVariables: {
shape: 'rect',
size: {
width: 120,
height: 60
},
style: {
fill: '#1890ff',
stroke: '#0050b3',
strokeWidth: 2,
icon: 'build',
iconColor: '#ffffff'
},
ports: {
groups: {
in: {
position: 'left',
attrs: {
circle: {
r: 4,
fill: '#ffffff',
stroke: '#1890ff'
}
}
},
out: {
position: 'right',
attrs: {
circle: {
r: 4,
fill: '#ffffff',
stroke: '#1890ff'
}
}
}
}
}
},
orderNum: 10,
enabled: true
};

View File

@ -0,0 +1,101 @@
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
/**
*
*
*/
export const EndEventNode: WorkflowNodeDefinition = {
nodeCode: "END_EVENT",
nodeName: "结束",
nodeType: NodeType.END_EVENT,
category: NodeCategory.EVENT,
description: "工作流的终止节点,标识流程的结束点",
panelVariablesSchema: {
type: "object",
title: "结束事件配置",
properties: {
code: {
type: "string",
title: "节点编码",
description: "结束事件节点的编码",
default: "end"
},
name: {
type: "string",
title: "节点名称",
description: "结束事件节点的显示名称",
default: "结束"
},
description: {
type: "string",
title: "节点描述",
description: "描述该结束事件的触发条件或结果"
},
resultType: {
type: "string",
title: "结束类型",
description: "流程结束的类型",
enum: ["SUCCESS", "FAILED", "CANCELLED"],
enumNames: ["成功结束", "失败结束", "取消结束"],
default: "SUCCESS"
}
},
required: ["code", "name"]
},
localVariablesSchema: {
type: "object",
properties: {
endTime: {
type: "string",
title: "结束时间",
description: "流程实例的结束时间"
},
duration: {
type: "number",
title: "执行时长",
description: "流程实例的总执行时长(毫秒)"
},
finalResult: {
type: "string",
title: "最终结果",
description: "流程执行的最终结果"
}
}
},
formVariablesSchema: null,
uiVariables: {
shape: 'circle',
size: {
width: 50,
height: 50
},
style: {
fill: '#ff4d4f',
stroke: '#cf1322',
strokeWidth: 2,
icon: 'stop',
iconColor: '#ffffff'
},
ports: {
groups: {
in: {
position: 'left',
attrs: {
circle: {
r: 4,
fill: '#ffffff',
stroke: '#ff4d4f'
}
}
}
}
}
},
orderNum: 99,
enabled: true
};

View File

@ -0,0 +1,88 @@
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
/**
*
*
*/
export const StartEventNode: WorkflowNodeDefinition = {
nodeCode: "START_EVENT",
nodeName: "开始",
nodeType: NodeType.START_EVENT,
category: NodeCategory.EVENT,
description: "工作流的起始节点,标识流程的开始点",
panelVariablesSchema: {
type: "object",
title: "开始事件配置",
properties: {
code: {
type: "string",
title: "节点编码",
description: "开始事件节点的编码",
default: "start"
},
name: {
type: "string",
title: "节点名称",
description: "开始事件节点的显示名称",
default: "开始"
},
description: {
type: "string",
title: "节点描述",
description: "描述该开始事件的触发条件或场景"
}
},
required: ["code", "name"]
},
localVariablesSchema: {
type: "object",
properties: {
startTime: {
type: "string",
title: "开始时间",
description: "流程实例的开始时间"
},
processInstanceId: {
type: "string",
title: "流程实例ID",
description: "当前流程实例的唯一标识"
}
}
},
formVariablesSchema: null,
uiVariables: {
shape: 'circle',
size: {
width: 50,
height: 50
},
style: {
fill: '#52c41a',
stroke: '#389e0d',
strokeWidth: 2,
icon: 'play-circle',
iconColor: '#ffffff'
},
ports: {
groups: {
out: {
position: 'right',
attrs: {
circle: {
r: 4,
fill: '#ffffff',
stroke: '#52c41a'
}
}
}
}
}
},
orderNum: 1,
enabled: true
};

View File

@ -0,0 +1,159 @@
// 工作流节点定义注册中心
import { DeployNode } from './DeployNode';
import { StartEventNode } from './StartEventNode';
import { EndEventNode } from './EndEventNode';
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
/**
*
*
*/
export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
StartEventNode,
EndEventNode,
DeployNode,
// 在此处添加新的节点定义...
];
/**
*
* @param nodeType
* @returns undefined
*/
export const getNodeDefinition = (nodeType: NodeType | string): WorkflowNodeDefinition | undefined => {
return NODE_DEFINITIONS.find(node => node.nodeType === nodeType);
};
/**
*
* @param nodeCode
* @returns undefined
*/
export const getNodeDefinitionByCode = (nodeCode: string): WorkflowNodeDefinition | undefined => {
return NODE_DEFINITIONS.find(node => node.nodeCode === nodeCode);
};
/**
*
* @param category
* @returns
*/
export const getNodesByCategory = (category: NodeCategory): WorkflowNodeDefinition[] => {
return NODE_DEFINITIONS.filter(node => node.category === category);
};
/**
*
* @returns
*/
export const getEnabledNodes = (): WorkflowNodeDefinition[] => {
return NODE_DEFINITIONS.filter(node => node.enabled !== false);
};
/**
*
* @returns
*/
export const getNodesByCategories = (): Record<NodeCategory, WorkflowNodeDefinition[]> => {
const grouped = NODE_DEFINITIONS.reduce((acc, node) => {
if (!acc[node.category]) {
acc[node.category] = [];
}
acc[node.category].push(node);
return acc;
}, {} as Record<NodeCategory, WorkflowNodeDefinition[]>);
// 按 orderNum 排序
Object.keys(grouped).forEach(category => {
grouped[category as NodeCategory].sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0));
});
return grouped;
};
/**
*
* @param nodeDefinition
* @returns
*/
export const validateNodeDefinition = (nodeDefinition: WorkflowNodeDefinition): {
isValid: boolean;
errors: string[];
} => {
const errors: string[] = [];
// 基础字段验证
if (!nodeDefinition.nodeCode) {
errors.push('节点编码不能为空');
}
if (!nodeDefinition.nodeName) {
errors.push('节点名称不能为空');
}
if (!nodeDefinition.nodeType) {
errors.push('节点类型不能为空');
}
if (!nodeDefinition.category) {
errors.push('节点分类不能为空');
}
// Schema 验证
if (!nodeDefinition.panelVariablesSchema) {
errors.push('面板变量配置不能为空');
} else {
if (!nodeDefinition.panelVariablesSchema.type) {
errors.push('面板变量配置缺少类型定义');
}
if (!nodeDefinition.panelVariablesSchema.properties) {
errors.push('面板变量配置缺少属性定义');
}
}
// UI变量验证
if (!nodeDefinition.uiVariables) {
errors.push('UI变量配置不能为空');
} else {
const ui = nodeDefinition.uiVariables;
if (!ui.shape) {
errors.push('UI配置缺少形状定义');
}
if (!ui.size || !ui.size.width || !ui.size.height) {
errors.push('UI配置缺少尺寸定义');
}
if (!ui.style) {
errors.push('UI配置缺少样式定义');
}
}
return {
isValid: errors.length === 0,
errors
};
};
/**
*
* @returns
*/
export const getNodeStatistics = () => {
const total = NODE_DEFINITIONS.length;
const enabled = getEnabledNodes().length;
const byCategory = getNodesByCategories();
return {
total,
enabled,
disabled: total - enabled,
categories: Object.keys(byCategory).length,
byCategory: Object.entries(byCategory).map(([category, nodes]) => ({
category,
count: nodes.length
}))
};
};
// 导出所有节点定义(向后兼容)
export { DeployNode, StartEventNode, EndEventNode };
// 导出类型
export type { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';

View File

@ -0,0 +1,173 @@
// 节点服务适配器
// 提供与原有API兼容的接口但使用本地节点定义
import {
NODE_DEFINITIONS,
getNodeDefinition,
getNodesByCategory,
getEnabledNodes,
getNodesByCategories
} from './definitions';
import { WorkflowNodeDefinition, NodeType, NodeCategory } from './types';
// 兼容原有的响应格式
export interface NodeDefinitionResponse {
id: number;
nodeCode: string;
nodeName: string;
nodeType: NodeType;
category: NodeCategory;
description?: string;
panelVariablesSchema: any;
uiVariables: any;
localVariablesSchema?: any;
formVariablesSchema?: any;
panelVariables?: Record<string, any>;
localVariables?: Record<string, any>;
orderNum?: number;
enabled?: boolean;
createTime?: string;
updateTime?: string;
version?: number;
}
/**
*
*/
const convertToResponse = (node: WorkflowNodeDefinition, index: number): NodeDefinitionResponse => {
return {
id: index + 1, // 生成虚拟ID
nodeCode: node.nodeCode,
nodeName: node.nodeName,
nodeType: node.nodeType,
category: node.category,
description: node.description,
panelVariablesSchema: node.panelVariablesSchema,
uiVariables: node.uiVariables,
localVariablesSchema: node.localVariablesSchema,
formVariablesSchema: node.formVariablesSchema,
orderNum: node.orderNum || 0,
enabled: node.enabled !== false,
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
version: 1
};
};
/**
* API调用
* @returns Promise<NodeDefinitionResponse[]>
*/
export const getNodeDefinitionList = async (): Promise<NodeDefinitionResponse[]> => {
// 模拟异步操作
return new Promise((resolve) => {
setTimeout(() => {
const nodes = getEnabledNodes();
const response = nodes.map((node, index) => convertToResponse(node, index));
resolve(response);
}, 10); // 很短的延迟,模拟网络请求
});
};
/**
*
* @param nodeType
* @returns Promise<NodeDefinitionResponse | null>
*/
export const getNodeDefinitionByType = async (nodeType: NodeType | string): Promise<NodeDefinitionResponse | null> => {
return new Promise((resolve) => {
setTimeout(() => {
const node = getNodeDefinition(nodeType);
if (node) {
const index = NODE_DEFINITIONS.findIndex(n => n.nodeType === nodeType);
resolve(convertToResponse(node, index));
} else {
resolve(null);
}
}, 10);
});
};
/**
*
* @returns Promise<Record<NodeCategory, NodeDefinitionResponse[]>>
*/
export const getNodeDefinitionsByCategories = async (): Promise<Record<NodeCategory, NodeDefinitionResponse[]>> => {
return new Promise((resolve) => {
setTimeout(() => {
const grouped = getNodesByCategories();
const result = {} as Record<NodeCategory, NodeDefinitionResponse[]>;
Object.entries(grouped).forEach(([category, nodes]) => {
result[category as NodeCategory] = nodes.map((node) =>
convertToResponse(node, NODE_DEFINITIONS.findIndex(n => n.nodeCode === node.nodeCode))
);
});
resolve(result);
}, 10);
});
};
/**
*
* @param category
* @returns Promise<NodeDefinitionResponse[]>
*/
export const getNodeDefinitionsByCategory = async (category: NodeCategory): Promise<NodeDefinitionResponse[]> => {
return new Promise((resolve) => {
setTimeout(() => {
const nodes = getNodesByCategory(category);
const response = nodes.map((node) =>
convertToResponse(node, NODE_DEFINITIONS.findIndex(n => n.nodeCode === node.nodeCode))
);
resolve(response);
}, 10);
});
};
/**
*
* @param nodeType
* @param config
* @returns Promise<{ isValid: boolean; errors: string[] }>
*/
export const validateNodeConfig = async (
nodeType: NodeType | string,
config: Record<string, any>
): Promise<{ isValid: boolean; errors: string[] }> => {
return new Promise((resolve) => {
setTimeout(() => {
const node = getNodeDefinition(nodeType);
if (!node) {
resolve({ isValid: false, errors: ['未知的节点类型'] });
return;
}
const errors: string[] = [];
const required = node.panelVariablesSchema.required || [];
// 验证必填字段
required.forEach(field => {
if (!config[field] || config[field] === '') {
const property = node.panelVariablesSchema.properties[field];
const title = property?.title || field;
errors.push(`${title} 是必填字段`);
}
});
resolve({ isValid: errors.length === 0, errors });
}, 10);
});
};
// 导出节点定义相关工具
export {
NODE_DEFINITIONS,
getNodeDefinition,
getNodesByCategory,
getEnabledNodes,
getNodesByCategories
} from './definitions';
export type { WorkflowNodeDefinition, NodeType, NodeCategory } from './types';

View File

@ -0,0 +1,118 @@
// 工作流节点定义相关类型
// 节点类型枚举
export enum NodeType {
START_EVENT = 'START_EVENT',
END_EVENT = 'END_EVENT',
USER_TASK = 'USER_TASK',
SERVICE_TASK = 'SERVICE_TASK',
SCRIPT_TASK = 'SCRIPT_TASK',
DEPLOY_NODE = 'DEPLOY_NODE',
GATEWAY_NODE = 'GATEWAY_NODE',
SUB_PROCESS = 'SUB_PROCESS',
CALL_ACTIVITY = 'CALL_ACTIVITY'
}
// 节点分类枚举
export enum NodeCategory {
EVENT = 'EVENT',
TASK = 'TASK',
GATEWAY = 'GATEWAY',
CONTAINER = 'CONTAINER'
}
// JSON Schema 定义
export interface JsonSchemaProperty {
type: string;
title?: string;
description?: string;
default?: any;
readOnly?: boolean;
enum?: any[];
enumNames?: string[];
format?: string;
minimum?: number;
maximum?: number;
minLength?: number;
maxLength?: number;
pattern?: string;
}
export interface JsonSchema {
type: string;
properties: Record<string, JsonSchemaProperty>;
required?: string[];
title?: string;
description?: string;
}
// 节点样式配置
export interface NodeStyle {
fill: string;
stroke: string;
strokeWidth: number;
icon: string;
iconColor: string;
}
// 节点尺寸
export interface NodeSize {
width: number;
height: number;
}
// 端口配置
export interface PortConfig {
r: number;
fill: string;
stroke: string;
}
export interface PortGroup {
position: 'left' | 'right' | 'top' | 'bottom';
attrs: {
circle: PortConfig;
};
}
export interface PortGroups {
in?: PortGroup;
out?: PortGroup;
}
// UI 变量配置
export interface UIVariables {
shape: 'rect' | 'circle' | 'diamond';
size: NodeSize;
style: NodeStyle;
ports: {
groups: PortGroups;
};
}
// 工作流节点定义接口
export interface WorkflowNodeDefinition {
nodeCode: string;
nodeName: string;
nodeType: NodeType;
category: NodeCategory;
description?: string;
panelVariablesSchema: JsonSchema;
localVariablesSchema?: JsonSchema;
formVariablesSchema?: JsonSchema | null;
uiVariables: UIVariables;
orderNum?: number;
enabled?: boolean;
}
// 节点实例数据(运行时)
export interface WorkflowNodeInstance {
id: string;
nodeCode: string;
nodeType: NodeType;
nodeName: string;
position?: { x: number; y: number };
panelVariables?: Record<string, any>;
localVariables?: Record<string, any>;
formVariables?: Record<string, any>;
}