ElementUI - Tree & Shiro
0 Views shiro, tree with
本文字数:2,405 字 | 阅读时长 ≈ 10 min

ElementUI - Tree & Shiro

0 Views shiro, tree with
本文字数:2,405 字 | 阅读时长 ≈ 10 min

ElementUI - Tree

在上一篇文章中我们介绍了Java后端如何构建一棵Tree树,并将数据返回给前端,前端Vue.js + ElementUI 又是如何渲染出来.

博文地址:Shiro权限管理项目中,如何构建权限菜单?

这次呢,我们谈如何使用ElementUI中的Tree组件,用于部门、角色、菜单的修改添加上,以’部门管理’举例,效果如下:

准备

开始

渲染

前端

在写后端接口数据前(当然如何写在上篇文章也有提到),我们先绘出前端界面:

首先是Tree组件

<el-form ref="form" :model="form">
    <el-form-item prop="parentId" v-model="form.parentId" label="上级部门" label-width="120px">
        <el-tree :data="roleTree" ref="tree" highlight-current show-checkbox check-strictly
                 :default-checked-keys="form.parentId"
                 :default-expanded-keys="form.parentId"
                 node-key="id"
                 :props="treeProps"></el-tree>
    </el-form-item>
</el-form>

解释

Attribute Tips
prop 本例中用户表单校验
v-model 双向绑定,格式为数组
:data 封装tree数据的数组,用于渲染树形结构
show-checkbox 显示勾选框
check-strictly 严格模式,父子节点无关系,不联动勾选
node-key 指定每个节点的key,最好就为节点的id值
:default-checked-keys 默认选中的项,参数为封装了node-key的数组
:default-expanded-keys 默认展开的项,参数为封装了node-key的数组
:props Tree树节点的配置选项

其次是Vue.js部分

new Vue({
    data: {
        //模态框状态标识
        dialogVisible: false,
        dialogTitle: '',
        roleTree: [], //部门Tree
        treeProps: {
            children: 'children',
            label: 'name'
        },
        //form表单对象
        form: {
            id: '',
            name: '',
            parentId: [],
        },
    },
    methods: {
        //触发保存按钮:添加、更新
        handleSave(id) {
            this.clearForm();
            //获取Dept列表
            this.$http.get(api.system.dept.roleTree).then(response => {
                this.roleTree = response.body.data;
            })
            if (id == null) {
                this.dialogTitle = '新增部门'
            } else {
                this.dialogTitle = '修改部门'
                this.$http.get(api.system.dept.findById(id)).then(response => {
                    this.form = response.body.data;
                    if (response.body.data.parentId == null) {
                        this.form.parentId = []
                    } else {
                        this.form.parentId = [response.body.data.parentId]
                    }
                })
            }
            this.dialogVisible = true;
        },
        clearForm() {
            if (this.$refs.form != undefined) {
                this.$refs.form.resetFields();
            }
            this.form.id = ''
            this.form.name = ''
            this.form.parentId = []
        },
    }
})

解释

代码基本如上了,需要注意以下几点:

到这里前端的数据渲染部分已基本完成(注意我粘贴的代码中省略了一部分),下面看一下Tree组件的数据结构,即roleTree

后端

上面可以看到Tree组件的数据格式,后端的任务就是获取数据库中的记录并封装为Tree树的结构返回给前端。

因为在 Shiro权限管理项目中,如何构建权限菜单? 一文中已经介绍了如何将数据库中的记录封装为Tree树结构,这里不过多解释,直接上代码:

service层实现

@Service
public class DeptServiceImpl extends BaseServiceImpl<Dept> implements DeptService {

    @Autowired
    private DeptMapper deptMapper;

    @Override
    public List<Tree<Dept>> tree() {
        List<Dept> list = queryList(new Dept());
        List<Tree<Dept>> treeList = new ArrayList<>();
        list.forEach(dept -> {
            Tree<Dept> tree = new Tree<>();
            tree.setId(dept.getId());
            tree.setParentId(dept.getParentId());
            tree.setName(dept.getName());
            treeList.add(tree);
        });
        return TreeUtils.build(treeList);
    }

    @Override
    public List<Dept> queryList(Dept dept) {
        try {
            Example example = new Example(Dept.class);
            if (StringUtils.isNotBlank(dept.getName())) {
                example.createCriteria().andCondition("name", dept.getName());
            }
            example.setOrderByClause("create_time");
            return this.selectByExample(example);
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }
}

后端的代码我们在 Shiro权限管理项目中,如何构建权限菜单? 中解释过了,这里不再讲解

编辑

前端

实现Tree组件单选

修改html

<el-form ref="form" :model="form">
    <el-form-item prop="parentId" v-model="form.parentId" label="上级部门" label-width="120px">
        <el-tree :data="roleTree" ref="tree" highlight-current show-checkbox check-strictly
                 :default-checked-keys="form.parentId"
                 :default-expanded-keys="form.parentId"
                 node-key="id"
                 @check-change="checkChange"
                 :props="treeProps"></el-tree>
    </el-form-item>
</el-form>

修改Vue.js部分

//Tree控件节点选中状态改变触发的事件
checkChange(data, node, self) {
    if (node) {
        this.form.parentId = [data.id];
        this.$refs.tree.setCheckedNodes([data.id])
    } else {
        if (this.$refs.tree.getCheckedKeys().length == 0) {
            this.form.parentId = [];
        }
    }
},

解释

@check-change是节点选中状态改变触发的事件。

因为这里我们讲编辑功能,必然涉及对节点的编辑(比如修改父级节点、修改关联权限节点)。在本例中主要是修改部门的上级节点,又因为我们规定一个节点最多有一个上级节点,那么Tree组件的勾选节点应该变为单选,而官方的Tree组件都是多选的,所以看到上面我们新增的checkChange()方法中的实现,目的就是实现单选。

checkChange(data, node, self)方法中参数data是勾选(取消勾选)节点的tree数据(包含其所有子节点的数据);node是当前触发节点是否被选中;self是当前节点本身是否被选中。

setCheckNodes()是设置Tree组件选中的节点,getCheckedKeys()是获取Tree组件当前选中的节点。实现单选,就是实现:A节点选中,再勾选B节点时,先清空已勾选节点,再用setCheckNodes()手动设置Tree组件勾选节点;这样就实现了单选(选中B,就取消A勾选状态)。
setCheckNodes()方法会触发checkChange()方法,所以要判断this.$refs.tree.getCheckedKeys().length

注意Tree组件中的:default-checked-keys的参数也是form.parentId所以我们要时刻保证parentId是一个数组(尽管后端需要的是String),所以仅需要在save()保存更改的时候再修改parentId为String,并且修改完成后要立即把parentId变为数组。

But

以上都是在努力把Tree组件改为单选,But,如果你业务需要多选就不需要上述checkChange()方法中的判断了。

保存更改

HTML触发保存按钮

<el-button type="primary" @click="save('form')">确 定</el-button>

你可能会注意到save('form')'form'是什么鬼?其实他是<el-form>中的prop="form",目的是为了实现表单校验功能,具体看官方文档:传送门

Vue.js中新增save方法

//保存
save(form) {
    this.$refs[form].validate((valid) => {
        if (valid) {
            this.dialogVisible = false;
            this.form.parentId = this.form.parentId[0]
            if (this.form.id == null || this.form.id == 0) {
                //添加
                this.$http.post(api.system.dept.add, JSON.stringify(this.form)).then(response => {
                    if (response.body.code == 200) {
                        this._notify(response.body.msg, 'success')
                    } else {
                        this._notify(response.body.msg, 'error')
                    }
                    this.clearForm();
                    this.init()
                    this.search(this.pageConf.pageCode, this.pageConf.pageSize)
                })
            } else {
                //修改
                this.$http.post(api.system.dept.update, JSON.stringify(this.form)).then(response => {
                    if (response.body.code == 200) {
                        this._notify(response.body.msg, 'success')
                    } else {
                        this._notify(response.body.msg, 'error')
                    }
                    this.clearForm();
                    this.init()
                    this.search(this.pageConf.pageCode, this.pageConf.pageSize)
                })
            }
        } else {
            return false;
        }
    })
},

后端

上面对部门的添加和修改节点其实都比较简单些,直接将前端数据保存进数据库即可,这里我们将一下节点的删除:

节点删除

节点删除,主要涉及:

举例:改变父级节点,service层实现如下:

@Override
@Transactional
public void delete(List<Long> ids) {
    this.batchDelete(ids, "id", Dept.class);
    this.deptMapper.changeTopNode(ids);
}

deptMapper的mapper.xml如下:

<update id="changeTopNode">
    update tb_dept
    set parent_id = 0
    where id in (select a.id
    from (select id from tb_dept where parent_id in
    <foreach collection="list" item="id" index="index" open="(" close=")" separator=",">
        #{id}
    </foreach>
    ) a);
</update>

注意

将上面

select id from tb_dept where parent_id in
<foreach collection="list" item="id" index="index" open="(" close=")" separator=",">
    #{id}
</foreach>

拆分,大概就是这样一段SQL:

select id from tb_dept where parent_id in (1, 2)

注意

select a.id from (select id from tb_menu where parent_id in (1, 2)) a;
select id from tb_menu where parent_id in (1, 2);

上述两种方式都能查询出来parent_id是(1,2)的记录行的id值。而第一种方式是先生成一张临时表a,再查询id值

update tb_menu
set parent_id = 0
where id in (select a.id
             from (select id from tb_menu where parent_id in (1, 2)) a);

update tb_menu
set parent_id = 0
where id in (select id from tb_menu where parent_id in (1, 2));

若执行后者会报错:You can't specify target table 'tb_menu' for update in FROM clause
百度查到是由于mysql不能对一张表查询到某些值就同时更新这张表,通过生成一张临时表来避免这个问题


交流

以上仅是个人的见解,可能有些地方是错误的,深知自己的菜鸡技术,欢迎大佬指出。

个人建了一个Java交流群:671017003。 欢迎大佬或是新人入驻一起交流学习Java技术。


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

如果你觉得这篇文章帮助到了你,你可以帮作者买一杯果汁表示鼓励