赞
踩
所用依赖
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 也一定要按照要求定义
点击添加
列显隐
搜索
根据列表增加
搜索
删除
控制列显示隐藏、排序只能数字
控制列筛选/过滤还未完成
经过两天加班封装的组件、全部源码奉上大佬们可以拿去继续开发、好用给个赞、快凹凸啦、谢谢
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。