当前位置: 首页 > news >正文

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

文章目录

  • 1、前言
  • 2、分析
  • 3、实现
  • 4、踩坑
    • 4.1、拖拽辅助线的坑
    • 4.2、数据的坑
    • 4.3、限制拖拽
    • 4.4、样式调整

1、前言

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

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

文件夹管理交互表现

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-treedraggablenode-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') returnif (data.id === props.billboardId) returnemit('switch', data)
}
// #endregion// #region 生命周期
onMounted(() => {interiorList.value = props?.list || []
})
// #endregion
</script>

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

4、踩坑

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

4.1、拖拽辅助线的坑

Tree组件没有itemSize属性,它的辅助线,默认是26px,而我的每一项,是36px的高度,所以就会导致辅助线不能对准。
辅助线

最开始我想着修改样式,给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) => {// 唯一IDconst uid = `${item.type}_${item.id}`// 父IDconst 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;}}
}

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


本次分享就到这儿啦,我是鹏多多,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

往期文章

  • Vue2全家桶+Element搭建的PC端在线音乐网站
  • vue3+element-plus配置cdn
  • 助你上手Vue3全家桶之Vue3教程
  • 助你上手Vue3全家桶之VueX4教程
  • 助你上手Vue3全家桶之Vue-Router4教程
  • 超详细!Vue的九种通信方式
  • 超详细!Vuex手把手教程
  • 使用nvm管理node.js版本以及更换npm淘宝镜像源
  • vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令
  • 超详细!Vue-Router手把手教程

个人主页

  • CSDN
  • GitHub
  • 简书
  • 博客园
  • 掘金

相关文章:

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

文章目录 1、前言2、分析3、实现4、踩坑4.1、拖拽辅助线的坑4.2、数据的坑4.3、限制拖拽4.4、样式调整 1、前言 最近在做一个文件夹管理的功能&#xff0c;要实现一个树状的文件夹面板。里面包含两种元素&#xff0c;文件夹以及文件。交互要求如下&#xff1a; 创建、删除&am…...

小谈设计模式(7)—装饰模式

小谈设计模式&#xff08;7&#xff09;—装饰模式 专栏介绍专栏地址专栏介绍 装饰模式装饰模式角色Component&#xff08;抽象组件&#xff09;ConcreteComponent&#xff08;具体组件&#xff09;Decorator&#xff08;抽象装饰器&#xff09;ConcreteDecorator&#xff08;具…...

nginx 多层代理 + k8s ingress 后端服务获取客户真实ip 配置

1.nginx http 七层代理 修改命令空间&#xff1a; namespace: nginx-ingress : configmap&#xff1a;nginx-configuration kubectl get cm nginx-configuration -n ingress-nginx -o yaml添加如上配置 compute-full-forwarded-for: “true” forwarded-for-header: X-Forwa…...

6种最常用的3D点云语义分割AI模型对比

由于增强现实/虚拟现实的发展及其在计算机视觉、自动驾驶和机器人领域的广泛应用&#xff0c;点云学习最近引起了人们的关注。 深度学习已成功用于解决 2D 视觉问题&#xff0c;然而&#xff0c;由于其处理面临独特的挑战&#xff0c;深度学习技术在点云上的使用仍处于起步阶段…...

UG NX二次开发(C#)-获取UI中选择对象的handle值

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、设计一个简单的UI界面3、创建工程项目4、测试结果1、前言 我在哔哩哔哩的视频中看到有人问我如何获取UI选择对象的Handle,本来想把Tag、Taggedobject、Handle三者的关系讲一下,然后看…...

win10,WSL的Ubuntu配python3.7手记

1.装linux 先在windows上安装WSL版本的Ubuntu Windows10系统安装Ubuntu子系统_哔哩哔哩_bilibili &#xff08;WSL2什么的一直没搞清楚&#xff09; 图形界面会出一些问题&#xff0c;注意勾选ccsm出的界面设置 win10安装Ubuntu16.04子系统&#xff0c;并开启桌面环境_win…...

02-Zookeeper实战

上一篇&#xff1a;01-Zookeeper特性与节点数据类型详解 1. zookeeper安装 Step1&#xff1a; 配置JAVA环境&#xff0c;检验环境&#xff1a; java -versionStep2: 下载解压 zookeeper wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.5.8/apache-zookeepe…...

【C语言深入理解指针(1)】

1.内存和地址 1.1内存 在讲内存和地址之前&#xff0c;我们想有个⽣活中的案例&#xff1a; 假设有⼀栋宿舍楼&#xff0c;把你放在楼⾥&#xff0c;楼上有100个房间&#xff0c;但是房间没有编号&#xff0c;你的⼀个朋友来找你玩&#xff0c;如果想找到你&#xff0c;就得挨…...

模拟实现简单的通讯录

前言&#xff1a;生活中处处都会看到或是用到通讯录&#xff0c;今天我们就通过C语言来简单的模拟实现一下通讯录。 鸡汤&#xff1a;跨越山海&#xff0c;终见曙光&#xff01; 链接:gitee仓库&#xff1a;代码链接 目录 主函数声明部分初始化通讯录实现扩容的函数增加通讯录所…...

rabbitMQ死信队列快速编写记录

文章目录 1.介绍1.1 什么是死信队列1.2 死信队列有什么用 2. 如何编码2.1 架构分析2.2 maven坐标2.3 工具类编写2.4 consumer1编写2.5 consumer2编写2.6 producer编写 3.整合springboot3.1 架构图3.2 maven坐标3.3 构建配置类&#xff0c;创建exchange&#xff0c;queue&#x…...

数位dp,338. 计数问题

338. 计数问题 - AcWing题库 给定两个整数 a 和 b&#xff0c;求 a 和 b 之间的所有数字中 0∼90∼9 的出现次数。 例如&#xff0c;a1024&#xff0c;b1032&#xff0c;则 a 和 b 之间共有 9 个数如下&#xff1a; 1024 1025 1026 1027 1028 1029 1030 1031 1032 其中 0 出…...

如何解决git clone http/https仓库失败(403错误)

本来不打算写这篇文章&#xff0c;但是后来又遇到这个问题忘了之前是怎么解决的了。 一般情况下&#xff0c;个人使用 GitHub 等平台时是使用 SSH 协议的&#xff0c;这样不光方便管理可访问用户&#xff0c;也保证了安全性。但是 GitHub 上仓库的 SSH 地址是要登陆才能看到&a…...

华为云云耀云服务器L实例评测 | 实例评测使用之硬件性能评测:华为云云耀云服务器下的硬件运行评测

华为云云耀云服务器L实例评测 &#xff5c; 实例评测使用之硬件性能评测&#xff1a;华为云云耀云服务器下的硬件运行评测 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀…...

Elasticsearch:使用 Elasticsearch 进行语义搜索

在数字时代&#xff0c;搜索引擎在通过浏览互联网上的大量可用信息来检索数据方面发挥着重要作用。 此方法涉及用户在搜索栏中输入特定术语或短语&#xff0c;期望搜索引擎返回与这些确切关键字匹配的结果。 虽然关键字搜索对于简化信息检索非常有价值&#xff0c;但它也有其局…...

JVM的主要组成及其作用

jvm主要组成部分有: 类加载器、运行时数据区 (内存结构)、执行引擎、本地接口库、垃圾回收机制 Java程序运行的时候&#xff0c;首先会通过类加载器把Java 代码转换成字节码。然后运行时数据区再将字节码加载到内存中&#xff0c;但字节码文件只是JVM 的一套指令集规范&#xf…...

会议AISTATS(Artificial Intelligence and Statistics) Latex模板参考文献引用问题

前言 在看AISTATS2024模板的时候&#xff0c;发现模板里面根本没有教怎么引用&#xff0c;要被气死了。 如下&#xff0c;引用(Cheesman, 1985)的时候&#xff0c;模板是自己手打上去的&#xff1f;而且模板提供的那三个引用&#xff0c;根本也没有Cheesman这个人&#xff0c…...

2023最新外贸建站:WordPress搭建外贸独立站零基础小白保姆级教程

想从零开始建立一个外贸自建站&#xff0c;那么你来对地方了。 如果你还在找外贸建站或者是WordPress建站教程&#xff0c;不妨看看这篇文章&#xff0c;本教程涵盖了2023最新的外贸建站教程&#xff0c;你将学会使用WordPress自建外贸独立站&#xff0c;步骤包括购买域名主机…...

HTTP请求交互基础(基于GPT3.5,持续更新)

HTTP交互基础 目的HTTP定义详解HTTP协议&#xff08;规范&#xff09;1. 主要组成部分1.1 请求行&#xff08;Request Line&#xff09;&#xff1a;包含请求方法、请求URI&#xff08;Uniform Resource Identifier&#xff09;和HTTP协议版本。1.2 请求头部&#xff08;Reques…...

小谈设计模式(6)—依赖倒转原则

小谈设计模式&#xff08;6&#xff09;—依赖倒转原则 专栏介绍专栏地址专栏介绍 依赖倒转原则核心思想关键点分析abc 优缺点分析优点降低模块间的耦合度提高代码的可扩展性便于进行单元测试 缺点增加代码的复杂性需要额外的设计和开发工作 Java代码实现示例分析 总结 专栏介绍…...

JetBrains常用插件

Codota AI Autocomplete Java and JavaScript&#xff1a;自动补全插件 Background Image plus&#xff1a;背景图片设置 rainbow brackets&#xff1a;彩虹括号&#xff0c;便于识别 CodeGlance2&#xff1a; 类似于 Sublime 中的代码缩略图&#xff08;代码小地图&#xff…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

Python常用模块:time、os、shutil与flask初探

一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...

41道Django高频题整理(附答案背诵版)

解释一下 Django 和 Tornado 的关系&#xff1f; Django和Tornado都是Python的web框架&#xff0c;但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架&#xff0c;鼓励快速开发和干净、实用的设计。它遵循MVC设计&#xff0c;并强调代码复用。Django有…...

shell脚本质数判断

shell脚本质数判断 shell输入一个正整数,判断是否为质数(素数&#xff09;shell求1-100内的质数shell求给定数组输出其中的质数 shell输入一个正整数,判断是否为质数(素数&#xff09; 思路&#xff1a; 1:1 2:1 2 3:1 2 3 4:1 2 3 4 5:1 2 3 4 5-------> 3:2 4:2 3 5:2 3…...