| | |
| | | |
| | | import React from 'react'; |
| | | import { DataTable } from 'react-admin'; |
| | | import { DataTable, useDataTableDataContext, useTranslate } from 'react-admin'; |
| | | import { TableFooter, TableRow, TableCell } from '@mui/material'; |
| | | |
| | | /** |
| | | * 计算函数映射 |
| | | */ |
| | | const calculators = { |
| | | sum: (data, field) => data.reduce((acc, record) => acc + (Number(record[field]) || 0), 0), |
| | | count: (data, field) => data.filter(record => record[field] != null && record[field] !== '').length, |
| | | avg: (data, field) => { |
| | | const validData = data.filter(record => record[field] != null); |
| | | if (validData.length === 0) return 0; |
| | | const sum = validData.reduce((acc, record) => acc + (Number(record[field]) || 0), 0); |
| | | return (sum / validData.length).toFixed(2); |
| | | }, |
| | | min: (data, field) => { |
| | | const values = data.map(record => Number(record[field]) || 0); |
| | | return values.length > 0 ? Math.min(...values) : 0; |
| | | }, |
| | | max: (data, field) => { |
| | | const values = data.map(record => Number(record[field]) || 0); |
| | | return values.length > 0 ? Math.max(...values) : 0; |
| | | }, |
| | | }; |
| | | |
| | | /** |
| | | * 计算类型的中文前缀映射 |
| | | */ |
| | | const typeLabels = { |
| | | sum: '合计', |
| | | count: '数量', |
| | | avg: '平均', |
| | | min: '最小', |
| | | max: '最大', |
| | | }; |
| | | |
| | | /** |
| | | * 内部 Footer 组件 |
| | | * @param {Object} props |
| | | * @param {Array} props.footerConfig - footer 配置数组 |
| | | * @param {string} props.footerLabel - 第一列显示的标签,默认'合计' |
| | | */ |
| | | const StickyTableFooter = ({ footerConfig, footerLabel = '合计' }) => { |
| | | const data = useDataTableDataContext(); |
| | | const translate = useTranslate(); |
| | | |
| | | const results = footerConfig.map(config => { |
| | | const { field, type = 'sum', label, render } = config; |
| | | const calculator = calculators[type]; |
| | | const value = calculator ? calculator(data, field) : 0; |
| | | const typePrefix = typeLabels[type] || '合计'; |
| | | |
| | | // 支持自定义渲染 |
| | | if (render) { |
| | | return { label, value: render(value, data), typePrefix }; |
| | | } |
| | | |
| | | // 获取翻译后的标签 |
| | | const displayLabel = label ? (label.startsWith('table.') || label.startsWith('common.') ? translate(label) : label) : field; |
| | | return { label: displayLabel, value, typePrefix }; |
| | | }); |
| | | |
| | | return ( |
| | | <TableFooter> |
| | | <TableRow> |
| | | {results.map((item, index) => ( |
| | | <TableCell key={index} variant="footer" align="left"> |
| | | {item.label} {item.typePrefix}: {item.value} |
| | | </TableCell> |
| | | ))} |
| | | <TableCell colSpan={99} /> |
| | | </TableRow> |
| | | </TableFooter> |
| | | ); |
| | | }; |
| | | |
| | | /** |
| | | * StickyDataTable Component |
| | | * |
| | | * 封装 react-admin 的 DataTable,实现传入列名即可固定列。 |
| | | * 封装 react-admin 的 DataTable,实现传入列名即可固定列,支持配置化 footer 汇总。 |
| | | * |
| | | * @param {Object} props |
| | | * @param {string[]} props.stickyLeft - 需要固定在左侧的字段 source 列表 |
| | | * @param {string[]} props.stickyRight - 需要固定在右侧的字段 source 列表 |
| | | * @param {Array} props.footerConfig - footer 汇总配置,格式:[{ field: 'anfme', type: 'sum', label: 'table.field.xxx' }] |
| | | * - field: 要计算的字段名 |
| | | * - type: 计算类型,支持 'sum' | 'count' | 'avg' | 'min' | 'max',默认 'sum' |
| | | * - label: 显示的标签,支持翻译 key 或直接显示的文本 |
| | | * - render: 可选,自定义渲染函数 (value, data) => ReactNode |
| | | * @param {string} props.footerLabel - footer 第一列标签,默认'合计' |
| | | */ |
| | | export const StickyDataTable = ({ stickyLeft = [], stickyRight = [], children, ...props }) => { |
| | | export const StickyDataTable = ({ |
| | | stickyLeft = [], |
| | | stickyRight = [], |
| | | footerConfig, |
| | | footerLabel = '合计', |
| | | children, |
| | | ...props |
| | | }) => { |
| | | |
| | | // 递归处理 Children,确保即便是 Fragment 包裹的列也能被处理 |
| | | const processChildren = (children) => { |
| | |
| | | }); |
| | | }; |
| | | |
| | | // 构建 foot 属性 |
| | | const footerComponent = footerConfig && footerConfig.length > 0 |
| | | ? () => <StickyTableFooter footerConfig={footerConfig} footerLabel={footerLabel} /> |
| | | : undefined; |
| | | |
| | | return ( |
| | | <DataTable {...props} sx={{ |
| | | <DataTable {...props} foot={footerComponent} sx={{ |
| | | '& .MuiTableCell-head': { |
| | | zIndex: 4, |
| | | borderBottom: 'none' // 遵循之前的优化,去除表头下边框 |
| | | }, |
| | | '& .MuiTableFooter-root': { |
| | | position: 'sticky', |
| | | bottom: 0, |
| | | zIndex: 3, |
| | | backgroundColor: '#FFFFFF', |
| | | }, |
| | | '& .MuiTableFooter-root .MuiTableCell-root': { |
| | | backgroundColor: '#f5f5f5', |
| | | fontWeight: 'bold', |
| | | borderTop: '2px solid #e0e0e0', |
| | | }, |
| | | }}> |
| | | {processChildren(children)} |
| | | </DataTable> |