Hooks + TS 搭建一个任务管理系统(八)-- 拖拽功能实现

简介: Hooks + TS 搭建一个任务管理系统(八)-- 拖拽功能实现

image.png

大家好,我是小丞同学,一名大二的前端爱好者


这个系列文章是实战 jira 任务管理系统的一个学习总结


非常感谢你的阅读,不对的地方欢迎指正


愿你忠于自己,热爱生活


在上一篇文章中,我们写好了任务组页面,就现在来说我们的项目已经基本完成了,所有的 CRUD 操作、路由跳转、页面布局都已经实现了。在这一篇文章中,我们再来优化一下我们的项目,我们给我的看板页面添加一个拖拽功能


这篇内容不是很懂,有点水,弄懂再来改


知识点抢先看

给看板添加拖拽功能

讲解 HTML5 中的 drop 和 drag

一、给看板添加拖拽功能

这一篇文章就只讲一个部分,正如标题所说,添加一个拖拽功能


实现效果像这样

9.gif我们实现这个功能采用了一个 react-beautiful-dnd 的库,关于这个库可以查看 : npm官网

关于这个库的使用呢,我们简单的介绍一下,首先我们需要定义一个 Droppable 组件来包裹我们的拖拽的元素,表示这块区域的内容我们能够拖拽,其次需要对放的地方,也就是我们的元素添加一个 Draggable 组件包裹,用来表示这块区域是能够放下的区域

在这里是重写了自带的 Drop 和 Drag 组件

这部分比较难,搞得不是很懂,提几个点吧

在这里我们想要抽离出一个 children 属性,不使用原生的 children 属性

由于 API 的要求,我们需要预留接收 ref,这里我们采用转发的方式来实现,通过 forwardRef 的方式来实现

export const DropChild = React.forwardRef<HTMLDivElement, DropChildProps>(({ children, ...props }, ref) =>
    <div ref={ref} {...props}>
        {children}
        {/* api要求加的 */}
        {props.provided?.placeholder}
    </div>
)

1. 实现 Drop 组件

// 这个文件相当于重构了 drop 原生组件
// 定义一个类型,不想用 自带的 children ,采用自己的
type DropProps = Omit<DroppableProps, 'children'> & { children: ReactNode }
export const Drop = ({ children, ...props }: DropProps) => {
    return <Droppable {...props}>
        {
            (provided => {
                if (React.isValidElement(children)) {
                    // 给所有的子元素都加上props属性
                    return React.cloneElement(children, {
                        ...provided.droppableProps,
                        ref: provided.innerRef,
                        provided
                    })
                }
                return <div />
            })
        }
    </Droppable>
}

2. 实现 Drag 组件

type DragProps = Omit<DraggableProps, 'children'> & { children: ReactNode }
export const Drag = ({ children, ...props }: DragProps) => {
    return <Draggable {...props}>
        {
            provided => {
                if (React.isValidElement(children)) {
                    return React.cloneElement(children, {
                        ...provided.draggableProps,
                        ...provided.dragHandleProps,
                        ref: provided.innerRef
                    })
                }
                return <div />
            }
        }
    </Draggable>
}

3. 拖拽持久化

写好了两个组件,虽然很难,可以直接 cv 一下这部分的代码。

理解起来还是挺可以的,使用 Drop 组件包裹拖得位置,用 Drag 组件包裹放的位置

最后我们需要持久化我们的状态,这里采用的是原生组件中自带的 onDragEnd 方法来实现

我们在这里需要再实现一个 hook 来实现这个功能,很难

这里我们通过 if 判断它当前是拖的看板还是任务,判断一下是左右还是上下拖拽,通过组件中自带的方法计算出放下的 id 和拿起来的 id 将它插入到这个 kanban 任务中即可

当我们拖拽完成时,会返回 source 和 destination 对象,这里面有我们拖拽的相关信息

如果是 column 的话就是看板之间的拖拽,我们需要调用我们新封装的一个 useReorderKanban 方法进行持久化

如果是 row 则调用任务之间的持久化方法 useRecordTask 方法进行持久化

export const useDragEnd = () => {
    // 先取到看板
    const { data: kanbans } = useKanbans(useKanbanSearchParams())
    const { mutate: reorderKanban } = useReorderKanban(useKanbansQueryKey())
    // 获取task信息
    const { data: allTasks = []} = useTasks(useTasksSearchParams())
    const { mutate: reorderTask } = useReorderTask(useTasksQueryKey())
    return useCallback(({ source, destination, type }: DropResult) => {
        if (!destination) {
            return
        }
        // 看板排序
        if (type === 'COLUMN') {
            const fromId = kanbans?.[source.index].id
            const toId = kanbans?.[destination.index].id
            // 如果没变化的时候直接return
            if (!fromId || !toId || fromId === toId) {
                return
            }
            // 判断放下的位置在目标的什么方位
            const type = destination.index > source.index ? 'after' : 'before'
            reorderKanban({ fromId, referenceId: toId, type })
        }
        if (type === 'ROW') {
            // 通过 + 转变为数字
            const fromKanbanId = +source.droppableId
            const toKanbanId = +destination.droppableId
            // 不允许跨版排序
            if (fromKanbanId !== toKanbanId) {
                return
            }
            // 获取拖拽的元素
            const fromTask = allTasks.filter(task => task.kanbanId === fromKanbanId)[source.index]
            const toTask = allTasks.filter(task => task.kanbanId === fromKanbanId)[destination.index]
            //
            if (fromTask?.id === toTask?.id) {
                return
            }
            reorderTask({
                fromId: fromTask?.id,
                referenceId: toTask?.id,
                fromKanbanId,
                toKanbanId,
                type: fromKanbanId === toKanbanId && destination.index > source.index ? 'after' : 'before'
            })
        }
    }, [allTasks, kanbans, reorderKanban, reorderTask])
}

 

4. useReorderKanban

通过传入一组数据,包括起始位置,插入位置,在插入位置的前面还是后面,这些数据,进行后台接口的判断,来进行持久化,这里采用的 useMutation 就是前面讲的,使用方法都很熟练了

// 持久化数据接口
export const useReorderKanban = (queryKey:QueryKey) => {
    const client = useHttp()
    return useMutation(
        (params: SortProps) => {
            return client('kanbans/reorder', {
                data: params,
                method: "POST"
            })
        },
        useReorderKanbanConfig(queryKey)
    )
}

5. 在 HTML5 中新增的 Drop 和 Drag

当我们需要设置某个元素可拖放时,只需要 draggable 设置为 true

<img draggable="true">

当拖放执行时,会发生 ondragstart 和 setData()

执行 ondragstart 会调用一个函数 drag 函数,它规定了被拖拽的数据

function drag(event)
{
    event.dataTransfer.setData("Text",ev.target.id);
}

这里的 Text 时我们需要添加到 drag object 中的数据类型

在何处放置被拖动的数据

默认地,无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式。

这要通过调用 ondragover 事件的 event.preventDefault() 方法:

event.preventDefault()

当防止时会发生 drop 事件

function drop(ev)
{
    ev.preventDefault();
    var data=ev.dataTransfer.getData("Text");
    ev.target.appendChild(document.getElementById(data));
}

代码解释:


调用 preventDefault() 来避免浏览器对数据的默认处理(drop 事件的默认行为是以链接形式打开)

通过 dataTransfer.getData("Text") 方法获得被拖的数据。该方法将返回在 setData() 方法中设置为相同类型的任何数据。

被拖数据是被拖元素的 id ("drag1")

把被拖元素追加到放置元素(目标元素)中

(参考于菜鸟教程)


可以亲自试一试:在线演示


总结

大概了解了一下如何使用 react-beautiful-dnd

关于拖拽持久化有了大概的认识

了解了 HTML5 中的 drop 和 drag

相关文章
|
6月前
|
小程序
小程序学习笔记(7) -- 自定义组件案例
小程序学习笔记(7) -- 自定义组件案例
|
前端开发 JavaScript API
020 Umi@4 中如何实现动态菜单
020 Umi@4 中如何实现动态菜单
1047 0
020 Umi@4 中如何实现动态菜单
|
3月前
|
JavaScript
基于Vue3+TS简单设计一个查看文章时点击展开和点击收起的小功能
该文章展示了如何使用Vue 3和TypeScript创建一个简单的展开和收起功能,用于文章查看时的交互体验。
135 0
基于Vue3+TS简单设计一个查看文章时点击展开和点击收起的小功能
|
3月前
|
前端开发 JavaScript 开发者
Angular与Webpack协同优化:打造生产级别的打包配置——详解从基础设置到高级代码拆分和插件使用
【8月更文挑战第31天】在现代前端开发中,优化应用性能和加载时间至关重要,尤其是对于使用Angular框架的项目。本文通过代码示例详细展示了如何配置Webpack,以实现生产级别的打包优化。从基础配置到生产环境设置、代码拆分,再到使用加载器与插件,每个步骤都旨在提升应用效率,确保快速加载和稳定运行。通过这些配置,开发者能更好地控制资源打包,充分发挥Webpack的强大功能。
77 0
|
3月前
|
开发框架 JSON 缓存
基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理
基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理
|
4月前
|
前端开发 JavaScript 索引
uniapp的u-album组件自定义删除功能
这样,你就可以在u-album组件中实现自定义的删除功能了。需要注意的是,这个删除操作只是在前端删除了图片项,并没有在后端删除对应的图片文件,如果你需要在后端也删除对应的图片文件,你还需要在删除操作后发送一个请求到后端,让后端删除对应的图片文件。
111 0
|
缓存 前端开发 NoSQL
vue-element-admin实战 | 第二篇: 最小改动接入后台实现根据权限动态加载菜单
vue-element-admin实战 | 第二篇: 最小改动接入后台实现根据权限动态加载菜单
|
资源调度 JavaScript 开发工具
vue3 + TS + elementplus + pinia实现后台管理系统左侧菜单联动实现 tab根据路由切换联动内容
vue3 + TS + elementplus + pinia实现后台管理系统左侧菜单联动实现 tab根据路由切换联动内容
430 0
|
前端开发
React+hook+ts+ant design封装一个具有编辑和新增功能的页面
React+hook+ts+ant design封装一个具有编辑和新增功能的页面
76 0
hook+ts业务开发思路5-完成列表页面的编写
hook+ts业务开发思路5-完成列表页面的编写
58 0
hook+ts业务开发思路5-完成列表页面的编写