uni-app写法在微信小程序实现tree树形结构实现复选效果
-
uni-app + 微信小程序
-
vue 写法
-
实现一个 tree 树形,勾选父元素时将子元素禁用掉,并且取消已勾选子元素
-
效果图
![]()
-
代码
-
组件 components/tree.vue
-
tree.vue
<template> <view class="tree-node" :style="{ paddingLeft: indent + 'px' }"> <!-- 节点内容 --> <view class="flex" > <!-- 展开/折叠图标 --> <view v-if="hasChildren" class="toggle-icon" @click="toggleExpand" > {{ node.expanded ? '▼' : '▶' }} </view> <!-- 复选框 --> <checkbox class="checkbox" :checked="node.checked" color="#ff9e19" :disabled="node.disabled" @click.stop="handleCheck" /> <view v-if="node.halfChecked" class="half-check"></view> <!-- 节点标签 --> <view>{{ node.name}}</view> </view> <!-- 子节点递归 --> <view v-if="node.expanded && hasChildren"> <tree v-for="(child, index) in node.children" :key="child.id" :node="child" @check="$emit('check', $event)" @expand="$emit('expand', $event)" :depth="depth + 1" /> </view> </view> </template> <script> import tree from './tree.vue'; export default { name: "Tree", components: { tree }, props: { node: Object, depth: { type: Number, default: 0 }, }, computed: { indent() { return this.depth * 20; // 每层缩进20px }, hasChildren() { return this.node.children && this.node.children.length > 0; } }, methods: { // 切换展开/折叠 toggleExpand() { this.$set(this.node, 'expanded', !this.node.expanded); this.$emit('expand', this.node); }, handleCheck() { this.$emit('check',this.node) }, } }; </script> <style > .tree-node { padding: 10rpx 0; } .checkbox { margin: 0 15rpx; } .toggle-icon { width: 40rpx; text-align: center; cursor: pointer; } </style> -
index 页面
-
index.vue
<template> <view class="container"> <tree v-for="node in treeData" :key="node.id" :node="node" :depth="0" @expand="handleExpand" @check="handleNodeCheck" /> </view> </template> <script> import Tree from '@/components/tree.vue'; export default { components: { Tree }, data() { return { treeData: [{ id: 'root', name: '根节点', checked: false, expanded: true, indeterminate: false, children: [ { id: 'child1', name: '子节点1', checked: false, expanded: true, indeterminate: false, children: [ { id: 'grandchild1', name: '孙节点1', checked: false, indeterminate: false, }, { id: 'grandchild2', name: '孙节点2', checked: false, indeterminate: false, } ] }, { id: 'child2', name: '子节点2', checked: false, indeterminate: false, } ] }], checkedNodes: [], // 选中节点 }; }, onLoad() { this.getData(); }, methods: { getData() { const tmp = this.addCheckedField(this.treeData); this.treeData = tmp; }, addCheckedField(tree) { tree.forEach(node => { node.checked = false; // 初始化checked为false node.halfChecked = false; node.expanded = true; node.disabled = false; node.copyChecked = false; if (node.children) { // 如果节点有子节点,递归调用此函数 this.addCheckedField(node.children); } }); return tree; }, handleNodeCheck(node) { this.updateCheckState(node); this.checkedNodes = this.getCheckedNodes(this.treeData); }, // 处理展开/折叠事件 handleExpand(node) { this.findAndUpdateNode(this.treeData, node,'expanded'); }, // 递归更新节点状态 findAndUpdateNode(tree, targetNode ,key) { for (let i = 0; i < tree.length; i++) { const node = tree[i]; if (node.id === targetNode.id) { this.$set(node, key, targetNode[key]); // 使用 Vue.set 保证响应性 return true; } if (node.children && node.children.length > 0) { if (this.findAndUpdateNode(node.children,targetNode, key)) { return true; } } } return false; }, // 更新选中状态 updateCheckState(node) { const newCheck = !node.checked node.checked = !node.checked; node.copyChecked = newCheck; node.halfChecked = false; this.findAndUpdateNode(this.treeData, node,'checked'); this.findAndUpdateNode(this.treeData, node,'copyChecked'); // // 向上更新父节点 // let parent = this.findParent(this.treeData, node); // while (parent) { // this.updateParent(parent); // parent = this.findParent(this.treeData, parent); // } // 向下更新子节点 this.checkChildren(node); }, // 更新父节点状态 updateParent(parent) { const checkedCount = parent.children?.filter(child => child.checked).length || 0; const total = parent.children?.length || 0; if (checkedCount === total) { parent.checked = true; parent.halfChecked = false; // this.$set(parent, 'checked', true); } else if (checkedCount > 0) { parent.checked = false; parent.halfChecked = true; // this.$set(parent, 'checked', false); } else { parent.checked = false; // this.$set(parent, 'checked', false); parent.halfChecked = false; } }, // 查找父节点 findParent(rootArray, node) { let parent = null; const traverse = (nodes) => { nodes.forEach((parentCandidate) => { if (parentCandidate.children?.some(child => child.id === node.id)) { parent = parentCandidate; return; } if (parentCandidate.children) { traverse(parentCandidate.children); } }); }; traverse(rootArray); return parent; }, // 递归检查子节点 checkChildren(node) { if (node.children) { node.children.forEach(child => { // child.checked = node.checked; child.copyChecked = node.copyChecked; child.disabled = node.copyChecked; if(node.copyChecked) { child.checked = false; this.findAndUpdateNode(this.treeData, child,'checked'); } this.findAndUpdateNode(this.treeData, child,'copyChecked'); this.findAndUpdateNode(this.treeData, child,'disabled'); child.halfChecked = false; this.checkChildren(child); }); } }, // 获取所有选中节点(修改为遍历数组) getCheckedNodes(nodes) { let result = []; nodes.forEach(node => { if (node.checked) result.push(node); if (node.children) { result = result.concat(this.getCheckedNodes(node.children)); } }); return result; }, } }; </script> <style> .container { padding: 20rpx; } </style> -
父元素和子元素联动效果,勾选、取消勾选父元素,子元素随着勾选,取消勾选,全选子元素自动勾选父元素
-
效果图
![]()
-
index.vue
<template> <view class="container"> <tree v-for="node in treeData" :key="node.id" :node="node" :depth="0" @expand="handleExpand" @check="handleNodeCheck" /> </view> </template> <script> import Tree from '@/components/tree.vue'; export default { components: { Tree }, data() { return { treeData: [{ id: 'root', name: '根节点', checked: false, expanded: true, indeterminate: false, children: [ { id: 'child1', name: '子节点1', checked: false, expanded: true, indeterminate: false, children: [ { id: 'grandchild1', name: '孙节点1', checked: false, indeterminate: false, }, { id: 'grandchild2', name: '孙节点2', checked: false, indeterminate: false, } ] }, { id: 'child2', name: '子节点2', checked: false, indeterminate: false, } ] }], checkedNodes: [], // 选中节点 }; }, onLoad() { this.getData(); }, methods: { getData() { const tmp = this.addCheckedField(this.treeData); this.treeData = tmp; }, addCheckedField(tree) { tree.forEach(node => { node.checked = false; // 初始化checked为false node.halfChecked = false; node.expanded = true; if (node.children) { // 如果节点有子节点,递归调用此函数 this.addCheckedField(node.children); } }); return tree; }, handleNodeCheck(node) { this.updateCheckState(node); this.checkedNodes = this.getCheckedNodes(this.treeData); console.log(this.checkedNodes) console.log(this.treeData) }, // 处理展开/折叠事件 handleExpand(node) { this.findAndUpdateNode(this.treeData, node,'expanded'); }, // 递归更新节点状态 findAndUpdateNode(tree, targetNode ,key) { for (let i = 0; i < tree.length; i++) { const node = tree[i]; if (node.id === targetNode.id) { this.$set(node, key, targetNode[key]); // 使用 Vue.set 保证响应性 return true; } if (node.children && node.children.length > 0) { if (this.findAndUpdateNode(node.children,targetNode, key)) { return true; } } } return false; }, // 更新选中状态 updateCheckState(node) { const newCheck = !node.checked node.checked = !node.checked; node.halfChecked = false; this.findAndUpdateNode(this.treeData, node,'checked'); // 向上更新父节点 let parent = this.findParent(this.treeData, node); while (parent) { this.updateParent(parent); parent = this.findParent(this.treeData, parent); } // 向下更新子节点 this.checkChildren(node); }, // 更新父节点状态 updateParent(parent) { const checkedCount = parent.children?.filter(child => child.checked).length || 0; const total = parent.children?.length || 0; if (checkedCount === total) { parent.checked = true; parent.halfChecked = false; // this.$set(parent, 'checked', true); } else if (checkedCount > 0) { parent.checked = false; parent.halfChecked = true; // this.$set(parent, 'checked', false); } else { parent.checked = false; // this.$set(parent, 'checked', false); parent.halfChecked = false; } }, // 查找父节点 findParent(rootArray, node) { let parent = null; const traverse = (nodes) => { nodes.forEach((parentCandidate) => { if (parentCandidate.children?.some(child => child.id === node.id)) { parent = parentCandidate; return; } if (parentCandidate.children) { traverse(parentCandidate.children); } }); }; traverse(rootArray); return parent; }, // 递归检查子节点 checkChildren(node) { if (node.children) { node.children.forEach(child => { child.checked = node.checked; child.halfChecked = false; // this.$set(child, 'checked', node.checked); this.findAndUpdateNode(this.treeData, child,'checked'); this.checkChildren(child); }); } }, // 获取所有选中节点(修改为遍历数组) getCheckedNodes(nodes) { let result = []; nodes.forEach(node => { if (node.checked) result.push(node); if (node.children) { result = result.concat(this.getCheckedNodes(node.children)); } }); return result; }, } }; </script> <style> .container { padding: 20rpx; } </style>



浙公网安备 33010602011771号