ant-design实现树的穿梭框,穿梭后右侧是已选树(二)

 根据上一篇目标一,进一步实现树的穿梭框

头部搜索功能有点问题,处理bug 在文末,咨询可私

主要内容:

基于ant-design树的穿梭框,实现穿梭后右侧是已选树,(当前antd右侧只有一个层级)

理想的树的穿梭框:

左边是完整的树,右边是已选的树,左边已选穿梭到右边,左边已选的消失,右边增加已选,右边也可以勾选然后穿梭回去左边,左边出现右边消失。

目标1:右边是已选的树,左边已选穿梭到右边,左边树不变可以继续操作,右边只可以看结果 

目标2:右边是已选的树,左边已选穿梭到右边,左边已选的消失掉,右边可以选择穿梭回去 

主要核心办法:左边不展示已被穿梭的数据(不改变左边的原数组,采用隐藏的方式,已经被穿梭的或者子集全部被穿梭的不展示)

目标2:目前效果 

ant-design实现树的穿梭框,穿梭后右侧是已选树(二)_第1张图片

步骤一:把目标一的搜索进行优化

改动代码:

  onSearch={(dir, val) => {
            if (dir === 'left') { // 左边搜索
              if (val !== '') {
                const newDeptList = this.onsearchDataSource(dataSource, val);
                this.setState({ newDataSource: newDeptList });
              } else {
                this.setState({ newDataSource: dataSource });// 左边没搜索时候的原始数据
              }
            } else { //右边搜索
              if (val !== '') {
                const newDeptList = this.onsearchDataSource(rightDataSource, val);
                this.setState({ newRightDataSource: newDeptList });
              } else {
                this.setState({ newRightDataSource: rightDataSource }); // 右边没搜索时候的原始数据
              }
            }
          }}
// 树搜索函数 
onsearchDataSource(dataSource, val) {
    // 递归查找存在的父级
    const deepFilter = (i, val) => {
      return (
        i.children.filter((o) => {
          if (o.label.indexOf(val) > -1) {
            return true;
          }
          if (o.children && o.children.length > 0) {
            return deepFilter(o, val);
          }
        }).length > 0
      );
    };
    const filterMenu = (list, val) => {
      return list
        .filter((item) => {
          if (item.label.indexOf(val) > -1) {
            return true;
          }
          if (item.children && item.children.length > 0) {
            return deepFilter(item, val);
          }
          return false;
        })
        .map((item) => {
          item = Object.assign({}, item);
          if (item.children) {
            item.children = item.children.filter((res) => res.label.indexOf(val) > -1);
            filterMenu(item.children, val);
          }
          return item;
        });
    };
    return filterMenu(dataSource, val);
  }

步骤二:把目标onCheck函数进行更新

onCheck={(_, even) => {
                    const {
                      checkedNodes,
                      node: {
                        props: { eventKey },
                      },
                    } = even;
                    // 筛选出最底层的子集 集合
                    const checkedChildKeys = checkedNodes.reduce((arr, e) => {
                      if (e.props.children.length <= 0) {
                        arr.push(e.key);
                      }
                      return arr;
                    }, []);
           
                    if (_.length > 0) {
                      this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) });
                      onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                      // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
                      onItemSelect('DEL' + Math.random(), true);
                    } else {
                  // 加上如果勾来勾去又没勾的情况
                      this.setState({ currTargetKeys: targetKeys });
                    }
                  }}
 

左边的tree : 

if (direction === 'left') {
              const checkedKeys = [...selectedKeys, ...currTargetKeys];
              return newDataSource.length > 0 ? (
                 {
                    const {
                      checkedNodes,
                      node: {
                        props: { eventKey },
                      },
                    } = even;
                    // 筛选出最底层的子集 集合
                    const checkedChildKeys = checkedNodes.reduce((arr, e) => {
                      if (e.props.children.length <= 0) {
                        arr.push(e.key);
                      }
                      return arr;
                    }, []);
                    if (_.length > 0) {
                      this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) });
                      onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                      // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
                      onItemSelect('DEL' + Math.random(), true);
                    } else {
                      this.setState({ currTargetKeys: targetKeys });
                    }
                  }}>
                  {generateTree(newDataSource, targetKeys, searchValue, disabled, 'left')}
                
              ) : (
                
); }

右边的tree :

// 右边从纯展示变成了可更改 所以增加已选的操作
if (direction === 'right') {
              const checkedKeys = [...rightCurrTargetKeys];
              return newRightDataSource.length > 0 ? (
                 {
                    const {
                      checkedNodes,
                      node: {
                        props: { eventKey },
                      },
                    } = even;
                    // 筛选出最底层的子集 集合
                    const checkedChildKeys = checkedNodes.reduce((arr, e) => {
                      if (e.props.children.length <= 0) {
                        arr.push(e.key);
                      }
                      return arr;
                    }, []);
                    //  勾选后更改左侧已选定内容
                    this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) });
                    // 设置右侧已勾选框
                    this.setState({ rightCurrTargetKeys: checkedChildKeys });
                    onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                  }}>
                  {generateTree(newRightDataSource, targetKeys, searchValue, false, 'right')}
                
              ) : (
                
); }

步骤三:generateTree展示的更新

目标一:左边没变

目标二:左边减少上次已穿梭的内容,右边增加新穿梭的内容,

// 不改变左边的原数组,采用隐藏的方式,已经被穿梭的或者子集全部被穿梭的不展示label
const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false, type = 'left') => {
  return treeNodes.map(({ children, ...props }) => {
    if (type === 'left') {
      // 是否子元素都被选择了 如果是则父不展示了
      const isAll =
        children.length > 0
          ? lodash.every(children, (e) => {
              return checkedKeys.includes(e.key);
            })
          : false;
// 自己是否被穿梭了 穿梭了则不展示了 !checkedKeys.includes(props.key)
      return !isAll && !checkedKeys.includes(props.key) ? (
        
              {props.label}
            
          }>
          {generateTree(children, checkedKeys, searchValue, disabled, type)}
        
      ) : (
        ''
      );
    } else {
// 右边的展示内容
      return (
        
              
                {props.label}
              
            
          }>
          {generateTree(children, checkedKeys, searchValue, disabled, type)}
        
      );
    }
  });
};

TreeTransfer 整个组件代码

import React, { Component } from 'react';
import './index.less';
import { Transfer, Tree, Tooltip } from 'antd';
import lodash from 'lodash';
const { TreeNode } = Tree;
const isChecked = (selectedKeys, eventKey) => {
  return selectedKeys.indexOf(eventKey) !== -1;
};

const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false, type = 'left') => {
  return treeNodes.map(({ children, ...props }) => {
    if (type === 'left') {
      // 是否子元素都被选择了
      const isAll =
        children.length > 0
          ? lodash.every(children, (e) => {
              return checkedKeys.includes(e.key);
            })
          : false;   
   return !isAll && !checkedKeys.includes(props.key) ? (
        
              {props.label}
            
          }>
          {generateTree(children, checkedKeys, searchValue, disabled, type)}
        
      ) : (
        ''
      );
    } else {
      return (
        
              = 0 ? 'red' : '' }}>
                {props.label}
              
            
          }>
          {generateTree(children, checkedKeys, searchValue, disabled, type)}
        
      );
    }
  });
};

class TreeTransfer extends Component {
  constructor(props, context) {
    super(props, context);
    this.stores = this.props.UserMgtMod;
    this.state = {
      searchValue: null,
      transferDataSource: [],
      currTargetKeys: [],
      rightCurrTargetKeys: [],
      defaultExpandAll: true,
      newDataSource: [],
      newRightDataSource: [],
    };
  }
  componentDidMount() {
    const { dataSource, targetKeys, rightDataSource } = this.props;
    this.flatten(dataSource);
    this.setState({
      currTargetKeys: targetKeys,
      newDataSource: dataSource,
      newRightDataSource: rightDataSource,
    });
  }
  UNSAFE_componentWillReceiveProps(nextprops) {
    const { dataSource, targetKeys, rightDataSource } = nextprops;
 
    this.setState({
      currTargetKeys: targetKeys,
      newDataSource: dataSource,
      newRightDataSource: rightDataSource,
    });
  }
  flatten(list = []) {
    const dataSource = this.state.transferDataSource;
    list.forEach((item) => {
      dataSource.push(item);
      this.setState({ transferDataSource: dataSource });
      this.flatten(item.children);
    });
  }
  onChange(e) {
    this.setState({
      searchValue: e.target.value,
    });
  }
  onsearchDataSource(dataSource, val) {
    // 递归查找存在的父级
    const deepFilter = (i, val) => {
      return (
        i.children.filter((o) => {
          if (o.label.indexOf(val) > -1) {
            return true;
          }
          if (o.children && o.children.length > 0) {
            return deepFilter(o, val);
          }
        }).length > 0
      );
    };
    const filterMenu = (list, val) => {
      return list
        .filter((item) => {
          if (item.label.indexOf(val) > -1) {
            return true;
          }
          if (item.children && item.children.length > 0) {
            return deepFilter(item, val);
          }
          return false;
        })
        .map((item) => {
          item = Object.assign({}, item);
          if (item.children) {
            item.children = item.children.filter((res) => res.label.indexOf(val) > -1);
            filterMenu(item.children, val);
          }
          return item;
        });
    };
    return filterMenu(dataSource, val);
  }
  render() {
    const { dataSource, targetKeys, rightDataSource, disabled, ...restProps } = this.props;
    const { transferDataSource, searchValue, currTargetKeys, rightCurrTargetKeys, newDataSource, newRightDataSource } = this.state;

    return (
      
item.label} showSearch showSelectAll={false} onSearch={(dir, val) => { if (dir === 'left') { if (val !== '') { const newDeptList = this.onsearchDataSource(dataSource, val); this.setState({ newDataSource: newDeptList }); } else { this.setState({ newDataSource: dataSource }); } } else { if (val !== '') { const newDeptList = this.onsearchDataSource(rightDataSource, val); this.setState({ newRightDataSource: newDeptList }); } else { this.setState({ newRightDataSource: rightDataSource }); } } }}> {({ direction, onItemSelect, selectedKeys }) => { if (direction === 'left') { const checkedKeys = [...selectedKeys, ...currTargetKeys]; return newDataSource.length > 0 ? ( { const { checkedNodes, node: { props: { eventKey }, }, } = even; // 筛选出最底层的子集 集合 const checkedChildKeys = checkedNodes.reduce((arr, e) => { if (e.props.children.length <= 0) { arr.push(e.key); } return arr; }, []); if (_.length > 0) { this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) }); onItemSelect(eventKey, !isChecked(checkedKeys, eventKey)); // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数 onItemSelect('DEL' + Math.random(), true); } else { this.setState({ currTargetKeys: targetKeys }); } }}> {generateTree(newDataSource, targetKeys, searchValue, disabled, 'left')} ) : (
); } if (direction === 'right') { const checkedKeys = [...rightCurrTargetKeys]; return newRightDataSource.length > 0 ? ( { const { checkedNodes, node: { props: { eventKey }, }, } = even; // 筛选出最底层的子集 集合 const checkedChildKeys = checkedNodes.reduce((arr, e) => { if (e.props.children.length <= 0) { arr.push(e.key); } return arr; }, []); // 勾选后更改左侧已选定内容 this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) }); // 设置右侧已勾选框 this.setState({ rightCurrTargetKeys: checkedChildKeys }); onItemSelect(eventKey, !isChecked(checkedKeys, eventKey)); }}> {generateTree(newRightDataSource, targetKeys, searchValue, false, 'right')} ) : (
); } }}
); } } export default TreeTransfer;

 组件引用案例:


         {
            this.setState({ roleModal: false, rightDataSource: [] });
          }}
          onCancel={() => {
            this.setState({ roleModal: false, rightDataSource: [] });
          }}>
           
            {this.state.roleModal && (
              
            )}
          
        

 关键函数

 handleChange = (newTargetKeys) => {
    const targetKeys = newTargetKeys.reduce((arr, e) => {
      // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
      if (e.indexOf('DEL') < 0) {
        arr.push(e);
      }
      return arr;
    }, []);
    // 递归查找是否存在包含选中key的父级 
    const deepFilter = (i, arr) => {
      return (
        i.children.filter((o) => {
          if (arr.includes(o.key)) {
            return true;
          }
          if (o.children && o.children.length > 0) {
            return deepFilter(o, arr);
          }
        }).length > 0
      );
    };
  // 找到一个存在key的父级 然后遍历获取层级内容
    const filterMenu = (list, arr) => {
      return list
        .filter((item) => {
          if (arr.includes(item.key)) {
            return true;
          }
          if (item.children && item.children.length > 0) {
            return deepFilter(item, arr);
          }
          return false;
        })
        .map((item) => {
          item = Object.assign({}, item);
          if (item.children) {
            item.children = filterMenu(item.children, arr);
          }
          return item;
        });
    };
    if (this.state.roleModal) {
      const { roleList } = toJS(this.stores.state);
      this.setState({ roleTargetKeys: [...new Set(targetKeys)] }); // 去重
      const rightDataSource = filterMenu(roleList, targetKeys); // 查询组装已选树
      this.stores.saveInfoModal({ roleIds: targetKeys });
      this.props.form.setFieldsValue({ roleIds: targetKeys });
      this.setState({ rightDataSource: rightDataSource || [] });
    }
   
  };

 触发方法:

  
{ this.setState({ roleModal: true }); setTimeout(() => { this.handleChange(this.state.roleTargetKeys); }, 100); }}> {'' + title === 'detail' ? '查看' : '请选择'} 已选{this.state.roleTargetKeys.length}项

 目标二就完成了

处理已勾选的显示值bug

ant-design实现树的穿梭框,穿梭后右侧是已选树(二)_第2张图片

 初始化

   this.state = {
      rightCheckLength: 0, // 记录右边已勾
      leftCheckLength: 0, // 记录左边已勾
    };
  }

 重置

  UNSAFE_componentWillReceiveProps(nextprops) {
  // 穿梭到右边更新后重置
    this.setState({
      leftCheckLength: 0,
      rightCheckLength: 0,
    });

  }

 ​ 自行定义title

 

​设置勾选

// 左边     
  
                    const checkedChildKeys = checkedNodes.reduce((arr, e) => {
                      if (e.props.children.length <= 0) {
                        arr.push(e.key);
                      }
                      return arr;
                    }, []);
                    // 设置勾选
                    this.setState({ leftCheckLength: checkedChildKeys.length });
 

// 右边
 
                    // 筛选出最底层的子集 集合
                    const checkedChildKeys = checkedNodes.reduce((arr, e) => {
                      if (e.props.children.length <= 0) {
                        arr.push(e.key);
                      }
                      return arr;
                    }, []);
                    this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) });
                    
                 

 完整代码:

import React, { Component } from 'react';
import './index.less';
import { Transfer, Tree, Tooltip } from 'antd';
import lodash from 'lodash';
const { TreeNode } = Tree;
const isChecked = (selectedKeys, eventKey) => {
  return selectedKeys.indexOf(eventKey) !== -1;
};

const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false, type = 'left') => {
  return treeNodes.map(({ children, ...props }) => {
    if (type === 'left') {
      // 是否子元素都被选择了
      const isAll =
        children && children.length > 0
          ? lodash.every(children, (e) => {
              return checkedKeys.includes(e.key);
            })
          : false;

      return !isAll && !checkedKeys.includes(props.key) ? (
        
              {props.label}
            
          }>
          {generateTree(children, checkedKeys, searchValue, disabled, type)}
        
      ) : (
        ''
      );
    } else {
      return (
        
              = 0 ? 'red' : '' }}>
                {props.label}
              
            
          }>
          {generateTree(children, checkedKeys, searchValue, disabled, type)}
        
      );
    }
  });
};

class TreeTransfer extends Component {
  constructor(props, context) {
    super(props, context);
    this.stores = this.props.UserMgtMod;
    this.state = {
      searchValue: null,
      transferDataSource: [],
      currTargetKeys: [],
      rightCurrTargetKeys: [],
      defaultExpandAll: true,
      newDataSource: [],
      newRightDataSource: [],
      rightCheckLength: 0,
      leftCheckLength: 0,
      dataSourceLength: 0,
    };
  }
  componentDidMount() {
    const { dataSource, targetKeys, rightDataSource } = this.props;
    this.flatten(dataSource);
    this.setState({
      currTargetKeys: targetKeys,
      newDataSource: dataSource,
      newRightDataSource: rightDataSource,
    });
    this.getChildrenLength(dataSource);
  }
  UNSAFE_componentWillReceiveProps(nextprops) {
    const { dataSource, targetKeys, rightDataSource } = nextprops;
    this.setState({
      leftCheckLength: 0,
      rightCheckLength: 0,
    });
    // this.flatten(nextprops.dataSource);
    this.setState({
      currTargetKeys: targetKeys,
      newDataSource: dataSource,
      newRightDataSource: rightDataSource,
    });
  }
  //  获取子数量 用在勾选是左上角的显示
  getChildrenLength(dataSource) {
    const getTail = (item) => (item.children && item.children.length > 0 ? item.children.map((m) => getTail(m)) : [item]);
    const result = lodash.flattenDeep(dataSource.map((m) => getTail(m)));
    this.setState({ dataSourceLength: result.length });
  }
  flatten(list = []) {
    const dataSource = this.state.transferDataSource;
    list.forEach((item) => {
      dataSource.push(item);
      this.setState({ transferDataSource: dataSource });
      this.flatten(item.children);
    });
  }
  onChange(e) {
    this.setState({
      searchValue: e.target.value,
    });
  }
  onsearchDataSource(dataSource, val) {
    // 递归查找存在的父级
    const deepFilter = (i, val) => {
      return (
        i.children.filter((o) => {
          if (o.label.indexOf(val) > -1) {
            return true;
          }
          if (o.children && o.children.length > 0) {
            return deepFilter(o, val);
          }
        }).length > 0
      );
    };
    const filterMenu = (list, val) => {
      return list
        .filter((item) => {
          if (item.label.indexOf(val) > -1) {
            return true;
          }
          if (item.children && item.children.length > 0) {
            return deepFilter(item, val);
          }
          return false;
        })
        .map((item) => {
          item = Object.assign({}, item);
          if (item.children) {
            item.children = item.children.filter((res) => res.label.indexOf(val) > -1);
            filterMenu(item.children, val);
          }
          return item;
        });
    };
    return filterMenu(dataSource, val);
  }
  render() {
    const { dataSource, targetKeys, rightDataSource, disabled, ...restProps } = this.props;
    const { leftCheckLength, rightCheckLength, transferDataSource, searchValue, currTargetKeys, rightCurrTargetKeys, newDataSource, newRightDataSource, dataSourceLength } = this.state;

    return (
      
item.label} showSearch titles={[`${leftCheckLength}/${dataSourceLength - targetKeys.length}项`, `${rightCheckLength}/${targetKeys.length}项`]} showSelectAll={false} onSearch={(dir, val) => { if (dir === 'left') { if (val !== '') { const newDeptList = this.onsearchDataSource(dataSource, val); this.setState({ newDataSource: newDeptList }); } else { this.setState({ newDataSource: dataSource }); } } else { if (val !== '') { const newDeptList = this.onsearchDataSource(rightDataSource, val); this.setState({ newRightDataSource: newDeptList }); } else { this.setState({ newRightDataSource: rightDataSource }); } } }}> {({ direction, onItemSelect, selectedKeys }) => { if (direction === 'left') { const checkedKeys = [...selectedKeys, ...currTargetKeys]; return newDataSource.length > 0 ? ( { const { checkedNodes, node: { props: { eventKey }, }, } = even; // 筛选出最底层的子集 集合 const checkedChildKeys = checkedNodes.reduce((arr, e) => { if (e.props.children.length <= 0) { arr.push(e.key); } return arr; }, []); this.setState({ leftCheckLength: checkedChildKeys.length }); if (_.length > 0) { this.setState({ currTargetKeys: lodash.union(targetKeys, checkedChildKeys) }); onItemSelect(eventKey, !isChecked(checkedKeys, eventKey)); // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数 onItemSelect('DEL' + Math.random(), true); } else { this.setState({ currTargetKeys: targetKeys }); } }}> {generateTree(newDataSource, targetKeys, searchValue, disabled, 'left')} ) : (
); } if (direction === 'right') { const checkedKeys = [...rightCurrTargetKeys]; return newRightDataSource.length > 0 ? ( { const { checkedNodes, node: { props: { eventKey }, }, } = even; // 筛选出最底层的子集 集合 const checkedChildKeys = checkedNodes.reduce((arr, e) => { if (e.props.children.length <= 0) { arr.push(e.key); } return arr; }, []); this.setState({ currTargetKeys: lodash.xor(checkedChildKeys, targetKeys) }); this.setState({ rightCheckLength: checkedChildKeys.length, rightCurrTargetKeys: checkedChildKeys }); onItemSelect(eventKey, !isChecked(checkedKeys, eventKey)); }}> {generateTree(newRightDataSource, targetKeys, searchValue, false, 'right')} ) : (
); } }}
); } } export default TreeTransfer;

你可能感兴趣的:(react,tree,前端,前端,react,javascript)