实现的效果图如下:
如Ant Design Vue 中所示,并没有提供获取半选节点的方法,当设置checked和checkStrictly时,父子节点也不再自动关联了
前提:从后端可以获取的数据分别是完整的树型数据、所有选中的节点数据(一个数组、同时包含 父节点和子节点),具体的大概数据可以看下面
树形结构(二重),parId是-1则表示是父节点数据:
这是返回的已选中节点的数组:
下面是具体的代码:
state = {
treeData: [], // 树形数据
checkedKeys: [],
checkable: false, // 因为我的页面是树型结构在页面右侧,根据左侧显示具体(看自己需求)
loading: true,
halfChecked: [], // 半选
checked: [] // 全选
};
render() {
const { checkedKeys, treeData, checkable, halfChecked, checked, loading } = this.state
<Tree
ref={r => this.treeRef = r}
className={styles.menuTree}
blockNode
showLine
// selectable={false}
checkable={checkable}
defaultExpandAll
// checkedKeys={checkedKeys}
checkedKeys={{
checked,
halfChecked
}}
checkStrictly
onCheck={({ checked: ck, halfChecked: hc, ...oth }, { checked, node: { props: { dataRef } } }) => {
// 选择
if (checked && dataRef.id) {
ck = ck.filter(i => i != `${dataRef.id}`).concat(`${dataRef.id}`);
}
// 如果有子级,则全部选上
if(dataRef.parId == '-1'&&dataRef.childList&&dataRef.childList.length > 0){
const kidKeys = this.getCKidKey(dataRef.childList);
if(checked){
ck = ck.concat(kidKeys);
}else{
ck = ck.filter(k=>{
const bo= !kidKeys.includes(k);
return bo;
})
}
}
// 如果选中的是子级,其父级也默认选中;取消选中时如果其父级下无选中内容,父级取消选中
if(dataRef.parId != '-1'){
if(checked){
ck.push(dataRef.parId.toString())
ck = Array.from(new Set(ck));
}else{
const ckId = dataRef.id; // 当前选中子级的id
const ckParId = dataRef.parId; // 当前选中de子级的父级id
const childList = this.treeMap[ckParId]?.childList; // 当前选中子级的父级 包含的子级
let isHave = false // 父级下的子级是否有选中的,默认无选中的
childList.forEach((item => {
const ass = ck.includes(item.id.toString())
if(ass) { // 如果还有选中的
isHave = true
return
}
}))
if(!isHave){
ck = ck.filter(item => item != ckParId)
}
}
}
const lastAllData = Array.from(new Set([...ck, ...hc]))
this.parentStatus(lastAllData);
}}
>
{this.renderTreeNodes(treeData)}
</Tree>
}
主要是onCheck
方法里面的处理,下面是用到的一些方法
// 所有已选节点分成两组,全选、半选。
parentStatus = (checked) => { // 这里的checked是指传入所有已选节点
const { treeData } = this.state;
const pData = [] // 半选的父级id数组
const allPData = [] // 全选的父级id数组
checked.forEach(i => {
treeData.forEach(j =>{
if(i == j.id){ // 如果有选中的父级
const ckPList = [] // 选中父级的子级数组
j.childList.forEach(r =>{
ckPList.push(r.id.toString())
})
if(this.isContained(checked, ckPList)){
allPData.push(j.id.toString())
}else{
pData.push(i.toString())
}
}
})
})
const halfCkData = [] // 半选状态数据
const allCkData = [] // 全选状态数据
for(const i of checked){
pData.includes(i)&&halfCkData.push(i);
!pData.includes(i)&&allCkData.push(i);
}
this.setState({
checked: allCkData,
halfChecked: halfCkData,
})
}
// 判断一个数组是否包含了另一个数组的全部元素
isContained = (a, b) => {
// a和b其中一个不是数组,直接返回false
if (!(a instanceof Array) || !(b instanceof Array)) return false;
const len = b.length;
// a的长度小于b的长度,直接返回false
if (a.length < len) return false;
for (let i = 0; i < len; i++) {
if (!a.includes(b[i])) return false;
}
return true;
};
//
ckeys = []
getKidKey = kids => {
kids.reduce((p, c, ci, arr) => {
p.push(c.key);
if (c.children) {
this.getKidKey(c.children);
}
return p;
}, this.ckeys);
}
getCKidKey = kids => {
this.getKidKey(kids);
const cks = [...this.ckeys];
this.ckeys = []
return cks;
}
ps因为代码是随着需求优化慢慢增加的,所以命名可能有点乱,方法也是比较杂又多,写出来了就随它了,懒得优化就这样了。如果有帮助到你的话就很nice啦~