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

核心代码如下:

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
相关推荐
前端小配角13 小时前
React难上手原因找到了,原来是因为坑太多了。。。
前端
是你的小橘呀13 小时前
零基础也能懂!React Hooks实战手册:useState/useEffect上手就会,告别类组件
前端·架构
xhxxx13 小时前
从样式到结构:TailwindCss + Fragment 如何让 React 代码更干净、更高效
前端·css·react.js
Maxkim14 小时前
「✍️JS原子笔记 」深入理解JS数据类型检测的4种核心方式
前端·javascript·面试
小高00714 小时前
Elips-Core:轻量级 Node.js Web 框架核心实现
前端·javascript·node.js
Focus_14 小时前
SSE+broadcastChannel
前端
zabr14 小时前
前端已死?我用 Trae + Gemini 零代码手搓 3D 塔罗牌,找到了新出路
前端·人工智能·aigc
Aotman_14 小时前
Vue MutationObserver 监听
前端·javascript·vue.js·elementui·前端框架·ecmascript
橙汁味的风14 小时前
2EM算法详解
人工智能·算法·机器学习
专注前端30年14 小时前
Vue3的生命周期钩子有哪些变化?
前端·javascript·vue.js