From 8d4941e88269abc21312e035024ed652f63b75e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=9A=E8=BE=B0=E5=85=88=E7=94=9F?= Date: Fri, 6 Dec 2024 22:52:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Designer/components/NodePanel/index.less | 226 +++++++++--------- .../Designer/components/NodePanel/index.tsx | 63 +++-- .../Definition/Designer/configs/nodeConfig.ts | 37 ++- .../Workflow/Definition/Designer/types.ts | 40 ++-- .../Definition/Designer/utils/nodeUtils.ts | 27 ++- 5 files changed, 232 insertions(+), 161 deletions(-) diff --git a/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.less b/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.less index 71b2942c..d64759ba 100644 --- a/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.less +++ b/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.less @@ -1,126 +1,114 @@ .node-panel { - height: 100%; - background: #fff; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; - &-loading { - height: 100%; + &-loading { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + :global { + .ant-tabs { + height: 100%; + display: flex; + flex-direction: column; + + .ant-tabs-nav { + margin: 0; + padding: 0 16px; + background-color: #fafafa; + border-bottom: 1px solid #f0f0f0; + + .ant-tabs-tab { + padding: 8px 16px !important; + margin: 0 !important; + transition: all 0.3s; + border-radius: 4px 4px 0 0; + + &:hover { + color: #1890ff; + background: rgba(24, 144, 255, 0.1); + } + + &.ant-tabs-tab-active { + background: #fff; + + .ant-tabs-tab-btn { + color: #1890ff; + font-weight: 500; + } + } + } + } + + .ant-tabs-content-holder { + flex: 1; + overflow: auto; + + .ant-tabs-content { + height: calc(100% - 44px); + } + } + } + } + + .node-panel-content { + padding: 16px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 16px; + overflow-y: auto; + + .node-card { + position: relative; + height: 100px; + padding: 12px; + background-color: #fff; + border: 2px solid transparent; + border-radius: 8px; + cursor: move; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + + &:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + } + + .node-icon { + width: 40px; + height: 40px; display: flex; align-items: center; justify-content: center; + font-size: 24px; + + img { + width: 32px; + height: 32px; + object-fit: contain; + } + } + + .node-name { + font-size: 12px; + color: #333; + text-align: center; + line-height: 1.2; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } - - .ant-tabs { - height: 100%; - - .ant-tabs-nav { - margin: 0; - padding: 12px 12px 0; - background: #fafafa; - - .ant-tabs-tab { - padding: 8px 16px !important; - margin: 0 !important; - transition: all 0.3s; - border-radius: 4px 4px 0 0; - - &:hover { - color: #1890ff; - } - - &.ant-tabs-tab-active { - background: #fff; - - .ant-tabs-tab-btn { - color: #1890ff; - font-weight: 500; - } - } - } - } - - .ant-tabs-content { - height: calc(100% - 44px); // 减去tab导航的高度 - } - } - - .node-panel-content { - height: 100%; - padding: 16px; - overflow-y: auto; - background: #fff; - - &::-webkit-scrollbar { - width: 6px; - height: 6px; - } - - &::-webkit-scrollbar-thumb { - background: #ccc; - border-radius: 3px; - } - - &::-webkit-scrollbar-track { - background: #f5f5f5; - border-radius: 3px; - } - } - - .node-card { - position: relative; - margin-bottom: 12px; - padding: 12px 16px; - background: #fff; - border: 1px solid #e6e6e6; - border-radius: 6px; - cursor: move; - transition: all 0.2s; - display: flex; - align-items: center; - - &:hover { - border-color: #1890ff; - background: #e6f7ff; - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1); - } - - .node-icon { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - margin-right: 12px; - border-radius: 6px; - background: #f0f5ff; - - i { - font-size: 20px; - color: #1890ff; - } - } - - .node-name { - font-size: 14px; - color: #262626; - font-weight: 500; - } - - &::after { - content: ''; - position: absolute; - right: 16px; - top: 50%; - transform: translateY(-50%); - width: 16px; - height: 16px; - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/%3E%3C/svg%3E") center/contain no-repeat; - opacity: 0.5; - transition: all 0.2s; - } - - &:hover::after { - opacity: 1; - } - } + } } diff --git a/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.tsx b/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.tsx index 1fd27be1..cd47e31d 100644 --- a/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.tsx +++ b/frontend/src/pages/Workflow/Definition/Designer/components/NodePanel/index.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Tabs, Spin } from 'antd'; import { NodeType, getNodeTypes } from '../../service'; +import { NODE_CONFIG } from '../../configs/nodeConfig'; import './index.less'; interface NodePanelProps { @@ -17,7 +18,9 @@ const NodePanel: React.FC = ({ onNodeDragStart }) => { try { const response = await getNodeTypes({ enabled: true }); if (response) { - setNodeTypes(response); + // 只保留有配置的节点类型 + const filteredTypes = response.filter(type => NODE_CONFIG[type.code]); + setNodeTypes(filteredTypes); } } catch (error) { console.error('获取节点类型失败:', error); @@ -45,23 +48,46 @@ const NodePanel: React.FC = ({ onNodeDragStart }) => { label: getCategoryLabel(category), children: (
- {types.map((nodeType) => ( -
{ - e.dataTransfer.setData('node-type', JSON.stringify(nodeType)); - onNodeDragStart(nodeType); - }} - style={{ borderColor: nodeType.color }} - > -
- + {types.map((nodeType) => { + const config = NODE_CONFIG[nodeType.code]; + if (!config) return null; + + return ( +
{ + e.dataTransfer.setData('node-type', JSON.stringify({ + ...nodeType, + config: config // 添加节点配置 + })); + onNodeDragStart(nodeType); + }} + style={{ + borderColor: config.theme.stroke, + backgroundColor: config.theme.fill + }} + > +
+ {config.extras?.icon ? ( + {nodeType.name} + ) : ( + + )} +
+
{nodeType.name}
-
{nodeType.name}
-
- ))} + ); + })}
), })); @@ -78,7 +104,7 @@ const NodePanel: React.FC = ({ onNodeDragStart }) => {
@@ -88,6 +114,7 @@ const NodePanel: React.FC = ({ onNodeDragStart }) => { // 获取类别标签 const getCategoryLabel = (category: string) => { const labels: Record = { + BASIC: '基础节点', TASK: '任务节点', EVENT: '事件节点', GATEWAY: '网关节点', diff --git a/frontend/src/pages/Workflow/Definition/Designer/configs/nodeConfig.ts b/frontend/src/pages/Workflow/Definition/Designer/configs/nodeConfig.ts index ea37326a..60d6193d 100644 --- a/frontend/src/pages/Workflow/Definition/Designer/configs/nodeConfig.ts +++ b/frontend/src/pages/Workflow/Definition/Designer/configs/nodeConfig.ts @@ -8,7 +8,16 @@ export const NODE_CONFIG: Record = { fill: '#f6ffed', stroke: '#52c41a' }, - label: '开始' + label: '开始', + extras: { + icon: { + 'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAzMzZjMCA0LjQtMy42IDgtOCA4SDU0NHYxNTJjMCA0LjQtMy42IDgtOCA4aC00OGMtNC40IDAtOC0zLjYtOC04VjQwOEgzMjhjLTQuNCAwLTgtMy42LTgtOHYtNDhjMC00LjQgMy42LTggOC04aDE1MlYxOTJjMC00LjQgMy42LTggOC04aDQ4YzQuNCAwIDggMy42IDggOHYxNTJoMTUyYzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiM1MmM0MWEiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=', + width: 32, + height: 32, + x: 24, + y: 24 + } + } }, END: { size: { width: 80, height: 80 }, @@ -17,7 +26,16 @@ export const NODE_CONFIG: Record = { fill: '#fff1f0', stroke: '#ff4d4f' }, - label: '结束' + label: '结束', + extras: { + icon: { + 'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAyODhjMCA0LjQtMy42IDgtOCA4SDMyOGMtNC40IDAtOC0zLjYtOC04di00OGMwLTQuNCAzLjYtOCA4LThoMzY4YzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiNmZjRkNGYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=', + width: 32, + height: 32, + x: 24, + y: 24 + } + } }, SHELL: { size: { width: 200, height: 80 }, @@ -26,12 +44,12 @@ export const NODE_CONFIG: Record = { fill: '#e6f7ff', stroke: '#1890ff' }, - label: '脚本', + label: 'Shell脚本', extras: { rx: 4, ry: 4, icon: { - 'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTYwIDI1NnY1MTJoNzA0VjI1NkgxNjB6IG02NDAgNDQ4SDE5MlYyODhoNjA4djQxNnpNMjI0IDQ4MGwxMjgtMTI4IDQ1LjMgNDUuMy04Mi43IDgyLjcgODIuNyA4Mi43TDM1MiA2MDhsLTEyOC0xMjh6IG0yMzEuMSAxOTJsLTQ1LjMtNDUuMyAxNTItMTUyIDQ1LjMgNDUuM2wtMTUyIDE1MnoiIGZpbGw9IiMxODkwZmYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=', + 'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTYwIDI1NnY1MTJoNzA0VjI1NkgxNjB6IG02NDAgNDQ4SDE5MlYyODhoNjA4djQxNnpNMjI0IDQ4MGwxMjgtMTI4IDQ1LjMgNDUuMy04Mi43IDgyLjcgODIuNyA4Mi43TDM1MiA2MDhsLTEyOC0xMjh6IG0yMzEuMSAxOTBsLTQ1LjMtNDUuMyAxNTItMTUyIDQ1LjMgNDUuM2wtMTUyIDE1MnoiIGZpbGw9IiMxODkwZmYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=', width: 32, height: 32, x: 16, @@ -60,7 +78,7 @@ export const NODE_CONFIG: Record = { } }, GATEWAY: { - size: { width: 80, height: 80 }, + size: { width: 60, height: 60 }, shape: 'polygon', theme: { fill: '#f9f0ff', @@ -68,7 +86,14 @@ export const NODE_CONFIG: Record = { }, label: '网关', extras: { - refPoints: '0,10 10,0 20,10 10,20' + refPoints: '0,30 30,0 60,30 30,60', + icon: { + 'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NzAzODY0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjY1NTUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNzgyLjcgNDQxLjRMNTQ1IDMwNC44YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3ODIuNyA0NDEuNHogbTExOC45IDQzMi45TDIxNy43IDE1OC4xQzIwMi4xIDE0Mi41IDE3NiAxNDIuNSAxNjAuNCAxNTguMWMtMTUuNiAxNS42LTE1LjYgNDEuNyAwIDU3LjNsNjgzLjkgNzE2LjJjMTUuNiAxNS42IDQxLjcgMTUuNiA1Ny4zIDBzMTUuNi00MS43IDAtNTcuM3ogbS00MjYtMjQuNmMtMC4zIDAuMy0wLjQgMC43LTAuNyAxTDIzNy42IDU4Mi42YzAuMy0wLjMgMC40LTAuNyAwLjctMWwyMzcuMyAyNjguMXogbTIzNy4zLTI2Ny4xTDQ3NS42IDg1MC43YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3MTIuOSA1ODIuNnogbS00NzUtMjY3LjFsMjM3LjMgMjY4LjFjLTAuMyAwLjMtMC40IDAuNy0wLjcgMUwyMzcuOSAzMTUuNXoiIGZpbGw9IiM3MjJlZDEiIHAtaWQ9IjY1NTYiPjwvcGF0aD48L3N2Zz4=', + width: 24, + height: 24, + x: 18, + y: 18 + } } } }; diff --git a/frontend/src/pages/Workflow/Definition/Designer/types.ts b/frontend/src/pages/Workflow/Definition/Designer/types.ts index b7aec164..6fdf4b94 100644 --- a/frontend/src/pages/Workflow/Definition/Designer/types.ts +++ b/frontend/src/pages/Workflow/Definition/Designer/types.ts @@ -3,30 +3,42 @@ export interface Position { y: number; } +export interface Size { + width: number; + height: number; +} + export interface NodeConfig { - label: string; - style: { - width?: number; - height?: number; - [key: string]: any; + size: Size; + shape: 'circle' | 'rect' | 'polygon'; + theme: { + fill: string; + stroke: string; }; - ports?: { - groups?: { - [key: string]: any; + label: string; + extras?: { + rx?: number; + ry?: number; + icon?: { + 'xlink:href': string; + width: number; + height: number; + x: number; + y: number; }; - items?: Array<{ - group: string; - [key: string]: any; - }>; }; } export interface NodeType { + id?: number; code: string; name: string; - label: string; - config: NodeConfig; + category: string; + color?: string; + icon?: string; + enabled?: boolean; description?: string; + config: NodeConfig; } export interface NodeData { diff --git a/frontend/src/pages/Workflow/Definition/Designer/utils/nodeUtils.ts b/frontend/src/pages/Workflow/Definition/Designer/utils/nodeUtils.ts index f3ef5654..ea4c73a8 100644 --- a/frontend/src/pages/Workflow/Definition/Designer/utils/nodeUtils.ts +++ b/frontend/src/pages/Workflow/Definition/Designer/utils/nodeUtils.ts @@ -69,6 +69,8 @@ interface NodeStyle { export const generateNodeStyle = (nodeType: string): NodeStyle => { try { const config = getNodeConfig(nodeType); + const isCircle = config.shape === 'circle'; + return { width: config.size.width, height: config.size.height, @@ -85,15 +87,32 @@ export const generateNodeStyle = (nodeType: string): NodeStyle => { fill: '#000000', fontSize: 14, fontWeight: 500, - ...(config.shape === 'rect' ? { - refX: 0.5, + ...(config.shape === 'circle' ? { + refY: 0.65, + textAnchor: 'middle', + textVerticalAnchor: 'middle' + } : config.shape === 'polygon' ? { + refY: 1.3, + textAnchor: 'middle', + textVerticalAnchor: 'middle' + } : { + refX: config.extras?.icon ? 0.3 : 0.5, refY: 0.5, textAnchor: 'middle', textVerticalAnchor: 'middle' - } : {}) + }) }, ...(config.extras?.icon ? { - image: config.extras.icon + image: { + ...config.extras.icon, + ...(config.shape === 'circle' ? { + x: (config.size.width - config.extras.icon.width) / 2, + y: config.size.height * 0.25 + } : config.shape === 'polygon' ? { + x: (config.size.width - config.extras.icon.width) / 2, + y: (config.size.height - config.extras.icon.height) / 2 + } : config.extras.icon) + } } : {}) } };