react dnd-kit drag-handle 拖拽排序手柄

npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities

 

import React, { useState } from 'react';
import {
  DndContext,
  DragOverlay,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
} from '@dnd-kit/core';
import {
  arrayMove,
  SortableContext,
  sortableKeyboardCoordinates,
  verticalListSortingStrategy,
  useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

// 可排序项组件
const SortableItem = ({ id, index, children }) => {
  const {
    attributes,
    listeners,
    setNodeRef,
    setActivatorNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({ id });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    position: 'relative',
    display: 'flex',
    alignItems: 'center',
    gap: '12px',
    padding: '16px',
    margin: '8px 0',
    background: isDragging ? 'transparent' : 'white',
    borderRadius: '4px',
    boxShadow: isDragging ? 'none' : '0 1px 3px rgba(0,0,0,0.12)',
  };

  return (
    <div ref={setNodeRef} style={style} {...attributes}>
      {/* 原位置虚线框 */}
      {isDragging && (
        <div style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
          border: '2px dashed #1890ff',
          borderRadius: '4px',
          opacity: 0.8
        }} />
      )}

      {/* 拖拽手柄 */}
      <div
        ref={setActivatorNodeRef}
        {...listeners}
        style={{
          cursor: isDragging ? 'grabbing' : 'grab',
          padding: '8px',
          background: '#f0f0f0',
          borderRadius: '4px',
          display: 'flex',
          flexDirection: 'column',
          gap: '4px',
        }}
      >
        {[...Array(3)].map((_, i) => (
          <div
            key={i}
            style={{
              width: '4px',
              height: '4px',
              background: '#666',
              borderRadius: '50%',
            }}
          />
        ))}
      </div>
      <div>{children}</div>
    </div>
  );
};

// 主组件
const App = () => {
  const [items, setItems] = useState([
    { id: '1', text: 'Item 1' },
    { id: '2', text: 'Item 2' },
    { id: '3', text: 'Item 3' },
    { id: '4', text: 'Item 4' },
  ]);
  const [activeId, setActiveId] = useState(null);

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: {
        distance: 8,
      },
    }),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    })
  );

  const handleDragStart = ({ active }) => {
    setActiveId(active.id);
  };

  const handleDragEnd = ({ active, over }) => {
    setActiveId(null);
    if (active.id !== over?.id) {
      setItems((items) => {
        const oldIndex = items.findIndex((i) => i.id === active.id);
        const newIndex = items.findIndex((i) => i.id === over?.id);
        return arrayMove(items, oldIndex, newIndex);
      });
    }
  };

  return (
    <div style={{ maxWidth: '600px', margin: '2rem auto', padding: '0 1rem' }}>
      <DndContext
        sensors={sensors}
        collisionDetection={closestCenter}
        onDragStart={handleDragStart}
        onDragEnd={handleDragEnd}
      >
        <SortableContext
          items={items}
          strategy={verticalListSortingStrategy}
        >
          {items.map((item, index) => (
            <SortableItem key={item.id} id={item.id} index={index}>
              {item.text}
            </SortableItem>
          ))}
        </SortableContext>

        {/* 拖拽预览层 */}
        <DragOverlay>
          {activeId ? (
            <div style={{
              padding: '16px',
              background: 'white',
              borderRadius: '4px',
              boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
              transformOrigin: 'center center'
            }}>
              {items.find(item => item.id === activeId)?.text}
            </div>
          ) : null}
        </DragOverlay>
      </DndContext>
    </div>
  );
};

export default App;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

web16888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值