效果:

子节点选中时候 displayedKeys 选中子节点和对应的父节点 id 数据回填的时候 会把父id过滤避免tree组件由于父id选中导致样式异常(父id选中之后默认会将全部子节点选中,但是这个时候选中的key子节点没有全部选中)

import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { Tree } from 'antd';
 
/**
 * LinkedMultiSelectTree 组件
 * 
 * @param {Object} props - 组件的配置项
 * @param {Array} props.treeData - 树形数据,格式为 { key, title, children? }
 * @param {Array} [props.value=[]] - 受控的选中节点 key 数组
 * @param {Function} [props.onChange] - 选中状态变化时的回调函数,接收新的选中节点的 key 数组
 * @param {Boolean} [props.parentLinkage=true] - 是否启用父节点联动,默认开启
 * @param {Function} [props.onDisplayedKeysChange] - 展示的节点(选中的节点和半选中的节点)变化时的回调函数
 * 
 * @returns {React.Element} 返回一个树形组件
 * 
 * @example
 * const treeData = [
 *   { key: '1', title: 'Node 1', children: [{ key: '1-1', title: 'Node 1-1' }] },
 *   { key: '2', title: 'Node 2' }
 * ];
 * 
 * const [selectedKeys, setSelectedKeys] = useState([]); // 不关联父节点的数据
 * const [displayedKeys, setDisplayedKeys] = useState([]); // 关联父节点的数据
 * 
 * const handleChange = (checkedKeys) => {
 *   console.log('选中的节点:', checkedKeys);
 * };
 * 
 * // 处理显示的权限键变化
 * const handleDisplayedKeysChange = useCallback((keys) => {
 *   setDisplayedKeys(keys);
 * }, []);
 * 
 * <LinkedMultiSelectTree
 *   treeData={treeData} // 权限树数据
 *   value={selectedKeys} // 当前选中的权限
 *   onChange={setSelectedKeys} // 权限选中变化
 *   onDisplayedKeysChange={handleDisplayedKeysChange} // 显示的权限键变化
 * />
 */
 
const LinkedMultiSelectTree = ({
  treeData,
  value = [],
  onChange,
  parentLinkage = true, // 是否开启父节点联动,默认开启
  onDisplayedKeysChange,
}) => {
  const { checkedKeys, indeterminateKeys, handleSelect } = useTreeCheckbox({
    treeData,
    value,
    onChange,
    parentLinkage,
  });
 
  // 使用 useRef 来存储上一次的 displayedKeys
  const prevDisplayedKeysRef = useRef([]);
 
  const displayedKeys = useMemo(() => {
    return parentLinkage
      ? Array.from(new Set([...checkedKeys, ...indeterminateKeys]))
      : checkedKeys;
  }, [checkedKeys, indeterminateKeys, parentLinkage]);
 
  useEffect(() => {
    if (onDisplayedKeysChange && JSON.stringify(prevDisplayedKeysRef.current) !== JSON.stringify(displayedKeys)) {
      onDisplayedKeysChange(displayedKeys);
      prevDisplayedKeysRef.current = displayedKeys;
    }
  }, [displayedKeys]);
 
 
 
  return (
    <Tree
      checkable
      treeData={treeData}
      indeterminateKeys={indeterminateKeys}
      checkedKeys={checkedKeys}
      onCheck={handleSelect}
    />
  );
}
 
 
/**
 * 递归获取所有叶子节点的 key
 * @param {Array} treeData - 树形数据
 * @returns {Array} - 叶子节点的 key 数组
 */
const getLeafKeys = (treeData) => {
  const keys = [];
  const traverse = (data) => {
    data.forEach((item) => {
      if (item.children && item.children.length > 0) {
        traverse(item.children);
      } else {
        keys.push(item.key);
      }
    });
  };
  traverse(treeData);
  return keys;
};
 
/**
 * @param {Object} params - 配置参数
 * @param {Array} params.treeData - 树形数据
 * @param {Array} params.value - 选中的节点 key 数组(受控)
 * @param {Function} params.onChange - 选中状态变化的回调函数
 * @param {Boolean} params.parentLinkage - 是否启用父节点联动
 * @returns {Object} - 多选框的状态和操作函数
 */
export const useTreeCheckbox = ({ treeData, value, onChange, parentLinkage }) => {
  const [indeterminateKeys, setIndeterminateKeys] = useState([]); // 半选中的节点
 
  // 使用 useMemo 计算叶子节点的 key,避免重复计算
  const leafKeys = useMemo(() => getLeafKeys(treeData), [treeData]);
 
  /**
   * 获取选中节点往上的父节点和祖父节点,直到根节点
   * @param {Array} treeData - 树形数据
   * @param {Array} selectedKeys - 选中的节点 key 数组
   * @returns {Array} - 包含选中节点的父节点和祖先节点的 key 数组
   */
  const getParentKeys = (treeData, selectedKeys) => {
    const parentKeys = new Set(); // 使用 Set 去重
 
    /**
     * 遍历树形结构查找父节点
     * @param {Array} nodes - 当前节点的数据
     * @param {Array} parentKeyPath - 当前节点的父节点路径
     */
    const traverse = (nodes, parentKeyPath = []) => {
      nodes.forEach((node) => {
        const currentKeyPath = [...parentKeyPath, node.key]; // 当前节点的 key 路径(包含祖先节点)
 
        // 如果当前节点是选中的节点,保存其所有祖先节点
        if (selectedKeys.includes(node.key)) {
          currentKeyPath.forEach((key) => parentKeys.add(key)); // 保存当前节点和所有父节点
        }
 
        // 如果当前节点有子节点,继续递归查找
        if (node.children) {
          traverse(node.children, currentKeyPath);
        }
      });
    };
 
    traverse(treeData); // 从根节点开始遍历
 
    return Array.from(parentKeys); // 将 Set 转换为数组并返回
  };
 
 
 
  /**
   * 处理选择框变化
   * @param {Array} newCheckedKeys - 当前选中的节点 key 数组
   * @param {Object} info - 包含半选中节点 key 和其他信息
   */
  const handleSelect = (newCheckedKeys, info) => {
    const halfCheckedKeys = info.halfCheckedKeys || [];
    setIndeterminateKeys(halfCheckedKeys);
 
    // 调用外部回调,通知父组件状态变化
    if (onChange) {
      onChange(newCheckedKeys, info);
    }
  };
 
  // 处理回填值
  const processedCheckedKeys = useMemo(() => {
    const selectedKeys = value.filter((key) => leafKeys.includes(key)); // 只选中叶子节点
 
    // 计算哪些父节点需要半选中
    const newIndeterminateKeys = parentLinkage ? getParentKeys(treeData, selectedKeys) : [];
 
    // 更新 indeterminateKeys
    setIndeterminateKeys(newIndeterminateKeys);
 
    return selectedKeys; // 返回选中的叶子节点
  }, [value, parentLinkage, leafKeys, treeData]);
 
  return {
    checkedKeys: processedCheckedKeys, // 回填时过滤的选中节点
    indeterminateKeys, // 半选中的节点
    leafKeys, // 所有叶子节点
    handleSelect, // 选中操作的处理函数
  };
};
 
 
export default LinkedMultiSelectTree