使用hook封装表格常用功能(react)

实现内容

  • 配置分页:usePagination
  • 生成过滤项:useFilter
  • 获取表格选择配置:useSelect
  • 生成批量删除按钮:useDelete
  • 生成模态框:useModal

基于react@18.2.0,antd@5.12.5

示例

render部分:

<React.Fragment>
    <Form layout="inline">
    {DeleteEle}
    {FilterEles}
    </Form>
    <Table
    {...{
        columns,
        dataSource: list || [],
        rowSelection,
        pagination: paginationConfig,
        rowKey: 'inform_uuid',
    }}
    />
    {ModalEle}
</React.Fragment>

效果:

完整实例

import React, { useState, useEffect } from 'react';
import { Form, Table, Tag } from 'antd';
import Detail from './Detail'; // 详情弹框的内容
import { LIST_RES } from '../store'; // 模拟请求数据
import TableGenerator from '../TableGenerator'; // 封装的钩子

const { useFilter, usePagination, useSelect, useDelete, useModal } =
   TableGenerator;

const TestTable = () => {
   // 1. 生成过滤项。
   // 传入配置,可以自行扩展。目前支持输入框、日期范围选择器、普通选择器
   // FilterEles:过滤项表单项
   // filterRefresh:暴露出来告诉外层需要重新获取数据了
   const { FilterEles, filter, filterRefresh } = useFilter([
      {
         name: 'name', // 字段
         label: '平台名称', // placeholder
         type: 'inputString', // 组件类型:字符串输入框
      },
      {
         name: 'status',
         label: '状态',
         type: 'select', // 组件类型:选择器
         initValue: 0, // 默认数据(可选)
         options: [
            { value: 0, label: '全部状态' },
            { value: 1, label: '成功' },
            { value: 2, label: '失败' },
         ],
      },
      {
         name: ['start_date', 'end_date'],
         label: ['开始日期', '结束日期'],
         type: 'dateRange', // 组件类型:日期选择器
      },
   ]);

   // 2. 获取分页配置。
   // pagination:{page,pageSize,total}
   // pageRefresh:暴露出来告诉外层需要重新获取数据了
   // paginationConfig:作为表格的分页属性
   const { pagination, setPagination, pageRefresh, paginationConfig } =
      usePagination();

   // 3. 表格选中配置
   // selectedKeys: Array<string>
   // rowSelection: 作为表格的选择属性
   const { selectedKeys, setSelectedKeys, rowSelection } = useSelect();

   // 4. 配置模态框。
   // 传入数组,用于配置所有弹框类型(详情、添加、编辑等),目前只支持详情,可自行扩展
   // ModalEle: 模态框组件
   // modal: {isShow:boolean, action:string, record:any}
   const { ModalEle, modal, setModal } = useModal([
      {
         title: '详情', // 弹框标题
         action: 'detail', // 弹框类型
         getComponent: (record) => <Detail data={record} />, // 弹框数据
         // ... 可以继续添加属性,其他属性将会应用到Modal中 比如footer、handleOk
      },
   ]);

   // 5. 批量删除按钮,需要搭配useSelect使用
   const [DeleteEle] = useDelete({
      url: 'ajax/deleteItem.json',
      data: {
         uuids: selectedKeys,
      },
      success: () => {  // 可选,删除成功后还需要调用的内容
         setSelectedKeys([]);
         getList();
      },
      keys: selectedKeys, // 用于判断按钮是否可点击
   });

   const [list, setList] = useState([]);
   const getList = () => {
      // 请求数据, 可传入filter和pagination作为参数
       Ajax.ajax({
         url: 'GetSmsInformList',
         data: {
            page: pagination.page,
            pageSize: pagination.pageSize,
            filter
         },
         success: (data) => {
            setList(data.list);
            setPagination({
               ...pagination,
               page: data.page,
               total: data.total
            });
         }
      });
      
      // 模拟数据
      // const data = LIST_RES.data;
      // setList(data.list);
      // setPagination({
      //   ...pagination,
      //   page: data.page,
      //   total: data.total,
      // });
   };
   // 触发刷新:修改page、pageSize、点击查询、重置时将会触发
   useEffect(() => {
      getList();
   }, [pageRefresh, filterRefresh]);

   const columns = [
      {
         title: '序号',
         width: 50,
         align: 'center',
         render: (v, r, index) =>
            index + 1 + (pagination.page - 1) * pagination.pageSize,
      },
      {
         title: '平台名称',
         width: 180,
         dataIndex: 'req_psname',
      },
      {
         title: '请求时间',
         width: 140,
         dataIndex: 'inform_time',
      },
      {
         title: '通知对象',
         width: 180,
         dataIndex: 'inform_pnumbers',
         render: (v) => v.join(', '),
      },
      {
         title: '通知内容',
         width: 200,
         dataIndex: 'inform_content',
      },
      {
         title: '通知结果',
         width: 80,
         dataIndex: 'inform_result',
         render: (v) =>
            v === 0 ? (
               <Tag color="#87d068">成功</Tag>
            ) : (
               <Tag color="#f50">失败</Tag>
            ),
      },
      {
         title: '操作',
         width: 60,
         render: (v, record) => (
            <React.Fragment>
               <a
                  onClick={() => {
                     setModal({
                        action: 'detail',
                        isShow: true,
                        record,
                     });
                  }}
               >
                  详情
               </a>
            </React.Fragment>
         ),
      },
   ];

   return (
      <React.Fragment>
         <Form layout="inline">
            {DeleteEle}
            {FilterEles}
         </Form>
         <Table
            {...{
               scroll: { x: 1170, y: '70vh' },
               columns,
               dataSource: list || [],
               rowSelection,
               pagination: paginationConfig,
               rowKey: 'inform_uuid',
            }}
         />
         {ModalEle}
      </React.Fragment>
   );
};

export default TestTable;

Hook封装

usePagination

配置表格的分页功能

const usePagination = () => {
    // 默认数据
  const [pagination, setPagination] = useState({
    page: 1,
    pageSize: 10,
    total: 0,
  });
  // 用于触发外层的刷新
  const [pageRefresh, setPageRefresh] = useState(false);

  const config = {
    size: "small",
    showQuickJumper: false,
    total: pagination.total,
    current: pagination.page,
    showSizeChanger: true,
    onChange: (current) => {
      setPagination({
        ...pagination,
        page: current,
      });
      setPageRefresh(() => !pageRefresh); // 修改页数后,通知外层刷新
    },
    pageSize: pagination.pageSize,
    onShowSizeChange: (page, pageSize) => {
      setPagination({
        page,
        pageSize,
      });
      setPageRefresh(() => !pageRefresh);// 修改页码后,通知外层刷新
    },
  };

  return {
    pagination,
    pageRefresh,
    setPagination,
    paginationConfig: config,
  };
};

useSelect

配置表格的选择功能

const useSelect = () => {
  const [selectedKeys, setSelectedKeys] = useState([]);
  const rowSelection = {
    onChange: (selectedKeys) => {
      setSelectedKeys(selectedKeys);
    },
    selectedRowKeys: selectedKeys,
  };

  return { selectedKeys, rowSelection, setSelectedKeys };
};

useDelete

配置表格的批量删除按钮

const useDelete = ({ url = "", data = {}, success = null, keys = [] }) => {
  const handleDel = () => {
    // 请求..
    message.success("删除成功");
    success && success();  // 
  };

  const DeleteEle = (
    <React.Fragment>
        { /**OPER.isDelete  : 当有权限时才显示  */}
      {OPER.isDelete && (
        <Form.Item>
          {keys.length === 0 ? (
            <Button icon={<DeleteOutlined />} size="small" disabled>
              批量删除
            </Button>
          ) : (
            <Popconfirm
              title="批量删除"
              description="你确定要删除吗?"
              okText="确定"
              cancelText="取消"
              onConfirm={handleDel}
            >
              <Button
                type="primary"
                danger
                icon={<DeleteOutlined />}
                size="small"
              >
                批量删除
              </Button>
            </Popconfirm>
          )}
        </Form.Item>
      )}
    </React.Fragment>
  );

  return [DeleteEle];
};

useFilter

配置过滤项,附带查询、重置按钮

import { initFilter, getFormItem } from "./utils";  // 根据配置配置初始化filter数据 ;  根据配置获取组件

const useFilter = (config = []) => {
  const [filter, setFilter] = useState(initFilter(config));
  const [refresh, setRefresh] = useState(false);

 // 重置过滤项
  const handleResetFilter = () => {
    setFilter(initFilter(config));
    setRefresh(!refresh); // 通知外层刷新
  };

  // 触发查询
  const handleSearch = () => {
    setRefresh(!refresh); // 通知外层刷新
  };

 // 修改日期类型
  const handleDateChange = ([startField, endField], [startTime, endTime]) => {
    setFilter({ ...filter, [startField]: startTime, [endField]: endTime });
  };

 // 修改input 类型
  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFilter({ ...filter, [name]: value });
  };

// 修改select 类型
  const handleSelectChange = (name, value) => {
    setFilter({ ...filter, [name]: value });
  };


 // 根据配置生成表单项
  const FormItemEles = getFormItem(config, {
    filter,
    handleInputChange,
    handleSelectChange,
    handleDateChange,
  });
 

  const HandlerEles = [
    <Form.Item>
      <Button
        type="primary"
        size="small"
        onClick={handleSearch}
        icon={<SearchOutlined />}
      >
        查询
      </Button>
    </Form.Item>,
    <Form.Item>
      <Button
        type="primary"
        size="small"
        onClick={handleResetFilter}
        icon={<ReloadOutlined />}
      >
        重置
      </Button>
    </Form.Item>,
  ];

  return {
    filter,
    FilterEles: [...FormItemEles, ...HandlerEles],
    filterRefresh: refresh,
  };
};

initFilter:根据不同类型初始化filter数据

const initFilter = (arr) => {
  const obj = {};
  arr.forEach((item) => {
    switch (item.type) {
      case "inputString":
        obj[item.name] = item.initValue || "";
        break;
      case "select":
        obj[item.name] = item.initValue || 0;
        break;
      case "dateRange":
        item.name.forEach((name) => {
          obj[name] = "";
        });
        break;
      default:
        break;
    }
  });

  return obj;
};

getFormItem:根据不同的类型生成表单项组件

const getFormItem = (
  arr,
  { filter, handleInputChange, handleSelectChange, handleDateChange }
) => {
  const Eles = [];
  arr.forEach((item) => {
    const name = item.name;
    const label = item.label;
    const value = filter[name];
    switch (item.type) {
      case "inputString":
        Eles.push(
          <Form.Item>
            <Input
              size="small"
              placeholder={label}
              name={name}
              value={value}
              onChange={handleInputChange}
            />
          </Form.Item>
        );
        break;
      case "select":
        Eles.push(
          <Form.Item>
            <Select
              size="small"
              style={{ width: 120 }}
              onChange={(v) => {
                handleSelectChange(name, v);
              }}
              name={name}
              value={value}
              options={item.options}
            />
          </Form.Item>
        );
        break;
      case "dateRange":
        Eles.push(
          <Form.Item>
            <RangePicker
              onChange={(dates) => {
                handleDateChange(name, dates);
              }}
              value={[filter[name[0]], filter[name[1]]]}
              placeholder={label}
              size="small"
            />
          </Form.Item>
        );
      default:
        break;
    }
  });

  return Eles;
};

useModal

配置模态框

const useModal = (
  config = [
    {
      title: "详情",
      action: "detail",
      getComponent: (record)=><div>详情</div>,
    },
  ]
) => {
  const [modal, setModal] = useState({
    isShow: false,
    record: null,
    action: "",
  });
  const handleCancel = () => {
    setModal({
      isShow: false,
      record: null,
      action: "",
    });
  };

  //对话框信息
  let modalProps = {
    open: modal.isShow,
    onCancel: handleCancel,
    maskClosable: false,
    footer: null,
  };

  const currentModal = config.find((item) => item.action === modal.action); // 找到当前模态框数据
  if (currentModal) {
    const { action, getComponent, ...other } = currentModal;
    // 配置传进来的其余属性
    modalProps = {
      ...modalProps,
      ...other,
    };
  }

  const ModalEle = (
    <Modal width="80%" {...modalProps}>
      {currentModal && currentModal.getComponent(modal.record)}
    </Modal>
  );

  return { ModalEle, modal, setModal };
};

源码

后记:起因是写表格写烦了,于是快速封装了一个,因为仅针对于当前的需求,所以写的比较简单,还有很多地方需要扩展。如果有其他思路,还请多多赐教~

posted @ 2023-12-28 17:30  sanhuamao  阅读(220)  评论(0编辑  收藏  举报