所用依赖
antd5、@ant-design/icons、react18
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;
/**
*
* @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
}
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
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>
</>
)
}
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
}
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 也一定要按照要求定义
根据列表增加
搜索
删除
控制列显示隐藏、排序只能数字
控制列筛选/过滤还未完成
经过两天加班封装的组件、全部源码奉上大佬们可以拿去继续开发、好用给个赞、快凹凸啦、谢谢