feat(flowEditor): 添加节点对齐辅助线功能
- 新增 useAlignmentGuidelines 钩子用于处理对齐逻辑 - 在 ReactFlow 组件中集成对齐辅助线 - 实现节点拖动时动态显示对齐线 - 拖动停止后清除对齐线master
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 };
|
||||
Loading…
Reference in New Issue