Transfer穿梭框自定义使用

155 阅读3分钟
import React, {
  useEffect,
  useCallback,
  useState,
  useRef,
  Fragment
} from 'react';
import {Form, Modal, Transfer, Checkbox, Empty, Button, message } from 'antd';
import { getSysUserByPage } from '@/services';
import empty from '@/Images/empty.png';
import './index.less';

const lodash = require('lodash');
const { isEmpty } = lodash;

// 组件接收数据
interface PropsType {
  // 选择用户的弹窗标题
  title: string;
  // 取消事件
  onCancel: () => void;
  // 最大用户,如果不限制最大用户,可以不传该字段
  maxCount?: number;
  // 点击确定,返回选择的用户信息
  onConfirm: (userIdArr: Array<UserType>) => void;
}

const SelectUser = (props: PropsType) => {
  const { title = intl.formatMessage({ id: 'Select Person' }), onCancel, maxCount, onConfirm } = props;

  const [formInstance] = Form.useForm();

  // 查询的分页信息
  const [pageInfo, setPageInfo] = useState({ pageNum: 1, pageSize: 100 });

  // 模糊搜索关键字
  const [searchKey, setSearchKey] = useState('');

  // 是否loading
  const [isLoading, setIsLoading] = useState(false);

  // 用户列表
  const [userList, setUserList] = useState<Array<UserType>>([]);

  // 当前选中的用户
  const [targetKeys, setTargetKeys] = useState<string[]>([]);
  const [selectedKeys, setSelectedKeys] = useState<any>([]);

  const userRef = useRef();


  /**
   * 分页获取用户列表I
   * pageNum: 页码
   * pageSize: 每页条数
   * key: 模糊搜索关键字
   */
  const getUsers = useCallback(
    async (pageNum: number, pageSize: number, key: string) => {
      setIsLoading(true);
      const res = await getSysUserByPage({
        pageNum,
        pageSize,
        syncStatus: true,
        nameSearchKey: key || undefined
      });
      const { code, data, msg } = res || {};
      if (code === 0) {
        const { current, size, records } = data || {};
        if (Array.isArray(records)) {
          if (records.length > 0) {
            setPageInfo({ pageNum: current, pageSize: size });
            const newRecord = records.map((item: UserType) => ({
              ...item,
              userId: `${item.userId}`
            }));
            if (pageNum === 1) {
              // const tempSelectedKeys = selectedKeys.filter(key => newRecord.some(item => item.userId === key));
              // setTargetKeys(selectedKeys);
              setUserList(newRecord);
              // setSelectedKeys(selectedKeys);

            } else {
              // const tempSelectedKeys = selectedKeys.filter(key => [...userList, ...newRecord].some(item => item.userId === key));
              // setTargetKeys(selectedKeys);
              setUserList([...userList, ...newRecord]);
              // setSelectedKeys(selectedKeys);


            }
          } else if (records.length === 0) {
            if (pageNum > 1) {
              getUsers(pageNum - 1, pageSize, key);
            } else {
              setPageInfo({ pageNum: 1, pageSize: size });
              // setTargetKeys(selectedKeys);
              setUserList([]);
              // setTargetKeys([]);
              // setSelectedKeys(selectedKeys);

            }
          }
        }
      } else {
        message.error(msg);
      }
      setIsLoading(false);
    },
    [userList]
  );

  useEffect(() => {
    getUsers(1, pageInfo.pageSize, searchKey);
  }, []);

  /**
   * 滑动自动加载下一页
   */
  const _onScroll = () => {
    if (userRef.current) {
      const scrollTop = userRef.current.scrollTop;
      const clientHeight = userRef.current.clientHeight;
      const scrollHeight = userRef.current.scrollHeight;
      if (scrollHeight - clientHeight - scrollTop === 0 && !isLoading) {
        getUsers(pageInfo.pageNum + 1, pageInfo.pageSize, searchKey);
      }
    }
  };

  /**
   * 点击确定
   */
  const buttonClick = () => {
    formInstance.validateFields().then((res) => {
      const { userIdArr } = res;
      const result: Array<UserType> = userIdArr.map((id: string) => {
        return userList.find((item: UserType) => id === item.userId);
      });
      onConfirm &&
        onConfirm(
          result.map((item: UserType) => ({
            ...item,
            userId: Number(item.userId)
          }))
        );
    });
  };

  /**
   * 点击右侧清空,清空选择项,重置表单
   */
  const _handleClear = () => {
    setTargetKeys([]);
    formInstance.resetFields();
  };

  /**
   * 点击穿梭框中间的按钮
   * @param keys 选择用户的userId
   * @returns
   */
  const _handleTransferChange = (keys: Array<string>) => {
    if (maxCount && keys.length > maxCount) {
      message.warn(intl.formatMessage({ id: 'Up to x persons can be selected. Please check and try again' }, { count: maxCount }));
      return;
    }
    console.log('keys', keys);
    setTargetKeys(keys);
    setSelectedKeys(keys);
  };

  /**
   * 点击左侧,模糊搜索按钮
   * @param value 模糊搜索关键字
   */
  const _handleSearch = (value: string) => {
    // _handleClear();
    setSearchKey(value);
    getUsers(1, pageInfo.pageSize, value);
  };

  return (
    <Modal
      visible
      centered
      canDragm
      title={title}
      footer={[
        <Button
          key="1"
          btnType="primary"
          style={{ marginRight: '10px' }}
          onClick={buttonClick}
        >
          {intl.formatMessage({ id: 'OK' })}
        </Button>,
        <Button key="2" onClick={onCancel}>
          {intl.formatMessage({ id: 'Cancel' })}
        </Button>
      ]}
      onCancel={onCancel}
      destroyOnClose
      className="component_selectUser_modal"
    >
      <Loading loading={isLoading}>
        <Form form={formInstance}>
          <Form.Item
            className="component_selectUser_formItem"
            label={intl.formatMessage({ id: 'Select Person' })}
            name="userIdArr"
            rules={[{ required: true, message: `${title}` }]}
          >
            <Transfer
              oneWay
              selectAllLabels={['', intl.formatMessage({ id: 'Selected' })]}
              showSelectAll={false}
              titles={[
                '',
                <Icon icon="deleteIcon" onClick={_handleClear} />
              ]}
              rowKey={(item: UserType) => item.userId}
              dataSource={userList}
              targetKeys={targetKeys}
              // selectedKeys={targetKeys}
              render={(item: UserType) => `${item.name}(${item.username})`}
              onChange={_handleTransferChange}
            >
              {({ direction, onItemSelect, selectedKeys }) => {
                if (direction === 'left') {
                  const checkedKeys: Array<string> = [
                    ...selectedKeys,
                    ...targetKeys
                  ];
                  return (
                    <Fragment>
                      <input className="component_selectUser_hiddenInput" />
                      <Input
                        className="component_selectUser_input"
                        allowClear
                        placeholder={intl.formatMessage({ id: 'Please enter search criteria' })}
                        onSearch={_handleSearch}
                        onEnterPress={_handleSearch}
                        onChange={(value: string) => setSearchKey(value)}
                        value={searchKey}
                      />
                      <Divider className="component_selectUser_divider" />
                      {isEmpty(userList) ? (
                        <Empty
                          className="component_selectUser_empty"
                          image={empty}
                          description={intl.formatMessage({ id: 'No data' })}
                        />
                      ) : (
                        <div
                          ref={userRef}
                          onScrollCapture={_onScroll}
                          className="component_selectUser_container"
                        >
                          <Checkbox.Group
                            value={checkedKeys}
                            className="component_selectUser_checkboxGroup"
                          >
                            {userList.map((item: UserType) => {
                              const { userId, name, username } = item;
                              const title = `${name || intl.formatMessage({ id: 'N/A' })}(${username || intl.formatMessage({ id: 'N/A' })})`;
                              return (
                                <Checkbox
                                  key={userId}
                                  value={userId}
                                  onChange={(e) => {
                                    const { checked, value } = e.target;
                                    if (
                                      (maxCount &&
                                        checkedKeys.length < maxCount) ||
                                      checkedKeys.includes(value) ||
                                      !maxCount
                                    ) {
                                      onItemSelect(value, checked);
                                    } else if (maxCount) {
                                      message.warn(intl.formatMessage({ id: 'Up to x persons can be selected. Please check and try again' }, { count: maxCount }));
                                    }
                                  }}
                                  className="component_selectUser_checkbox"
                                >
                                  <span title={title}>{title}</span>
                                </Checkbox>
                              );
                            })}
                          </Checkbox.Group>
                        </div>
                      )}
                    </Fragment>
                  );
                }
              }}
            </Transfer>
          </Form.Item>
        </Form>
      </Loading>
    </Modal>
  );
};

export default SelectUser;


样式

.component_selectUser_modal {
  width: 700px !important;

  .component_selectUser_formItem {
    flex-wrap: nowrap;

    >.ant4-form-item-control {
      width: calc(100% - 107px);
    }

    .component_selectUser_hiddenInput {
      display: none;
    }

    .component_selectUser_input {
      width: 100%;
    }

    .component_selectUser_divider {
      margin: 10px 0;
    }

    .component_selectUser_empty {
      margin-top: 144px;
      text-align: center;
    }

    .ant-transfer {
      .component_selectUser_container {
        width: 100%;
        height: calc(100% - 51px);
        overflow: auto;

        .component_selectUser_checkboxGroup {
          width: 100%;

          .component_selectUser_checkbox {
            width: 100%;
            height: 30px;
            display: flex;
            align-items: center;

            &:hover {
              background-color: #edf8ff;
            }

            >span {
              &:first-child {
                top: 0;
              }

              &:last-child {
                display: inline-block;
                width: calc(100% - 32px);
                text-overflow: ellipsis;
                overflow: hidden;
                white-space: nowrap;
              }
            }
          }
        }
      }

      .ant-transfer-list {
        width: calc(50% - 33px);

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

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

          .ant-transfer-list-body-customize-wrapper {
            height: 100%;
          }

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

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

          .ant-checkbox-wrapper {
            margin-left: 0;
          }

          .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-header {
            display: none;
          }
        }

        &:last-child {
          .ant-transfer-list-body-not-found {
            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;
          }
        }
      }
    }
  }
}