ToB企服应用市场:ToB评测及商务社交产业平台

标题: react + ts + material-ui V5版本的table封装 [打印本页]

作者: 泉缘泉    时间: 2024-8-31 21:54
标题: react + ts + material-ui V5版本的table封装
以下是一份 material-ui V5版本的table封装
  1. import React, { forwardRef, useImperativeHandle, useEffect, useState } from 'react';
  2. import {
  3.     Table,
  4.     TableBody,
  5.     TableSortLabel,
  6.     TableCell,
  7.     TableContainer,
  8.     TableHead,
  9.     TableRow,
  10.     Typography,
  11.     Paper,
  12.     Checkbox,
  13.     CircularProgress,
  14.     Box,
  15.     TablePagination,
  16.     Grid,
  17.     Button
  18. } from '@mui/material';
  19. import { useDispatch } from 'store';
  20. import { useIntl } from 'react-intl';
  21. import { openSnackbar } from 'store/slices/snackbar';
  22. // import { cloneDeep } from 'lodash';
  23. interface IColumn {
  24.     slot?: string;
  25.     title?: string;
  26.     prop: string;
  27.     fixed?: string;
  28.     minWidth?: number;
  29.     sorting?: boolean; // 是否可以排序
  30.     titleRender?: (row: any, injectionData?: any) => React.ReactNode;
  31.     render?: (row: any, injectionData?: any) => React.ReactNode;
  32.     isRowSelectable?: (row: any) => boolean; // 新增属性,用于判断某行是否可以被勾选
  33.     align?: 'left' | 'center' | 'right'; // 新增属性,用于指定对齐方式
  34. }
  35. // interface IRequiredParameters {
  36. //     [key: string]: any;
  37. // }
  38. interface ItableConfig {
  39.     columns: IColumn[];
  40.     Api: any; //请求接口方法
  41.     fetchConfig?: {};
  42.     onSort?: (column: string, order: 'asc' | 'desc') => void;
  43.     showSelect?: boolean; // 控制是否显示选择框
  44.     initParams?: {};
  45.     requestParams?: {};
  46.     pagination?: {
  47.         fixedColumns?: string[];
  48.         sorting?: boolean;
  49.         pageSize: number;
  50.         pageSizeOptions?: number[];
  51.     };
  52.     isDisableFirstRequest?: boolean; //是否禁用第一次进入时就请求接口  为true时,需要手动调用request方法
  53.     rowKey?: string;
  54.     isCacheCheckOptions?: boolean; // 新增属性,控制是否缓存勾选项
  55.     dataRootArrKey?: string;
  56.     paginationKey?: {
  57.         pageSizeKeyName: string;
  58.         totalCountKeyName: string;
  59.         pageIndexKeyName: string;
  60.     };
  61.     injectionData?: any; //从外部注入的数据  给内部通讯
  62.     dataListKey?: string; // data下面的第二级别list key的名称
  63.     maxHeight?: number | string;
  64.     onSelectChange?: (selected: any[]) => void; // 新增属性,当选择变化时调用 selectedIds: string[],
  65.     showTablePagination?: boolean; //是否展示分页组件
  66.     render?: (row: any) => React.ReactNode;
  67.     getCurrentStatus?(status: boolean): any; //获取当前是否是loading状态  来同步search的按钮状态
  68.     requiredParametersCallBack?: (params: any) => boolean; //搜索必要参数条件
  69.     onChangeList?: (data: any[], obj: any) => void; // 获取列表数据回调
  70.     onGetListAfter?: (ls?: any[]) => void; // 获取列表成功后调用一下
  71. }
  72. // type Order = 'asc' | 'desc';
  73. export interface TableMethods {
  74.     getTableList: (option?: any) => void; // 你希望父组件能调用的方法
  75.     resetTableList: (option?: any) => void; // 你希望父组件能调用的方法
  76.     clearSelection: () => void; // 新增的方法
  77.     getCheckedRows: () => void;
  78. }
  79. const EnhancedTable = forwardRef<TableMethods, ItableConfig>(
  80.     (
  81.         {
  82.             columns,
  83.             Api,
  84.             showTablePagination = true,
  85.             paginationKey = {
  86.                 pageSizeKeyName: 'pageSize',
  87.                 totalCountKeyName: 'total',
  88.                 pageIndexKeyName: 'page'
  89.             },
  90.             onSort,
  91.             initParams = {},
  92.             onSelectChange,
  93.             showSelect = false,
  94.             dataRootArrKey,
  95.             rowKey = 'id',
  96.             pagination = {
  97.                 pageSize: 10
  98.             },
  99.             maxHeight = 610,
  100.             requestParams = [],
  101.             dataListKey = 'list',
  102.             isCacheCheckOptions = false,
  103.             isDisableFirstRequest = false,
  104.             getCurrentStatus,
  105.             requiredParametersCallBack = null,
  106.             onChangeList,
  107.             onGetListAfter,
  108.             injectionData
  109.         },
  110.         ref
  111.     ) => {
  112.         // 使用 useImperativeHandle 来暴露方法给父组件
  113.         useImperativeHandle(ref, () => ({
  114.             getTableList(option?: any) {
  115.                 option ? getList(option) : getList();
  116.             },
  117.             resetTableList(option?: any) {
  118.                 // ... 实现你希望父组件能调用的方法
  119.                 if (page === 0 && rowsPerPage === pagination.pageSize) {
  120.                     option ? getList(option) : getList();
  121.                 } else {
  122.                     setRowsPerPage(pagination.pageSize);
  123.                     setPage(0);
  124.                 }
  125.             },
  126.             getCheckedRows() {
  127.                 return selected;
  128.             },
  129.             clearSelection // 暴露新的方法
  130.         }));
  131.         const [isRestPageIndex, setIsRestPageIndex] = useState(true);
  132.         const getStickyStyle = (column: IColumn, index: number): React.CSSProperties => {
  133.             if (column.fixed) {
  134.                 return {
  135.                     position: 'sticky',
  136.                     borderLeft: column.fixed === 'right' ? '1px solid rgba(224, 224, 224, 1)' : undefined,
  137.                     boxShadow: column.fixed === 'right' ? '-2px 0px 3px rgba(0, 0, 0, 0.2)' : undefined,
  138.                     right: column.fixed === 'right' ? 0 : undefined,
  139.                     left: column.fixed === 'left' ? 0 : undefined,
  140.                     backgroundColor: '#fff',
  141.                     zIndex: 2
  142.                 };
  143.             }
  144.             return {};
  145.         };
  146.         const dispatch = useDispatch();
  147.         const intl = useIntl();
  148.         const [data, setData] = useState<any[]>([]);
  149.         const [rowsPerPage, setRowsPerPage] = useState(pagination.pageSize || 10);
  150.         const [loading, setLoading] = useState(false);
  151.         const [order, setOrder] = useState<'asc' | 'desc'>('asc');
  152.         const [orderBy, setOrderBy] = useState<string | null>(null);
  153.         const [totalCount, setTotalCount] = useState(0);
  154.         const [selected, setSelected] = useState<any[]>([]);
  155.         const [page, setPage] = useState(0);
  156.         const defaultErrorMessage = '出了点问题请稍后再试';
  157.         // const [lastRequestParams, setLastRequestParams] = useState<any>({});
  158.         // 新增的方法来清除所有勾选项
  159.         const clearSelection = () => {
  160.             setSelected([]);
  161.             if (onSelectChange) {
  162.                 onSelectChange([]);
  163.             }
  164.         };
  165.         // isDisableFirstRequest
  166.         const [flagFirst, setFlagFirst] = useState(false);
  167.         useEffect(() => {
  168.             if (isDisableFirstRequest) {
  169.                 if (flagFirst) {
  170.                     getList();
  171.                 } else {
  172.                     setFlagFirst(true);
  173.                 }
  174.             } else {
  175.                 getList();
  176.             }
  177.         }, [page, rowsPerPage]);
  178.         useEffect(() => {
  179.             getCurrentStatus && getCurrentStatus(loading);
  180.         }, [loading]);
  181.         const [isCacheCheckFlag, setIsCacheCheckFlag] = useState(false);
  182.         const getList = async (option = {}) => {
  183.             if (loading) return;
  184.             var ppppageIndex = page + 1;
  185.             if (isRestPageIndex) {
  186.                 setPage(0);
  187.                 ppppageIndex = 1;
  188.             }
  189.             try {
  190.                 var params: any = {
  191.                     [paginationKey.pageIndexKeyName]: ppppageIndex, //TablePagination ui是从0开始的
  192.                     [paginationKey.pageSizeKeyName]: rowsPerPage,
  193.                     ...initParams,
  194.                     ...requestParams,
  195.                     ...option
  196.                 };
  197.                 if (!!requiredParametersCallBack) {
  198.                     var fl: boolean = requiredParametersCallBack(params); //返回true才截断
  199.                     if (fl) {
  200.                         return false;
  201.                     }
  202.                 }
  203.                 setLoading(true);
  204.                 // 如果请求参数变化,并且不是因为翻页或修改每页条数(即是一次新的搜索),则清除勾选项
  205.                 if (showSelect) {
  206.                     if (isCacheCheckOptions) {
  207.                         if (
  208.                             isCacheCheckFlag
  209.                             // (lastRequestParams[paginationKey.pageIndexKeyName] !== params[paginationKey.pageIndexKeyName] ||
  210.                             //     lastRequestParams[paginationKey.pageSizeKeyName] !== params[paginationKey.pageSizeKeyName])
  211.                         ) {
  212.                             setIsCacheCheckFlag(false);
  213.                         }
  214.                     } else {
  215.                         clearSelection();
  216.                     }
  217.                 }
  218.                 const res = await Api(params, dispatch, intl);
  219.                 // // 更新最后一次请求参数
  220.                 // setLastRequestParams(cloneDeep(params));
  221.                 setLoading(false);
  222.                 setIsRestPageIndex(true);
  223.                 if (res.code === 0) {
  224.                     const datas: {
  225.                         [dataListKey: string]: any;
  226.                     } = res.data;
  227.                     const ls = datas[dataListKey];
  228.                     if (ls && Array.isArray(ls)) {
  229.                         setData(ls);
  230.                         const ss = paginationKey.totalCountKeyName as string;
  231.                         var num = datas[ss] as number;
  232.                         setTotalCount(num); //总页码
  233.                         onChangeList?.(ls, res);
  234.                     } else {
  235.                         setData([]);
  236.                         setTotalCount(0);
  237.                     }
  238.                     onGetListAfter && onGetListAfter(ls);
  239.                 } else {
  240.                     setLoading(false);
  241.                     setData([]);
  242.                     setTotalCount(0);
  243.                     onChangeList?.([], res);
  244.                     dispatch(
  245.                         openSnackbar({
  246.                             open: true,
  247.                             message: res.msg || defaultErrorMessage,
  248.                             variant: 'alert',
  249.                             alert: {
  250.                                 color: 'error'
  251.                             },
  252.                             close: false,
  253.                             anchorOrigin: {
  254.                                 vertical: 'top',
  255.                                 horizontal: 'center'
  256.                             }
  257.                         })
  258.                     );
  259.                 }
  260.             } catch (error) {
  261.                 setLoading(false);
  262.                 setIsRestPageIndex(true);
  263.                 console.log('error error error', error);
  264.             }
  265.         };
  266.         const handleSortRequest = (column: string) => {
  267.             const isAsc = orderBy === column && order === 'asc';
  268.             setOrder(isAsc ? 'desc' : 'asc');
  269.             setOrderBy(column);
  270.             onSort?.(column, isAsc ? 'desc' : 'asc');
  271.         };
  272.         const handleClick = (event: React.MouseEvent<unknown>, row: any) => {
  273.             const isSelectable = columns.every((column) => (column.isRowSelectable ? column.isRowSelectable(row) : true));
  274.             if (!isSelectable) {
  275.                 // 如果行不可选,直接返回不执行任何操作
  276.                 return;
  277.             }
  278.             const selectedIndex = selected.findIndex((r) => r[rowKey] === row[rowKey]);
  279.             let newSelected: any[] = [];
  280.             if (selectedIndex === -1) {
  281.                 newSelected = newSelected.concat(selected, row);
  282.             } else if (selectedIndex === 0) {
  283.                 newSelected = newSelected.concat(selected.slice(1));
  284.             } else if (selectedIndex === selected.length - 1) {
  285.                 newSelected = newSelected.concat(selected.slice(0, -1));
  286.             } else if (selectedIndex > 0) {
  287.                 newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1));
  288.             }
  289.             setSelected(newSelected);
  290.             onSelectChange?.(newSelected);
  291.         };
  292.         const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
  293.             if (event.target.checked) {
  294.                 // 仅选择那些满足 isRowSelectable 条件的行
  295.                 const newSelecteds = data.filter((row) =>
  296.                     columns.every((column) => (column.isRowSelectable ? column.isRowSelectable(row) : true))
  297.                 );
  298.                 setSelected(newSelecteds);
  299.                 onSelectChange?.(newSelecteds);
  300.             } else {
  301.                 setSelected([]);
  302.                 onSelectChange?.([]);
  303.             }
  304.         };
  305.         const handleChangePage = (_event: unknown, newPage: number) => {
  306.             setIsCacheCheckFlag(true);
  307.             setPage(newPage);
  308.             setIsRestPageIndex(false);
  309.         };
  310.         const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
  311.             setIsCacheCheckFlag(true);
  312.             setRowsPerPage(parseInt(event.target.value, 10));
  313.         };
  314.         return (
  315.             <Box>
  316.                 {showSelect && (
  317.                     <Box sx={{ minHeight: '38px' }}>
  318.                         <Grid container>
  319.                             <Grid item>
  320.                                 <Typography
  321.                                     sx={{
  322.                                         fontSize: '16px',
  323.                                         paddingTop: '5px'
  324.                                     }}
  325.                                     variant="h6"
  326.                                 >
  327.                                     {'已勾选'}:
  328.                                     <Button
  329.                                         sx={{
  330.                                             cursor: 'auto',
  331.                                             padding: '0',
  332.                                             minWidth: '20px'
  333.                                         }}
  334.                                     >
  335.                                         {selected.length}
  336.                                     </Button>
  337.                                     {'项'}
  338.                                 </Typography>
  339.                             </Grid>
  340.                             {selected.length > 0 && (
  341.                                 <Grid
  342.                                     item
  343.                                     sx={{
  344.                                         marginLeft: '10px'
  345.                                     }}
  346.                                 >
  347.                                     <Button onClick={clearSelection}> {'取消选择'}</Button>
  348.                                 </Grid>
  349.                             )}
  350.                         </Grid>
  351.                     </Box>
  352.                 )}
  353.                 <Paper sx={{ width: '100%', overflow: 'hidden' }}>
  354.                     <TableContainer component={Paper} sx={{ maxHeight: maxHeight, overflow: 'auto' }}>
  355.                         <Table
  356.                             stickyHeader
  357.                             aria-label="sticky table"
  358.                             sx={{
  359.                                 minWidth: 750,
  360.                                 tableLayout: 'auto',
  361.                                 '& .MuiTableCell-root': {
  362.                                     borderBottom: '1px solid rgba(224, 224, 224, 1)' // 底部边框
  363.                                 }
  364.                             }}
  365.                         >
  366.                             <TableHead>
  367.                                 <TableRow>
  368.                                     {showSelect && (
  369.                                         <TableCell padding="checkbox">
  370.                                             <Checkbox
  371.                                                 indeterminate={selected.length > 0 && selected.length < totalCount}
  372.                                                 checked={totalCount > 0 && selected.length === totalCount}
  373.                                                 onChange={handleSelectAllClick}
  374.                                                 inputProps={{ 'aria-label': 'select all desserts' }}
  375.                                             />
  376.                                         </TableCell>
  377.                                     )}
  378.                                     {columns.map((column) => (
  379.                                         <TableCell
  380.                                             align={column.align || 'left'} // 使用 align 属性,如果未指定,默认为左对齐
  381.                                             key={column.prop ? column.prop : Math.floor(Math.random() * 10000) + ''}
  382.                                             style={{
  383.                                                 minWidth: column?.minWidth,
  384.                                                 position: column.fixed ? 'sticky' : undefined,
  385.                                                 top: 0, // 确保固定列头在顶部
  386.                                                 right: column.fixed === 'right' ? 0 : undefined,
  387.                                                 backgroundColor: '#f8fafc', // 确保固定列的背景色不透明
  388.                                                 zIndex: column.fixed ? 110 : 1, // 确保固定列在滚动时覆盖其他列,1100 是 MUI 中的 AppBar zIndex
  389.                                                 borderLeft: column.fixed === 'right' ? '1px solid rgba(224, 224, 224, 1)' : undefined,
  390.                                                 boxShadow: column.fixed === 'right' ? '-2px 0px 3px rgba(0, 0, 0, 0.2)' : undefined
  391.                                             }}
  392.                                             sortDirection={orderBy === column.prop ? order : false}
  393.                                         >
  394.                                             {column.titleRender ? (
  395.                                                 column.titleRender(column, injectionData)
  396.                                             ) : column.sorting ? (
  397.                                                 <TableSortLabel
  398.                                                     active={orderBy === column.prop}
  399.                                                     direction={orderBy === column.prop ? order : 'asc'}
  400.                                                     onClick={() => handleSortRequest(column.prop)}
  401.                                                 >
  402.                                                     {column.title}
  403.                                                 </TableSortLabel>
  404.                                             ) : (
  405.                                                 column.title
  406.                                             )}
  407.                                         </TableCell>
  408.                                     ))}
  409.                                     {columns.map((column) =>
  410.                                         column.slot === 'right' ? (
  411.                                             <TableCell key={column.prop} style={{ minWidth: column.minWidth }}>
  412.                                                 {column.title}
  413.                                             </TableCell>
  414.                                         ) : null
  415.                                     )}
  416.                                 </TableRow>
  417.                             </TableHead>
  418.                             <TableBody>
  419.                                 {loading ? (
  420.                                     <TableRow>
  421.                                         <TableCell colSpan={columns.length + (showSelect ? 1 : 0)} style={{ textAlign: 'center' }}>
  422.                                             <CircularProgress />
  423.                                         </TableCell>
  424.                                     </TableRow>
  425.                                 ) : data.length > 0 ? (
  426.                                     data.map((row, index) => {
  427.                                         const isItemSelected = selected.some((r) => r[rowKey] === row[rowKey]);
  428.                                         const labelId = `enhanced-table-checkbox-${index}`;
  429.                                         // 使用 column 中的 isRowSelectable 函数来判断行是否可选,如果没有提供,则默认为可选
  430.                                         const isSelectable = columns.every((column) =>
  431.                                             column.isRowSelectable ? column.isRowSelectable(row) : true
  432.                                         );
  433.                                         return (
  434.                                             <TableRow
  435.                                                 hover
  436.                                                 onClick={
  437.                                                     showSelect
  438.                                                         ? (event) => {
  439.                                                               // 检查该行是否可选
  440.                                                               const isSelectable = columns.every((column) =>
  441.                                                                   column.isRowSelectable ? column.isRowSelectable(row) : true
  442.                                                               );
  443.                                                               if (isSelectable) {
  444.                                                                   handleClick(event, row);
  445.                                                               }
  446.                                                           }
  447.                                                         : undefined
  448.                                                 }
  449.                                                 role="checkbox"
  450.                                                 aria-checked={isItemSelected}
  451.                                                 tabIndex={-1}
  452.                                                 key={row[rowKey]}
  453.                                                 selected={isItemSelected}
  454.                                             >
  455.                                                 {showSelect && (
  456.                                                     <TableCell padding="checkbox">
  457.                                                         <Checkbox
  458.                                                             checked={isItemSelected}
  459.                                                             disabled={!isSelectable} // 根据 isSelectable 禁用或启用复选框
  460.                                                             inputProps={{ 'aria-labelledby': labelId }}
  461.                                                         />
  462.                                                     </TableCell>
  463.                                                 )}
  464.                                                 {columns.map((column) => (
  465.                                                     <TableCell key={`${row[rowKey]}-${column.prop}`} style={getStickyStyle(column, index)}>
  466.                                                         {column.render ? column.render(row, injectionData) : row[column?.prop]}
  467.                                                     </TableCell>
  468.                                                 ))}
  469.                                             </TableRow>
  470.                                         );
  471.                                     })
  472.                                 ) : (
  473.                                     <TableRow>
  474.                                         <TableCell colSpan={columns.length + (showSelect ? 1 : 0)} align="center">
  475.                                             {'暂无数据'}
  476.                                         </TableCell>
  477.                                     </TableRow>
  478.                                 )}
  479.                             </TableBody>
  480.                         </Table>
  481.                     </TableContainer>
  482.                     {showTablePagination && (
  483.                         <Box sx={{ display: 'flex', justifyContent: 'flex-start' }}>
  484.                             <TablePagination
  485.                                 rowsPerPageOptions={pagination?.pageSizeOptions || [10, 20, 30, 50, 100]}
  486.                                 component="div"
  487.                                 count={totalCount}
  488.                                 rowsPerPage={rowsPerPage}
  489.                                 page={page}
  490.                                 sx={{
  491.                                     '.MuiTablePagination-toolbar': {
  492.                                         alignItems: 'center', // 确保工具栏中的所有元素都垂直居中
  493.                                         justifyContent: 'flex-end' // 工具栏内的元素靠右对齐
  494.                                     },
  495.                                     '.MuiTablePagination-selectLabel': {
  496.                                         margin: 0 // 移除默认的外边距
  497.                                     },
  498.                                     '.MuiTablePagination-select': {
  499.                                         margin: 0 // 移除默认的外边距
  500.                                     },
  501.                                     '.MuiTablePagination-displayedRows': {
  502.                                         margin: 0 // 移除默认的外边距
  503.                                     },
  504.                                     marginLeft: '-7px !important'
  505.                                     // 你可以根据需要添加更多的样式规则
  506.                                 }}
  507.                                 onPageChange={handleChangePage}
  508.                                 onRowsPerPageChange={handleChangeRowsPerPage}
  509.                             />
  510.                         </Box>
  511.                     )}
  512.                 </Paper>
  513.             </Box>
  514.         );
  515.     }
  516. );
  517. export default EnhancedTable;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4