react+antd封装表格组件2.0

react+antd封装表格组件2.0

1.0版本 仅仅封装组件,不涉及方法

1.0 仅封装组件

此方法把所用方法集体封装,以后就可以无脑开发拉!

  • 只需传入路径,组件列表请求、增删改后更新属性均在组件内发生
  • 当前页不是第一页且删除后无数据,则自动跳转到上一页
  • 查询时防止多次点击,点击一次后加遮罩,数据返回后放开
  • 已有查询条件时,再次分页带上查询条件

需要掌握知识点

useImperativeHandle

一个用于暴露自定义ref属性和自定义方法的钩子函数。可以使得父组件可以通过ref访问子组件中定义的方法和属性,从而实现对子组件的精细控制

!!使用useImperativeHandle时必须与forwardRef搭配使用,否则会报错

  • 具体使用步骤如下
javascript 复制代码
//在子组件中定义需要暴露给父组件的方法和属性,并将这些方法和属性放在一个对象中
import { useImperativeHandle, forwardRef } from 'react';

const Child = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  // 定义需要暴露给父组件的方法和属性
  useImperativeHandle(ref, () => ({
    increment,
    count,
  }));

  return <div>{count}</div>;
});
javascript 复制代码
//在父组件中使用子组件,并给子组件传递一个ref属性,
import { useRef } from 'react';
import Child from './Child';

const Parent = () => {
  // 创建一个ref
  const childRef = useRef();

  // 在父组件中通过ref访问子组件的方法和属性
  const handleClick = () => {
    childRef.current.increment();
  };

  return (
    <div>
      <button onClick={handleClick}>Click me</button>
      <Child ref={childRef} />
    </div>
  );
};
//父组件中的handleClick方法通过childRef.current访问了Child组件中的increment方法和count属性。

组件代码

javascript 复制代码
import { Table, Pagination, Button, Dropdown, Checkbox, message } from 'antd';
import { useDispatch } from 'umi';
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { PicRightOutlined } from '@ant-design/icons';

import './index.less';

const TableComponent = forwardRef((props, ref) => {
  const dispatch = useDispatch();
  const [open, setOpen] = useState(false); //动态控制列的下拉显隐
  const [items, setItems] = useState([]); //当前的下拉选择状态
  const [loading, setLoading] = useState(true); //当前的加载状态
  const [dataSource, setDataSource] = useState([]); //列表数据

  const [total, setTotal] = useState(0); //分页总数
  const [size, setSize] = useState(10); //分页页数
  const [current, setCurrent] = useState(1); //分页当前页


  // 抛出获取列表方法
  useImperativeHandle(ref, () => ({
    getList,
  }));

  // 表格列表
  const [columns, setColumns] = useState(
    props.columns.map((item) => {
      return {
        ...item,
        align: 'center',
        ellipsis: {
          showTitle: false,
        },
      };
    }),
  );

  //控制列的初始值
  const [starCol, setStarCol] = useState(
    props.columns.map((item) => {
      return {
        ...item,
        check: true,
        align: 'center',
        ellipsis: {
          showTitle: false,
        },
      };
    }),
  );

  useEffect(() => {
    changItem(starCol);
  }, [props]);



  useEffect(() => {
    if (props.columns || props.columns.length > 0) {
      setColumns(
        props.columns.map((item) => {
          return {
            ...item,
            align: 'center',
            ellipsis: {
              showTitle: false,
            },
          };
        }),
      );
      setStarCol(
        props.columns.map((item) => {
          return {
            ...item,
            check: true,
            align: 'center',
            ellipsis: {
              showTitle: false,
            },
          };
        }),
      );
    }
  }, [props.columns]);


  useEffect(() => {
    // 初始请求列表
    if (props.url) {
      getList();
    }
  }, [props.url]);
  
  useEffect(() => {
    // 新增编辑后列表刷新
    if (props.list) {
      const list = props.list?.map((item, index) => {
        return {
          index: serialNumber(current, size, index + 1),
          ...item,
        };
      });
      setDataSource(list);
    }
  }, [props.list]);

  useEffect(() => {
    if (props.search) {
      dispatch({
        type: 'utils/searchLoadingEvent',
        payload: true,
      });
      // 搜索请求列表
      getList(1, size, props.search, true);
    }
  }, [props.search]); //监听列表传入查询条件

  const getList = (pageCurrent, pageSize, values, isClickSearch) => {
    if (!isClickSearch) {
      // 获取列表时 没点击查询按钮 加载列表
      setLoading(true);
    }
    const payload = {
      current: pageCurrent || current,
      size: pageSize || size,
      ...values,
    };
    dispatch({
      type: props.url,
      payload: payload,
      callback: (res) => {
        if (res) {
          setTotal(res.total);
          setSize(res.size);
          setCurrent(res.current);
          const list = res.data?.map((item, index) => {
            return {
              index: serialNumber(res.current, res.size, index + 1),
              ...item,
            };
          });
          setDataSource(list);
          setLoading(false);
          props.listPaginationChange(payload); //更新接口条件
          
          // 处理列表最后一条数据删除时请求上一页数据
          if (res.data?.length == 0 && res.total > 0) {
            getList(res.current - 1, res.size, props.search);
          }
          
          //点击查询按钮 获取到数据后 关闭局部加载
          if (isClickSearch) {
            dispatch({
              type: 'utils/searchLoadingEvent',
              payload: false,
            });
          }
        }
      },
    });
  };

  // 分页查询列表
  const paginationChange = (pageCurrent, pageSize) => {
    getList(pageCurrent, pageSize, props.search || null);
  };

//这个方法可加可不加,是因为前端需要展示序号,而后端不返回,所以就自己计算啦!
  const serialNumber = (pageIndex, pageSize, index) => {
    return (pageIndex - 1) * pageSize + index;
    
  };

  // 控制显示列的操作
  const onClick = ({ key }) => {
    if (key == 'all') {
      const newDrop = starCol;
      newDrop.map((o, index) => {
        o.check = true;
      });
      changItem(starCol);
      setColumns(
        props.columns.map((item) => {
          return {
            ...item,
            check: true,
            align: 'center',
            ellipsis: {
              showTitle: false,
            },
          };
        }),
      );
    } else {
      const newDrop = starCol;
      newDrop.map((o, index) => {
        if (index == key) {
          o.check = !o.check;
        }
      });
      let newColumns = newDrop.filter((o) => o.check);
      if (newColumns.length == 0) {
        message.warning('请至少选择一列');
      } else {
        setStarCol(newDrop);
        changItem(newDrop);
        setColumns(newColumns); //列状态
      }
    }
  };

  // 列的选中状态改变
  const changItem = (data) => {
    let opitems = data.map((item, index) => {
      return {
        key: index,
        label: <Checkbox checked={item.check}>{item.title}</Checkbox>,
      };
    });
    opitems.unshift(
      {
        key: 'all',
        label: <Button>{'全选列'}</Button>,
      },
      {
        type: 'divider',
      },
    );
    setItems(opitems); //当前的下拉状态
  };

  return (
    <div className="table">
      {!props?.selectIf && (
        <Dropdown
          menu={{
            items,
            onClick,
          }}
          overlayClassName="drop"
          trigger={['click']}
          onOpenChange={() => setOpen(!open)}
          open={open}
          arrow
          placement="bottomRight"
        >
          <a onClick={(e) => e.preventDefault()}>
            <Button icon={<PicRightOutlined />} title="显示/隐藏列"></Button>
          </a>
        </Dropdown>
      )}

      <Table
        bordered
        columns={columns}
        rowSelection={props?.rowSelection}
        // dataSource={isShow ? dataSource : null}
        dataSource={dataSource}
        rowKey={(item) => item.id || item.categoryId || item.modelId}
        pagination={false}
        className="insiadeTable"
        scroll={{
          y: 450,
          ...props?.scroll,
        }}
        onRow={props?.onRow}
        rowClassName={props?.rowClassName}
        summary={props?.summary}
        loading={loading}
      />
      <div className="pagination">
        <Pagination
          showQuickJumper
          defaultCurrent={current}
          total={total}
          showTotal={(total) => `共 ${total} 条`}
          size={size}
          current={current}
          onChange={paginationChange}
          pageSizeOptions={[10, 50, 100, 500]}
        />
      </div>
    </div>
  );
});

export default TableComponent;

引用

javascript 复制代码
···
  const [search, setSearch] = useState(null); //搜索条件
  const tableComponentRef = useRef(null);
  const list = useSelector((state) =>state.roleManagement.roleManagementList);
  const [search, setSearch] = useState(null);
  const [listChange, setListChange] = useState(list);
  const [listPaginationChange, setListPaginationChange] = useState(null);
···
  useEffect(() => {
    if (list) {
      setListChange(list); //组件内数据响应后此处更新
    }
  }, [list]);
···
  const onFinish = (values) => {
    setSearch(values); //点击搜索后设置值
  };

···
  <Form onFinish={onFinish} form={form} requiredMark={'Hidden'}>
        <Row gutter={16}>
          <Col span={6}>
            <Form.Item
              name="keyword"
              label="关键字"
            >
              <Input allowClear />
            </Form.Item>
          </Col>
          <Col span={4}>
            <Space>
              <Button
                type="primary"
                icon={<SearchOutlined />}
                htmlType="submit"
              >
                搜索
              </Button>
              <Button
                icon={<SyncOutlined />}
                onClick={() => {
                  form.resetFields();
                }}
              >
                重置
              </Button>
            </Space>
          </Col>
        </Row>
      </Form>
  
     <TableComponent
        ref={tableComponentRef} //组件实例
        url="roleManagement/getRoleManagementList"
        search={search} //搜索条件
        list={listChange} //数据源
        listPaginationChange={(e) => {
        //{current:1,size:10,..查询条件} 格式
          setListPaginationChange(e);
        }}
        className="list"
        columns={columns} //列
        scroll={{
          x: 1600,
        }}
      />
  • 新增修改组件
javascript 复制代码
      <Edit
        fillingForm={fillingForm}
        open={isModalOpen}
        setIsModalOpen={setIsModalOpen}
        //修改新增组件只需传入listPaginationChange,便可保留分页及查询条件
        listPaginationChange={listPaginationChange}
      />
     
     //修改新增后调用即可 
    const getRoleList = () => {
     dispatch({
      type: 'roleManagement/getRoleManagementList',
      payload: props.listPaginationChange,
   };
相关推荐
醉城夜风~1 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华2 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学6 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
先吃饱再说6 小时前
编写一个颜色选择器之后,我理解了 React 工程化的三层架构
react.js·前端框架·前端工程化
先吃饱再说6 小时前
别说你懂 useEffect:从底层机制到生命周期管理,这篇全讲透了
react.js·前端框架
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim