element-ui 中设计层级和动态加载节点的组件el-tree,对于前端来说,用的比较多,否则对于vue项目,要自己写render操作dom。当操作的树形结构层级比较多时,多用懒加载。例如实现如下功能,就是用el-tree实现的。
实现这个功能有几个问题 要解决:
1.改样式。官网提供的样式不能够满足开发的需要,尤其是对tree进行增删改的时候。所以需要用到render-content自定义样式。
2.加载问题。懒加载,如何插入数据。
3.增删改之后刷新问题。
前两个问题很好解决,最后一个问题不好弄,尤其是添加操作刷新问题,是个难点,需要反复读官方文档。
核心代码主要在list列表这个页面,直接附上代码,代码中有详细的注释讲解。
直接复制代码不能运行,因为调用了一些后端的接口,但是功能的主要实现,都已列出并注释。
<template>
<div class="dept-page">
<div class="dept-tit">
<h3>单位管理</h3>
</div>
<div class="dept-con" v-loading="isLoading">
<el-tree
node-key="deptCode"
:props="treeProps"
:load="loadNode"
lazy
:render-content="renderContent"
ref="tree"
>
</el-tree>
</div>
<AddSubDepartment ref="addSubDepartment" @addReload = 'addReload'></AddSubDepartment>
<EditDepartment ref="editDepartment" @editReload = 'editReload'></EditDepartment>
</div>
</template>
<script>
import {mapState} from "vuex";
import AddSubDepartment
from "@/views/modules/configuration/subModules/department/subModules/addSubDepartment/addBackup";
import EditDepartment
from "@/views/modules/configuration/subModules/department/subModules/editDepartment/editBackup";
import subDeptList from "@/views/modules/configuration/subModules/department/subModules/subDeptList/subDeptList";
import {deleteDepartment, getDepartmentList} from "@/api/modules/configuration/configuration";
export default {
name: "departmentList",
data () {
return {
isLoading:false,
subDeptId:'',
showSubDept:false,
departmentList:[],
treeProps: {
isLeaf: 'isLowest'
},
}
},
components:{
AddSubDepartment,
EditDepartment,
subDeptList
},
computed:{
...mapState('user', ['v_user']),
...mapState("departments", ["localDeptTree",'v_globalDeptTree']),
},
mounted() {
},
methods:{
loadNode(node,resolve) {
// console.log('----node------',node);
// console.log('----resolve------',resolve);
const _this = this;
_this.isLoading = true
if (node.level === 0) {
const id = _this.localDeptTree[0].id
getDepartmentList(id).then(res=>{
//isLowes后端传回的数据,数字字符串,标识自节点的个数,所以为0时,标识叶子节点
//由于isLowest付给了isLeaf,isLeaf的类型是boolean,所以要转化一下,如果后端传的就是Boolean类型,就不用了
res.forEach((item,index) => {
if(item.isLowest == '0'){
item.isLowest = true
}else{
item.isLowest = false
}
})
return resolve(res);
_this.isLoading = false
}).catch(err=>{
console.log(`列表获取失败!${err}`)
}).finally(() => {
_this.isLoading = false
})
}
if (node.level >= 1) {
getDepartmentList(node.data.deptId).then(res=>{
//正常不会出现这种情况,应为有isLeaf的判断,如果是叶子结点,是不能点击的
if(res.length == 0){
alert('没有子节点')
return resolve([]);
}else{
res.forEach((item,index) => {
if(item.isLowest == '0'){
item.isLowest = true
}else{
item.isLowest = false
}
})
return resolve(res);//重要,懒加载中动态加载子集
}
}).catch(err=>{
console.log(`列表获取失败!${err}`)
}).finally(() => {
_this.isLoading = false
})
}
},
//打开添加子节点的弹出层
openAddDialog(e,node,data){
e.stopPropagation()
const _this =this
_this.$refs.addSubDepartment.show('add',e,node,data);
},
//添加操作成功,关闭弹出层后调用刷新列表的方法
addReload(node,data,e){
//需求中添加的是子节点,所以点击的当前node应该是添加节点的父节点
data.isLowest = true //后端返回的数据,实际上就是isLeaf,新添加的数据是没有子节点的,所以是叶子结点,如果后端给返回了,可以不设置
this.$refs.tree.append(data,node); //将新添加数据插入到node中,作为其子节点,append为插件提供的方法,需要通过this.$refs.tree调用
//将插入的子节点设置为叶子结点。
node.childNodes.forEach(item => {
if(item.data.deptId == data.deptId){ //deptId是数据的唯一标识,根据返回数据而定,相当于id
item.isLeaf = true;
}
});
node.isLeaf = false; //添加了节点之后,此节点不可能是叶子结点了,设置为false,控制前面那个小三角是否显示
node.data.isLowest = false; //控制render-content中删除按钮是否显示,根据系统需求而定,我们的需求中,删除按钮显示需要有些条件判断
if(!node.loaded){ //loaded表示节点是否有加载过,加载过之后为true
e.path[3].click(); //loaded=false时,子节点未加载过,添加节点后触发所点击节点的click事件,从而触发@load事件
//click应加在class="custom-tree-node"的节点上,需要遍历点击按钮e这个元素中的path,
// 我的render-content设置的结构中,path[3]就是custom-tree-node,因此这样写。如果结构不同,可能结果不同,但最终都要加在custom-tree-node上
}else{
node.expanded = true; //子节点已经load过,添加后也需要展开子节点,expanded控制展开
}
},
//打开修改弹出层
openEditDialog(e,node,data){
e.stopPropagation()
const _this =this
_this.$refs.editDepartment.show(data,node,e);
},
//修改成功后执行刷新方法
editReload(node,data,e){
//将修改成功的数据,赋值给当前操作的node.data,数据驱动,页面就会变成修改后的内容
Object.keys(data).forEach(key=>{node.data[key]=data[key]})
},
deleteDept(e,node,data){
e.stopPropagation()
const _this =this
_this.$confirm(`确认要删除已选单位吗?删除此单位将删除此单位的所有用户。`).then(_ => {
deleteDepartment(data.deptId).then( res => {
_this.$toast({
duration:'1000',
type: 'success',
message: '删除成功!'
})
//el-tree 处理删除后的刷新问题
//数据请求删除成功后,要在页面上将数据移除,这样就能实现不刷新页面,数据驱动,动态删除数据的功能
//因此,请求删除接口成功后,对el-tree 的节点进行操作
const parent = node.parent;
const children =[];
parent.childNodes.forEach(item => {
children.push(item.data)
});
const index = children.findIndex(d => d.deptId === data.deptId);
parent.childNodes.splice(index, 1);
if(parent.childNodes.length == 0){ //判断删除的是否是最后一个节点,如果是,父级节点将变成叶子结点
parent.isLeaf = true; //设置叶子节点,用于层级显示
parent.data.isLowest = true;//设置叶子节点,用于render-content渲染dom
}
}).catch(err=>{
console.log(err)
}).finally(()=>{
this.isLoading = false
})
}).catch(_ => {});
},
//通过render懒加载单位
renderContent(h, { node, data, store }) {
//根据系统需求,通过data中的属性渲染dom,例如有的节点不能显示删除按钮,有点不能显示添加按钮,根据需求而定
if(data.deptType != 'N01E01E01E01E01E' && data.deptType != 'N01E01E01E02E' && data.deptType != 'N01E02E01E01E'){
if(data.isLowest){
return (
<span class="custom-tree-node">
<span class="dept-name">{data.deptName}</span>
<span class="dept-code">{data.deptId}</span>
<span class="dept-oper">
<el-button size="mini" type="primary" on-click={ (e) => this.openAddDialog(e,node,data) } >添加下级单位</el-button>
<el-button size="mini" type="primary" on-click={ (e) => this.openEditDialog(e,node, data) }>编辑</el-button>
<el-button size="mini" type="primary" on-click={ (e) => this.deleteDept(e,node, data) }>删除</el-button>
</span>
</span>
);
}else{
return (
<span class="custom-tree-node">
<span class="dept-name">{data.deptName}</span>
<span class="dept-code">{data.deptId}</span>
<span class="dept-oper">
<el-button size="mini" type="primary" on-click={ (e) => this.openAddDialog(e,node,data) } >添加下级单位</el-button>
<el-button size="mini" type="primary" on-click={ (e) => this.openEditDialog(e,node, data) }>编辑</el-button>
</span>
</span>
);
}
}else{
if(data.isLowest){
return (
<span class="custom-tree-node">
<span class="dept-name">{data.deptName}</span>
<span class="dept-code">{data.deptId}</span>
<span class="dept-oper">
<el-button size="mini" type="primary" on-click={ (e) => this.openEditDialog(e,node, data) }>编辑</el-button>
<el-button size="mini" type="primary" on-click={ (e) => this.deleteDept(e,node, data) }>删除</el-button>
</span>
</span>
);
}else{
return (
<span class="custom-tree-node">
<span class="dept-name">{data.deptName}</span>
<span class="dept-code">{data.deptId}</span>
<span class="dept-oper">
<el-button size="mini" type="primary" on-click={ (e) => this.openEditDialog(e,node, data) }>编辑</el-button>
</span>
</span>
);
}
}
}
}
}
</script>
<style lang="scss" scoped>
.dept-page{
.dept-tit{
height: 48px;
border-bottom:1px solid #ddd;
h3{
margin-left:20px;
font-size: 14px;
color: #3169ba;
line-height: 48px;
}
}
.dept-con{
padding:20px;
}
}
//对el-tree样式的改写
.el-tree{
border:1px solid #ddd;
overflow: hidden;
>>>.el-tree-node__content{
padding:5px;
border-bottom:1px solid #ddd;
.custom-tree-node{
width:100%;
overflow: hidden;
height:30px;
line-height:30px;
.dept-name{
width:500px;
float:left;
padding:0 10px;
cursor:pointer;
}
.dept-code{
float:left;
padding:0 10px;
}
.dept-oper{
float:right;
width:240px;
text-align:left;
padding:0 10px;
}
}
}
}
</style>