diff --git a/frontend/src/components/FormDesigner/Designer.tsx b/frontend/src/components/FormDesigner/Designer.tsx index 38c37117..7cf5c2c6 100644 --- a/frontend/src/components/FormDesigner/Designer.tsx +++ b/frontend/src/components/FormDesigner/Designer.tsx @@ -37,7 +37,6 @@ import { useSensor, useSensors, pointerWithin, - rectIntersection, closestCenter } from '@dnd-kit/core'; import { arrayMove } from '@dnd-kit/sortable'; @@ -135,35 +134,84 @@ const FormDesigner: React.FC = ({ }) ); - // 自定义碰撞检测策略:优先使用指针检测,避免栅格内部区域干扰排序 + // 自定义碰撞检测策略:基于边界判断,区分"插入"和"拖入" const customCollisionDetection = useCallback((args: any) => { - // 首先尝试使用 pointerWithin - 只有指针真正进入区域时才触发 const pointerCollisions = pointerWithin(args); - if (pointerCollisions.length > 0) { - // 过滤掉栅格列(grid-xxx-col-xxx),优先选择画布或字段 - const nonGridCollisions = pointerCollisions.filter((collision: any) => { - const id = collision.id.toString(); - return !id.startsWith('grid-') || id === 'canvas' || id === 'canvas-bottom'; - }); - - if (nonGridCollisions.length > 0) { - return nonGridCollisions; + if (pointerCollisions.length === 0) { + return closestCenter(args); + } + + // 查找栅格字段的碰撞 + const gridFieldCollisions = pointerCollisions.filter((collision: any) => { + const fieldId = collision.id.toString(); + return fields.some(f => f.id === fieldId && f.type === 'grid'); + }); + + // 如果拖到栅格字段上,使用边界检测 + 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; + } + } } - - // 如果只有栅格列,说明确实是要拖入栅格 - return pointerCollisions; } - - // 如果 pointerWithin 没有结果,使用 rectIntersection 作为后备 - const rectCollisions = rectIntersection(args); - if (rectCollisions.length > 0) { - return rectCollisions; + + // 非栅格字段:优先级排序 + // 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; } - - // 最后使用 closestCenter 作为兜底 - return closestCenter(args); - }, []); + + // 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; + }, [fields]); // 生成唯一 ID const generateId = () => `field_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; diff --git a/frontend/src/components/FormDesigner/components/DesignCanvas.tsx b/frontend/src/components/FormDesigner/components/DesignCanvas.tsx index c1970d3f..3d975d15 100644 --- a/frontend/src/components/FormDesigner/components/DesignCanvas.tsx +++ b/frontend/src/components/FormDesigner/components/DesignCanvas.tsx @@ -96,9 +96,12 @@ const DesignCanvas: React.FC = ({
{fields.map((field) => ( - {/* 插入指示器 - 显示在拖拽悬停的字段上方 */} - {activeId && overId === field.id && activeId !== field.id && ( - + {/* 插入指示器 - 显示在字段上方(栅格字段会根据边界自动判断) */} + {activeId && + overId === field.id && + activeId !== field.id && + !overId?.toString().includes('-col-') && ( + )}