場景
在人員管理系統中,有不少頁面需要選擇目前的部門樹形結構中的部門進而作為篩選條件進行查詢。
怎樣借助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>();
最終執行這個方法后得到的數據為