1
This commit is contained in:
parent
a8c512eee6
commit
051709ed2a
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user