LazySelect.tsx 3.56 KB
import { Select, Spin } from 'antd';
import debounce from 'lodash/debounce';
import difference from 'lodash/difference';
import { useEffect, useState } from 'react';

/**
 * 懒加载 Select ,适用于数据超大的下拉框
 * @param {Select 官方属性} props
 */
const LazySelect = (props) => {
  const { value, onChange, query, pageSize } = props;
  // 清除 porps 中 query,避免控制台警告
  const selectProps = { ...props, query: undefined };

  const [selected, setSelected] = useState(value);
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [current, setCurrent] = useState(1);
  const [total, setTotal] = useState(0);
  const [searchText] = useState('');

  const getSelectedArray = (obj) => {
    let selectedValues = obj;
    // 如果是单选,将值封装为数组
    if (obj && obj instanceof Array === false) {
      selectedValues = [obj];
    }
    return selectedValues;
  };

  // 添加 300 毫秒防抖
  const handleQuery = debounce(async (param) => {
    setLoading(true);
    const resp = await query(param);
    const resData = resp.data;
    let options = resData?.data?.map((p: any) => {
      return {
        ...p,
        label: p.productName,
        value: p.productName,
        key: p.id,
      };
    });
    // 搜索服务端异步加载
    // const handleSearch = (filter) => {
    //   setSearchText(filter);
    //   handleQuery({
    //     filter,
    //     selectedValues: getSelectedArray(selected),
    //     pageSize,
    //     current
    //   });
    // };

    //第一个商品默认为要新增的商品
    // if (searchValue?.trim() !== "") {
    //   options.unshift({ productName: searchValue, type: "add", label: searchValue, value: searchValue })
    // }
    // return options;
    setTotal(resData.total);
    setData(data.concat(options));
    setLoading(false);
  }, 300);

  // 组件初始化时加载一次数据
  useEffect(() => {
    handleQuery({
      filter: '',
      selectedValues: getSelectedArray(value),
    });
  }, []);

  // 外部注入的 value 变化后,如果 value 在 data 中不存在,则加载数据
  useEffect(() => {
    setSelected(value);
    const dataKeys = data.map((item) => item.value);
    const diff = difference(getSelectedArray(value), dataKeys);
    if (diff && diff.length > 0) {
      handleQuery({
        filter: '',
        selectedValues: getSelectedArray(value),
      });
    }
  }, [value]);

  const keywordChange = (newWord: string) => {
    setData([]);
    setTotal(0);
    setCurrent(1);
    handleSearch(newWord);
  };

  const handleChange = (newValue, option) => {
    setSelected(newValue);
    if (onChange) {
      // 将值通过 onChange 传递到外部
      onChange(newValue, option);
    }
  };

  const scrollEnd = (e) => {
    e.persist();
    const { target } = e;
    // 滚动 触底 看接口是否还有剩余的值没传过来
    if (target.scrollTop + target.offsetHeight === target.scrollHeight) {
      if (current * pageSize < total) {
        setCurrent(current + 1);
        handleSearch(searchText);
      }
    }
  };

  return (
    <Select
      {...selectProps}
      value={selected}
      loading={loading}
      onSearch={keywordChange}
      onChange={handleChange}
      onPopupScroll={scrollEnd}
      filterOption={false}
      options={data}
      showSearch
      showArrow
      notFoundContent={loading ? <Spin size="small" /> : null}
    >
      {/* {data.map(d => (
        <Option key={d.value} title={d.label}>
          {d.label}
        </Option>
      ))} */}
    </Select>
  );
};

export default LazySelect;