前端树形结构实现勾选,半勾选,取消勾选。

核心代码如下:

javascript 复制代码
    const handleCheckedTree = data => {
		if(!data) return [];
		data.forEach(node => {
		if (node.select === true) {
		  node.checked = true;
		}
		if (node.children && node.children.length > 0) {
		  handleCheckedTree(node.children);
		}
	  });
	  return data;
	}
	
	const toggleCheck = (node, checked) =>{
		//设置当前节点及其所有子节点的状态
		setNodeAndChildren(node, checked);
		//向上重新计算所有父节点的状态
		updateAllParents(node);
	}
	
	const setNodeAndChildren = (node, checked) => {
		// 清除半选状态
		if (node.indeterminate) {
		    node.indeterminate = false;
		}
		node.checked = checked;
		if (node?.children?.length) {
		    node.children.forEach(child => {
		      setNodeAndChildren(child, checked);
		    });
		}
	}
	
	const updateAllParents = startNode => {
		// 从当前节点开始,向上递归更新所有父节点
		function updateParent(node) {
		    // 如果没有父节点,停止递归
		    if (!node.parent) return;
		    
		    const parent = node.parent;
		    const children = parent.children || [];
		    
		    // 检查子节点的状态
		    let checkedCount = 0;
		    let indeterminateCount = 0;
		    let uncheckedCount = 0;
		    
		    children.forEach(child => {
		      if (child.indeterminate) {
		        indeterminateCount++;
		      } else if (child.checked) {
		        checkedCount++;
		      } else {
		        uncheckedCount++;
		      }
		    });
		    
		    // 设置父节点的状态
		    if (indeterminateCount > 0) {
		      // 有子节点是半选状态,父节点一定是半选
		      parent.checked = false;
		      parent.indeterminate = true;
		    } else if (checkedCount === children.length) {
		      // 所有子节点都选中
		      parent.checked = true;
		      parent.indeterminate = false;
		    } else if (uncheckedCount === children.length) {
		      // 所有子节点都没选中
		      parent.checked = false;
		      parent.indeterminate = false;
		    } else {
		      // 部分选中,部分未选中(但没有半选)
		      parent.checked = false;
		      parent.indeterminate = true;
		    }
		    
		    // 继续向上更新
		    updateParent(parent);
		}
		  
		// 从当前节点开始向上更新
		updateParent(startNode);
	}
	
	const initTreeBySelect = nodes => {
	  const buildTree = (nodeData, parent = null) => {
	    const node = {
	      ...nodeData,
	      parent: parent,
	      checked: false,
	      indeterminate: false,
	      children: []
	    };
	    
	    if (nodeData.children && nodeData.children.length > 0) {
	      node.children = nodeData.children.map(childData => buildTree(childData, node));
	    }
	    
	    return node;
	  }
	
	  const tree = nodes.map(nodeData => buildTree(nodeData));
	  
	  const calculateNodeState = node => {
	    if (!node.children || node.children.length === 0) {
	      node.checked = node.isSelect || false;
	      node.indeterminate = false;
	      return {
	        hasSelected: node.checked,
	        hasUnselected: !node.checked
	      };
	    }
	    
	    // 非叶子节点:先计算子节点
	    let hasSelectedChild = false;
	    let hasUnselectedChild = false;
	    let allChildrenSelected = true;
	    
	    node.children.forEach(child => {
	      const childResult = calculateNodeState(child);
	      
	      if (childResult.hasSelected) hasSelectedChild = true;
	      if (childResult.hasUnselected) hasUnselectedChild = true;
	      
	      // 检查子节点自身的checked状态
	      if (!child.checked && !child.indeterminate) {
	        allChildrenSelected = false;
	      }
	    });
	    
	    if (node.isSelect) {
	      // 如果父级选中,强制所有子级为选中状态
	      node.checked = true;
	      node.indeterminate = false;
	      
	      // 递归设置所有子节点为选中状态
	      const setAllChildrenChecked = (childNode) => {
	        childNode.checked = true;
	        childNode.indeterminate = false;
	        
	        if (childNode.children && childNode.children.length > 0) {
	          childNode.children.forEach(setAllChildrenChecked);
	        }
	      };
	      
	      node.children.forEach(setAllChildrenChecked);
	    } else {
	      // 父级未选中,根据子节点状态计算
	      if (hasSelectedChild && hasUnselectedChild) {
	        // 子节点部分选中,部分未选中
	        node.checked = false;
	        node.indeterminate = true;
	      } else if (hasSelectedChild && !hasUnselectedChild) {
	        // 所有子节点都选中(或没有子节点)
	        node.checked = true;
	        node.indeterminate = false;
	      } else if (!hasSelectedChild && hasUnselectedChild) {
	        // 所有子节点都未选中
	        node.checked = false;
	        node.indeterminate = false;
	      } else {
	        // 默认情况
	        node.checked = false;
	        node.indeterminate = false;
	      }
	    }
	    
	    return {
	      hasSelected: node.checked || node.indeterminate || hasSelectedChild,
	      hasUnselected: !node.checked || hasUnselectedChild
	    };
	  }
	  
	  // 计算每个根节点的状态
	  tree.forEach(root => calculateNodeState(root));
	  
	  return tree;
	};

使用方法

javascript 复制代码
//后端返回了一个list
treeData= initTreeBySelect(res?.data || []);


//然后点击复选框方法
//item是点击的项目,
toggleCheck(item,!item.checked)

//处理完成之后包含三种状态
未选择: checked:false  indeterminate:false
半选:checked:false  indeterminate:true
全选:checked:true   indeterminate:false
相关推荐
憧憬成为web高手5 小时前
ACTF 12307复现
前端·bootstrap·html
wordbaby6 小时前
Axios 上传大文件崩溃:鸿蒙 RNOH 下 XHR 返回空响应头引发的"假失败"
前端·react native
wordbaby6 小时前
React Native 列表分页实战:下拉刷新与上拉加载的工程化方案
前端·react native
wordbaby6 小时前
脱离 Tab 栏的艺术:React Native 全屏子页面的导航架构实践
前端·react native·harmonyos
陈随易7 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
wordbaby7 小时前
React Native 新架构落地鸿蒙:跨三端政务级应用的工程实践与深度复盘
前端·react native·harmonyos
excel8 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
ZC跨境爬虫8 小时前
模块化烹饪小程序开发日记 Day7:(菜谱详情接口开发与JSON数据读取全流程)
前端·javascript·css·ui·微信小程序·json
smj2302_796826529 小时前
解决leetcode第3943题递增后的数对数量
数据结构·python·算法·leetcode
এ慕ོ冬℘゜9 小时前
JS 前端基础面试题
开发语言·前端·javascript