This commit is contained in:
dengqichen 2025-10-21 10:31:09 +08:00
parent a8c512eee6
commit 051709ed2a
3 changed files with 135 additions and 146 deletions

View File

@ -26,6 +26,7 @@ interface FlowCanvasProps {
onEdgeClick?: (event: React.MouseEvent, edge: any) => void; onEdgeClick?: (event: React.MouseEvent, edge: any) => void;
onDrop?: (event: React.DragEvent) => void; onDrop?: (event: React.DragEvent) => void;
onDragOver?: (event: React.DragEvent) => void; onDragOver?: (event: React.DragEvent) => void;
onViewportChange?: () => void;
className?: string; className?: string;
} }
@ -38,6 +39,7 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({
onEdgeClick, onEdgeClick,
onDrop, onDrop,
onDragOver, onDragOver,
onViewportChange,
className = '' className = ''
}) => { }) => {
const [nodes, , onNodesStateChange] = useNodesState(initialNodes); const [nodes, , onNodesStateChange] = useNodesState(initialNodes);
@ -133,6 +135,7 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({
onEdgeClick={onEdgeClick} onEdgeClick={onEdgeClick}
onDrop={handleDrop} onDrop={handleDrop}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onMove={onViewportChange}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
isValidConnection={isValidConnection} isValidConnection={isValidConnection}
fitView fitView

View File

@ -1,11 +1,10 @@
import React from 'react'; import React from 'react';
import { Button, Space, Divider, Tooltip } from 'antd'; import { Button, Divider, Tooltip } from 'antd';
import { import {
SaveOutlined, SaveOutlined,
UndoOutlined, UndoOutlined,
RedoOutlined, RedoOutlined,
CopyOutlined, CopyOutlined,
ScissorOutlined,
DeleteOutlined, DeleteOutlined,
ZoomInOutlined, ZoomInOutlined,
ZoomOutOutlined, ZoomOutOutlined,
@ -20,8 +19,6 @@ interface WorkflowToolbarProps {
onUndo?: () => void; onUndo?: () => void;
onRedo?: () => void; onRedo?: () => void;
onCopy?: () => void; onCopy?: () => void;
onCut?: () => void;
onPaste?: () => void;
onDelete?: () => void; onDelete?: () => void;
onZoomIn?: () => void; onZoomIn?: () => void;
onZoomOut?: () => void; onZoomOut?: () => void;
@ -40,8 +37,6 @@ const WorkflowToolbar: React.FC<WorkflowToolbarProps> = ({
onUndo, onUndo,
onRedo, onRedo,
onCopy, onCopy,
onCut,
onPaste,
onDelete, onDelete,
onZoomIn, onZoomIn,
onZoomOut, onZoomOut,
@ -67,7 +62,7 @@ const WorkflowToolbar: React.FC<WorkflowToolbarProps> = ({
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)'
}} }}
> >
{/* 左侧:标题和返回按钮 */} {/* 左侧:返回按钮和标题 */}
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<Tooltip title="返回列表"> <Tooltip title="返回列表">
<Button <Button
@ -78,154 +73,124 @@ const WorkflowToolbar: React.FC<WorkflowToolbarProps> = ({
/> />
</Tooltip> </Tooltip>
<Divider type="vertical" /> <Divider type="vertical" />
<div> <h2 style={{
<h2 style={{ margin: 0,
margin: 0, fontSize: '16px',
fontSize: '16px', fontWeight: '600',
fontWeight: '600', color: '#374151'
color: '#374151' }}>
}}> {title}
{title} </h2>
</h2>
<div style={{
fontSize: '12px',
color: '#6b7280',
marginTop: '2px'
}}>
React Flow
</div>
</div>
</div> </div>
{/* 中间:主要操作按钮 */} {/* 右侧:操作按钮区域 */}
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Space size={4}> {/* 撤销/重做 */}
{/* 保存 */} <Tooltip title="撤销">
<Tooltip title="保存工作流"> <Button
<Button type="text"
type="primary" icon={<UndoOutlined />}
icon={<SaveOutlined />} onClick={onUndo}
onClick={onSave} disabled={!canUndo}
size="small" size="small"
> />
</Tooltip>
</Button> <Tooltip title="重做">
</Tooltip> <Button
type="text"
icon={<RedoOutlined />}
onClick={onRedo}
disabled={!canRedo}
size="small"
/>
</Tooltip>
<Divider type="vertical" /> <Divider type="vertical" />
{/* 撤销/重做 */} {/* 编辑操作 */}
<Tooltip title="撤销"> <Tooltip title="复制">
<Button <Button
type="text" type="text"
icon={<UndoOutlined />} icon={<CopyOutlined />}
onClick={onUndo} onClick={onCopy}
disabled={!canUndo} size="small"
size="small" />
/> </Tooltip>
</Tooltip> <Tooltip title="删除选中">
<Tooltip title="重做"> <Button
<Button type="text"
type="text" icon={<DeleteOutlined />}
icon={<RedoOutlined />} onClick={onDelete}
onClick={onRedo} size="small"
disabled={!canRedo} style={{ color: '#ef4444' }}
size="small" />
/> </Tooltip>
</Tooltip> <Tooltip title="全选">
<Button
type="text"
icon={<SelectOutlined />}
onClick={onSelectAll}
size="small"
/>
</Tooltip>
<Divider type="vertical" /> <Divider type="vertical" />
{/* 编辑操作 */} {/* 视图操作 */}
<Tooltip title="复制"> <Tooltip title="放大">
<Button <Button
type="text" type="text"
icon={<CopyOutlined />} icon={<ZoomInOutlined />}
onClick={onCopy} onClick={onZoomIn}
size="small" size="small"
/> />
</Tooltip> </Tooltip>
<Tooltip title="剪切"> <Tooltip title="缩小">
<Button <Button
type="text" type="text"
icon={<ScissorOutlined />} icon={<ZoomOutOutlined />}
onClick={onCut} onClick={onZoomOut}
size="small" size="small"
/> />
</Tooltip> </Tooltip>
<Tooltip title="删除选中"> <Tooltip title="适应视图">
<Button <Button
type="text" type="text"
icon={<DeleteOutlined />} icon={<ExpandOutlined />}
onClick={onDelete} onClick={onFitView}
size="small" size="small"
style={{ color: '#ef4444' }} />
/> </Tooltip>
</Tooltip>
<Tooltip title="全选">
<Button
type="text"
icon={<SelectOutlined />}
onClick={onSelectAll}
size="small"
/>
</Tooltip>
<Divider type="vertical" /> <Divider type="vertical" />
{/* 视图操作 */} {/* 缩放比例显示 */}
<Tooltip title="放大">
<Button
type="text"
icon={<ZoomInOutlined />}
onClick={onZoomIn}
size="small"
/>
</Tooltip>
<Tooltip title="缩小">
<Button
type="text"
icon={<ZoomOutOutlined />}
onClick={onZoomOut}
size="small"
/>
</Tooltip>
<Tooltip title="适应视图">
<Button
type="text"
icon={<ExpandOutlined />}
onClick={onFitView}
size="small"
/>
</Tooltip>
</Space>
</div>
{/* 右侧:状态信息 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
fontSize: '12px',
color: '#6b7280'
}}>
<div style={{ <div style={{
background: '#f3f4f6', background: '#f3f4f6',
padding: '4px 8px', padding: '4px 8px',
borderRadius: '4px', borderRadius: '4px',
fontFamily: 'monospace' fontSize: '12px',
color: '#6b7280',
fontFamily: 'monospace',
minWidth: '70px',
textAlign: 'center'
}}> }}>
: {Math.round(zoom * 100)}% {Math.round(zoom * 100)}%
</div>
<div style={{
background: '#ecfdf5',
color: '#065f46',
padding: '4px 8px',
borderRadius: '4px',
fontWeight: '500'
}}>
React Flow
</div> </div>
<Divider type="vertical" />
{/* 保存按钮(最右侧) */}
<Tooltip title="保存工作流">
<Button
type="primary"
icon={<SaveOutlined />}
onClick={onSave}
size="small"
>
</Button>
</Tooltip>
</div> </div>
</div> </div>
); );

View File

@ -34,6 +34,7 @@ const WorkflowDesignInner: React.FC = () => {
} = useReactFlow(); } = useReactFlow();
const [workflowTitle, setWorkflowTitle] = useState('新建工作流'); const [workflowTitle, setWorkflowTitle] = useState('新建工作流');
const [currentZoom, setCurrentZoom] = useState(1); // 当前缩放比例
const reactFlowWrapper = useRef<HTMLDivElement>(null); const reactFlowWrapper = useRef<HTMLDivElement>(null);
// 当前工作流ID // 当前工作流ID
@ -67,6 +68,11 @@ const WorkflowDesignInner: React.FC = () => {
loadData(); loadData();
}, [currentWorkflowId, loadWorkflow, setNodes, setEdges]); }, [currentWorkflowId, loadWorkflow, setNodes, setEdges]);
// 初始化缩放比例
useEffect(() => {
setCurrentZoom(getZoom());
}, [getZoom]);
// 自动适应视图 // 自动适应视图
useEffect(() => { useEffect(() => {
// 延迟执行fitView以确保节点已渲染 // 延迟执行fitView以确保节点已渲染
@ -77,10 +83,12 @@ const WorkflowDesignInner: React.FC = () => {
minZoom: 1.0, // 最小缩放100% minZoom: 1.0, // 最小缩放100%
maxZoom: 1.0 // 最大缩放100%确保默认100% maxZoom: 1.0 // 最大缩放100%确保默认100%
}); });
// 更新zoom显示
setTimeout(() => setCurrentZoom(getZoom()), 850);
}, 100); }, 100);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [fitView]); }, [fitView, getZoom]);
// 初始化示例节点 - 优化位置和布局 // 初始化示例节点 - 优化位置和布局
const initialNodes: FlowNode[] = [ const initialNodes: FlowNode[] = [
@ -217,15 +225,21 @@ const WorkflowDesignInner: React.FC = () => {
const handleFitView = useCallback(() => { const handleFitView = useCallback(() => {
fitView({ padding: 0.2, duration: 800 }); fitView({ padding: 0.2, duration: 800 });
}, [fitView]); // 延迟更新zoom值以获取最新的缩放比例
setTimeout(() => setCurrentZoom(getZoom()), 850);
}, [fitView, getZoom]);
const handleZoomIn = useCallback(() => { const handleZoomIn = useCallback(() => {
zoomIn({ duration: 300 }); zoomIn({ duration: 300 });
}, [zoomIn]); // 延迟更新zoom值以获取最新的缩放比例
setTimeout(() => setCurrentZoom(getZoom()), 350);
}, [zoomIn, getZoom]);
const handleZoomOut = useCallback(() => { const handleZoomOut = useCallback(() => {
zoomOut({ duration: 300 }); zoomOut({ duration: 300 });
}, [zoomOut]); // 延迟更新zoom值以获取最新的缩放比例
setTimeout(() => setCurrentZoom(getZoom()), 350);
}, [zoomOut, getZoom]);
// 处理节点拖拽放置 - 使用官方推荐的screenToFlowPosition方法 // 处理节点拖拽放置 - 使用官方推荐的screenToFlowPosition方法
const handleDrop = useCallback((event: React.DragEvent) => { const handleDrop = useCallback((event: React.DragEvent) => {
@ -352,6 +366,12 @@ const WorkflowDesignInner: React.FC = () => {
setConfigEdge(null); setConfigEdge(null);
}, []); }, []);
// 监听视图变化(缩放、平移等)
const handleViewportChange = useCallback(() => {
const zoom = getZoom();
setCurrentZoom(zoom);
}, [getZoom]);
return ( return (
<div <div
className="workflow-design-container" className="workflow-design-container"
@ -375,7 +395,7 @@ const WorkflowDesignInner: React.FC = () => {
onFitView={handleFitView} onFitView={handleFitView}
canUndo={false} canUndo={false}
canRedo={false} canRedo={false}
zoom={getZoom()} zoom={currentZoom}
/> />
{/* 主要内容区域 */} {/* 主要内容区域 */}
@ -394,6 +414,7 @@ const WorkflowDesignInner: React.FC = () => {
onNodeClick={handleNodeClick} onNodeClick={handleNodeClick}
onEdgeClick={handleEdgeClick} onEdgeClick={handleEdgeClick}
onDrop={handleDrop} onDrop={handleDrop}
onViewportChange={handleViewportChange}
className="workflow-canvas" className="workflow-canvas"
/> />
</div> </div>