From ca1491a5c4361750b607c3cc0a9e584527e3070b Mon Sep 17 00:00:00 2001 From: Juliefuling <1156308054@qq.com> Date: Thu, 18 Apr 2024 15:30:31 +0800 Subject: [PATCH] =?UTF-8?q?[issues/1196]Table=20=E5=88=97=E5=A4=B4?= =?UTF-8?q?=E6=8B=96=E5=8A=A8=E6=97=B6=E5=90=88=E8=AE=A1=E8=A1=8C=E9=94=99?= =?UTF-8?q?=E4=BD=8D;=20=E4=BF=AE=E6=94=B9=E5=90=88=E8=AE=A1=E8=A1=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8TableSummary=E6=96=B9=E5=BC=8F=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=EF=BC=9B=E6=94=AF=E6=8C=81slot=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=90=88=E8=AE=A1=E8=A1=8C=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /issues/1196 1、Table 列头拖动时合计行错位; 2、修改合计行使用TableSummary方式渲染; 3、支持slot自定义合计行展示; 4、列添加customSummaryRender自定义渲染函数 --- mock/demo/table-demo.ts | 2 + src/components/Table/src/BasicTable.vue | 31 +++- .../Table/src/components/TableSummary.tsx | 155 ++++++++++++++++++ src/components/Table/src/types/column.ts | 2 + src/components/Table/src/types/table.ts | 8 + src/views/demo/table/FooterTable.vue | 12 ++ src/views/demo/table/tableData.tsx | 18 ++ 7 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 src/components/Table/src/components/TableSummary.tsx diff --git a/mock/demo/table-demo.ts b/mock/demo/table-demo.ts index fbb51f6..082e021 100644 --- a/mock/demo/table-demo.ts +++ b/mock/demo/table-demo.ts @@ -30,6 +30,8 @@ const demoList = (() => { avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()), imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1), imgs: getRandomPics(Math.ceil(Math.random() * 3) + 1), + age: Math.ceil(Math.random() * 3) + 1, + score: Math.ceil(Math.random() * 80) + 1, date: `@date('yyyy-MM-dd')`, time: `@time('HH:mm')`, 'no|100000-10000000': 100000, diff --git a/src/components/Table/src/BasicTable.vue b/src/components/Table/src/BasicTable.vue index 58da9a9..00fdc45 100644 --- a/src/components/Table/src/BasicTable.vue +++ b/src/components/Table/src/BasicTable.vue @@ -41,6 +41,11 @@ + @@ -55,6 +60,7 @@ import CustomSelectHeader from './components/CustomSelectHeader.vue' import expandIcon from './components/ExpandIcon'; import HeaderCell from './components/HeaderCell.vue'; + import TableSummary from './components/TableSummary'; import { InnerHandlers } from './types/table'; import { usePagination } from './hooks/usePagination'; import { useColumns } from './hooks/useColumns'; @@ -67,12 +73,12 @@ import { useTableHeader } from './hooks/useTableHeader'; import { useTableExpand } from './hooks/useTableExpand'; import { createTableContext } from './hooks/useTableContext'; - import { useTableFooter } from './hooks/useTableFooter'; + // import { useTableFooter } from './hooks/useTableFooter'; import { useTableForm } from './hooks/useTableForm'; import { useDesign } from '/@/hooks/web/useDesign'; import { useCustomSelection } from "./hooks/useCustomSelection"; - import { omit } from 'lodash-es'; + import { omit, pick } from 'lodash-es'; import { basicProps } from './props'; import { isFunction } from '/@/utils/is'; import { warn } from '/@/utils/log'; @@ -82,6 +88,7 @@ Table, BasicForm, HeaderCell, + TableSummary, CustomSelectHeader, }, props: basicProps, @@ -227,7 +234,20 @@ const { getHeaderProps } = useTableHeader(getProps, slots, handlers); - const { getFooterProps } = useTableFooter(getProps, slots, getScrollRef, tableElRef, getDataSourceRef); + const getSummaryProps = computed(() => { + return pick(unref(getProps), ['summaryFunc', 'summaryData', 'hasExpandedRow', 'rowKey']); + }); + + const getIsEmptyData = computed(() => { + return (unref(getDataSourceRef) || []).length === 0; + }); + + const showSummaryRef = computed(() => { + const summaryProps = unref(getSummaryProps); + return (summaryProps.summaryFunc || summaryProps.summaryData) && !unref(getIsEmptyData); + }); + + // const { getFooterProps } = useTableFooter(getProps, slots, getScrollRef, tableElRef, getDataSourceRef); const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } = useTableForm(getProps, slots, fetch, getLoading); @@ -249,7 +269,7 @@ columns: toRaw(unref(getViewColumns)), pagination: toRaw(unref(getPaginationInfo)), dataSource, - footer: unref(getFooterProps), + // footer: unref(getFooterProps), ...unref(getExpandOption), // 【QQYUN-5837】动态计算 expandIconColumnIndex expandIconColumnIndex: getExpandIconColumnIndex.value, @@ -407,6 +427,9 @@ isCustomSelection, // update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题 slotNamesGroup, + getSummaryProps, + getIsEmptyData, + showSummaryRef, }; }, }); diff --git a/src/components/Table/src/components/TableSummary.tsx b/src/components/Table/src/components/TableSummary.tsx new file mode 100644 index 0000000..34ce3ac --- /dev/null +++ b/src/components/Table/src/components/TableSummary.tsx @@ -0,0 +1,155 @@ +import type { PropType, VNode } from 'vue'; +import { defineComponent, unref, computed, isVNode } from 'vue'; +import { cloneDeep, pick } from 'lodash-es'; +import { isFunction } from '/@/utils/is'; +import type { BasicColumn } from '../types/table'; +import { INDEX_COLUMN_FLAG } from '../const'; +import { propTypes } from '/@/utils/propTypes'; +import { useTableContext } from '../hooks/useTableContext'; +import { TableSummary, TableSummaryRow, TableSummaryCell } from 'ant-design-vue'; + +const SUMMARY_ROW_KEY = '_row'; +const SUMMARY_INDEX_KEY = '_index'; +export default defineComponent({ + name: 'BasicTableSummary', + components: { TableSummary, TableSummaryRow, TableSummaryCell }, + props: { + summaryFunc: { + type: Function as PropType, + }, + summaryData: { + type: Array as PropType, + }, + rowKey: propTypes.string.def('key'), + // 是否有展开列 + hasExpandedRow: propTypes.bool, + }, + setup(props) { + const table = useTableContext(); + + const getDataSource = computed((): Recordable[] => { + const { summaryFunc, summaryData } = props; + if (summaryData?.length) { + summaryData.forEach((item, i) => (item[props.rowKey] = `${i}`)); + return summaryData; + } + if (!isFunction(summaryFunc)) { + return []; + } + let dataSource = cloneDeep(unref(table.getDataSource())); + dataSource = summaryFunc(dataSource); + dataSource.forEach((item, i) => { + item[props.rowKey] = `${i}`; + }); + return dataSource; + }); + + const getColumns = computed(() => { + const dataSource = unref(getDataSource); + let columns: BasicColumn[] = cloneDeep(table.getColumns({ sort: true })); + columns = columns.filter((item) => !item.defaultHidden); + const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG); + const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY)); + const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY)); + + // 是否有序号列 + let hasIndexCol = false; + // 是否有选择列 + let hasSelection = table.getRowSelection() && hasRowSummary; + + if (index !== -1) { + if (hasIndexSummary) { + hasIndexCol = true; + columns[index].customSummaryRender = ({ record }) => record[SUMMARY_INDEX_KEY]; + columns[index].ellipsis = false; + } else { + Reflect.deleteProperty(columns[index], 'customSummaryRender'); + } + } + + if (hasSelection) { + const isFixed = columns.some((col) => col.fixed === 'left' || col.fixed === true); + columns.unshift({ + width: 60, + title: 'selection', + key: 'selectionKey', + align: 'center', + ...(isFixed ? { fixed: 'left' } : {}), + customSummaryRender: ({ record }) => hasIndexCol ? '' : record[SUMMARY_ROW_KEY], + }); + } + + if (props.hasExpandedRow) { + const isFixed = columns.some((col) => col.fixed === 'left'); + columns.unshift({ + width: 50, + title: 'expandedRow', + key: 'expandedRowKey', + align: 'center', + ...(isFixed ? { fixed: 'left' } : {}), + customSummaryRender: () => '', + }); + } + return columns; + }); + + function isRenderCell(data: any) { + return data && typeof data === 'object' && !Array.isArray(data) && !isVNode(data); + } + + const getValues = (row: Recordable, col: BasicColumn, index: number) => { + const value = row[col.dataIndex as string]; + let childNode: VNode | JSX.Element | string | number | undefined | null; + childNode = value; + if (col.customSummaryRender) { + const renderData = col.customSummaryRender({ + text: value, + value, + record: row, + index, + column: cloneDeep(col) + }) + if (isRenderCell(renderData)) { + childNode = renderData.children + } else { + childNode = renderData + } + if (typeof childNode === 'object' && !Array.isArray(childNode) && !isVNode(childNode)) { + childNode = null; + } + if (Array.isArray(childNode) && childNode.length === 1) { + childNode = childNode[0]; + } + return childNode; + } + return childNode; + } + + const getCellProps = (col: BasicColumn) => { + const cellProps = pick(col, ['colSpan', 'rowSpan', 'align']); + return { + ...cellProps + } + } + + return () => { + return ( + + {(unref(getDataSource) || []).map(row => { + return + {unref(getColumns).map((col, index) => { + return + {getValues(row, col, index)} + + })} + + })} + + ) + } + }, +}); diff --git a/src/components/Table/src/types/column.ts b/src/components/Table/src/types/column.ts index 8f820f4..bcae5dc 100644 --- a/src/components/Table/src/types/column.ts +++ b/src/components/Table/src/types/column.ts @@ -131,6 +131,8 @@ export interface ColumnProps { */ customRender?: CustomRenderFunction | VNodeChild | JSX.Element; + customSummaryRender?: CustomRenderFunction | VNodeChild | JSX.Element; + /** * Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true * @type boolean | Function diff --git a/src/components/Table/src/types/table.ts b/src/components/Table/src/types/table.ts index c3a463d..cc3fabe 100644 --- a/src/components/Table/src/types/table.ts +++ b/src/components/Table/src/types/table.ts @@ -453,6 +453,14 @@ export interface BasicColumn extends ColumnProps { ifShow?: boolean | ((column: BasicColumn) => boolean); //compType-用于记录类型 compType?: string; + customSummaryRender?: (opt: { + value: any; + text: any; + record: Recordable; + index: number; + renderIndex?: number; + column: BasicColumn; +}) => any | VNodeChild | JSX.Element; } export type ColumnChangeParam = { diff --git a/src/views/demo/table/FooterTable.vue b/src/views/demo/table/FooterTable.vue index e81780b..4f68bae 100644 --- a/src/views/demo/table/FooterTable.vue +++ b/src/views/demo/table/FooterTable.vue @@ -18,16 +18,28 @@ prev += next.no; return prev; }, 0); + const totalAge = tableData.reduce((prev, next) => { + prev += next.age; + return prev; + }, 0); + const totalScore = tableData.reduce((prev, next) => { + prev += next.score; + return prev; + }, 0); return [ { _row: '合计', _index: '平均值', no: totalNo, + age: Math.round(totalAge / tableData.length), + score: Math.round(totalScore / tableData.length) }, { _row: '合计', _index: '平均值', no: totalNo, + age: Math.round(totalAge / tableData.length), + score: Math.round(totalScore / tableData.length) }, ]; } diff --git a/src/views/demo/table/tableData.tsx b/src/views/demo/table/tableData.tsx index 5356c92..8bc8ab2 100644 --- a/src/views/demo/table/tableData.tsx +++ b/src/views/demo/table/tableData.tsx @@ -8,6 +8,7 @@ export function getBasicColumns(): BasicColumn[] { dataIndex: 'id', fixed: 'left', width: 200, + resizable: true }, { title: '姓名', @@ -18,6 +19,23 @@ export function getBasicColumns(): BasicColumn[] { { text: 'Female', value: 'female' }, ], }, + { + title: '年龄', + dataIndex: 'age', + width: 100, + resizable: true, + customSummaryRender: ({text}) => { + return ( + {text} + ) + } + }, + { + title: '得分', + dataIndex: 'score', + width: 100, + resizable: true + }, { title: '地址', dataIndex: 'address',