1
This commit is contained in:
parent
20c4184d45
commit
3315114522
@ -1,7 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Modal, Form, Input, Tabs, Card, Button, Space, message } from 'antd';
|
import { Modal, Tabs, Button, Space, message } from 'antd';
|
||||||
import { SaveOutlined, ReloadOutlined } from '@ant-design/icons';
|
import { SaveOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||||
|
import { BetaSchemaForm } from '@ant-design/pro-components';
|
||||||
|
import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils';
|
||||||
import type { FlowNode, FlowNodeData } from '../types';
|
import type { FlowNode, FlowNodeData } from '../types';
|
||||||
|
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||||
|
import { isConfigurableNode } from '../nodes/types';
|
||||||
|
|
||||||
interface NodeConfigModalProps {
|
interface NodeConfigModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@ -10,7 +14,11 @@ interface NodeConfigModalProps {
|
|||||||
onOk: (nodeId: string, updatedData: Partial<FlowNodeData>) => void;
|
onOk: (nodeId: string, updatedData: Partial<FlowNodeData>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { TextArea } = Input;
|
interface FormData {
|
||||||
|
configs?: Record<string, any>;
|
||||||
|
inputMapping?: Record<string, any>;
|
||||||
|
outputMapping?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
||||||
visible,
|
visible,
|
||||||
@ -18,187 +26,176 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onOk
|
onOk
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState('basic');
|
const [activeTab, setActiveTab] = useState('config');
|
||||||
|
const [formData, setFormData] = useState<FormData>({});
|
||||||
|
|
||||||
|
// 获取节点定义
|
||||||
|
const nodeDefinition: WorkflowNodeDefinition | null = node?.data?.nodeDefinition || null;
|
||||||
|
|
||||||
// 重置表单数据
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible && node) {
|
if (visible && node && nodeDefinition) {
|
||||||
form.setFieldsValue({
|
// 从节点数据中获取现有配置
|
||||||
label: node.data.label,
|
const nodeData = node.data || {};
|
||||||
description: node.data.nodeDefinition?.description || '',
|
|
||||||
// 基础配置
|
// 准备默认的基本信息配置
|
||||||
timeout: node.data.configs?.timeout || 3600,
|
const defaultConfig = {
|
||||||
retryCount: node.data.configs?.retryCount || 0,
|
nodeName: nodeDefinition.nodeName, // 默认节点名称
|
||||||
priority: node.data.configs?.priority || 'normal',
|
nodeCode: nodeDefinition.nodeCode, // 默认节点编码
|
||||||
// 输入映射
|
description: nodeDefinition.description // 默认节点描述
|
||||||
inputMapping: JSON.stringify(node.data.inputMapping || {}, null, 2),
|
};
|
||||||
// 输出映射
|
|
||||||
outputMapping: JSON.stringify(node.data.outputMapping || {}, null, 2),
|
// 合并默认值和已保存的配置
|
||||||
|
setFormData({
|
||||||
|
configs: { ...defaultConfig, ...(nodeData.configs || {}) },
|
||||||
|
inputMapping: nodeData.inputMapping || {},
|
||||||
|
outputMapping: nodeData.outputMapping || {},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
form.resetFields();
|
setFormData({});
|
||||||
}
|
}
|
||||||
}, [visible, node, form]);
|
}, [visible, node, nodeDefinition]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = () => {
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// 解析JSON字符串
|
|
||||||
let inputMapping = {};
|
|
||||||
let outputMapping = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
inputMapping = values.inputMapping ? JSON.parse(values.inputMapping) : {};
|
|
||||||
} catch (error) {
|
|
||||||
message.error('输入映射JSON格式错误');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
outputMapping = values.outputMapping ? JSON.parse(values.outputMapping) : {};
|
|
||||||
} catch (error) {
|
|
||||||
message.error('输出映射JSON格式错误');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedData: Partial<FlowNodeData> = {
|
const updatedData: Partial<FlowNodeData> = {
|
||||||
label: values.label,
|
// 更新节点标签为配置中的节点名称
|
||||||
configs: {
|
label: formData.configs?.nodeName || node.data.label,
|
||||||
...node.data.configs,
|
configs: formData.configs,
|
||||||
timeout: values.timeout,
|
inputMapping: formData.inputMapping,
|
||||||
retryCount: values.retryCount,
|
outputMapping: formData.outputMapping,
|
||||||
priority: values.priority,
|
|
||||||
},
|
|
||||||
inputMapping,
|
|
||||||
outputMapping,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onOk(node.id, updatedData);
|
onOk(node.id, updatedData);
|
||||||
message.success('节点配置保存成功');
|
message.success('节点配置保存成功');
|
||||||
onCancel();
|
onCancel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存节点配置失败:', error);
|
if (error instanceof Error) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
form.resetFields();
|
if (nodeDefinition) {
|
||||||
if (node) {
|
const defaultConfig = {
|
||||||
form.setFieldsValue({
|
nodeName: nodeDefinition.nodeName,
|
||||||
label: node.data.label,
|
nodeCode: nodeDefinition.nodeCode,
|
||||||
description: node.data.nodeDefinition?.description || '',
|
description: nodeDefinition.description
|
||||||
timeout: 3600,
|
};
|
||||||
retryCount: 0,
|
|
||||||
priority: 'normal',
|
setFormData({
|
||||||
inputMapping: '{}',
|
configs: defaultConfig,
|
||||||
outputMapping: '{}',
|
inputMapping: {},
|
||||||
|
outputMapping: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderBasicConfig = () => (
|
const handleConfigChange = (values: Record<string, any>) => {
|
||||||
<Card size="small" title="基本信息">
|
setFormData(prev => ({
|
||||||
<Form.Item
|
...prev,
|
||||||
label="节点名称"
|
configs: values
|
||||||
name="label"
|
}));
|
||||||
rules={[{ required: true, message: '请输入节点名称' }]}
|
};
|
||||||
>
|
|
||||||
<Input placeholder="请输入节点名称" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="节点描述" name="description">
|
const handleInputMappingChange = (values: Record<string, any>) => {
|
||||||
<TextArea
|
setFormData(prev => ({
|
||||||
placeholder="请输入节点描述"
|
...prev,
|
||||||
rows={3}
|
inputMapping: values
|
||||||
maxLength={500}
|
}));
|
||||||
showCount
|
};
|
||||||
|
|
||||||
|
const handleOutputMappingChange = (values: Record<string, any>) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
outputMapping: values
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!nodeDefinition) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建Tabs配置
|
||||||
|
const tabItems = [
|
||||||
|
{
|
||||||
|
key: 'config',
|
||||||
|
label: '基本配置',
|
||||||
|
children: (
|
||||||
|
<div style={{ padding: '16px 0' }}>
|
||||||
|
<BetaSchemaForm
|
||||||
|
key={`configs-${node?.id}-${JSON.stringify(formData.configs)}`}
|
||||||
|
layoutType="Form"
|
||||||
|
columns={convertJsonSchemaToColumns(nodeDefinition.configSchema as any)}
|
||||||
|
initialValues={formData.configs}
|
||||||
|
onValuesChange={(_, allValues) => handleConfigChange(allValues)}
|
||||||
|
submitter={false}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
<Form.Item
|
// 如果是可配置节点,添加输入和输出映射TAB
|
||||||
label="超时时间(秒)"
|
if (isConfigurableNode(nodeDefinition)) {
|
||||||
name="timeout"
|
if (nodeDefinition.inputMappingSchema) {
|
||||||
rules={[{ required: true, message: '请输入超时时间' }]}
|
tabItems.push({
|
||||||
>
|
key: 'input',
|
||||||
<Input type="number" min={1} placeholder="3600" />
|
label: '输入映射',
|
||||||
</Form.Item>
|
children: (
|
||||||
|
<div style={{ padding: '16px 0' }}>
|
||||||
<Form.Item
|
<BetaSchemaForm
|
||||||
label="重试次数"
|
key={`input-${node?.id}-${JSON.stringify(formData.inputMapping)}`}
|
||||||
name="retryCount"
|
layoutType="Form"
|
||||||
>
|
columns={convertJsonSchemaToColumns(nodeDefinition.inputMappingSchema as any)}
|
||||||
<Input type="number" min={0} max={10} placeholder="0" />
|
initialValues={formData.inputMapping}
|
||||||
</Form.Item>
|
onValuesChange={(_, allValues) => handleInputMappingChange(allValues)}
|
||||||
|
submitter={false}
|
||||||
<Form.Item
|
|
||||||
label="优先级"
|
|
||||||
name="priority"
|
|
||||||
>
|
|
||||||
<Input placeholder="normal" />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderInputMapping = () => (
|
|
||||||
<Card size="small" title="输入参数映射">
|
|
||||||
<Form.Item
|
|
||||||
label="输入映射配置"
|
|
||||||
name="inputMapping"
|
|
||||||
extra="请输入有效的JSON格式,用于配置节点的输入参数映射"
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
placeholder={`示例:
|
|
||||||
{
|
|
||||||
"param1": "\${workflow.variable1}",
|
|
||||||
"param2": "固定值",
|
|
||||||
"param3": "\${previous.output.result}"
|
|
||||||
}`}
|
|
||||||
rows={12}
|
|
||||||
style={{ fontFamily: 'Monaco, Consolas, monospace' }}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</div>
|
||||||
</Card>
|
),
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const renderOutputMapping = () => (
|
if (nodeDefinition.outputMappingSchema) {
|
||||||
<Card size="small" title="输出参数映射">
|
tabItems.push({
|
||||||
<Form.Item
|
key: 'output',
|
||||||
label="输出映射配置"
|
label: '输出映射',
|
||||||
name="outputMapping"
|
children: (
|
||||||
extra="请输入有效的JSON格式,用于配置节点的输出参数映射"
|
<div style={{ padding: '16px 0' }}>
|
||||||
>
|
<BetaSchemaForm
|
||||||
<TextArea
|
key={`output-${node?.id}-${JSON.stringify(formData.outputMapping)}`}
|
||||||
placeholder={`示例:
|
layoutType="Form"
|
||||||
{
|
columns={convertJsonSchemaToColumns(nodeDefinition.outputMappingSchema as any)}
|
||||||
"result": "\${task.output.result}",
|
initialValues={formData.outputMapping}
|
||||||
"status": "\${task.status}",
|
onValuesChange={(_, allValues) => handleOutputMappingChange(allValues)}
|
||||||
"message": "\${task.output.message}"
|
submitter={false}
|
||||||
}`}
|
|
||||||
rows={12}
|
|
||||||
style={{ fontFamily: 'Monaco, Consolas, monospace' }}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</div>
|
||||||
</Card>
|
),
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={`配置节点: ${node?.data?.label || '未知节点'}`}
|
title={`编辑节点 - ${nodeDefinition.nodeName}`}
|
||||||
open={visible}
|
open={visible}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
width={800}
|
width={800}
|
||||||
style={{ top: 20 }}
|
style={{ top: 20 }}
|
||||||
footer={
|
footer={
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={onCancel}>取消</Button>
|
<Button onClick={onCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
icon={<ReloadOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
@ -216,39 +213,13 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form
|
<div style={{ maxHeight: '70vh', overflow: 'auto' }}>
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
initialValues={{
|
|
||||||
timeout: 3600,
|
|
||||||
retryCount: 0,
|
|
||||||
priority: 'normal',
|
|
||||||
inputMapping: '{}',
|
|
||||||
outputMapping: '{}'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeTab}
|
activeKey={activeTab}
|
||||||
onChange={setActiveTab}
|
onChange={setActiveTab}
|
||||||
items={[
|
items={tabItems}
|
||||||
{
|
|
||||||
key: 'basic',
|
|
||||||
label: '基本配置',
|
|
||||||
children: renderBasicConfig()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'input',
|
|
||||||
label: '输入映射',
|
|
||||||
children: renderInputMapping()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'output',
|
|
||||||
label: '输出映射',
|
|
||||||
children: renderOutputMapping()
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Form>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,82 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Divider } from 'antd';
|
import { NodeCategory } from '../types';
|
||||||
import { NodeType, NodeCategory, type NodeDefinitionData } from '../types';
|
import { NODE_DEFINITIONS } from '../nodes/definitions';
|
||||||
|
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||||
|
|
||||||
// 节点定义数据
|
// 图标映射函数
|
||||||
const nodeDefinitions: NodeDefinitionData[] = [
|
const getNodeIcon = (iconName: string): string => {
|
||||||
{
|
const iconMap: Record<string, string> = {
|
||||||
nodeCode: 'start_event',
|
'play-circle': '▶️',
|
||||||
nodeName: '开始事件',
|
'stop-circle': '⏹️',
|
||||||
nodeType: NodeType.START_EVENT,
|
'user': '👤',
|
||||||
category: NodeCategory.EVENT,
|
'api': '⚙️',
|
||||||
description: '工作流开始节点',
|
'code': '📜',
|
||||||
icon: '▶️',
|
'build': '🚀',
|
||||||
color: '#10b981'
|
'jenkins': '🔨',
|
||||||
},
|
'gateway': '💎'
|
||||||
{
|
};
|
||||||
nodeCode: 'end_event',
|
return iconMap[iconName] || '📋';
|
||||||
nodeName: '结束事件',
|
};
|
||||||
nodeType: NodeType.END_EVENT,
|
|
||||||
category: NodeCategory.EVENT,
|
|
||||||
description: '工作流结束节点',
|
|
||||||
icon: '⏹️',
|
|
||||||
color: '#ef4444'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodeCode: 'user_task',
|
|
||||||
nodeName: '用户任务',
|
|
||||||
nodeType: NodeType.USER_TASK,
|
|
||||||
category: NodeCategory.TASK,
|
|
||||||
description: '需要用户手动处理的任务',
|
|
||||||
icon: '👤',
|
|
||||||
color: '#6366f1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodeCode: 'service_task',
|
|
||||||
nodeName: '服务任务',
|
|
||||||
nodeType: NodeType.SERVICE_TASK,
|
|
||||||
category: NodeCategory.TASK,
|
|
||||||
description: '自动执行的服务任务',
|
|
||||||
icon: '⚙️',
|
|
||||||
color: '#f59e0b'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodeCode: 'script_task',
|
|
||||||
nodeName: '脚本任务',
|
|
||||||
nodeType: NodeType.SCRIPT_TASK,
|
|
||||||
category: NodeCategory.TASK,
|
|
||||||
description: '执行脚本代码的任务',
|
|
||||||
icon: '📜',
|
|
||||||
color: '#8b5cf6'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodeCode: 'deploy_node',
|
|
||||||
nodeName: '部署节点',
|
|
||||||
nodeType: NodeType.DEPLOY_NODE,
|
|
||||||
category: NodeCategory.TASK,
|
|
||||||
description: '应用部署任务',
|
|
||||||
icon: '🚀',
|
|
||||||
color: '#06b6d4'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodeCode: 'jenkins_build',
|
|
||||||
nodeName: 'Jenkins构建',
|
|
||||||
nodeType: NodeType.JENKINS_BUILD,
|
|
||||||
category: NodeCategory.TASK,
|
|
||||||
description: 'Jenkins构建任务',
|
|
||||||
icon: '🔨',
|
|
||||||
color: '#dc2626'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nodeCode: 'gateway_node',
|
|
||||||
nodeName: '网关节点',
|
|
||||||
nodeType: NodeType.GATEWAY_NODE,
|
|
||||||
category: NodeCategory.GATEWAY,
|
|
||||||
description: '条件分支网关',
|
|
||||||
icon: '💎',
|
|
||||||
color: '#7c3aed'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
interface NodePanelProps {
|
interface NodePanelProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -84,16 +24,16 @@ interface NodePanelProps {
|
|||||||
|
|
||||||
const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||||
// 按分类分组节点
|
// 按分类分组节点
|
||||||
const nodesByCategory = nodeDefinitions.reduce((acc, node) => {
|
const nodesByCategory = 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, NodeDefinitionData[]>);
|
}, {} as Record<NodeCategory, WorkflowNodeDefinition[]>);
|
||||||
|
|
||||||
// 拖拽开始处理
|
// 拖拽开始处理
|
||||||
const handleDragStart = (event: React.DragEvent, nodeDefinition: NodeDefinitionData) => {
|
const handleDragStart = (event: React.DragEvent, nodeDefinition: WorkflowNodeDefinition) => {
|
||||||
event.dataTransfer.setData('application/reactflow', JSON.stringify({
|
event.dataTransfer.setData('application/reactflow', JSON.stringify({
|
||||||
nodeType: nodeDefinition.nodeType,
|
nodeType: nodeDefinition.nodeType,
|
||||||
nodeDefinition
|
nodeDefinition
|
||||||
@ -102,11 +42,10 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 渲染节点项
|
// 渲染节点项
|
||||||
const renderNodeItem = (nodeDefinition: NodeDefinitionData) => (
|
const renderNodeItem = (nodeDefinition: WorkflowNodeDefinition) => (
|
||||||
<div
|
<div
|
||||||
key={nodeDefinition.nodeCode}
|
key={nodeDefinition.nodeCode}
|
||||||
draggable
|
draggable
|
||||||
onDragStart={(e) => handleDragStart(e, nodeDefinition)}
|
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@ -121,7 +60,7 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.background = '#f9fafb';
|
e.currentTarget.style.background = '#f9fafb';
|
||||||
e.currentTarget.style.borderColor = nodeDefinition.color;
|
e.currentTarget.style.borderColor = nodeDefinition.uiConfig.style.fill;
|
||||||
e.currentTarget.style.transform = 'translateX(2px)';
|
e.currentTarget.style.transform = 'translateX(2px)';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
@ -140,9 +79,10 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
|||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
width: '20px',
|
width: '20px',
|
||||||
textAlign: 'center'
|
textAlign: 'center',
|
||||||
|
color: nodeDefinition.uiConfig.style.fill
|
||||||
}}>
|
}}>
|
||||||
{nodeDefinition.icon}
|
{getNodeIcon(nodeDefinition.uiConfig.style.icon)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|||||||
@ -1,238 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Card, Form, Input, Select, Button, Space, message, Divider } from 'antd';
|
|
||||||
import { SaveOutlined, ClearOutlined } from '@ant-design/icons';
|
|
||||||
import type { FlowNode, FlowEdge } from '../types';
|
|
||||||
|
|
||||||
interface PropertyPanelProps {
|
|
||||||
selectedNode?: FlowNode | null;
|
|
||||||
selectedEdge?: FlowEdge | null;
|
|
||||||
onNodeUpdate?: (nodeId: string, updates: any) => void;
|
|
||||||
onEdgeUpdate?: (edgeId: string, updates: any) => void;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|
||||||
selectedNode,
|
|
||||||
selectedEdge,
|
|
||||||
onNodeUpdate,
|
|
||||||
onEdgeUpdate,
|
|
||||||
className = ''
|
|
||||||
}) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedNode) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
label: selectedNode.data.label,
|
|
||||||
timeout: selectedNode.data.configs?.timeout || 3600,
|
|
||||||
retryCount: selectedNode.data.configs?.retryCount || 0,
|
|
||||||
priority: selectedNode.data.configs?.priority || 'normal',
|
|
||||||
});
|
|
||||||
} else if (selectedEdge) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
label: selectedEdge.data?.label || '',
|
|
||||||
conditionType: selectedEdge.data?.condition?.type || 'DEFAULT',
|
|
||||||
expression: selectedEdge.data?.condition?.expression || '',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
setHasChanges(false);
|
|
||||||
}, [selectedNode, selectedEdge, form]);
|
|
||||||
|
|
||||||
// 监听表单变化
|
|
||||||
const handleFormChange = () => {
|
|
||||||
setHasChanges(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
const handleSave = async () => {
|
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
|
|
||||||
if (selectedNode && onNodeUpdate) {
|
|
||||||
const updates = {
|
|
||||||
label: values.label,
|
|
||||||
configs: {
|
|
||||||
...selectedNode.data.configs,
|
|
||||||
timeout: values.timeout,
|
|
||||||
retryCount: values.retryCount,
|
|
||||||
priority: values.priority,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
onNodeUpdate(selectedNode.id, updates);
|
|
||||||
message.success('节点配置已保存');
|
|
||||||
} else if (selectedEdge && onEdgeUpdate) {
|
|
||||||
const updates = {
|
|
||||||
label: values.label,
|
|
||||||
condition: {
|
|
||||||
type: values.conditionType,
|
|
||||||
expression: values.expression,
|
|
||||||
priority: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
onEdgeUpdate(selectedEdge.id, updates);
|
|
||||||
message.success('连接配置已保存');
|
|
||||||
}
|
|
||||||
|
|
||||||
setHasChanges(false);
|
|
||||||
} catch (error) {
|
|
||||||
message.error('保存失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 清空选择
|
|
||||||
const handleClear = () => {
|
|
||||||
form.resetFields();
|
|
||||||
setHasChanges(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 渲染节点属性
|
|
||||||
const renderNodeProperties = () => {
|
|
||||||
if (!selectedNode) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card size="small" title={`节点属性: ${selectedNode.data.nodeType}`}>
|
|
||||||
<Form.Item label="节点名称" name="label" rules={[{ required: true }]}>
|
|
||||||
<Input placeholder="请输入节点名称" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="超时时间(秒)" name="timeout">
|
|
||||||
<Input type="number" min={1} placeholder="3600" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="重试次数" name="retryCount">
|
|
||||||
<Input type="number" min={0} max={10} placeholder="0" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="优先级" name="priority">
|
|
||||||
<Select placeholder="选择优先级">
|
|
||||||
<Select.Option value="low">低</Select.Option>
|
|
||||||
<Select.Option value="normal">普通</Select.Option>
|
|
||||||
<Select.Option value="high">高</Select.Option>
|
|
||||||
<Select.Option value="urgent">紧急</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 渲染边属性
|
|
||||||
const renderEdgeProperties = () => {
|
|
||||||
if (!selectedEdge) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card size="small" title="连接属性">
|
|
||||||
<Form.Item label="连接名称" name="label">
|
|
||||||
<Input placeholder="请输入连接名称" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="条件类型" name="conditionType">
|
|
||||||
<Select placeholder="选择条件类型">
|
|
||||||
<Select.Option value="DEFAULT">默认</Select.Option>
|
|
||||||
<Select.Option value="EXPRESSION">表达式</Select.Option>
|
|
||||||
<Select.Option value="SCRIPT">脚本</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="条件表达式" name="expression">
|
|
||||||
<Input.TextArea
|
|
||||||
placeholder="请输入条件表达式"
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`property-panel ${className}`} style={{
|
|
||||||
width: '300px',
|
|
||||||
height: '100%',
|
|
||||||
background: '#f8fafc',
|
|
||||||
borderLeft: '1px solid #e5e7eb',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
overflow: 'hidden'
|
|
||||||
}}>
|
|
||||||
<div style={{
|
|
||||||
padding: '16px',
|
|
||||||
background: '#ffffff',
|
|
||||||
borderBottom: '1px solid #e5e7eb',
|
|
||||||
flexShrink: 0
|
|
||||||
}}>
|
|
||||||
<h3 style={{
|
|
||||||
margin: 0,
|
|
||||||
fontSize: '14px',
|
|
||||||
fontWeight: '600',
|
|
||||||
color: '#374151'
|
|
||||||
}}>
|
|
||||||
属性面板
|
|
||||||
</h3>
|
|
||||||
<p style={{
|
|
||||||
margin: '4px 0 0 0',
|
|
||||||
fontSize: '12px',
|
|
||||||
color: '#6b7280'
|
|
||||||
}}>
|
|
||||||
{selectedNode ? '节点配置' : selectedEdge ? '连接配置' : '请选择节点或连接'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ flex: 1, overflow: 'auto', padding: '16px' }}>
|
|
||||||
{!selectedNode && !selectedEdge && (
|
|
||||||
<div style={{
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#9ca3af',
|
|
||||||
fontSize: '14px',
|
|
||||||
marginTop: '40px'
|
|
||||||
}}>
|
|
||||||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>📋</div>
|
|
||||||
<p>请选择一个节点或连接</p>
|
|
||||||
<p style={{ fontSize: '12px' }}>点击节点或连接来编辑属性</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
onValuesChange={handleFormChange}
|
|
||||||
>
|
|
||||||
{renderNodeProperties()}
|
|
||||||
{renderEdgeProperties()}
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(selectedNode || selectedEdge) && (
|
|
||||||
<div style={{
|
|
||||||
padding: '16px',
|
|
||||||
background: '#ffffff',
|
|
||||||
borderTop: '1px solid #e5e7eb',
|
|
||||||
flexShrink: 0
|
|
||||||
}}>
|
|
||||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
|
||||||
<Button
|
|
||||||
icon={<ClearOutlined />}
|
|
||||||
onClick={handleClear}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<SaveOutlined />}
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={!hasChanges}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PropertyPanel;
|
|
||||||
@ -7,8 +7,8 @@ import WorkflowToolbar from './components/WorkflowToolbar';
|
|||||||
import NodePanel from './components/NodePanel';
|
import NodePanel from './components/NodePanel';
|
||||||
import FlowCanvas from './components/FlowCanvas';
|
import FlowCanvas from './components/FlowCanvas';
|
||||||
import NodeConfigModal from './components/NodeConfigModal';
|
import NodeConfigModal from './components/NodeConfigModal';
|
||||||
import PropertyPanel from './components/PropertyPanel';
|
import type { FlowNode, FlowEdge, FlowNodeData } from './types';
|
||||||
import type { FlowNode, FlowEdge, DragNodeData, FlowNodeData } from './types';
|
import type { WorkflowNodeDefinition } from './nodes/types';
|
||||||
import { NodeType } from './types';
|
import { NodeType } from './types';
|
||||||
import { useWorkflowSave } from './hooks/useWorkflowSave';
|
import { useWorkflowSave } from './hooks/useWorkflowSave';
|
||||||
import { useWorkflowLoad } from './hooks/useWorkflowLoad';
|
import { useWorkflowLoad } from './hooks/useWorkflowLoad';
|
||||||
@ -40,12 +40,11 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
|
|
||||||
// 节点配置模态框状态
|
// 节点配置模态框状态
|
||||||
const [configModalVisible, setConfigModalVisible] = useState(false);
|
const [configModalVisible, setConfigModalVisible] = useState(false);
|
||||||
const [selectedNode, setSelectedNode] = useState<FlowNode | null>(null);
|
const [configNode, setConfigNode] = useState<FlowNode | null>(null);
|
||||||
const [selectedEdge, setSelectedEdge] = useState<FlowEdge | null>(null);
|
|
||||||
|
|
||||||
// 保存和加载hooks
|
// 保存和加载hooks
|
||||||
const { saving, hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave();
|
const { hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave();
|
||||||
const { loading: loadingWorkflow, workflowDefinition, loadWorkflow } = useWorkflowLoad();
|
const { workflowDefinition, loadWorkflow } = useWorkflowLoad();
|
||||||
|
|
||||||
// 加载工作流数据
|
// 加载工作流数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -230,7 +229,7 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
if (!dragData) return;
|
if (!dragData) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { nodeType, nodeDefinition }: DragNodeData = JSON.parse(dragData);
|
const { nodeType, nodeDefinition }: { nodeType: string; nodeDefinition: WorkflowNodeDefinition } = JSON.parse(dragData);
|
||||||
|
|
||||||
// 根据React Flow官方文档,screenToFlowPosition会自动处理所有边界计算
|
// 根据React Flow官方文档,screenToFlowPosition会自动处理所有边界计算
|
||||||
// 不需要手动减去容器边界!
|
// 不需要手动减去容器边界!
|
||||||
@ -245,11 +244,20 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
position,
|
position,
|
||||||
data: {
|
data: {
|
||||||
label: nodeDefinition.nodeName,
|
label: nodeDefinition.nodeName,
|
||||||
nodeType,
|
nodeType: nodeDefinition.nodeType,
|
||||||
category: nodeDefinition.category,
|
category: nodeDefinition.category,
|
||||||
icon: nodeDefinition.icon,
|
icon: nodeDefinition.uiConfig.style.icon,
|
||||||
color: nodeDefinition.color,
|
color: nodeDefinition.uiConfig.style.fill,
|
||||||
nodeDefinition
|
// 保存原始节点定义引用,用于配置
|
||||||
|
nodeDefinition,
|
||||||
|
// 初始化配置数据
|
||||||
|
configs: {
|
||||||
|
nodeName: nodeDefinition.nodeName,
|
||||||
|
nodeCode: nodeDefinition.nodeCode,
|
||||||
|
description: nodeDefinition.description
|
||||||
|
},
|
||||||
|
inputMapping: {},
|
||||||
|
outputMapping: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -261,31 +269,22 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [screenToFlowPosition, setNodes]);
|
}, [screenToFlowPosition, setNodes]);
|
||||||
|
|
||||||
// 处理节点点击 - 单击选择,双击打开配置面板
|
// 处理节点双击 - 打开配置面板
|
||||||
const handleNodeClick = useCallback((event: React.MouseEvent, node: FlowNode) => {
|
const handleNodeClick = useCallback((event: React.MouseEvent, node: FlowNode) => {
|
||||||
console.log('节点点击:', node);
|
// 只处理双击事件
|
||||||
|
|
||||||
// 清除边选择
|
|
||||||
setSelectedEdge(null);
|
|
||||||
|
|
||||||
// 检查是否是双击
|
|
||||||
if (event.detail === 2) {
|
if (event.detail === 2) {
|
||||||
setSelectedNode(node);
|
console.log('双击节点,打开配置:', node);
|
||||||
|
setConfigNode(node);
|
||||||
setConfigModalVisible(true);
|
setConfigModalVisible(true);
|
||||||
} else {
|
|
||||||
setSelectedNode(node);
|
|
||||||
message.info(`选择了节点: ${node.data.label}`);
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 处理边点击
|
// 处理边双击 - 暂时只记录日志
|
||||||
const handleEdgeClick = useCallback((event: React.MouseEvent, edge: FlowEdge) => {
|
const handleEdgeClick = useCallback((event: React.MouseEvent, edge: FlowEdge) => {
|
||||||
console.log('边点击:', edge);
|
if (event.detail === 2) {
|
||||||
|
console.log('双击边:', edge);
|
||||||
// 清除节点选择
|
message.info(`双击了连接: ${edge.data?.label || '连接'},配置功能待实现`);
|
||||||
setSelectedNode(null);
|
}
|
||||||
setSelectedEdge(edge);
|
|
||||||
message.info(`选择了连接: ${edge.data?.label || '连接'}`);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 处理节点配置更新
|
// 处理节点配置更新
|
||||||
@ -306,28 +305,10 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
markUnsaved(); // 标记有未保存更改
|
markUnsaved(); // 标记有未保存更改
|
||||||
}, [setNodes, markUnsaved]);
|
}, [setNodes, markUnsaved]);
|
||||||
|
|
||||||
// 处理边配置更新
|
|
||||||
const handleEdgeConfigUpdate = useCallback((edgeId: string, updatedData: any) => {
|
|
||||||
setEdges((edges) =>
|
|
||||||
edges.map((edge) =>
|
|
||||||
edge.id === edgeId
|
|
||||||
? {
|
|
||||||
...edge,
|
|
||||||
data: {
|
|
||||||
...edge.data,
|
|
||||||
...updatedData,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: edge
|
|
||||||
)
|
|
||||||
);
|
|
||||||
markUnsaved(); // 标记有未保存更改
|
|
||||||
}, [setEdges, markUnsaved]);
|
|
||||||
|
|
||||||
// 关闭配置模态框
|
// 关闭配置模态框
|
||||||
const handleCloseConfigModal = useCallback(() => {
|
const handleCloseConfigModal = useCallback(() => {
|
||||||
setConfigModalVisible(false);
|
setConfigModalVisible(false);
|
||||||
setSelectedNode(null);
|
setConfigNode(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -343,7 +324,6 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
title={`${workflowTitle}${hasUnsavedChanges ? ' *' : ''}`}
|
title={`${workflowTitle}${hasUnsavedChanges ? ' *' : ''}`}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
saving={saving}
|
|
||||||
onUndo={handleUndo}
|
onUndo={handleUndo}
|
||||||
onRedo={handleRedo}
|
onRedo={handleRedo}
|
||||||
onCopy={handleCopy}
|
onCopy={handleCopy}
|
||||||
@ -376,20 +356,12 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
className="workflow-canvas"
|
className="workflow-canvas"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 属性面板 */}
|
|
||||||
<PropertyPanel
|
|
||||||
selectedNode={selectedNode}
|
|
||||||
selectedEdge={selectedEdge}
|
|
||||||
onNodeUpdate={handleNodeConfigUpdate}
|
|
||||||
onEdgeUpdate={handleEdgeConfigUpdate}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 节点配置弹窗 */}
|
{/* 节点配置弹窗 */}
|
||||||
<NodeConfigModal
|
<NodeConfigModal
|
||||||
visible={configModalVisible}
|
visible={configModalVisible}
|
||||||
node={selectedNode}
|
node={configNode}
|
||||||
onCancel={handleCloseConfigModal}
|
onCancel={handleCloseConfigModal}
|
||||||
onOk={handleNodeConfigUpdate}
|
onOk={handleNodeConfigUpdate}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -0,0 +1,189 @@
|
|||||||
|
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部署任务节点定义
|
||||||
|
* 可配置节点,支持配置、输入映射、输出映射
|
||||||
|
*/
|
||||||
|
export const DeployNode: ConfigurableNodeDefinition = {
|
||||||
|
nodeCode: "DEPLOY_NODE",
|
||||||
|
nodeName: "构建任务",
|
||||||
|
nodeType: NodeType.DEPLOY_NODE,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: "执行应用构建和部署任务",
|
||||||
|
|
||||||
|
// UI 配置
|
||||||
|
uiConfig: {
|
||||||
|
size: {
|
||||||
|
width: 120,
|
||||||
|
height: 60
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#1890ff',
|
||||||
|
stroke: '#0050b3',
|
||||||
|
strokeWidth: 2,
|
||||||
|
icon: 'build',
|
||||||
|
iconColor: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 基本配置Schema - 设计时用于生成表单,保存时转为key/value(包含基本信息+节点配置)
|
||||||
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "节点的基本信息和构建任务的配置参数",
|
||||||
|
properties: {
|
||||||
|
// 基本信息
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称",
|
||||||
|
default: "构建任务"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符",
|
||||||
|
default: "DEPLOY_NODE"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明",
|
||||||
|
default: "执行应用构建和部署任务"
|
||||||
|
},
|
||||||
|
// 节点配置
|
||||||
|
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"]
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import {BaseNodeDefinition, NodeType, NodeCategory} from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束事件节点定义
|
||||||
|
* 简单节点,无需额外配置
|
||||||
|
*/
|
||||||
|
export const EndEventNode: BaseNodeDefinition = {
|
||||||
|
nodeCode: "END_EVENT",
|
||||||
|
nodeName: "结束",
|
||||||
|
nodeType: NodeType.END_EVENT,
|
||||||
|
category: NodeCategory.EVENT,
|
||||||
|
description: "工作流的结束节点",
|
||||||
|
|
||||||
|
// UI 配置
|
||||||
|
uiConfig: {
|
||||||
|
size: {
|
||||||
|
width: 80,
|
||||||
|
height: 50
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#ff4d4f',
|
||||||
|
stroke: '#cf1322',
|
||||||
|
strokeWidth: 2,
|
||||||
|
icon: 'stop-circle',
|
||||||
|
iconColor: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 基本配置Schema - 用于生成"基本配置"TAB(包含基本信息)
|
||||||
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "节点的基本配置信息",
|
||||||
|
properties: {
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称",
|
||||||
|
default: "结束"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符",
|
||||||
|
default: "END_EVENT"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明",
|
||||||
|
default: "工作流的结束节点"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["nodeName", "nodeCode"]
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,180 @@
|
|||||||
|
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务任务节点定义
|
||||||
|
* 可配置节点,支持配置、输入映射、输出映射
|
||||||
|
*/
|
||||||
|
export const ServiceTaskNode: ConfigurableNodeDefinition = {
|
||||||
|
nodeCode: "SERVICE_TASK",
|
||||||
|
nodeName: "服务任务",
|
||||||
|
nodeType: NodeType.SERVICE_TASK,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: "自动执行的服务任务",
|
||||||
|
|
||||||
|
// UI 配置
|
||||||
|
uiConfig: {
|
||||||
|
size: {
|
||||||
|
width: 120,
|
||||||
|
height: 60
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#fa8c16',
|
||||||
|
stroke: '#d46b08',
|
||||||
|
strokeWidth: 2,
|
||||||
|
icon: 'api',
|
||||||
|
iconColor: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 基本配置Schema
|
||||||
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "服务任务的基本配置信息",
|
||||||
|
properties: {
|
||||||
|
// 基本信息
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称",
|
||||||
|
default: "服务任务"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符",
|
||||||
|
default: "SERVICE_TASK"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明",
|
||||||
|
default: "自动执行的服务任务"
|
||||||
|
},
|
||||||
|
// 节点配置
|
||||||
|
serviceUrl: {
|
||||||
|
type: "string",
|
||||||
|
title: "服务URL",
|
||||||
|
description: "调用的服务接口地址",
|
||||||
|
default: "https://api.example.com/service"
|
||||||
|
},
|
||||||
|
httpMethod: {
|
||||||
|
type: "string",
|
||||||
|
title: "HTTP方法",
|
||||||
|
description: "HTTP请求方法",
|
||||||
|
enum: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||||
|
default: "POST"
|
||||||
|
},
|
||||||
|
timeout: {
|
||||||
|
type: "number",
|
||||||
|
title: "超时时间(秒)",
|
||||||
|
description: "服务调用超时时间",
|
||||||
|
default: 30,
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 300
|
||||||
|
},
|
||||||
|
retryCount: {
|
||||||
|
type: "number",
|
||||||
|
title: "重试次数",
|
||||||
|
description: "失败时的重试次数",
|
||||||
|
default: 3,
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 10
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
type: "object",
|
||||||
|
title: "请求头",
|
||||||
|
description: "HTTP请求头配置",
|
||||||
|
default: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async: {
|
||||||
|
type: "boolean",
|
||||||
|
title: "异步执行",
|
||||||
|
description: "是否异步执行服务调用",
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["nodeName", "nodeCode", "serviceUrl", "httpMethod"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输入映射Schema
|
||||||
|
inputMappingSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "输入映射",
|
||||||
|
description: "从上游节点接收的数据映射配置",
|
||||||
|
properties: {
|
||||||
|
requestBody: {
|
||||||
|
type: "object",
|
||||||
|
title: "请求体",
|
||||||
|
description: "发送给服务的请求数据",
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
queryParams: {
|
||||||
|
type: "object",
|
||||||
|
title: "查询参数",
|
||||||
|
description: "URL查询参数",
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
pathParams: {
|
||||||
|
type: "object",
|
||||||
|
title: "路径参数",
|
||||||
|
description: "URL路径参数",
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
authToken: {
|
||||||
|
type: "string",
|
||||||
|
title: "认证令牌",
|
||||||
|
description: "服务调用的认证令牌",
|
||||||
|
default: "${upstream.token}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输出映射Schema
|
||||||
|
outputMappingSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "输出映射",
|
||||||
|
description: "传递给下游节点的数据映射配置",
|
||||||
|
properties: {
|
||||||
|
responseData: {
|
||||||
|
type: "object",
|
||||||
|
title: "响应数据",
|
||||||
|
description: "服务返回的响应数据",
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
statusCode: {
|
||||||
|
type: "number",
|
||||||
|
title: "状态码",
|
||||||
|
description: "HTTP响应状态码",
|
||||||
|
default: 200
|
||||||
|
},
|
||||||
|
responseHeaders: {
|
||||||
|
type: "object",
|
||||||
|
title: "响应头",
|
||||||
|
description: "HTTP响应头信息",
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
executionTime: {
|
||||||
|
type: "number",
|
||||||
|
title: "执行时间",
|
||||||
|
description: "服务调用耗时(毫秒)",
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
type: "boolean",
|
||||||
|
title: "执行成功",
|
||||||
|
description: "服务调用是否成功",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: "string",
|
||||||
|
title: "错误信息",
|
||||||
|
description: "失败时的错误信息",
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["responseData", "statusCode", "success"]
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import {BaseNodeDefinition, NodeType, NodeCategory} from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始事件节点定义
|
||||||
|
* 简单节点,无需额外配置
|
||||||
|
*/
|
||||||
|
export const StartEventNode: BaseNodeDefinition = {
|
||||||
|
nodeCode: "START_EVENT",
|
||||||
|
nodeName: "开始",
|
||||||
|
nodeType: NodeType.START_EVENT,
|
||||||
|
category: NodeCategory.EVENT,
|
||||||
|
description: "工作流的起始节点",
|
||||||
|
|
||||||
|
// UI 配置
|
||||||
|
uiConfig: {
|
||||||
|
size: {
|
||||||
|
width: 80,
|
||||||
|
height: 50
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#52c41a',
|
||||||
|
stroke: '#389e08',
|
||||||
|
strokeWidth: 2,
|
||||||
|
icon: 'play-circle',
|
||||||
|
iconColor: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 基本配置Schema - 用于生成"基本配置"TAB(包含基本信息)
|
||||||
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "节点的基本配置信息",
|
||||||
|
properties: {
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称",
|
||||||
|
default: "开始"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符",
|
||||||
|
default: "START_EVENT"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明",
|
||||||
|
default: "工作流的起始节点"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["nodeName", "nodeCode"]
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户任务节点定义
|
||||||
|
* 可配置节点,支持配置、输入映射、输出映射
|
||||||
|
*/
|
||||||
|
export const UserTaskNode: ConfigurableNodeDefinition = {
|
||||||
|
nodeCode: "USER_TASK",
|
||||||
|
nodeName: "用户任务",
|
||||||
|
nodeType: NodeType.USER_TASK,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: "需要用户手动执行的任务",
|
||||||
|
|
||||||
|
// UI 配置
|
||||||
|
uiConfig: {
|
||||||
|
size: {
|
||||||
|
width: 120,
|
||||||
|
height: 60
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#722ed1',
|
||||||
|
stroke: '#531dab',
|
||||||
|
strokeWidth: 2,
|
||||||
|
icon: 'user',
|
||||||
|
iconColor: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 基本配置Schema
|
||||||
|
configSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "基本配置",
|
||||||
|
description: "用户任务的基本配置信息",
|
||||||
|
properties: {
|
||||||
|
// 基本信息
|
||||||
|
nodeName: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点名称",
|
||||||
|
description: "节点在流程图中显示的名称",
|
||||||
|
default: "用户任务"
|
||||||
|
},
|
||||||
|
nodeCode: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点编码",
|
||||||
|
description: "节点的唯一标识符",
|
||||||
|
default: "USER_TASK"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
title: "节点描述",
|
||||||
|
description: "节点的详细说明",
|
||||||
|
default: "需要用户手动执行的任务"
|
||||||
|
},
|
||||||
|
// 节点配置
|
||||||
|
assignee: {
|
||||||
|
type: "string",
|
||||||
|
title: "任务分配人",
|
||||||
|
description: "任务分配给的用户",
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
candidateGroups: {
|
||||||
|
type: "array",
|
||||||
|
title: "候选组",
|
||||||
|
description: "可以执行此任务的用户组",
|
||||||
|
items: {
|
||||||
|
type: "string"
|
||||||
|
},
|
||||||
|
default: []
|
||||||
|
},
|
||||||
|
dueDate: {
|
||||||
|
type: "string",
|
||||||
|
title: "截止时间",
|
||||||
|
description: "任务的截止时间",
|
||||||
|
format: "date-time"
|
||||||
|
},
|
||||||
|
priority: {
|
||||||
|
type: "string",
|
||||||
|
title: "优先级",
|
||||||
|
description: "任务优先级",
|
||||||
|
enum: ["low", "normal", "high", "urgent"],
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
formKey: {
|
||||||
|
type: "string",
|
||||||
|
title: "表单键",
|
||||||
|
description: "关联的表单标识",
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["nodeName", "nodeCode"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输入映射Schema
|
||||||
|
inputMappingSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "输入映射",
|
||||||
|
description: "从上游节点接收的数据映射配置",
|
||||||
|
properties: {
|
||||||
|
taskData: {
|
||||||
|
type: "object",
|
||||||
|
title: "任务数据",
|
||||||
|
description: "传递给用户任务的数据",
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
assigneeExpression: {
|
||||||
|
type: "string",
|
||||||
|
title: "分配人表达式",
|
||||||
|
description: "动态计算分配人的表达式",
|
||||||
|
default: "${upstream.userId}"
|
||||||
|
},
|
||||||
|
formVariables: {
|
||||||
|
type: "object",
|
||||||
|
title: "表单变量",
|
||||||
|
description: "表单初始化变量",
|
||||||
|
default: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输出映射Schema
|
||||||
|
outputMappingSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "输出映射",
|
||||||
|
description: "传递给下游节点的数据映射配置",
|
||||||
|
properties: {
|
||||||
|
taskResult: {
|
||||||
|
type: "object",
|
||||||
|
title: "任务结果",
|
||||||
|
description: "用户任务的执行结果",
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
completedBy: {
|
||||||
|
type: "string",
|
||||||
|
title: "完成人",
|
||||||
|
description: "实际完成任务的用户",
|
||||||
|
default: "${task.assignee}"
|
||||||
|
},
|
||||||
|
completedAt: {
|
||||||
|
type: "string",
|
||||||
|
title: "完成时间",
|
||||||
|
description: "任务完成的时间",
|
||||||
|
format: "date-time",
|
||||||
|
default: "${task.endTime}"
|
||||||
|
},
|
||||||
|
decision: {
|
||||||
|
type: "string",
|
||||||
|
title: "决策结果",
|
||||||
|
description: "用户的决策结果",
|
||||||
|
enum: ["approved", "rejected", "pending"],
|
||||||
|
default: "approved"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["taskResult", "completedBy"]
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import {WorkflowNodeDefinition} from '../types';
|
||||||
|
import {DeployNode} from './DeployNode';
|
||||||
|
import {StartEventNode} from './StartEventNode';
|
||||||
|
import {EndEventNode} from './EndEventNode';
|
||||||
|
import {UserTaskNode} from './UserTaskNode';
|
||||||
|
import {ServiceTaskNode} from './ServiceTaskNode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有节点定义的注册表
|
||||||
|
*/
|
||||||
|
export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
|
||||||
|
StartEventNode,
|
||||||
|
EndEventNode,
|
||||||
|
UserTaskNode,
|
||||||
|
ServiceTaskNode,
|
||||||
|
DeployNode,
|
||||||
|
// 在这里添加更多节点定义
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出节点定义
|
||||||
|
*/
|
||||||
|
export {
|
||||||
|
StartEventNode,
|
||||||
|
EndEventNode,
|
||||||
|
UserTaskNode,
|
||||||
|
ServiceTaskNode,
|
||||||
|
DeployNode
|
||||||
|
};
|
||||||
99
frontend/src/pages/Workflow2/Design/nodes/types.ts
Normal file
99
frontend/src/pages/Workflow2/Design/nodes/types.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// 节点分类
|
||||||
|
export enum NodeCategory {
|
||||||
|
EVENT = 'EVENT',
|
||||||
|
TASK = 'TASK',
|
||||||
|
GATEWAY = 'GATEWAY',
|
||||||
|
CONTAINER = 'CONTAINER'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点类型
|
||||||
|
export enum NodeType {
|
||||||
|
START_EVENT = 'START_EVENT',
|
||||||
|
END_EVENT = 'END_EVENT',
|
||||||
|
USER_TASK = 'USER_TASK',
|
||||||
|
SERVICE_TASK = 'SERVICE_TASK',
|
||||||
|
SCRIPT_TASK = 'SCRIPT_TASK',
|
||||||
|
DEPLOY_NODE = 'DEPLOY_NODE',
|
||||||
|
JENKINS_BUILD = 'JENKINS_BUILD',
|
||||||
|
GATEWAY_NODE = 'GATEWAY_NODE',
|
||||||
|
SUB_PROCESS = 'SUB_PROCESS',
|
||||||
|
CALL_ACTIVITY = 'CALL_ACTIVITY'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 NodeStyle {
|
||||||
|
fill: string;
|
||||||
|
icon: string;
|
||||||
|
stroke: string;
|
||||||
|
iconColor: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
iconSize?: number;
|
||||||
|
borderRadius?: string;
|
||||||
|
boxShadow?: string;
|
||||||
|
transition?: string;
|
||||||
|
fontSize?: string;
|
||||||
|
fontWeight?: string;
|
||||||
|
fontFamily?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UIConfig {
|
||||||
|
size: NodeSize;
|
||||||
|
style: NodeStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础节点定义(只有基本配置)
|
||||||
|
export interface BaseNodeDefinition {
|
||||||
|
nodeCode: string;
|
||||||
|
nodeName: string;
|
||||||
|
nodeType: NodeType;
|
||||||
|
category: NodeCategory;
|
||||||
|
description: string;
|
||||||
|
uiConfig: UIConfig;
|
||||||
|
configSchema: JSONSchema; // 基本配置Schema(包含基本信息+节点配置)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可配置节点定义(有3个TAB:基本配置、输入、输出)
|
||||||
|
export interface ConfigurableNodeDefinition extends BaseNodeDefinition {
|
||||||
|
inputMappingSchema?: JSONSchema; // 输入映射的Schema定义
|
||||||
|
outputMappingSchema?: JSONSchema; // 输出映射的Schema定义
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工作流节点定义(联合类型)
|
||||||
|
export type WorkflowNodeDefinition = BaseNodeDefinition | ConfigurableNodeDefinition;
|
||||||
|
|
||||||
|
// 节点实例数据(运行时)
|
||||||
|
export interface NodeInstanceData {
|
||||||
|
nodeCode: string;
|
||||||
|
nodeName: string;
|
||||||
|
nodeType: NodeType;
|
||||||
|
category: NodeCategory;
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
// 运行时数据(key/value格式)
|
||||||
|
configs?: Record<string, any>; // 基本配置数据(包含基本信息+节点配置)
|
||||||
|
inputMapping?: Record<string, any>;
|
||||||
|
outputMapping?: Record<string, any>;
|
||||||
|
|
||||||
|
// UI位置信息
|
||||||
|
position: { x: number; y: number };
|
||||||
|
uiConfig: UIConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为可配置节点
|
||||||
|
export const isConfigurableNode = (def: WorkflowNodeDefinition): def is ConfigurableNodeDefinition => {
|
||||||
|
return 'inputMappingSchema' in def || 'outputMappingSchema' in def;
|
||||||
|
};
|
||||||
@ -40,17 +40,14 @@ export interface FlowNodeData extends Record<string, unknown> {
|
|||||||
category: NodeCategory;
|
category: NodeCategory;
|
||||||
icon: string;
|
icon: string;
|
||||||
color: string;
|
color: string;
|
||||||
// 节点配置数据
|
|
||||||
configs?: {
|
// 运行时数据(与原系统兼容的格式)
|
||||||
timeout?: number; // 超时时间(秒)
|
configs?: Record<string, any>; // 基本配置数据(包含基本信息+节点配置)
|
||||||
retryCount?: number; // 重试次数
|
|
||||||
priority?: string; // 优先级
|
|
||||||
[key: string]: any; // 其他自定义配置
|
|
||||||
};
|
|
||||||
inputMapping?: Record<string, any>; // 输入参数映射
|
inputMapping?: Record<string, any>; // 输入参数映射
|
||||||
outputMapping?: Record<string, any>; // 输出参数映射
|
outputMapping?: Record<string, any>; // 输出参数映射
|
||||||
// 原始节点定义
|
|
||||||
nodeDefinition?: NodeDefinitionData;
|
// 原始节点定义(使用新的节点定义接口)
|
||||||
|
nodeDefinition?: import('./nodes/types').WorkflowNodeDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
// React Flow 边数据 - 添加索引签名以满足React Flow的类型约束
|
// React Flow 边数据 - 添加索引签名以满足React Flow的类型约束
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user