Browse Source

连线功能

feature/x1-web-request
root 4 months ago
parent
commit
ec3bdc9df5
  1. 593
      src/X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx
  2. 117
      src/X1.WebUI/src/pages/testcases/ReactFlowTest.tsx

593
src/X1.WebUI/src/pages/testcases/ReactFlowDesigner.tsx

@ -8,24 +8,23 @@ import ReactFlow, {
useEdgesState,
Controls,
Background,
MiniMap,
Panel,
ReactFlowProvider,
useReactFlow,
NodeMouseHandler,
Handle,
Position,
} from 'reactflow';
import 'reactflow/dist/style.css';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import {
Save,
RotateCcw,
Download,
Upload,
Settings,
Eye,
EyeOff,
Trash2,
Plus,
Play,
Square,
GitBranch,
@ -39,8 +38,7 @@ import {
Activity,
Signal,
SignalHigh,
SignalLow,
MoreHorizontal
SignalLow
} from 'lucide-react';
import { TestStep } from '@/services/teststepsService';
@ -64,7 +62,8 @@ interface ReactFlowDesignerProps {
}
// 自定义节点组件
const TestStepNode = ({ data }: { data: any }) => {
const TestStepNode = ({ data, id, selected }: { data: any; id: string; selected?: boolean }) => {
const getIconComponent = (iconName: string) => {
// 根据图标名称返回对应的图标组件
switch (iconName) {
@ -161,7 +160,11 @@ const TestStepNode = ({ data }: { data: any }) => {
<div className={`group relative transition-all duration-200`}>
{/* 开始和结束步骤使用圆形 */}
{(data.stepType === 1 || data.stepType === 2) && (
<div className={`px-2 py-1 ${nodeStyle.shape} ${nodeStyle.bgColor} border ${nodeStyle.borderColor}`}>
<div
className={`px-2 py-1 ${nodeStyle.shape} ${nodeStyle.bgColor} border ${nodeStyle.borderColor} ${
selected ? 'ring-2 ring-blue-500' : ''
}`}
>
<div className="flex items-center space-x-1">
<div className={`flex-shrink-0 w-3 h-3 rounded-lg ${getIconBgColor(data.icon || 'settings')} flex items-center justify-center`}>
{getIconComponent(data.icon || 'settings')}
@ -177,7 +180,11 @@ const TestStepNode = ({ data }: { data: any }) => {
{/* 处理步骤使用矩形 */}
{data.stepType === 3 && (
<div className={`px-2 py-1 ${nodeStyle.shape} ${nodeStyle.bgColor} border ${nodeStyle.borderColor}`}>
<div
className={`px-2 py-1 ${nodeStyle.shape} ${nodeStyle.bgColor} border ${nodeStyle.borderColor} ${
selected ? 'ring-2 ring-blue-500' : ''
}`}
>
<div className="flex items-center space-x-1">
<div className={`flex-shrink-0 w-3 h-3 rounded-lg ${getIconBgColor(data.icon || 'settings')} flex items-center justify-center`}>
{getIconComponent(data.icon || 'settings')}
@ -193,7 +200,11 @@ const TestStepNode = ({ data }: { data: any }) => {
{/* 判断步骤使用菱形 */}
{data.stepType === 4 && (
<div className={`px-2 py-1 ${nodeStyle.shape} ${nodeStyle.bgColor} border ${nodeStyle.borderColor}`}>
<div
className={`px-2 py-1 ${nodeStyle.shape} ${nodeStyle.bgColor} border ${nodeStyle.borderColor} ${
selected ? 'ring-2 ring-blue-500' : ''
}`}
>
<div className="flex items-center space-x-1">
<div className={`flex-shrink-0 w-3 h-3 rounded-lg ${getIconBgColor(data.icon || 'settings')} flex items-center justify-center`}>
{getIconComponent(data.icon || 'settings')}
@ -206,122 +217,247 @@ const TestStepNode = ({ data }: { data: any }) => {
</div>
</div>
)}
{/* 连接点 - 根据节点类型显示不同的连接点 */}
{/* 开始节点 (type=1) - 只有输出连接点 */}
{data.stepType === 1 && (
<>
<Handle
type="source"
position={Position.Right}
id="right"
className="w-3 h-3 bg-green-500 border-2 border-white rounded-full hover:bg-green-600"
style={{ right: -6 }}
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
className="w-3 h-3 bg-green-500 border-2 border-white rounded-full hover:bg-green-600"
style={{ bottom: -6 }}
/>
</>
)}
{/* 结束节点 (type=2) - 只有输入连接点 */}
{data.stepType === 2 && (
<>
<Handle
type="target"
position={Position.Top}
id="top"
className="w-3 h-3 bg-blue-500 border-2 border-white rounded-full hover:bg-blue-600"
style={{ top: -6 }}
/>
<Handle
type="target"
position={Position.Left}
id="left"
className="w-3 h-3 bg-blue-500 border-2 border-white rounded-full hover:bg-blue-600"
style={{ left: -6 }}
/>
</>
)}
{/* 处理节点 (type=3) - 有输入和输出连接点 */}
{data.stepType === 3 && (
<>
<Handle
type="target"
position={Position.Top}
id="top"
className="w-3 h-3 bg-blue-500 border-2 border-white rounded-full hover:bg-blue-600"
style={{ top: -6 }}
/>
<Handle
type="source"
position={Position.Right}
id="right"
className="w-3 h-3 bg-green-500 border-2 border-white rounded-full hover:bg-green-600"
style={{ right: -6 }}
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
className="w-3 h-3 bg-green-500 border-2 border-white rounded-full hover:bg-green-600"
style={{ bottom: -6 }}
/>
<Handle
type="target"
position={Position.Left}
id="left"
className="w-3 h-3 bg-blue-500 border-2 border-white rounded-full hover:bg-blue-600"
style={{ left: -6 }}
/>
</>
)}
{/* 判断节点 (type=4) - 有输入和输出连接点 */}
{data.stepType === 4 && (
<>
<Handle
type="target"
position={Position.Top}
id="top"
className="w-3 h-3 bg-blue-500 border-2 border-white rounded-full hover:bg-blue-600"
style={{ top: -6 }}
/>
<Handle
type="source"
position={Position.Right}
id="right"
className="w-3 h-3 bg-green-500 border-2 border-white rounded-full hover:bg-green-600"
style={{ right: -6 }}
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
className="w-3 h-3 bg-green-500 border-2 border-white rounded-full hover:bg-green-600"
style={{ bottom: -6 }}
/>
<Handle
type="target"
position={Position.Left}
id="left"
className="w-3 h-3 bg-blue-500 border-2 border-white rounded-full hover:bg-blue-600"
style={{ left: -6 }}
/>
</>
)}
</div>
);
};
// 创建节点类型映射
const nodeTypes = {
testStep: TestStepNode,
};
function ReactFlowDesignerInner({
flowSteps,
onSaveFlow,
onLoadFlow
onSaveFlow
}: ReactFlowDesignerProps) {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [showMiniMap, setShowMiniMap] = useState(true);
const [showControls, setShowControls] = useState(true);
const [isDraggingOver, setIsDraggingOver] = useState(false);
const [currentZoom, setCurrentZoom] = useState(1.5);
// 初始化时打印默认缩放级别
useEffect(() => {
console.log('React Flow initial zoom:', currentZoom);
}, []);
const [edgeType, setEdgeType] = useState<'step' | 'smoothstep' | 'straight'>('straight');
// 添加一个 ref 来直接访问 React Flow 容器
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const { project } = useReactFlow();
const { screenToFlowPosition } = useReactFlow();
// 使用ref存储函数,避免useEffect依赖问题
const screenToFlowPositionRef = useRef(screenToFlowPosition);
const setNodesRef = useRef(setNodes);
// 备用拖拽事件监听器
// 更新ref值
useEffect(() => {
const wrapper = reactFlowWrapper.current;
if (wrapper) {
console.log('Setting up backup drag events on wrapper:', wrapper);
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
// 检查是否允许 move 操作,否则使用 copy
if (e.dataTransfer!.effectAllowed.includes('move')) {
e.dataTransfer!.dropEffect = 'move';
} else {
e.dataTransfer!.dropEffect = 'copy';
}
console.log('Backup dragover event triggered');
};
const handleDrop = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
console.log('Backup drop event triggered');
const data = e.dataTransfer?.getData('application/json');
console.log('Backup drop data:', data);
if (data) {
try {
const parsedData = JSON.parse(data);
console.log('Backup parsed data:', parsedData);
if (parsedData.type === 'test-step') {
const { step } = parsedData;
// 获取 React Flow 容器的边界
const reactFlowBounds = wrapper.getBoundingClientRect();
const position = project({
x: e.clientX - reactFlowBounds.left,
y: e.clientY - reactFlowBounds.top,
});
// 检查是否可以添加此类型的节点
if (!canAddNodeType(step.stepType)) {
console.log('Cannot add node: type limit reached');
return;
}
const newNode = {
id: `node-${Date.now()}`,
type: 'testStep',
position,
data: {
stepId: step.id,
stepName: step.stepName,
stepType: step.stepType,
stepTypeName: step.stepTypeName,
description: step.description,
icon: step.icon,
},
};
console.log('Adding node via backup drop:', newNode);
setNodes((nds) => nds.concat(newNode));
}
} catch (error) {
console.error('Error in backup drop handler:', error);
}
}
};
screenToFlowPositionRef.current = screenToFlowPosition;
setNodesRef.current = setNodes;
}, [screenToFlowPosition, setNodes]);
// 连线验证规则
const isValidConnection = useCallback((connection: Connection) => {
const sourceNode = nodes.find(node => node.id === connection.source);
const targetNode = nodes.find(node => node.id === connection.target);
if (!sourceNode || !targetNode) {
return false;
}
wrapper.addEventListener('dragover', handleDragOver, { passive: false });
wrapper.addEventListener('drop', handleDrop, { passive: false });
const sourceType = sourceNode.data.stepType;
const targetType = targetNode.data.stepType;
// 连线规则:
// 1. 不能从结束节点连出
// 2. 不能连到开始节点
// 3. 不能自己连自己
// 4. 不能重复连线
if (sourceType === 2) {
alert('结束节点不能作为连线的起点!');
return false;
}
if (targetType === 1) {
alert('不能连线到开始节点!');
return false;
}
return () => {
wrapper.removeEventListener('dragover', handleDragOver);
wrapper.removeEventListener('drop', handleDrop);
};
if (connection.source === connection.target) {
alert('不能连线到自己!');
return false;
}
}, [setNodes, project]);
// 检查是否已存在相同的连线
const existingEdge = edges.find(edge =>
edge.source === connection.source && edge.target === connection.target
);
if (existingEdge) {
alert('该连线已存在!');
return false;
}
return true;
}, [nodes, edges]);
const onConnect = useCallback(
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
(params: Connection) => {
console.log('尝试连接:', params);
if (isValidConnection(params) && params.source && params.target) {
const newEdge = {
...params,
id: `edge-${Date.now()}`,
type: edgeType,
animated: false,
style: {
stroke: '#3b82f6',
strokeWidth: 2,
},
data: {
condition: 'default'
}
};
console.log('创建新连线:', newEdge);
setEdges((eds) => {
const updatedEdges = addEdge(newEdge, eds);
console.log('当前所有连线:', updatedEdges);
return updatedEdges;
});
} else {
console.log('连接验证失败');
}
},
[isValidConnection, setEdges, edgeType]
);
// 处理连线点击事件
const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => {
alert(`连线信息:\n从: ${edge.source}\n到: ${edge.target}\n条件: ${edge.data?.condition || '默认'}`);
}, []);
// 处理连线双击事件
const onEdgeDoubleClick = useCallback((event: React.MouseEvent, edge: Edge) => {
const newCondition = prompt('请输入连线条件:', edge.data?.condition || '默认');
if (newCondition !== null) {
setEdges((currentEdges) => currentEdges.map(e =>
e.id === edge.id
? { ...e, data: { ...e.data, condition: newCondition } }
: e
));
}
}, [setEdges]);
// 处理缩放事件
const onMove = useCallback((event: any, viewport: any) => {
const { zoom } = viewport;
setCurrentZoom(zoom);
console.log('React Flow zoom changed:', zoom);
}, []);
// 点击画布取消选择
const onPaneClick = useCallback(() => {
// 可以在这里添加画布点击逻辑
}, []);
// 检查是否可以添加特定类型的节点
@ -329,14 +465,12 @@ function ReactFlowDesignerInner({
if (stepType === 1) { // 开始步骤
const startNodes = nodes.filter(node => node.data.stepType === 1);
if (startNodes.length > 0) {
console.log('Cannot add Start node: already exists');
alert('流程图中只能有一个开始节点!');
return false;
}
} else if (stepType === 2) { // 结束步骤
const endNodes = nodes.filter(node => node.data.stepType === 2);
if (endNodes.length > 0) {
console.log('Cannot add End node: already exists');
alert('流程图中只能有一个结束节点!');
return false;
}
@ -344,72 +478,41 @@ function ReactFlowDesignerInner({
return true;
}, [nodes]);
// 使用ref存储canAddNodeType函数
const canAddNodeTypeRef = useRef(canAddNodeType);
useEffect(() => {
canAddNodeTypeRef.current = canAddNodeType;
}, [canAddNodeType]);
// 重置缩放级别
const resetZoom = useCallback(() => {
const reactFlowInstance = useReactFlow();
reactFlowInstance.setViewport({ x: 0, y: 0, zoom: 1.5 });
console.log('React Flow zoom reset to 150%');
}, []);
const handleSave = () => {
const handleSave = useCallback(() => {
onSaveFlow(nodes as any[], edges as any[]);
};
}, [nodes, edges, onSaveFlow]);
const handleClear = () => {
const handleClear = useCallback(() => {
setNodes([]);
setEdges([]);
};
const handleUndo = () => {
// React Flow 内置了撤销/重做功能,通过 Controls 组件提供
console.log('撤销功能通过 Controls 组件提供');
};
const handleRedo = () => {
console.log('重做功能通过 Controls 组件提供');
};
const handleExport = () => {
const data = { nodes, edges };
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'test-flow.json';
a.click();
URL.revokeObjectURL(url);
};
const handleImport = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = (e: any) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e: any) => {
try {
const data = JSON.parse(e.target.result);
setNodes(data.nodes || []);
setEdges(data.edges || []);
} catch (error) {
console.error('导入文件失败:', error);
}
};
reader.readAsText(file);
}, [setNodes, setEdges]);
// 删除选中的连线
const handleDeleteSelected = useCallback(() => {
setEdges((currentEdges) => {
const selectedEdges = currentEdges.filter(edge => edge.selected);
if (selectedEdges.length > 0) {
return currentEdges.filter(edge => !edge.selected);
}
};
input.click();
};
return currentEdges;
});
}, [setEdges]);
const toggleMiniMap = () => {
setShowMiniMap(!showMiniMap);
};
const toggleControls = () => {
setShowControls(!showControls);
};
const toggleControls = useCallback(() => {
setShowControls((prev) => !prev);
}, []);
// 处理拖拽事件
const onDragOver = useCallback((event: React.DragEvent) => {
@ -420,38 +523,29 @@ function ReactFlowDesignerInner({
} else {
event.dataTransfer.dropEffect = 'copy';
}
console.log('React Flow onDragOver triggered');
}, []);
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
console.log('React Flow onDrop triggered');
try {
const data = event.dataTransfer.getData('application/json');
console.log('React Flow drop data:', data);
if (data) {
const parsedData = JSON.parse(data);
console.log('React Flow parsed data:', parsedData);
if (parsedData.type === 'test-step') {
const { step } = parsedData;
// 使用 React Flow 的 project 函数计算位置
// 获取 React Flow 容器的边界并计算相对位置
const reactFlowBounds = reactFlowWrapper.current?.getBoundingClientRect();
const position = project({
x: event.clientX - (reactFlowBounds?.left || 0),
y: event.clientY - (reactFlowBounds?.top || 0),
// 使用新的screenToFlowPosition函数,不需要手动计算边界
const position = screenToFlowPositionRef.current({
x: event.clientX,
y: event.clientY,
});
console.log('React Flow calculated position:', position);
// 检查是否可以添加此类型的节点
if (!canAddNodeType(step.stepType)) {
console.log('Cannot add node: type limit reached');
if (!canAddNodeTypeRef.current(step.stepType)) {
return;
}
@ -469,16 +563,14 @@ function ReactFlowDesignerInner({
},
};
console.log('React Flow new node:', newNode);
setNodes((nds) => nds.concat(newNode));
console.log('React Flow node added successfully');
setNodesRef.current((nds) => nds.concat(newNode));
}
}
} catch (error) {
console.error('React Flow drop error:', error);
}
},
[setNodes, project]
[] // 移除依赖项,使用ref
);
return (
@ -490,15 +582,21 @@ function ReactFlowDesignerInner({
</CardTitle>
<div className="flex items-center space-x-2">
<Button
size="sm"
variant="outline"
onClick={toggleMiniMap}
className="flex items-center gap-1"
>
{showMiniMap ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
{/* 连线类型选择器 */}
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-600 dark:text-gray-300">线:</span>
<Select value={edgeType} onValueChange={(value: 'step' | 'smoothstep' | 'straight') => setEdgeType(value)}>
<SelectTrigger className="w-32 h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="straight">线</SelectItem>
<SelectItem value="smoothstep">线</SelectItem>
<SelectItem value="step">线</SelectItem>
</SelectContent>
</Select>
</div>
<Button
size="sm"
variant="outline"
@ -511,151 +609,42 @@ function ReactFlowDesignerInner({
<Button
size="sm"
variant="outline"
onClick={handleClear}
onClick={handleDeleteSelected}
className="flex items-center gap-1"
>
<Trash2 className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="outline"
onClick={handleImport}
className="flex items-center gap-1"
>
<Upload className="h-4 w-4" />
线
</Button>
<Button
size="sm"
variant="outline"
onClick={handleExport}
onClick={handleClear}
className="flex items-center gap-1"
>
<Download className="h-4 w-4" />
<Trash2 className="h-4 w-4" />
</Button>
<Button onClick={handleSave} className="flex items-center gap-1">
<Save className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="outline"
onClick={() => {
const testNode = {
id: `test-node-${Date.now()}`,
type: 'testStep',
position: { x: 100, y: 100 },
data: {
stepId: 'test',
stepName: '测试节点',
stepType: 1,
stepTypeName: '测试类型',
icon: 'settings',
},
};
setNodes((nds) => nds.concat(testNode));
console.log('Test node added');
}}
className="flex items-center gap-1"
>
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
</CardHeader>
<CardContent className="p-0 flex-1 overflow-hidden">
<div ref={reactFlowWrapper} className="w-full h-full relative">
{/* 添加一个拖拽区域覆盖整个画布 - 只在拖拽时激活 */}
<div
className={`absolute inset-0 z-10 transition-all duration-200 ${
isDraggingOver
? 'pointer-events-auto bg-blue-50/20'
: 'pointer-events-none bg-transparent'
}`}
onDragEnter={(e) => {
e.preventDefault();
setIsDraggingOver(true);
}}
onDragLeave={(e) => {
e.preventDefault();
// 只有当真正离开容器时才设置 false
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
setIsDraggingOver(false);
}
}}
onDragOver={(e) => {
e.preventDefault();
// 检查是否允许 move 操作,否则使用 copy
if (e.dataTransfer.effectAllowed.includes('move')) {
e.dataTransfer.dropEffect = 'move';
} else {
e.dataTransfer.dropEffect = 'copy';
}
console.log('Overlay dragover event triggered');
}}
onDrop={(e) => {
e.preventDefault();
e.stopPropagation();
setIsDraggingOver(false);
console.log('Overlay drop event triggered');
const data = e.dataTransfer.getData('application/json');
console.log('Overlay drop data:', data);
if (data) {
try {
const parsedData = JSON.parse(data);
console.log('Overlay parsed data:', parsedData);
if (parsedData.type === 'test-step') {
const { step } = parsedData;
// 获取 React Flow 容器的边界并计算相对位置
const reactFlowBounds = reactFlowWrapper.current?.getBoundingClientRect();
const position = project({
x: e.clientX - (reactFlowBounds?.left || 0),
y: e.clientY - (reactFlowBounds?.top || 0),
});
// 检查是否可以添加此类型的节点
if (!canAddNodeType(step.stepType)) {
console.log('Cannot add node: type limit reached');
return;
}
const newNode = {
id: `node-${Date.now()}`,
type: 'testStep',
position,
data: {
stepId: step.id,
stepName: step.stepName,
stepType: step.stepType,
stepTypeName: step.stepTypeName,
description: step.description,
icon: step.icon,
},
};
console.log('Adding node via overlay drop:', newNode);
setNodes((nds) => nds.concat(newNode));
}
} catch (error) {
console.error('Error in overlay drop handler:', error);
}
}
}}
/>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onEdgeClick={onEdgeClick}
onEdgeDoubleClick={onEdgeDoubleClick}
onDragOver={onDragOver}
onDrop={onDrop}
onMove={onMove}
onPaneClick={onPaneClick}
nodeTypes={nodeTypes}
defaultViewport={{ x: 0, y: 0, zoom: 1.5 }}
minZoom={1}
@ -663,10 +652,12 @@ function ReactFlowDesignerInner({
style={{ width: '100%', height: '100%' }}
deleteKeyCode="Delete"
multiSelectionKeyCode="Shift"
connectOnClick={true}
snapToGrid={true}
snapGrid={[15, 15]}
>
<Background />
{showControls && <Controls />}
{showMiniMap && <MiniMap />}
<Panel position="top-left" className="bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm p-2 rounded shadow">
<div className="text-xs text-gray-600 dark:text-gray-300 flex items-center justify-between">
<span>: {Math.round(currentZoom * 100)}%</span>
@ -678,6 +669,12 @@ function ReactFlowDesignerInner({
</button>
</div>
</Panel>
<Panel position="top-right" className="bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm p-2 rounded shadow">
<div className="text-xs text-gray-600 dark:text-gray-300">
<div>: {nodes.length}</div>
<div>线: {edges.length}</div>
</div>
</Panel>
</ReactFlow>
</div>
</CardContent>

117
src/X1.WebUI/src/pages/testcases/ReactFlowTest.tsx

@ -0,0 +1,117 @@
import React, { useState, useCallback } from 'react';
import ReactFlow, {
Node,
Edge,
addEdge,
Connection,
useNodesState,
useEdgesState,
Handle,
Position,
ReactFlowProvider,
} from 'reactflow';
import 'reactflow/dist/style.css';
// 简单的测试节点
const TestNode = ({ data }: { data: any }) => {
return (
<div className="px-4 py-2 shadow-md rounded-md bg-white border-2 border-stone-400">
<div className="flex items-center">
<div className="rounded-full w-12 h-12 flex items-center justify-center bg-gray-100">
{data.emoji}
</div>
<div className="ml-2">
<div className="text-lg font-bold">{data.label}</div>
<div className="text-gray-500">{data.type}</div>
</div>
</div>
<Handle
type="target"
position={Position.Top}
className="w-3 h-3 bg-blue-500 border-2 border-white rounded-full"
style={{ top: -6 }}
/>
<Handle
type="source"
position={Position.Bottom}
className="w-3 h-3 bg-green-500 border-2 border-white rounded-full"
style={{ bottom: -6 }}
/>
</div>
);
};
const nodeTypes = {
testNode: TestNode,
};
const initialNodes: Node[] = [
{
id: '1',
type: 'testNode',
position: { x: 250, y: 100 },
data: { label: '开始', type: '开始节点', emoji: '🚀' },
},
{
id: '2',
type: 'testNode',
position: { x: 250, y: 300 },
data: { label: '处理', type: '处理节点', emoji: '⚙️' },
},
{
id: '3',
type: 'testNode',
position: { x: 250, y: 500 },
data: { label: '结束', type: '结束节点', emoji: '🏁' },
},
];
function ReactFlowTestInner() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = useCallback(
(params: Connection) => {
console.log('连接参数:', params);
if (params.source && params.target) {
const newEdge = {
...params,
id: `edge-${Date.now()}`,
type: 'smoothstep',
animated: false,
style: { stroke: '#3b82f6', strokeWidth: 2 },
};
console.log('创建新连线:', newEdge);
setEdges((eds) => {
const updatedEdges = addEdge(newEdge, eds);
console.log('当前所有连线:', updatedEdges);
return updatedEdges;
});
}
},
[setEdges]
);
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
/>
</div>
);
}
export default function ReactFlowTest() {
return (
<ReactFlowProvider>
<ReactFlowTestInner />
</ReactFlowProvider>
);
}
Loading…
Cancel
Save