1
This commit is contained in:
parent
20c4184d45
commit
3315114522
@ -1,7 +1,11 @@
|
||||
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 { BetaSchemaForm } from '@ant-design/pro-components';
|
||||
import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils';
|
||||
import type { FlowNode, FlowNodeData } from '../types';
|
||||
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||
import { isConfigurableNode } from '../nodes/types';
|
||||
|
||||
interface NodeConfigModalProps {
|
||||
visible: boolean;
|
||||
@ -10,7 +14,11 @@ interface NodeConfigModalProps {
|
||||
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> = ({
|
||||
visible,
|
||||
@ -18,187 +26,176 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
||||
onCancel,
|
||||
onOk
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
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(() => {
|
||||
if (visible && node) {
|
||||
form.setFieldsValue({
|
||||
label: node.data.label,
|
||||
description: node.data.nodeDefinition?.description || '',
|
||||
// 基础配置
|
||||
timeout: node.data.configs?.timeout || 3600,
|
||||
retryCount: node.data.configs?.retryCount || 0,
|
||||
priority: node.data.configs?.priority || 'normal',
|
||||
// 输入映射
|
||||
inputMapping: JSON.stringify(node.data.inputMapping || {}, null, 2),
|
||||
// 输出映射
|
||||
outputMapping: JSON.stringify(node.data.outputMapping || {}, null, 2),
|
||||
if (visible && node && nodeDefinition) {
|
||||
// 从节点数据中获取现有配置
|
||||
const nodeData = node.data || {};
|
||||
|
||||
// 准备默认的基本信息配置
|
||||
const defaultConfig = {
|
||||
nodeName: nodeDefinition.nodeName, // 默认节点名称
|
||||
nodeCode: nodeDefinition.nodeCode, // 默认节点编码
|
||||
description: nodeDefinition.description // 默认节点描述
|
||||
};
|
||||
|
||||
// 合并默认值和已保存的配置
|
||||
setFormData({
|
||||
configs: { ...defaultConfig, ...(nodeData.configs || {}) },
|
||||
inputMapping: nodeData.inputMapping || {},
|
||||
outputMapping: nodeData.outputMapping || {},
|
||||
});
|
||||
} else {
|
||||
form.resetFields();
|
||||
setFormData({});
|
||||
}
|
||||
}, [visible, node, form]);
|
||||
}, [visible, node, nodeDefinition]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const handleSubmit = () => {
|
||||
if (!node) return;
|
||||
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
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> = {
|
||||
label: values.label,
|
||||
configs: {
|
||||
...node.data.configs,
|
||||
timeout: values.timeout,
|
||||
retryCount: values.retryCount,
|
||||
priority: values.priority,
|
||||
},
|
||||
inputMapping,
|
||||
outputMapping,
|
||||
// 更新节点标签为配置中的节点名称
|
||||
label: formData.configs?.nodeName || node.data.label,
|
||||
configs: formData.configs,
|
||||
inputMapping: formData.inputMapping,
|
||||
outputMapping: formData.outputMapping,
|
||||
};
|
||||
|
||||
onOk(node.id, updatedData);
|
||||
message.success('节点配置保存成功');
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
console.error('保存节点配置失败:', error);
|
||||
if (error instanceof Error) {
|
||||
message.error(error.message);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
form.resetFields();
|
||||
if (node) {
|
||||
form.setFieldsValue({
|
||||
label: node.data.label,
|
||||
description: node.data.nodeDefinition?.description || '',
|
||||
timeout: 3600,
|
||||
retryCount: 0,
|
||||
priority: 'normal',
|
||||
inputMapping: '{}',
|
||||
outputMapping: '{}',
|
||||
if (nodeDefinition) {
|
||||
const defaultConfig = {
|
||||
nodeName: nodeDefinition.nodeName,
|
||||
nodeCode: nodeDefinition.nodeCode,
|
||||
description: nodeDefinition.description
|
||||
};
|
||||
|
||||
setFormData({
|
||||
configs: defaultConfig,
|
||||
inputMapping: {},
|
||||
outputMapping: {},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderBasicConfig = () => (
|
||||
<Card size="small" title="基本信息">
|
||||
<Form.Item
|
||||
label="节点名称"
|
||||
name="label"
|
||||
rules={[{ required: true, message: '请输入节点名称' }]}
|
||||
>
|
||||
<Input placeholder="请输入节点名称" />
|
||||
</Form.Item>
|
||||
const handleConfigChange = (values: Record<string, any>) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
configs: values
|
||||
}));
|
||||
};
|
||||
|
||||
<Form.Item label="节点描述" name="description">
|
||||
<TextArea
|
||||
placeholder="请输入节点描述"
|
||||
rows={3}
|
||||
maxLength={500}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
const handleInputMappingChange = (values: Record<string, any>) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
inputMapping: values
|
||||
}));
|
||||
};
|
||||
|
||||
<Form.Item
|
||||
label="超时时间(秒)"
|
||||
name="timeout"
|
||||
rules={[{ required: true, message: '请输入超时时间' }]}
|
||||
>
|
||||
<Input type="number" min={1} placeholder="3600" />
|
||||
</Form.Item>
|
||||
const handleOutputMappingChange = (values: Record<string, any>) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
outputMapping: values
|
||||
}));
|
||||
};
|
||||
|
||||
<Form.Item
|
||||
label="重试次数"
|
||||
name="retryCount"
|
||||
>
|
||||
<Input type="number" min={0} max={10} placeholder="0" />
|
||||
</Form.Item>
|
||||
if (!nodeDefinition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
<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={`示例:
|
||||
// 构建Tabs配置
|
||||
const tabItems = [
|
||||
{
|
||||
"param1": "\${workflow.variable1}",
|
||||
"param2": "固定值",
|
||||
"param3": "\${previous.output.result}"
|
||||
}`}
|
||||
rows={12}
|
||||
style={{ fontFamily: 'Monaco, Consolas, monospace' }}
|
||||
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>
|
||||
</Card>
|
||||
);
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const renderOutputMapping = () => (
|
||||
<Card size="small" title="输出参数映射">
|
||||
<Form.Item
|
||||
label="输出映射配置"
|
||||
name="outputMapping"
|
||||
extra="请输入有效的JSON格式,用于配置节点的输出参数映射"
|
||||
>
|
||||
<TextArea
|
||||
placeholder={`示例:
|
||||
{
|
||||
"result": "\${task.output.result}",
|
||||
"status": "\${task.status}",
|
||||
"message": "\${task.output.message}"
|
||||
}`}
|
||||
rows={12}
|
||||
style={{ fontFamily: 'Monaco, Consolas, monospace' }}
|
||||
// 如果是可配置节点,添加输入和输出映射TAB
|
||||
if (isConfigurableNode(nodeDefinition)) {
|
||||
if (nodeDefinition.inputMappingSchema) {
|
||||
tabItems.push({
|
||||
key: 'input',
|
||||
label: '输入映射',
|
||||
children: (
|
||||
<div style={{ padding: '16px 0' }}>
|
||||
<BetaSchemaForm
|
||||
key={`input-${node?.id}-${JSON.stringify(formData.inputMapping)}`}
|
||||
layoutType="Form"
|
||||
columns={convertJsonSchemaToColumns(nodeDefinition.inputMappingSchema as any)}
|
||||
initialValues={formData.inputMapping}
|
||||
onValuesChange={(_, allValues) => handleInputMappingChange(allValues)}
|
||||
submitter={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
);
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (nodeDefinition.outputMappingSchema) {
|
||||
tabItems.push({
|
||||
key: 'output',
|
||||
label: '输出映射',
|
||||
children: (
|
||||
<div style={{ padding: '16px 0' }}>
|
||||
<BetaSchemaForm
|
||||
key={`output-${node?.id}-${JSON.stringify(formData.outputMapping)}`}
|
||||
layoutType="Form"
|
||||
columns={convertJsonSchemaToColumns(nodeDefinition.outputMappingSchema as any)}
|
||||
initialValues={formData.outputMapping}
|
||||
onValuesChange={(_, allValues) => handleOutputMappingChange(allValues)}
|
||||
submitter={false}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`配置节点: ${node?.data?.label || '未知节点'}`}
|
||||
title={`编辑节点 - ${nodeDefinition.nodeName}`}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
width={800}
|
||||
style={{ top: 20 }}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={handleReset}
|
||||
@ -216,39 +213,13 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
timeout: 3600,
|
||||
retryCount: 0,
|
||||
priority: 'normal',
|
||||
inputMapping: '{}',
|
||||
outputMapping: '{}'
|
||||
}}
|
||||
>
|
||||
<div style={{ maxHeight: '70vh', overflow: 'auto' }}>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
items={[
|
||||
{
|
||||
key: 'basic',
|
||||
label: '基本配置',
|
||||
children: renderBasicConfig()
|
||||
},
|
||||
{
|
||||
key: 'input',
|
||||
label: '输入映射',
|
||||
children: renderInputMapping()
|
||||
},
|
||||
{
|
||||
key: 'output',
|
||||
label: '输出映射',
|
||||
children: renderOutputMapping()
|
||||
}
|
||||
]}
|
||||
items={tabItems}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,82 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Card, Divider } from 'antd';
|
||||
import { NodeType, NodeCategory, type NodeDefinitionData } from '../types';
|
||||
import { NodeCategory } from '../types';
|
||||
import { NODE_DEFINITIONS } from '../nodes/definitions';
|
||||
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||
|
||||
// 节点定义数据
|
||||
const nodeDefinitions: NodeDefinitionData[] = [
|
||||
{
|
||||
nodeCode: 'start_event',
|
||||
nodeName: '开始事件',
|
||||
nodeType: NodeType.START_EVENT,
|
||||
category: NodeCategory.EVENT,
|
||||
description: '工作流开始节点',
|
||||
icon: '▶️',
|
||||
color: '#10b981'
|
||||
},
|
||||
{
|
||||
nodeCode: 'end_event',
|
||||
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'
|
||||
}
|
||||
];
|
||||
// 图标映射函数
|
||||
const getNodeIcon = (iconName: string): string => {
|
||||
const iconMap: Record<string, string> = {
|
||||
'play-circle': '▶️',
|
||||
'stop-circle': '⏹️',
|
||||
'user': '👤',
|
||||
'api': '⚙️',
|
||||
'code': '📜',
|
||||
'build': '🚀',
|
||||
'jenkins': '🔨',
|
||||
'gateway': '💎'
|
||||
};
|
||||
return iconMap[iconName] || '📋';
|
||||
};
|
||||
|
||||
interface NodePanelProps {
|
||||
className?: string;
|
||||
@ -84,16 +24,16 @@ interface NodePanelProps {
|
||||
|
||||
const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||
// 按分类分组节点
|
||||
const nodesByCategory = nodeDefinitions.reduce((acc, node) => {
|
||||
const nodesByCategory = NODE_DEFINITIONS.reduce((acc, node) => {
|
||||
if (!acc[node.category]) {
|
||||
acc[node.category] = [];
|
||||
}
|
||||
acc[node.category].push(node);
|
||||
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({
|
||||
nodeType: nodeDefinition.nodeType,
|
||||
nodeDefinition
|
||||
@ -102,11 +42,10 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||
};
|
||||
|
||||
// 渲染节点项
|
||||
const renderNodeItem = (nodeDefinition: NodeDefinitionData) => (
|
||||
const renderNodeItem = (nodeDefinition: WorkflowNodeDefinition) => (
|
||||
<div
|
||||
key={nodeDefinition.nodeCode}
|
||||
draggable
|
||||
onDragStart={(e) => handleDragStart(e, nodeDefinition)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@ -121,7 +60,7 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
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)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
@ -140,9 +79,10 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||
<div style={{
|
||||
fontSize: '16px',
|
||||
width: '20px',
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
color: nodeDefinition.uiConfig.style.fill
|
||||
}}>
|
||||
{nodeDefinition.icon}
|
||||
{getNodeIcon(nodeDefinition.uiConfig.style.icon)}
|
||||
</div>
|
||||
<div>
|
||||
<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 FlowCanvas from './components/FlowCanvas';
|
||||
import NodeConfigModal from './components/NodeConfigModal';
|
||||
import PropertyPanel from './components/PropertyPanel';
|
||||
import type { FlowNode, FlowEdge, DragNodeData, FlowNodeData } from './types';
|
||||
import type { FlowNode, FlowEdge, FlowNodeData } from './types';
|
||||
import type { WorkflowNodeDefinition } from './nodes/types';
|
||||
import { NodeType } from './types';
|
||||
import { useWorkflowSave } from './hooks/useWorkflowSave';
|
||||
import { useWorkflowLoad } from './hooks/useWorkflowLoad';
|
||||
@ -40,12 +40,11 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
|
||||
// 节点配置模态框状态
|
||||
const [configModalVisible, setConfigModalVisible] = useState(false);
|
||||
const [selectedNode, setSelectedNode] = useState<FlowNode | null>(null);
|
||||
const [selectedEdge, setSelectedEdge] = useState<FlowEdge | null>(null);
|
||||
const [configNode, setConfigNode] = useState<FlowNode | null>(null);
|
||||
|
||||
// 保存和加载hooks
|
||||
const { saving, hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave();
|
||||
const { loading: loadingWorkflow, workflowDefinition, loadWorkflow } = useWorkflowLoad();
|
||||
const { hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave();
|
||||
const { workflowDefinition, loadWorkflow } = useWorkflowLoad();
|
||||
|
||||
// 加载工作流数据
|
||||
useEffect(() => {
|
||||
@ -230,7 +229,7 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
if (!dragData) return;
|
||||
|
||||
try {
|
||||
const { nodeType, nodeDefinition }: DragNodeData = JSON.parse(dragData);
|
||||
const { nodeType, nodeDefinition }: { nodeType: string; nodeDefinition: WorkflowNodeDefinition } = JSON.parse(dragData);
|
||||
|
||||
// 根据React Flow官方文档,screenToFlowPosition会自动处理所有边界计算
|
||||
// 不需要手动减去容器边界!
|
||||
@ -245,11 +244,20 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
position,
|
||||
data: {
|
||||
label: nodeDefinition.nodeName,
|
||||
nodeType,
|
||||
nodeType: nodeDefinition.nodeType,
|
||||
category: nodeDefinition.category,
|
||||
icon: nodeDefinition.icon,
|
||||
color: nodeDefinition.color,
|
||||
nodeDefinition
|
||||
icon: nodeDefinition.uiConfig.style.icon,
|
||||
color: nodeDefinition.uiConfig.style.fill,
|
||||
// 保存原始节点定义引用,用于配置
|
||||
nodeDefinition,
|
||||
// 初始化配置数据
|
||||
configs: {
|
||||
nodeName: nodeDefinition.nodeName,
|
||||
nodeCode: nodeDefinition.nodeCode,
|
||||
description: nodeDefinition.description
|
||||
},
|
||||
inputMapping: {},
|
||||
outputMapping: {}
|
||||
}
|
||||
};
|
||||
|
||||
@ -261,31 +269,22 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
}
|
||||
}, [screenToFlowPosition, setNodes]);
|
||||
|
||||
// 处理节点点击 - 单击选择,双击打开配置面板
|
||||
// 处理节点双击 - 打开配置面板
|
||||
const handleNodeClick = useCallback((event: React.MouseEvent, node: FlowNode) => {
|
||||
console.log('节点点击:', node);
|
||||
|
||||
// 清除边选择
|
||||
setSelectedEdge(null);
|
||||
|
||||
// 检查是否是双击
|
||||
// 只处理双击事件
|
||||
if (event.detail === 2) {
|
||||
setSelectedNode(node);
|
||||
console.log('双击节点,打开配置:', node);
|
||||
setConfigNode(node);
|
||||
setConfigModalVisible(true);
|
||||
} else {
|
||||
setSelectedNode(node);
|
||||
message.info(`选择了节点: ${node.data.label}`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 处理边点击
|
||||
// 处理边双击 - 暂时只记录日志
|
||||
const handleEdgeClick = useCallback((event: React.MouseEvent, edge: FlowEdge) => {
|
||||
console.log('边点击:', edge);
|
||||
|
||||
// 清除节点选择
|
||||
setSelectedNode(null);
|
||||
setSelectedEdge(edge);
|
||||
message.info(`选择了连接: ${edge.data?.label || '连接'}`);
|
||||
if (event.detail === 2) {
|
||||
console.log('双击边:', edge);
|
||||
message.info(`双击了连接: ${edge.data?.label || '连接'},配置功能待实现`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 处理节点配置更新
|
||||
@ -306,28 +305,10 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
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(() => {
|
||||
setConfigModalVisible(false);
|
||||
setSelectedNode(null);
|
||||
setConfigNode(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -343,7 +324,6 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
title={`${workflowTitle}${hasUnsavedChanges ? ' *' : ''}`}
|
||||
onSave={handleSave}
|
||||
onBack={handleBack}
|
||||
saving={saving}
|
||||
onUndo={handleUndo}
|
||||
onRedo={handleRedo}
|
||||
onCopy={handleCopy}
|
||||
@ -376,20 +356,12 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
className="workflow-canvas"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 属性面板 */}
|
||||
<PropertyPanel
|
||||
selectedNode={selectedNode}
|
||||
selectedEdge={selectedEdge}
|
||||
onNodeUpdate={handleNodeConfigUpdate}
|
||||
onEdgeUpdate={handleEdgeConfigUpdate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 节点配置弹窗 */}
|
||||
<NodeConfigModal
|
||||
visible={configModalVisible}
|
||||
node={selectedNode}
|
||||
node={configNode}
|
||||
onCancel={handleCloseConfigModal}
|
||||
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;
|
||||
icon: string;
|
||||
color: string;
|
||||
// 节点配置数据
|
||||
configs?: {
|
||||
timeout?: number; // 超时时间(秒)
|
||||
retryCount?: number; // 重试次数
|
||||
priority?: string; // 优先级
|
||||
[key: string]: any; // 其他自定义配置
|
||||
};
|
||||
|
||||
// 运行时数据(与原系统兼容的格式)
|
||||
configs?: Record<string, any>; // 基本配置数据(包含基本信息+节点配置)
|
||||
inputMapping?: Record<string, any>; // 输入参数映射
|
||||
outputMapping?: Record<string, any>; // 输出参数映射
|
||||
// 原始节点定义
|
||||
nodeDefinition?: NodeDefinitionData;
|
||||
|
||||
// 原始节点定义(使用新的节点定义接口)
|
||||
nodeDefinition?: import('./nodes/types').WorkflowNodeDefinition;
|
||||
}
|
||||
|
||||
// React Flow 边数据 - 添加索引签名以满足React Flow的类型约束
|
||||
|
||||
Loading…
Reference in New Issue
Block a user