Ant+React树形穿梭框实现(支持搜索和全选)

1.效果图

image.png

2.官方组件的示例不是很全面,而且右边不是树形的;直接上源码,我的是ts的语法,参考的原文章是js语法
//组件代码
import React, { useState,forwardRef,useImperativeHandle } from 'react'
import { Transfer, Tree,Modal } from 'antd'
import { DataNode } from 'antd/es/tree'
import './chooseUserTransfer.less'
const AuthorizationModal = (props: any, ref: any) => {
    const [targetKeys, setTargetKeys] = useState<any>([])
    const [rightTreeData, setRightTreeData] = useState([])
    const [treeData,setTreeData]=useState([])
    const generateTree = (treeNodes: DataNode[] = [], checkedKeys: string[] = []): DataNode[] =>
        treeNodes.map(({ children, ...props }:any) => ({
            ...props,
            disabled: checkedKeys.includes(props.key as string),
            children: generateTree(children, checkedKeys),
        }))
 
    const dealCheckboxSeleted = ({ node, onItemSelect, onItemSelectAll }:any, direction:any) => {
        let {
            checked,
            halfCheckedKeys,
            node: { key, children },
        } = node
        // 勾选的是父节点
        if (children?.length > 0) {
            let keys: any[] = []
            let temp = []
            if (direction === 'left') {
                let state = false
                if (rightTreeData.length > 0) {
                    rightTreeData?.map((item:any) => {
                        if (item?.childCompanies?.length > 0 && (item.key == key)) {
                            temp = item.childCompanies.filter((v:any) => !item.childCompanies.some(((t:any) => t.key === v.key)))
                            temp?.forEach((child:any) => {
                                keys.push(child.key)
                            })
                        } else {
                            state = true
                        }
                    })
                } else {
                    state = true
                }
                if (state) {
                    children?.forEach((child:any) => {
                        keys.push(child.key)
                    })
                }
                onItemSelectAll([...keys, key], checked)
            }
            if (direction === 'right') {
                children?.forEach((child:any) => {
                    keys.push(child.key)
                })
                onItemSelectAll([...keys], checked)
            }
        } else {
            // 勾选的是子节点
            if (!checked) {
                // 查找该元素的父元素
                let parentKeys = []
                parentKeys = [halfCheckedKeys?.[0]] || []
                if (parentKeys[0] == undefined) {
                    // 当一级下的二级全部取消勾选,一级也取消勾选
                    treeData.forEach((tree:any) => {
                        if (tree.children) {
                            tree.children?.forEach((child:any) => {
                                if (child?.key === key) {
                                    parentKeys.push(tree?.key)
                                }
                            })
                        }
                    })
                }
                onItemSelectAll([...parentKeys, key], checked)
            } else {
                let parentKey:any =''
                treeData.forEach((tree:any) => {
                    if (tree?.children) {
                        tree.children?.forEach((child:any) => {
                            if (child?.key === key) {
                                parentKey = tree?.key
                            }
                        })
                    }
                })
 
                if (!halfCheckedKeys?.includes(parentKey) && parentKey != '') {
                    onItemSelectAll([key, parentKey], checked)
                } else {
                    onItemSelect(key, checked)
                }
            }
        }
    }
    
    // modal组件
    const [type, setType] = useState(1);//1选择用户2选择用户组
    const [isModalOpen, setIsModalOpen] = useState(false);
    const handleOk = () => {
        let result:any = []
        rightTreeData.forEach((ele:any)=>{
          result.push({
            key:ele.key,
            title:ele.title,
          })
          ele.children.forEach((eles:any)=>{
            result.push({
              key:eles.key,
              title:eles.title,
            })
          })
        })
        result=result.filter((ele: any) =>targetKeys.includes(ele.key)&&ele.key.split('_').length>2)
        props.onChildData(type, result)
        setIsModalOpen(false);
      };
      const handleCancel = () => {
        setIsModalOpen(false);
      };
      useImperativeHandle(ref, () => ({
        // changeVal 就是暴露给父组件的方法
        // changeVal: () => {
        //     setIsModalOpen(true);
    
        // }
        showModal
    
      }));
      
    const showModal = (type: number, data: any, rightData: []) => {
        setIsModalOpen(true);
        setType(type)
        setTreeData(data)
        let keyArr:any=[]
        rightData.forEach((ele:any)=>{
            keyArr.push(ele.key)
            ele.children.forEach((eles:any)=>{
                keyArr.push(eles.key)
            })
        })
        setTargetKeys(keyArr)
        setRightTreeData(rightData)
    };
    const TreeTransfer = ({ dataSource, targetKeys, ...restProps }:any) => {
        const transferDataSource:any = []
        const dataSourceData = dataSource
        let rightTreeDataResult = [...rightTreeData]
        function flatten(list = []) {
            list.forEach((item:any) => {
                transferDataSource.push(item)
                flatten(item.children)
            })
        }
        flatten(dataSource)
        return ( 
            <Transfer
                {...restProps}
                targetKeys={targetKeys}
                dataSource={transferDataSource}
                className="tree-transfer"
                showSearch
                showSelectAll={true}
                render={item => item.title}
                rowKey={record => record.key}
                // 搜索功能逻辑
                onSearch={(dir, val) => {
                    let data = (dir === 'left' ? dataSourceData : rightTreeData)
                    // 1.先遍历二级,过滤出搜索对应的数据;
                    // 2.如果二级有数据,过滤出二级的companyName和一级的companyName
                    // 3.最后把一级不符合搜索对应的值过滤
                    const newDeptList = data?.map((item:any) => {
                        item = Object.assign({}, item)
                        if (item.children) {
                            item.children = item.children?.filter((res:any) => (res.title.indexOf(val) > -1))
                        }
                        return item
                    }).filter((item:any) => {
                        if (item.children?.length > 0 || val.length == 0) {
                            item = Object.assign({}, item)
                            item.children?.filter((e:any) => (
                                e.title.indexOf(val) > -1 ? '' : item.title.indexOf(val) > -1
                            ))
                        } else {
                            item = item.title.indexOf(val) > -1
                        }
                        return item
                    })
                    if (dir === 'left') {
                        dataSource = newDeptList
                    }
                    if (dir === 'right') {
                        rightTreeDataResult = newDeptList
                    }
                }}
            >
                {({ direction, onItemSelect, onItemSelectAll, selectedKeys }) => {
                    if (direction === 'left') {
                        const checkedKeys = [...selectedKeys, ...targetKeys]
                        return (
                            <Tree
                                blockNode
                                checkable
                                checkedKeys={checkedKeys}
                                defaultExpandedKeys={checkedKeys}
                                treeData={generateTree(dataSource, targetKeys)}
                                fieldNames={{ title: 'title', key: 'key', children: 'children' }}
                                onCheck={(_, node) => {
                                    dealCheckboxSeleted({ node, onItemSelect, onItemSelectAll }, direction)
                                }}
                                onSelect={(_, node) => {
                                    dealCheckboxSeleted({ node, onItemSelect, onItemSelectAll }, direction)
                                }}
                            />
                        )
                    }
                    if (direction === 'right') {
                        //defaultExpandAll 默认展开全部
                        const checkedKeys = [...selectedKeys]
                        return (
                            <Tree
                                blockNode
                                checkable
                                checkedKeys={checkedKeys}
                                treeData={rightTreeDataResult}
                                fieldNames={{ title: 'title', key: 'key', children: 'children' }}
                                onCheck={(_, node) => {
                                    dealCheckboxSeleted({ node, onItemSelect, onItemSelectAll }, direction)
                                }}
                                onSelect={(_, node) => {
                                    dealCheckboxSeleted({ node, onItemSelect, onItemSelectAll }, direction)
                                }}
                            />
                        )
                    }
                }}
            </Transfer>
        )
    }
 
    /**
     * 改变右边tree数据
     * @param {*右边tree需要处理的keys集合} keys 
     * @param {*0-删除以上的keys 1-新增以上的keys} type
     */
    const getRightTreeData = (keys:any, type:any) => {
        let arr:any = [...rightTreeData]
        if (keys?.length > 0) {
            keys.forEach((key:any) => {
                treeData.forEach((data:any) => {
                    if (key === data.key) {
                        // 勾选的是父节点,查看右侧是否有勾选对象
                        let index = arr.findIndex((i:any) => {
                            return i.key === key
                        })
                        if (type === 1) {
                            if (index === -1) {
                                arr.push(data)
                            } else if (index > -1 && arr?.[index]?.children?.length < data?.children?.length) {
                                // 先选择子项再勾选该父级时,传过来的keys是 ['0-1-0','0-1'],此时第一次循环已经将该父级放到arr中,
                                // 再遍历0-1时,需要先删除再将全部的children复制
                                arr.splice(index, 1)
                                arr.push(data)
                            }
                        } else if (type === 0) {
                            if (index > -1) {
                                arr.splice(index, 1)
                            }
                        }
                    } else {
                        // 勾选的是子节点
                        // 左侧数据处理
                        let selectedParentKey = '' //选定的父项id
                        let selectedObj:any = {}       //选定对象
                        if (data?.children?.length > 0) {
                            data.children.forEach((child:any) => {
                                if (key === child.key) {
                                    selectedParentKey = data.key
                                    selectedObj = child
                                }
                            })
                        }
                        // 右侧数据处理
                        if (Object.keys(selectedObj)?.length > 0) {
                            let newData:any = {}
                            // 查看右侧是否有选中子项的父项
                            let index = arr.findIndex((item:any) => {
                                return item.key === selectedParentKey
                            })
                            if (index > -1) {
                                // 右侧已有选中子项的父项,selectedIndex查看右侧子项是否有勾选对象
                                let oldChildArr = [...arr[index].children]
                                let selectedIndex = oldChildArr?.findIndex(o => {
                                    return o.key === selectedObj.key
                                })
                                if (selectedIndex === -1 && type === 1) {
                                    arr[index].children.push(selectedObj)
                                }
                                if (selectedIndex > -1 && type === 0) {
                                    arr[index].children.splice(selectedIndex, 1)
                                    if (arr[index].children?.length === 0) {
                                        arr.splice(index, 1)
                                    }
                                }
                            } else {
                                // 右侧没有选中子项的父项
                                if (type === 1) {
                                    newData = { ...data }
                                    newData.children = []
                                    newData.children.push(selectedObj)
                                    arr.push(newData)
                                } else if (type === 0) {
                                    arr = []
                                }
                            }
                        }
                    }
                })
            })
            setRightTreeData(arr)
        }
    }
 
    // 左右移动按钮
    const onChange = (keys:any, direction:any, moveKeys:any) => {
        let changeArrType = 1 // 0-删除  1-新增
        if (direction == 'left') {
            changeArrType = 0
            if (keys.length > 0) {
                treeData.forEach((item:any) => {
                    let index = keys.indexOf(item.key)
                    if (index > -1 && item.children?.length > 0) {
                        item.children?.forEach((v:any) => {
                            if (moveKeys.includes(v.key)) {
                                keys.splice(index, 1)
                            }
                        })
                    }
                })
            }
        }
        setTargetKeys(keys)
        let keysList = changeArrType === 1 ? keys : moveKeys
        getRightTreeData(keysList, changeArrType)
    };
 
    return  <Modal width={600} title={type == 1 ? '选择用户' : '选择设备'} open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
       <TreeTransfer dataSource={treeData} targetKeys={targetKeys} onChange={onChange} /> </Modal>
}
 
export default forwardRef(AuthorizationModal)
3.如何引入使用代码
import ChooseUserTreeTransfer from "../../components/Common/Add/chooseUserTransfer"
//方法
const childRefUser = useRef<any>(null)
const eventClick=()=>{
 childRefUser.current.showModal()
}
const getChildDataUser = (type: number, data: any) => {
}
//组件使用
 <ChooseUserTreeTransfer ref={childRefUser} onChildData={getChildDataUser}></ChooseUserTreeTransfer>

备注
参考地址:https://blog.csdn.net/weixin_49581008/article/details/128953065

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Ant Design提供了Transfer组件,可以实现左右树穿梭。具体实现步骤如下: 1. 安装Ant Design和React: ``` npm install antd --save npm install react --save ``` 2. 引入Transfer组件: ``` import { Transfer } from 'antd'; ``` 3. 创建数据源: ``` const dataSource = [ { key: '1', title: 'Node1', description: 'Node1 description', children: [ { key: '1-1', title: 'Child Node1', description: 'Child Node1 description', }, { key: '1-2', title: 'Child Node2', description: 'Child Node2 description', }, ], }, { key: '2', title: 'Node2', description: 'Node2 description', children: [ { key: '2-1', title: 'Child Node3', description: 'Child Node3 description', }, { key: '2-2', title: 'Child Node4', description: 'Child Node4 description', }, ], }, ]; ``` 4. 创建Transfer组件并传入数据源: ``` <Transfer dataSource={dataSource} showSearch targetKeys={targetKeys} render={item => item.title} /> ``` 其中,`dataSource`为数据源,`showSearch`为是否显示搜索,`targetKeys`为目标数据源,`render`为渲染每个节点的方法。 完整代码例子如下: ``` import React, { useState } from 'react'; import { Transfer } from 'antd'; const dataSource = [ { key: '1', title: 'Node1', description: 'Node1 description', children: [ { key: '1-1', title: 'Child Node1', description: 'Child Node1 description', }, { key: '1-2', title: 'Child Node2', description: 'Child Node2 description', }, ], }, { key: '2', title: 'Node2', description: 'Node2 description', children: [ { key: '2-1', title: 'Child Node3', description: 'Child Node3 description', }, { key: '2-2', title: 'Child Node4', description: 'Child Node4 description', }, ], }, ]; function App() { const [targetKeys, setTargetKeys] = useState([]); const handleChange = (newTargetKeys) => { setTargetKeys(newTargetKeys); }; return ( <Transfer dataSource={dataSource} showSearch targetKeys={targetKeys} onChange={handleChange} render={item => item.title} /> ); } export default App; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值