fix: 修复虚拟列表拖拽时显示空白问题
- 添加 DragOverlay 独立渲染拖拽中的项目 - 拖拽时扩展预加载范围 (OVERSCAN 2→10) - 添加 activeId 状态跟踪拖拽项目 - 拖拽中的卡片以半透明效果独立显示
This commit is contained in:
@@ -16,6 +16,8 @@ import {
|
|||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
type DragEndEvent,
|
type DragEndEvent,
|
||||||
|
type DragStartEvent,
|
||||||
|
DragOverlay,
|
||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
import {
|
import {
|
||||||
SortableContext,
|
SortableContext,
|
||||||
@@ -35,6 +37,7 @@ const VISIBLE_COUNT = 5;
|
|||||||
const ROW_HEIGHT = 60; // 卡片高度 52 + 间距 8
|
const ROW_HEIGHT = 60; // 卡片高度 52 + 间距 8
|
||||||
const ROW_GAP = 8;
|
const ROW_GAP = 8;
|
||||||
const OVERSCAN = 2;
|
const OVERSCAN = 2;
|
||||||
|
const DRAG_OVERSCAN = 10; // 拖拽时增加的预加载范围
|
||||||
|
|
||||||
interface FieldListProps {
|
interface FieldListProps {
|
||||||
fields: FieldConfig[];
|
fields: FieldConfig[];
|
||||||
@@ -120,6 +123,7 @@ export default function FieldList({
|
|||||||
}: FieldListProps) {
|
}: FieldListProps) {
|
||||||
const { t } = useI18n('testDataGenerator');
|
const { t } = useI18n('testDataGenerator');
|
||||||
const [scrollTop, setScrollTop] = useState(0);
|
const [scrollTop, setScrollTop] = useState(0);
|
||||||
|
const [activeId, setActiveId] = useState<string | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
@@ -139,8 +143,13 @@ export default function FieldList({
|
|||||||
[selectedIndex, onSelect],
|
[selectedIndex, onSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDragStart = (event: DragStartEvent) => {
|
||||||
|
setActiveId(String(event.active.id));
|
||||||
|
};
|
||||||
|
|
||||||
const handleDragEnd = (event: DragEndEvent) => {
|
const handleDragEnd = (event: DragEndEvent) => {
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
|
setActiveId(null);
|
||||||
if (!over || active.id === over.id) return;
|
if (!over || active.id === over.id) return;
|
||||||
|
|
||||||
const oldIndex = fields.findIndex((f) => f.id === active.id);
|
const oldIndex = fields.findIndex((f) => f.id === active.id);
|
||||||
@@ -165,14 +174,18 @@ export default function FieldList({
|
|||||||
? { height: 'auto' as const, overflowY: 'visible' as const }
|
? { height: 'auto' as const, overflowY: 'visible' as const }
|
||||||
: { height: fixedContainerHeight, overflowY: 'auto' as const };
|
: { height: fixedContainerHeight, overflowY: 'auto' as const };
|
||||||
|
|
||||||
// 虚拟列表:计算可见范围
|
// 虚拟列表:计算可见范围(拖拽时扩展预加载范围)
|
||||||
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - OVERSCAN);
|
const currentOverscan = activeId ? DRAG_OVERSCAN : OVERSCAN;
|
||||||
|
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - currentOverscan);
|
||||||
const visibleEnd = Math.min(
|
const visibleEnd = Math.min(
|
||||||
fields.length,
|
fields.length,
|
||||||
Math.ceil((scrollTop + fixedContainerHeight) / ROW_HEIGHT) + OVERSCAN,
|
Math.ceil((scrollTop + fixedContainerHeight) / ROW_HEIGHT) + currentOverscan,
|
||||||
);
|
);
|
||||||
const visibleFields = isVirtual ? fields.slice(visibleStart, visibleEnd) : fields;
|
const visibleFields = isVirtual ? fields.slice(visibleStart, visibleEnd) : fields;
|
||||||
|
|
||||||
|
// 查找拖拽中的项目(用于 DragOverlay)
|
||||||
|
const activeField = activeId ? fields.find((f) => f.id === activeId) : null;
|
||||||
|
|
||||||
const isMaxReached = fields.length >= MAX_FIELDS;
|
const isMaxReached = fields.length >= MAX_FIELDS;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -206,6 +219,7 @@ export default function FieldList({
|
|||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
|
onDragStart={handleDragStart}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
>
|
>
|
||||||
<SortableContext items={fields.map((f) => f.id)} strategy={verticalListSortingStrategy}>
|
<SortableContext items={fields.map((f) => f.id)} strategy={verticalListSortingStrategy}>
|
||||||
@@ -264,6 +278,21 @@ export default function FieldList({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
|
<DragOverlay>
|
||||||
|
{activeField ? (
|
||||||
|
<div className="rounded-lg border border-primary bg-background shadow-lg opacity-90">
|
||||||
|
<div className="flex items-center gap-2 h-full px-3 py-2">
|
||||||
|
<div className="p-1">
|
||||||
|
<GripVertical className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<FieldItem field={activeField} isSelected={false} onClick={() => {}} />
|
||||||
|
</div>
|
||||||
|
<div className="h-8 w-8 shrink-0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</DragOverlay>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user