基于antd封装的穿梭框

39 阅读4分钟
import { Space, Button, Transfer, Empty, Tree } from 'antd';
import {
  Form,
  Icon,
  Input,
  Loading,
  Modal
} from 'antd';
import empty from '@/Images/others/empty.png';
const lodash = require('lodash');
const { cloneDeep, isEmpty } = lodash;
import { useEffect, useState } from 'react';
import { connect } from 'dva';
import styles from './index.less';

interface Props {
  onCancel: () => void;
  submitRegion: (data: any) => void;
  aList: any[];
}

const SelectRegion = (props: Props) => {
  const { onCancel, aList } = props;

  // 封装后的树形数据
  const [newTree, setNewTree] = useState<any>();

  // 搜索后的tree列表
  const [searchTree, setSearchTree] = useState<Array<any>>([]);

  // 搜索框中的值
  const [searchValue, setSearchValue] = useState<string>('');

  // 展开的树节点id
  const [expandedKeys, setExpandedKeys] = useState<Array<React.Key>>([]);

  // 是否自动展开树节点的父节点
  const [autoExpandParent, setAutoExpandParent] = useState(true);

  // 当前选中的规格项
  const [targetKeys, setTargetKeys] = useState<any>([]);

  // 平铺数组
  const [newDataSource, setNewDataSource] = useState<Array<any>>([]);

  // // 原始的平铺数组
  // const [originalData, setOriginalData] = useState<Array<any>>([]);

  // form表单实例
  const [forms] = Form.useForm();

  // 实时转平铺数组
  useEffect(() => {
    if (aList) {
      const tempList: Array<any> = [];
      const flatten = (list: Array<any>) => {
        if (Array.isArray(list) && list.length > 0) {
          list.forEach((item: any) => {
            tempList.push(item);
            flatten(item.children);
          });
        }
      };
      flatten(aList);
      setNewDataSource(tempList);
    }
    setNewTree(aList);
  }, [aList]);

  /**
   * 封装树形数据方法
   * @param tree 要封装的数据数树
   * @returns 返回封装的数组
   */
  const _packageTree = (tree: Array<any>) => {
    if (Array.isArray(tree) && tree.length > 0) {
      const list: Array<any> = tree.map((item: any) => ({
        title: item.fullName,
        key: item.deptId,
        deptId: item.deptId,
        children: item.childrenDeptList
          ? _packageTree(item.childrenDeptList)
          : null
      }));
      return list;
    }
  };

  // 删除按钮样式
  const transferTitles = [
    '',
    <Icon
      icon="deleteIcon"
      onClick={() => {
        setTargetKeys([]);
      }}
    />
  ];

  // 关键字搜索树数据回调
  useEffect(() => {
    if (newTree && Array.isArray(newTree) && newTree.length > 0) {
      const loop = (data: Array<any>): Array<any> =>
        data.map((item) => {
          const strTitle = item.title;
          const index = strTitle.indexOf(searchValue);
          const beforeStr = strTitle.substring(0, index);
          const afterStr = strTitle.slice(index + searchValue.length);
          const title =
            index > -1 ? (
              <span>
                {beforeStr}
                <span className={styles.selectRegion_searchText}>
                  {searchValue}
                </span>
                {afterStr}
              </span>
            ) : (
              <span>{strTitle}</span>
            );
          if (item.children) {
            return { title, key: item.key, children: loop(item.children) };
          }
          return {
            title,
            key: item.key
          };
        });
      const treeData = loop(newTree);
      setSearchTree(treeData);
    }
  }, [searchValue]);

  /**
   * @method 选项在两栏间转移的回调
   * @param {Array} keys 右栏当前数组
   */
  const _handleTransferChange = (keys: any) => {
    setTargetKeys(keys);
  };

  /**
   * @method 搜索的回调
   * @param {String} value 当前搜索框的值
   */
  const _handleSearch = (value: string) => {
    if (value) {
      const newExpandedKeys: Array<any> = newDataSource
        .map((item: any) => {
          if (item.title.indexOf(value) > -1) {
            return _getParentKey(item.key, newDataSource);
          }
          return null;
        })
        .filter(
          (item: any, i: number, self: Array<any>) =>
            item && self.indexOf(item) === i
        );
      setExpandedKeys(newExpandedKeys);
      setAutoExpandParent(true);
    } else {
      setExpandedKeys([]);
    }
    setSearchValue(value);
  };

  /**
   * @method 展开树的回调
   * @param {Array} keys 当前展开的key数组
   */
  const _handleExpand = (keys: React.Key[]) => {
    setExpandedKeys(keys);
    setAutoExpandParent(false);
  };

  /**
   * @method 根据节点的key获取其父节点的key
   * @param {Number} key 节点的key值
   * @param {Array} tree 树数据
   * @return 返回展开的的父节点
   */
  const _getParentKey = (key: number, tree: Array<any>): React.Key => {
    let parentKey: React.Key = '';
    for (let i = 0; i < tree.length; i++) {
      const node = tree[i];
      if (node.children) {
        if (node.children.some((item: any) => item.key === key)) {
          parentKey = node.key;
        } else if (_getParentKey(key, node.children)) {
          parentKey = _getParentKey(key, node.children);
        }
      }
    }
    return parentKey;
  };

  /**
   * 生成搜索后的tree列表
   * @param tree
   * @param checkedKeys
   * @returns 返回搜索后的tree列表
   */
  const _generateTree = (tree: any, checkedKeys: any) => {
    let list: any = [];
    if (Array.isArray(tree) && tree.length > 0) {
      list = tree.map(({ children, ...props }) => ({
        ...props,
        disabled: checkedKeys.includes(props.key),
        children: _generateTree(children, checkedKeys)
      }));
    }
    return list;
  };

  /**
   * 选中方法
   * @param selectedKeys 当前已选择的数组
   * @param key 循环遍历的每一个树数据的key
   * @returns 返回选中状态
   */
  const isChecked = (selectedKeys: any, key: any) => selectedKeys.includes(key);

  /**
   * 提交区域
   */
  const _submitRegion = () => {
    console.log('222');
    const targetList: any[] = [];
    newDataSource.forEach((item: any) => {
      if (targetKeys.includes(item.key)) {
        targetList.push(item);
      }
    });
    console.log('targetList', targetList);
  };

  console.log('newDataSource11', newDataSource);

  return (
    <Modal
      title={intl.formatMessage({ id: 'Select the country/region' })}
      canDragm
      visible
      onCancel={onCancel}
      footer={
        <Space>
          <Button type="primary" onClick={_submitRegion}>
            {intl.formatMessage({ id: 'OK' })}
          </Button>
          <Button onClick={onCancel}>
            {intl.formatMessage({ id: 'Cancel' })}
          </Button>
        </Space>
      }
      className={styles.selectRegion}
    >
      <Transfer
        className={styles.selectRegion_transfers}
        oneWay
        showSelectAll={false}
        selectAllLabels={['', intl.formatMessage({ id: 'Selected3' })]}
        titles={transferTitles}
        dataSource={newDataSource}
        targetKeys={targetKeys}
        render={(item: any) => item.title}
        onChange={_handleTransferChange}
      >
        {({ direction, onItemSelect, selectedKeys }) => {
          if (direction === 'left') {
            const checkedKeys: any = [...selectedKeys, ...targetKeys];
            return (
              <>
                <Input
                  className={styles.selectRegion_input}
                  allowClear
                  value={searchValue}
                  onChange={_handleSearch}
                  onSearch={_handleSearch}
                  placeholder={intl.formatMessage({
                    id: 'Please enter...'
                  })}
                />
                {isEmpty(searchTree) ? (
                  <Empty
                    image={empty}
                    className={styles.selectRegion_empty}
                    description={intl.formatMessage({ id: 'No data' })}
                  />
                ) : (
                  <Tree
                    className={styles.selectRegion_tree}
                    blockNode
                    checkable
                    checkStrictly
                    defaultExpandAll
                    checkedKeys={checkedKeys}
                    treeData={_generateTree(searchTree, targetKeys)}
                    expandedKeys={expandedKeys}
                    autoExpandParent={autoExpandParent}
                    onExpand={_handleExpand}
                    onCheck={(_, { node: { key } }) => {
                      onItemSelect(key as any, !isChecked(checkedKeys, key));
                    }}
                    onSelect={(_, { node: { key } }) => {
                      onItemSelect(key as any, !isChecked(checkedKeys, key));
                    }}
                  />
                )}
              </>
            );
          }
        }}
      </Transfer>
      {/* </Form.Item>
      </Form> */}
    </Modal>
  );
};

export default connect(({ saleCountry }: any) => {
  const { aList } = saleCountry;
  return {
    aList
  };
})(SelectRegion);

CSS样式

.selectRegion {
  width: 720px !important;

  :global {
    .ant4-modal-footer {
      button {
        width: 80px;
        height: 30px;
      }
    }

    .ant-transfer {
      .ant-transfer-list {
        margin-top: 7px;
        width: 260px !important;
        height: 425px !important;

        .ant-transfer-list-header {
          background-color: #f2f3f5;
        }

        .ant-transfer-list-body {
          height: 440px;
          padding: 10px;

          .ant-tree-node-selected {
            background-color: #fff;
          }

          .ant-empty-image {
            margin-bottom: 6px;
          }

          .ant-empty-description {
            color: rgba(0, 0, 0, 0.7);
          }

          .ant-transfer-list-content-item {
            &:hover {
              background-color: #edf8ff;

              .ant-transfer-list-content-item-remove {
                visibility: visible;
              }
            }

            .ant-transfer-list-content-item-remove {
              visibility: hidden;

              .anticon-delete {
                svg {
                  color: #999;
                  border-radius: 50%;

                  path {
                    d: path('M513.43007 1019.262092c-280.20375 0-507.388982-227.207745-507.388982-507.410472 0-280.224216 227.185232-507.409448 507.388982-507.409448 280.247752 0 507.391029 227.185232 507.391029 507.409448C1020.821099 792.054347 793.678846 1019.262092 513.43007 1019.262092zM746.107387 363.903034c9.540284-9.53926 9.540284-25.021883 0-34.539654l-51.822272-51.800783c-9.535167-9.558703-24.977881-9.558703-34.518165 0L512.976746 424.334381 366.184495 277.562597c-9.53619-9.558703-24.977881-9.558703-34.518165 0l-51.822272 51.800783c-9.538237 9.517771-9.538237 25.001417 0 34.539654l146.793274 146.770761-146.793274 146.790204c-9.538237 9.518794-9.538237 25.004487 0 34.540677l51.822272 51.79976c9.540284 9.538237 24.981974 9.538237 34.518165 0L512.976746 597.014232l146.790204 146.790204c9.540284 9.538237 24.982998 9.538237 34.518165 0l51.822272-51.79976c9.540284-9.53619 9.540284-25.021883 0-34.540677L599.317183 510.674818 746.107387 363.903034z'
                      );
                  }
                }
              }
            }
          }
        }

        &:first-child {

          // .ant-transfer-list-content {
          //   width: 210px;
          //   margin-top: 5px;
          //   border-top: 1px solid rgb(214, 214, 214);
          // }
          .ant-transfer-list-header {
            display: none;
          }

          .ant-transfer-list-body-search-wrapper {
            position: relative;
            flex: none;
            padding: 0;
            width: 250px;
            margin-top: -6px;
            margin-left: -6px;
          }
        }

        &:last-child {
          .ant-transfer-list-header {
            background-color: rgba(242, 243, 245, 1);
          }

          .ant-transfer-list-body-search-wrapper {
            display: none;
          }
        }
      }

      .ant-transfer-operation {
        width: 50px;
        height: 50px;

        button {
          margin-bottom: 0;
          width: 100%;
          height: 100%;
          border-radius: 50%;
          color: #0183cc;
          background-color: #fff;
          border-color: #2993ce;

          .anticon-right {
            svg {
              font-size: 22px;

              path {
                d: path('M885.113 489.373L628.338 232.599c-12.496-12.497-32.758-12.497-45.254 0-12.497 12.497-12.497 32.758 0 45.255l203.3 203.3H158.025c-17.036 0-30.846 13.811-30.846 30.846 0 17.036 13.811 30.846 30.846 30.846h628.36L583.084 746.147c-12.497 12.496-12.497 32.758 0 45.255 6.248 6.248 14.438 9.372 22.627 9.372s16.379-3.124 22.627-9.372l256.775-256.775a31.999 31.999 0 0 0 0-45.254z'
                  );
              }
            }
          }

          &:hover {
            color: #2993ce !important;
            background-color: #eef9ff !important;
            border-color: #1394dd !important;
          }

          &:active {
            color: #287bab !important;
            background-color: #f7faff !important;
            border-color: #287bab !important;
          }

          &[disabled] {
            color: rgba(0, 0, 0, 0.25) !important;
            border-color: #d9d9d9 !important;
            background-color: #f5f5f5 !important;
          }
        }
      }
    }
  }

  // .selectRegion_form {
  .selectRegion_transfers {
    margin-left: 5px;

    .selectRegion_searchText {
      color: #ff0000;
    }

    .selectRegion_input {
      width: 287px;
      margin-left: -4.5px;
      margin-top: -5px;
      border-radius: 0;
    }

    .selectRegion_empty {
      padding-top: 47%;
    }

    .selectRegion_tree {
      padding-top: 5px;
      margin-left: -5px;
      border-top: 1px solid rgb(240, 240, 240);
      margin-top: 5px;
      width: 300px;
      height: 388px;
      overflow-y: auto;
      border-radius: 0;
    }
  }

  // }
}