1
This commit is contained in:
parent
b26216a619
commit
978a24790a
@ -1,219 +1,192 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {Tabs, TabsProps} from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import {Cell} from '@antv/x6';
|
import { Cell } from '@antv/x6';
|
||||||
import type {NodeDefinitionResponse} from "../nodes/nodeService";
|
import type { WorkflowNodeDefinition, ConfigurableNodeDefinition } from "../nodes/types";
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetFooter,
|
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import {Button} from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {Input} from "@/components/ui/input";
|
|
||||||
import {Label} from "@/components/ui/label";
|
|
||||||
import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils';
|
import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils';
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
|
|
||||||
interface NodeConfigDrawerProps {
|
interface NodeConfigModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
node: Cell | null;
|
node: Cell | null;
|
||||||
nodeDefinition: NodeDefinitionResponse | null;
|
nodeDefinition: WorkflowNodeDefinition | null;
|
||||||
onOk: (values: any) => void;
|
onOk: (values: any) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Variables {
|
interface FormData {
|
||||||
[key: string]: any;
|
config?: Record<string, any>;
|
||||||
|
inputMapping?: Record<string, any>;
|
||||||
|
outputMapping?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
|
const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
||||||
visible,
|
visible,
|
||||||
node,
|
node,
|
||||||
nodeDefinition,
|
nodeDefinition,
|
||||||
onOk,
|
onOk,
|
||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
}) => {
|
||||||
const [panelValues, setPanelValues] = React.useState<Variables>({});
|
const [formData, setFormData] = useState<FormData>({});
|
||||||
const [localValues, setLocalValues] = React.useState<Variables>({});
|
const [activeTab, setActiveTab] = useState('config');
|
||||||
|
|
||||||
|
// 判断是否为可配置节点
|
||||||
|
const isConfigurableNode = (def: WorkflowNodeDefinition): def is ConfigurableNodeDefinition => {
|
||||||
|
return 'inputMappingSchema' in def || 'outputMappingSchema' in def;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeDefinition) {
|
if (nodeDefinition && node) {
|
||||||
// 使用修改后的 schema 获取字段配置
|
// 从节点数据中获取现有配置
|
||||||
const panelColumns = convertJsonSchemaToColumns(nodeDefinition.panelVariablesSchema || { type: 'object', properties: {} });
|
const nodeData = node.getData() || {};
|
||||||
const localColumns = convertJsonSchemaToColumns(nodeDefinition.localVariablesSchema || { type: 'object', properties: {} });
|
setFormData({
|
||||||
// 初始化表单值,包括默认值
|
config: nodeData.config || {},
|
||||||
const initialPanelValues = panelColumns.reduce((acc, column) => {
|
inputMapping: nodeData.inputMapping || {},
|
||||||
if (column.initialValue !== undefined) {
|
outputMapping: nodeData.outputMapping || {},
|
||||||
acc[column.dataIndex] = column.initialValue;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as Variables);
|
|
||||||
|
|
||||||
const initialLocalValues = localColumns.reduce((acc, column) => {
|
|
||||||
if (column.initialValue !== undefined) {
|
|
||||||
acc[column.dataIndex] = column.initialValue;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as Variables);
|
|
||||||
|
|
||||||
// 设置初始值
|
|
||||||
setPanelValues({
|
|
||||||
...initialPanelValues,
|
|
||||||
...(nodeDefinition.panelVariables || {}),
|
|
||||||
// 如果是新节点,使用节点的 nodeType 和 nodeName 作为默认值
|
|
||||||
...(node && {
|
|
||||||
code: nodeDefinition.nodeType,
|
|
||||||
name: nodeDefinition.nodeName
|
|
||||||
})
|
|
||||||
});
|
|
||||||
setLocalValues({
|
|
||||||
...initialLocalValues,
|
|
||||||
...(nodeDefinition.localVariables || {})
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setFormData({});
|
||||||
}
|
}
|
||||||
}, [nodeDefinition]);
|
}, [nodeDefinition, node]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const updatedNodeDefinition = {
|
onOk(formData);
|
||||||
...nodeDefinition,
|
|
||||||
panelVariables: panelValues,
|
|
||||||
localVariables: localValues
|
|
||||||
};
|
|
||||||
onOk(updatedNodeDefinition);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderFormField = (column: any, value: any, onChange: (value: any) => void) => {
|
const handleConfigChange = (values: Record<string, any>) => {
|
||||||
console.log('renderFormField 被调用:', { column, value });
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
config: values
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
// 特殊处理 code 字段
|
const handleInputMappingChange = (values: Record<string, any>) => {
|
||||||
const isCodeField = column.dataIndex === 'code';
|
setFormData(prev => ({
|
||||||
const isReadOnly = isCodeField || column.readonly === true;
|
...prev,
|
||||||
|
inputMapping: values
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const props = {
|
const handleOutputMappingChange = (values: Record<string, any>) => {
|
||||||
value: value || '',
|
setFormData(prev => ({
|
||||||
onChange: (e: any) => !isReadOnly && onChange(e.target?.value ?? e),
|
...prev,
|
||||||
placeholder: column.fieldProps?.placeholder,
|
outputMapping: values
|
||||||
disabled: isReadOnly,
|
}));
|
||||||
readOnly: isReadOnly,
|
};
|
||||||
className: `${isReadOnly ? "bg-gray-100" : ""} ${column.fieldProps?.className || ""}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (column.valueType) {
|
if (!nodeDefinition) {
|
||||||
case 'select':
|
return null;
|
||||||
return (
|
}
|
||||||
<Select
|
|
||||||
value={value}
|
// 构建Tabs配置
|
||||||
onValueChange={onChange}
|
const tabItems = [
|
||||||
disabled={column.readonly === true}
|
{
|
||||||
>
|
key: 'config',
|
||||||
<SelectTrigger>
|
label: '基本配置',
|
||||||
<SelectValue placeholder={column.fieldProps?.placeholder} />
|
children: (
|
||||||
</SelectTrigger>
|
<div style={{ padding: '16px 0' }}>
|
||||||
<SelectContent>
|
<BetaSchemaForm
|
||||||
{column.fieldProps?.options?.map((option: any) => (
|
layoutType="Form"
|
||||||
<SelectItem key={option.value} value={option.value}>
|
columns={convertJsonSchemaToColumns(nodeDefinition.configSchema as any)}
|
||||||
{option.label}
|
initialValues={formData.config}
|
||||||
</SelectItem>
|
onValuesChange={(_, allValues) => handleConfigChange(allValues)}
|
||||||
))}
|
submitter={false}
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
case 'switch':
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
disabled={column.readonly === true}
|
|
||||||
/>
|
/>
|
||||||
);
|
|
||||||
case 'textarea':
|
|
||||||
case 'code':
|
|
||||||
return <Textarea {...props} />;
|
|
||||||
case 'digit':
|
|
||||||
return <Input {...props} type="number" />;
|
|
||||||
default:
|
|
||||||
return <Input {...props} />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderFormFields = (schema: any, values: Variables, onChange: (key: string, value: any) => void) => {
|
|
||||||
if (!schema?.properties) return null;
|
|
||||||
|
|
||||||
const columns = convertJsonSchemaToColumns(schema);
|
|
||||||
|
|
||||||
return columns.map(column => (
|
|
||||||
<div key={column.dataIndex} className="space-y-2">
|
|
||||||
<Label>{column.title}</Label>
|
|
||||||
{renderFormField(
|
|
||||||
column,
|
|
||||||
values[column.dataIndex],
|
|
||||||
(value) => onChange(column.dataIndex, value)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePanelChange = (key: string, value: any) => {
|
|
||||||
setPanelValues(prev => ({...prev, [key]: value}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLocalChange = (key: string, value: any) => {
|
|
||||||
setLocalValues(prev => ({...prev, [key]: value}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
|
||||||
nodeDefinition?.panelVariablesSchema && {
|
|
||||||
key: 'panel',
|
|
||||||
label: '面板变量',
|
|
||||||
children: (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{renderFormFields(nodeDefinition.panelVariablesSchema, panelValues, handlePanelChange)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
nodeDefinition?.localVariablesSchema && {
|
];
|
||||||
key: 'local',
|
|
||||||
label: '环境变量',
|
// 如果是可配置节点,添加输入和输出映射TAB
|
||||||
children: (
|
if (isConfigurableNode(nodeDefinition)) {
|
||||||
<div className="space-y-4">
|
if (nodeDefinition.inputMappingSchema) {
|
||||||
{renderFormFields(nodeDefinition.localVariablesSchema, localValues, handleLocalChange)}
|
tabItems.push({
|
||||||
</div>
|
key: 'input',
|
||||||
)
|
label: '输入映射',
|
||||||
|
children: (
|
||||||
|
<div style={{ padding: '16px 0' }}>
|
||||||
|
<BetaSchemaForm
|
||||||
|
layoutType="Form"
|
||||||
|
columns={convertJsonSchemaToColumns(nodeDefinition.inputMappingSchema as any)}
|
||||||
|
initialValues={formData.inputMapping}
|
||||||
|
onValuesChange={(_, allValues) => handleInputMappingChange(allValues)}
|
||||||
|
submitter={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
].filter(Boolean) as TabsProps['items'];
|
|
||||||
|
if (nodeDefinition.outputMappingSchema) {
|
||||||
|
tabItems.push({
|
||||||
|
key: 'output',
|
||||||
|
label: '输出映射',
|
||||||
|
children: (
|
||||||
|
<div style={{ padding: '16px 0' }}>
|
||||||
|
<BetaSchemaForm
|
||||||
|
layoutType="Form"
|
||||||
|
columns={convertJsonSchemaToColumns(nodeDefinition.outputMappingSchema as any)}
|
||||||
|
initialValues={formData.outputMapping}
|
||||||
|
onValuesChange={(_, allValues) => handleOutputMappingChange(allValues)}
|
||||||
|
submitter={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={visible} onOpenChange={(open) => !open && onCancel()}>
|
<Sheet open={visible} onOpenChange={(open) => !open && onCancel()}>
|
||||||
<SheetContent side="right" className="w-[480px] sm:w-[480px]">
|
<SheetContent
|
||||||
<div className="flex flex-col h-full">
|
style={{
|
||||||
<SheetHeader>
|
width: '600px',
|
||||||
<SheetTitle>编辑节点 - {nodeDefinition?.nodeName || ''}</SheetTitle>
|
maxWidth: '90vw',
|
||||||
</SheetHeader>
|
display: 'flex',
|
||||||
<div className="flex-1 overflow-y-auto py-6">
|
flexDirection: 'column'
|
||||||
<Tabs items={items} className="border-none"/>
|
}}
|
||||||
</div>
|
>
|
||||||
<div className="shrink-0 border-t bg-background p-4">
|
<SheetHeader>
|
||||||
<div className="flex justify-end gap-2">
|
<SheetTitle>
|
||||||
<Button variant="outline" onClick={onCancel}>
|
编辑节点 - {nodeDefinition.nodeName}
|
||||||
取消
|
</SheetTitle>
|
||||||
</Button>
|
</SheetHeader>
|
||||||
<Button onClick={handleSubmit}>
|
|
||||||
确定
|
<div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
||||||
</Button>
|
<Tabs
|
||||||
</div>
|
activeKey={activeTab}
|
||||||
</div>
|
onChange={setActiveTab}
|
||||||
|
items={tabItems}
|
||||||
|
style={{ flex: 1, display: 'flex', flexDirection: 'column' }}
|
||||||
|
className="flex-tabs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid #f0f0f0',
|
||||||
|
padding: '16px 0 0 0',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
gap: '8px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button variant="outline" onClick={onCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmit}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NodeConfigDrawer;
|
export default NodeConfigModal;
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
BranchesOutlined
|
BranchesOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {getNodeDefinitionList} from "../nodes/nodeService";
|
import {getNodeDefinitionList} from "../nodes/nodeService";
|
||||||
import type {NodeDefinitionResponse} from "../nodes/nodeService";
|
import type {WorkflowNodeDefinition} from "../nodes/types";
|
||||||
|
|
||||||
// 使用 Collapse 组件,不需要解构 Panel
|
// 使用 Collapse 组件,不需要解构 Panel
|
||||||
|
|
||||||
@ -56,13 +56,13 @@ const categoryConfig: Record<NodeCategory, {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface NodePanelProps {
|
interface NodePanelProps {
|
||||||
onNodeDragStart?: (node: NodeDefinitionResponse, e: React.DragEvent) => void,
|
onNodeDragStart?: (node: WorkflowNodeDefinition, e: React.DragEvent) => void,
|
||||||
nodeDefinitions?: NodeDefinitionResponse[]
|
nodeDefinitions?: WorkflowNodeDefinition[]
|
||||||
}
|
}
|
||||||
|
|
||||||
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<NodeDefinitionResponse[]>([]);
|
const [nodeDefinitions, setNodeDefinitions] = useState<WorkflowNodeDefinition[]>([]);
|
||||||
|
|
||||||
// 加载节点定义列表
|
// 加载节点定义列表
|
||||||
const loadNodeDefinitions = async () => {
|
const loadNodeDefinitions = async () => {
|
||||||
@ -91,17 +91,17 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
|
|||||||
}
|
}
|
||||||
acc[node.category].push(node);
|
acc[node.category].push(node);
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<NodeCategory, NodeDefinitionResponse[]>);
|
}, {} as Record<NodeCategory, WorkflowNodeDefinition[]>);
|
||||||
|
|
||||||
// 处理节点拖拽开始事件
|
// 处理节点拖拽开始事件
|
||||||
const handleDragStart = (node: NodeDefinitionResponse, e: React.DragEvent) => {
|
const handleDragStart = (node: WorkflowNodeDefinition, 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: NodeDefinitionResponse) => {
|
const renderNodeIcon = (node: WorkflowNodeDefinition) => {
|
||||||
const iconName = node.uiVariables?.style.icon;
|
const iconName = node.uiConfig?.style.icon;
|
||||||
// 首先尝试使用配置的图标
|
// 首先尝试使用配置的图标
|
||||||
let IconComponent = iconMap[iconName];
|
let IconComponent = iconMap[iconName];
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
|
|||||||
return (
|
return (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
style={{
|
style={{
|
||||||
color: node.uiVariables?.style.iconColor || '#1890ff',
|
color: node.uiConfig?.style.iconColor || '#1890ff',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
marginRight: '6px'
|
marginRight: '6px'
|
||||||
}}
|
}}
|
||||||
@ -121,17 +121,17 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNodeItemStyle = (node: NodeDefinitionResponse) => ({
|
const getNodeItemStyle = (node: WorkflowNodeDefinition) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '10px 12px',
|
padding: '10px 12px',
|
||||||
border: `1px solid ${node.uiVariables?.style.stroke}`,
|
border: `1px solid ${node.uiConfig?.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.uiVariables?.style.fill,
|
background: node.uiConfig?.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,7 +163,7 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
|
|||||||
}}>
|
}}>
|
||||||
{groupedNodes[category as NodeCategory]?.map(node => (
|
{groupedNodes[category as NodeCategory]?.map(node => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={node.id}
|
key={node.nodeCode}
|
||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
<div style={{fontSize: '14px', fontWeight: 500}}>{node.description}</div>
|
<div style={{fontSize: '14px', fontWeight: 500}}>{node.description}</div>
|
||||||
|
|||||||
@ -91,8 +91,6 @@ export const CONNECTING_CONFIG = {
|
|||||||
name: 'manhattan'
|
name: 'manhattan'
|
||||||
},
|
},
|
||||||
validateConnection({
|
validateConnection({
|
||||||
sourceView,
|
|
||||||
targetView,
|
|
||||||
sourceMagnet,
|
sourceMagnet,
|
||||||
targetMagnet
|
targetMagnet
|
||||||
}: any) {
|
}: any) {
|
||||||
|
|||||||
@ -4,14 +4,14 @@ import { Graph } from '@antv/x6';
|
|||||||
import { getDefinitionDetail, saveDefinition } from '../../Definition/service';
|
import { getDefinitionDetail, saveDefinition } from '../../Definition/service';
|
||||||
import { getNodeDefinitionList } from '../nodes/nodeService';
|
import { getNodeDefinitionList } from '../nodes/nodeService';
|
||||||
import { validateWorkflow } from '../utils/validator';
|
import { validateWorkflow } from '../utils/validator';
|
||||||
import { addNodeToGraph } from '../utils/nodeUtils';
|
import { restoreNodeFromData } from '../utils/nodeUtils';
|
||||||
import type { NodeDefinitionResponse } from '../nodes/nodeService';
|
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流数据管理 Hook
|
* 工作流数据管理 Hook
|
||||||
*/
|
*/
|
||||||
export const useWorkflowData = () => {
|
export const useWorkflowData = () => {
|
||||||
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinitionResponse[]>([]);
|
const [nodeDefinitions, setNodeDefinitions] = useState<WorkflowNodeDefinition[]>([]);
|
||||||
const [isNodeDefinitionsLoaded, setIsNodeDefinitionsLoaded] = useState(false);
|
const [isNodeDefinitionsLoaded, setIsNodeDefinitionsLoaded] = useState(false);
|
||||||
const [definitionData, setDefinitionData] = useState<any>(null);
|
const [definitionData, setDefinitionData] = useState<any>(null);
|
||||||
const [title, setTitle] = useState<string>('工作流设计');
|
const [title, setTitle] = useState<string>('工作流设计');
|
||||||
@ -56,19 +56,21 @@ export const useWorkflowData = () => {
|
|||||||
// 创建节点
|
// 创建节点
|
||||||
response.graph?.nodes?.forEach((existingNode: any) => {
|
response.graph?.nodes?.forEach((existingNode: any) => {
|
||||||
console.log('正在还原节点:', existingNode.nodeType, existingNode);
|
console.log('正在还原节点:', existingNode.nodeType, existingNode);
|
||||||
const node = addNodeToGraph(false, graphInstance, existingNode, nodeDefinitions);
|
|
||||||
|
|
||||||
|
// 查找节点定义
|
||||||
|
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === existingNode.nodeType);
|
||||||
|
if (!nodeDefinition) {
|
||||||
|
console.error('找不到节点定义:', existingNode.nodeType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复节点
|
||||||
|
const node = restoreNodeFromData(graphInstance, existingNode, nodeDefinition);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
console.error('节点创建失败:', existingNode);
|
console.error('节点创建失败:', existingNode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只设置 graph 属性
|
|
||||||
node.setProp('graph', {
|
|
||||||
uiVariables: existingNode.uiVariables,
|
|
||||||
panelVariables: existingNode.panelVariables,
|
|
||||||
localVariables: existingNode.localVariables
|
|
||||||
});
|
|
||||||
nodeMap.set(existingNode.id, node);
|
nodeMap.set(existingNode.id, node);
|
||||||
console.log('节点创建成功,ID:', node.id, '映射 ID:', existingNode.id);
|
console.log('节点创建成功,ID:', node.id, '映射 ID:', existingNode.id);
|
||||||
});
|
});
|
||||||
@ -199,27 +201,23 @@ export const useWorkflowData = () => {
|
|||||||
|
|
||||||
// 获取所有节点和边的数据
|
// 获取所有节点和边的数据
|
||||||
const nodes = graph.getNodes().map(node => {
|
const nodes = graph.getNodes().map(node => {
|
||||||
const nodeType = node.getProp('nodeType');
|
const nodeData = node.getData();
|
||||||
const graphData = node.getProp('graph') || {};
|
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === nodeData.nodeType);
|
||||||
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === nodeType);
|
|
||||||
const position = node.getPosition();
|
const position = node.getPosition();
|
||||||
|
|
||||||
const {
|
|
||||||
uiVariables,
|
|
||||||
panelVariables
|
|
||||||
} = graphData;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
nodeCode: nodeType,
|
nodeCode: nodeData.nodeCode,
|
||||||
nodeType: nodeType,
|
nodeType: nodeData.nodeType,
|
||||||
nodeName: node.attr('label/text'),
|
nodeName: nodeData.nodeName,
|
||||||
uiVariables: {
|
position,
|
||||||
...uiVariables,
|
uiConfig: {
|
||||||
position: position
|
...(nodeDefinition?.uiConfig || {}),
|
||||||
|
position
|
||||||
},
|
},
|
||||||
panelVariables,
|
config: nodeData.config || {},
|
||||||
localVariables: nodeDefinition?.localVariablesSchema || {}
|
inputMapping: nodeData.inputMapping || {},
|
||||||
|
outputMapping: nodeData.outputMapping || {}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -240,14 +238,15 @@ export const useWorkflowData = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 收集并合并所有节点的 localVariablesSchema
|
// 收集并合并所有节点的输入映射Schema (用于全局变量)
|
||||||
const allLocalSchemas = nodes
|
const allInputSchemas = nodes
|
||||||
.map(node => {
|
.map(node => {
|
||||||
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === node.nodeType);
|
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === node.nodeType);
|
||||||
return nodeDefinition?.localVariablesSchema;
|
// 检查是否为可配置节点并有输入映射Schema
|
||||||
|
return 'inputMappingSchema' in nodeDefinition! ? nodeDefinition.inputMappingSchema : null;
|
||||||
})
|
})
|
||||||
.filter(schema => schema); // 过滤掉空值
|
.filter(schema => schema); // 过滤掉空值
|
||||||
const mergedLocalSchema = mergeLocalVariablesSchemas(allLocalSchemas);
|
const mergedLocalSchema = mergeLocalVariablesSchemas(allInputSchemas);
|
||||||
|
|
||||||
// 构建保存数据
|
// 构建保存数据
|
||||||
const saveData = {
|
const saveData = {
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import { Graph } from '@antv/x6';
|
import { Graph } from '@antv/x6';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { addNodeToGraph } from '../utils/nodeUtils';
|
import { createNodeFromDefinition } from '../utils/nodeUtils';
|
||||||
import type { NodeDefinitionResponse } from '../nodes/nodeService';
|
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流拖拽功能 Hook
|
* 工作流拖拽功能 Hook
|
||||||
*/
|
*/
|
||||||
export const useWorkflowDragDrop = (
|
export const useWorkflowDragDrop = (
|
||||||
graph: Graph | null,
|
graph: Graph | null,
|
||||||
nodeDefinitions: NodeDefinitionResponse[]
|
nodeDefinitions: WorkflowNodeDefinition[]
|
||||||
) => {
|
) => {
|
||||||
// 处理节点拖拽开始
|
// 处理节点拖拽开始
|
||||||
const handleNodeDragStart = (node: NodeDefinitionResponse, e: React.DragEvent) => {
|
const handleNodeDragStart = (node: WorkflowNodeDefinition, e: React.DragEvent) => {
|
||||||
e.dataTransfer.setData('node', JSON.stringify(node));
|
e.dataTransfer.setData('node', JSON.stringify(node));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,10 +24,10 @@ export const useWorkflowDragDrop = (
|
|||||||
const nodeData = e.dataTransfer.getData('node');
|
const nodeData = e.dataTransfer.getData('node');
|
||||||
if (!nodeData) return;
|
if (!nodeData) return;
|
||||||
|
|
||||||
const node = JSON.parse(nodeData);
|
const nodeDefinition = 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});
|
||||||
addNodeToGraph(true, graph, node, nodeDefinitions, point);
|
createNodeFromDefinition(graph, nodeDefinition, point);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建节点失败:', error);
|
console.error('创建节点失败:', error);
|
||||||
message.error('创建节点失败');
|
message.error('创建节点失败');
|
||||||
|
|||||||
@ -3,15 +3,15 @@ import { Graph, Edge } from '@antv/x6';
|
|||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { GraphInitializer } from '../utils/graph/graphInitializer';
|
import { GraphInitializer } from '../utils/graph/graphInitializer';
|
||||||
import { EventRegistrar } from '../utils/graph/eventRegistrar';
|
import { EventRegistrar } from '../utils/graph/eventRegistrar';
|
||||||
import type { NodeDefinitionResponse } from '../nodes/nodeService';
|
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流图形管理 Hook
|
* 工作流图形管理 Hook
|
||||||
*/
|
*/
|
||||||
export const useWorkflowGraph = (
|
export const useWorkflowGraph = (
|
||||||
nodeDefinitions: NodeDefinitionResponse[],
|
nodeDefinitions: WorkflowNodeDefinition[],
|
||||||
isNodeDefinitionsLoaded: boolean,
|
isNodeDefinitionsLoaded: boolean,
|
||||||
onNodeEdit: (cell: any, nodeDefinition?: NodeDefinitionResponse) => void,
|
onNodeEdit: (cell: any, nodeDefinition?: WorkflowNodeDefinition) => void,
|
||||||
onEdgeEdit: (edge: Edge) => void
|
onEdgeEdit: (edge: Edge) => void
|
||||||
) => {
|
) => {
|
||||||
const [graph, setGraph] = useState<Graph | null>(null);
|
const [graph, setGraph] = useState<Graph | null>(null);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { Cell, Edge } from '@antv/x6';
|
import { Cell, Edge } from '@antv/x6';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import type { NodeDefinitionResponse } from '../nodes/nodeService';
|
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||||
import type { EdgeCondition } from '../types';
|
import type { EdgeCondition } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,13 +9,13 @@ import type { EdgeCondition } from '../types';
|
|||||||
*/
|
*/
|
||||||
export const useWorkflowModals = () => {
|
export const useWorkflowModals = () => {
|
||||||
const [selectedNode, setSelectedNode] = useState<Cell | null>(null);
|
const [selectedNode, setSelectedNode] = useState<Cell | null>(null);
|
||||||
const [selectedNodeDefinition, setSelectedNodeDefinition] = useState<NodeDefinitionResponse | null>(null);
|
const [selectedNodeDefinition, setSelectedNodeDefinition] = useState<WorkflowNodeDefinition | null>(null);
|
||||||
const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
|
const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
|
||||||
const [configModalVisible, setConfigModalVisible] = useState(false);
|
const [configModalVisible, setConfigModalVisible] = useState(false);
|
||||||
const [expressionModalVisible, setExpressionModalVisible] = useState(false);
|
const [expressionModalVisible, setExpressionModalVisible] = useState(false);
|
||||||
|
|
||||||
// 处理节点编辑
|
// 处理节点编辑
|
||||||
const handleNodeEdit = useCallback((cell: Cell, nodeDefinition?: NodeDefinitionResponse) => {
|
const handleNodeEdit = useCallback((cell: Cell, nodeDefinition?: WorkflowNodeDefinition) => {
|
||||||
setSelectedNode(cell);
|
setSelectedNode(cell);
|
||||||
setSelectedNodeDefinition(nodeDefinition || null);
|
setSelectedNodeDefinition(nodeDefinition || null);
|
||||||
setConfigModalVisible(true);
|
setConfigModalVisible(true);
|
||||||
@ -28,15 +28,20 @@ export const useWorkflowModals = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 处理节点配置更新
|
// 处理节点配置更新
|
||||||
const handleNodeConfigUpdate = useCallback((updatedNodeDefinition: any) => {
|
const handleNodeConfigUpdate = useCallback((formData: any) => {
|
||||||
if (!selectedNode) return;
|
if (!selectedNode) return;
|
||||||
|
|
||||||
// 设置节点的 graph 属性,将所有数据统一放在 graph 下
|
// 更新节点数据
|
||||||
selectedNode.setProp('graph', updatedNodeDefinition);
|
const nodeData = selectedNode.getData() || {};
|
||||||
|
const updatedData = {
|
||||||
|
...nodeData,
|
||||||
|
...formData
|
||||||
|
};
|
||||||
|
selectedNode.setData(updatedData);
|
||||||
|
|
||||||
// 更新节点显示名称(如果存在)
|
// 更新节点显示名称(如果配置中有nodeName)
|
||||||
if (updatedNodeDefinition.panelVariables?.name) {
|
if (formData.config?.nodeName) {
|
||||||
selectedNode.attr('label/text', updatedNodeDefinition.panelVariables.name);
|
selectedNode.attr('label/text', formData.config.nodeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfigModalVisible(false);
|
setConfigModalVisible(false);
|
||||||
|
|||||||
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 节点数据转换器
|
||||||
|
* 负责在设计时Schema格式和运行时key/value格式之间转换
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ConfigurableNodeDefinition,
|
||||||
|
NodeInstanceData,
|
||||||
|
NodeFormData,
|
||||||
|
WorkflowNodeDefinition,
|
||||||
|
UIConfig
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export class NodeDataConverter {
|
||||||
|
/**
|
||||||
|
* 判断节点是否为可配置节点
|
||||||
|
*/
|
||||||
|
static isConfigurableNode(node: WorkflowNodeDefinition): node is ConfigurableNodeDefinition {
|
||||||
|
return 'inputMappingSchema' in node || 'outputMappingSchema' in node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存时:将用户表单数据转换为后端存储格式
|
||||||
|
*/
|
||||||
|
static convertToSaveFormat(
|
||||||
|
nodeDefinition: WorkflowNodeDefinition,
|
||||||
|
formData: NodeFormData,
|
||||||
|
position: { x: number; y: number },
|
||||||
|
uiConfig: UIConfig
|
||||||
|
): NodeInstanceData {
|
||||||
|
const baseData = {
|
||||||
|
nodeCode: nodeDefinition.nodeCode,
|
||||||
|
nodeName: nodeDefinition.nodeName,
|
||||||
|
nodeType: nodeDefinition.nodeType,
|
||||||
|
category: nodeDefinition.category,
|
||||||
|
description: nodeDefinition.description,
|
||||||
|
position,
|
||||||
|
uiConfig,
|
||||||
|
config: formData.config || {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果是可配置节点,添加输入输出映射
|
||||||
|
if (this.isConfigurableNode(nodeDefinition)) {
|
||||||
|
return {
|
||||||
|
...baseData,
|
||||||
|
inputMapping: formData.inputMapping || {},
|
||||||
|
outputMapping: formData.outputMapping || {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载时:将后端数据转换为表单格式
|
||||||
|
*/
|
||||||
|
static convertToFormFormat(
|
||||||
|
nodeDefinition: WorkflowNodeDefinition,
|
||||||
|
savedData: NodeInstanceData
|
||||||
|
): NodeFormData {
|
||||||
|
const formData: NodeFormData = {
|
||||||
|
config: savedData.config || {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果是可配置节点,添加输入输出映射
|
||||||
|
if (this.isConfigurableNode(nodeDefinition)) {
|
||||||
|
return {
|
||||||
|
...formData,
|
||||||
|
inputMapping: savedData.inputMapping || {},
|
||||||
|
outputMapping: savedData.outputMapping || {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,106 +1,18 @@
|
|||||||
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
|
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建任务节点定义
|
* 部署任务节点定义
|
||||||
* 用于执行应用的构建和部署任务
|
* 可配置节点,支持配置、输入映射、输出映射
|
||||||
*/
|
*/
|
||||||
export const DeployNode: WorkflowNodeDefinition = {
|
export const DeployNode: ConfigurableNodeDefinition = {
|
||||||
nodeCode: "DEPLOY_NODE",
|
nodeCode: "DEPLOY_NODE",
|
||||||
nodeName: "构建任务",
|
nodeName: "构建任务",
|
||||||
nodeType: NodeType.DEPLOY_NODE,
|
nodeType: NodeType.DEPLOY_NODE,
|
||||||
category: NodeCategory.TASK,
|
category: NodeCategory.TASK,
|
||||||
description: "执行应用构建和部署任务,支持多种构建工具和部署环境",
|
description: "执行应用构建和部署任务",
|
||||||
|
|
||||||
// 面板变量配置 - 用户在节点配置面板中可以设置的参数
|
// UI 配置 - 节点在画布上的显示样式
|
||||||
panelVariablesSchema: {
|
uiConfig: {
|
||||||
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',
|
shape: 'rect',
|
||||||
size: {
|
size: {
|
||||||
width: 120,
|
width: 120,
|
||||||
@ -111,7 +23,7 @@ export const DeployNode: WorkflowNodeDefinition = {
|
|||||||
stroke: '#0050b3',
|
stroke: '#0050b3',
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
icon: 'build',
|
icon: 'build',
|
||||||
iconColor: '#ffffff'
|
iconColor: '#fff'
|
||||||
},
|
},
|
||||||
ports: {
|
ports: {
|
||||||
groups: {
|
groups: {
|
||||||
@ -120,7 +32,7 @@ export const DeployNode: WorkflowNodeDefinition = {
|
|||||||
attrs: {
|
attrs: {
|
||||||
circle: {
|
circle: {
|
||||||
r: 4,
|
r: 4,
|
||||||
fill: '#ffffff',
|
fill: '#fff',
|
||||||
stroke: '#1890ff'
|
stroke: '#1890ff'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,7 +42,7 @@ export const DeployNode: WorkflowNodeDefinition = {
|
|||||||
attrs: {
|
attrs: {
|
||||||
circle: {
|
circle: {
|
||||||
r: 4,
|
r: 4,
|
||||||
fill: '#ffffff',
|
fill: '#fff',
|
||||||
stroke: '#1890ff'
|
stroke: '#1890ff'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,6 +51,161 @@ export const DeployNode: WorkflowNodeDefinition = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
orderNum: 10,
|
// 基本配置Schema - 设计时用于生成表单,保存时转为key/value(包含基本信息+节点配置)
|
||||||
enabled: true
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "节点的基本信息和构建任务的配置参数",
|
||||||
|
properties: {
|
||||||
|
// 基本信息
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明"
|
||||||
|
},
|
||||||
|
// 节点配置
|
||||||
|
buildCommand: {
|
||||||
|
type: "string",
|
||||||
|
title: "构建命令",
|
||||||
|
description: "执行构建的命令",
|
||||||
|
default: "npm run build"
|
||||||
|
},
|
||||||
|
timeout: {
|
||||||
|
type: "number",
|
||||||
|
title: "超时时间(秒)",
|
||||||
|
description: "构建超时时间",
|
||||||
|
default: 300,
|
||||||
|
minimum: 30,
|
||||||
|
maximum: 3600
|
||||||
|
},
|
||||||
|
retryCount: {
|
||||||
|
type: "number",
|
||||||
|
title: "重试次数",
|
||||||
|
description: "构建失败时的重试次数",
|
||||||
|
default: 2,
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 5
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: "string",
|
||||||
|
title: "运行环境",
|
||||||
|
description: "构建运行的环境",
|
||||||
|
enum: ["development", "staging", "production"],
|
||||||
|
default: "production"
|
||||||
|
},
|
||||||
|
dockerImage: {
|
||||||
|
type: "string",
|
||||||
|
title: "Docker镜像",
|
||||||
|
description: "构建使用的Docker镜像",
|
||||||
|
default: "node:18-alpine"
|
||||||
|
},
|
||||||
|
workingDirectory: {
|
||||||
|
type: "string",
|
||||||
|
title: "工作目录",
|
||||||
|
description: "构建的工作目录",
|
||||||
|
default: "/app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["nodeName", "nodeCode", "buildCommand", "timeout"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输入映射Schema - 定义从上游节点接收的数据
|
||||||
|
inputMappingSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "输入映射",
|
||||||
|
description: "从上游节点接收的数据映射配置",
|
||||||
|
properties: {
|
||||||
|
sourceCodePath: {
|
||||||
|
type: "string",
|
||||||
|
title: "源代码路径",
|
||||||
|
description: "源代码在存储中的路径",
|
||||||
|
default: "${upstream.outputPath}"
|
||||||
|
},
|
||||||
|
buildArgs: {
|
||||||
|
type: "array",
|
||||||
|
title: "构建参数",
|
||||||
|
description: "额外的构建参数列表",
|
||||||
|
items: {
|
||||||
|
type: "string"
|
||||||
|
},
|
||||||
|
default: []
|
||||||
|
},
|
||||||
|
envVariables: {
|
||||||
|
type: "object",
|
||||||
|
title: "环境变量",
|
||||||
|
description: "构建时的环境变量",
|
||||||
|
properties: {},
|
||||||
|
additionalProperties: true
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
type: "string",
|
||||||
|
title: "依赖文件",
|
||||||
|
description: "依赖配置文件路径",
|
||||||
|
default: "${upstream.dependenciesFile}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["sourceCodePath"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输出映射Schema - 定义传递给下游节点的数据
|
||||||
|
outputMappingSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "输出映射",
|
||||||
|
description: "传递给下游节点的数据映射配置",
|
||||||
|
properties: {
|
||||||
|
buildArtifactPath: {
|
||||||
|
type: "string",
|
||||||
|
title: "构建产物路径",
|
||||||
|
description: "构建完成后的产物存储路径",
|
||||||
|
default: "/artifacts/${buildId}"
|
||||||
|
},
|
||||||
|
buildLog: {
|
||||||
|
type: "string",
|
||||||
|
title: "构建日志",
|
||||||
|
description: "构建过程的日志文件路径",
|
||||||
|
default: "/logs/build-${buildId}.log"
|
||||||
|
},
|
||||||
|
buildStatus: {
|
||||||
|
type: "string",
|
||||||
|
title: "构建状态",
|
||||||
|
description: "构建完成状态",
|
||||||
|
enum: ["SUCCESS", "FAILED", "TIMEOUT"],
|
||||||
|
default: "SUCCESS"
|
||||||
|
},
|
||||||
|
buildTime: {
|
||||||
|
type: "number",
|
||||||
|
title: "构建耗时",
|
||||||
|
description: "构建耗时(秒)",
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
dockerImageTag: {
|
||||||
|
type: "string",
|
||||||
|
title: "Docker镜像标签",
|
||||||
|
description: "构建生成的Docker镜像标签",
|
||||||
|
default: "${imageRegistry}/${projectName}:${buildId}"
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
type: "object",
|
||||||
|
title: "构建元数据",
|
||||||
|
description: "构建过程中的元数据信息",
|
||||||
|
properties: {
|
||||||
|
buildId: { type: "string" },
|
||||||
|
buildTime: { type: "string" },
|
||||||
|
gitCommit: { type: "string" },
|
||||||
|
gitBranch: { type: "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["buildArtifactPath", "buildStatus"]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@ -1,94 +1,40 @@
|
|||||||
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
|
import { BaseNodeDefinition, NodeType, NodeCategory } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结束事件节点定义
|
* 结束事件节点定义
|
||||||
* 工作流的终止节点,表示流程的结束
|
* 简单节点,无需额外配置
|
||||||
*/
|
*/
|
||||||
export const EndEventNode: WorkflowNodeDefinition = {
|
export const EndEventNode: BaseNodeDefinition = {
|
||||||
nodeCode: "END_EVENT",
|
nodeCode: "END_EVENT",
|
||||||
nodeName: "结束",
|
nodeName: "结束",
|
||||||
nodeType: NodeType.END_EVENT,
|
nodeType: NodeType.END_EVENT,
|
||||||
category: NodeCategory.EVENT,
|
category: NodeCategory.EVENT,
|
||||||
description: "工作流的终止节点,标识流程的结束点",
|
description: "工作流的结束节点",
|
||||||
|
|
||||||
panelVariablesSchema: {
|
// UI 配置 - 节点在画布上的显示样式
|
||||||
type: "object",
|
uiConfig: {
|
||||||
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',
|
shape: 'circle',
|
||||||
size: {
|
size: {
|
||||||
width: 50,
|
width: 40,
|
||||||
height: 50
|
height: 40
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
fill: '#ff4d4f',
|
fill: '#f5222d',
|
||||||
stroke: '#cf1322',
|
stroke: '#cf1322',
|
||||||
strokeWidth: 2,
|
strokeWidth: 1,
|
||||||
icon: 'stop',
|
icon: 'stop',
|
||||||
iconColor: '#ffffff'
|
iconColor: '#fff'
|
||||||
},
|
},
|
||||||
ports: {
|
ports: {
|
||||||
groups: {
|
groups: {
|
||||||
|
// 结束节点只有输入端口
|
||||||
in: {
|
in: {
|
||||||
position: 'left',
|
position: 'left',
|
||||||
attrs: {
|
attrs: {
|
||||||
circle: {
|
circle: {
|
||||||
r: 4,
|
r: 4,
|
||||||
fill: '#ffffff',
|
fill: '#fff',
|
||||||
stroke: '#ff4d4f'
|
stroke: '#f5222d'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,6 +42,28 @@ export const EndEventNode: WorkflowNodeDefinition = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
orderNum: 99,
|
// 基本配置Schema - 用于生成"基本配置"TAB(包含基本信息)
|
||||||
enabled: true
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "节点的基本配置信息",
|
||||||
|
properties: {
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["nodeName", "nodeCode"]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@ -1,80 +1,39 @@
|
|||||||
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
|
import { BaseNodeDefinition, NodeType, NodeCategory } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始事件节点定义
|
* 开始事件节点定义
|
||||||
* 工作流的起始节点,表示流程的开始
|
* 简单节点,无需额外配置
|
||||||
*/
|
*/
|
||||||
export const StartEventNode: WorkflowNodeDefinition = {
|
export const StartEventNode: BaseNodeDefinition = {
|
||||||
nodeCode: "START_EVENT",
|
nodeCode: "START_EVENT",
|
||||||
nodeName: "开始",
|
nodeName: "开始",
|
||||||
nodeType: NodeType.START_EVENT,
|
nodeType: NodeType.START_EVENT,
|
||||||
category: NodeCategory.EVENT,
|
category: NodeCategory.EVENT,
|
||||||
description: "工作流的起始节点,标识流程的开始点",
|
description: "工作流的起始节点",
|
||||||
|
|
||||||
panelVariablesSchema: {
|
// UI 配置 - 节点在画布上的显示样式
|
||||||
type: "object",
|
uiConfig: {
|
||||||
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',
|
shape: 'circle',
|
||||||
size: {
|
size: {
|
||||||
width: 50,
|
width: 40,
|
||||||
height: 50
|
height: 40
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
fill: '#52c41a',
|
fill: '#52c41a',
|
||||||
stroke: '#389e0d',
|
stroke: '#389e08',
|
||||||
strokeWidth: 2,
|
strokeWidth: 1,
|
||||||
icon: 'play-circle',
|
icon: 'play-circle',
|
||||||
iconColor: '#ffffff'
|
iconColor: '#fff'
|
||||||
},
|
},
|
||||||
ports: {
|
ports: {
|
||||||
groups: {
|
groups: {
|
||||||
|
// 开始节点只有输出端口
|
||||||
out: {
|
out: {
|
||||||
position: 'right',
|
position: 'right',
|
||||||
attrs: {
|
attrs: {
|
||||||
circle: {
|
circle: {
|
||||||
r: 4,
|
r: 4,
|
||||||
fill: '#ffffff',
|
fill: '#fff',
|
||||||
stroke: '#52c41a'
|
stroke: '#52c41a'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +42,28 @@ export const StartEventNode: WorkflowNodeDefinition = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
orderNum: 1,
|
// 基本配置Schema - 用于生成"基本配置"TAB(包含基本信息)
|
||||||
enabled: true
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "节点的基本配置信息",
|
||||||
|
properties: {
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["nodeName", "nodeCode"]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@ -1,159 +1,76 @@
|
|||||||
// 工作流节点定义注册中心
|
import { WorkflowNodeDefinition, NodeCategory, NodeType } from '../types';
|
||||||
|
import { NodeDataConverter } from '../NodeDataConverter';
|
||||||
import { DeployNode } from './DeployNode';
|
import { DeployNode } from './DeployNode';
|
||||||
import { StartEventNode } from './StartEventNode';
|
import { StartEventNode } from './StartEventNode';
|
||||||
import { EndEventNode } from './EndEventNode';
|
import { EndEventNode } from './EndEventNode';
|
||||||
import { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 所有节点定义的注册表
|
* 所有节点定义的注册表
|
||||||
* 新增节点时,需要在此处注册
|
|
||||||
*/
|
*/
|
||||||
export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
|
export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
|
||||||
StartEventNode,
|
StartEventNode,
|
||||||
EndEventNode,
|
EndEventNode,
|
||||||
DeployNode,
|
DeployNode,
|
||||||
// 在此处添加新的节点定义...
|
// 在这里添加更多节点定义
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据节点类型获取节点定义
|
* 根据节点类型获取节点定义
|
||||||
* @param nodeType 节点类型
|
|
||||||
* @returns 节点定义或undefined
|
|
||||||
*/
|
*/
|
||||||
export const getNodeDefinition = (nodeType: NodeType | string): WorkflowNodeDefinition | undefined => {
|
export const getNodeDefinition = (nodeType: NodeType): WorkflowNodeDefinition | undefined => {
|
||||||
return NODE_DEFINITIONS.find(node => node.nodeType === nodeType);
|
return NODE_DEFINITIONS.find(node => node.nodeType === nodeType);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据节点编码获取节点定义
|
* 根据节点代码获取节点定义
|
||||||
* @param nodeCode 节点编码
|
|
||||||
* @returns 节点定义或undefined
|
|
||||||
*/
|
*/
|
||||||
export const getNodeDefinitionByCode = (nodeCode: string): WorkflowNodeDefinition | undefined => {
|
export const getNodeDefinitionByCode = (nodeCode: string): WorkflowNodeDefinition | undefined => {
|
||||||
return NODE_DEFINITIONS.find(node => node.nodeCode === nodeCode);
|
return NODE_DEFINITIONS.find(node => node.nodeCode === nodeCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据分类获取节点定义列表
|
* 获取指定分类的所有节点
|
||||||
* @param category 节点分类
|
|
||||||
* @returns 节点定义数组
|
|
||||||
*/
|
*/
|
||||||
export const getNodesByCategory = (category: NodeCategory): WorkflowNodeDefinition[] => {
|
export const getNodesByCategory = (category: NodeCategory): WorkflowNodeDefinition[] => {
|
||||||
return NODE_DEFINITIONS.filter(node => node.category === category);
|
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[]> => {
|
export const getNodesByCategories = (): Record<NodeCategory, WorkflowNodeDefinition[]> => {
|
||||||
const grouped = NODE_DEFINITIONS.reduce((acc, node) => {
|
return NODE_DEFINITIONS.reduce((acc, node) => {
|
||||||
if (!acc[node.category]) {
|
if (!acc[node.category]) {
|
||||||
acc[node.category] = [];
|
acc[node.category] = [];
|
||||||
}
|
}
|
||||||
acc[node.category].push(node);
|
acc[node.category].push(node);
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<NodeCategory, WorkflowNodeDefinition[]>);
|
}, {} 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): {
|
export const getEnabledNodes = (): WorkflowNodeDefinition[] => {
|
||||||
isValid: boolean;
|
// 目前所有节点都是启用的,后续可以添加enabled字段
|
||||||
errors: string[];
|
return NODE_DEFINITIONS;
|
||||||
} => {
|
|
||||||
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 = () => {
|
export const isConfigurableNode = NodeDataConverter.isConfigurableNode;
|
||||||
const total = NODE_DEFINITIONS.length;
|
|
||||||
const enabled = getEnabledNodes().length;
|
|
||||||
const byCategory = getNodesByCategories();
|
|
||||||
|
|
||||||
return {
|
/**
|
||||||
total,
|
* 导出节点定义
|
||||||
enabled,
|
*/
|
||||||
disabled: total - enabled,
|
export {
|
||||||
categories: Object.keys(byCategory).length,
|
StartEventNode,
|
||||||
byCategory: Object.entries(byCategory).map(([category, nodes]) => ({
|
EndEventNode,
|
||||||
category,
|
DeployNode
|
||||||
count: nodes.length
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 导出所有节点定义(向后兼容)
|
/**
|
||||||
export { DeployNode, StartEventNode, EndEventNode };
|
* 导出数据转换器
|
||||||
|
*/
|
||||||
// 导出类型
|
export { NodeDataConverter };
|
||||||
export type { WorkflowNodeDefinition, NodeType, NodeCategory } from '../types';
|
|
||||||
@ -1,173 +1,59 @@
|
|||||||
// 节点服务适配器
|
/**
|
||||||
// 提供与原有API兼容的接口,但使用本地节点定义
|
* 节点服务 - 提供节点定义的访问接口
|
||||||
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NODE_DEFINITIONS,
|
NODE_DEFINITIONS,
|
||||||
getNodeDefinition,
|
getNodeDefinition,
|
||||||
getNodesByCategory,
|
getNodesByCategory,
|
||||||
getEnabledNodes,
|
|
||||||
getNodesByCategories
|
getNodesByCategories
|
||||||
} from './definitions';
|
} from './definitions';
|
||||||
import { WorkflowNodeDefinition, NodeType, NodeCategory } from './types';
|
import {
|
||||||
|
WorkflowNodeDefinition,
|
||||||
// 兼容原有的响应格式
|
NodeType,
|
||||||
export interface NodeDefinitionResponse {
|
NodeCategory
|
||||||
id: number;
|
} from './types';
|
||||||
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 => {
|
export const getNodeDefinitionList = async (): Promise<WorkflowNodeDefinition[]> => {
|
||||||
return {
|
return new Promise((resolve) => {
|
||||||
id: index + 1, // 生成虚拟ID
|
setTimeout(() => {
|
||||||
nodeCode: node.nodeCode,
|
resolve(NODE_DEFINITIONS);
|
||||||
nodeName: node.nodeName,
|
}, 10);
|
||||||
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[]> => {
|
export const getNodeDefinitionsGroupedByCategory = async (): Promise<Record<NodeCategory, WorkflowNodeDefinition[]>> => {
|
||||||
// 模拟异步操作
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const nodes = getEnabledNodes();
|
resolve(getNodesByCategories());
|
||||||
const response = nodes.map((node, index) => convertToResponse(node, index));
|
}, 10);
|
||||||
resolve(response);
|
|
||||||
}, 10); // 很短的延迟,模拟网络请求
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据节点类型获取节点定义
|
* 根据节点类型获取节点定义
|
||||||
* @param nodeType 节点类型
|
|
||||||
* @returns Promise<NodeDefinitionResponse | null>
|
|
||||||
*/
|
*/
|
||||||
export const getNodeDefinitionByType = async (nodeType: NodeType | string): Promise<NodeDefinitionResponse | null> => {
|
export const getNodeDefinitionByType = async (nodeType: NodeType): Promise<WorkflowNodeDefinition | undefined> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const node = getNodeDefinition(nodeType);
|
resolve(getNodeDefinition(nodeType));
|
||||||
if (node) {
|
|
||||||
const index = NODE_DEFINITIONS.findIndex(n => n.nodeType === nodeType);
|
|
||||||
resolve(convertToResponse(node, index));
|
|
||||||
} else {
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
}, 10);
|
}, 10);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取按分类分组的节点定义
|
* 根据分类获取节点定义
|
||||||
* @returns Promise<Record<NodeCategory, NodeDefinitionResponse[]>>
|
|
||||||
*/
|
*/
|
||||||
export const getNodeDefinitionsByCategories = async (): Promise<Record<NodeCategory, NodeDefinitionResponse[]>> => {
|
export const getNodeDefinitionsByCategory = async (category: NodeCategory): Promise<WorkflowNodeDefinition[]> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const grouped = getNodesByCategories();
|
resolve(getNodesByCategory(category));
|
||||||
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);
|
}, 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';
|
|
||||||
|
|||||||
@ -1,6 +1,63 @@
|
|||||||
// 工作流节点定义相关类型
|
import { BaseResponse } from "@/types/base";
|
||||||
|
|
||||||
// 节点类型枚举
|
// JSON Schema 定义
|
||||||
|
export interface JSONSchema {
|
||||||
|
type: string;
|
||||||
|
properties?: Record<string, any>;
|
||||||
|
required?: string[];
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI 变量配置
|
||||||
|
export interface NodeSize {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PortStyle {
|
||||||
|
r: number;
|
||||||
|
fill: string;
|
||||||
|
stroke: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PortAttributes {
|
||||||
|
circle: PortStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PortPosition = 'left' | 'right' | 'top' | 'bottom';
|
||||||
|
|
||||||
|
export interface PortConfig {
|
||||||
|
attrs: PortAttributes;
|
||||||
|
position: PortPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PortGroups {
|
||||||
|
in?: PortConfig;
|
||||||
|
out?: PortConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NodeShape = 'rect' | 'circle' | 'diamond';
|
||||||
|
|
||||||
|
export interface NodeStyle {
|
||||||
|
fill: string;
|
||||||
|
icon: string;
|
||||||
|
stroke: string;
|
||||||
|
iconColor: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UIConfig {
|
||||||
|
size: NodeSize;
|
||||||
|
ports: {
|
||||||
|
groups: PortGroups;
|
||||||
|
};
|
||||||
|
shape: NodeShape;
|
||||||
|
style: NodeStyle;
|
||||||
|
position?: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点类型和分类(保持原有的枚举格式)
|
||||||
export enum NodeType {
|
export enum NodeType {
|
||||||
START_EVENT = 'START_EVENT',
|
START_EVENT = 'START_EVENT',
|
||||||
END_EVENT = 'END_EVENT',
|
END_EVENT = 'END_EVENT',
|
||||||
@ -13,7 +70,6 @@ export enum NodeType {
|
|||||||
CALL_ACTIVITY = 'CALL_ACTIVITY'
|
CALL_ACTIVITY = 'CALL_ACTIVITY'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 节点分类枚举
|
|
||||||
export enum NodeCategory {
|
export enum NodeCategory {
|
||||||
EVENT = 'EVENT',
|
EVENT = 'EVENT',
|
||||||
TASK = 'TASK',
|
TASK = 'TASK',
|
||||||
@ -21,98 +77,64 @@ export enum NodeCategory {
|
|||||||
CONTAINER = 'CONTAINER'
|
CONTAINER = 'CONTAINER'
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON Schema 定义
|
// 基础节点定义(只有基本配置)
|
||||||
export interface JsonSchemaProperty {
|
export interface BaseNodeDefinition {
|
||||||
type: string;
|
nodeCode: string;
|
||||||
title?: string;
|
nodeName: string;
|
||||||
description?: string;
|
nodeType: NodeType;
|
||||||
default?: any;
|
category: NodeCategory;
|
||||||
readOnly?: boolean;
|
description: string;
|
||||||
enum?: any[];
|
uiConfig: UIConfig;
|
||||||
enumNames?: string[];
|
configSchema: JSONSchema; // 基本配置Schema(包含基本信息+节点配置)
|
||||||
format?: string;
|
|
||||||
minimum?: number;
|
|
||||||
maximum?: number;
|
|
||||||
minLength?: number;
|
|
||||||
maxLength?: number;
|
|
||||||
pattern?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JsonSchema {
|
// 可配置节点定义(有3个TAB:基本配置、输入、输出)
|
||||||
type: string;
|
export interface ConfigurableNodeDefinition extends BaseNodeDefinition {
|
||||||
properties: Record<string, JsonSchemaProperty>;
|
inputMappingSchema?: JSONSchema; // 输入映射的Schema定义
|
||||||
required?: string[];
|
outputMappingSchema?: JSONSchema; // 输出映射的Schema定义
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 节点样式配置
|
// 工作流节点定义(联合类型)
|
||||||
export interface NodeStyle {
|
export type WorkflowNodeDefinition = BaseNodeDefinition | ConfigurableNodeDefinition;
|
||||||
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 {
|
export interface NodeInstanceData {
|
||||||
id: string;
|
nodeCode: string;
|
||||||
nodeCode: string;
|
nodeName: string;
|
||||||
nodeType: NodeType;
|
nodeType: NodeType;
|
||||||
nodeName: string;
|
category: NodeCategory;
|
||||||
position?: { x: number; y: number };
|
description?: string;
|
||||||
panelVariables?: Record<string, any>;
|
|
||||||
localVariables?: Record<string, any>;
|
// 运行时数据(key/value格式)
|
||||||
formVariables?: Record<string, any>;
|
config?: Record<string, any>; // 基本配置数据(包含基本信息+节点配置)
|
||||||
|
inputMapping?: Record<string, any>;
|
||||||
|
outputMapping?: Record<string, any>;
|
||||||
|
|
||||||
|
// UI位置信息
|
||||||
|
position: { x: number; y: number };
|
||||||
|
uiConfig: UIConfig; // 包含运行时可能更新的UI配置,如位置
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容现有API的响应格式
|
||||||
|
export interface NodeDefinitionResponse extends BaseResponse {
|
||||||
|
nodeCode: string;
|
||||||
|
nodeName: string;
|
||||||
|
nodeType: NodeType;
|
||||||
|
category: NodeCategory;
|
||||||
|
description: string;
|
||||||
|
uiConfig: UIConfig | null;
|
||||||
|
configSchema?: JSONSchema | null; // 基本配置Schema
|
||||||
|
|
||||||
|
// 兼容字段(保持现有组件正常工作)
|
||||||
|
panelVariablesSchema?: JSONSchema | null;
|
||||||
|
localVariablesSchema?: JSONSchema | null;
|
||||||
|
panelVariables?: Record<string, any>;
|
||||||
|
localVariables?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据转换器工具类型
|
||||||
|
export interface NodeFormData {
|
||||||
|
config?: Record<string, any>; // 基本配置表单数据(包含基本信息+节点配置)
|
||||||
|
inputMapping?: Record<string, any>;
|
||||||
|
outputMapping?: Record<string, any>;
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Graph, Edge } from '@antv/x6';
|
import { Graph, Edge } from '@antv/x6';
|
||||||
import type { NodeDefinitionResponse } from "../../nodes/nodeService";
|
import type { WorkflowNodeDefinition } from "../../nodes/types";
|
||||||
import { NodeStyleManager, PortManager, ContextMenuManager, EdgeStyleManager, SelectionManager } from './eventHandlers';
|
import { NodeStyleManager, PortManager, ContextMenuManager, EdgeStyleManager, SelectionManager } from './eventHandlers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7,14 +7,14 @@ import { NodeStyleManager, PortManager, ContextMenuManager, EdgeStyleManager, Se
|
|||||||
*/
|
*/
|
||||||
export class EventRegistrar {
|
export class EventRegistrar {
|
||||||
private graph: Graph;
|
private graph: Graph;
|
||||||
private nodeDefinitions: NodeDefinitionResponse[];
|
private nodeDefinitions: WorkflowNodeDefinition[];
|
||||||
private onNodeEdit: (cell: any, nodeDefinition?: NodeDefinitionResponse) => void;
|
private onNodeEdit: (cell: any, nodeDefinition?: WorkflowNodeDefinition) => void;
|
||||||
private onEdgeEdit: (edge: Edge) => void;
|
private onEdgeEdit: (edge: Edge) => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
graph: Graph,
|
graph: Graph,
|
||||||
nodeDefinitions: NodeDefinitionResponse[],
|
nodeDefinitions: WorkflowNodeDefinition[],
|
||||||
onNodeEdit: (cell: any, nodeDefinition?: NodeDefinitionResponse) => void,
|
onNodeEdit: (cell: any, nodeDefinition?: WorkflowNodeDefinition) => void,
|
||||||
onEdgeEdit: (edge: Edge) => void
|
onEdgeEdit: (edge: Edge) => void
|
||||||
) {
|
) {
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
@ -44,7 +44,7 @@ export class EventRegistrar {
|
|||||||
PortManager.showPorts(node.id);
|
PortManager.showPorts(node.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.graph.on('node:mouseleave', ({node}) => {
|
this.graph.on('node:mouseleave', ({node}: any) => {
|
||||||
NodeStyleManager.resetNodeStyle(node);
|
NodeStyleManager.resetNodeStyle(node);
|
||||||
PortManager.hidePorts(node.id);
|
PortManager.hidePorts(node.id);
|
||||||
});
|
});
|
||||||
@ -74,13 +74,7 @@ export class EventRegistrar {
|
|||||||
const nodeType = node.getProp('nodeType');
|
const nodeType = node.getProp('nodeType');
|
||||||
const nodeDefinition = this.nodeDefinitions.find(def => def.nodeType === nodeType);
|
const nodeDefinition = this.nodeDefinitions.find(def => def.nodeType === nodeType);
|
||||||
if (nodeDefinition) {
|
if (nodeDefinition) {
|
||||||
const savedConfig = node.getProp('workflowDefinitionNode');
|
this.onNodeEdit(node, nodeDefinition);
|
||||||
const mergedNodeDefinition = {
|
|
||||||
...nodeDefinition,
|
|
||||||
...savedConfig,
|
|
||||||
...node.getProp('graph') || {}
|
|
||||||
};
|
|
||||||
this.onNodeEdit(node, mergedNodeDefinition);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,78 +1,135 @@
|
|||||||
import {Graph} from '@antv/x6';
|
import { Graph } from '@antv/x6';
|
||||||
import {convertPortConfig} from '../constants';
|
import { WorkflowNodeDefinition, NodeInstanceData } from '../nodes/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加节点到图形
|
* 从节点定义创建新节点
|
||||||
* @param isNew 是否为新节点
|
|
||||||
* @param graph X6 Graph实例
|
|
||||||
* @param currentNodeDefinition 工作流节点定义
|
|
||||||
* @param allNodeDefinitions 工作流节点定义列表
|
|
||||||
* @param position 新节点的位置(可选)
|
|
||||||
* @returns 创建的节点实例
|
|
||||||
*/
|
*/
|
||||||
export const addNodeToGraph = (
|
export const createNodeFromDefinition = (
|
||||||
isNew: boolean,
|
graph: Graph,
|
||||||
graph: Graph,
|
nodeDefinition: WorkflowNodeDefinition,
|
||||||
currentNodeDefinition: any,
|
position: { x: number; y: number }
|
||||||
allNodeDefinitions: any,
|
|
||||||
position?: { x: number; y: number }
|
|
||||||
) => {
|
) => {
|
||||||
let nodeDefinition = allNodeDefinitions.find((def: any) => def.nodeType === currentNodeDefinition.nodeType);
|
const { uiConfig } = nodeDefinition;
|
||||||
|
|
||||||
if (!nodeDefinition) {
|
// 根据形状类型设置正确的 shape
|
||||||
console.error('找不到节点定义:', currentNodeDefinition.nodeType);
|
let shape = 'rect';
|
||||||
return null;
|
if (uiConfig.shape === 'circle') {
|
||||||
|
shape = 'circle';
|
||||||
|
} else if (uiConfig.shape === 'diamond') {
|
||||||
|
shape = 'polygon';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建节点配置
|
||||||
|
const nodeConfig = {
|
||||||
|
shape,
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
width: uiConfig.size.width,
|
||||||
|
height: uiConfig.size.height,
|
||||||
|
attrs: {
|
||||||
|
body: {
|
||||||
|
...uiConfig.style,
|
||||||
|
...(uiConfig.shape === 'diamond' ? {
|
||||||
|
refPoints: '0,10 10,0 20,10 10,20',
|
||||||
|
} : {})
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
text: nodeDefinition.nodeName,
|
||||||
|
fontSize: 12,
|
||||||
|
fill: '#000'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ports: convertPortConfig(uiConfig.ports),
|
||||||
|
// 同时设置为props和data,方便访问
|
||||||
|
nodeType: nodeDefinition.nodeType,
|
||||||
|
nodeCode: nodeDefinition.nodeCode,
|
||||||
|
data: {
|
||||||
|
nodeType: nodeDefinition.nodeType,
|
||||||
|
nodeCode: nodeDefinition.nodeCode,
|
||||||
|
nodeName: nodeDefinition.nodeName
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 合并UI变量:使用节点定义中的完整配置,但保留保存数据中的位置信息
|
return graph.addNode(nodeConfig);
|
||||||
let uiVariables = isNew
|
};
|
||||||
? nodeDefinition.uiVariables
|
|
||||||
: {
|
/**
|
||||||
...nodeDefinition.uiVariables, // 完整的UI配置
|
* 从保存的数据恢复节点
|
||||||
...currentNodeDefinition.uiVariables // 保存的位置信息(如果有的话)
|
*/
|
||||||
};
|
export const restoreNodeFromData = (
|
||||||
// 根据形状类型设置正确的 shape
|
graph: Graph,
|
||||||
let shape = 'rect'; // 默认使用矩形
|
nodeData: NodeInstanceData,
|
||||||
if (uiVariables.shape === 'circle') {
|
_nodeDefinition: WorkflowNodeDefinition
|
||||||
shape = 'circle';
|
) => {
|
||||||
} else if (uiVariables.shape === 'diamond') {
|
const { uiConfig } = nodeData;
|
||||||
shape = 'polygon';
|
|
||||||
}
|
// 根据形状类型设置正确的 shape
|
||||||
|
let shape = 'rect';
|
||||||
// 创建节点配置
|
if (uiConfig.shape === 'circle') {
|
||||||
const nodeConfig: any = {
|
shape = 'circle';
|
||||||
inherit: 'rect',
|
} else if (uiConfig.shape === 'diamond') {
|
||||||
width: uiVariables.size.width,
|
shape = 'polygon';
|
||||||
height: uiVariables.size.height,
|
}
|
||||||
attrs: {
|
|
||||||
body: {
|
// 创建节点配置
|
||||||
...uiVariables.style,
|
const nodeConfig = {
|
||||||
...(uiVariables.shape === 'diamond' ? {
|
id: nodeData.nodeCode, // 使用保存的ID
|
||||||
refPoints: '0,10 10,0 20,10 10,20',
|
shape,
|
||||||
} : {})
|
x: nodeData.position.x,
|
||||||
},
|
y: nodeData.position.y,
|
||||||
label: {
|
width: uiConfig.size.width,
|
||||||
text: isNew ? nodeDefinition.nodeName : currentNodeDefinition.nodeName
|
height: uiConfig.size.height,
|
||||||
},
|
attrs: {
|
||||||
},
|
body: {
|
||||||
shape,
|
...uiConfig.style,
|
||||||
nodeType: isNew ? nodeDefinition.nodeType : currentNodeDefinition.nodeType,
|
...(uiConfig.shape === 'diamond' ? {
|
||||||
nodeCode: nodeDefinition.nodeCode,
|
refPoints: '0,10 10,0 20,10 10,20',
|
||||||
ports: convertPortConfig(uiVariables.ports)
|
} : {})
|
||||||
};
|
},
|
||||||
|
label: {
|
||||||
// 为还原的节点设置ID,保持与保存数据的一致性
|
text: nodeData.nodeName,
|
||||||
if (!isNew && currentNodeDefinition.id) {
|
fontSize: 12,
|
||||||
nodeConfig.id = currentNodeDefinition.id;
|
fill: '#000'
|
||||||
}
|
},
|
||||||
|
},
|
||||||
const nodePosition = isNew ? position : currentNodeDefinition.uiVariables?.position;
|
ports: convertPortConfig(uiConfig.ports),
|
||||||
if (nodePosition) {
|
// 同时设置为props和data,方便访问
|
||||||
Object.assign(nodeConfig, nodePosition);
|
nodeType: nodeData.nodeType,
|
||||||
}
|
nodeCode: nodeData.nodeCode,
|
||||||
|
data: {
|
||||||
console.log('创建节点配置:', nodeConfig);
|
nodeType: nodeData.nodeType,
|
||||||
const node = graph.addNode(nodeConfig);
|
nodeCode: nodeData.nodeCode,
|
||||||
|
nodeName: nodeData.nodeName,
|
||||||
return node;
|
config: nodeData.config,
|
||||||
|
inputMapping: nodeData.inputMapping,
|
||||||
|
outputMapping: nodeData.outputMapping
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return graph.addNode(nodeConfig);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换端口配置为X6格式
|
||||||
|
*/
|
||||||
|
const convertPortConfig = (ports: any) => {
|
||||||
|
if (!ports?.groups) return { items: [] };
|
||||||
|
|
||||||
|
const groups: any = {};
|
||||||
|
const items: any[] = [];
|
||||||
|
|
||||||
|
Object.entries(ports.groups).forEach(([key, group]: [string, any]) => {
|
||||||
|
groups[key] = {
|
||||||
|
position: group.position,
|
||||||
|
attrs: {
|
||||||
|
circle: {
|
||||||
|
...group.attrs.circle,
|
||||||
|
magnet: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
items.push({ group: key });
|
||||||
|
});
|
||||||
|
|
||||||
|
return { groups, items };
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue
Block a user