From 6dc5a00d34954e67119b614afcd2d046cef0cc26 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 20 Oct 2025 13:53:13 +0800 Subject: [PATCH] 1 --- frontend/package.json | 2 - frontend/pnpm-lock.yaml | 106 ----------- frontend/src/pages/System/Menu/service.ts | 1 - .../Design/components/NodeConfigModal.tsx | 2 +- .../Design/components/NodePanel.tsx | 14 +- .../Workflow/Definition/Design/index.tsx | 17 +- .../Workflow/Definition/Design/service.ts | 14 +- .../Definition/Design/utils/nodeUtils.ts | 24 ++- frontend/src/pages/X6Test/index.tsx | 102 ----------- frontend/src/pages/X6Test/types.ts | 19 -- frontend/src/router/index.tsx | 9 - .../workflow/nodes/definitions/DeployNode.ts | 144 +++++++++++++++ .../nodes/definitions/EndEventNode.ts | 101 ++++++++++ .../nodes/definitions/StartEventNode.ts | 88 +++++++++ .../src/workflow/nodes/definitions/index.ts | 159 ++++++++++++++++ frontend/src/workflow/nodes/nodeService.ts | 173 ++++++++++++++++++ frontend/src/workflow/nodes/types.ts | 118 ++++++++++++ 17 files changed, 835 insertions(+), 258 deletions(-) delete mode 100644 frontend/src/pages/X6Test/index.tsx delete mode 100644 frontend/src/pages/X6Test/types.ts create mode 100644 frontend/src/workflow/nodes/definitions/DeployNode.ts create mode 100644 frontend/src/workflow/nodes/definitions/EndEventNode.ts create mode 100644 frontend/src/workflow/nodes/definitions/StartEventNode.ts create mode 100644 frontend/src/workflow/nodes/definitions/index.ts create mode 100644 frontend/src/workflow/nodes/nodeService.ts create mode 100644 frontend/src/workflow/nodes/types.ts diff --git a/frontend/package.json b/frontend/package.json index 0584418c..1860f9da 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,8 +27,6 @@ "@formily/core": "^2.3.2", "@formily/react": "^2.3.2", "@hookform/resolvers": "^3.9.1", - "@logicflow/core": "^2.0.9", - "@logicflow/extension": "^2.0.13", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-avatar": "^1.1.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 4e64440b..a187c9c9 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -59,12 +59,6 @@ importers: '@hookform/resolvers': specifier: ^3.9.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': 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) @@ -393,9 +387,6 @@ packages: '@antv/graphlib@2.0.4': 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': resolution: {integrity: sha512-/zP8pRz28ahYSVk4fsfhl3T1X3FvCAi7Q3YQ8H2LZEvTojYYrJtO2AXFsojehU6HGVnIAq+QIjd3He6ot8+gvA==} @@ -904,14 +895,6 @@ packages: '@juggle/resize-observer@3.4.0': 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': resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} peerDependencies: @@ -1789,9 +1772,6 @@ packages: cpu: [x64] os: [win32] - '@sphinxxxx/color-conversion@2.2.2': - resolution: {integrity: sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==} - '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2641,9 +2621,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 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: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -2843,9 +2820,6 @@ packages: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} - medium-editor@5.23.3: - resolution: {integrity: sha512-he9/TdjX8f8MGdXGfCs8AllrYnqXJJvjNkDKmPg3aPW/uoIrlRqtkFthrwvmd+u4QyzEiadhCCM0EwTiRdUCJw==} - memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} @@ -2900,20 +2874,6 @@ packages: ml-matrix@6.12.0: 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: resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} @@ -3093,9 +3053,6 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - preact@10.25.1: - resolution: {integrity: sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og==} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3122,9 +3079,6 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - rangy@1.3.2: - resolution: {integrity: sha512-fS1C4MOyk8T+ZJZdLcgrukPWxkyDXa+Hd2Kj+Zg4wIK71yrWgmjzHubzPMY1G+WD9EgGxMp3fIL0zQ1ickmSWA==} - rc-align@2.4.5: resolution: {integrity: sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==} @@ -3863,13 +3817,6 @@ packages: resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} 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: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} @@ -4289,8 +4236,6 @@ snapshots: dependencies: '@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))': dependencies: '@antv/event-emitter': 0.1.3 @@ -4826,29 +4771,6 @@ snapshots: '@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)': dependencies: monaco-editor: 0.52.2 @@ -5670,8 +5592,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.28.1': optional: true - '@sphinxxxx/color-conversion@2.2.2': {} - '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.26.3 @@ -6689,8 +6609,6 @@ snapshots: dependencies: function-bind: 1.1.2 - hoist-non-react-statics@2.5.5: {} - hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -6870,8 +6788,6 @@ snapshots: semver: 5.7.2 optional: true - medium-editor@5.23.3: {} - memoize-one@6.0.0: {} merge-stream@2.0.0: {} @@ -6925,18 +6841,6 @@ snapshots: is-any-array: 2.0.1 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: {} mousetrap@1.6.5: {} @@ -7091,8 +6995,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preact@10.25.1: {} - prelude-ls@1.2.1: {} prop-types@15.8.1: @@ -7118,8 +7020,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - rangy@1.3.2: {} - rc-align@2.4.5: dependencies: babel-runtime: 6.26.0 @@ -7973,12 +7873,6 @@ snapshots: 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: dependencies: '@types/d3-array': 3.2.1 diff --git a/frontend/src/pages/System/Menu/service.ts b/frontend/src/pages/System/Menu/service.ts index e095b878..bf92c35b 100644 --- a/frontend/src/pages/System/Menu/service.ts +++ b/frontend/src/pages/System/Menu/service.ts @@ -163,7 +163,6 @@ export const getCurrentUserMenus = async () => { // version: 0, // name: "X6测试", // path: "/x6-test", - // component: "/X6Test/index", // icon: "experiment", // type: MenuTypeEnum.MENU, // parentId: 0, diff --git a/frontend/src/pages/Workflow/Definition/Design/components/NodeConfigModal.tsx b/frontend/src/pages/Workflow/Definition/Design/components/NodeConfigModal.tsx index ed40cd85..9f174d0c 100644 --- a/frontend/src/pages/Workflow/Definition/Design/components/NodeConfigModal.tsx +++ b/frontend/src/pages/Workflow/Definition/Design/components/NodeConfigModal.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import {Tabs, TabsProps} from 'antd'; import {Cell} from '@antv/x6'; -import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types"; +import type {NodeDefinitionResponse} from "@/workflow/nodes/nodeService"; import { Sheet, SheetContent, diff --git a/frontend/src/pages/Workflow/Definition/Design/components/NodePanel.tsx b/frontend/src/pages/Workflow/Definition/Design/components/NodePanel.tsx index ba032eba..24a86dd7 100644 --- a/frontend/src/pages/Workflow/Definition/Design/components/NodePanel.tsx +++ b/frontend/src/pages/Workflow/Definition/Design/components/NodePanel.tsx @@ -1,6 +1,6 @@ import React, {useState, useEffect} from 'react'; import {Card, Collapse, Tooltip, message} from 'antd'; -import type {NodeDefinition, NodeCategory} from '../types'; +import type {NodeCategory} from '../types'; import { PlayCircleOutlined, StopOutlined, @@ -13,9 +13,9 @@ import { BranchesOutlined } from '@ant-design/icons'; 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 = { @@ -24,6 +24,7 @@ const iconMap: Record = { 'user': UserOutlined, 'api': ApiOutlined, 'code': CodeOutlined, + 'build': CodeOutlined, // 构建任务图标 'fork': NodeIndexOutlined, 'branches': SplitCellsOutlined, 'apartment': AppstoreOutlined @@ -36,6 +37,7 @@ const typeIconMap: Record = { 'USER_TASK': UserOutlined, 'SERVICE_TASK': ApiOutlined, 'SCRIPT_TASK': CodeOutlined, + 'DEPLOY_NODE': CodeOutlined, // 构建任务节点 'EXCLUSIVE_GATEWAY': NodeIndexOutlined, 'PARALLEL_GATEWAY': SplitCellsOutlined, 'SUB_PROCESS': AppstoreOutlined, @@ -167,8 +169,10 @@ const NodePanel: React.FC = ({onNodeDragStart}) => {
{node.description}
} - overlayStyle={tooltipStyle} - overlayInnerStyle={tooltipOverlayInnerStyle} + styles={{ + root: tooltipStyle, + body: tooltipOverlayInnerStyle + }} placement="right" arrow={false} > diff --git a/frontend/src/pages/Workflow/Definition/Design/index.tsx b/frontend/src/pages/Workflow/Definition/Design/index.tsx index 16f9d40d..e3cd3f2a 100644 --- a/frontend/src/pages/Workflow/Definition/Design/index.tsx +++ b/frontend/src/pages/Workflow/Definition/Design/index.tsx @@ -34,7 +34,7 @@ import NodeConfigDrawer from './components/NodeConfigModal'; import {validateWorkflow} from './utils/validator'; import {addNodeToGraph} from './utils/nodeUtils'; import './index.less'; -import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types"; +import type {NodeDefinitionResponse} from "@/workflow/nodes/nodeService"; import ExpressionModal from './components/ExpressionModal'; import {EdgeCondition} from './types'; @@ -651,7 +651,7 @@ const WorkflowDesign: React.FC = () => { onClick: () => { closeMenu(); setSelectedNode(cell); - setSelectedNodeDefinition(nodeDefinitions.find(def => def.type === cell.getProp('type'))); + setSelectedNodeDefinition(nodeDefinitions.find(def => def.nodeType === cell.getProp('nodeType'))); setConfigModalVisible(true); } }, @@ -1044,13 +1044,26 @@ const WorkflowDesign: React.FC = () => { return; } + // 确保节点定义已加载 + if (!nodeDefinitions || nodeDefinitions.length === 0) { + console.error('节点定义未加载,无法还原工作流'); + return; + } + // 清空画布 graphInstance.clearCells(); const nodeMap = new Map(); // 创建节点 response.graph?.nodes?.forEach((existingNode: any) => { + console.log('正在还原节点:', existingNode.nodeType, existingNode); const node = addNodeToGraph(false, graphInstance, existingNode, nodeDefinitions); + + if (!node) { + console.error('节点创建失败:', existingNode); + return; + } + // 只设置 graph 属性 node.setProp('graph', { uiVariables: existingNode.uiVariables, diff --git a/frontend/src/pages/Workflow/Definition/Design/service.ts b/frontend/src/pages/Workflow/Definition/Design/service.ts index 4380fec5..324147ec 100644 --- a/frontend/src/pages/Workflow/Definition/Design/service.ts +++ b/frontend/src/pages/Workflow/Definition/Design/service.ts @@ -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 const getNodeDefinitionList = () => - request.get(`${NODE_DEFINITION_URL}/list`); +// 导出节点定义获取函数 +export { getNodeDefinitionList }; diff --git a/frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts b/frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts index d3bc36e8..78cda0f8 100644 --- a/frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts +++ b/frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts @@ -18,7 +18,19 @@ export const addNodeToGraph = ( position?: { x: number; y: number } ) => { 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 let shape = 'rect'; // 默认使用矩形 if (uiVariables.shape === 'circle') { @@ -53,5 +65,13 @@ export const addNodeToGraph = ( if (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; }; \ No newline at end of file diff --git a/frontend/src/pages/X6Test/index.tsx b/frontend/src/pages/X6Test/index.tsx deleted file mode 100644 index 9641ed6a..00000000 --- a/frontend/src/pages/X6Test/index.tsx +++ /dev/null @@ -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(null); - const graphRef = useRef({ 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 ( - -
- - ); -}; - -export default X6TestPage; \ No newline at end of file diff --git a/frontend/src/pages/X6Test/types.ts b/frontend/src/pages/X6Test/types.ts deleted file mode 100644 index 03d2da6a..00000000 --- a/frontend/src/pages/X6Test/types.ts +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 64722cba..3a39e239 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -30,7 +30,6 @@ const User = lazy(() => import('../pages/System/User')); const Role = lazy(() => import('../pages/System/Role')); const Menu = lazy(() => import('../pages/System/Menu')); const Department = lazy(() => import('../pages/System/Department')); -const X6Test = lazy(() => import('../pages/X6Test')); const WorkflowDefinitionList = lazy(() => import('../pages/Workflow/Definition')); const WorkflowDesign = lazy(() => import('../pages/Workflow/Definition/Design')); const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance')); @@ -146,14 +145,6 @@ const router = createBrowserRouter([ } ] }, - // { - // path: 'x6-test', - // element: ( - // }> - // - // - // ) - // }, { path: 'workflow', children: [ diff --git a/frontend/src/workflow/nodes/definitions/DeployNode.ts b/frontend/src/workflow/nodes/definitions/DeployNode.ts new file mode 100644 index 00000000..c9187e6f --- /dev/null +++ b/frontend/src/workflow/nodes/definitions/DeployNode.ts @@ -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 +}; diff --git a/frontend/src/workflow/nodes/definitions/EndEventNode.ts b/frontend/src/workflow/nodes/definitions/EndEventNode.ts new file mode 100644 index 00000000..422d5509 --- /dev/null +++ b/frontend/src/workflow/nodes/definitions/EndEventNode.ts @@ -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 +}; diff --git a/frontend/src/workflow/nodes/definitions/StartEventNode.ts b/frontend/src/workflow/nodes/definitions/StartEventNode.ts new file mode 100644 index 00000000..56866a41 --- /dev/null +++ b/frontend/src/workflow/nodes/definitions/StartEventNode.ts @@ -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 +}; diff --git a/frontend/src/workflow/nodes/definitions/index.ts b/frontend/src/workflow/nodes/definitions/index.ts new file mode 100644 index 00000000..5801e9b1 --- /dev/null +++ b/frontend/src/workflow/nodes/definitions/index.ts @@ -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 => { + const grouped = NODE_DEFINITIONS.reduce((acc, node) => { + if (!acc[node.category]) { + acc[node.category] = []; + } + acc[node.category].push(node); + return acc; + }, {} as Record); + + // 按 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'; diff --git a/frontend/src/workflow/nodes/nodeService.ts b/frontend/src/workflow/nodes/nodeService.ts new file mode 100644 index 00000000..da983c35 --- /dev/null +++ b/frontend/src/workflow/nodes/nodeService.ts @@ -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; + localVariables?: Record; + 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 + */ +export const getNodeDefinitionList = async (): Promise => { + // 模拟异步操作 + return new Promise((resolve) => { + setTimeout(() => { + const nodes = getEnabledNodes(); + const response = nodes.map((node, index) => convertToResponse(node, index)); + resolve(response); + }, 10); // 很短的延迟,模拟网络请求 + }); +}; + +/** + * 根据节点类型获取节点定义 + * @param nodeType 节点类型 + * @returns Promise + */ +export const getNodeDefinitionByType = async (nodeType: NodeType | string): Promise => { + 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> + */ +export const getNodeDefinitionsByCategories = async (): Promise> => { + return new Promise((resolve) => { + setTimeout(() => { + const grouped = getNodesByCategories(); + const result = {} as Record; + + 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 + */ +export const getNodeDefinitionsByCategory = async (category: NodeCategory): Promise => { + 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 +): 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'; diff --git a/frontend/src/workflow/nodes/types.ts b/frontend/src/workflow/nodes/types.ts new file mode 100644 index 00000000..209a0e46 --- /dev/null +++ b/frontend/src/workflow/nodes/types.ts @@ -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; + 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; + localVariables?: Record; + formVariables?: Record; +}