ElementUI中Tree组件使用案例讲解
首先官网上的树形控件教程地址为Element - The world's most popular Vue UI framework
案例一:
要实现这种类型的树控件,并且后边相关操作:
1.1后端准备工作
首先,数据库表为:
查询接口返回的实体类为:
@Data
@NoArgsConstructor
@RequiredArgsConstructor // 有参构造
@EqualsAndHashCode(callSuper = false ,of = "name")// 表示以name去重写的Equals和HashCode
@Accessors(chain = true)
@TableName("t_department")
@ApiModel(value="Department对象", description="")
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "部门名称")
@Excel(name = "部门")
@NonNull
private String name;
@ApiModelProperty(value = "父id")
private Integer parentId;
@ApiModelProperty(value = "路径")
private String depPath;
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@ApiModelProperty(value = "是否上级")
private Boolean isParent;
@ApiModelProperty(value = "子部门列表")
@TableField(exist = false)
private List<Department> children;
@ApiModelProperty(value = "返回结果,存储过程使用")
@TableField(exist = false)
private Integer result;
}
查询接口返回的数据格式:通过属性 children来判断是否有子节点
[
{
"id": 1,
"name": "股东会",
"parentId": -1,
"depPath": ".1",
"enabled": true,
"isParent": true,
"children": [
{
"id": 2,
"name": "董事会",
"parentId": 1,
"depPath": ".1.2",
"enabled": true,
"isParent": true,
"children": [
{
"id": 3,
"name": "总办",
"parentId": 2,
"depPath": ".1.2.3",
"enabled": true,
"isParent": true,
"children": [
{
"id": 4,
"name": "财务部",
"parentId": 3,
"depPath": ".1.2.3.4",
"enabled": true,
"isParent": false,
"children": [],
"result": null
},
{
"id": 5,
"name": "市场部",
"parentId": 3,
"depPath": ".1.2.3.5",
"enabled": true,
"isParent": true,
"children": [
{
"id": 6,
"name": "华东市场部",
"parentId": 5,
"depPath": "1.2.3.5.6",
"enabled": true,
"isParent": true,
"children": [
{
"id": 8,
"name": "上海市场部",
"parentId": 6,
"depPath": "1.2.3.5.6.8",
"enabled": true,
"isParent": false,
"children": [],
"result": null
}
],
"result": null
},
{
"id": 7,
"name": "华南市场部",
"parentId": 5,
"depPath": "1.2.3.5.7",
"enabled": true,
"isParent": false,
"children": [],
"result": null
},
{
"id": 9,
"name": "西北市场部",
"parentId": 5,
"depPath": ".1.2.3.5.9",
"enabled": true,
"isParent": true,
"children": [
{
"id": 10,
"name": "贵阳市场",
"parentId": 9,
"depPath": ".1.2.3.5.9.10",
"enabled": true,
"isParent": true,
"children": [
{
"id": 11,
"name": "乌当区市场",
"parentId": 10,
"depPath": ".1.2.3.5.9.10.11",
"enabled": true,
"isParent": false,
"children": [],
"result": null
}
],
"result": null
}
],
"result": null
}
],
"result": null
},
{
"id": 12,
"name": "技术部",
"parentId": 3,
"depPath": ".1.2.3.12",
"enabled": true,
"isParent": false,
"children": [],
"result": null
},
{
"id": 13,
"name": "运维部",
"parentId": 3,
"depPath": ".1.2.3.13",
"enabled": true,
"isParent": false,
"children": [],
"result": null
}
],
"result": null
}
],
"result": null
},
{
"id": 150,
"name": "aaa",
"parentId": 1,
"depPath": ".1.150",
"enabled": true,
"isParent": true,
"children": [
{
"id": 151,
"name": "abbb",
"parentId": 150,
"depPath": ".1.150.151",
"enabled": true,
"isParent": false,
"children": [],
"result": null
}
],
"result": null
},
{
"id": 154,
"name": "ccc",
"parentId": 1,
"depPath": ".1.154",
"enabled": true,
"isParent": false,
"children": [],
"result": null
},
{
"id": 157,
"name": "dddd",
"parentId": 1,
"depPath": ".1.157",
"enabled": true,
"isParent": false,
"children": [],
"result": null
}
],
"result": null
}
]
1.2前端代码
从官网上复制一个模板过来,搜索的话,直接往下找一个有搜索框的,把搜索框复制过来,复制好就可以开始修改,写增删改查的方法调用了。
写好的代码如下:
<template>
<div style="width: 550px;">
<el-input
placeholder="请输入部门名称进行搜索..."
prefix-icon="el-icon-search"
v-model="filterText">
</el-input>
<el-tree
:data="deps"
:props="defaultProps"
:filter-node-method="filterNode"
:expand-on-click-node="false"
ref="tree">
<!-- slot-scope可以自定义树节点里的内容,node当前节点的node对象,data后端对应返回的数据 -->
<span class="custom-tree-node" slot-scope="{ node, data }" style="display:flex; justify-content: space-between;width: 100%;">
<span>{{ data.name }}</span>
<span>
<el-button
type="primary"
size="mini"
class="depBtn"
@click="() => showAddDep(data)">
添加部门
</el-button>
<el-button
type="danger"
size="mini"
class="depBtn"
@click="() => deleteDep(data)">
删除部门
</el-button>
</span>
</span>
</el-tree>
<!-- 添加部门的弹出框 -->
<el-dialog
title="添加部门"
:visible.sync="dialogVisible"
width="30%">
<div>
<table>
<tr>
<td><el-tag>上级部门</el-tag></td>
<td><el-tag>{{pname}}</el-tag></td>
</tr>
<tr>
<td><el-tag>部门名称</el-tag></td>
<td><el-input v-model="dep.name" placeholder="请输入部门名称..."></el-input></td>
</tr>
</table>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="doAddDep">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data(){
return{
// 树的搜索条件
filterText: '',
// 树的数据
deps: [],
// 树的配置
defaultProps: {
children: 'children',
label: 'name'
},
// 添加弹出框
dialogVisible: false,
// 添加的部门数据
dep:{
name:'',
parentId: -1
},
// 上级部门名称
pname: ''
}
},
mounted(){
this.initDeps();
},
methods: {
// 初始化数据
initDeps(){
this.getRequest('/system/basic/department/').then(resp=>{
this.deps = resp;
})
},
// 树的搜索
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
// 添加弹框
showAddDep(data){
console.log(data)
this.dep.parentId = data.id;
this.pname = data.name;
this.dialogVisible = 'true'
},
// 添加
doAddDep(){
this.postRequest('/system/basic/department/',this.dep).then(resp=>{
if(resp.code==200){
resp.obj.children = []
this.addDep2Deps(this.deps,resp.obj);
this.initDep();
this.dialogVisible = false;
}
})
},
initDep(){
this.dep = {
name: '',
parentId: -1
}
this.panme = ''
},
// 添加成功后手动的给树加数据
addDep2Deps(deps,dep){
for(let i = 0; i<deps.length; i++){
let d= deps[i];
if(d.id == dep.parentId){
d.children = d.children.concat(dep);
if(d.children.length>0){
d.isParent = true;
}
return;
}else{
// 递归
this.addDep2Deps(d.children,dep);
}
}
},
// 删除
deleteDep(data){
console.log(data)
if(data.isParent){
this.$message.error("父部门无法删除");
}else{
this.$confirm('此操作将永久['+data.name+']部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest('/system/basic/department/'+data.id).then(resp=>{
if(resp.code==200){
this.removeDepFromDeps(null,this.deps,data.id);
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
// 手动删除 (父部门,总部门数据,要删除的部门id)
removeDepFromDeps(p,deps,id){
for(let i=0; i<deps.length; i++){
let d = deps[i];
if(d.id==id){
deps.splice(i,1);
if(deps.length==0){
p.isParent = false;
}
return;
}else{
this.removeDepFromDeps(d,d.children,id);
}
}
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
}
}
}
</script>
<style>
.depBtn{
padding: 3px;
}
</style>
需要注意的是:
1:树组件中 :data="deps" 加载树组建的数据。:props="defaultProps" 加载树组件的配置,其中label是给个枝叶的名字对应的字段,children是子节点对应的字段。:filter-node-method="filterNode" 树组件的搜索,filterNode过滤的回调,这个官网拷贝即可。:expand-on-click-node="false" 关闭点击折叠,只有点击前面的小三角才能展开折叠。
2:搜索框绑定的数据filterText,下面监听这个数据的改变,如果改变了,就调用树的filter方法(this.$refs.tree.filter(val);),进行过滤,这个方法的回调在树组件的属性中定义:filter-node-method="filterNode" ,也就是输入框值改变了,会去掉filterNode这个方法,这个方法传入两个值,value是文本框输入值,data是树组件的数据,value为空直接返回,不为空则过滤。
3: 添加部门的时候,添加成功后不能单纯的掉接口,重新请求一次树中的数据,这样的话,每添加一个部门,整个树就会折叠起来。这时候就需要不调接口请求新的数据,在js中操作树中的数据this.deps,让它和数据库保持同步。首先在成功后调用方法: addDep2Deps(deps,dep) 这个方法第一个参数是整个树中的数据,第二个参数是新添加的树的数据(这个数据是添加成功接口回显的数据,也就是添加的这条记录信息)在这个方法中,会循环,找到对应的父节点,给它的子节点拼接数据,修改父节点isParent属性(是否有孩子),之后就一直递归,知道添加完成为止。这个数据添加成功后,清空弹出框中输入的信息,关闭弹出框。
4:删除操作,同样,删除时也要手动的在js中把这条数据删除,不能请求新数据,请求新数据会导致树整个关闭,用户体验十分不好,需要在js中把树中的数据和数据库中进行同步。
调用接口删除成功后,调用removeDepFromDeps(p,deps,id)方法,在js中进行数据删除,调用时,第一个参数传null表示从顶层节点开始往下找,第二个参数是树的数据,第三个参数是要删除部门的id。同样进行循环,添加中比较的是父节点的id,删除是,比较的就是这个叶子的id,如果相同,那就删除,同时,判断把这个叶子删掉后,这个枝下面还有没有叶子,因为有叶子的枝是不能删除的,所以要再判断一下修改isParent的状态,之后还是一样,在一层中没有查询到的话,就递归到下一层去找。
案例二
这种前面带有选择框的,进行授权操作
2.1后端准备
对应的数据库表
对应的实体类:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_menu")
@ApiModel(value="Menu对象", description="")
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "url")
private String url;
@ApiModelProperty(value = "path")
private String path;
@ApiModelProperty(value = "组件")
private String component;
@ApiModelProperty(value = "菜单名")
private String name;
@ApiModelProperty(value = "图标")
private String iconCls;
@ApiModelProperty(value = "是否保持激活")
private Boolean keepAlive;
@ApiModelProperty(value = "是否要求权限")
private Boolean requireAuth;
@ApiModelProperty(value = "父id")
private Integer parentId;
@ApiModelProperty(value = "是否启用")
private Boolean enabled;
@ApiModelProperty(value = "子菜单")
@TableField(exist = false) // 告诉mybatisplus这个字段不在表中,查询的时候不要去查
private List<Menu> children;
@ApiModelProperty(value = "角色列表")
@TableField(exist = false)
private List<Role> roles;
}
查询接口返回的数据
{
"code": 200,
"message": "查询成功",
"obj": [
{
"id": 1,
"url": null,
"path": null,
"component": null,
"name": "所有",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": [
{
"id": 2,
"url": null,
"path": null,
"component": null,
"name": "员工资料",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": [
{
"id": 7,
"url": null,
"path": null,
"component": null,
"name": "基本资料",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 8,
"url": null,
"path": null,
"component": null,
"name": "高级资料",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
}
],
"roles": null
},
{
"id": 3,
"url": null,
"path": null,
"component": null,
"name": "人事管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": [
{
"id": 9,
"url": null,
"path": null,
"component": null,
"name": "员工资料",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 10,
"url": null,
"path": null,
"component": null,
"name": "员工奖惩",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 11,
"url": null,
"path": null,
"component": null,
"name": "员工培训",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 12,
"url": null,
"path": null,
"component": null,
"name": "员工调薪",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 13,
"url": null,
"path": null,
"component": null,
"name": "员工调动",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
}
],
"roles": null
},
{
"id": 4,
"url": null,
"path": null,
"component": null,
"name": "薪资管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": [
{
"id": 14,
"url": null,
"path": null,
"component": null,
"name": "工资账套管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 15,
"url": null,
"path": null,
"component": null,
"name": "员工账套设置",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 16,
"url": null,
"path": null,
"component": null,
"name": "工资表管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 17,
"url": null,
"path": null,
"component": null,
"name": "月末处理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 18,
"url": null,
"path": null,
"component": null,
"name": "工资表查询",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
}
],
"roles": null
},
{
"id": 5,
"url": null,
"path": null,
"component": null,
"name": "统计管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": [
{
"id": 19,
"url": null,
"path": null,
"component": null,
"name": "综合信息统计",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 20,
"url": null,
"path": null,
"component": null,
"name": "员工积分统计",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 21,
"url": null,
"path": null,
"component": null,
"name": "人事信息统计",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 22,
"url": null,
"path": null,
"component": null,
"name": "人事记录统计",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
}
],
"roles": null
},
{
"id": 6,
"url": null,
"path": null,
"component": null,
"name": "系统管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": [
{
"id": 23,
"url": null,
"path": null,
"component": null,
"name": "基础信息设置",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 24,
"url": null,
"path": null,
"component": null,
"name": "系统管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 25,
"url": null,
"path": null,
"component": null,
"name": "操作日志管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 26,
"url": null,
"path": null,
"component": null,
"name": "操作员管理",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 27,
"url": null,
"path": null,
"component": null,
"name": "备份恢复数据库",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
},
{
"id": 28,
"url": null,
"path": null,
"component": null,
"name": "初始化数据库",
"iconCls": null,
"keepAlive": null,
"requireAuth": null,
"parentId": null,
"enabled": null,
"children": null,
"roles": null
}
],
"roles": null
}
],
"roles": null
}
]
}
2.2前端代码
<template>
<div>
<!-- 添加角色 -->
<div class="permissManaTool">
<el-input size="small" placeholder="请输入角色英文名" v-model="role.name">
<template slot="prepend">ROLE_</template>
</el-input>
<el-input size="small" placeholder="请输入角色中文名" v-model="role.nameZh" @keydown.enter.native="addRole"></el-input>
<el-button size="small" type="primary" icon="el-icon-plus" @click="addRole">添加角色</el-button>
</div>
<!-- 手风琴 -->
<div style="margin-top:10px; width:660px">
<el-collapse v-model="activeName" accordion @change="change">
<el-collapse-item :title="r.nameZh" :name="r.id" v-for="(r,index) in roles" :key="index">
<!-- 卡片 -->
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>可访问资源</span>
<el-button style="float: right; padding: 3px 0; color: #ff0000;" type="text" icon="el-icon-delete" @click="doDeleteRole(r)"></el-button>
</div>
<div>
<!-- 树 -->
<el-tree
show-checkbox
node-key="id"
ref="tree"
:key="index"
:default-checked-keys="selectMenus"
:data="allMenus" :props="defaultProps"></el-tree>
<div style="display:flex; justify-content: flex-end">
<el-button size="mini" @click="cancelUpdate">取消修改</el-button>
<el-button size="mini" type="primary" @click="doUpdate(r.id,index)">确定修改</el-button>
</div>
</div>
</el-card>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
export default {
data(){
return{
role:{
name:'',
nameZh:''
},
activeName : '-1',
roles:[],
allMenus: [],
// 树菜单的属性,children子菜单的属性,label显示出来的名字,都是后端接口返回来的字段名字
defaultProps: {
children: 'children',
label: 'name'
},
// 树中选中的节点
selectMenus: []
}
},
mounted(){
this.initRoles();
},
methods:{
// 初始化所有菜单
initAllMenus(){
this.getRequest('/system/basic/permiss/menus').then(resp=>{
if(resp.code==200){
this.allMenus = resp.obj;
}
})
},
// 根据角色id获取菜单
initSelectdMenus(rid){
this.getRequest('/system/basic/permiss/mid/'+rid).then(resp=>{
if(resp.code==200){
this.selectMenus = resp.obj;
}
})
},
// 获取角色列表
initRoles(){
this.getRequest('/system/basic/permiss/').then(resp=>{
if(resp.code==200){
this.roles = resp.obj;
}
})
},
// 手风琴点击事件,展开rid是每个name,name对应着后端字段id,关闭时rid为空
change(rid){
if(rid){
this.initAllMenus();
this.initSelectdMenus(rid);
}
},
// 修改角色权限
doUpdate(rid,index){
// 拿到这个手风琴下面的树
let tree = this.$refs.tree[index];
// 传true只打印叶子节点的id
let selectedKeys = tree.getCheckedKeys(true);
let url = '/system/basic/permiss/?rid='+ rid;
selectedKeys.forEach(key => {
url += '&mids='+ key;
});
this.putRequest(url).then(resp=>{
if(resp.code==200){
this.activeName = '-1'
}
})
},
cancelUpdate(){
this.activeName = '-1'
},
// 添加角色
addRole(){
if(this.role.name && this.role.nameZh){
this.postRequest('/system/basic/permiss/',this.role).then(resp=>{
if(resp.code==200){
this.initRoles();
this.role.name = '';
this.role.nameZh = '';
}
})
}else{
this.$message.error("所有字段不能为空");
}
},
// 删除角色
doDeleteRole(role){
this.$confirm('此操作将永久删除'+role.nameZh+'角色, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest('/system/basic/permiss/role/'+r.id).then(resp => {
if(resp.code == 200){
this.initRoles();
}else{
this.$message.error(resp.message)
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
}
}
</script>
<style>
.permissManaTool{
display: flex;
justify-content: flex-start;
}
.permissManaTool .el-input{
width: 300px;
margin-right: 10px;
}
</style>
值得注意的是:
1:每个角色下面展开的权限树列表用的是手风琴组件(折叠面板)这里还要给树加上key,因为每个手风琴下面都是一个树 折叠面板饿了吗官网
2:树要展示前面的选择框,要给树组件加上show-checkbox属性。:default-checked-keys="selectMenus" 默认选中的key,这个selectMenus需要去data中定义一个数组。
3:在添加时,可通过 let tree = this.$refs.tree[index]; 拿到整个手风琴下面的树,let selectedKeys = tree.getCheckedKeys(true) 获取选中节点的id,修改成功后,把手风琴的折叠属性,定义成-1,折叠即可,因为下次再打开的时候,会去数据库查出这个角色对应菜单的最新数据。
到此这篇关于ElementUI中Tree组件使用的文章就介绍到这了,更多相关ElementUI中Tree组件使用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341