react-beautiful-dnd + react(Hooks) 实现二级拖拽目录式列表(开发记录)

1.任务需求

实现一个章节目录,要求章和章之间可以进行拖拽排序;每一章内的节可以拖拽排序,并且可以把其他章的小节拖拽到其他章中,章是不可以拖拽到其他章内的,章是可以新建的,小节是请求后端查询数据库返回的。示例如下
在这里插入图片描述
首先对技术做了调研,本着提高开发效率,能不自己造轮子就尽量找对应好用的库进行开发的理念上,我对react的相关的拖拽库进行了调研,其中包括react-draggable,react-sortable-hoc,react-beautiful-dnd等拖拽库,不谈其优劣,只谈适合业务场景,最终选择了react-beautiful-dnd这个拖拽库
react-beautiful-dnd英文github地址:https://github.com/atlassian/react-beautiful-dnd
react-beautiful-dnd中文github地址:https://github.com/chinanf-boy/react-beautiful-dnd-zh
react-beautiful-dnd库demo演示地址:https://react-beautiful-dnd.netlify.app/?path=/story/single-vertical-list–basic

文档比较简单,也不是那么易看,网上想搜索相关的例子也没有符合我的需求可供参考的,于是我就拉下了github的源码,去看了下源码,意外的发现,在源码中还有demo的源码,拉下源码后 yarn安装下依赖(不用我过多描述了)然后进入package.json中,找到storybook(那就启动吧,yarn storybook),神奇的一幕出现了,访问9002端口,这些demo就都出现了,然后顺藤摸瓜,找到demo的文件夹storybook,你就看到demo源码了
在这里插入图片描述

首先根据需求确定了一下这个数据结构如下图
在这里插入图片描述
这个基本用法文档都有说,我这里就不细说了,讲一下具体实现思路以及我的代码,根据文档描述
在这里插入图片描述DragDropContext作为最外层包裹住可以拖拽的部分
在这里插入图片描述
Droppable可以理解为一个拖拽域,里面的Draggable包裹的就是可拖拽元素,这些理解了之后对于当前这个场景就可以设计出这样的结构,以下是伪代码

<DragDropContext>
	<Droppable>
		<Draggable>
			{章}
			<Droppable>
				<Draggable>{节}</Draggable>
				<Draggable>{节}</Draggable>
				<Draggable>{节}</Draggable>
			</Droppable>
		</Draggable>
	</Droppable>
</DragDropContext>

一个这样的结构就出来的,再参考文档以及示例写出以下代码

 let defaultChapter: any = {
    id: '6',
    name: '默认章',
    node_list: [],
  }
 const [nodeList, setNodeList] = useState([defaultChapter])
  • 最外层
<DragDropContext onDragEnd={onDragEnd}>
   <div className={css.boardContain}>{board}</div>
</DragDropContext>
  • board
  const board = (
    <Droppable droppableId="board" type="COLUMN" direction="vertical">
      {(provided) => (
        <div ref={provided.innerRef} {...provided.droppableProps}>
          {nodeList?.map((node, index) => (
            <Column
              key={node?.name}
              index={index}
              title={node?.name}
              node={node}
            />
          ))}
          {provided.placeholder}
        </div>
      )}
    </Droppable>
  )
  • Column
const Column: React.FC<ColumnProps> = (props) => {
    const { title, index, node } = props

    return (
      <Draggable draggableId={title} index={index} key={title}>
        {(provided, snapshot) => (
          <div ref={provided.innerRef} {...provided.draggableProps}>
            <div
              // isDragging={snapshot.isDragging}
              {...provided.dragHandleProps}
              style={{
                marginBottom: '8px',
                marginTop: '8px',
                fontSize: '16px',
              }}
            >
              {/* {title} */}
              <Tooltip title="拖拽排序">
                <MenuOutlined
                  style={{
                    color: '#666',
                    marginRight: 5,
                  }}
                />
              </Tooltip>
              {index + 1}{' '}
              <Input
                style={{ width: 200, height: 32, fontSize: '16px' }}
                defaultValue={title}
                onChange={(e)=>chapterNameChange(e,index)}
              />
              {index !== 0 && (
                <CloseOutlined
                  color="#ccc"
                  size={10}
                  onClick={() => deleChapterBtnClick(index, node)}
                />
              )}
            </div>
            <ChapterList
              preIndex={index + 1}
              listId={title}
              listType="CHAPT"
              list={node.node_list}
            />
          </div>
        )}
      </Draggable>
    )
  }
  • ChapterList
const ChapterList: React.FC<ChapterProps> = (props) => {
    const { list, listId, listType, preIndex } = props

    return (
      <Droppable droppableId={listId} type={listType}>
        {(dropProvided, dropSnapshot) => (
          <div ref={dropProvided.innerRef} {...dropProvided.droppableProps}>
            {list?.map((li, index) => (
              <Draggable key={li.id} draggableId={li.id} index={index}>
                {(dragProvided, dragSnapshot) => (
                  <>
                    <div
                      ref={dragProvided.innerRef}
                      {...dragProvided.draggableProps}
                      key={li.id}
                      // isDragging={dragSnapshot.isDragging}
                    >
                      <div
                        {...dragProvided.dragHandleProps}
                        style={{ marginBottom: 13 }}
                      >
                        <Tooltip title="拖拽排序">
                          <MenuOutlined
                            style={{
                              color: '#666',
                              marginRight: 5,
                            }}
                          />
                        </Tooltip>
                        <span className={css.listItem}>
                          {' '}
                          {preIndex}.{index + 1} {li.name}
                        </span>
                      </div>
                    </div>
                  </>
                )}
              </Draggable>
            ))}
            {dropProvided.placeholder}
          </div>
        )}
      </Droppable>
    )
  }
//最外层要加上onDragEnd才能保存拖拽后的位置哦,主要逻辑其实就是拖拽的这个节或章替换掉目标的位置
const reorder = (
    list: any[],
    startIndex: number,
    endIndex: number,
  ): any[] => {
    const result = Array.from(list)
    const [removed] = result.splice(startIndex, 1)
    result.splice(endIndex, 0, removed)

    return result
  }
  
const onDragEnd = (result: any) => {
    const { source, destination, draggableId, type } = result
    console.log(
      'source',
      source,
      'destination',
      destination,
      'draggableId',
      draggableId,
      'type',
      type,
    )
    if (!destination) {
      return
    }
    if (
      source.droppableId === destination.droppableId &&
      source.index === destination.index
    ) {
      return
    }
    if (result.type === 'COLUMN') {
      const ordered = reorder(
        // this.state.ordered,
        nodeList,
        source.index,
        destination.index,
      )
      setNodeList([...ordered])
      return
    }
    if (result.type === 'CHAPT') {
      const sourceArr = nodeList.filter((n) => n.name == source.droppableId)[0]
        .node_list
      const desArr = nodeList.filter(
        (n) => n.name === destination.droppableId,
      )[0].node_list
      console.log(sourceArr, desArr)
      let moveItem = sourceArr.splice(source.index, 1)[0]
      desArr.splice(destination.index, 0, moveItem)
      console.log(sourceArr, desArr)
      return
    }
  }

最后

遇到的坑是数不胜数,印象最深刻的就是在最外层如果想让他溢出滚动的话,加overflow:auto是会出问题的,会导致新建的章的位置不被记录,改成overflow:overlay就解决了这个问题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您需要使用@dnd-kit/core来实现table的拖拽,并在拖拽过程中弹出确认提示框。下面是可能的实现: 首先,您需要将@dnd-kit/core安装到您的项目中。您可以使用npm或yarn进行安装: ``` npm install @dnd-kit/core 或 yarn add @dnd-kit/core ``` 然后,您需要在您的组件中引入@dnd-kit/core: ``` import { DndContext, DragOverlay } from '@dnd-kit/core'; ``` 接着,在您的组件中定义一个Draggable元素,它将被用于拖拽表格行: ``` import { Draggable } from '@dnd-kit/core'; function Table() { const [items, setItems] = useState([ {id: 1, value: 'Item 1'}, {id: 2, value: 'Item 2'}, {id: 3, value: 'Item 3'}, {id: 4, value: 'Item 4'}, ]); return ( <table> <tbody> {items.map(item => ( <Draggable key={item.id}> {({attributes, listeners, setNodeRef, transform}) => ( <tr ref={setNodeRef} {...attributes} {...listeners}> <td>{item.value}</td> </tr> )} </Draggable> ))} </tbody> </table> ); } ``` 在上面的代码中,我们使用了Draggable组件来包装每个表格行。Draggable组件接受一个函数作为子组件,该函数将返回一个包含拖拽所需属性和事件处理程序的对象。我们将这些属性和事件处理程序应用到表格行上。 接下来,您需要在您的组件中定义一个Overlay元素,它将被用于显示拖拽过程中的提示框: ``` import { Overlay } from '@dnd-kit/core'; function Table() { // ... return ( <DndContext> <table> <tbody> {items.map(item => ( <Draggable key={item.id}> {/* ... */} </Draggable> ))} </tbody> </table> <Overlay> {({draggingItem}) => ( <div> {draggingItem && ( <Popconfirm title="确定要移动吗?"> <div>移动到这里</div> </Popconfirm> )} </div> )} </Overlay> </DndContext> ); } ``` 在上面的代码中,我们使用了Overlay组件来包装一个Popconfirm元素,它将显示在拖拽过程中。Overlay组件接受一个函数作为子组件,该函数将返回一个包含正在拖拽的项目的对象。如果正在拖拽,我们将显示一个Popconfirm元素,提示用户确认移动操作。 最后,您需要定义一些拖拽动作的处理程序,使拖拽能够正常工作: ``` import { DndContext, DragOverlay, useDraggable } from '@dnd-kit/core'; function Table() { // ... const handleDragEnd = ({active, over}) => { if (active.id !== over.id) { // 移动项目 const oldIndex = items.findIndex(item => item.id === active.id); const newIndex = items.findIndex(item => item.id === over.id); const newItems = [...items]; newItems.splice(oldIndex, 1); newItems.splice(newIndex, 0, items[oldIndex]); setItems(newItems); } }; return ( <DndContext onDragEnd={handleDragEnd}> {/* ... */} </DndContext> ); } ``` 在上面的代码中,我们定义了一个handleDragEnd处理程序,它将在拖拽结束时被调用。在这个处理程序中,我们将检查拖拽的源和目标是否不同,如果不同,我们将移动项目到新的位置。 这就是使用@dnd-kit/core实现table拖拽并在拖拽过程中弹出确认提示框的基本步骤。希望这能帮助到您!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值