You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于React Beautiful DND实现类Clickup泳道的行列拖拽任务管理功能

基于React Beautiful DND实现多行多列任务泳道拖拽(仿Clickup模式)

1. 重构数据结构

先把原有的单列任务结构改成多列泳道结构,每个列对应不同任务状态/分类,为任务新增status、dueDate字段,方便拖拽时更新属性:

// 初始列数据(每列对应一个泳道,包含自身属性和默认规则)
const initialColumns = [
  {
    id: 'column-todo',
    title: '待办',
    status: 'todo',
    defaultDueDate: '2024-12-01' // 拖拽到该列时的默认截止日期
  },
  {
    id: 'column-in-progress',
    title: '进行中',
    status: 'in-progress',
    defaultDueDate: '2024-11-20'
  },
  {
    id: 'column-done',
    title: '已完成',
    status: 'done',
    defaultDueDate: '2024-11-15'
  }
];

// 初始任务数据,关联对应列的status
const initialTasks = [
  { id: 'task-1', title: '完成组件封装', status: 'todo', dueDate: '2024-12-01' },
  { id: 'task-2', title: '编写单元测试', status: 'todo', dueDate: '2024-12-01' },
  { id: 'task-3', title: '优化页面性能', status: 'in-progress', dueDate: '2024-11-20' }
];

2. 完整组件实现

以下是扩展后的多列拖拽组件,包含行列布局、跨列拖拽时的属性更新逻辑:

import { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import './TaskBoard.css';

const TaskBoard = () => {
  const [columns, setColumns] = useState(initialColumns);
  const [tasks, setTasks] = useState(initialTasks);

  // 按列的status分组任务,方便渲染
  const getTasksByColumn = (columnStatus) => {
    return tasks.filter(task => task.status === columnStatus);
  };

  const onDragEnd = (result) => {
    const { source, destination, draggableId } = result;

    // 拖拽未完成(比如拖到组件外)直接返回
    if (!destination) return;

    // 同列内排序
    if (source.droppableId === destination.droppableId) {
      const columnStatus = columns.find(col => col.id === source.droppableId).status;
      const columnTasks = [...getTasksByColumn(columnStatus)];
      const [movedTask] = columnTasks.splice(source.index, 1);
      columnTasks.splice(destination.index, 0, movedTask);

      // 更新任务列表(若需持久化可额外添加position字段)
      setTasks(tasks.map(task => 
        task.id === draggableId ? movedTask : task
      ));
    } else {
      // 跨列拖拽:更新任务的status和dueDate
      const destColumn = columns.find(col => col.id === destination.droppableId);
      const movedTask = tasks.find(task => task.id === draggableId);
      
      // 更新任务属性:状态改为目标列的status,截止日期用目标列默认值
      const updatedTask = {
        ...movedTask,
        status: destColumn.status,
        dueDate: destColumn.defaultDueDate
      };

      setTasks(tasks.map(task => 
        task.id === draggableId ? updatedTask : task
      ));
    }
  };

  return (
    <div className="task-board">
      <DragDropContext onDragEnd={onDragEnd}>
        {columns.map(column => (
          <div key={column.id} className="task-column">
            <h3 className="column-title">{column.title}</h3>
            <Droppable droppableId={column.id}>
              {(provided) => (
                <div
                  ref={provided.innerRef}
                  {...provided.droppableProps}
                  className="task-list"
                >
                  {getTasksByColumn(column.status).map((task, index) => (
                    <Draggable
                      key={task.id}
                      draggableId={task.id}
                      index={index}
                    >
                      {(provided) => (
                        <div
                          ref={provided.innerRef}
                          {...provided.draggableProps}
                          {...provided.dragHandleProps}
                          className="task-card"
                        >
                          <div className="task-title">{task.title}</div>
                          <div className="task-meta">
                            <span>状态: {task.status}</span>
                            <span>截止日期: {task.dueDate}</span>
                          </div>
                        </div>
                      )}
                    </Draggable>
                  ))}
                  {provided.placeholder}
                </div>
              )}
            </Droppable>
          </div>
        ))}
      </DragDropContext>
    </div>
  );
};

export default TaskBoard;

3. 基础样式(TaskBoard.css)

添加简单样式实现多行多列的泳道布局:

.task-board {
  display: flex;
  gap: 20px;
  padding: 20px;
  flex-wrap: wrap; /* 支持多行自动换行 */
}

.task-column {
  background-color: #f4f5f7;
  border-radius: 8px;
  min-width: 280px;
  max-width: 280px;
  padding: 12px;
}

.column-title {
  margin: 0 0 12px 0;
  font-size: 16px;
  color: #333;
}

.task-list {
  min-height: 100px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.task-card {
  background-color: white;
  border-radius: 6px;
  padding: 12px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  cursor: grab;
}

.task-card:active {
  cursor: grabbing;
}

.task-title {
  font-weight: 500;
  margin-bottom: 8px;
}

.task-meta {
  font-size: 12px;
  color: #666;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

关键逻辑说明

  • 多行多列布局:通过flex+flex-wrap实现泳道自动换行,适配不同屏幕宽度。
  • 跨列属性更新:在onDragEnd中判断跨列拖拽时,自动将任务的status替换为目标列状态,同时更新dueDate为目标列默认值(若需自定义日期,可在此处添加弹窗选择逻辑)。
  • 任务分组渲染:通过getTasksByColumn方法按任务status关联对应列,确保每个列只渲染属于自身的任务。

内容的提问来源于stack exchange,提问作者RamkumarR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:05:57