表单设计器
This commit is contained in:
parent
03d4d21424
commit
1058487821
@ -37,7 +37,6 @@ import {
|
|||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
pointerWithin,
|
pointerWithin,
|
||||||
rectIntersection,
|
|
||||||
closestCenter
|
closestCenter
|
||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
import { arrayMove } from '@dnd-kit/sortable';
|
import { arrayMove } from '@dnd-kit/sortable';
|
||||||
@ -135,35 +134,84 @@ const FormDesigner: React.FC<FormDesignerProps> = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// 自定义碰撞检测策略:优先使用指针检测,避免栅格内部区域干扰排序
|
// 自定义碰撞检测策略:基于边界判断,区分"插入"和"拖入"
|
||||||
const customCollisionDetection = useCallback((args: any) => {
|
const customCollisionDetection = useCallback((args: any) => {
|
||||||
// 首先尝试使用 pointerWithin - 只有指针真正进入区域时才触发
|
|
||||||
const pointerCollisions = pointerWithin(args);
|
const pointerCollisions = pointerWithin(args);
|
||||||
|
|
||||||
if (pointerCollisions.length > 0) {
|
if (pointerCollisions.length === 0) {
|
||||||
// 过滤掉栅格列(grid-xxx-col-xxx),优先选择画布或字段
|
return closestCenter(args);
|
||||||
const nonGridCollisions = pointerCollisions.filter((collision: any) => {
|
}
|
||||||
const id = collision.id.toString();
|
|
||||||
return !id.startsWith('grid-') || id === 'canvas' || id === 'canvas-bottom';
|
// 查找栅格字段的碰撞
|
||||||
|
const gridFieldCollisions = pointerCollisions.filter((collision: any) => {
|
||||||
|
const fieldId = collision.id.toString();
|
||||||
|
return fields.some(f => f.id === fieldId && f.type === 'grid');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (nonGridCollisions.length > 0) {
|
// 如果拖到栅格字段上,使用边界检测
|
||||||
return nonGridCollisions;
|
if (gridFieldCollisions.length > 0 && args.pointerCoordinates) {
|
||||||
|
const gridCollision = gridFieldCollisions[0];
|
||||||
|
const droppableContainer = args.droppableContainers.find(
|
||||||
|
(container: any) => container.id === gridCollision.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (droppableContainer?.rect?.current) {
|
||||||
|
const rect = droppableContainer.rect.current;
|
||||||
|
const pointer = args.pointerCoordinates;
|
||||||
|
|
||||||
|
// 计算鼠标在栅格字段中的相对垂直位置(0-1)
|
||||||
|
const relativeY = (pointer.y - rect.top) / rect.height;
|
||||||
|
|
||||||
|
// 边缘阈值:上下各 20%
|
||||||
|
const edgeThreshold = 0.2;
|
||||||
|
|
||||||
|
if (relativeY < edgeThreshold || relativeY > (1 - edgeThreshold)) {
|
||||||
|
// 在边缘区域 → 返回栅格字段本身(插入到栅格前后)
|
||||||
|
return [gridCollision];
|
||||||
|
} else {
|
||||||
|
// 在中心区域 → 查找栅格列(拖入栅格内部)
|
||||||
|
const gridColumnCollisions = pointerCollisions.filter((collision: any) => {
|
||||||
|
const id = collision.id.toString();
|
||||||
|
return id.startsWith('grid-') && id.includes('-col-');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (gridColumnCollisions.length > 0) {
|
||||||
|
return gridColumnCollisions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非栅格字段:优先级排序
|
||||||
|
// 1. 优先选择非栅格字段
|
||||||
|
const nonGridFields = pointerCollisions.filter((collision: any) => {
|
||||||
|
const id = collision.id.toString();
|
||||||
|
return id.startsWith('field_') && !fields.some(f => f.id === id && f.type === 'grid');
|
||||||
|
});
|
||||||
|
if (nonGridFields.length > 0) {
|
||||||
|
return nonGridFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 画布或底部区域
|
||||||
|
const canvasCollisions = pointerCollisions.filter((collision: any) => {
|
||||||
|
const id = collision.id.toString();
|
||||||
|
return id === 'canvas' || id === 'canvas-bottom';
|
||||||
|
});
|
||||||
|
if (canvasCollisions.length > 0) {
|
||||||
|
return canvasCollisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 栅格列
|
||||||
|
const gridColumns = pointerCollisions.filter((collision: any) => {
|
||||||
|
const id = collision.id.toString();
|
||||||
|
return id.startsWith('grid-') && id.includes('-col-');
|
||||||
|
});
|
||||||
|
if (gridColumns.length > 0) {
|
||||||
|
return gridColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果只有栅格列,说明确实是要拖入栅格
|
|
||||||
return pointerCollisions;
|
return pointerCollisions;
|
||||||
}
|
}, [fields]);
|
||||||
|
|
||||||
// 如果 pointerWithin 没有结果,使用 rectIntersection 作为后备
|
|
||||||
const rectCollisions = rectIntersection(args);
|
|
||||||
if (rectCollisions.length > 0) {
|
|
||||||
return rectCollisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后使用 closestCenter 作为兜底
|
|
||||||
return closestCenter(args);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 生成唯一 ID
|
// 生成唯一 ID
|
||||||
const generateId = () => `field_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
const generateId = () => `field_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|||||||
@ -96,9 +96,12 @@ const DesignCanvas: React.FC<DesignCanvasProps> = ({
|
|||||||
<div className="form-designer-field-list">
|
<div className="form-designer-field-list">
|
||||||
{fields.map((field) => (
|
{fields.map((field) => (
|
||||||
<React.Fragment key={field.id}>
|
<React.Fragment key={field.id}>
|
||||||
{/* 插入指示器 - 显示在拖拽悬停的字段上方 */}
|
{/* 插入指示器 - 显示在字段上方(栅格字段会根据边界自动判断) */}
|
||||||
{activeId && overId === field.id && activeId !== field.id && (
|
{activeId &&
|
||||||
<DropIndicator text="释放以插入到此处" />
|
overId === field.id &&
|
||||||
|
activeId !== field.id &&
|
||||||
|
!overId?.toString().includes('-col-') && (
|
||||||
|
<DropIndicator text={field.type === 'grid' ? "释放以插入到栅格前/后" : "释放以插入到此处"} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FieldItem
|
<FieldItem
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user