This commit is contained in:
dengqichen 2024-12-20 13:43:25 +08:00
parent d8cc3c0983
commit debef2f881
6 changed files with 88 additions and 124 deletions

View File

@ -2,12 +2,12 @@ import React, { useEffect, ReactNode } from 'react';
import {Drawer, Form, Input, Select, Button, Space, Divider, Tooltip} from 'antd'; import {Drawer, Form, Input, Select, Button, Space, Divider, Tooltip} from 'antd';
import {InfoCircleOutlined} from '@ant-design/icons'; import {InfoCircleOutlined} from '@ant-design/icons';
import {Cell} from '@antv/x6'; import {Cell} from '@antv/x6';
import { NodeDefinition } from '../types'; import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types";
interface NodeConfigDrawerProps { interface NodeConfigDrawerProps {
visible: boolean; visible: boolean;
node: Cell | null; node: Cell | null;
nodeDefinition: NodeDefinition | null; nodeDefinition: NodeDefinitionResponse | null;
onOk: (values: any) => void; onOk: (values: any) => void;
onCancel: () => void; onCancel: () => void;
} }
@ -34,19 +34,8 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
if (visible && node && nodeDefinition) { if (visible && node && nodeDefinition) {
const currentConfig = node.getProp('config') || {}; const currentConfig = node.getProp('config') || {};
if (!currentConfig.name) { if (!currentConfig.name) {
currentConfig.name = nodeDefinition.name; currentConfig.name = nodeDefinition.nodeName;
} }
// 从节点定义中获取 delegate 的默认值
const defaultDelegate = nodeDefinition.graphConfig.configSchema?.properties?.delegate?.default;
// 优先使用当前配置的值,如果没有则使用默认值
const delegateValue = currentConfig.delegate !== undefined ? currentConfig.delegate : defaultDelegate;
// 设置表单值,包括 code 和其他配置项
form.setFieldsValue({
code: node.getProp('code') === undefined ? nodeDefinition.graphConfig.code : node.getProp('code'),
delegate: delegateValue, // 使用当前值或默认值
...currentConfig
});
} }
}, [visible, node, nodeDefinition, form]); }, [visible, node, nodeDefinition, form]);
@ -188,7 +177,7 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
return ( return (
<Drawer <Drawer
title={`编辑节点 - ${nodeDefinition?.graphConfig.name || ''}`} title={`编辑节点 - ${nodeDefinition?.nodeName || ''}`}
placement="right" placement="right"
width={480} width={480}
onClose={handleCancel} onClose={handleCancel}
@ -215,11 +204,9 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
<Form.Item name="delegate" hidden> <Form.Item name="delegate" hidden>
<Input/> <Input/>
</Form.Item> </Form.Item>
{nodeDefinition?.graphConfig.configSchema && {nodeDefinition?.panelVariablesSchema &&
Object.entries(nodeDefinition.graphConfig.configSchema.properties).map(([key, property]) => { Object.entries(nodeDefinition.panelVariablesSchema.properties).map(([key, property]) => {
// 跳过 code 和 delegate 字段的显示 const required = nodeDefinition.panelVariablesSchema?.properties.required?.includes(key) || false;
if (key === 'code' || key === 'delegate') return null;
const required = nodeDefinition.graphConfig.configSchema.required?.includes(key) || false;
return renderFormItem(key, property as SchemaProperty, required); return renderFormItem(key, property as SchemaProperty, required);
})} })}
</Form> </Form>

View File

@ -1,5 +1,5 @@
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import {Card, Collapse, Tooltip, message, Spin} from 'antd'; import {Card, Collapse, Tooltip, message} from 'antd';
import type {NodeDefinition, NodeCategory} from '../types'; import type {NodeDefinition, NodeCategory} from '../types';
import { import {
PlayCircleOutlined, PlayCircleOutlined,
@ -13,6 +13,7 @@ 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";
const {Panel} = Collapse; const {Panel} = Collapse;
@ -53,19 +54,20 @@ const categoryConfig: Record<NodeCategory, {
}; };
interface NodePanelProps { interface NodePanelProps {
onNodeDragStart?: (node: NodeDefinition, e: React.DragEvent) => void, onNodeDragStart?: (node: NodeDefinitionResponse, e: React.DragEvent) => void,
nodeDefinitions?: NodeDefinition[] nodeDefinitions?: NodeDefinitionResponse[]
} }
const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => { const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinition[]>([]); const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinitionResponse[]>([]);
// 加载节点定义列表 // 加载节点定义列表
const loadNodeDefinitions = async () => { const loadNodeDefinitions = async () => {
setLoading(true); setLoading(true);
try { try {
const data = await getNodeDefinitionList(); const data = await getNodeDefinitionList();
console.log(data)
setNodeDefinitions(data); setNodeDefinitions(data);
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@ -87,29 +89,29 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
} }
acc[node.category].push(node); acc[node.category].push(node);
return acc; return acc;
}, {} as Record<NodeCategory, NodeDefinition[]>); }, {} as Record<NodeCategory, NodeDefinitionResponse[]>);
// 处理节点拖拽开始事件 // 处理节点拖拽开始事件
const handleDragStart = (node: NodeDefinition, e: React.DragEvent) => { const handleDragStart = (node: NodeDefinitionResponse, e: React.DragEvent) => {
e.dataTransfer.setData('node', JSON.stringify(node)); e.dataTransfer.setData('node', JSON.stringify(node));
onNodeDragStart?.(node, e); onNodeDragStart?.(node, e);
}; };
// 渲染节点图标 // 渲染节点图标
const renderNodeIcon = (node: NodeDefinition) => { const renderNodeIcon = (node: NodeDefinitionResponse) => {
const iconName = node.graphConfig.uiSchema.style.icon; const iconName = node.uiVariables?.style.icon;
// 首先尝试使用配置的图标 // 首先尝试使用配置的图标
let IconComponent = iconMap[iconName]; let IconComponent = iconMap[iconName];
// 如果没有找到对应的图标,使用节点类型对应的默认图标 // 如果没有找到对应的图标,使用节点类型对应的默认图标
if (!IconComponent) { if (!IconComponent) {
IconComponent = typeIconMap[node.type] || AppstoreOutlined; IconComponent = typeIconMap[node.nodeType] || AppstoreOutlined;
} }
return ( return (
<IconComponent <IconComponent
style={{ style={{
color: node.graphConfig.uiSchema.style.iconColor || '#1890ff', color: node.uiVariables?.style.iconColor || '#1890ff',
fontSize: '16px', fontSize: '16px',
marginRight: '6px' marginRight: '6px'
}} }}
@ -117,17 +119,17 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
); );
}; };
const getNodeItemStyle = (node: NodeDefinition) => ({ const getNodeItemStyle = (node: NodeDefinitionResponse) => ({
width: '100%', width: '100%',
padding: '10px 12px', padding: '10px 12px',
border: `1px solid ${node.graphConfig.uiSchema.style.stroke}`, border: `1px solid ${node.uiVariables?.style.stroke}`,
borderRadius: '6px', borderRadius: '6px',
cursor: 'move', cursor: 'move',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
gap: '10px', gap: '10px',
background: node.graphConfig.uiSchema.style.fill, background: node.uiVariables?.style.fill,
transition: 'all 0.3s', transition: 'all 0.3s',
boxShadow: '0 1px 2px rgba(0,0,0,0.05)', boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
'&:hover': { '&:hover': {
@ -163,19 +165,6 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
title={ title={
<div> <div>
<div style={{fontSize: '14px', fontWeight: 500}}>{node.description}</div> <div style={{fontSize: '14px', fontWeight: 500}}>{node.description}</div>
<div style={{marginTop: 12}}>
<div style={{fontSize: '13px', color: '#8c8c8c'}}></div>
<ul style={{
paddingLeft: 16,
margin: '8px 0',
fontSize: '13px',
color: '#595959'
}}>
{node.graphConfig.details.features.map((feature, index) => (
<li key={index}>{feature}</li>
))}
</ul>
</div>
</div> </div>
} }
overlayStyle={tooltipStyle} overlayStyle={tooltipStyle}
@ -201,7 +190,7 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}}>{node.name}</span> }}>{node.nodeName}({node.nodeType})</span>
</div> </div>
</div> </div>
</Tooltip> </Tooltip>

View File

@ -43,6 +43,7 @@ import {
DEFAULT_STYLES, DEFAULT_STYLES,
} from './constants'; } from './constants';
import './index.less'; import './index.less';
import {NodeDefinitionResponse, NodeDesignDataResponse} from "@/pages/Workflow/NodeDesign/types";
const WorkflowDesign: React.FC = () => { const WorkflowDesign: React.FC = () => {
const {id} = useParams<{ id: string }>(); const {id} = useParams<{ id: string }>();
@ -52,10 +53,10 @@ const WorkflowDesign: React.FC = () => {
const minimapContainerRef = useRef<HTMLDivElement>(null); const minimapContainerRef = useRef<HTMLDivElement>(null);
const [graph, setGraph] = useState<Graph | null>(null); const [graph, setGraph] = useState<Graph | null>(null);
const [selectedNode, setSelectedNode] = useState<Cell | null>(null); const [selectedNode, setSelectedNode] = useState<Cell | null>(null);
const [selectedNodeDefinition, setSelectedNodeDefinition] = useState<NodeDefinition | null>(null); const [selectedNodeDefinition, setSelectedNodeDefinition] = useState<NodeDefinitionResponse | null>(null);
const [configModalVisible, setConfigModalVisible] = useState(false); const [configModalVisible, setConfigModalVisible] = useState(false);
const [definitionData, setDefinitionData] = useState<any>(null); const [definitionData, setDefinitionData] = useState<any>(null);
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinition[]>([]); const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinitionResponse[]>([]);
const [isNodeDefinitionsLoaded, setIsNodeDefinitionsLoaded] = useState(false); const [isNodeDefinitionsLoaded, setIsNodeDefinitionsLoaded] = useState(false);
const [forceUpdate, setForceUpdate] = useState(false); const [forceUpdate, setForceUpdate] = useState(false);
const [scale, setScale] = useState(1); const [scale, setScale] = useState(1);
@ -571,9 +572,8 @@ const WorkflowDesign: React.FC = () => {
// 节点双击事件 // 节点双击事件
graph.on('node:dblclick', ({node}) => { graph.on('node:dblclick', ({node}) => {
const nodeType = node.getProp('type'); const nodeType = node.getProp('type');
console.log(nodeType)
// 从节点定义列表中找到对应的定义 // 从节点定义列表中找到对应的定义
const nodeDefinition = nodeDefinitions.find(def => def.type === nodeType); const nodeDefinition = nodeDefinitions.find(def => def.nodeType === nodeType);
if (nodeDefinition) { if (nodeDefinition) {
setSelectedNode(node); setSelectedNode(node);
setSelectedNodeDefinition(nodeDefinition); setSelectedNodeDefinition(nodeDefinition);
@ -1027,7 +1027,7 @@ const WorkflowDesign: React.FC = () => {
}, [graphContainerRef, id, nodeDefinitions, isNodeDefinitionsLoaded]); }, [graphContainerRef, id, nodeDefinitions, isNodeDefinitionsLoaded]);
// 处理节点拖拽开始 // 处理节点拖拽开始
const handleNodeDragStart = (node: NodeDefinition, e: React.DragEvent) => { const handleNodeDragStart = (node: NodeDefinitionResponse, e: React.DragEvent) => {
e.dataTransfer.setData('node', JSON.stringify(node)); e.dataTransfer.setData('node', JSON.stringify(node));
}; };
@ -1043,6 +1043,7 @@ const WorkflowDesign: React.FC = () => {
const node = JSON.parse(nodeData); const node = JSON.parse(nodeData);
const {clientX, clientY} = e; const {clientX, clientY} = e;
const point = graph.clientToLocal({x: clientX, y: clientY}); const point = graph.clientToLocal({x: clientX, y: clientY});
console.log("dasdasd", graph, node, nodeDefinitions, point);
addNodeToGraph(true, graph, node, nodeDefinitions, point); addNodeToGraph(true, graph, node, nodeDefinitions, point);
} catch (error) { } catch (error) {
console.error('创建节点失败:', error); console.error('创建节点失败:', error);

View File

@ -5,7 +5,7 @@ import {convertPortConfig} from '../constants';
* *
* @param isNew * @param isNew
* @param graph X6 Graph实例 * @param graph X6 Graph实例
* @param workflowDefinitionNode * @param nodeDefinitions
* @param workflowNodeDefinitionList * @param workflowNodeDefinitionList
* @param position * @param position
* @returns * @returns
@ -13,13 +13,13 @@ import {convertPortConfig} from '../constants';
export const addNodeToGraph = ( export const addNodeToGraph = (
isNew: boolean, isNew: boolean,
graph: Graph, graph: Graph,
workflowDefinitionNode: any, nodeDefinitions: any,
workflowNodeDefinitionList: any, workflowNodeDefinitionList: any,
position?: { x: number; y: number } position?: { x: number; y: number }
) => { ) => {
let nodeDefinition = workflowNodeDefinitionList.find(def => def.type === workflowDefinitionNode.type); let nodeDefinition = workflowNodeDefinitionList.find(def => def.nodeType === nodeDefinitions.nodeType);
let uiGraph = isNew ? nodeDefinition.graphConfig.uiSchema : workflowDefinitionNode.graph; let uiGraph = isNew ? nodeDefinition.uiVariables : nodeDefinitions.graph;
console.log(uiGraph)
// 根据形状类型设置正确的 shape // 根据形状类型设置正确的 shape
let shape = 'rect'; // 默认使用矩形 let shape = 'rect'; // 默认使用矩形
if (uiGraph.shape === 'circle') { if (uiGraph.shape === 'circle') {
@ -41,12 +41,12 @@ export const addNodeToGraph = (
} : {}) } : {})
}, },
label: { label: {
text: isNew ? nodeDefinition.name : workflowDefinitionNode.name text: isNew ? nodeDefinition.nodeName : nodeDefinitions.name
}, },
}, },
shape, shape,
type: isNew ? nodeDefinition.type : workflowDefinitionNode.type, type: isNew ? nodeDefinition.nodeType : nodeDefinitions.type,
code: uiGraph.code, code: uiGraph.nodeCode,
ports: convertPortConfig(uiGraph.ports), ports: convertPortConfig(uiGraph.ports),
nodeDefinition: nodeDefinition nodeDefinition: nodeDefinition
}; };
@ -55,9 +55,9 @@ export const addNodeToGraph = (
if (isNew && position) { if (isNew && position) {
// 新节点使用传入的position // 新节点使用传入的position
Object.assign(nodeConfig, { x: position.x, y: position.y }); Object.assign(nodeConfig, { x: position.x, y: position.y });
} else if (!isNew && workflowDefinitionNode.graph?.position) { } else if (!isNew && nodeDefinitions.graph?.position) {
// 已有节点使用后端返回的position // 已有节点使用后端返回的position
Object.assign(nodeConfig, { position: workflowDefinitionNode.graph.position }); Object.assign(nodeConfig, { position: nodeDefinitions.graph.position });
} }
// 设置节点ID如果有 // 设置节点ID如果有

View File

@ -65,8 +65,7 @@ const FormRenderer: React.FC<{
schema: any; schema: any;
path?: string; path?: string;
readOnly?: boolean; readOnly?: boolean;
currentTab?: any; }> = ({schema, path = '', readOnly = false}) => {
}> = ({schema, path = '', readOnly = false, currentTab}) => {
if (!schema || !schema.properties) return null; if (!schema || !schema.properties) return null;
const renderPortConfig = (portSchema: any, portPath: string) => { const renderPortConfig = (portSchema: any, portPath: string) => {
if (!portSchema || !portSchema.properties) return null; if (!portSchema || !portSchema.properties) return null;
@ -86,12 +85,6 @@ const FormRenderer: React.FC<{
required={portSchema.required?.includes('position')} required={portSchema.required?.includes('position')}
initialValue={'default' in position ? position.default : undefined} initialValue={'default' in position ? position.default : undefined}
style={{marginBottom: 16}} style={{marginBottom: 16}}
rules={[
{
required: portSchema.required?.includes('position'),
message: `${position.type === 'string' ? '输入' : '选择'}${position.title}`
}
]}
> >
{readOnly ? ( {readOnly ? (
<span style={{color: '#666'}}>{position.default || '-'}</span> <span style={{color: '#666'}}>{position.default || '-'}</span>
@ -119,7 +112,6 @@ const FormRenderer: React.FC<{
schema={attrs} schema={attrs}
path={`${portPath}.attrs`} path={`${portPath}.attrs`}
readOnly={readOnly} readOnly={readOnly}
currentTab={currentTab}
/> />
</Card> </Card>
)} )}
@ -200,7 +192,6 @@ const FormRenderer: React.FC<{
schema={value} schema={value}
path={fieldPath} path={fieldPath}
readOnly={readOnly} readOnly={readOnly}
currentTab={currentTab}
/> />
</Card> </Card>
); );
@ -216,15 +207,9 @@ const FormRenderer: React.FC<{
</span> </span>
} }
tooltip={value.description} tooltip={value.description}
required={currentTab?.schemaKey === 'uiVariables' && schema.required?.includes(key)} required={schema.required?.includes(key)}
initialValue={'default' in value ? value.default : undefined} initialValue={'default' in value ? value.default : undefined}
style={{marginBottom: 16}} style={{marginBottom: 16}}
rules={currentTab?.schemaKey === 'uiVariables' ? [
{
required: schema.required?.includes(key),
message: `${value.type === 'string' ? '输入' : '选择'}${value.title}`
}
] : undefined}
> >
{readOnly ? ( {readOnly ? (
<span style={{color: '#666'}}>{value.default || '-'}</span> <span style={{color: '#666'}}>{value.default || '-'}</span>
@ -653,7 +638,6 @@ const NodeDesignForm: React.FC = () => {
<FormRenderer <FormRenderer
schema={getCurrentSchema()} schema={getCurrentSchema()}
readOnly={tab.readonly} readOnly={tab.readonly}
currentTab={tab}
/> />
</div> </div>
) : null ) : null

View File

@ -76,6 +76,7 @@ export interface NodeDesignDataResponse extends BaseResponse{
nodeName: string; nodeName: string;
nodeType: string; nodeType: string;
category: string; category: string;
description: string;
panelVariablesSchema: NodeVariablesSchema | null; panelVariablesSchema: NodeVariablesSchema | null;
localVariablesSchema: NodeVariablesSchema | null; localVariablesSchema: NodeVariablesSchema | null;
formVariablesSchema: NodeVariablesSchema | null; formVariablesSchema: NodeVariablesSchema | null;
@ -100,6 +101,8 @@ export interface NodeDefinitionResponse extends BaseResponse {
nodeCode: string; nodeCode: string;
nodeName: string; nodeName: string;
nodeType: NodeTypeEnum; nodeType: NodeTypeEnum;
category: string;
description: string;
panelVariablesSchema: NodeVariablesSchema | null; panelVariablesSchema: NodeVariablesSchema | null;
uiVariables: UIVariables | null; uiVariables: UIVariables | null;
localVariablesSchema: NodeVariablesSchema | null; localVariablesSchema: NodeVariablesSchema | null;