[issues/1196]Table 列头拖动时合计行错位; 修改合计行使用TableSummary方式渲染;支持slot自定义合计行展示
/issues/1196 1、Table 列头拖动时合计行错位; 2、修改合计行使用TableSummary方式渲染; 3、支持slot自定义合计行展示; 4、列添加customSummaryRender自定义渲染函数pull/1201/head
parent
ec1649629d
commit
ca1491a5c4
|
@ -30,6 +30,8 @@ const demoList = (() => {
|
||||||
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
|
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
|
||||||
imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1),
|
imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1),
|
||||||
imgs: 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')`,
|
date: `@date('yyyy-MM-dd')`,
|
||||||
time: `@time('HH:mm')`,
|
time: `@time('HH:mm')`,
|
||||||
'no|100000-10000000': 100000,
|
'no|100000-10000000': 100000,
|
||||||
|
|
|
@ -41,6 +41,11 @@
|
||||||
</template>
|
</template>
|
||||||
<!-- update-begin--author:liaozhiyang---date:22030717---for:【issues-179】antd3 一些警告以及报错(针对表格) -->
|
<!-- update-begin--author:liaozhiyang---date:22030717---for:【issues-179】antd3 一些警告以及报错(针对表格) -->
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="showSummaryRef" #summary="data">
|
||||||
|
<slot name="summary" v-bind="data || {}">
|
||||||
|
<TableSummary v-bind="getSummaryProps" />
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,6 +60,7 @@
|
||||||
import CustomSelectHeader from './components/CustomSelectHeader.vue'
|
import CustomSelectHeader from './components/CustomSelectHeader.vue'
|
||||||
import expandIcon from './components/ExpandIcon';
|
import expandIcon from './components/ExpandIcon';
|
||||||
import HeaderCell from './components/HeaderCell.vue';
|
import HeaderCell from './components/HeaderCell.vue';
|
||||||
|
import TableSummary from './components/TableSummary';
|
||||||
import { InnerHandlers } from './types/table';
|
import { InnerHandlers } from './types/table';
|
||||||
import { usePagination } from './hooks/usePagination';
|
import { usePagination } from './hooks/usePagination';
|
||||||
import { useColumns } from './hooks/useColumns';
|
import { useColumns } from './hooks/useColumns';
|
||||||
|
@ -67,12 +73,12 @@
|
||||||
import { useTableHeader } from './hooks/useTableHeader';
|
import { useTableHeader } from './hooks/useTableHeader';
|
||||||
import { useTableExpand } from './hooks/useTableExpand';
|
import { useTableExpand } from './hooks/useTableExpand';
|
||||||
import { createTableContext } from './hooks/useTableContext';
|
import { createTableContext } from './hooks/useTableContext';
|
||||||
import { useTableFooter } from './hooks/useTableFooter';
|
// import { useTableFooter } from './hooks/useTableFooter';
|
||||||
import { useTableForm } from './hooks/useTableForm';
|
import { useTableForm } from './hooks/useTableForm';
|
||||||
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
import { useCustomSelection } from "./hooks/useCustomSelection";
|
import { useCustomSelection } from "./hooks/useCustomSelection";
|
||||||
|
|
||||||
import { omit } from 'lodash-es';
|
import { omit, pick } from 'lodash-es';
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '/@/utils/is';
|
||||||
import { warn } from '/@/utils/log';
|
import { warn } from '/@/utils/log';
|
||||||
|
@ -82,6 +88,7 @@
|
||||||
Table,
|
Table,
|
||||||
BasicForm,
|
BasicForm,
|
||||||
HeaderCell,
|
HeaderCell,
|
||||||
|
TableSummary,
|
||||||
CustomSelectHeader,
|
CustomSelectHeader,
|
||||||
},
|
},
|
||||||
props: basicProps,
|
props: basicProps,
|
||||||
|
@ -227,7 +234,20 @@
|
||||||
|
|
||||||
const { getHeaderProps } = useTableHeader(getProps, slots, handlers);
|
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);
|
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } = useTableForm(getProps, slots, fetch, getLoading);
|
||||||
|
|
||||||
|
@ -249,7 +269,7 @@
|
||||||
columns: toRaw(unref(getViewColumns)),
|
columns: toRaw(unref(getViewColumns)),
|
||||||
pagination: toRaw(unref(getPaginationInfo)),
|
pagination: toRaw(unref(getPaginationInfo)),
|
||||||
dataSource,
|
dataSource,
|
||||||
footer: unref(getFooterProps),
|
// footer: unref(getFooterProps),
|
||||||
...unref(getExpandOption),
|
...unref(getExpandOption),
|
||||||
// 【QQYUN-5837】动态计算 expandIconColumnIndex
|
// 【QQYUN-5837】动态计算 expandIconColumnIndex
|
||||||
expandIconColumnIndex: getExpandIconColumnIndex.value,
|
expandIconColumnIndex: getExpandIconColumnIndex.value,
|
||||||
|
@ -407,6 +427,9 @@
|
||||||
isCustomSelection,
|
isCustomSelection,
|
||||||
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||||
slotNamesGroup,
|
slotNamesGroup,
|
||||||
|
getSummaryProps,
|
||||||
|
getIsEmptyData,
|
||||||
|
showSummaryRef,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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<Fn>,
|
||||||
|
},
|
||||||
|
summaryData: {
|
||||||
|
type: Array as PropType<Recordable[]>,
|
||||||
|
},
|
||||||
|
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 (
|
||||||
|
<TableSummary fixed>
|
||||||
|
{(unref(getDataSource) || []).map(row => {
|
||||||
|
return <TableSummaryRow key={row[props.rowKey]}>
|
||||||
|
{unref(getColumns).map((col, index) => {
|
||||||
|
return <TableSummaryCell
|
||||||
|
{...getCellProps(col)}
|
||||||
|
index={index}
|
||||||
|
key={`${row[props.rowKey]}_${col.dataIndex}_${index}`}
|
||||||
|
>
|
||||||
|
{getValues(row, col, index)}
|
||||||
|
</TableSummaryCell>
|
||||||
|
})}
|
||||||
|
</TableSummaryRow>
|
||||||
|
})}
|
||||||
|
</TableSummary>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
|
@ -131,6 +131,8 @@ export interface ColumnProps<T> {
|
||||||
*/
|
*/
|
||||||
customRender?: CustomRenderFunction<T> | VNodeChild | JSX.Element;
|
customRender?: CustomRenderFunction<T> | VNodeChild | JSX.Element;
|
||||||
|
|
||||||
|
customSummaryRender?: CustomRenderFunction<T> | VNodeChild | JSX.Element;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true
|
* Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true
|
||||||
* @type boolean | Function
|
* @type boolean | Function
|
||||||
|
|
|
@ -453,6 +453,14 @@ export interface BasicColumn extends ColumnProps<Recordable> {
|
||||||
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||||
//compType-用于记录类型
|
//compType-用于记录类型
|
||||||
compType?: string;
|
compType?: string;
|
||||||
|
customSummaryRender?: (opt: {
|
||||||
|
value: any;
|
||||||
|
text: any;
|
||||||
|
record: Recordable;
|
||||||
|
index: number;
|
||||||
|
renderIndex?: number;
|
||||||
|
column: BasicColumn;
|
||||||
|
}) => any | VNodeChild | JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ColumnChangeParam = {
|
export type ColumnChangeParam = {
|
||||||
|
|
|
@ -18,16 +18,28 @@
|
||||||
prev += next.no;
|
prev += next.no;
|
||||||
return prev;
|
return prev;
|
||||||
}, 0);
|
}, 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 [
|
return [
|
||||||
{
|
{
|
||||||
_row: '合计',
|
_row: '合计',
|
||||||
_index: '平均值',
|
_index: '平均值',
|
||||||
no: totalNo,
|
no: totalNo,
|
||||||
|
age: Math.round(totalAge / tableData.length),
|
||||||
|
score: Math.round(totalScore / tableData.length)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_row: '合计',
|
_row: '合计',
|
||||||
_index: '平均值',
|
_index: '平均值',
|
||||||
no: totalNo,
|
no: totalNo,
|
||||||
|
age: Math.round(totalAge / tableData.length),
|
||||||
|
score: Math.round(totalScore / tableData.length)
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ export function getBasicColumns(): BasicColumn[] {
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: 200,
|
width: 200,
|
||||||
|
resizable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '姓名',
|
title: '姓名',
|
||||||
|
@ -18,6 +19,23 @@ export function getBasicColumns(): BasicColumn[] {
|
||||||
{ text: 'Female', value: 'female' },
|
{ text: 'Female', value: 'female' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '年龄',
|
||||||
|
dataIndex: 'age',
|
||||||
|
width: 100,
|
||||||
|
resizable: true,
|
||||||
|
customSummaryRender: ({text}) => {
|
||||||
|
return (
|
||||||
|
<span style="color: red;">{text}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '得分',
|
||||||
|
dataIndex: 'score',
|
||||||
|
width: 100,
|
||||||
|
resizable: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '地址',
|
title: '地址',
|
||||||
dataIndex: 'address',
|
dataIndex: 'address',
|
||||||
|
|
Loading…
Reference in New Issue