/** * 工作流编辑器组件 * 使用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 (
{ reactFlowInstance.current = instance; }} nodeTypes={{ custom: CustomNode }} fitView > nodeTypes[node.data.type]?.color || '#ccc'} style={{ height: 120, background: '#f0f2f5' }} /> {/* 工具面板 */} 添加节点 {Object.entries(nodeTypes).map(([key, type]) => ( ))} {/* 操作按钮 */}
{/* 节点编辑抽屉 */} setDrawerVisible(false)} footer={ } > {selectedNode && (