需求:子级关联父级,父级不关联子级
主要需要花费时间去实现的是组织树的check勾选方法以及数据回显(全选、半选)
一、组织树勾选
kotlin
check(checkedKeys, e) {
const { checked, halfChecked, dataRef, eventKey } = e.node
const { checked: selectChecked, halfChecked: halfSelectChecked } = checkedKeys
console.log('checked:', checked, 'halfChecked:', halfChecked, e)
// 判断有无子节点
if (dataRef?.children && dataRef?.children?.length) {
// 有子节点
if (!checked && !halfChecked) {
// 半选
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
// 两个数组取交集,相同的部分取出来
const remindData = selectChecked.filter((v) => nodes.indexOf(v) === -1)
halfSelectChecked.push(eventKey)
this.checkedKeys = {
checked: remindData.filter((v) => v !== eventKey), // 把该key从全选里面去掉
halfChecked: [...halfSelectChecked, ...nodes]
}
} else if (!checked && halfChecked) {
// 全选
const childNodesFirst = this.getChildNode(this.treeData, eventKey, [])
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
let selectChecked1 = selectChecked
let halfSelectChecked1 = halfSelectChecked
let isSame
if (nodes && nodes?.length > 0) {
nodes.map((item) => {
// 获取item下的所有子节点且包括item
const childNodes = this.getChildNode(this.treeData, item, [])
// 子节点全选时,点击全选状态变成空状态的交互
const selectChecked2 = selectChecked1
isSame = this.includes(
[...childNodesFirst, ...selectChecked1],
childNodes.filter((v) => v !== item)
) // 比较两个数组是否相同,后面的数组有一个没有在前面数组里面就false
selectChecked2.push(item)
selectChecked1 = isSame ? selectChecked2 : selectChecked1.filter((v) => v !== item)
halfSelectChecked1 = halfSelectChecked
halfSelectChecked1.push(item)
return item
})
}
this.checkedKeys = {
checked: selectChecked1,
halfChecked: isSame ? halfSelectChecked1 : halfSelectChecked
}
// 父节点全选
this.checkedKeys = {
checked: [...childNodesFirst, ...selectChecked1],
halfChecked: [...halfSelectChecked]
}
} else {
// 全不选
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
const childNodes = this.getChildNode(this.treeData, eventKey, [])
// 两个数组取交集,相同的部分取出来
const remindData = selectChecked.filter((v) => childNodes.indexOf(v) === -1)
const remindData2 = remindData.filter((v) => nodes.indexOf(v) === -1)
// 把叶子节点的最近一级的父节点的半选去掉
const remindHalfData = halfSelectChecked.filter((v) => childNodes.indexOf(v) === -1)
this.checkedKeys = {
checked: [...remindData2],
halfChecked: [...remindHalfData, ...nodes] // 第一次在全选状态点击二级节点时,父节点变成空状态不是半选状态
}
}
} else {
// 无子节点
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
let selectChecked1 = [...new Set(selectChecked)]
let halfSelectChecked1 = halfSelectChecked
let isSame
let selectChecked2 = [] // 叶子节点全选反向选择的变量
if (nodes && nodes?.length > 0) {
nodes.forEach((item) => {
// 获取item下的所有子节点且包括item
const childNodes = this.getChildNode(this.treeData, item, [])
// 子节点全选时,点击全选状态变成空状态的交互
// const selectChecked2 = [...selectChecked1, ...nodes]
// 这种会导致所有父节点都变成全选状态,但是实际爷节点是半选状态的时候,不需要变成全选
isSame = this.includes(
[...selectChecked, ...nodes], // 爷节点在孙子节点全选时没有变成全选状态
childNodes.filter((v) => v !== item)
) // 比较两个数组是否相同,后面的数组有一个没有在前面数组里面就false
// 子节点全选时只把当前父节点变成全选,爷节点不需要变成全选
selectChecked2 = isSame
? [...selectChecked1, ...[item]]
: selectChecked1.filter((v) => v !== item)
selectChecked1 = selectChecked2
halfSelectChecked1 = halfSelectChecked
halfSelectChecked1.push(item)
return item
})
this.checkedKeys = {
checked: selectChecked1,
halfChecked: isSame ? halfSelectChecked1 : halfSelectChecked
}
}
}
const checkedList = this.$lodash.uniq([
...this.checkedKeys.checked,
...this.checkedKeys.halfChecked
])
this.$emit('onCheck', checkedList)
}
数据回显
监听拿到需要回显的数据为一个数组,拿到当前组织树对应回显每个数据的所有子节点为一个数组,判断两个数组是否有交集,若有交集则为全选,无交集即半选。
javascript
watch: {
roleResources: {
handler(newVal) {
// this.checkedKeys = newVal
const arr = newVal
const checked = []
const halfChecked = []
arr.forEach((item) => {
const value = this.getChildNode(this.treeData, item, []) // 获取item的所有子节点
const isSame = this.includes(arr, value) // 判断两个数组是否有交集
if (isSame) {
checked.push(item) // true即勾选
} else {
halfChecked.push(item) // false即半选
}
})
this.checkedKeys = { checked, halfChecked }
},
immediate: true,
deep: true
}
},
完整代码
vue:
kotlin
<template>
<a-tree
v-model="checkedKeys"
checkable
:checkStrictly="true"
:checkedKeys="checkedKeys"
:expanded-keys="expandedKeys"
:auto-expand-parent="autoExpandParent"
:selected-keys="selectedKeys"
:tree-data="treeData"
:replaceFields="replaceFields"
@check="check"
@expand="onExpand"
@select="onSelect"
class="role-ant-tree"
/>
</template>
<script>
import roleTreeChecked from '@/mixins/roleTreeChecked'
export default {
name: 'RoleTree',
mixins: [roleTreeChecked],
props: {
treeData: Array,
roleResources: Array
},
data() {
return {
expandedKeys: [],
autoExpandParent: true,
checkedKeys: {
checked: [],
halfChecked: []
},
selectedKeys: [],
replaceFields: { key: 'id' },
childrenKeys: []
}
},
watch: {
roleResources: {
handler(newVal) {
// this.checkedKeys = newVal
const arr = newVal
const checked = []
const halfChecked = []
arr.forEach((item) => {
const value = this.getChildNode(this.treeData, item, []) // 获取item的所有子节点
const isSame = this.includes(arr, value) // 判断两个数组是否有交集
if (isSame) {
checked.push(item) // true即勾选
} else {
halfChecked.push(item) // false即半选
}
})
this.checkedKeys = { checked, halfChecked }
},
immediate: true,
deep: true
}
},
methods: {
onExpand(expandedKeys) {
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
this.expandedKeys = expandedKeys
this.autoExpandParent = false
},
onSelect(selectedKeys) {
this.selectedKeys = selectedKeys
},
handleChild(children) {
children.forEach((item) => {
this.childrenKeys.push(item.id)
if (item?.children && item.children?.length > 0) {
this.handleChild(item.children)
}
})
},
check(checkedKeys, e) {
const { checked, halfChecked, dataRef, eventKey } = e.node
const { checked: selectChecked, halfChecked: halfSelectChecked } = checkedKeys
console.log('checked:', checked, 'halfChecked:', halfChecked, e)
// 判断有无子节点
if (dataRef?.children && dataRef?.children?.length) {
// 有子节点
if (!checked && !halfChecked) {
// 半选
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
// 两个数组取交集,相同的部分取出来
const remindData = selectChecked.filter((v) => nodes.indexOf(v) === -1)
halfSelectChecked.push(eventKey)
this.checkedKeys = {
checked: remindData.filter((v) => v !== eventKey), // 把该key从全选里面去掉
halfChecked: [...halfSelectChecked, ...nodes]
}
} else if (!checked && halfChecked) {
// 全选
const childNodesFirst = this.getChildNode(this.treeData, eventKey, [])
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
let selectChecked1 = selectChecked
let halfSelectChecked1 = halfSelectChecked
let isSame
if (nodes && nodes?.length > 0) {
nodes.map((item) => {
// 获取item下的所有子节点且包括item
const childNodes = this.getChildNode(this.treeData, item, [])
// 子节点全选时,点击全选状态变成空状态的交互
const selectChecked2 = selectChecked1
isSame = this.includes(
[...childNodesFirst, ...selectChecked1],
childNodes.filter((v) => v !== item)
) // 比较两个数组是否相同,后面的数组有一个没有在前面数组里面就false
selectChecked2.push(item)
selectChecked1 = isSame ? selectChecked2 : selectChecked1.filter((v) => v !== item)
halfSelectChecked1 = halfSelectChecked
halfSelectChecked1.push(item)
return item
})
}
this.checkedKeys = {
checked: selectChecked1,
halfChecked: isSame ? halfSelectChecked1 : halfSelectChecked
}
// 父节点全选
this.checkedKeys = {
checked: [...childNodesFirst, ...selectChecked1],
halfChecked: [...halfSelectChecked]
}
} else {
// 全不选
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
const childNodes = this.getChildNode(this.treeData, eventKey, [])
// 两个数组取交集,相同的部分取出来
const remindData = selectChecked.filter((v) => childNodes.indexOf(v) === -1)
const remindData2 = remindData.filter((v) => nodes.indexOf(v) === -1)
// 把叶子节点的最近一级的父节点的半选去掉
const remindHalfData = halfSelectChecked.filter((v) => childNodes.indexOf(v) === -1)
this.checkedKeys = {
checked: [...remindData2],
halfChecked: [...remindHalfData, ...nodes] // 第一次在全选状态点击二级节点时,父节点变成空状态不是半选状态
}
}
} else {
// 无子节点
const nodes = this.getFathersById(eventKey, this.treeData, 'id').filter(
(v) => v !== eventKey
) // 获取当前节点的所有父节点且过滤自身
let selectChecked1 = [...new Set(selectChecked)]
let halfSelectChecked1 = halfSelectChecked
let isSame
let selectChecked2 = [] // 叶子节点全选反向选择的变量
if (nodes && nodes?.length > 0) {
nodes.forEach((item) => {
// 获取item下的所有子节点且包括item
const childNodes = this.getChildNode(this.treeData, item, [])
// 子节点全选时,点击全选状态变成空状态的交互
// const selectChecked2 = [...selectChecked1, ...nodes]
// 这种会导致所有父节点都变成全选状态,但是实际爷节点是半选状态的时候,不需要变成全选
isSame = this.includes(
[...selectChecked, ...nodes], // 爷节点在孙子节点全选时没有变成全选状态
childNodes.filter((v) => v !== item)
) // 比较两个数组是否相同,后面的数组有一个没有在前面数组里面就false
// 子节点全选时只把当前父节点变成全选,爷节点不需要变成全选
selectChecked2 = isSame
? [...selectChecked1, ...[item]]
: selectChecked1.filter((v) => v !== item)
selectChecked1 = selectChecked2
halfSelectChecked1 = halfSelectChecked
halfSelectChecked1.push(item)
return item
})
this.checkedKeys = {
checked: selectChecked1,
halfChecked: isSame ? halfSelectChecked1 : halfSelectChecked
}
}
}
const checkedList = this.$lodash.uniq([
...this.checkedKeys.checked,
...this.checkedKeys.halfChecked
])
this.$emit('onCheck', checkedList)
}
}
}
</script>
<style lang="scss">
.role-ant-tree {
height: 80%;
}
</style>
js(roleTreeChecked.js):
javascript
const childsNodeDeepWay = (nodes, arr) => {
if (nodes) {
nodes.forEach((ele) => {
arr.push(ele.id)
if (ele.children) {
childsNodeDeepWay(ele.children, arr)
}
})
}
}
export default {
methods: {
getChildNode(nodes, item, arr) {
nodes.forEach((el) => {
if (el.id === item) {
arr.push(el.id)
if (el.children) {
childsNodeDeepWay(el.children, arr)
}
} else if (el.children) {
this.getChildNode(el.children, item, arr)
}
})
return arr
},
getFathersById(id, data, prop = 'id') {
const arr = []
const rev = (dataList, IDS) => {
let flag = false
for (let i = 0, { length } = dataList; i < length; i += 1) {
const node = dataList[i]
if (node[prop] === IDS) {
arr.unshift(node[prop])
flag = true
} else if (node.children && node.children.length) {
if (rev(node.children, IDS)) {
arr.unshift(node[prop])
flag = true
}
}
}
return flag
}
rev(data, id)
return arr
},
includes(arr1, arr2) {
return arr2.every((val) => arr1.includes(val))
},
flatTree(treeData = []) {
let result = []
treeData.forEach((ele) => {
// 先克隆一份数据作为第一层级的填充
const arr = JSON.parse(JSON.stringify(ele))
delete arr?.children
result.push(arr)
if (ele?.children && ele?.children?.length > 0) {
result = result.concat(this.flatTree(ele.children))
}
})
return result
}
}
}