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

vue使用树形结构展示文件和文件夹

1. 树形结构显示
  • 显示文件夹和文件:使用 el-tree 组件展示树形结构,文件夹和文件的图标通过 el-icon 进行动态显示。文件夹使用 Folder 图标,文件使用 Files 图标。
  • 节点点击:点击树形节点后,会将选中的节点保存到 selectedNode 中,方便后续操作(如重命名、删除)。
2. 创建文件夹
  • 在根目录或者当前选中的文件夹下,点击 新建文件夹 按钮,触发 createFolder 方法创建一个新的文件夹。
  • 新文件夹将被添加到当前选中节点的 children 数组中,或者如果没有选中任何节点,则将文件夹添加到根目录。
3. 创建文件
  • 类似于文件夹,点击 新建文件 按钮,触发 createFile 方法创建一个新的文件,并将其添加到当前选中的文件夹下或者根目录。
4. 删除文件/文件夹
  • 通过点击 删除文件/文件夹 按钮,触发 removeFile 方法,删除当前选中的文件或文件夹。
  • 删除操作会检查所选文件夹或文件的父级节点,如果有父节点,直接从父节点的 children 中移除该项;如果是根节点,则从根目录数组中删除。
5. 重命名文件/文件夹
  • 通过右键点击某个节点,弹出自定义的上下文菜单。选择 重命名 选项后,触发 renameNode 方法,通过 prompt 弹窗输入新的名称,并更新节点的 label
6. 删除文件/文件夹(右键菜单)
  • 右键点击文件夹或文件,弹出自定义右键菜单,选择 删除 后会弹出确认对话框,确认删除后,调用 deleteNode 删除当前选中的文件/文件夹。
7. 上下文菜单
  • 在树形结构中,右键点击文件夹或文件时,会弹出自定义的上下文菜单,显示 重命名 和 删除 选项。
  • 上下文菜单会根据鼠标点击的位置动态显示,通过 contextMenuPosition 控制菜单的位置。
8. 事件监听
  • 点击外部区域隐藏上下文菜单:使用 document.addEventListener("click", handleOutsideClick) 监听点击事件,点击树形组件外部区域时,隐藏上下文菜单并清空选中的节点。
  • 组件卸载时移除事件监听:在组件卸载时,通过 onBeforeUnmount 钩子移除点击外部区域的事件监听,避免内存泄漏。

<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import { Folder, Files } from "@element-plus/icons-vue";// 在组件挂载后添加事件监听
onMounted(() => {document.addEventListener("click", handleOutsideClick);
});
// 在组件卸载前移除事件监听
onBeforeUnmount(() => {document.removeEventListener("click", handleOutsideClick);
});
// 定义树形数据
const data = ref([{label: "文件夹1",type: "folder",children: [{label: " 1-1",type: "folder",children: [{label: "1-1-1",type: "file",},],},],},{label: "文件夹2",type: "folder",children: [{label: "2-1",type: "folder",children: [{label: "2-1-1",type: "file",},],},{label: "2-2",type: "file",},],},{label: "文件夹3",type: "folder",children: [],},
]);// 配置树形数据的显示属性,指定文件夹使用 Folder 图标
const defaultProps = {children: "children",label: "label",icon: "icon", // 图标属性
};
// 当前选中的节点
const selectedNode = ref(null);
const handleNodeClick = (data) => {selectedNode.value = data;
};
// 监听点击事件,判断点击是否在树形组件外
const handleOutsideClick = (event) => {const treeElement = document.querySelector(".el-tree"); // 获取树形组件的 DOM 元素const buttonContainer = document.querySelector(".header"); // 获取按钮区域// 判断点击区域是否在树形组件或按钮区域外if (!treeElement.contains(event.target) &&!buttonContainer.contains(event.target)) {selectedNode.value = null; // 如果点击区域不是树或按钮区域,则清除选中的节点showContextMenu.value = false; // 隐藏菜单}
};
// 创建文件夹
const createFolder = () => {const newFolder = {label: "新文件夹",type: "folder",children: [],};if (selectedNode.value && selectedNode.value.type === "folder") {// 在选中的文件夹下创建selectedNode.value.children.push(newFolder);} else {// 根目录下创建data.value.push(newFolder);}
};// 创建文件
const createFile = () => {const newFile = {label: "新文件",type: "file",};if (selectedNode.value && selectedNode.value.type === "folder") {// 在选中的文件夹下创建selectedNode.value.children.push(newFile);} else {// 根目录下创建data.value.push(newFile);}
};// 删除文件或文件夹
const removeFile = () => {if (!selectedNode.value) {alert("请先选择一个文件或文件夹");return;}const parentNode = findParentNode(data.value, selectedNode.value);if (parentNode) {const index = parentNode.children.findIndex((item) => item.label === selectedNode.value.label);if (index !== -1) {parentNode.children.splice(index, 1);selectedNode.value = null; // 删除后清空选中的节点}} else {// 如果没有父节点,表示是根节点,直接从根节点删除const index = data.value.findIndex((item) => item.label === selectedNode.value.label);if (index !== -1) {data.value.splice(index, 1);selectedNode.value = null; // 删除后清空选中的节点}}
};
// 查找父节点
const findParentNode = (nodes, targetNode) => {for (let node of nodes) {if (node.children && node.children.includes(targetNode)) {return node;} else if (node.children) {const result = findParentNode(node.children, targetNode);if (result) return result;}}return null;
};
// 控制上下文菜单显示
const showContextMenu = ref(false); 
// 上下文菜单位置
const contextMenuPosition = ref({ top: 0, left: 0 }); 
//右键显示菜单
const handleContextMenu = (event, data, node) => {event.preventDefault(); // 阻止默认右键菜单selectedNode.value = node.data; // 保存当前节点showContextMenu.value = true; // 显示自定义右键菜单contextMenuPosition.value = { top: event.clientY, left: event.clientX }; // 设置菜单显示位置
};
//重命名
const renameNode = () => {if (selectedNode.value) {const newName = prompt("请输入新名称", selectedNode.value.label);if (newName) {selectedNode.value.label = newName; // 修改节点名称}}showContextMenu.value = false; // 隐藏菜单
};
//删除
const deleteNode = () => {if (selectedNode.value) {const confirmDelete = confirm(`确定删除 ${selectedNode.value.label} 吗?`);if (confirmDelete) {removeFile()}}showContextMenu.value = false; // 隐藏菜单
};
</script><template><div class="container"><header class="header"><!-- 新建文件和文件夹的按钮 --><el-button type="primary" @click="createFolder">新建文件夹</el-button><el-button type="primary" @click="createFile">新建文件</el-button><el-button type="primary" @click="removeFile">删除文件/文件夹</el-button></header><div class="main"><aside class="aside"><el-treestyle="max-width: 600px":data="data":props="defaultProps"@node-click="handleNodeClick"@node-contextmenu="handleContextMenu"><!-- 使用 render-content 插槽自定义节点显示 --><template #default="{ node, data }"><span><!-- 判断节点类型来动态显示图标 --><el-icon v-if="data.type === 'folder'"><Folder /></el-icon><el-icon v-if="data.type === 'file'"><Files /></el-icon>{{ data.label }}</span></template></el-tree><divclass="dropdown"v-if="showContextMenu":style="{position: 'absolute',top: contextMenuPosition.top + 'px',left: contextMenuPosition.left + 'px',}"><ul slot="dropdown"><li @click="renameNode">重命名</li><li @click="deleteNode">删除</li></ul></div></aside><div class="content"><!-- 内容区域 --></div></div></div>
</template><style scoped>
.container {width: 100%;height: 100%;display: flex;flex-direction: column;
}.header {height: 60px;border:1px solid #ccc;
}.main {flex: 1;display: flex;
}.aside {width: 200px;border-right: 1px solid #ccc;
}.content {flex: 1;padding: 10px;
}
.dropdown {position: absolute;background-color: #fff;padding:20px;border-radius: 5px;border:1px solid #ccc;
}
</style>

相关文章:

vue使用树形结构展示文件和文件夹

1. 树形结构显示 显示文件夹和文件&#xff1a;使用 el-tree 组件展示树形结构&#xff0c;文件夹和文件的图标通过 el-icon 进行动态显示。文件夹使用 Folder 图标&#xff0c;文件使用 Files 图标。节点点击&#xff1a;点击树形节点后&#xff0c;会将选中的节点保存到 sel…...

PHP框架+gatewayworker实现在线1对1聊天--聊天界面布局+创建websocket连接(5)

文章目录 聊天界面布局html代码 创建websocket连接为什么要绑定&#xff1f; 聊天界面布局 在View/Index目录下创建index.html html代码 <div id"chat"><div id"nbar"><div class"pull-left">与牛德胜正在聊天...</div…...

LinuxUbuntu打开VSCode白屏解决方案

解决方法是 以root权限打开VSCode sudo /usr/share/code/code --no-sandbox --unity-launch...

在 ESP 上运行 AWTK

AWTK 基于 esp 的移植。 测试硬件平台为 ESP32-S3-Touch-LCD-4.3&#xff0c;其它平台请根据实际平台自行调整。 安装下载工具 建议下载离线版本 ESP IDF v5.3.2 下载代码 git clone https://github.com/zlgopen/awtk-esp.git cd awtk-esp git clone https://github.com/zlg…...

硬件工程师面试题 21-30

把常见的硬件面试题进行总结&#xff0c;方便及时巩固复习。其中包括网络上的资源、大佬们的大厂面试题&#xff0c;其中可能会题目类似&#xff0c;加强印象即可。 更多硬件面试题&#xff1a;硬件工程师面试题 1-10硬件工程师面试题 11-20 21、单片机最小系统需要什么&#x…...

开源架构的容器化部署优化版

上三篇文章推荐&#xff1a; 开源架构的微服务架构实践优化版&#xff08;New&#xff09; 开源架构中的数据库选择优化版&#xff08;New&#xff09; 开源架构学习指南&#xff1a;文档与资源的智慧锦囊&#xff08;New&#xff09; 我管理的社区推荐&#xff1a;【青云交社区…...

Qt使用CMake编译项目时报错:#undefined reference to `vtable for MainView‘

博主将.h文件和.cpp文件放到了不同的文件目录下面&#xff0c;如下图所示&#xff1a; 于是构建项目的时候就报错了#undefined reference to vtable for MainView&#xff0c;这个是由于src/view目录下的CMake无法自动moc头文件导致的&#xff0c;需要手动moc include/view目录…...

python学习笔记—12—

1. 布尔类型 (1) 定义 (2) 比较运算符 (3) 代码演示 1. 手动定义 bool_1 True bool_2 False print(f"bool_1的内容是&#xff1a;{bool_1}, 类型是&#xff1a;{type(bool_1)}") print(f"bool_2的内容是&#xff1a;{bool_2}, 类型是&#xff1a;{type(bool…...

==和===的区别,被坑的一天

在 JavaScript 中&#xff0c; 和 都用于比较两个值&#xff0c;但它们有一个重要的区别&#xff1a; 1. (宽松相等运算符) 进行比较时&#xff0c;会 自动类型转换&#xff08;也叫做强制类型转换&#xff09;&#xff0c;即如果比较的两个值的类型不同&#xff0c;JavaScr…...

基于 GPUTasker 的 GPU 使用情况钉钉推送机器人实现

引言 https://github.com/cnstark/gputasker 随着 AI 模型的广泛应用&#xff0c;GPU 成为团队中最重要的资源之一。然而&#xff0c;如何实时监控 GPU 的使用情况并及时通知团队是一个值得关注的问题。为了更好地管理显卡资源&#xff0c;本文基于 GPUTasker&#xff0c;实现了…...

Python自学 - 函数初步(内置函数、模块函数、自定义函数)

1 Python自学 - 函数初步(内置函数、模块函数、自定义函数) 1.1 内置函数 几乎所有的编程都会提供一些内置函数&#xff0c;以便完成一些最基本的任务&#xff0c;Python提供了丰富的内置函数&#xff0c;熟悉内置函数可以给工作带来极大便利。   Python官方的内置函数介绍网…...

【生活】冬天如何选口罩(医用口罩,N95, KN95还是KP95?带不带呼吸阀门?带不带活性炭?)

&#x1f4a1;总结一下就是&#xff1a; 日常防护的话&#xff0c;医用口罩就可以啦。要是想长时间佩戴N95&#xff08;KN95&#xff09;口罩的话也可以. 在高风险环境&#xff08;像医院、疫情防控期间&#xff09;&#xff0c;一定要选不带呼吸阀门的N95口罩KN95&#xff09…...

HTML5新特性|01 音频视频

音频 1、Audio (音频) HTML5提供了播放音频文件的标准 2、control(控制器) control 属性供添加播放、暂停和音量控件 3、标签: <audio> 定义声音 <source> 规定多媒体资源,可以是多个<!DOCTYPE html> <html lang"en"> <head><…...

迅为RK3568开发板编译Android12源码包-设置屏幕配置

在源码编译之前首先要确定自己想要使用的屏幕并修改源码&#xff0c;在编译镜像&#xff0c;烧写镜像。如下图所示&#xff1a; 第一步&#xff1a;确定要使用的屏幕种类&#xff0c;屏幕种类选择如下所示&#xff1a; iTOP-3568 开发板支持以下种类屏幕&#xff1a; 迅为 LV…...

力扣hot100——图论

200. 岛屿数量 class Solution { public:int numIslands(vector<vector<char>>& grid) {int ans 0;vector<int> dx { 0, 1, 0, -1 };vector<int> dy { 1, 0, -1, 0 };int n grid.size(), m grid[0].size();vector<vector<int>> …...

Docker- Unable to find image “hello-world“locally

Docker- Unable to find image “hello-world“locally 文章目录 Docker- Unable to find image “hello-world“locally问题描述一. 切换镜像1. 编辑镜像源2. 切换镜像内容 二、 检查设置1、 重启dockers2、 检查配置是否生效3. Docker镜像源检查4. Dokcer执行测试 三、自定义…...

spring-boot启动源码分析(二)之SpringApplicationRunListener

在上一篇《spring-boot启动源码分析&#xff08;一&#xff09;之SpringApplication实例构造》后&#xff0c;继续看了一个月的Spring boot启动源码&#xff0c;初步把流程看完了&#xff0c;接下来会不断输出总结&#xff0c;以巩固这段时间的学习。同时也希望能帮到同样感兴趣…...

ELK入门教程(超详细)

什么是ELK&#xff1f; ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称(后来出现的filebeat属于beats家族中的一员&#xff0c;可以用来替代logstash的数据收集功能&#xff0c;比较轻量级)&#xff0c;也被称为Elastic Stack。 Filebeat Filebeat是用于转…...

人工智能知识分享第六天-机器学习_​逻辑回归(Logistic Regression)

简介 在机器学习中&#xff0c;分类问题是一种常见的任务&#xff0c;目标是根据输入特征将数据点分配到不同的类别中。为了实现分类&#xff0c;我们需要训练一个分类器&#xff0c;该分类器能够根据输入数据的特征进行预测。 逻辑回归&#xff08;Logistic Regression&…...

基于Springboot + vue实现的校园周边美食探索及分享平台

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

云原生周刊:k0s 成为 CNCF 沙箱项目

开源项目推荐 HAMi HAMi&#xff08;原名 k8s‑vGPU‑scheduler&#xff09;是一款 CNCF Sandbox 级别的开源 K8s 中间件&#xff0c;通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度&#xff0c;为容器提供统一接口&#xff0c;实现细粒度资源配额…...