兼容H5、安卓App、微信小程序
实现逻辑:给默认选中节点的所有子节点添加一个disabled属性,以此禁用子节点。
/components/sonTreeNode/sonTreeNode.vue 封装成组件
<template>
<view>
<view :class="['item',item.is_level==1?'pL1':item.is_level==2?'pL2':'pL3']" v-for="(item, index) in treeList"
:key="index">
<view class="item--row" @click.stop="handleOpenClose(item, index)">
<view class="icon-box">
<u-icon :name="item.isOpen?'arrow-down-fill':'arrow-up-fill'" size="12" color="#a8abb2"
v-if="item.children && item.children.length"></u-icon>
</view>
<view class="checkbox-box">
<u-checkbox-group>
<u-checkbox :disabled="item.disabled" :activeColor="themeColor" :label="item.name"
:name="item.id" :checked='item.checked' usedAlone @change="changeCheckbox($event,item)" />
</u-checkbox-group>
</view>
</view>
<!-- 使用组件本身渲染子项 -->
<view v-if="item.isOpen && item.children && item.children.length">
<treeItem :list="item.children">
</treeItem>
</view>
</view>
</view>
</template>
<script>
// 引入当前组件
import treeItem from '../sonTreeNode/sonTreeNode'
let activeTreeList = []
export default {
name: 'treeItem',
components: {
treeItem
},
// 接收列表数据
props: {
list: {
type: Array,
default: () => []
},
checkedId: {
type: Array,
default: () => []
},
},
data() {
return {
themeColor: this.$themeColor,
treeList: [],
}
},
mounted() {
this.setListUpOpen(this.list)
this.treeList = this.list
if (activeTreeList.length == 0) {
activeTreeList = this.list
}
},
methods: {
// 全部展开
setListUpOpen(list, isOpen = true) {
list.forEach(item => {
item.isOpen = isOpen
// 数据回显,选中当前checked和禁用子节点 Start
if (this.checkedId.includes(item.id)) {
item.checked = true
function setSonDisabled(son) {
son.forEach(v => {
v.disabled = true
if (v?.children?.length > 0) {
setSonDisabled(v.children)
}
})
}
if (item?.children?.length > 0) {
setSonDisabled(item.children)
}
}
// End
if (item?.children?.length > 0) {
this.setListUpOpen(item.children)
}
})
return list
},
// 处理展开或收起
handleOpenClose(item, index) {
// 如果不存在isOpen属性就添加该属性。
if (!item.hasOwnProperty('isOpen')) {
item.isOpen = false
}
item.isOpen = !item.isOpen
this.$forceUpdate()
},
// 禁用子节点
disableNode(node, disabled) {
node.forEach(item => {
item.checked = false
item.disabled = disabled
if (item?.children?.length > 0) {
this.disableNode(item.children, disabled)
}
})
return node
},
setAssign(node, child) {
node.forEach(item => {
child.forEach(v => {
if (item.id == v.id) {
if (v.hasOwnProperty('checked')) {
item.checked = v.checked
}
item.children = v.children
}
})
if (item?.children?.length > 0) {
this.setAssign(item.children, child)
}
})
return node
},
changeCheckbox(isChecked, item) {
let isHasChild = item?.children?.length > 0
let oldTreeList = this.treeList
if (isChecked) {
item.checked = true
if (isHasChild) {
this.disableNode(item.children, true)
}
} else {
item.checked = false
if (isHasChild) {
this.disableNode(item.children, false)
}
}
activeTreeList = this.setAssign(activeTreeList, oldTreeList)
if (isHasChild) {
// #ifdef H5 ||APP-PLUS
this.treeList = []
this.$nextTick(() => {
this.treeList = oldTreeList
})
// #endif
// #ifdef MP-WEIXIN
this.loadTreeList()
// #endif
}
},
getActiveTreeList() {
return activeTreeList
},
cleatActiveTreeList() {
activeTreeList = []
},
}
}
</script>
<style scoped lang="scss">
.pL1 {
padding-left: 10rpx;
}
.pL2 {
padding-left: 20rpx;
}
.pL3 {
padding-left: 30rpx;
}
.item {
margin-bottom: 15rpx;
.item--row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.icon-box {
width: 30rpx;
}
.checkbox-box {}
}
}
</style>
data.js 定义树形结构数据
const treeList = [{
"id": 8,
"name": "2栋",
"pid": 0,
"children": [{
"id": 31,
"name": "C单元",
"pid": 8,
"children": []
},
{
"id": 30,
"name": "B单元",
"pid": 8,
"children": []
},
{
"id": 13,
"name": "A单元",
"pid": 8,
"children": []
}
]
},
{
"id": 9,
"name": "3栋",
"pid": 0,
"children": [{
"id": 27,
"name": "B单元",
"pid": 9,
"children": [{
"id": 28,
"name": "6楼",
"pid": 27,
}]
},
{
"id": 14,
"name": "A单元",
"pid": 9,
"children": []
}
]
},
{
"id": 11,
"name": "4栋",
"pid": 0,
"children": [{
"id": 29,
"name": "B单元",
"pid": 11,
"children": []
},
{
"id": 18,
"name": "A单元",
"pid": 11,
"children": [{
"id": 53,
"name": "22222",
"pid": 18,
}]
}
]
},
{
"id": 7,
"name": "1栋",
"pid": 0,
"children": [{
"id": 67,
"name": "A单元",
"pid": 7,
"children": []
},
{
"id": 66,
"name": "B单元",
"pid": 7,
"children": []
},
{
"id": 65,
"name": "C单元",
"pid": 7,
"children": []
},
]
}
]
export default treeList
页面文件
<template>
<view class="">
<u-button type="primary" @click="openPopup()">打开弹窗</u-button>
<u-popup :show="showPopup" mode="bottom" :round="20" closeable @close="closePopup" :closeOnClickOverlay="false">
<view class="popup-wrap">
<view class="popup-title">
选择子项目
</view>
<view class="popup-content">
<sonTreeNode :list="treeList" ref="sonTreeNodeRef" :checkedId="checkedId"
v-if="treeList.length>0" />
</view>
<view class="popup-footer">
<view class="btn-box1">
<u-button @click="closePopup()">取消</u-button>
</view>
<view class="btn-box2">
<u-button type="primary" @click="confirmPopup()">确定</u-button>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import Vue from 'vue'
import sonTreeNode from '@/packageD/components/sonTreeNode/sonTreeNode.vue'
import treeList from "./data.js"
export default {
components: {
sonTreeNode
},
data() {
return {
treeList: [],
showPopup: false,
checkedId: [13, 18, 7, 28]
};
},
onLoad() {},
onUnload() {
// #ifdef MP-WEIXIN
this.clearInstance()
// #endif
},
mounted() {
// #ifdef MP-WEIXIN
Vue.prototype.loadTreeList = this.loadTreeList;
// #endif
},
methods: {
clearInstance() {
// 清除实例的逻辑
this.$delete(Vue.prototype, 'loadTreeList');
},
loadTreeList() {
this.$refs.sonTreeNodeRef.treeList = []
this.$nextTick(() => {
this.$refs.sonTreeNodeRef.treeList = this.$refs.sonTreeNodeRef.getActiveTreeList()
})
},
openPopup(item, index) {
this.treeList = treeList
this.showPopup = true
},
confirmPopup() {
let treeList = this.$refs.sonTreeNodeRef.treeList
console.log("选中的id=", this.getCheckedIdArr(treeList));
this.checkedId = this.getCheckedIdArr(treeList)
this.closePopup()
},
closePopup() {
this.$refs.sonTreeNodeRef.cleatActiveTreeList()
this.showPopup = false
this.treeList = []
},
getCheckedIdArr(node, arr = []) {
node.forEach(item => {
if (item.checked) {
arr.push(item.id)
}
if (item?.children?.length > 0) {
this.getCheckedIdArr(item.children, arr)
}
})
return arr
},
},
}
</script>
<style lang="scss" scoped>
.popup-wrap {
padding: 20rpx 40rpx;
.popup-title {
font-size: 36rpx;
text-align: center;
}
.popup-content {
margin-top: 20rpx;
height: 800rpx;
overflow-y: auto;
}
.popup-footer {
display: flex;
justify-content: space-between;
.btn-box1,
.btn-box2 {
width: 48%;
}
}
}
</style>
效果图