el-tree实现类似windows文件列表,并支持折叠、展开和重命名
1.需求:
后台管理系统的左侧导航菜单中文件管理下是个文件夹列表树,
UI给的设计稿是这样的
实现:左侧用了element的el-menu嵌套el-tree来实现,并且支持展开/折叠某个节点和重命名某个节点
实现效果:
2.代码实现:
<div id="treepage">
<el-menu ref="menuRefs" default-active="" class="el-menu-vertical-demo" :collapse="isCollapse">
<template v-for="item in menuData">
<!-- 有子级菜单 -->
<el-submenu v-if="item.children" :index="item.id">
<template slot="title">
<i :class="item.icon"></i>
<span slot="title">{{item.title}}</span>
</template>
<template v-for="childItem in item.children">
<!-- childItem.type === 'treeFile'给的是固定的类型来区分是menu-item还是文件tree -->
<el-tree v-if="childItem.type === 'treeFile'" ref="treeNode" :highlight-current="true" :data="treeData"
:default-expanded-keys="defaultTreeKey" :props="defaultProps" node-key="id" accordion
@node-contextmenu="handleNodeRightMouse" @node-click="handleNodeClick">
<span slot-scope="{node,data}" class="icon_name">
<svg class="icon" aria-hidden="true" style="width:1.3rem;height:1.3rem">
<use xlink:href="#icon-wenjianjia" />
</svg>
<span v-if="!data.isRename" :ref="data.id" style="padding-left:4px">{{ node.label }}</span>
<el-input v-else :ref="data.id" v-model.trim="cateNameInput" maxlength="20" placeholder="请输入"
@click.stop.native @blur="$event => editSave($event, data)"
@keyup.native.enter="$event.target.blur" />
</span>
</el-tree>
<el-menu-item v-show="childItem.type !== 'treeFile'" :index="childItem.id">{{childItem.title}}
</el-menu-item>
</template>
</el-submenu>
<!-- 无子元素 -->
<el-menu-item v-if="!item.children" :index="item.id">
<i :class="item.icon"></i>
<span slot="title">{{item.title}}</span>
</el-menu-item>
</template>
</el-menu>
<!--文件右击菜单 -->
<div v-if="optionCardShow" id="option-button-group" :style="{left: optionCardX + 'px',
top: optionCardY + 'px'
}">
<div @click="handleAllNode('open')" class="card-item">
<img src="public/image/open.svg" alt="">全部展开
</div>
<div @click="handleAllNode('fold')" class="card-item">
<img src="public/image/fold.svg" alt="">全部折叠
</div>
<div @click="renameFile" class="card-item" v-if="isNotRootNode">
<img src="public/image/rename.svg" alt="">重命名
</div>
</div>
</div>
//文件夹树数据结构
let treeData = [
{
label: "团队文件",
id: 0,
isRename: false,
children: [
{
label: "一级 1",
id: 1,
isRename: false,
children: [
{
label: "二级 1-1",
id: 4,
isRename: false,
children: [
{
label: "三级 1-1-1",
isRename: false,
id: 5,
},
],
},
],
},
{
id: 2,
label: "一级 2",
isRename: false,
children: [
{
label: "二级 2-1",
isRename: false,
id: 6,
children: [
{
label: "三级 2-1-1",
isRename: false,
id: 7,
},
],
},
{
label: "二级 2-2",
isRename: false,
id: 8,
children: [
{
label: "三级 2-2-1",
isRename: false,
id: 9,
},
],
},
],
},
{
id: 3,
label: "一级 3",
isRename: false,
children: [
{
label: "二级 3-1",
isRename: false,
id: 10,
children: [
{
label: "三级 3-1-1",
isRename: false,
id: 11,
},
],
},
{
label: "二级 3-2",
id: 12,
children: [
{
label: "三级 3-2-1",
id: 13,
},
],
},
],
},
]
},
]
//menu菜单数据结构
let menuData = [
{
id: '1',
title: '文件管理',
icon: 'el-icon-document',
type: 'menu',
children: [{
id: '1-1',
title: '团队文件',
icon: 'icon-wenjianjia',
type: 'treeFile',
},
{
id: '1-2',
title: '分享管理',
icon: '',
type: 'menu',
}
]
}, {
id: '2',
title: '设置',
icon: 'el-icon-setting',
type: 'menu',
}
]
data () {
return {
treeData: treeData,//文件树数据
menuData: menuData,//menu菜单数据
defaultProps: {
children: "children",
label: "label",
},
isCollapse: false,//菜单折叠
cateNameInput: "", // 当前修改文件名
optionCardShow: false, // 文件夹节点操作卡是否显示
optionCardX: "", // 文件夹节点操作卡位置
optionCardY: "",
optionData: "", // 右键选中的节点
node: null, // 右键当前选中的节点
isNotRootNode: true,//是否是根目录
defaultTreeKey: [],//设置默认选中当前文件夹树node
}
},
methods: {
// 设置默认节点 (这个是选择某一个文件夹层级后,刷新页面默认选中)
handleExpend () {
this.defaultTreeKey = []
// 5给的是测试数据,某一层级的ID
this.defaultTreeKey.push(5)
this.$nextTick(() => {
this.$refs.treeNode[0].setCurrentKey(5);
});
},
// 点击菜单tree形文件节点,隐藏右键菜单
handleNodeClick (data, node, self) {
this.optionCardShow = false;
},
// 右击文件夹tree
handleNodeRightMouse (e, data, n, t) {
this.optionCardShow = false;
this.optionCardX = e.x; // 让右键菜单出现在鼠标右键的位置
this.optionCardY = e.y;
this.optionData = data;
this.node = n; // 将当前节点保存
this.tree = t;
this.isNotRootNode = !!n.parent.parent
this.optionCardShow = true; // 展示右键菜单
},
// 失去焦点后保存
editSave (val, data) {
console.log(this.optionData);
data.isRename = !data.isRename;
data.label = this.cateNameInput || data.label;
this.optionCardShow = false;
// 修改名称成功
},
// 重命名节点
renameFile () {
this.cateNameInput = this.optionData.label;
this.optionData.isRename = !this.optionData.isRename;
this.optionCardShow = false;
this.$nextTick(() => {
this.$refs[this.optionData.id][0] &&
this.$refs[this.optionData.id][0].focus(); // 获取输入框,自动获取焦点
});
},
// 展开或者折叠node点击事件
handleAllNode (type) {
if (!this.optionData) {
return
}
let _data = [this.optionData]
_data.length && this.handleNowNodeData(_data, type == 'open')
this.optionCardShow = false;
},
// 展开or折叠当前父节点
handleNowNodeData (data, bool) {
let _this = this
data.forEach((el) => {
_this.$refs.treeNode[0].store.nodesMap[el.id].expanded = bool;
el.children && el.children.length > 0
? _this.handleNowNodeData(el.children, bool)
: ""; // 子级递归
});
},
}
#treepage {
width: 13vw;
}
.el-tree {
padding: 0;
overflow-y: auto;
}
.el-tree {
background: #fff !important;
color: #000 !important;
padding: 0 10px 10px 30px;
font-size: 14px;
}
.el-tree-node__content {
display: flex;
align-items: center;
height: 35px;
}
.el-tree-node__children {
overflow: visible;
}
.el-tree-node__content:hover,
.el-tree-node:focus>.el-tree-node__content {
background-color: #ffffff !important;
color: #6bafed !important;
}
.el-tree-node.is-current>.el-tree-node__content {
background: transparent !important;
color: #6bafed !important;
}
.icon_name {
display: flex;
align-items: center;
}
.el-input__inner {
height: 25px;
margin-left: 5%;
font-size: 14px;
padding-left: 8px;
}
.el-input.is-active .el-input__inner,
.el-input__inner:focus {
border-color: #1890ff;
}
.right_table {
width: 70%;
border: 1px solid rgba(0, 0, 0, 0.1);
}
#option-button-group {
padding: 0 10px 10px 10px;
font-size: 12px;
color: #000000;
z-index: 9999;
position: fixed;
background: #ffffff;
box-shadow: 0px 9px 28px 8px rgba(0, 0, 0, 0.05),
0px 6px 16px 0px rgba(0, 0, 0, 0.08), 0px 3px 6px -4px rgba(0, 0, 0, 0.12);
border-radius: 2px 2px 2px 2px;
}
#option-button-group .card-item {
margin-top: 10px;
display: flex;
align-content: center;
cursor: pointer;
}
.card-item img {
margin-top: 3px;
margin-right: 5px
}
.iconfont {
font-size: 12px;
margin-right: 10px;
}
.el-empty {
margin-top: 20%;
}
.noMenuTile .el-submenu__title {
height: 0;
}
.noMenuTile .el-icon-arrow-down:before {
content: '';
}