0%

树组件 - 交互小结

开发环境

  • 语言:vue@^2.6.10
  • UI框架及版本:ant-design-vue@1.7.4

开发背景

  后台管理系统,颗粒度需要管理到按钮,管理系统的结构是:一级目录>二级目录>按钮,有时候需要给不同用户看到不同程度的界面,比如A只需要被提供页面查看按钮,也就是只开放到二级目录就可以;比如B需要进行一些操作,就需要开放到按钮层。用户可以不用这些功能,但作为开发不能不会;而且基于项目已经是这种数据结构和交互诉求,那唯一能进行变通的,也就是前端在UI组件默认的交互下,自己重新定义各种交互方式咯。

iShot_2022-11-15_11.35.53.png

需要达成的交互

  1. 去掉半选中的交互,改成完全选中和完全取消
  2. 选中父亲节点,该父节点下所有子节点需被勾选中;取消父亲节点,该父节点下所有子节点需被取消
  3. 选中某一个子节点,该子节点所有的父亲节点都需要被选中
  4. 取消某一子节点,逐层校验上一级的父节点是否需要被选中(极限情况是,只选了一个子节点,当取消这个子节点,则整课树都是处于未被选中的状态)(这次的功能不需要这个功能,用户需不需要这个功能是用户的事,自己能不能实现这种交互那是自己的态度和能力的问题,两码事)
    iShot_2022-11-15_14.42.21.gif

思路

  将树结构还原成数据库权限表里一条一条的数据,换句话说,就是先创建一个空数组totalNavIds,将所有父节点以及该父节点下的所有子节点,都存入该空数组totalNavIds中,方便后期实现交互3

  接着,将树组件改成完全受控(父子节点选中状态不再关联),然后自己定义”点击复选框触发”的方法实现交互1交互2

难点

  主要是理解上出现过歧义,之前因为不能直接套用UI树组件的半选中交互,就很头疼(因为后端需要提交父节点,后端不可能完全按照前端需要的单独提供选中和半选中的数据结构吧,前端的UI框架的数据结构多有不同,后端一般会提供更为灵活的接口,看前端自己来处理不同的数据结构才对),另外是交互4这个要自下而上的进行校验父节点是不是要被取消,前端计算量比较大,但应该是能够实现,现在还没有实现)

实现的关键代码

1
2
3
4
5
6
7
8
9
10
11
<a-tree
:disabled="disabled"
v-model="navIds"
:checkStrictly="true"
:replaceFields="{ key: 'id', children: 'navList', title: 'name' }"
:show-line="true"
:tree-data="navList"
checkable
@check="(checkedKeys, info) => onCheckNav(checkedKeys, info)"
/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取所有的的菜单权限
async fetchNavData() {
this.loading = true
let resultInfo = await Service.getAllNavList()
this.loading = false
if (!resultInfo.result) return
let { navList } = resultInfo.data || ''
// PC端
this.navList = navList || []
// - 子节点反选 父节点 -
// PC端 递归 找出所有的节点 ,放在一个数组中
navList.forEach(item => {
this.handleTotalNavList(item)
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// PC 端 check 事件
onSelectNav(selectedKeys, info) {
this.navIds = selectedKeys.checked
let data = info.node.dataRef
// 判断勾选的状态
let flag = info.checked
let { parentId } = data
this.currentNavNode = data
if (flag) {
// 勾选
this.setNavParentId(parentId)
this.selectNavAll()
} else {
// 取消勾选
this.cancelSelectNavAll()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 子节点反选 父节点
setNavParentId(id) {
if (!id) return
let { totalNavIds } = this
this.navIds.push(id)
let parentNode = totalNavIds.filter(item => {
return item.id === id
})[0]
if (parentNode) {
this.setNavParentId(parentNode.parentId)
}
}
1
2
3
4
5
6
7
8
9
10
// PC 端 针对某一节点 全选
selectNavAll() {
// console.log('selectNavAll=====')
let { navIds, currentNavNode } = this
let { id, navList } = currentNavNode
if (navIds.indexOf(id) < 0) {
navIds.push(id)
}
this.setNavChildId(navList)
},
1
2
3
4
5
6
7
8
9
10
11
12
// PC 端 针对某一节点 取消全选
cancelSelectNavAll() {
// console.log('cancelSelectNavAll=====')
let { navIds, currentNavNode } = this
let { id, navList } = currentNavNode
let index = navIds.indexOf(id)
if (index >= 0) {
navIds.splice(index, 1)
}
// 递归这个节点下面的所有子节点,并且取消选中
this.clearNasChildId(navList)
}
1
2
3
4
5
6
7
8
9

handleTotalNavList(node) {
this.totalNavIds.push(node)
if (node.navList && node.navList.length > 0) {
node.navList.forEach(item => {
this.handleTotalNavList(item)
})
}
}

参考资料

树形数据前端生成还是后台生成返回比较好?2022.10.21