当前位置: 首页 > 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…...

Python热重载工具Reloadium:实现函数级代码热更新与AI辅助开发

1. 项目概述&#xff1a;Reloadium&#xff0c;一个改变Python开发工作流的“时光机”如果你和我一样&#xff0c;是个常年泡在Python项目里的开发者&#xff0c;那你一定对“修改代码 -> 停止程序 -> 重新运行 -> 等待启动”这个循环深恶痛绝。尤其是在调试Web后端&a…...

网页项目之大五人格测试:认识真实的自己

大五人格测试&#xff1a;认识真实的自己 你是否曾好奇&#xff0c;自己的人格特质是什么&#xff1f;为什么有些人天生善于社交&#xff0c;有些人却更喜欢独处&#xff1f;为什么有人总是追求完美&#xff0c;有些人却随性自在&#xff1f; 心理学研究表明&#xff0c;人格的…...

TS-182快速打通Modbus干变温控箱与PROFINET PLC连接

项目背景&#xff1a;在电力配电系统中&#xff0c;干式变压器的安全运行离不开温控箱的实时监测与保护。作为变压器温控箱的生产厂商&#xff0c;您是否遇到过这样的困扰&#xff1a;客户现场的主控系统采用西门子S7-1500 PLC&#xff08;PROFINET协议&#xff09;&#xff0c…...

CORP开源协作框架:从人治到规则驱动的自动化协作协议

1. 项目概述&#xff1a;一个面向未来的开源协作框架最近在折腾一个开源项目&#xff0c;叫CORP&#xff0c;全称是“Collaborative Open-source Resource Platform”。这名字听起来挺唬人&#xff0c;但说白了&#xff0c;它想解决的就是开源世界里一个老生常谈但又一直没被彻…...

FastAPI清洁架构实践:从分层设计到可维护项目搭建

1. 项目概述&#xff1a;一个为FastAPI项目设立的“洁净室”当你开始一个新的FastAPI项目时&#xff0c;面对的是一个空白的画布。理论上&#xff0c;你可以自由地绘制任何架构&#xff0c;但现实往往是&#xff1a;随着第一个路由、第一个数据库模型、第一个业务逻辑的加入&am…...

vibe-to-ui:让AI助手将你的“感觉”翻译成专业设计系统

1. 项目概述&#xff1a;当“感觉”成为设计语言如果你和我一样&#xff0c;是一个能写出复杂业务逻辑&#xff0c;但一碰到UI设计就头疼的开发者&#xff0c;那今天聊的这个工具&#xff0c;可能会彻底改变你的工作流。我们常常陷入一个困境&#xff1a;心里有一个模糊的“感觉…...

Spring Boot项目接入Claude的3种生产级方案,含安全沙箱、审计日志与LLM调用熔断机制

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Spring Boot项目接入Claude的3种生产级方案&#xff0c;含安全沙箱、审计日志与LLM调用熔断机制 在高可用AI服务场景中&#xff0c;将Claude大模型能力安全、可控、可观测地集成进Spring Boot应用&…...

基于T4技术栈的现代全栈应用开发实践与最佳实践解析

1. 项目概述&#xff1a;一个现代全栈应用的原型与起点最近在GitHub上看到一个挺有意思的项目&#xff0c;叫timothymiller/t4-app。乍一看这个名字&#xff0c;可能有点摸不着头脑&#xff0c;但点进去你会发现&#xff0c;这其实是一个精心设计的全栈Web应用模板。它不是某个…...

MegaParse:一站式文档解析库的设计原理与工程实践

1. 项目概述&#xff1a;从“MegaParse”看文档解析的“大”与“全”在信息爆炸的时代&#xff0c;我们每天都要处理海量的文档——PDF报告、Word合同、Excel表格、PPT演示稿&#xff0c;甚至网页截图和扫描件。对于开发者、数据分析师和知识管理从业者来说&#xff0c;如何将这…...

IP2366至为芯支持C口双向快充的140W多串锂电池充放电SOC芯片

英集芯IP2366是一款应用于移动电源、电动工具、智能家居、储能电源等方案的多串锂电池充电SOC芯片。支持高达140W的双向同步升降压充放电&#xff0c;充电电流可达5A。支持2至6节锂电池/磷酸铁锂电池串联&#xff0c;集成PD3.1、QC3.0等多种快充协议。内置14bit ADC&#xff0c…...