场景
在人员管理系统中,有不少页面需要选择目前的部门树形结构中的部门进而作为筛选条件进行查询。
怎样借助ElementUI的el-tree控件封装成公共控件并请求SpringBoot后台数据获取部门数据并封装成前端需要的树形结构数据。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。
实现
前端实现
首先在项目下新建components目录存放公共组件,在目录下新建LeftCheckTree目录,并在此目录下新建index.vue用来实现公共部门树组件。
在页面上需要一个模糊搜索的输入框和el-tree控件
<template> <div> <div class="head-container"> <el-input v-model="deptName" placeholder="请输入部门名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px" /> </div> <div class="head-container"> <el-tree :data="deptOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree" default-expand-all show-checkbox @check="handleCheck" /> </div> </div> </template>
然后需要引入一些样式组件和方法等
import Treeselect from "@riophae/vue-treeselect"; import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import { treeselect } from "@/api/system/dept"; import "@riophae/vue-treeselect/dist/vue-treeselect.css";
这里引用的 import { treeselect } from "@/api/system/dept";是获取部门数据的方法
所以在api/system/dept.js中的treeselect方法中会请求后台查询部门数据
// 查询部门下拉树结构 export function treeselect() { return request({ url: '/system/dept/treeselect', method: 'get' }) }
这里省略封装axios请求的过程,请求后台数据部门下面介绍。
为了实现在此页面一加载完就查询部门数据,在created函数中执行请求数据的方法
created() { this.getTreeselect(); }, methods: { /** 查询部门下拉树结构 */ getTreeselect() { treeselect().then(response => { this.deptOptions = response.data; }); },
请求后台获取的数据赋值给部门树选项数组,此数组需要提前声明
data() { return { // 部门树选项 deptOptions: [],
然后通过 :data="deptOptions"将数据绑定给el-tree控件。
控件还添加了filter-node-method对树节点进行筛选时执行的方法,返回true表示这个节点可以显示,返回false则表示这个节点会被隐藏。
:filter-node-method="filterNode" filterNode是个函数 // 筛选节点 filterNode(value, data) { if (!value) return true; return data.label.indexOf(value) !== -1; },
此组件完整示例代码
<template> <div> <div class="head-container"> <el-input v-model="deptName" placeholder="请输入部门名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px" /> </div> <div class="head-container"> <el-tree :data="deptOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree" default-expand-all show-checkbox @check="handleCheck" /> </div> </div> </template> <script> import Treeselect from "@riophae/vue-treeselect"; import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import { treeselect } from "@/api/system/dept"; import "@riophae/vue-treeselect/dist/vue-treeselect.css"; export default { name: "leftCheckTree", components: { Treeselect }, props: {}, data() { return { // 部门名称 deptName: undefined, defaultProps: { children: "children", label: "label" }, // 部门树选项 deptOptions: [], }; }, watch: { // 根据名称筛选部门树 deptName(val) { this.$refs.tree.filter(val); } }, created() { this.getTreeselect(); }, methods: { /** 查询部门下拉树结构 */ getTreeselect() { treeselect().then(response => { this.deptOptions = response.data; }); }, // 筛选节点 filterNode(value, data) { if (!value) return true; return data.label.indexOf(value) !== -1; }, //多选 handleCheck(data, checked){ let deptIdList = []; for(let i = 0;i<checked.checkedNodes.length;i++){ if(!checked.checkedNodes[i].children){ deptIdList.push(checked.checkedNodes[i].id) } } this.$emit('handleCheck', deptIdList) } } }; </script> <style lang="scss" scoped> </style>
然后通过
export default { name: "leftCheckTree",
就可以将此组件暴露并且名字为leftCheckTree
那么我们在需要的页面就可以引用这个组件了。
首先在页面中添加组件
<template> <div class="app-container"> <el-row :gutter="20"> <!--部门数据--> <el-col :span="4" :xs="24"> <left-check-tree @handleCheck="handleCheck"></left-check-tree> </el-col>
在此页面我们需要获取到选中的部门的id的数组。
在上面的树组件中的
this.$emit('handleCheck', deptIdList)
就是实现子组件向父组件传值,名字叫handleCheck,值时deptList即多选时选中的部门id,即多选选中的节点的部门id属性。
那么在父页面即引用这个数组件的页面中就可以通过@handleCheck="handleCheck"
并且在handleCheck方法中
handleCheck(deptIdList) { this.queryParams.bmids = deptIdList; console.log(this.queryParams.bmids); },
获取到选中的部门id的数组并且将其赋值给父页面即调用树控件页面的对象的数组属性,即查询参数的部门id数组属性
// 查询参数 queryParams: { pageNum: 1, pageSize: 10, bmids: [],
这样就能获取到要查询那几个部门的数据的部门id的数组。
在对应SpringBoot后台接口中,使用Post接受请求参数,因为部门数组的查询参数使用get请求的话会有长度限制。
@PostMapping("/selectListBySx") public TableDataInfo selectKqKqryszList(@RequestBody() KqKqrysz kqKqrysz) { List<KqKqrysz> list = kqKqryszService.selectKqKqryszListBySx(kqKqrysz); return getDataTable(list); }
接受参数的实体类中需要添加一个
/**** * 部门id数组传参用 */ private Long[] bmids;
部门id的数组属性以及get和set方法。
在接受到参数后一直传递到mapper层对应的xml里面
<!--根据筛选条件查询--> <select id="selectListBySx" parameterType="KqKqrysz" resultMap="KqKqryszResult"> SELECT * from table1 <where> <if test="bmids != null and bmids.length >0"> and j.bmid in <foreach collection="bmids" item="item" open="(" separator="," close=")"> ${item} </foreach> </if> </where> </select>
这样就可以查询部门id是不是包含在传递的参数数组中的数据。
对应的部门数据库的设计
主要是要有部门id和父级部门id和部门名称这几个字段,通过父级id就能构建出父子级的关系。
比如可以这样添加数据
第一个测试部门的父级部门是0,则代表它是顶级部门,下面的父级部门的id是上面的顶级部门的id,所以这样就能构造出父子级部门的关系。
然后再说怎样将后台父子级的数据构造成前端需要的树控件的数据。
前面在封装公共控件时
请求后台数据对应的接口
/** * 获取部门下拉树列表 */ @GetMapping("/treeselect") public AjaxResult treeselect(SysDept dept) { List<SysDept> depts = deptService.selectDeptList(dept); return AjaxResult.success(deptService.buildDeptTreeSelect(depts)); }
首先是查询出数据库中存储的所有的部门数据deptService.selectDeptList(dept);
字段信息和上面设计数据库时对应。
然后将其构建成前端需要的数据源的形式通过buildDeptTreeSelect。
首先是请求数据,在对应的mapper层
<select id="selectDeptList" parameterType="SysDept" resultMap="SysDeptResult"> <include refid="selectDeptVo"/> where d.del_flag = '0' <if test="parentId != null and parentId != 0"> AND parent_id = #{parentId} </if> <if test="deptName != null anddeptName != ''"> AND dept_name like concat('%', #{deptName}, '%') </if> <if test="status != null andstatus != ''"> AND status = #{status} </if> order by d.parent_id, d.order_num </select>
最终将数据库中的数据以父级id和排序号排序查询出来。查询出数据库中所有的对象的list
然后调用下面的构建前端数据的方法
此方法的实现中
public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts) { List<SysDept> deptTrees = buildDeptTree(depts); return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); }
又调用了buildDeptTree方法,最终是要构建成父级节点要有children这个属性,即符合el-tree赋值的标准。
el-tree官方示例赋值代码
<el-tree :data="data" show-checkbox node-key="id" :default-expanded-keys="[2, 3]" :default-checked-keys="[5]"> </el-tree> <script> export default { data() { return { data: [{ id: 1, label: '一级 2', children: [{ id: 3, label: '二级 2-1', children: [{ id: 4, label: '三级 3-1-1' }, { id: 5, label: '三级 3-1-2', disabled: true }] }, { id: 2, label: '二级 2-2', disabled: true, children: [{ id: 6, label: '三级 3-2-1' }, { id: 7, label: '三级 3-2-2', disabled: true }] }] }], defaultProps: { children: 'children', label: 'label' } }; } }; </script>
所在在上面的buildDeptTree方法中
/** * 构建前端所需要树结构 * * @param depts 部门列表 * @return 树结构列表 */ @Override public List<SysDept> buildDeptTree(List<SysDept> depts) { List<SysDept> returnList = new ArrayList<SysDept>(); List<Long> tempList = new ArrayList<Long>(); for (SysDept dept : depts) { tempList.add(dept.getDeptId()); } for (Iterator<SysDept> iterator = depts.iterator(); iterator.hasNext();) { SysDept dept = (SysDept) iterator.next(); // 如果是顶级节点, 遍历该父节点的所有子节点 if (!tempList.contains(dept.getParentId())) { recursionFn(depts, dept); returnList.add(dept); } } if (returnList.isEmpty()) { returnList = depts; } return returnList; }
这其中有用到了递归函数recursionFn
/** * 递归列表 */ private void recursionFn(List<SysDept> list, SysDept t) { // 得到子节点列表 List<SysDept> childList = getChildList(list, t); t.setChildren(childList); for (SysDept tChild : childList) { if (hasChild(list, tChild)) { // 判断是否有子节点 Iterator<SysDept> it = childList.iterator(); while (it.hasNext()) { SysDept n = (SysDept) it.next(); recursionFn(list, n); } } } }
注意为了构建每个节点的children属性,所以在SysDept这个实体类中要比数据库多一个children属性,并且是一个list
/** 部门ID */ private Long deptId; /** 父部门ID */ private Long parentId; /** 祖级列表 */ private String ancestors; /** 部门名称 */ private String deptName; /** 显示顺序 */ private String orderNum; /** 负责人 */ private String leader; /** 联系电话 */ private String phone; /** 邮箱 */ private String email; /** 部门状态:0正常,1停用 */ private String status; /** 删除标志(0代表存在 2代表删除) */ private String delFlag; /** 父部门名称 */ private String parentName; /** 子部门 */ private List<SysDept> children = new ArrayList<SysDept>();
最终执行这个方法后得到的数据为