el-tree 在父子节点的联动上有点小麻烦,在刚开始设计时我并没有仔细研究 API,导致出现了好多问题,本篇是踩坑记录。
另有全选/全不选,全部展开/收起操作,本篇也做一个记录。
菜单权限设计
业务需求:在菜单权限设计中,新增权限状态下,选择子级菜单,对应的父级菜单也应该被赋予权限。
编辑权限状态下,打开权限设置时,应将已有权限做一个勾选。
父子节点默认非严格关联
check-strictly
属性表示在“显示复选框”的情况下,是否“严格的遵循父子不互相关联”的做法,默认为 false。此时,选择父级菜单,子级菜单会全部选中;选择子级菜单,父级前会有“-”标记:
当值设置为 true 时,选择父级菜单,子级菜单不会被选中:
方案一:非严格关联
在提交选择时,应该将子级菜单对应的父级也加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| getAllNodes(treeRef.value!.getCheckedNodes(false, false))
const getAllNodes = (arr: Array) => { let list = [] let pidList = [] arr.forEach((item) => { list.push(item.id) if (item.pid !== 0 && list.indexOf(item.pid) == -1) { list.push(item.pid) pidList.push(item.pid) } }) if (state.menuTree && state.menuTree.length > 0) { state.menuTree.forEach((sys) => { if (sys.children && sys.children.length > 0) { sys.children.forEach((i) => { pidList.forEach((pid) => { if (i.level !== 0 && i.id == pid) { list.push(i.pid) } }) }) } }) } return list }
|
提交时完美实现。
但是出现了一个之前欠考虑的问题,就是在编辑状态下(即新增时已有勾选,初始化时后端返回已勾选数组,需要前端在打开时勾选),因为父子非严格关联,导致父节点勾选时,对应所有子节点都被勾选。
方案二:严格关联
使用 changeCheck 方法,在节点被勾选时,将其所有父节点勾选。level 为组件自带属性,可以基于此进行节点递归。
| <el-tree ref="treeRef" :data="menuTree" show-checkbox node-key="id" :props="defaultProps" @check="changeCheck" :check-strictly="true" />
|
| const changeCheck = (node) => { let thisNode = treeRef.value!.getNode(node.id) const keys = treeRef.value!.getCheckedKeys() if (thisNode.checked) { for (let i = thisNode.level; i > 1; i--) { if (!thisNode.parent.checked) { thisNode = thisNode.parent keys.push(thisNode.data.id) } } } treeRef.value!.setCheckedKeys(keys) }
|
这个有个新问题就是,用户在操作时,可能将父节点取消勾选,在提交时就只剩下子节点了,所以需要在提交时再进行一次校验,将未加入(用户手动取消)的父节点再次加入。
| const handleAddRoleMenu = () => { getAllNodes(treeRef.value!.getCheckedNodes(false, false)) const nodeList = treeRef.value!.getCheckedKeys(false) }
const getAllNodes = (arr: Array) => { arr.forEach((item) => { changeCheck(item) }) }
|
提交时保证了所有被选择节点,及其父节点都被加入。
全选/全不选
| <el-switch v-model="changeSelectV" :active-value="true" :inactive-value="false" @change="changeSelect" />
|
| const changeSelect = () => { if (changeSelectV.value == true) { treeRef.value!.setCheckedNodes(state.menuTree) } else { treeRef.value!.setCheckedNodes([]) } }
|
当tree 设置了check-strictly
,通过 setCheckedNodes 全选方法失效,父子不关联,只能选中一级父节点,只能用遍历的方式选中节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const changeSelect = () => { if (changeSelectV.value == true) { travelNodes(state.menuTree) } else { treeRef.value!.setCheckedNodes([]) } }
const travelNodes = (tmpRoot) => { if (Array.isArray(tmpRoot)) { tmpRoot.forEach((item) => { treeRef.value!.setChecked(item.id, true, true) if (item.children && item.children.length > 0) { travelNodes(item.children) } }) } }
|
全部展开/收起
| <el-switch v-model="changeCollapseV" :active-value="true" :inactive-value="false" @change="changeCollapse" />
|
| const changeCollapse = () => { travelExpend(state.menuTree, changeCollapseV.value) }
const travelExpend = (branch: Array, expend: boolean) => { branch.forEach((item) => { treeRef.value!.store.nodesMap[item.id].expanded = expend if (item.children) { travelExpend(item.children, expend) } }) }
|