自定义树形穿梭框组件

因项目需求是库表结构,这里做的是动态加载子选项,默认不显示表名选项,点击库名再去动态加载并显示该库中的所有表,效果如下:

.ts代码:

// 自定义树形穿梭框
import React, { useEffect, useState } from 'react';
import { Tree, Checkbox, Button, Input, message } from 'antd';
import { CaretDownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import './TreeSelectTransfer.less';
import { ApiRes } from '@/api/config';

const { Search } = Input;
const { TreeNode } = Tree;

const getLeftAndRightKey = (data, rightDataKey = {}) => {
    let len = data.length;
    let leftData: any = new Array(len).fill('').map(() => {
        return { children: [] };
    });
    let rightData: any = new Array(len).fill('').map(() => {
        return { children: [] };
    });

    let leftTotalCount = 0;
    let rightTotalCount = 0;
    data.map((item, index) => {
        if (!item.children) {
            leftData[index].name = item.name;
            leftData[index].key = index + '';
        } else {
            item.children.map((childItem, childIndex) => {
                let nowKey = `${childItem.id}`;
                if (nowKey in rightDataKey) {
                    rightData[index].name = item.name;
                    rightData[index].key = index + '';
                    rightData[index].children.push(childItem);
                    rightTotalCount += 1;
                } else {
                    leftTotalCount += 1;
                    leftData[index].name = item.name;
                    leftData[index].key = index + '';
                    leftData[index].children.push(childItem);
                }
            });
        }
    });
    return {
        leftData,
        rightData,
        leftTotalCount,
        rightTotalCount
    };
};

// 搜索过滤
const filterData = (data, filterValue) => {
    let filterTrimV = filterValue.trim();
    if (!filterTrimV) {
        return data;
    }
    data = data.filter((item) => {
        if (item.name && item.name.includes(filterTrimV)) {
            return true;
        } else if (item.children && item.children.length) {
            item.children = item.children.filter((childItem) => {
                if (childItem.name.includes(filterTrimV)) {
                    return true;
                } else {
                    return false;
                }
            });
            return item.children.length > 0;
        }
        return true;
    });
    return data;
};

function TreeSelectTransfer(props) {
    const [leftSelectKey, setLeftSelectKey] = useState({});
    const [rightDataKey, setRightDataKey] = useState({});
    const [rightSelectKey, setRightSelectKey] = useState({});
    const [leftFValue, setLeftFValue] = useState('');
    const [rightFValue, setRightFValue] = useState('');
    const [selectKeys, setSelectKeys] = useState([]);

    const [dataSource, setDataSource] = useState([]);
    const [data, setData] = useState([]);
    const [childrenData, setChildrenData] = useState([]);
    const [fLeftData, setFLeftData] = useState([]);
    const [leftData, setLeftData] = useState([]);
    const [rightData, setRightData] = useState([]);
    const [fRightData, setFRightData] = useState([]);
    const [leftSelectAll, setLeftSelectAll] = useState(true);
    const [rightSelectAll, setRightSelectAll] = useState(true);
    const [leftTotalCount, setLeftTotalCount] = useState(null);
    const [rightTotalCount, setRightTotalCount] = useState(null);
    const { titles = [] } = props;

    // 全选
    const selectOrCancelAll = (type, selectKeys, data, isSelectAll) => () => {
        if (isSelectAll) {
            if (type === 'leftSelectKey') {
                setLeftSelectKey({});
            } else {
                setRightSelectKey({});
            }
        } else {
            let newSelectKeys = { ...selectKeys };
            data.map((item) => {
                item.children.map((childItem) => {
                    if (!(childItem.key in newSelectKeys)) {
                        newSelectKeys[childItem.id] = childItem.name;
                    }
                });
            });
            if (type === 'leftSelectKey') {
                setLeftSelectKey(newSelectKeys);
            } else {
                setRightSelectKey(newSelectKeys);
            }
        }
    };

    const selectGoToRight = () => {
        const { handleChangeTable } = props;
        const dataKey = { ...rightDataKey, ...leftSelectKey };
        let targetKeys = Object.keys(dataKey);
        setLeftSelectKey({});
        setRightDataKey(dataKey);
        if (typeof handleChangeTable == 'function') {
            handleChangeTable(targetKeys, 'right', selectKeys);
        }
    };
    const selectGoToLeft = () => {
        const { handleChangeTable } = props;
        for (let key in rightSelectKey) {
            delete rightDataKey[key];
        }
        let targetKeys = Object.keys(rightDataKey);
        setRightSelectKey({});
        setRightDataKey(rightDataKey);

        if (typeof handleChangeTable == 'function') {
            handleChangeTable(targetKeys, 'left', selectKeys);
        }
    };

    const bindCheckLeft = (checkedKeys, e) => {
        if (!checkedKeys.length) {
            setLeftSelectKey({});
        }
        if (e.checkedNodes.length) {
            const temp = {};
            const keys = [];
            e.checkedNodes.map((item) => {
                if (!item.children) {
                    temp[item.key] = item.title;
                    keys.push(item.key);
                }
            });
            setLeftSelectKey(temp);
            setSelectKeys(keys);
        }
    };
    const bindCheckRight = (checkedKeys, e) => {
        if (!checkedKeys.length) {
            setRightSelectKey({});
        }
        if (e.checkedNodes.length) {
            const temp = {};
            const keys = [];
            e.checkedNodes.map((item) => {
                if (!item.children) {
                    temp[item.key] = item.title;
                    keys.push(item.key);
                }
            });
            setRightSelectKey(temp);
            setSelectKeys(keys);
        }
    };
    const searchOnChange = (type, e) => {
        const { value } = e.target;
        if (type == 'left') {
            setLeftFValue(value);
        } else {
            setRightFValue(value);
        }
    };

    const onSelectNode = (selectedKeys, node) => {
        const { Api, dbId } = props.childrenParams;
        Api({ dbId, schemaName: node.node.title }).then((res: ApiRes) => {
            if (res.code === 200) {
                const temp =
                    res.data.length &&
                    res.data.map((item, index) => {
                        return {
                            id: item.tableId,
                            key: `${selectedKeys}-${index}`,
                            name: item.tableName
                        };
                    });
                dataSource.length &&
                    dataSource.map((item) => {
                        if (item.name === node.node.title) {
                            item.children = temp;
                        }
                    });
                setChildrenData(temp);
                setData(dataSource);
            } else {
                message.error(res.message);
            }
        });
    };
    useEffect(() => {
        if (props.tableCacheMap.length) {
            setDataSource(props.tableCacheMap);
            setData(props.tableCacheMap);
        }
    }, [props.tableCacheMap]);
    useEffect(() => {
        let { leftData, rightData, leftTotalCount, rightTotalCount } = getLeftAndRightKey(data, rightDataKey);
        setLeftData(leftData);
        setRightData(rightData);
        setLeftTotalCount(leftTotalCount);
        setRightTotalCount(rightTotalCount);
        setLeftSelectAll(leftSelectCount == leftTotalCount);
        setRightSelectAll(rightSelectCount == rightTotalCount);

        const leftList = filterData(leftData, leftFValue);
        const rightList = filterData(rightData, rightFValue);
        setFLeftData(leftList);
        setFRightData(rightList);
        if (leftSelectCount === 0) {
            setLeftSelectAll(false);
        }
        if (rightSelectCount === 0) {
            setRightSelectAll(false);
        }
    }, [data, childrenData, Object.keys(rightDataKey).length, leftFValue, rightFValue, leftSelectKey, rightSelectKey]);
    useEffect(() => {}, [leftData, rightData, leftFValue]);

    let leftSelectCount = Object.keys(leftSelectKey).length;
    let rightSelectCount = Object.keys(rightSelectKey).length;
    let isHaveLeftChecked = leftSelectCount > 0;
    let isHaveRightChecked = rightSelectCount > 0;

    return (
        <div className="tree-select-transfer">
            <div className="tst-l">
                <div className="tst-header">
                    <Checkbox
                        indeterminate={!leftSelectAll && isHaveLeftChecked}
                        onChange={selectOrCancelAll('leftSelectKey', leftSelectKey, leftData, leftSelectAll)}
                        checked={leftSelectAll}
                    >
                        {`${leftSelectCount ? leftSelectCount + '/' : ''}${leftTotalCount} 项`}
                    </Checkbox>
                    {titles[0] ? <span style={{ float: 'right' }}>{titles[0]}</span> : null}
                </div>
                <Search style={{ marginBottom: 8 }} placeholder="Search" onChange={(e) => searchOnChange('left', e)} />
                <div style={{ overflow: 'auto', height: '300px' }}>
                    <Tree
                        checkable
                        showIcon
                        defaultExpandAll
                        onCheck={bindCheckLeft}
                        checkedKeys={Object.keys(leftSelectKey)}
                        switcherIcon={<CaretDownOutlined />}
                        onSelect={onSelectNode}
                    >
                        {fLeftData.map((item) => {
                            if (!!item.name) {
                                return (
                                    <TreeNode title={item.name} key={item.key} checkable={!!item.children.length}>
                                        {item.children.map((childItem) => {
                                            return <TreeNode key={childItem.id} title={childItem.name} />;
                                        })}
                                    </TreeNode>
                                );
                            }
                        })}
                    </Tree>
                </div>
            </div>
            <div className="tst-m ">
                <Button
                    type="primary"
                    style={{ marginBottom: 4 }}
                    disabled={!isHaveLeftChecked}
                    onClick={selectGoToRight}
                >
                    <RightOutlined />
                </Button>
                <Button type="primary" disabled={!isHaveRightChecked} onClick={selectGoToLeft}>
                    <LeftOutlined />
                </Button>
            </div>
            <div className="tst-r">
                <div className="tst-header">
                    <Checkbox
                        indeterminate={!rightSelectAll && isHaveRightChecked}
                        checked={rightSelectAll}
                        onChange={selectOrCancelAll('rightSelectKey', rightSelectKey, rightData, rightSelectAll)}
                    >
                        {`${rightSelectCount ? rightSelectCount + '/' : ''}${rightTotalCount} 项`}
                    </Checkbox>
                    {titles[1] ? <span style={{ float: 'right' }}>{titles[1]}</span> : null}
                </div>
                <Search style={{ marginBottom: 8 }} placeholder="Search" onChange={(e) => searchOnChange('right', e)} />
                <div style={{ overflow: 'auto', height: '300px' }}>
                    <Tree
                        checkable
                        showIcon
                        defaultExpandAll
                        onCheck={bindCheckRight}
                        checkedKeys={Object.keys(rightSelectKey)}
                        switcherIcon={<CaretDownOutlined />}
                    >
                        {fRightData.map((item) => {
                            if (!!item.name) {
                                return (
                                    <TreeNode title={item.name} key={item.key}>
                                        {item.children.map((childItem) => {
                                            let isChecked = childItem.key in rightSelectKey;
                                            return (
                                                <TreeNode
                                                    icon={<Checkbox checked={isChecked} />}
                                                    key={childItem.id}
                                                    title={childItem.name}
                                                />
                                            );
                                        })}
                                    </TreeNode>
                                );
                            }
                        })}
                    </Tree>
                </div>
            </div>
        </div>
    );
}

export default TreeSelectTransfer;

样式 .less:

.tree-select-transfer {
  overflow: hidden;
  width: 832px;
  height: 400px;
  display: flex;
  align-items: center;
  vertical-align: middle;
    .tst-l, .tst-r {
      background-color: #fff;
      flex: 1;
      border: 1px solid #d9d9d9;
      display: inline-block;
      border-radius: 4px;
      position: relative;
      height: 100%;
      padding: 0 8px;
      padding-top: 40px;
      width: 0;
      .tst-header {
        height: 34px;
        padding: 6px 12px;
        border-radius: 4px 4px 0 0;
        color: #000;
        border-bottom: 1px solid #e8e8e8;
        overflow: hidden;
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
      }
      .ant-tree-node-content-wrapper .ant-tree-iconEle {
        display: none;
      }
      .ant-tree-child-tree .ant-tree-node-content-wrapper .ant-tree-iconEle {
        display: inline-block;
      }
      .ant-tree-child-tree .ant-tree-switcher {
        display: none;
      }
      .ant-input-suffix {
        color: rgba(0,0,0,0.45);
        cursor: pointer;
        &:hover {
          color: rgba(0,0,0,0.8);
        }
      }
    }
    .tst-m {
      display: grid;
      height: 60px;
      margin: 0 20px;
      bottom {
        width: 32px;
      }
    }
}

页面引入:

import React, { useState, useEffect } from 'react';
import { listDsSchemaName, listTablesByDsIdAndSchemaName } from '@/api/offlineTask/index';
import TreeSelectTransfer from '@/components/treeSelectTransfer';

function BigTask(props) {
    const [tableCacheMap, setTableCacheMap] = useState([]);
    
    // 获取所有库名
    function getListDsSchemaName(dbId) {
       ...
    }

    // 数据表API接口及参数
    const childrenParams = {
        Api: listTablesByDsIdAndSchemaName,
        dbId: props.syncForm.dbId
    };

    // 点击库名获取改库下所有数据表
    function handleChangeTable(targetKeys, direction, moveKeys) {
      ...
    }
  
    return (
      <TreeSelectTransfer
         tableCacheMap={tableCacheMap}
         childrenParams={childrenParams}
         handleChangeTable={handleChangeTable}
       />
  )
}
export default BigTask;
原文地址:https://www.cnblogs.com/minjh/p/15127585.html