使用Vue3+elementPlus的Tree组件实现一个拖拽文件夹管理

2025-12-13 0 124

目录

  • 1、前言
  • 2、分析
  • 3、实现
  • 4、踩坑

    • 4.1、拖拽辅助线的坑
    • 4.2、数据的坑
    • 4.3、限制拖拽
    • 4.4、样式调整

1、前言

最近在做一个文件夹管理的功能,要实现一个树状的文件面板。里面包含两种元素,文件夹以及文件。交互要求如下:

  1. 创建、删除,重命名文件夹和文件
  2. 可以拖拽,拖拽文件到文件夹中,或着拖拽文件夹到文件夹中
  3. 文件夹可展开,显示里面全部文件
  4. 拖拽的时候要有辅助线显示

使用Vue3+elementPlus的Tree组件实现一个拖拽文件夹管理

2、分析

根据这个要求,我先找到了vue.draggable.next这个库,结合elementPlus的Collapse折叠面板,以及Vue 3的递归组件封装了一个组件drag-folder,结果测试发现,这个库太久没维护了,很多事件不支持,导致功能很难实现。比如,拖动的时候拿不到拖动对象所选中的目标、没有辅助线、Collapse折叠面板关闭后无法拖入等问题。

就在头疼之时,我不小心看到了elementPlus的Tree组件,发现它也支持拖拽,而且有辅助线,有丰富的回调事件,于是,我准备魔改一下。

3、实现

Tree组件只需要准备一个树状数据,然后根据数据渲染出Tree组件即可,可以自定义子节点的键名,也可以使用插槽自定义内容,于是一番操作后,我完成了第二版的drag-folder组件:

<template>
 <div class=\"drag_folder_box\">
 <el-tree
 draggable
 node-key=\"uid\"
 :default-expanded-keys=\"defaultExpanded\"
 :data=\"interiorList\"
 :allow-drop=\"handleDragBehavior\"
 :allow-drag=\"handleAllowDrag\"
 @node-drag-start=\"handleDragStart\"
 @node-drag-enter=\"handleDragEnter\"
 @node-drag-leave=\"handleDragLeave\"
 @node-drag-over=\"handleDragOver\"
 @node-drag-end=\"handleDragEnd\"
 @node-drop=\"handleDrop\"
 @node-click=\"handleSwitchBillboard\"
 >
 <template #default=\"{ data }\">
 <div class=\"tree_item\">
 <div class=\"item_title\">{{ data.label }}</div>
 <div class=\"item_operate\">
 <div class=\"operate_item\" title=\"编辑\" @click.stop=\"editBillboard(data)\">
 <el-icon :size=\"16\">
 <ele-Edit />
 </el-icon>
 </div>
 <div class=\"operate_item\" title=\"删除\" @click.stop=\"deleteBillboard(data)\">
 <el-icon :size=\"16\">
 <ele-Delete />
 </el-icon>
 </div>
 </div>
 </div>
 </template>
 </el-tree>
 </div>
</template>

<script setup lang=\"ts\">
import { ref, onMounted, watch } from \'vue\'
import type Node from \'element-plus/es/components/tree/src/model/node\'
import type { DragEvents } from \'element-plus/es/components/tree/src/model/useDragNode\'
import type { AllowDropType, NodeDropType } from \'element-plus/es/components/tree/src/tree.type\'
import type { IGetBillboardListTreeItem } from \'@/types/data-billboard\'

// #region 组件逻辑
interface IProps {
 list?: Array<IGetBillboardListTreeItem>
}

const props = withDefaults(defineProps<IProps>(), {
 list: () => []
})

const emit = defineEmits([\'edit\', \'delete\', \'switch\', \'change\'])

const interiorList = ref<Array<IGetBillboardListTreeItem>>([])
// #endregion

// #region 拖拽逻辑
watch(
 () => props.list,
 (newValue) => {
 interiorList.value = newValue
 },
 {
 deep: true,
 immediate: true
 }
)
// 节点开始拖拽时
const handleDragStart = (node: Node, ev: DragEvents) => {}

// 拖拽进入其他节点时
const handleDragEnter = (draggingNode: Node, dropNode: Node, ev: DragEvents) => {}

// 拖拽离开某个节点时
const handleDragLeave = (draggingNode: Node, dropNode: Node, ev: DragEvents) => {}

// 在拖拽节点时
const handleDragOver = (draggingNode: Node, dropNode: Node, ev: DragEvents) => {}

// 拖拽结束时
const handleDragEnd = (draggingNode: Node, dropNode: Node, dropType: NodeDropType, ev: DragEvents) => {}

// 拖拽成功时
const handleDrop = (draggingNode: Node, dropNode: Node, dropType: NodeDropType, ev: DragEvents) => {
 emit(\'change\', interiorList.value)
}
// 是否允许拖拽
const handleAllowDrag = (draggingNode: Node) => {
 return true
}
// 拖拽行为判断
const handleDragBehavior = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
 // 如果选中的节点不是看板 则不允许拖动到内部,只能是 \'prev\' 或 \'next\'
 if (dropNode.data.type === \'billboard\') {
 return type !== \'inner\'
 }
 return true
}

// 编辑看板/文件夹
const editBillboard = (data: IGetBillboardListTreeItem) => {
 emit(\'edit\', data)
}

// 删除看板/文件夹
const deleteBillboard = (data: IGetBillboardListTreeItem) => {
 emit(\'delete\', data)
}

// 选择看板
const handleSwitchBillboard = (data: IGetBillboardListTreeItem) => {
 if (data.type === \'dir\') return
 if (data.id === props.billboardId) return
 emit(\'switch\', data)
}
// #endregion

// #region 生命周期
onMounted(() => {
 interiorList.value = props?.list || []
})
// #endregion
</script>

	

这个组件,使用起来很简单,只需要引入组件,然后绑定list就行了,下面我讲一下这里面的一些坑。

4、踩坑

这里面有几个坑,但是基本都解决了。

4.1、拖拽辅助线的坑

Tree组件没有itemSize属性,它的辅助线,默认是26px,而我的每一项,是36px的高度,所以就会导致辅助线不能对准。
使用Vue3+elementPlus的Tree组件实现一个拖拽文件夹管理

最开始我想着修改样式,给height和line-height,发现不起作用。于是我去翻源码,发现源码:node_modules\\element-plus\\lib\\components\\tree\\src\\model\\useDragNode.js里,treeNodeDragOver方法是给辅助线设置top的,这个top是根据前面的iconPosition的高度来的,所以我设置了icon的height和line-height,一顿操纵如下:

.el-tree-node__expand-icon {  line-height: 36px !important;  height: 36px !important;  padding: 0px 2px !important;
}

改完发现,面板折叠起来是正常的,但是打开后就还是不正常,审查元素发现,这个icon会旋转,打开面板后会添加一个expanded的类名,该类名添加了transform: rotate(90deg)的属性,导致高度不对了,于是我又一顿操作:

.el-tree-node__expand-icon {  line-height: 36px !important;  height: 36px !important;  padding: 0px 2px !important;
}
.el-tree-node__expand-icon.expanded {  transform: rotate(0deg);  & svg {  transform: rotate(90deg);  }
}

4.2、数据的坑

这个是我们后端设计的锅。文件夹和文件的ID,会出现一样的,没有唯一ID,没办法,谁让我们前端就是这么善解人意,写个递归函数,拼接一个唯一ID出来咯。

const formatBillboardList = (list: Array<IBillboardTreeItem>, pid: number): Array<IBillboardTreeItem> => {
 return list.map((item, index) => {
 // 唯一ID
 const uid = `${item.type}_${item.id}`
 // 父ID
 const parentId = pid === 0 ? item.id : pid
 // 子层
 const children = Array.isArray(item.children) ? formatBillboardList(item.children, item.id) : []
 return {
 ...item,
 uid,
 order: index,
 parentId,
 children
 }
 })
}

const list = formatBillboardList(res.data, 0)

4.3、限制拖拽

文件夹和文件,都是可以拖拽的,但是文件可以拖拽到文件夹上,文件夹不能拖拽到文件里。这里我们用到了Tree组件的allow-drop处理函数,它又三个参数,分别是拖拽对象,目标对象,拖拽类型。

const handleDragBehavior = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {  // 如果选中的节点不是看板 则不允许拖动到内部,只能是 \'prev\' 或 \'next\'  if (dropNode.data.type === \'billboard\') {  return type !== \'inner\'  }  return true
}

4.4、样式调整

  • 文件夹和文件的样式不一样,要区分,这里我们用Tree组件的props属性,定制class实现
const treeOption = {  class: (data: IGetBillboardListTreeItem, node: Node) => {  if (data.id === props.billboardId && data.type === \'billboard\') {  return \'on_tree_item\'  } else if (data.type === \'dir\') {  return \'folder\'  } else {  return \'billboard\'  }  }
}
  • 每一层,要有不同的缩进,比如第一层缩进10,第二层20,以此类推,这里我们用Tree组件的indent属性实现,直接绑定即可。
<el-tree :indent=\"10\"></el-tree> 
  • 修改hover和focus时候的背景色
.drag_folder_box {
 &:deep(.el-tree-node__content) {  width: 100%;  height: auto;  border-bottom: 1px solid #c1c1c1;  &:hover {  background-color: #e4f2ff !important;  }  &:focus {  background-color: #e4f2ff !important;  }  }
}

如上,基本的样式和功能就全部完成了。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 使用Vue3+elementPlus的Tree组件实现一个拖拽文件夹管理 https://www.zuozi.net/36459.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务