selectPaging.tsx 10.4 KB
import React from 'react';
import type { MySelectProps } from './index';
import MySelect from './index';
import { UpCircleFilled, DownCircleFilled } from '@ant-design/icons';
import createStorage, {
  destroyStora,
  ADD_StORAGE_TYPE,
  FIND_STORAGE_TYPE,
  QUERY_STORAGE_TYPE,
  MATCH_DATAS_TYPE,
} from './createStorage';

export type serverParamsProps = Record<string, any>;
export interface optionsProps extends serverParamsProps {
  pageNumber: number;
  pageSize: number;
}

export interface MySelectPagingProps extends MySelectProps {
  // 重构onChildError 方法  需要返回要追加的数据
  onChildError?: (keys: string[], list: any[]) => Promise<any | any[]>;
  // 获取下拉数据接口
  serverResult: (options: optionsProps, name: string) => Promise<any>;
  // 下拉接口查询传入参数
  serverParams?: serverParamsProps;
  // 单页数据大小 默认20
  pageSize?: number;
  // option 列表自定义渲染
  optionRender?: (record: any, index?: number) => any;
  // 禁止服务器搜索
  notServer?: boolean;
  // 数据加载成功时触发
  onReady?: (list: any[], initial: boolean) => void;
  // 是否启用 storage 做全局数据缓存
  storage?: string;
  // 禁用列表
  disableds?: string[];
}

/**
 *  Select 深度托管 依赖MySelect
 *  修改1: 数据托管
 *  修改2: 添加页面切换数据
 */
class SelectPaging extends React.Component<MySelectPagingProps> {
  state = {
    currentList: [], // 当前数据
    pageNumber: 1,
    pageSize: 20,
    serverName: '', // 搜索值
    totalCount: 0,
    loading: false,
    isReady: true,  // 是否初次执行
  };

  stora: any = null;
  reducer: any;

  constructor(props: MySelectPagingProps) {
    super(props);
    this.onSearch = this.onSearch.bind(this);
    this.dropdownRender = this.dropdownRender.bind(this);
    const { pageSize, storage, notServer } = this.props;
    if (pageSize) this.state.pageSize = pageSize;
    if (notServer) this.state.pageSize = 0;
    const [stora, reducer] = createStorage(storage);
    this.stora = stora;
    this.reducer = reducer;
  }

  onSearch(val: string = '') {
    const { notServer } = this.props;
    if (notServer) {
      return;
    }
    this.setState(
      {
        serverName: val,
      },
      () => {
        this.getServerResult();
      },
    );
  }

  componentDidMount() {
    this.getServerResult();
  }

  componentWillUnmount() {
    const { storage } = this.props;
    // 判断是否为临时 stora 库, 并销毁当前 stora 库
    if (this.stora && !storage) {
      destroyStora(this.stora.type);
    }
  }

  /**
   * 加载数据
   */
  getServerResult() {
    const { pageSize, pageNumber, serverName, isReady } = this.state;
    const { serverResult, serverParams, onReady } = this.props;
    const params = { pageNumber, pageSize, ...serverParams };
    // 判断是否启动storage 尝试从 stora中获取数据
    if (this.stora) {
      const data = this.reducer({ type: FIND_STORAGE_TYPE, params: { ...params, name: serverName } });
      if (data) {
        const { result, totalCount } = data;
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
        onReady && onReady(result, isReady);
        this.setState({
          currentList: result,
          totalCount,
          isReady: false,
        });
        return;
      }
    }
    this.setState({
      loading: true,
    });
    serverResult(params, serverName)
      .then((data: any) => {
        const { result, totalCount } = data;
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
        onReady && onReady(result, isReady);
        this.setState({
          currentList: result,
          totalCount,
        });
        // 判断是否启动storage  将数据存入stora 
        if (this.stora) {
          this.reducer({ type: ADD_StORAGE_TYPE, params: { ...params, name: serverName }, result, totalCount })
        }
      })
      .finally(() => {
        this.setState({ loading: false, isReady: false });
      });
  }

  render() {
    const { currentList, loading } = this.state;
    const { onChange, onChildError, optionRender, notServer, storage, disableds, ...props } = this.props;
    const { datas } = this.stora;
    return (
      <MySelect
        {...props}
        optionFilterProp="children"
        options={undefined}
        loading={loading}
        onSearch={this.onSearch}
        // 手动完成 list数据筛选集合
        onSearchProt={() => {
          this.setState({
            totalCount: 0,
            pageNumber: 1,
          })
        }}
        onChange={(vals: string) => {
          // 多选模式下,数据列表从 stora 中获取
          if (Array.isArray(vals)) {
            // eslint-disable-next-line @typescript-eslint/no-unused-expressions
            onChange && onChange(vals, this.reducer({ type: QUERY_STORAGE_TYPE, vals }));
            return;
          }
          // eslint-disable-next-line @typescript-eslint/no-unused-expressions
          onChange && onChange(vals, datas[vals] || {});
        }}
        onChildError={async (keys: string[], children: any[]): Promise<any> => {
          // eslint-disable-next-line no-param-reassign
          keys = this.storaErrorFilter(keys);
          if (!keys.length) return;
          // eslint-disable-next-line no-param-reassign
          keys = await this.onChildErorr(keys, children);
          if (!keys.length) return;
          this.errorOnChange(keys);
        }}
        dropdownRender={this.dropdownRender}
      >
        {currentList && currentList.map((item: any, index: number) => {
          if (optionRender) {
            return optionRender(item, index);
          }
          const { id, name } = item;
          const disabled = disableds && disableds.some(key => key === id)
          return (
            <MySelect.Option key={id} value={id} disabled={disabled}>
              {name}
            </MySelect.Option>
          );
        })}
      </MySelect>
    );
  }

  /**
   * 对于已删除无法匹配到数据,尝试抛出 onChange 事件修正数据
   */
  errorOnChange(keys: string[]) {
    const { mode, value, onChange } = this.props;
    if (mode === 'multiple' || mode === 'tags') {
      // 多选模式
      if (keys.length === value.length) {
        if(onChange) onChange(undefined, []);
      } else {
        // 筛选出已匹配成功数据
        const successList = value.filter((id: string) => !keys.some((Eid) => id === Eid))
        if(onChange) onChange(successList, this.reducer({ type: QUERY_STORAGE_TYPE, vals: successList }))
      }
    } else {
      // 单选模式
      // eslint-disable-next-line no-lonely-if
      if(onChange) onChange(undefined, { value: '' });       // 触发onChange 事件尝试清空错误值
    }
  }

  /**
   * 在stora 库中检查能否匹配到数据
   */
  storaErrorFilter(keys: string[]) {
    const { currentList } = this.state;
    const { error, list } = this.reducer({
      type: MATCH_DATAS_TYPE,
      vals: keys,
    })
    if (list.length) {
      this.setState({
        currentList: [...list, ...currentList],
      })
    }
    return error;
  }

  /**
   *  尝试抛出 onChildError 事件,期待用户返回正确未匹配到内容
   */
  async onChildErorr(keys: string[], children: any[]) {
    const { onChildError } = this.props;
    const { currentList } = this.state;
    if (onChildError) {
      // 尝试通过 onChildError 方法获取未捕获内容
      const options = await onChildError(keys, children);
      if (options) {
        if (Array.isArray(options)) {
          this.setState({
            currentList: [...options, ...currentList],
          });
          this.reducer({ type: ADD_StORAGE_TYPE, params: {}, result: options })
          // 返回未匹配到数据
          // eslint-disable-next-line no-param-reassign
          keys = keys.filter((id) => !options.some((item) => item.id === id));
        } else {
          this.setState({
            currentList: [options, ...currentList],
          });
          this.reducer({ type: ADD_StORAGE_TYPE, params: {}, result: [options] })
          // eslint-disable-next-line no-param-reassign
          keys = keys.filter((id) => options.id !== id);
        }
      }
    }
    return keys;
  }

  dropdownRender(menu: React.ReactElement) {
    const { loading, pageNumber, pageSize, totalCount } = this.state;
    const { notServer } = this.props;
    // 不显示上一页
    const notPrev = pageNumber === 1;
    // 不显示显示下一页
    const notNext = !totalCount || pageNumber * pageSize >= totalCount
    // 是否显示分页 
    const pagina = !notServer && (!notPrev || !notNext);
    return (
      <div
        style={
          pagina
            ? {
              position: 'relative',
            }
            : {}
        }
      >
        {menu}
        {pagina && (
          <div
            style={{
              position: 'absolute',
              right: 0,
              width: '30px',
              background: '#fff',
              top: 0,
              bottom: 0,
              flexDirection: 'column',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: '24px',
            }}
            onMouseDown={(e) => e.preventDefault()}
          >
            <UpCircleFilled
              type="up-circle"
              onClick={() => {
                if (notPrev || loading) return;
                this.setState(
                  {
                    pageNumber: pageNumber - 1,
                  },
                  () => {
                    this.getServerResult();
                  },
                );
              }}
              style={{
                marginBottom: '20px',
                cursor: notPrev ? 'not-allowed' : 'pointer',
                color: notPrev ? '#999999' : '#3399fc',
              }}
            />
            <DownCircleFilled
              type="down-circle"
              onClick={() => {
                if (notNext || loading) return;
                this.setState(
                  {
                    pageNumber: pageNumber + 1,
                  },
                  () => {
                    this.getServerResult();
                  },
                );
              }}
              style={{
                cursor: notNext ? 'not-allowed' : 'pointer',
                color: notNext ? '#999999' : '#3399fc',
              }}
            />
          </div>
        )}
      </div>
    );
  }
}

export default SelectPaging;