javascript
复制代码
<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-tree
style="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>
<div
class="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>