/**
* 工作流编辑器组件
* 使用React Flow实现可视化工作流编辑
*/
import { useState, useCallback, useRef } from 'react';
import ReactFlow, {
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
Controls,
MiniMap,
Background,
Panel,
MarkerType
} from 'reactflow';
import 'reactflow/dist/style.css';
import {
Card,
Button,
Drawer,
Form,
Input,
Select,
Space,
Typography,
message,
Modal,
Divider,
Tag
} from 'antd';
import {
PlusOutlined,
SaveOutlined,
ClearOutlined,
PlayCircleOutlined,
QuestionCircleOutlined,
CodeOutlined,
FileTextOutlined,
BranchesOutlined,
CheckCircleOutlined
} from '@ant-design/icons';
const { Title, Text } = Typography;
const { TextArea } = Input;
const { Option } = Select;
// 节点类型定义
const nodeTypes = {
start: {
label: '开始节点',
icon: ,
color: '#52c41a'
},
question: {
label: '提问节点',
icon: ,
color: '#1890ff'
},
knowledge: {
label: '知识查询',
icon: ,
color: '#722ed1'
},
code: {
label: '代码执行',
icon: ,
color: '#fa8c16'
},
condition: {
label: '条件分支',
icon: ,
color: '#f5222d'
},
end: {
label: '结束节点',
icon: ,
color: '#52c41a'
}
};
// 自定义节点组件
const CustomNode = ({ data, selected }) => {
const nodeType = nodeTypes[data.type] || nodeTypes.question;
return (
{nodeType.icon}
{data.label}
{data.description && (
{data.description}
)}
);
};
// 初始节点
const initialNodes = [
{
id: 'start',
type: 'custom',
position: { x: 250, y: 50 },
data: {
type: 'start',
label: '开始',
description: '工作流起点'
}
}
];
const WorkflowEditor = ({ value, onChange, onSave }) => {
const [nodes, setNodes, onNodesChange] = useNodesState(value?.nodes || initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(value?.edges || []);
const [drawerVisible, setDrawerVisible] = useState(false);
const [selectedNode, setSelectedNode] = useState(null);
const [nodeForm] = Form.useForm();
const reactFlowWrapper = useRef(null);
const reactFlowInstance = useRef(null);
// 连接节点
const onConnect = useCallback(
(params) => {
const newEdge = {
...params,
type: 'smoothstep',
animated: true,
style: { stroke: '#1890ff', strokeWidth: 2 },
markerEnd: {
type: MarkerType.ArrowClosed,
color: '#1890ff'
}
};
setEdges((eds) => addEdge(newEdge, eds));
},
[setEdges]
);
// 添加节点
const addNode = (type) => {
const newNode = {
id: `${type}_${Date.now()}`,
type: 'custom',
position: {
x: Math.random() * 400 + 100,
y: Math.random() * 300 + 100
},
data: {
type,
label: nodeTypes[type].label,
description: '',
config: {}
}
};
setNodes((nds) => nds.concat(newNode));
message.success(`已添加${nodeTypes[type].label}`);
};
// 选中节点
const onNodeClick = (event, node) => {
setSelectedNode(node);
nodeForm.setFieldsValue({
label: node.data.label,
description: node.data.description,
...node.data.config
});
setDrawerVisible(true);
};
// 删除节点
const deleteNode = (nodeId) => {
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
message.success('节点已删除');
};
// 更新节点
const updateNode = (values) => {
setNodes((nds) =>
nds.map((node) => {
if (node.id === selectedNode.id) {
return {
...node,
data: {
...node.data,
label: values.label,
description: values.description,
config: {
...values,
label: undefined,
description: undefined
}
}
};
}
return node;
})
);
setDrawerVisible(false);
message.success('节点已更新');
};
// 清空画布
const clearCanvas = () => {
Modal.confirm({
title: '确认清空',
content: '确定要清空所有节点和连接吗?',
onOk: () => {
setNodes([initialNodes[0]]);
setEdges([]);
message.success('画布已清空');
}
});
};
// 保存工作流
const handleSave = () => {
const workflow = {
nodes,
edges,
timestamp: Date.now()
};
if (onChange) {
onChange(workflow);
}
if (onSave) {
onSave(workflow);
}
message.success('工作流已保存');
};
// 验证工作流
const validateWorkflow = () => {
if (nodes.length < 2) {
message.warning('工作流至少需要包含开始和结束节点');
return false;
}
const hasStart = nodes.some(n => n.data.type === 'start');
const hasEnd = nodes.some(n => n.data.type === 'end');
if (!hasStart || !hasEnd) {
message.warning('工作流必须包含开始和结束节点');
return false;
}
return true;
};
return (
);
};
export default WorkflowEditor;