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

news2024/11/19 13:18:19

主要内容:

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

理想的树的穿梭框:

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

目前实现的效果:

左边是完整的树,右边是已选的树,左边已选穿梭到右边,左边不变,右边只可以看结果,不可以操作,这样右边的是结果,操作都在左边。

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

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

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

ant-design树的穿梭框:

不足:右侧不是树结构,混乱了层级

目标1:目前大致效果 

步骤一:应用ant-design树的穿梭框代码(穿梭框 Transfer - Ant Design)

更改内部的代码

改动代码:

          {({ direction, onItemSelect, selectedKeys }) => {
            if (direction === 'left') {
              const checkedKeys = [...selectedKeys, ...currTargetKeys];
              return (
                <Tree
                  blockNode
                  autoExpandParent={true}
                  disabled={disabled}
                  checkable
                  checkedKeys={checkedKeys}
                  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;
                    }, []);

                    this.setState({ currTargetKeys: checkedChildKeys });
                    onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                    // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
                    onItemSelect('DEL' + Math.random(), true);
                  }}
                 >
                  {generateTree(newDataSource, targetKeys, searchValue, disabled)}
                </Tree>
              );
            }
            if (direction === 'right') {
              return (
                <Tree blockNode checkable autoExpandParent={true} disabled={disabled}>
                  {generateTree(rightDataSource, targetKeys, searchValue, true)}
                </Tree>
              );
            }
          }}


1.处理树的内部自定义内容 ( generateTree())

// 处理树的内部自定义内容
const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false) => {
  return treeNodes.map(({ children, ...props }) => {
    return (
      <TreeNode
        {...props}
        disabled={disabled}
        key={props.key}
        title={
          <Tooltip placement='topLeft' title={props.label}>
            <span className='title-over' style={{ color: props.label.indexOf(searchValue) >= 0 ? 'red' : '' }}>
              {props.label} // 根据数据情况显示
            </span>
          </Tooltip>
        }>
        {generateTree(children, checkedKeys, searchValue, disabled)}
      </TreeNode>
    );
  });
};

 generateTree的四个参数:treeNodes (树数据), checkedKeys (已选值), searchValue(搜索值), disabled(是否禁止)

左边的treeNodes :原本的树/通过搜索后的树

  if (direction === 'left') {
              const checkedKeys = [...selectedKeys, ...currTargetKeys];
              return (
                <Tree
                  blockNode
                  autoExpandParent={true}
                  disabled={disabled}
                  checkable
                  checkedKeys={checkedKeys}
                  onCheck={(_, even) => {
                   // ...
                  }}
                 >
                  {generateTree(newDataSource, targetKeys, searchValue, disabled)}
                </Tree>
              );
            }

右边的treeNodes :已选的树/已选通过搜索后的树

右侧同理

 if (direction === 'right') {
              return (
                <Tree blockNode checkable autoExpandParent={true} disabled={disabled}>
                  {generateTree(rightDataSource, targetKeys, searchValue, true)}
                </Tree>
              );
            }

  当onCheck的时候,我们需要拿到的是最底层的子集集合

 2.筛选出最底层的子集 集合 

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

3.手动设置已选的唯一key 

// 设置已选的id  
    this.setState({ currTargetKeys: checkedChildKeys })

 注意点:

 处理选中还是没选的样式,因为如果我勾选后取消勾选,不能触发antd内部的变化函数,所以它以为没变化,往右穿梭框不会可点击;所以手动添加了一个随机号(加特殊标识),就会触发更新按钮变成,然后后面通过(特殊标识)删掉手动添加的随机数。

                    onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                    // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
                    onItemSelect('DEL' + Math.random(), true);

 onsearch: 

主要代码,其实目标二 三 也可以使用类似的方法 处理 ,

思路:

左边将已选值当做搜索关键字一样,把左边已选内容除掉,

右边反向操作,只留下已选值的树,

  onSearch={(dir, val) => {
            if (dir === 'left') {
              if (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;
                    });
                };
                const newDeptList = filterMenu(dataSource, val);
                newDataSource = newDeptList;
              } else {
                newDataSource = dataSource;
              }
            }
          }}

TreeTransfer 整个组件代码

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

const generateTree = (treeNodes = [], checkedKeys = [], searchValue, disabled = false) => {
  return treeNodes.map(({ children, ...props }) => {
    return (
      <TreeNode
        {...props}
        disabled={disabled}
        key={props.key}
        title={
          <Tooltip placement='topLeft' title={props.label}>
            <span className='title-over' style={{ color: props.label.indexOf(searchValue) >= 0 ? 'red' : '' }}>
              {props.label}
            </span>
          </Tooltip>
        }>
        {generateTree(children, checkedKeys, searchValue, disabled)}
      </TreeNode>
    );
  });
};

class TreeTransfer extends Component {
  constructor(props, context) {
    super(props, context);
    this.stores = this.props.UserMgtMod;
    this.state = {
      searchValue: null,
      transferDataSource: [],
      currTargetKeys: [],
      defaultExpandAll: true,
    };
  }
  componentDidMount() {
    const { dataSource, targetKeys } = this.props;
    this.flatten(dataSource); // 扁平化层级为一维数组
    this.setState({ currTargetKeys: targetKeys }); // 设置传入的默认已选值
  }
  // 及时更新
  UNSAFE_componentWillReceiveProps(nextprops) {
    this.flatten(nextprops.dataSource);
    this.setState({ currTargetKeys: nextprops.targetKeys });
  }
  // 扁平化层级为一维数组
  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,
    });
  }

  render() {
    const {
      dataSource, // 左侧树 原始数据
      targetKeys, // 外部传入的已选key
      rightDataSource, // 左侧已选数据 移入到右侧的数据
      disabled, // 是否禁用(只能查看的时候)
      ...restProps
    } = this.props;
    const {
      transferDataSource, // 扁平化的数据(显示总数目)
      searchValue, // 搜索关键字
      currTargetKeys, // 内部管理的当前已选key
    } = this.state;
    let newDataSource = dataSource || [];

    return (
      <div>
        <Transfer
          {...restProps}
          disabled={disabled}
          targetKeys={currTargetKeys}
          dataSource={transferDataSource}
          className='tree-transfer'
          render={(item) => item.label}
          showSearch
          showSelectAll={false}
          // 搜索
          onSearch={(dir, val) => {
            if (dir === 'left') {
              if (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;
                    });
                };
                const newDeptList = filterMenu(dataSource, val);
                newDataSource = newDeptList;
              } else {
                newDataSource = dataSource;
              }
            }
          }}>
          {({ direction, onItemSelect, selectedKeys }) => {
            if (direction === 'left') {
              const checkedKeys = [...selectedKeys, ...currTargetKeys];
              return (
                newDataSource.length > 0 ? (
                  <Tree
                    blockNode
                    defaultExpandParent={true} // 初始化生效 异步加载的情况下 使用newDataSource.length > 0
                    disabled={disabled}
                    checkable
                    checkedKeys={checkedKeys}
                    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;
                      }, []);
                      // 设置已选key
                      this.setState({ currTargetKeys: checkedChildKeys });
                      // 设置已选效果
                      onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                      // 增加随机数 为了可以触发向右穿梭的按钮 触发后删除随机数
                      onItemSelect('DEL' + Math.random(), true);
                    }}
                    onSelect={(
                      _,
                      {
                        node: {
                          props: { eventKey },
                        },
                      },
                    ) => {
                      this.setState({ currTargetKeys: _ });
                      onItemSelect(eventKey, !isChecked(checkedKeys, eventKey));
                    }}>
                    {generateTree(newDataSource, targetKeys, searchValue, disabled)}
                  </Tree>
                )
              ) : (
                <div></div>
              );
            }
            if (direction === 'right') {
              return (
                rightDataSource.length > 0 ? (
                  <Tree blockNode checkable defaultExpandAll={true} disabled={disabled}>
                    {generateTree(rightDataSource, targetKeys, searchValue, true)}
                  </Tree>
                )
              ); : (
                <div></div>
              )
            }
          }}
        </Transfer>
      </div>
    );
  }
}
export default TreeTransfer;

 组件引用案例:


        <Modal
          width={1000}
          title={'所属角色'}
          className='user-modal'
          visible={this.state.roleModal}
          onOk={() => {
            this.setState({ roleModal: false, rightDataSource: [] });
          }}
          onCancel={() => {
            this.setState({ roleModal: false, rightDataSource: [] });
          }}>
          <Spin spinning={roleLoading}> 
            {this.state.roleModal && (
              <TreeTransfer
                disabled={'' + title === 'detail' ? true : false}
                dataSource={roleList} // 原始树结构
                rightDataSource={rightDataSource}
                targetKeys={roleTargetKeys}
                onChange={this.handleChange.bind(this)}
              />
            )}
          </Spin>
        </Modal>

 关键函数

 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 || [] });
    }
   
  };

 触发方法:

  <div style={{ display: 'flex' }}>
              <YxButton
                type='primary'
                style={{ marginRight: '10px' }}
                onClick={() => {
                  this.setState({ roleModal: true });
                  setTimeout(() => {
                    this.handleChange(this.state.roleTargetKeys);
                  }, 100);
                }}>
                {'' + title === 'detail' ? '查看' : '请选择'}
              </YxButton>
              已选{this.state.roleTargetKeys.length}项
            </div>

 目标一就完成了,

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/516946.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode-206. 反转链表

目录 双指针递归 题目来源 206. 反转链表 双指针 定义两个指针&#xff1a; pre 和 cur&#xff1b;pre 在前 cur 在后。 每次让 cur 的 next 指向 pre&#xff0c;实现一次局部反转 局部反转完成之后&#xff0c;pre 和 cur 同时往前移动一个位置 循环上述过程&#xff0c;直…

AWS S3 跨账号迁移

目录 **迁移架构&#xff1a;****具体实施&#xff1a;****1. 在目标账号创建策略&#xff08;S3MigrationPolicy&#xff09;和角色&#xff08;S3MigrationRole&#xff09;****2. 安装 aws cli&#xff0c;并配置$ aws configure&#xff0c;**[请参阅 AWS CLI 文档中的安装…

C++回调函数

回调函数 文章目录 回调函数一、函数指针二、回调函数应用Reference 一、函数指针 指针是一个变量&#xff0c;是用来指向内存地址。 一个程序运行时&#xff0c;所有和运行相关的东西都需要加载到内存当中&#xff0c;因此可以通过指针指向该内存。 函数是存放在内存代码区…

Otter CTF--Network(web网络1-3)

目录 一.题目 1.Birdmans Data 2.Look At Me 3.Otter Leak 一.题目 网址 OtterCTF 1.Birdmans Data 下载文件&#xff1a; .pcap文件 Wireshark打开&#xff1a; 既然是web 我们就从http分析&#xff1a; 追踪流 HTTP流&#xff1a; 发现两个密钥key&#xff1a; {"…

数据集 | 基于语音(Speech)/多模态(Multimodal)的情绪识别数据集,格式及下载

本文主要介绍了一些常用的语音&#x1f5e3;识别数据集&#xff0c;文件格式以及下载地址&#xff1a; 目录 1.IEMOCAP Emotion Speech Database(English) 2.Emo-DB Database(German) 文件命名 对象 3.Ryerson Audio-Visual Database of Emotional Speech and Song (Engli…

解密JS代码:一个有趣的故事

作为一名前端开发者&#xff0c;我们经常需要处理加密和解密的任务。近日&#xff0c;我遇到了一个有趣的故事和一个需要解密的JavaScript代码。让我和你分享一下这个故事以及我是如何解密这段代码的。 最近我收到了一个任务&#xff0c;要将一个网站上的一段JavaScript代码进…

torch_geometric获取datasets(解决连不了外网的问题)

文章目录 1. torch_geometric.data介绍2. 使用Planetoid下载Cora数据集的代码3. 解决程序运行的机器无法联网的问题3.1 尝试运行&#xff0c;查看数据集下载链接3.2 放置到对应文件夹下3.3 重新运行之前写的程序 4. 一点感慨 1. torch_geometric.data介绍 torch_geometric&…

进货商模式玩法解析:当老板、亲自进货、自己赚差价?

如今很多人都看到互联网的发展前景&#xff0c;有了创业的想法&#xff0c;但是资金、技术、市场等问题给他们带来了瓶颈。进货商模式的出现&#xff0c;为这些&#xff08;文章编辑ycy6221&#xff09;有创业想法&#xff0c;有梦想的人打破了这些限制&#xff0c;而且还可以实…

HTTP协议演进:为什么说HTTP/1.1的时代已经过去了

前言 欢迎来到今天的每日一题&#xff0c;每日一提。昨天聊到了&#xff0c;HTTP 是什么。有哪些组成部分。并且最后提到了 HTTP 的一些缺点&#xff0c;比如&#xff1a;性能较低&#xff0c;容易导致网络拥塞和延迟&#xff0c;不支持服务器推送等等。设计协议的大佬们&#…

ChatGPT实现安全漏洞检查

安全漏洞检查 几乎每一项新技术的出现&#xff0c;都会首先被运用在安全领域&#xff0c;ChatGPT 也不例外。在 ChatGPT 出现的第一时间&#xff0c;利用 ChatGPT 编写钓鱼邮件&#xff0c;进行社会工程学攻击&#xff0c;辅助分析代码漏洞等等就成为热门话题。其实技术只是工…

快速上手Vite 配置指南

&#x1f482; 个人网站:【紫陌】【笔记分享网】 &#x1f485; 想寻找共同学习交流、共同成长的伙伴&#xff0c;请点击【前端学习交流群】 文章最后有作者l联系方式&#xff08;备注进群&#xff09; 1.认识vite 什么是vite呢&#xff1f; 官方的定位&#xff1a;下一代前端…

《水经注地图服务》数据源说明

&#xff08;本文首发于“水经注GIS”公号&#xff0c;关注公号免费领取地图数据&#xff09; 《水经注地图服务》&#xff08;WeServer&#xff09;是一款可快速发布全国乃至全球海量卫星影像的地图发布服务产品&#xff0c;该产品完全遵循OGC相关协议标准&#xff0c;是一个…

老板给情人转166万,妻子起诉后追回,网友:这是被白嫖三年

作为一个已婚男人&#xff0c;戚某在婚姻关系中背离了对妻子的忠诚&#xff0c;与小自己14岁的女员工小汪发展出不正当的男女关系。而小汪&#xff0c;在明知对方已婚的情况下&#xff0c;仍然选择继续纠缠&#xff0c;最终付出了惨痛的代价。 据了解&#xff0c;戚某在上海经营…

会议签到二维码制作教程

纸质签到表、人工逐一核对等传统的会议签到方式&#xff0c;存在着耗时耗力、容易出错、不环保等种种弊端。 可以制作一个包含签到表单的签到二维码&#xff0c;参会人员使用微信扫码签到&#xff0c;自动授权填写手机号、定位等信息&#xff0c;管理人员在小程序以及电脑端实…

【团购-自己实现代理 Objective-C语言】

一、我们上节课,实现了数据加载,下面是不是有个加载更多啊, 1.我们先把这个“加载更多”给大家做一下, 这个加载更多,注意看,因为这个加载更多,是显示在UITableView的最底部的, 它会随着UITableView一起滚动吧, 证明,它是在UITableView的tableFooterView里面显示的…

Linux系统中tar.gz与rpm结尾的文件在安装程序时究竟有什么不同?

tar.gz tar.gz就是一个压缩包&#xff01; 使用的时候需要使用tar命令配上一些参数来进行解压。 关于tar的使用&#xff0c;大家可以参考这篇博客&#xff0c;这位大佬写的很详细。 Linux tar命令详解 在解压完后&#xff0c;这就是一个独立的文件&#xff0c;里面包含着关…

第十五章_Redis与MySQL数据双写一致性工程落地案例

复习面试题 采用双检加锁策略 多个线程同时去查询数据库的这条数据&#xff0c;那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。 其他的线程走到这一步拿不到锁就等着&#xff0c;等第一个线程查询到了数据&#xff0c;然后做缓存。 后面的线程进来发现已经…

Redis内幕揭秘:探索Redis基础知识及应用场景,挖掘出高效的缓存技术

Redis 是一个开源的内存数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。以下是 Redis 的发展史&#xff1a; 2009 年&#xff1a;Salvatore Sanfilippo 开始编写 Redis。2010 年&#xff1a;Redis 发布了 1.0 版本。这个版本包含了许多常用的数据结构&…

在陌生人社交场景 挖呀挖呀挖……

在陌生人社交场景 挖呀挖呀挖&#xff0c; 找可靠的大品牌&#xff08;网易&#xff09;&#xff0c;享最优惠的价~ &#x1f604; 对于“深挖”娱乐社交领域需求的开发者来说&#xff0c;陌生人社交是毋庸置疑最受青睐的场景。尤其是对渴望交流、敢于表达自我的年轻人来说&…

10-HTML-表单标签

标签描述<form>定义供用户输入的 HTML 表单。<input>定义输入控件。<textarea>定义多行的文本输入控件。<button>定义按钮。<select>定义选择列表&#xff08;下拉列表&#xff09;。<optgroup>定义选择列表中相关选项的组合。<option&…