封装一个下拉选择Table(可分页、可搜索)DropdownTa

2021-11-30  本文已影响0人  importUIKit

实例演示如下:

demo.gif

提示:项目依赖于antd
代码直接贴了,可以直接使用
github代码地址

import { Select, Table, Input, message, TablePaginationConfig } from "antd";
import { useRef, useState, useEffect } from "react";
import * as React from "react";
// import type { PaginationConfig } from "antd";

const { Search } = Input;

export interface PaginationConfig {
  total?: number;
  defaultCurrent?: number;
  disabled?: boolean;
  current?: number;
  defaultPageSize?: number;
  pageSize?: number;
  onChange?: (page: number, pageSize?: number) => void;
  hideOnSinglePage?: boolean;
  showSizeChanger?: boolean;
  pageSizeOptions?: string[];
  onShowSizeChange?: (current: number, size: number) => void;
  showQuickJumper?:
    | boolean
    | {
        goButton?: React.ReactNode;
      };
  showTotal?: (total: number, range: [number, number]) => React.ReactNode;
  simple?: boolean;
  style?: React.CSSProperties;
  locale?: Object;
  className?: string;
  prefixCls?: string;
  selectPrefixCls?: string;
  itemRender?: (
    page: number,
    type: "page" | "prev" | "next" | "jump-prev" | "jump-next",
    originalElement: React.ReactElement<HTMLElement>
  ) => React.ReactNode;
  role?: string;
  showLessItems?: boolean;
  [key: string]: any;
}

export type DropdownTableProps<T, D> = {
  columns?: T[]; // table列配置
  mode?: "radio" | "checkbox"; // 单选 多选
  placeholder?: string | ""; // placeholder
  optionValueProp?: string | "value";
  optionLabelProp?: string | "label";
  searchPlaceholder?: string | ""; // 搜索框的searchPlaceholder
  limit?: number | undefined; //限制最多选择几个
  onChange?: (value: string[]) => void; // 选择值改变后
  dropdownStyle?: React.CSSProperties; // 下拉框样式
  defaultOptions?: { value: string; label: string }[]; // 设置默认选项,在需要回填时使用
  value?: string[] | string; // 设置值
  tableProps?: {
    dataSource: D[];
    loading: boolean;
    onChange?: (
      pagination?: TablePaginationConfig,
      filters?: any,
      sorter?: any
    ) => void;
    pagination?: TablePaginationConfig | false;
    [key: string]: any;
  }; // table的参数
  onSearch?: (keyword: string) => void;
};

const DropdownTable = <
  T extends Record<string, any>,
  D extends Record<string, any>
>({
  columns,
  mode = "radio",
  placeholder = "",
  optionValueProp = "value",
  optionLabelProp = "label",
  searchPlaceholder = "",
  limit,
  onChange,
  dropdownStyle,
  defaultOptions,
  value,
  tableProps,
  onSearch,
}: DropdownTableProps<T, D>) => {
  const ref = useRef<any>();
  const [thisSelectedRowKeys, setThisSelectedRowKeys] = useState<string[]>([]);
  const [selectedRowObjects, setSelectedRowObjects] = useState<
    { value: string; label: string }[]
  >([]);
  const [keyword, setKeyword] = useState<string>("");
  const [searchTag, setSearchTag] = useState<number>(1);

  useEffect(() => {
    if (typeof value === "string") {
      setThisSelectedRowKeys([value]);
      return;
    }
    setThisSelectedRowKeys(value || []);
  }, [value]);

  useEffect(() => {
    setSelectedRowObjects((old) => {
      if (defaultOptions) {
        return [...old, ...defaultOptions];
      }
      return [...old];
    });
  }, [defaultOptions]);

  const listenDataToCallBack = (
    keys: string[],
    objects: { value: string; label: string }[]
  ) => {
    setThisSelectedRowKeys(keys);
    setSelectedRowObjects(objects);
    onChange?.(keys);
  };

  const rowChangeBackArray = (key: string) => {
    if (mode !== "checkbox") {
      return [key];
    }
    const index = thisSelectedRowKeys.indexOf(key);
    const newArray = [...thisSelectedRowKeys];
    if (index === -1) {
      newArray.push(key);
    } else {
      newArray.splice(index, 1);
    }
    return newArray;
  };

  const rowObjectChangeBackArray = (valueString: string, label: string) => {
    if (mode !== "checkbox") {
      return [{ value: valueString, label }];
    }
    const index = thisSelectedRowKeys.indexOf(valueString);
    const newArray = [...selectedRowObjects];
    if (index === -1) {
      newArray.push({ value: valueString, label });
    } else {
      newArray.splice(index, 1);
    }
    return newArray;
  };

  const clickRow = (record: D) => {
    if (mode === "radio") {
      ref.current.selectRef.current.blur();
    }

    const key = record[optionValueProp];
    const newArray = rowChangeBackArray(key);
    const newObjectArray = rowObjectChangeBackArray(
      key,
      record[optionLabelProp]
    );

    if (limit && newArray.length > limit) {
      message.info(`最多只能选择${limit}个`);
      return;
    }
    listenDataToCallBack(newArray, newObjectArray);
  };

  const onSelectAllTable = (
    changeRows: any[],
    selected: boolean,
    selectData: any[],
    id: string,
    isRow: boolean
  ): string[] => {
    const selectCode = [...selectData];
    if (selected) {
      for (let index = 0; index < changeRows.length; index++) {
        const element = changeRows[index];
        if (isRow) {
          selectCode.push(element);
        } else {
          selectCode.push(element[id]);
        }
      }
      return selectCode;
    }
    const result = [];
    for (let i = 0; i < selectCode.length; i++) {
      let k = 0;
      const item = isRow ? selectCode[i][id] : selectCode[i];
      for (let j = 0; j < changeRows.length; j++) {
        if (item !== changeRows[j][id]) {
          k += 1;
          if (k === changeRows.length) {
            result.push(selectCode[i]);
          }
        }
      }
    }
    return result;
  };

  // rowSelection
  const rowSelection = {
    type: mode,
    selectedRowKeys: thisSelectedRowKeys,
    onSelect: (
      record: T,
      selected: boolean,
      selectedRows: string[],
      nativeEvent: React.TouchEvent
    ) => {
      nativeEvent.stopPropagation();
      const key = record[optionValueProp];
      const label = record[optionLabelProp];
      const newArray = rowChangeBackArray(key);
      const newObjectArray = rowObjectChangeBackArray(key, label);
      if (limit && newArray.length > limit) {
        message.info(`最多只能选择${limit}个`);
        return;
      }
      listenDataToCallBack(newArray, newObjectArray);
    },
    onSelectAll: (
      selected: boolean,
      selectedRows: string[],
      changeRows: any[]
    ) => {
      const newKeys = onSelectAllTable(
        changeRows,
        selected,
        thisSelectedRowKeys,
        optionValueProp,
        false
      );
      if (limit && newKeys.length > limit) {
        message.info(`最多只能选择${limit}个`);
        return;
      }
      const newArray = [...selectedRowObjects];
      if (selected) {
        for (let i = 0; i < changeRows.length; i++) {
          const item = changeRows[i];
          const valueString = item[optionValueProp];
          if (thisSelectedRowKeys.indexOf(valueString) === -1) {
            const label = item[optionLabelProp];
            newArray.push({ label, value: valueString });
          }
        }
      } else {
        for (let i = 0; i < changeRows.length; i++) {
          const item = changeRows[i];
          const valueString = item[optionValueProp];
          const index = thisSelectedRowKeys.indexOf(valueString);
          if (index !== -1) {
            newArray.splice(index, 1);
          }
        }
      }
      listenDataToCallBack(newKeys, newArray);
    },
  };

  const getRowSelection = (): any => {
    if (mode === "radio") {
      return {
        columnWidth: 0,
        type: mode,
        renderCell: () => "",
        selectedRowKeys: thisSelectedRowKeys,
      };
    }
    return rowSelection;
  };

  const handleChange = (v: string[]) => {
    setThisSelectedRowKeys(v);
    onChange?.(v);
  };

  return (
    <>
      <Select
        ref={ref}
        placeholder={placeholder}
        showSearch={false}
        allowClear
        showArrow
        onChange={handleChange}
        style={{ width: "100%" }}
        options={selectedRowObjects}
        mode="multiple"
        onClear={() => {
          listenDataToCallBack([], []);
        }}
        // tagRender={tagRender}
        value={thisSelectedRowKeys}
        dropdownStyle={dropdownStyle}
        dropdownRender={() => {
          return (
            <div style={{ ...dropdownStyle, padding: 12 }}>
              <Search
                value={keyword}
                placeholder={searchPlaceholder}
                style={{
                  marginBottom: 12,
                }}
                allowClear
                onSearch={() => {
                  setSearchTag(searchTag + 1);
                }}
                onChange={(e) => {
                  if (e.target.value === "") {
                    setKeyword("");
                    onSearch?.("");
                    setSearchTag(searchTag + 1);
                    return;
                  }
                  setKeyword(e.target.value);
                  onSearch?.(e.target.value);
                }}
                enterButton
              />
              <Table
                {...tableProps}
                onRow={(record) => ({
                  onClick: () => {
                    clickRow(record);
                  },
                })}
                size="small"
                rowSelection={{ ...getRowSelection() }}
                columns={columns}
                rowKey={optionValueProp}
              />
            </div>
          );
        }}
      />
    </>
  );
};

export default DropdownTable;

使用实例demo代码

import "./index.less";
import DropdownTable from "./DropdownTable";
import { useAntdTable } from "ahooks";
import { Button, Form, message } from "antd";
import { useState } from "react";

const columns = [
  {
    title: "用户名",
    dataIndex: "id",
    key: "id",
  },
  {
    title: "姓名",
    dataIndex: "name",
    key: "name",
  },
];

const names = ["张", "李", "陈", "荀", "诸葛", "牛", "刘"];
const dataSource: { name: string; id: string }[] = [];
for (let i = 0; i < 20; i++) {
  const index = Math.floor(Math.random() * names.length);
  dataSource.push({
    name: `${names[index]}${i + 1}`,
    id: `${i + 1}`,
  });
}

const getData = (current: number, pageSize: number, searchKey?: string) => {
  console.log(current, pageSize);

  return new Promise((resolve) => {
    setTimeout(() => {
      const start = (current - 1) * pageSize;
      const array: { name: string; id: string }[] = [];

      if (searchKey) {
        array.push(
          ...[...dataSource].filter((rs) => rs.name.indexOf(searchKey) !== -1)
        );
        if (array.length > 5) {
          resolve({
            total: array.length,
            list: [...array].splice(start, pageSize),
          });
        } else {
          resolve({
            total: array.length,
            list: array,
          });
        }
      } else {
        array.push(...[...dataSource].splice(start, pageSize));
        resolve({
          total: 20,
          list: array,
        });
      }
    }, 1000);
  });
};

const CustomComponentPage = () => {
  const [form] = Form.useForm();

  const [searchKey, setSearchKey] = useState("");

  const { tableProps } = useAntdTable<
    {
      total: number;
      list: { name: string; id: string }[];
    },
    { name: string; id: string },
    { name: string; id: string }
  >(
    (rs) => {
      const { current, pageSize } = rs;
      console.log("res", rs);

      // console.log("current:", current, pageSize);
      return getData(current, pageSize, searchKey);
    },
    {
      refreshDeps: [searchKey],
      defaultPageSize: 5,
      formatResult: (res) => {
        return res;
      },
    }
  );
  console.log(tableProps.pagination);

  return (
    <div className="custom-component-page">
      <div className="custom-component-page__demo">
        普通单选用法:
        <DropdownTable
          columns={columns}
          mode="radio"
          placeholder="点击选择用户"
          searchPlaceholder="请输入用户名或者姓名搜索"
          optionValueProp="id"
          optionLabelProp="name"
          onChange={(selectedKeys) => {
            console.log("selectedKeys:", selectedKeys);
          }}
          tableProps={{ ...(tableProps as any) }}
          dropdownStyle={{ minWidth: 360 }} // 设置下拉表最小宽度,此处设置width无效必须设置minWidth
        />
      </div>
      <div className="custom-component-page__demo">
        普通多选用法:
        <DropdownTable
          columns={columns}
          mode="checkbox"
          placeholder="点击选择用户"
          searchPlaceholder="请输入用户名或者姓名搜索"
          optionValueProp="id"
          optionLabelProp="name"
          onChange={(selectedKeys) => {
            console.log("selectedKeys:", selectedKeys);
          }}
          tableProps={{ ...(tableProps as any) }}
          dropdownStyle={{ minWidth: 360 }} // 设置下拉表最小宽度,此处设置width无效必须设置minWidth
        />
      </div>
      <div className="custom-component-page__demo">
        <Form
          onFinish={(values) => {
            console.log("values:", values);
            message.info(`获取到表单数据${JSON.stringify(values)}`);
          }}
          form={form}
          initialValues={{ table: ["3", "4"] }}
        >
          <Form.Item label="form设置初始值" name="table">
            <DropdownTable
              columns={columns}
              defaultOptions={[...dataSource]
                .map((rs) => {
                  return { value: rs.id, label: rs.name };
                })
                .splice(2, 2)}
              mode="checkbox"
              placeholder="点击选择用户"
              searchPlaceholder="请输入用户名或者姓名搜索"
              optionValueProp="id"
              optionLabelProp="name"
              onChange={(selectedKeys) => {
                console.log("selectedKeys:", selectedKeys);
              }}
              onSearch={(keyword) => {
                setSearchKey(keyword);
              }}
              tableProps={{ ...(tableProps as any) }}
              dropdownStyle={{ minWidth: 360 }} // 设置下拉表最小宽度,此处设置width无效必须设置minWidth
            />
          </Form.Item>
        </Form>
        <Button
          onClick={() => {
            form.submit();
          }}
        >
          提交
        </Button>
      </div>
    </div>
  );
};
export default CustomComponentPage;

上一篇下一篇

猜你喜欢

热点阅读