feat(flowEditor): 添加节点对齐辅助线功能

- 新增 useAlignmentGuidelines 钩子用于处理对齐逻辑
- 在 ReactFlow 组件中集成对齐辅助线
- 实现节点拖动时动态显示对齐线
- 拖动停止后清除对齐线
master
钟良源 5 months ago
parent 1c977cb4c2
commit d7ad2a5730

@ -0,0 +1,125 @@
import React, { useState, useCallback } from 'react';
import { Node, useReactFlow } from '@xyflow/react';
interface Guideline {
type: 'left' | 'right' | 'center' | 'top' | 'middle' | 'bottom';
x?: number;
y?: number;
from: string;
to: string;
}
interface AlignmentGuidelines {
verticalLines: Array<Guideline & { x: number }>;
horizontalLines: Array<Guideline & { y: number }>;
}
const TOLERANCE = 2;
export const useAlignmentGuidelines = () => {
const [guidelines, setGuidelines] = useState<AlignmentGuidelines>({
verticalLines: [],
horizontalLines: []
});
const reactFlowInstance = useReactFlow();
const clearGuidelines = useCallback(() => {
setGuidelines({ verticalLines: [], horizontalLines: [] });
}, []);
const getGuidelines = useCallback((draggingNode: Node, allNodes: Node[]) => {
const { position, width = 100, height = 50 } = draggingNode;
const { x: dx, y: dy } = position;
const dWidth = width;
const dHeight = height;
const verticalLines: Array<Guideline & { x: number }> = [];
const horizontalLines: Array<Guideline & { y: number }> = [];
allNodes.forEach(node => {
if (node.id === draggingNode.id) return;
const { position: pos, measured } = node;
const { x, y } = pos;
const width2 = measured.width | 0;
const height2 = measured.height | 0;
// 垂直对齐
if (Math.abs(dx - x) < TOLERANCE) {
verticalLines.push({ type: 'left', x: x - width2 - 1, from: draggingNode.id, to: node.id });
}
// 水平对齐
if (Math.abs(dy - y) < TOLERANCE) {
horizontalLines.push({ type: 'top', y: y - height2 / 2 - 3, from: draggingNode.id, to: node.id });
}
});
setGuidelines({ verticalLines, horizontalLines });
return { verticalLines, horizontalLines };
}, []);
const AlignmentGuides = () => {
// 将节点坐标转换为屏幕坐标
const screenVerticalLines = guidelines.verticalLines.map(line => ({
...line,
x: reactFlowInstance.flowToScreenPosition({ x: line.x, y: 0 }).x
}));
const screenHorizontalLines = guidelines.horizontalLines.map(line => ({
...line,
y: reactFlowInstance.flowToScreenPosition({ x: 0, y: line.y }).y
}));
return (
<>
{/* 垂直辅助线 */}
{screenVerticalLines.map((line, i) => (
<div
key={`v-${i}`}
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: `${Math.round(line.x)}px`,
width: '1px',
backgroundColor: '#2196F3',
pointerEvents: 'none',
zIndex: 1000,
transform: 'translateX(-0.5px)'
}}
/>
))}
{/* 水平辅助线 */}
{screenHorizontalLines.map((line, i) => (
<div
key={`h-${i}`}
style={{
position: 'absolute',
left: 0,
right: 0,
top: `${Math.round(line.y)}px`,
height: '1px',
backgroundColor: '#2196F3',
pointerEvents: 'none',
zIndex: 1000,
transform: 'translateY(-0.5px)'
}}
/>
))}
</>
);
};
return {
guidelines,
getGuidelines,
clearGuidelines,
AlignmentGuides
};
};
export type { AlignmentGuidelines, Guideline };

@ -28,9 +28,10 @@ import NodeContextMenu from './components/nodeContextMenu';
import EdgeContextMenu from './components/edgeContextMenu';
import PaneContextMenu from './components/paneContextMenu';
import NodeEditModal from './components/nodeEditModal';
import AddNodeMenu from './components/addNodeMenu'; // 添加导入
import AddNodeMenu from './components/addNodeMenu';
import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType';
import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
const edgeTypes: EdgeTypes = {
custom: CustomEdge
@ -69,6 +70,8 @@ const FlowEditor: React.FC = () => {
const [edgeForNodeAdd, setEdgeForNodeAdd] = useState<Edge | null>(null);
const [positionForNodeAdd, setPositionForNodeAdd] = useState<{ x: number, y: number } | null>(null);
const { getGuidelines, clearGuidelines, AlignmentGuides } = useAlignmentGuidelines();
// 获取handle类型 (api或data)
const getHandleType = (handleId: string, nodeParams: any) => {
// 检查是否为api类型的handle
@ -274,6 +277,20 @@ const FlowEditor: React.FC = () => {
[reactFlowInstance]
);
const onNodeDrag = useCallback(
(_: any, node: Node) => {
// 获取对齐线
getGuidelines(node, nodes);
},
[nodes, getGuidelines]
);
// 节点拖拽结束处理
const onNodeDragStop = useCallback(() => {
// 清除对齐线
clearGuidelines();
}, [clearGuidelines]);
useEffect(() => {
const { nodes: convertedNodes, edges: convertedEdges } = convertFlowData(exampleFlowData);
// 为所有边添加类型
@ -601,12 +618,16 @@ const FlowEditor: React.FC = () => {
edges={edges}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
snapToGrid={true}
snapGrid={[2, 2]}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onReconnect={onReconnect}
onDrop={onDrop}
onDragOver={onDragOver}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragStop}
onNodeContextMenu={onNodeContextMenu}
onEdgeContextMenu={onEdgeContextMenu}
onNodeDoubleClick={onNodeDoubleClick}
@ -642,6 +663,7 @@ const FlowEditor: React.FC = () => {
<div></div>
<Button onClick={saveFlowDataToServer} type="primary"></Button>
</Panel>
<AlignmentGuides />
</ReactFlow>
{/*节点右键上下文*/}

Loading…
Cancel
Save