react 封装antd table 第一版---

封装antd table

所用依赖antd5、@ant-design/icons、react18

实现代码

  • table组件
import { Table } from "antd"
import { TableProps, MenuDataType } from "@/types"
import ToolsCom from "../tools"
import React, { useState, useContext } from "react"
import { ColumnsType } from "antd/es/table"
import { getIntersectionObjArrByKey } from "@/utils"

/**
 * table context 用于向下传递表格列数据
 */

type TableCloumsContext = {
    allClomens: ColumnsType<MenuDataType> //不变的列,用于操作列时使用
    changeClm: ColumnsType<MenuDataType>  //操作后的列
}

const TableContext: TableCloumsContext = React.createContext({ allClomens: null, changeClm: null })



export const getTableCloums = (): TableCloumsContext => useContext(TableContext)


/**
 * 
 * @param props columns列、data:数据、pagination、是否显示分页
 * toolsbar :自定义表头控制工具
 * reloadTable:刷新表格方法
 * @returns 
 */
const TableAntd = (props: TableProps) => {
    //...tableProps 很重要 
    const { columns, data, isBorder = false, reloadTable, rowSelection: t1, toolsbar, ...tableProps } = props
    const [chooseIds, setChooseIds] = useState<number | undefined | string>()
    const [formDataClonms, setFormDataClonms] = useState<ColumnsType<MenuDataType>>(JSON.parse(JSON.stringify(columns)))
    const rowSelection = {
        onChange: (selectedRowKeys: React.Key[], selectedRows: MenuDataType[]) => {
            setChooseIds(selectedRowKeys)
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
        // getCheckboxProps: (record: DataType) => ({
        //   disabled: record.name === 'Disabled User', // Column configuration not to be checked
        //   name: record.name,
        // }),
    };
    //列显隐
    const byKeyIsHideden = (key: string, isHidden: boolean) => {
        let arr: ColumnsType<MenuDataType>
        if (isHidden) {

            arr = formDataClonms.filter(it => it.key != key)
            setFormDataClonms(arr)
        } else {
            let obj = columns.find(it => it.key == key)

            arr = getIntersectionObjArrByKey('key', columns, [obj, ...formDataClonms])
            setFormDataClonms(arr)
        }
    }
    //列排序
    const byKeyIsSort = (key: string, isSort: boolean) => {

        let arr: ColumnsType<MenuDataType>
        if (isSort) {
            arr = formDataClonms.map(item => {
                if (item.key == key) return { ...item, sorter: (a, b) => a[key] - b[key] };
                return item
            })

            setFormDataClonms(arr)
        } else {
            arr = formDataClonms.map(item => {
                if (item.key == key) {
                    //删除sorter 
                    let { sorter, ...res } = item
                    return res;
                }
                return item
            })
            setFormDataClonms(arr)
        }
    }


    const handProps = { byKeyisiideden: byKeyIsHideden, reloadtable: reloadTable, bykeyissort: byKeyIsSort }
    return (<>
        <TableContext.Provider value={{ allClomens: columns, changeClm: formDataClonms }} >
            {toolsbar ? <ToolsCom chooseIds={chooseIds}  {...handProps} {...toolsbar}  ></ToolsCom> : null}
            <Table
                rowSelection={
                    {
                        type: t1,
                        ...rowSelection
                    }}
                columns={formDataClonms}
                // pagination={pagination}
                // loading={loading}
                {...tableProps}
                dataSource={data}
                bordered={isBorder}
            />
        </TableContext.Provider>
    </>)
}


TableAntd.defaultProps = {
    isBorder: false,
    pagination: true,
    loading: false
}

export default TableAntd;
  • getIntersectionObjArrByKey 方法
  /**
 * 
 * @param attribute 对象数组中对象属性,用于比较
 * @param arr1 对象数组--筛选后的对象(模板对象
 * @param arr2 对象数组
 * @returns 两个对象的交集通过attribute
 */
export function getIntersectionObjArrByKey(attribute: string, arr1: any[], arr2: any[]): any[] {

  let res: any[] = []

  for (let i = 0, len = arr1.length; i < len; i++) {
      for (let j = 0, length = arr2.length; j < length; j++) {
          if (arr1[i][attribute] == arr2[j][attribute]) {
              res.push(arr1[i])
          }
      }
  }

  return res

}
  • toolCom组件


import "@/assets/style/less/tools.less"
import { Space, Form, Button } from 'antd'
import { UndoOutlined, NumberOutlined, SearchOutlined } from "@ant-design/icons"
import { Add, Del, Reflesh, EditClomns } from '@/components/btn';
import { MenuDataType, ToolsOption } from "@/types"
import { getTableCloums } from "@/components/table"
import { byClomeBackFromItem } from "@/components/btn"
import { ColumnsType } from "antd/es/table"
import { useState } from 'react';

// 搜索表单
const SearchFromForClomns = (props: any) => {
    const { reloadtable, searchApi } = props
    let { changeClm: clm } = getTableCloums()

    let searchArr = clm.filter(item => item.isSearch)


    /**
     * 
     * @param clomes table列数据
     * @returns form表单
     */
    const byTableCloumsCreateFrom = (clomes: ColumnsType<MenuDataType>) => {
        const [form] = Form.useForm();
        const onReset = () => {
            form.resetFields();
        };

        const onFinish = (values: any) => {
            //存在则使用传过来的方法
            searchApi && searchApi(values)

            reloadtable && reloadtable(values)
            console.log(values);

        };
        return (
            <>
                {<Form
                    form={form}
                    labelCol={{ span: 5 }}
                    // wrapperCol={{ span: 16 }}
                    layout="inline"
                    onFinish={onFinish}
                >
                    {
                        clomes.map((item: any) => {
                            return byClomeBackFromItem(item, true)
                        })
                    }

                    <Form.Item >
                        <Space>
                            <Button type="primary" htmlType="submit">
                                提交
                            </Button>
                            <Button htmlType="button" onClick={onReset}>
                                重置
                            </Button>
                        </Space>
                    </Form.Item>

                </Form>}

            </>
        )
    }



    return (
        <>
            {byTableCloumsCreateFrom(searchArr)}
        </>
    )
}




const ToolsCom = (props: ToolsOption) => {
    const [isSearch, setIssearch] = useState(false)

    const { addBtn, delBtn, byKeyisiideden, bykeyissort, reload, seach, columnsEdit, reloadtable, chooseIds, searchApi, addApi, delApi } = props
        
    const handProps = { byKeyisiideden, reloadtable, bykeyissort }
    return (
        <>
            {isSearch ? <SearchFromForClomns searchApi={searchApi} reloadtable={reloadtable} /> : null}
            <div className='tools-bar'>

                <Space>
                    {addBtn ? <Add type={"primary"} addApi={addApi} reloadtable={reloadtable} >增加</Add> : ''}
                    {delBtn ? <Del danger={true} delApi={delApi} chooseIds={chooseIds} reloadtable={reloadtable}>删除</Del> : ''}
                </Space>
                <Space>
                    {reload ? <Reflesh reloadtable={reloadtable} shape="circle" icon={<UndoOutlined />} /> : ''}
                    {columnsEdit ? <EditClomns {...handProps} shape="circle" icon={<NumberOutlined />} /> : ''}
                    {seach ? <Button onClick={() => setIssearch(!isSearch)} shape="circle" icon={<SearchOutlined />} /> : ''}
                </Space>
            </div>

        </>


    )
}


export default ToolsCom
  • btn组件
import { Table,Button, Modal, Form,Checkbox, Input, message, Radio, TreeSelect, Drawer } from 'antd'
import { useState } from 'react'
import { BtnRenderTypes, ToolsOption } from "@/types"
import { getTableCloums } from "@/components/table"
import type { ColumnsType } from 'antd/es/table';
import { MenuDataType } from "@/types"
import type { CheckboxChangeEvent } from 'antd/es/checkbox'


const { confirm } = Modal;


/**
 * 
 * @param item 
 * @returns 根据类型生成fromItem项
 */
export const byClomeBackFromItem = (item: any, seach?: boolean) => {
    if (item.radio)
        return (
            <Form.Item key={item.key} style={{ width: '45%', marginBottom: '20px' }} name={item.dataIndex} label={item.title} rules={[{ required: (seach ? false : item.isRequire), message: `${item.title} 为必填项` }]} >
                <Radio.Group>
                    <Radio value='menu'>菜单</Radio>
                    <Radio value='btn'>按钮</Radio>
                </Radio.Group>
            </Form.Item>
        )

    if (item.treeSelect)
        return (
            <Form.Item key={item.key} style={{ width: '45%', marginBottom: '20px' }} name={item.dataIndex} label={item.title} rules={[{ required: (seach ? false : item.isRequire), message: `${item.title} 为必填项` }]} >
                <TreeSelect
                    showSearch
                    style={{ width: '100%' }}
                />
            </Form.Item>
        )

    return (
        <Form.Item key={item.key} style={{ width: '45%', marginBottom: '20px' }} name={item.dataIndex} label={item.title} rules={[{ required: (seach ? false : item.isRequire), message: `${item.title} 为必填项` }]} >
            <Input />
        </Form.Item>
    )
}
/**
 * 
 * @param props 
 * @returns table 根据table列添加
 */
export const Add = (props: BtnRenderTypes & ToolsOption) => {

    const [isOpen, setIsOpen] = useState(false)
    let { changeClm: clm } = getTableCloums()
    // console.log(reloadtable);
    const {reloadtable, addApi,...btns}=props
    const [form] = Form.useForm();
    /**
     * 
     * @param clomes table列数据
     * @returns form表单
     */
    const byTableCloumsCreateFrom = (clomes: ColumnsType<MenuDataType>) => {
        return (
            <>
                {<Form
                    form={form}
                    labelCol={{ span: 8 }}
                    // wrapperCol={{ span: 16 }}
                    layout="inline"
                >
                    {
                        clomes.map((item: any) => {
                            if (item.title == '操作' || item.options == 'options') return null;
                            return byClomeBackFromItem(item)
                        })
                    }

                </Form>}

            </>
        )
    }



    const handleOk = () => {
        form
            .validateFields()
            .then(async (values: any) => {
                // 校验成功
                setIsOpen(false)
                addApi && addApi(values) //add
                reloadtable && reloadtable()

            })
            .catch((err: any) => {
                // 校验失败
                console.log('err: ', err);
            });
    }

    const handleCancel = () => {
        form.resetFields() // 重置表单
        setIsOpen(false)
    }
    return (
        <>
            <Button {...btns} onClick={() => setIsOpen(true)}>{props.children}</Button>
            <Modal width={1000} onOk={handleOk} okText="提交" title="增加" open={isOpen} onCancel={handleCancel} cancelText='取消' >
                {byTableCloumsCreateFrom(clm)}
            </Modal>
        </>
    )
}

const HandleCloumnsTable = (props: any) => {
    const { byKeyisiideden, bykeyissort } = props

    let { allClomens: allClm } = getTableCloums()
    let dataSource: readonly any[] | undefined = []
    if (allClm) {
        /**
         * 返回对应的选择框和列名,用于渲染复选框table
         */
        dataSource = allClm.filter(i => i.title != '操作' || i.dataIndex != 'options').map(item => ({ key: item.key, name: item.title, hidden: null, filter: null, sort: null }))
    }
    /**
     * 
     * @param e 当前复选框实例
     * @param type 点击的类型
     * @param record 每行数据 
     */
    const handTableCheckbox = (e: CheckboxChangeEvent, type: string, record: any) => {
        switch (type) {
            case 'hidden':
                byKeyisiideden && byKeyisiideden(record.key, e.target.checked)
                break;
            case 'filter':
                /**
                 * 过滤还没弄 后期再弄
                 */
                console.log("点击啦过滤");
                break;
            case 'sort':
                bykeyissort && bykeyissort(record.key, e.target.checked)
                break;
            default:
                break
        }


    }
    //筛选列表所需
    const columns: ColumnsType<any> = [
        {
            key: "name",
            title: '列名',
            dataIndex: 'name',
        },
        {
            key: 'hidden',
            title: '隐藏',
            dataIndex: 'hidden',
            render: (_, record) => (
                <Checkbox onChange={(e: any) => handTableCheckbox(e, 'hidden', record)}></Checkbox>
            )
        },
        {
            key: 'filter',
            title: '过滤',
            dataIndex: 'filter',
            render: (_, record) => (
                <Checkbox onChange={(e: any) => handTableCheckbox(e, 'filter', record)}></Checkbox>
            )
        }
        , {
            key: 'sort',
            title: '排序',
            dataIndex: 'sort',
            render: (_, record) => (
                <Checkbox onChange={(e: any) => handTableCheckbox(e, 'sort', record)}></Checkbox>
            )
        }
    ]
    return (

        <Table columns={columns} dataSource={dataSource} pagination={false} >

        </Table>
    )

}


/**
 * 
 * @param props 
 * @returns table 删除
 */
export const Del = (props: BtnRenderTypes & ToolsOption) => {
    const [messageApi, contextHolder] = message.useMessage();
    let { chooseIds, reloadtable, delApi,...btns } = props
    const error = () => {
        messageApi.open({
            type: 'error',
            content: '请选择至少一条数据',
        });
    };
    //queren
    const warning = () => {
        confirm({
            title: '确认删除数据吗?',
            okText: '确认',
            cancelText: '取消',
            onCancel() { },
            onOk() {
                delApi && delApi(chooseIds) //del
                //删除操作
                reloadtable && reloadtable()
            },
        })

    };

    const del = () => {
        if (!chooseIds) return error();
        if (!chooseIds?.length) return error();
        warning()
    }
    return (
        <>
            {contextHolder}
            <Button {...btns} onClick={del} >{props.children}</Button>
        </>
    )
}
/**
 * 
 * @param props 
 * @returns 刷新table数据组件
 */
export const Reflesh = (props: BtnRenderTypes) => {
    let {  reloadtable,...btns } = props

    return (
        <>
            <Button {...btns} onClick={() => reloadtable()} >{props.children}</Button>
        </>
    )
}

/**
 * 
 * @param props 
 * @returns table列表 列操作显隐
 */
export const EditClomns = (props: BtnRenderTypes) => {
    let { byKeyisiideden, bykeyissort,reloadtable,...btns } = props
    const handProps = { byKeyisiideden, bykeyissort }
    const [open, setOpen] = useState(false);
    const handClick = () => {
        setOpen(true)
    }

    return (
        <>
            <Drawer title="列显隐" placement="right" onClose={() => setOpen(false)} open={open}>
                <HandleCloumnsTable {...handProps} />
            </Drawer>
            <Button {...btns} onClick={handClick} >{props.children}</Button>
        </>
    )
}






  • ts类型 这里可能不太完美
import type { ColumnsType } from 'antd/es/table';
import { MenuDataType } from "@/types"

export type BtnRenderTypes = {
  children?: any
  reloadTable?: Function
  [key: string]: any
}


export type ToolsOption = {
  
  addBtn?: boolean  //是否使用添加功能
  delBtn?: boolean  //是否使用删除
  reload?: boolean //是否使用刷新列表
  seach?: boolean //是否使用搜索
  columnsEdit?: boolean  //是否编辑
  chooseIds?: number | number[] | string[] | string
  searchApi?:Function  //用于搜索的请求方法 必须是请求
  addApi?:Function   //用于添加的请求方法
  delApi?:Function   // 用于删除的请求方法
  [key:string]: any
}

export type TableProps = {
  columns: ColumnsType<MenuDataType>
  data: MenuDataType[]|undefined
  isBorder?: boolean
  pagination?: boolean | any
  loading?: boolean | undefined
  toolsbar?: ToolsOption | undefined
  reloadTable?: Function
  [key: string]: any
}

  • MenuDataType
export type MenuDataType = {
  name: string
  id: number | string
  path: string
  source?: string
  code?: string
  children: MenuDataType[]
  [key: string]: any
}

如何使用代码


    /**
     * columns中{}列的每一项 
     * isRequires:表示添加时是否为必填项
     * radio:是在渲染列表时是否为ridio渲染
     * 操作列title必须为‘操作’ 或 dataIndex=“options” 
     */

    let columns: ColumnsType<MenuDataType> = [
        {
            title: '菜单名称',
            dataIndex: 'name',
            key: 'name',
            isSearch: true,
            isRequire: true
        },
        {
            title: '路由地址',
            dataIndex: 'path',
            key: 'path',
            isSearch: true,
            isRequire: true
        },
        {
            title: '菜单图标',
            dataIndex: 'source',
            key: 'source',
            isRequire: true
        },
        {
            title: '菜单编号',
            dataIndex: 'code',
            key: 'code',
            isSearch: true,
            isRequire: true
        },
        {
            title: '菜单排序',
            dataIndex: 'sort',
            key: 'sort',
            isRequire: true,
        },
        {
            title: '菜单类型',
            dataIndex: 'alias',
            key: 'alias',
            isRequire: true,
            radio: true

        },
        {
            title: '操作',
            dataIndex: 'options',
            key: 'options',
            render: (_, record: MenuDataType) => {
                return (<Space>
                    <Button onClick={() => { look(_, record) }} type="link">查看</Button>
                    <Button type="link">编辑</Button>
                    <Button type="link">删除</Button>
                    {
                        record.alias == 'menu' ? <Button type="link">添加子项</Button> : null
                    }
                    {
                        record.ifEdit ? <Button type="link" onClick={() => { EditPageForAmis(_, record) }} >编辑页面</Button> : null
                    }


                </Space>)


            }
        }
    ]


       <AntdTable
                columns={columns}
                loading={loading}
                pagination={false}
                data={data}
                isBorder={true}
                rowSelection='checkbox'
                reloadTable={getData}
                toolsbar={{
                    addBtn: true,
                    delBtn: true,
                    reload: true,
                    seach: true,
                    columnsEdit: true
                }}
            />
//toolsbar 非常重要 
//columns 也一定要按照要求定义

效果图

react 封装antd table 第一版---_第1张图片
点击添加
react 封装antd table 第一版---_第2张图片
列显隐
react 封装antd table 第一版---_第3张图片
搜索
在这里插入图片描述

已完成

  • 根据列表增加

  • 搜索

  • 删除

  • 控制列显示隐藏、排序只能数字

控制列筛选/过滤还未完成

经过两天加班封装的组件、全部源码奉上大佬们可以拿去继续开发、好用给个赞、快凹凸啦、谢谢

你可能感兴趣的:(react.js,javascript,前端)