feat: table support summary

pull/4639/head
tangjinzhou 2021-09-09 23:16:08 +08:00
parent 9693d89342
commit 16e801249c
18 changed files with 366 additions and 74 deletions

View File

@ -172,7 +172,14 @@ export type {
ColumnProps as TableColumnProps, ColumnProps as TableColumnProps,
ColumnsType as TableColumnsType, ColumnsType as TableColumnsType,
} from './table'; } from './table';
export { default as Table, TableColumn, TableColumnGroup } from './table'; export {
default as Table,
TableColumn,
TableColumnGroup,
TableSummary,
TableSummaryRow,
TableSummaryCell,
} from './table';
export type { TransferProps } from './transfer'; export type { TransferProps } from './transfer';
export { default as Transfer } from './transfer'; export { default as Transfer } from './transfer';

View File

@ -1,7 +1,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { ColumnType } from './interface'; import type { ColumnType } from './interface';
export type ColumnProps = ColumnType; export type ColumnProps<RecordType = unknown> = ColumnType<RecordType>;
export default defineComponent<ColumnProps>({ export default defineComponent<ColumnProps>({
name: 'ATableColumn', name: 'ATableColumn',
slots: ['title', 'filterIcon'], slots: ['title', 'filterIcon'],

View File

@ -489,7 +489,9 @@ const InteralTable = defineComponent<
return classNames( return classNames(
{ {
[`${prefixCls}-row-selected`]: selectedKeySet.value.has(getRowKey.value(record, index)), [`${prefixCls.value}-row-selected`]: selectedKeySet.value.has(
getRowKey.value(record, index),
),
}, },
mergedRowClassName, mergedRowClassName,
); );
@ -645,11 +647,4 @@ const Table = defineComponent<TableProps>({
}, },
}); });
Table.SELECTION_ALL = SELECTION_ALL;
Table.SELECTION_INVERT = SELECTION_INVERT;
Table.SELECTION_NONE = SELECTION_NONE;
Table.Column = Column;
Table.ColumnGroup = ColumnGroup;
Table.Summary = Summary;
export default Table; export default Table;

View File

@ -20,9 +20,6 @@ Ellipsis cell content via setting `column.ellipsis`.
<template> <template>
<a-table :columns="columns" :data-source="data"> <a-table :columns="columns" :data-source="data">
<template #name="{ text }">
<a>{{ text }}</a>
</template>
<template #bodyCell="{ column, text }"> <template #bodyCell="{ column, text }">
<template v-if="column.dataIndex === 'name'"> <template v-if="column.dataIndex === 'name'">
<a>{{ text }}</a> <a>{{ text }}</a>
@ -38,7 +35,6 @@ const columns = [
title: 'Name', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
slots: { customRender: 'name' },
}, },
{ {
title: 'Age', title: 'Age',

View File

@ -15,6 +15,7 @@
<FixedHeader /> <FixedHeader />
<GroupingColumns /> <GroupingColumns />
<Head /> <Head />
<MultipleSorter />
<NestedTable /> <NestedTable />
<ResetFilter /> <ResetFilter />
<RowSelectionAndOperation /> <RowSelectionAndOperation />
@ -22,6 +23,7 @@
<RowSelection /> <RowSelection />
<Size /> <Size />
<Stripe /> <Stripe />
<Summary />
<TemplateCom /> <TemplateCom />
</demo-sort> </demo-sort>
</template> </template>
@ -51,6 +53,8 @@ import Size from './size.vue';
import TemplateCom from './template.vue'; import TemplateCom from './template.vue';
import Ellipsis from './ellipsis.vue'; import Ellipsis from './ellipsis.vue';
import Stripe from './stripe.vue'; import Stripe from './stripe.vue';
import MultipleSorter from './multiple-sorter.vue';
import Summary from './summary.vue';
import CN from '../index.zh-CN.md'; import CN from '../index.zh-CN.md';
import US from '../index.en-US.md'; import US from '../index.en-US.md';
@ -81,6 +85,8 @@ export default {
Size, Size,
TemplateCom, TemplateCom,
Stripe, Stripe,
MultipleSorter,
Summary,
}, },
}; };
</script> </script>

View File

@ -0,0 +1,99 @@
<docs>
---
order: 7
title:
en-US: Multiple sorter
zh-CN: 多列排序
---
## zh-CN
`column.sorter` 支持 `multiple` 字段以配置多列排序优先级通过 `sorter.compare` 配置排序逻辑你可以通过不设置该函数只启动多列排序的交互形式
## en-US
`column.sorter` support `multiple` to config the priority of sort columns. Though `sorter.compare` to customize compare function. You can also leave it empty to use the interactive only.
</docs>
<template>
<a-table :columns="columns" :data-source="data" @change="onChange" />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Chinese Score',
dataIndex: 'chinese',
sorter: {
compare: (a, b) => a.chinese - b.chinese,
multiple: 3,
},
},
{
title: 'Math Score',
dataIndex: 'math',
sorter: {
compare: (a, b) => a.math - b.math,
multiple: 2,
},
},
{
title: 'English Score',
dataIndex: 'english',
sorter: {
compare: (a, b) => a.english - b.english,
multiple: 1,
},
},
];
const data = [
{
key: '1',
name: 'John Brown',
chinese: 98,
math: 60,
english: 70,
},
{
key: '2',
name: 'Jim Green',
chinese: 98,
math: 66,
english: 89,
},
{
key: '3',
name: 'Joe Black',
chinese: 98,
math: 90,
english: 70,
},
{
key: '4',
name: 'Jim Red',
chinese: 88,
math: 99,
english: 89,
},
];
export default defineComponent({
setup() {
return {
data,
columns,
onChange: (pagination, filters, sorter, extra) => {
console.log('params', pagination, filters, sorter, extra);
},
};
},
});
</script>

View File

@ -37,9 +37,8 @@ To perform operations and clear selections after selecting some rows, use `rowSe
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, reactive, toRefs } from 'vue'; import { computed, defineComponent, reactive, toRefs } from 'vue';
import { ColumnProps } from 'ant-design-vue/es/table/interface';
type Key = ColumnProps['key']; type Key = string | number;
interface DataType { interface DataType {
key: Key; key: Key;

View File

@ -19,12 +19,10 @@ Use `rowSelection.selections` custom selections, default no select dropdown, sho
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, ref, unref } from 'vue'; import { defineComponent, computed, ref, unref } from 'vue';
import { ColumnProps } from 'ant-design-vue/es/table/interface'; import { Table } from 'ant-design-vue';
type Key = ColumnProps['key'];
interface DataType { interface DataType {
key: Key; key: string | number;
name: string; name: string;
age: number; age: number;
address: string; address: string;
@ -57,9 +55,9 @@ for (let i = 0; i < 46; i++) {
export default defineComponent({ export default defineComponent({
setup() { setup() {
const selectedRowKeys = ref<Key[]>([]); // Check here to configure the default column const selectedRowKeys = ref<DataType['key'][]>([]); // Check here to configure the default column
const onSelectChange = (changableRowKeys: Key[]) => { const onSelectChange = (changableRowKeys: string[]) => {
console.log('selectedRowKeys changed: ', changableRowKeys); console.log('selectedRowKeys changed: ', changableRowKeys);
selectedRowKeys.value = changableRowKeys; selectedRowKeys.value = changableRowKeys;
}; };
@ -70,19 +68,15 @@ export default defineComponent({
onChange: onSelectChange, onChange: onSelectChange,
hideDefaultSelections: true, hideDefaultSelections: true,
selections: [ selections: [
{ Table.SELECTION_ALL,
key: 'all-data', Table.SELECTION_INVERT,
text: 'Select All Data', Table.SELECTION_NONE,
onSelect: () => {
selectedRowKeys.value = [...Array(46).keys()]; // 0...45
},
},
{ {
key: 'odd', key: 'odd',
text: 'Select Odd Row', text: 'Select Odd Row',
onSelect: (changableRowKeys: Key[]) => { onSelect: changableRowKeys => {
let newSelectedRowKeys = []; let newSelectedRowKeys = [];
newSelectedRowKeys = changableRowKeys.filter((key, index) => { newSelectedRowKeys = changableRowKeys.filter((_key, index) => {
if (index % 2 !== 0) { if (index % 2 !== 0) {
return false; return false;
} }
@ -94,9 +88,9 @@ export default defineComponent({
{ {
key: 'even', key: 'even',
text: 'Select Even Row', text: 'Select Even Row',
onSelect: (changableRowKeys: Key[]) => { onSelect: changableRowKeys => {
let newSelectedRowKeys = []; let newSelectedRowKeys = [];
newSelectedRowKeys = changableRowKeys.filter((key, index) => { newSelectedRowKeys = changableRowKeys.filter((_key, index) => {
if (index % 2 !== 0) { if (index % 2 !== 0) {
return true; return true;
} }

View File

@ -7,40 +7,40 @@ title:
--- ---
## zh-CN ## zh-CN
第一列是联动的选择框 第一列是联动的选择框
默认点击 checkbox 触发选择行为需要 `点击行` 触发可参考例子https://codesandbox.io/s/row-selection-on-click-tr58v 默认点击 checkbox 触发选择行为需要 `点击行` 触发可参考例子https://codesandbox.io/s/row-selection-on-click-tr58v
## en-US ## en-US
Rows can be selectable by making first column as a selectable column. Rows can be selectable by making first column as a selectable column.
selection happens when clicking checkbox defaultly. You can see https://codesandbox.io/s/row-selection-on-click-tr58v if you need row-click selection behavior. selection happens when clicking checkbox defaultly. You can see https://codesandbox.io/s/row-selection-on-click-tr58v if you need row-click selection behavior.
</docs> </docs>
<template> <template>
<a-table :row-selection="rowSelection" :columns="columns" :data-source="data"> <a-table :row-selection="rowSelection" :columns="columns" :data-source="data">
<template #name="{ text }"> <template #bodyCell="{ column, text }">
<a>{{ text }}</a> <template v-if="column.dataIndex === 'name'">
<a>{{ text }}</a>
</template>
<template v-else>{{ text }}</template>
</template> </template>
</a-table> </a-table>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { ColumnProps } from 'ant-design-vue/es/table/interface'; import type { TableProps, TableColumnType } from 'ant-design-vue';
type Key = ColumnProps['key'];
interface DataType { interface DataType {
key: Key; key: string;
name: string; name: string;
age: number; age: number;
address: string; address: string;
} }
const columns = [ const columns: TableColumnType<DataType>[] = [
{ {
title: 'Name', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
slots: { customRender: 'name' },
}, },
{ {
title: 'Age', title: 'Age',
@ -80,8 +80,8 @@ const data: DataType[] = [
export default defineComponent({ export default defineComponent({
setup() { setup() {
const rowSelection = { const rowSelection: TableProps['rowSelection'] = {
onChange: (selectedRowKeys: Key[], selectedRows: DataType[]) => { onChange: (selectedRowKeys: string[], selectedRows: DataType[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
}, },
getCheckboxProps: (record: DataType) => ({ getCheckboxProps: (record: DataType) => ({

View File

@ -0,0 +1,158 @@
<docs>
---
order: 29
title:
en-US: Summary
zh-CN: 总结栏
---
## zh-CN
通过 `summary` 设置总结栏使用 `a-table-summary-cell` 同步 Column 的固定状态你可以通过配置 `a-table-summary` `fixed` 属性使其固定
## en-US
Set summary content by `summary` prop. Sync column fixed status with `a-table-summary-cell`. You can fixed it by set `a-table-summary` `fixed` prop.
</docs>
<template>
<a-table :columns="columns" :data-source="data" :pagination="false" bordered>
<template #summary>
<a-table-summary-row>
<a-table-summary-cell>Total</a-table-summary-cell>
<a-table-summary-cell>
<a-typography-text type="danger">{{ totals.totalBorrow }}</a-typography-text>
</a-table-summary-cell>
<a-table-summary-cell>
<a-typography-text>{{ totals.totalRepayment }}</a-typography-text>
</a-table-summary-cell>
</a-table-summary-row>
<a-table-summary-row>
<a-table-summary-cell>Balance</a-table-summary-cell>
<a-table-summary-cell :col-span="2">
<a-typography-text type="danger">
{{ totals.totalBorrow - totals.totalRepayment }}
</a-typography-text>
</a-table-summary-cell>
</a-table-summary-row>
</template>
</a-table>
<a-table
:columns="fixedColumns"
:data-source="fixedData"
:pagination="false"
:scroll="{ x: 2000, y: 500 }"
bordered
>
<template #summary>
<a-table-summary fixed>
<a-table-summary-row>
<a-table-summary-cell :index="0">Summary</a-table-summary-cell>
<a-table-summary-cell :index="1">This is a summary content</a-table-summary-cell>
</a-table-summary-row>
</a-table-summary>
</template>
</a-table>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const columns = ref([
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Borrow',
dataIndex: 'borrow',
},
{
title: 'Repayment',
dataIndex: 'repayment',
},
]);
const data = ref([
{
key: '1',
name: 'John Brown',
borrow: 10,
repayment: 33,
},
{
key: '2',
name: 'Jim Green',
borrow: 100,
repayment: 0,
},
{
key: '3',
name: 'Joe Black',
borrow: 10,
repayment: 10,
},
{
key: '4',
name: 'Jim Red',
borrow: 75,
repayment: 45,
},
]);
const fixedColumns = ref([
{
title: 'Name',
dataIndex: 'name',
fixed: true,
width: 100,
},
{
title: 'Description',
dataIndex: 'description',
},
]);
const fixedData = ref<{ key: number; name: string; description: string }[]>([]);
for (let i = 0; i < 20; i += 1) {
fixedData.value.push({
key: i,
name: ['Light', 'Bamboo', 'Little'][i % 3],
description: 'Everything that has a beginning, has an end.',
});
}
const totals = computed(() => {
let totalBorrow = 0;
let totalRepayment = 0;
data.value.forEach(({ borrow, repayment }) => {
totalBorrow += borrow;
totalRepayment += repayment;
});
return { totalBorrow, totalRepayment };
});
return {
data,
columns,
totals,
fixedColumns,
fixedData,
};
},
});
</script>
<style>
#components-table-demo-summary tfoot th,
#components-table-demo-summary tfoot td {
background: #fafafa;
}
[data-theme='dark'] #components-table-demo-summary tfoot th,
[data-theme='dark'] #components-table-demo-summary tfoot td {
background: #1d1d1d;
}
</style>

View File

@ -1,27 +1,67 @@
import Table, { tableProps } from './Table'; import Table, { tableProps } from './Table';
import type Column from './Column'; import Column from './Column';
import type ColumnGroup from './ColumnGroup'; import ColumnGroup from './ColumnGroup';
import type { TableProps, TablePaginationConfig } from './Table'; import type { TableProps, TablePaginationConfig } from './Table';
import type { App } from 'vue'; import { App, defineComponent } from 'vue';
import { Summary, SummaryCell, SummaryRow } from '../vc-table';
import { SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE } from './hooks/useSelection';
export type { ColumnProps } from './Column'; export type { ColumnProps } from './Column';
export type { ColumnsType, ColumnType, ColumnGroupType } from './interface'; export type { ColumnsType, ColumnType, ColumnGroupType } from './interface';
export type { TableProps, TablePaginationConfig }; export type { TableProps, TablePaginationConfig };
const TableSummaryRow = defineComponent({ ...SummaryRow, name: 'ATableSummaryRow' });
const TableSummaryCell = defineComponent({ ...SummaryCell, name: 'ATableSummaryCell' });
const TempSummary = defineComponent({
...Summary,
name: 'ATableSummary',
});
const TableSummary = TempSummary as typeof TempSummary & {
Cell: typeof TableSummaryCell;
Row: typeof TableSummaryRow;
};
TableSummary.Cell = TableSummaryCell;
TableSummary.Row = TableSummaryRow;
const T = Table as typeof Table &
Plugin & {
Column: typeof Column;
ColumnGroup: typeof ColumnGroup;
Summary: typeof TableSummary;
SELECTION_ALL: typeof SELECTION_ALL;
SELECTION_INVERT: typeof SELECTION_INVERT;
SELECTION_NONE: typeof SELECTION_NONE;
};
T.SELECTION_ALL = SELECTION_ALL;
T.SELECTION_INVERT = SELECTION_INVERT;
T.SELECTION_NONE = SELECTION_NONE;
T.Column = Column;
T.ColumnGroup = ColumnGroup;
T.Summary = TableSummary;
/* istanbul ignore next */ /* istanbul ignore next */
Table.install = function (app: App) { T.install = function (app: App) {
app.component(Table.name, Table); app.component(TableSummary.name, TableSummary);
app.component(Table.Column.name, Table.Column); app.component(TableSummaryCell.name, TableSummaryCell);
app.component(Table.ColumnGroup.name, Table.ColumnGroup); app.component(TableSummaryRow.name, TableSummaryRow);
app.component(T.name, T);
app.component(T.Column.name, Column);
app.component(T.ColumnGroup.name, ColumnGroup);
return app; return app;
}; };
export const TableColumn = Table.Column; export {
export const TableColumnGroup = Table.ColumnGroup; tableProps,
export { tableProps }; TableSummary,
TableSummaryRow,
TableSummaryCell,
Column as TableColumn,
ColumnGroup as TableColumnGroup,
};
export default Table as typeof Table & export default T;
Plugin & {
readonly Column: typeof Column;
readonly ColumnGroup: typeof ColumnGroup;
};

View File

@ -109,7 +109,7 @@ export default defineComponent<CellProps>({
align, align,
rowType, rowType,
isSticky, isSticky,
column, column = {},
cellType, cellType,
} = props; } = props;
const cellPrefixCls = `${prefixCls}-cell`; const cellPrefixCls = `${prefixCls}-cell`;

View File

@ -20,7 +20,7 @@ export default defineComponent<SummaryCellProps>({
const tableContext = useInjectTable(); const tableContext = useInjectTable();
const summaryContext = useInjectSummary(); const summaryContext = useInjectSummary();
return () => { return () => {
const { index, colSpan, rowSpan, align } = props; const { index, colSpan = 1, rowSpan, align } = props;
const { prefixCls, direction } = tableContext; const { prefixCls, direction } = tableContext;
const { scrollColumnIndex, stickyOffsets, flattenColumns } = summaryContext; const { scrollColumnIndex, stickyOffsets, flattenColumns } = summaryContext;
const lastIndex = index + colSpan - 1; const lastIndex = index + colSpan - 1;
@ -33,7 +33,6 @@ export default defineComponent<SummaryCellProps>({
stickyOffsets, stickyOffsets,
direction, direction,
); );
return ( return (
<Cell <Cell
class={attrs.class as string} class={attrs.class as string}
@ -44,7 +43,7 @@ export default defineComponent<SummaryCellProps>({
dataIndex={null} dataIndex={null}
align={align} align={align}
customRender={() => ({ customRender={() => ({
children: slots.defalut?.(), children: slots.default?.(),
props: { props: {
colSpan: mergedColSpan, colSpan: mergedColSpan,
rowSpan, rowSpan,

View File

@ -1,7 +1,5 @@
import { computed, defineComponent, onBeforeUnmount, watchEffect } from 'vue'; import { computed, defineComponent, onBeforeUnmount, watchEffect } from 'vue';
import { useInjectTable } from '../context/TableContext'; import { useInjectTable } from '../context/TableContext';
import Cell from './Cell';
import Row from './Row';
export interface SummaryProps { export interface SummaryProps {
fixed?: boolean | 'top' | 'bottom'; fixed?: boolean | 'top' | 'bottom';
@ -11,8 +9,6 @@ let indexGuid = 0;
const Summary = defineComponent<SummaryProps>({ const Summary = defineComponent<SummaryProps>({
props: ['fixed'] as any, props: ['fixed'] as any,
name: 'Summary', name: 'Summary',
Row,
Cell,
setup(props, { slots }) { setup(props, { slots }) {
const tableContext = useInjectTable(); const tableContext = useInjectTable();
const uniKey = `table-summary-uni-key-${++indexGuid}`; const uniKey = `table-summary-uni-key-${++indexGuid}`;

View File

@ -1,4 +1,6 @@
import Summary from './Summary'; import Summary from './Summary';
import SummaryRow from './Row';
import SummaryCell from './Cell';
import type { DefaultRecordType, StickyOffsets } from '../interface'; import type { DefaultRecordType, StickyOffsets } from '../interface';
import { computed, defineComponent, reactive, toRef } from 'vue'; import { computed, defineComponent, reactive, toRef } from 'vue';
import type { FlattenColumns } from '../context/SummaryContext'; import type { FlattenColumns } from '../context/SummaryContext';
@ -34,4 +36,5 @@ export default defineComponent({
}, },
}); });
export { SummaryRow, SummaryCell };
export const FooterComponents = Summary; export const FooterComponents = Summary;

View File

@ -332,7 +332,7 @@ export default defineComponent<TableProps>({
const summaryFixedInfos = reactive<Record<string, boolean | string>>({}); const summaryFixedInfos = reactive<Record<string, boolean | string>>({});
const fixFooter = computed(() => { const fixFooter = computed(() => {
const info = Object.values(summaryFixedInfos)[0]; const info = Object.values(summaryFixedInfos)[0];
return fixHeader.value || (stickyState.value.isSticky && info); return (fixHeader.value || stickyState.value.isSticky) && info;
}); });
const summaryCollect = (uniKey: string, fixed: boolean | string) => { const summaryCollect = (uniKey: string, fixed: boolean | string) => {
@ -568,7 +568,7 @@ export default defineComponent<TableProps>({
stickyState.value; stickyState.value;
const TableComponent = getComponent(['table'], 'table'); const TableComponent = getComponent(['table'], 'table');
const summaryNode = slots.summary?.(); const summaryNode = slots.summary?.({ pageData: mergedData.value });
let groupTableNode; let groupTableNode;
@ -703,7 +703,7 @@ export default defineComponent<TableProps>({
{bodyContent} {bodyContent}
{/* Summary Table */} {/* Summary Table */}
{fixFooter.value !== 'top' && ( {fixFooter.value && fixFooter.value !== 'top' && (
<FixedHolder <FixedHolder
{...fixedHolderProps} {...fixedHolderProps}
stickyBottomOffset={offsetSummary} stickyBottomOffset={offsetSummary}

View File

@ -1,10 +1,10 @@
// base rc-table@7.17.2 // base rc-table@7.17.2
import Table from './Table'; import Table from './Table';
import { FooterComponents as Summary } from './Footer'; import { FooterComponents as Summary, SummaryCell, SummaryRow } from './Footer';
import Column from './sugar/Column'; import Column from './sugar/Column';
import ColumnGroup from './sugar/ColumnGroup'; import ColumnGroup from './sugar/ColumnGroup';
import { INTERNAL_COL_DEFINE } from './utils/legacyUtil'; import { INTERNAL_COL_DEFINE } from './utils/legacyUtil';
export { Summary, Column, ColumnGroup, INTERNAL_COL_DEFINE }; export { Summary, Column, ColumnGroup, SummaryCell, SummaryRow, INTERNAL_COL_DEFINE };
export default Table; export default Table;

View File

@ -1,5 +1,5 @@
// debugger tsx // debugger tsx
import Demo from '../../components/table/demo/template.vue'; import Demo from '../../components/table/demo/summary.vue';
export default { export default {
render() { render() {