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>
    
posted @ 2025-06-17 16:11  不完美的完美  阅读(329)  评论(0)    收藏  举报