refactor: table

pull/6285/head
tangjinzhou 2023-02-16 19:23:44 +08:00
parent 8472c25633
commit a6a270b44a
16 changed files with 175 additions and 96 deletions

View File

@ -0,0 +1,21 @@
type RecordType = Record<string, any>;
function extendsObject<T extends RecordType>(...list: T[]) {
const result: RecordType = { ...list[0] };
for (let i = 1; i < list.length; i++) {
const obj = list[i];
if (obj) {
Object.keys(obj).forEach(key => {
const val = obj[key];
if (val !== undefined) {
result[key] = val;
}
});
}
}
return result;
}
export default extendsObject;

View File

@ -32,6 +32,7 @@ function renderExpandIcon(locale: TableLocale) {
[`${iconPrefix}-collapsed`]: expandable && !expanded,
})}
aria-label={expanded ? locale.collapse : locale.expand}
aria-expanded={expanded}
/>
);
};

View File

@ -383,9 +383,19 @@ const InteralTable = defineComponent<
const [transformBasicColumns] = useColumns(toRef(props, 'contextSlots'));
const columnTitleProps = computed(() => ({
...sorterTitleProps.value,
}));
const columnTitleProps = computed(() => {
const mergedFilters: Record<string, FilterValue> = {};
const filtersValue = filters.value;
Object.keys(filtersValue).forEach(filterKey => {
if (filtersValue[filterKey] !== null) {
mergedFilters[filterKey] = filtersValue[filterKey]!;
}
});
return {
...sorterTitleProps.value,
filters: mergedFilters,
};
});
const [transformTitleColumns] = useTitleColumns(columnTitleProps);
// ========================== Pagination ==========================
@ -413,7 +423,7 @@ const InteralTable = defineComponent<
changeEventInfo.pagination =
props.pagination === false
? {}
: getPaginationParam(props.pagination, mergedPagination.value);
: getPaginationParam(mergedPagination.value, props.pagination);
changeEventInfo.resetPagination = resetPagination;
});
@ -556,8 +566,8 @@ const InteralTable = defineComponent<
const defaultPosition = direction.value === 'rtl' ? 'left' : 'right';
const { position } = mergedPagination.value;
if (position !== null && Array.isArray(position)) {
const topPos = position.find(p => p.indexOf('top') !== -1);
const bottomPos = position.find(p => p.indexOf('bottom') !== -1);
const topPos = position.find(p => p.includes('top'));
const bottomPos = position.find(p => p.includes('bottom'));
const isDisable = position.every(p => `${p}` === 'none');
if (!topPos && !bottomPos && !isDisable) {
bottomPaginationNode = renderPagination(defaultPosition);

View File

@ -140,14 +140,14 @@ describe('Table.filter', () => {
});
});
// TODO
xit('can be controlled by filterDropdownVisible', done => {
xit('can be controlled by filterDropdownOpen', done => {
const wrapper = mount(
Table,
getTableOptions({
columns: [
{
...column,
filterDropdownVisible: true,
filterDropdownOpen: true,
},
],
}),
@ -160,7 +160,7 @@ describe('Table.filter', () => {
columns: [
{
...column,
filterDropdownVisible: false,
filterDropdownOpen: false,
},
],
});
@ -179,7 +179,7 @@ describe('Table.filter', () => {
columns: [
{
...column,
onFilterDropdownVisibleChange: handleChange,
onFilterDropdownOpenChange: handleChange,
},
],
}),

View File

@ -525,7 +525,7 @@ describe('Table.rowSelection', () => {
value: 'Lucy',
},
],
filterDropdownVisible: true,
filterDropdownOpen: true,
onFilter: (value, record) => record.name.indexOf(value) === 0,
},
];

View File

@ -123,7 +123,7 @@ export default defineComponent({
customFilterDropdown: true,
onFilter: (value, record) =>
record.name.toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
onFilterDropdownOpenChange: visible => {
if (visible) {
setTimeout(() => {
searchInput.value.focus();
@ -143,7 +143,7 @@ export default defineComponent({
customFilterDropdown: true,
onFilter: (value, record) =>
record.address.toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
onFilterDropdownOpenChange: visible => {
if (visible) {
setTimeout(() => {
searchInput.value.focus();

View File

@ -1,4 +1,3 @@
import isEqual from 'lodash-es/isEqual';
import FilterFilled from '@ant-design/icons-vue/FilterFilled';
import Button from '../../../button';
import Menu from '../../../menu';
@ -26,6 +25,8 @@ import type { EventHandler } from '../../../_util/EventInterface';
import FilterSearch from './FilterSearch';
import Tree from '../../../tree';
import type { CheckboxChangeEvent } from '../../../checkbox/interface';
import devWarning from '../../../vc-util/devWarning';
import isEqual from '../../../vc-util/isEqual';
interface FilterRestProps {
confirm?: Boolean;
@ -99,7 +100,7 @@ function renderFilterItems({
return item;
});
}
export type TreeColumnFilterItem = ColumnFilterItem;
export interface FilterDropdownProps<RecordType> {
tablePrefixCls: string;
prefixCls: string;
@ -108,7 +109,7 @@ export interface FilterDropdownProps<RecordType> {
filterState?: FilterState<RecordType>;
filterMultiple: boolean;
filterMode?: 'menu' | 'tree';
filterSearch?: FilterSearchType;
filterSearch?: FilterSearchType<ColumnFilterItem | TreeColumnFilterItem>;
columnKey: Key;
triggerFilter: (filterState: FilterState<RecordType>) => void;
locale: TableLocale;
@ -136,7 +137,29 @@ export default defineComponent<FilterDropdownProps<any>>({
const contextSlots = useInjectSlots();
const filterMode = computed(() => props.filterMode ?? 'menu');
const filterSearch = computed(() => props.filterSearch ?? false);
const filterDropdownVisible = computed(() => props.column.filterDropdownVisible);
const filterDropdownOpen = computed(
() => props.column.filterDropdownOpen || props.column.filterDropdownVisible,
);
const onFilterDropdownOpenChange = computed(
() => props.column.onFilterDropdownOpenChange || props.column.onFilterDropdownVisibleChange,
);
if (process.env.NODE_ENV !== 'production') {
[
['filterDropdownVisible', 'filterDropdownOpen', props.column.filterDropdownVisible],
[
'onFilterDropdownVisibleChange',
'onFilterDropdownOpenChange',
props.column.onFilterDropdownVisibleChange,
],
].forEach(([deprecatedName, newName, prop]) => {
devWarning(
prop === undefined || prop === null,
'Table',
`\`${deprecatedName}\` is deprecated. Please use \`${newName}\` instead.`,
);
});
}
const visible = ref(false);
const filtered = computed(
() =>
@ -166,13 +189,11 @@ export default defineComponent<FilterDropdownProps<any>>({
const triggerVisible = (newVisible: boolean) => {
visible.value = newVisible;
props.column.onFilterDropdownVisibleChange?.(newVisible);
onFilterDropdownOpenChange.value?.(newVisible);
};
const mergedVisible = computed(() =>
typeof filterDropdownVisible.value === 'boolean'
? filterDropdownVisible.value
: visible.value,
typeof filterDropdownOpen.value === 'boolean' ? filterDropdownOpen.value : visible.value,
);
const propFilteredKeys = computed(() => props.filterState?.filteredKeys);
@ -234,14 +255,14 @@ export default defineComponent<FilterDropdownProps<any>>({
});
// ======================= Submit ========================
const internalTriggerFilter = (keys: Key[] | undefined | null) => {
const internalTriggerFilter = (keys?: Key[]) => {
const { column, columnKey, filterState } = props;
const mergedKeys = keys && keys.length ? keys : null;
if (mergedKeys === null && (!filterState || !filterState.filteredKeys)) {
return null;
}
if (isEqual(mergedKeys, filterState?.filteredKeys)) {
if (isEqual(mergedKeys, filterState?.filteredKeys, true)) {
return null;
}
@ -318,6 +339,13 @@ export default defineComponent<FilterDropdownProps<any>>({
return item;
});
const getFilterData = (node: any): TreeColumnFilterItem => ({
...node,
text: node.title,
value: node.key,
children: node.children?.map(item => getFilterData(item)) || [],
});
const treeData = computed(() => getTreeData({ filters: props.column.filters }));
// ======================== Style ========================
const dropdownMenuClass = computed(() =>
@ -394,7 +422,12 @@ export default defineComponent<FilterDropdownProps<any>>({
// onExpand={onExpandChange}
filterTreeNode={
searchValue.value.trim()
? node => searchValueMatched(searchValue.value, node.title)
? node => {
if (typeof filterSearch.value === 'function') {
return filterSearch.value(searchValue.value, getFilterData(node));
}
return searchValueMatched(searchValue.value, node.title);
}
: undefined
}
/>
@ -443,6 +476,7 @@ export default defineComponent<FilterDropdownProps<any>>({
return isEqual(
(props.column.defaultFilteredValue || []).map(key => String(key)),
selectedKeys,
true,
);
}
@ -464,6 +498,9 @@ export default defineComponent<FilterDropdownProps<any>>({
filters: column.filters,
visible: mergedVisible.value,
column: column.__originColumn__,
close: () => {
triggerVisible(false);
},
});
} else if (filterDropdownRef.value) {
dropdownContent = filterDropdownRef.value;
@ -512,8 +549,8 @@ export default defineComponent<FilterDropdownProps<any>>({
<Dropdown
overlay={menu}
trigger={['click']}
visible={mergedVisible.value}
onVisibleChange={onVisibleChange}
open={mergedVisible.value}
onOpenChange={onVisibleChange}
getPopupContainer={getPopupContainer}
placement={direction.value === 'rtl' ? 'bottomLeft' : 'bottomRight'}
>

View File

@ -1,5 +1,16 @@
import KeyCode from '../../../_util/KeyCode';
import type { KeyboardEventHandler } from '../../../_util/EventInterface';
const onKeyDown: KeyboardEventHandler = event => {
const { keyCode } = event;
if (keyCode === KeyCode.ENTER) {
event.stopPropagation();
}
};
const FilterDropdownMenuWrapper = (_props, { slots }) => (
<div onClick={e => e.stopPropagation()}>{slots.default?.()}</div>
<div onClick={e => e.stopPropagation()} onKeydown={onKeyDown}>
{slots.default?.()}
</div>
);
export default FilterDropdownMenuWrapper;

View File

@ -74,9 +74,9 @@ function injectFilter<RecordType>(
dropdownPrefixCls: string,
columns: ColumnsType<RecordType>,
filterStates: FilterState<RecordType>[],
triggerFilter: (filterState: FilterState<RecordType>) => void,
getPopupContainer: GetPopupContainer | undefined,
locale: TableLocale,
triggerFilter: (filterState: FilterState<RecordType>) => void,
getPopupContainer?: GetPopupContainer | undefined,
pos?: string,
): ColumnsType<RecordType> {
return columns.map((column, index) => {
@ -121,9 +121,9 @@ function injectFilter<RecordType>(
dropdownPrefixCls,
newColumn.children,
filterStates,
locale,
triggerFilter,
getPopupContainer,
locale,
columnPos,
),
};
@ -217,7 +217,9 @@ function useFilter<RecordType>({
const mergedFilterStates = computed(() => {
const collectedStates = collectFilterStates(mergedColumns.value, false);
if (collectedStates.length === 0) {
return collectedStates;
}
let filteredKeysIsAllNotControlled = true;
let filteredKeysIsAllControlled = true;
collectedStates.forEach(({ filteredKeys }) => {
@ -230,7 +232,23 @@ function useFilter<RecordType>({
// Return if not controlled
if (filteredKeysIsAllNotControlled) {
return filterStates.value;
// Filter column may have been removed
const keyList = (mergedColumns.value || []).map((column, index) =>
getColumnKey(column, getColumnPos(index)),
);
return filterStates.value
.filter(({ key }) => keyList.includes(key))
.map(item => {
const col = mergedColumns.value[keyList.findIndex(key => key === item.key)];
return {
...item,
column: {
...item.column,
...col,
},
forceFiltered: col.filtered,
};
});
}
devWarning(
@ -257,9 +275,9 @@ function useFilter<RecordType>({
dropdownPrefixCls.value,
innerColumns,
mergedFilterStates.value,
locale.value,
triggerFilter,
getPopupContainer.value,
locale.value,
);
};
return [transformColumns, mergedFilterStates, filters];

View File

@ -3,12 +3,13 @@ import type { Ref } from 'vue';
import { computed } from 'vue';
import type { PaginationProps } from '../../pagination';
import type { TablePaginationConfig } from '../interface';
import extendsObject from '../../_util/extendsObject';
export const DEFAULT_PAGE_SIZE = 10;
export function getPaginationParam(
pagination: TablePaginationConfig | boolean | undefined,
mergedPagination: TablePaginationConfig,
pagination: TablePaginationConfig | boolean | undefined,
) {
const param: any = {
current: mergedPagination.current,
@ -27,23 +28,6 @@ export function getPaginationParam(
return param;
}
function extendsObject<T extends Object>(...list: T[]) {
const result: T = {} as T;
list.forEach(obj => {
if (obj) {
Object.keys(obj).forEach(key => {
const val = (obj as any)[key];
if (val !== undefined) {
(result as any)[key] = val;
}
});
}
});
return result;
}
export default function usePagination(
totalRef: Ref<number>,
paginationRef: Ref<TablePaginationConfig | false | undefined>,
@ -81,7 +65,7 @@ export default function usePagination(
});
const refreshPagination = (current?: number, pageSize?: number) => {
if (pagination.value === false) return;
if (paginationRef.value === false) return;
setInnerPagination({
current: current ?? 1,
pageSize: pageSize || mergedPagination.value.pageSize,
@ -89,7 +73,7 @@ export default function usePagination(
};
const onInternalChange: PaginationProps['onChange'] = (current, pageSize) => {
if (pagination.value) {
if (paginationRef.value) {
pagination.value.onChange?.(current, pageSize);
}
refreshPagination(current, pageSize);
@ -98,7 +82,7 @@ export default function usePagination(
return [
computed(() => {
return pagination.value === false
return paginationRef.value === false
? {}
: { ...mergedPagination.value, onChange: onInternalChange };
}),

View File

@ -55,10 +55,7 @@ export type INTERNAL_SELECTION_ITEM =
| typeof SELECTION_INVERT
| typeof SELECTION_NONE;
function flattenData<RecordType>(
data: RecordType[] | undefined,
childrenColumnName: string,
): RecordType[] {
function flattenData<RecordType>(childrenColumnName: string, data: RecordType[]): RecordType[] {
let list: RecordType[] = [];
(data || []).forEach(record => {
list.push(record);
@ -66,7 +63,7 @@ function flattenData<RecordType>(
if (record && typeof record === 'object' && childrenColumnName in record) {
list = [
...list,
...flattenData<RecordType>((record as any)[childrenColumnName], childrenColumnName),
...flattenData<RecordType>(childrenColumnName, (record as any)[childrenColumnName]),
];
}
});
@ -130,7 +127,7 @@ export default function useSelection<RecordType>(
// Get flatten data
const flattedData = computed(() =>
flattenData(configRef.pageData.value, configRef.childrenColumnName.value),
flattenData(configRef.childrenColumnName.value, configRef.pageData.value),
);
// Get all checkbox props
@ -448,6 +445,7 @@ export default function useSelection<RecordType>(
}
onChange={onSelectAllChange}
disabled={flattedDataLength.value === 0 || allDisabled}
aria-label={customizeSelections ? 'Custom selection' : 'Select all'}
skipGroup
/>
{customizeSelections}

View File

@ -135,10 +135,12 @@ function injectSorter<RecordType>(
class={classNames(`${prefixCls}-column-sorter-up`, {
active: sorterOrder === ASCEND,
})}
role="presentation"
/>
);
const downNode = sortDirections.includes(DESCEND) && (
<CaretDownOutlined
role="presentation"
class={classNames(`${prefixCls}-column-sorter-down`, {
active: sorterOrder === DESCEND,
})}
@ -210,11 +212,7 @@ function injectSorter<RecordType>(
// Inform the screen-reader so it can tell the visually impaired user which column is sorted
if (sorterOrder) {
if (sorterOrder === 'ascend') {
cell['aria-sort'] = 'ascending';
} else {
cell['aria-sort'] = 'descending';
}
cell['aria-sort'] = sorterOrder === 'ascend' ? 'ascending' : 'descending';
}
cell.class = classNames(cell.class, `${prefixCls}-column-has-sorters`);

View File

@ -85,6 +85,7 @@ Specify `dataSource` of Table as an array of data.
| expandedRowKeys(v-model) | Current expanded row keys | string\[] | - | |
| expandedRowRender | Expanded container render for each row | Function({record, index, indent, expanded}):VNode\|v-slot | - | |
| expandFixed | Set column to be fixed: `true`(same as left) `'left'` `'right'` | boolean \| string | false | 3.0 |
| expandColumnTitle | Set the title of the expand column | v-slot | - | 4.0.0 |
| expandIcon | Customize row expand Icon. | Function(props):VNode \| v-slot:expandIcon="props" | - | |
| expandRowByClick | Whether to expand row by clicking anywhere in the whole row | boolean | `false` | |
| footer | Table footer renderer | Function(currentPageData)\| v-slot:footer="currentPageData" | | |
@ -93,7 +94,7 @@ Specify `dataSource` of Table as an array of data.
| indentSize | Indent size in pixels of tree data | number | 15 | |
| loading | Loading status of table | boolean\|[object](/components/spin) | `false` | |
| locale | i18n text including filter, sort, empty text, etc | object | filterConfirm: 'Ok' <br /> filterReset: 'Reset' <br /> emptyText: 'No Data' | |
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object | | |
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object \| `false` | | |
| rowClassName | Row's className | Function(record, index):string | - | |
| rowExpandable | Enable row can be expandable | (record) => boolean | - | |
| rowKey | Row's unique key, could be a string or function that returns a string | string\|Function(record, index):string | `key` | |
@ -102,7 +103,7 @@ Specify `dataSource` of Table as an array of data.
| showExpandColumn | Show expand column | boolean | true | 3.0 |
| showHeader | Whether to show table header | boolean | `true` | |
| showSorterTooltip | The header show next sorter direction tooltip. It will be set as the property of Tooltip if its type is object | boolean \| [Tooltip props](/components/tooltip/#API) | true | 3.0 |
| size | Size of table | `default` \| `middle` \| `small` \| `large` | `default` | |
| size | Size of table | `middle` \| `small` \| `large` | `large` | |
| sortDirections | Supported sort way, could be `ascend`, `descend` | Array | \[`ascend`, `descend`] | 3.0 |
| sticky | Set sticky header and scroll bar | boolean \| `{offsetHeader?: number, offsetScroll?: number, getContainer?: () => HTMLElement}` | - | 3.0 |
| summary | Summary content | v-slot:summary | - | 3.0 |
@ -162,7 +163,7 @@ One of the Table `columns` prop for describing the table's columns, Column has t
| ellipsis | ellipsize cell content, not working with sorter and filters for now.<br />tableLayout would be `fixed` when `ellipsis` is true. | boolean | false | 1.5.0 |
| ellipsis | The ellipsis cell content, not working with sorter and filters for now.<br />tableLayout would be `fixed` when `ellipsis` is `true` or `{ showTitle?: boolean }` | boolean \| {showTitle?: boolean } | false | 3.0 |
| filterDropdown | Customized filter overlay | VNode \| (props: FilterDropdownProps) => VNode | - | |
| filterDropdownVisible | Whether `filterDropdown` is visible | boolean | - | |
| filterDropdownOpen | Whether `filterDropdown` is open | boolean | - | 4.0 |
| filtered | Whether the `dataSource` is filtered | boolean | `false` | |
| filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - | |
| filterIcon | Customized filter icon | ({filtered: boolean, column: Column}) | `false` | |
@ -176,13 +177,14 @@ One of the Table `columns` prop for describing the table's columns, Column has t
| minWidth | Drag the minimum width of the column, it will be affected by the automatic adjustment and distribution of the table width | number | 50 | 3.0 |
| resizable | Whether the width can be adjusted by dragging, at this time width must be number type | boolean | - | 3.0 |
| responsive | The list of breakpoints at which to display this column. Always visible if not set. | [Breakpoint](#Breakpoint)\[] | - | 3.0 |
| rowScope | Set scope attribute for all cells in this column | `row` \| `rowgroup` | - | 4.0 |
| sortDirections | supported sort way, could be `'ascend'`, `'descend'` | Array | `['ascend', 'descend']` | 1.5.0 |
| sorter | Sort function for local sort, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. If you need sort buttons only, set to `true` | Function\|boolean | - | |
| sortOrder | Order of sorted values: `'ascend'` `'descend'` `false` | boolean\|string | - | |
| sortOrder | Order of sorted values: `'ascend'` `'descend'` `null` | string | - | |
| title | Title of this column | string | - | |
| width | Width of this column | string\|number | - | |
| onFilter | Callback executed when the confirm filter button is clicked, Use as a `filter` event when using template or jsx | Function | - | |
| onFilterDropdownVisibleChange | Callback executed when `filterDropdownVisible` is changed, Use as a `filterDropdownVisible` event when using template or jsx | function(visible) {} | - | |
| onFilterDropdownOpenChange | Callback executed when `filterDropdownOpen` is changed, Use as a `filterDropdownOpen` event when using template or jsx | function(open) {} | - | 4.0 |
#### Breakpoint
@ -275,12 +277,6 @@ return <Table rowKey="uid" />;
return <Table rowKey={record => record.uid} />;
```
## Migrate to v3
Table deprecated `column.slots`, added `v-slot:bodyCell`, `v-slot:headerCell`, custom cells, and added `column.customFilterDropdown` `v-slot:customFilterDropdown`, custom filtering Menu, added `v-slot:customFilterIcon` custom filter button, but `column.slots` is still available, we will remove it in the next major version.
Besides, the breaking change is changing `dataIndex` from nest string path like `user.age` to string array path like `['user', 'age']`. This help to resolve developer should additional work on the field which contains `.`.
## FAQ
### How to hide pagination when single page or no data?

View File

@ -90,6 +90,7 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3yz3QqMlShYAAAAAAA
| expandedRowKeys(v-model) | 展开的行,控制属性 | string\[] | - | |
| expandedRowRender | 额外的展开行 | Function(record, index, indent, expanded):VNode \| v-slot:expandedRowRender="{record, index, indent, expanded}" | - | |
| expandFixed | 控制展开图标是否固定,可选 true `left` `right` | boolean \| string | false | 3.0 |
| expandColumnTitle | 自定义展开列表头 | v-slot | - | 4.0.0 |
| expandIcon | 自定义展开图标 | Function(props):VNode \| v-slot:expandIcon="props" | - | |
| expandRowByClick | 通过点击行来展开子行 | boolean | `false` | |
| footer | 表格尾部 | Function(currentPageData)\|v-slot:footer="currentPageData" | | |
@ -98,7 +99,7 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3yz3QqMlShYAAAAAAA
| indentSize | 展示树形数据时,每层缩进的宽度,以 px 为单位 | number | 15 | |
| loading | 页面是否加载中 | boolean\|[object](/components/spin-cn) | false | |
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: `确定` <br> filterReset: `重置` <br> emptyText: `暂无数据` | |
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination-cn/)文档,设为 false 时不展示和进行分页 | object | | |
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination-cn/)文档,设为 false 时不展示和进行分页 | object \| `false` | | |
| rowClassName | 表格行的类名 | Function(record, index):string | - | |
| rowExpandable | 设置是否允许行展开 | (record) => boolean | - | 3.0 |
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string\|Function(record):string | 'key' | |
@ -107,7 +108,7 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3yz3QqMlShYAAAAAAA
| showExpandColumn | 设置是否展示行展开列 | boolean | true | 3.0 |
| showHeader | 是否显示表头 | boolean | true | |
| showSorterTooltip | 表头是否显示下一次排序的 tooltip 提示。当参数类型为对象时,将被设置为 Tooltip 的属性 | boolean \| [Tooltip props](/components/tooltip/) | true | 3.0 |
| size | 表格大小 | default \| middle \| small | default | |
| size | 表格大小 | `large` \| `middle` \| `small` | `large` | |
| sortDirections | 支持的排序方式,取值为 `ascend` `descend` | Array | \[`ascend`, `descend`] | |
| sticky | 设置粘性头部和滚动条 | boolean \| `{offsetHeader?: number, offsetScroll?: number, getContainer?: () => HTMLElement}` | - | 3.0 |
| summary | 总结栏 | v-slot:summary | - | 3.0 |
@ -166,7 +167,7 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3yz3QqMlShYAAAAAAA
| defaultSortOrder | 默认排序顺序 | `ascend` \| `descend` | - | |
| ellipsis | 超过宽度将自动省略,暂不支持和排序筛选一起使用。<br />设置为 `true``{ showTitle?: boolean }` 时,表格布局将变成 `tableLayout="fixed"`。 | boolean \| { showTitle?: boolean } | false | 3.0 |
| filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | VNode \| (props: FilterDropdownProps) => VNode | - | |
| filterDropdownVisible | 用于控制自定义筛选菜单是否可见 | boolean | - | |
| filterDropdownOpen | 用于控制自定义筛选菜单是否可见 | boolean | - | |
| filtered | 标识数据是否经过过滤,筛选图标会高亮 | boolean | false | |
| filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - | |
| filterIcon | 自定义 filter 图标。 | VNode \| ({filtered: boolean, column: Column}) => vNode | false | |
@ -180,14 +181,15 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3yz3QqMlShYAAAAAAA
| minWidth | 拖动列最小宽度,会受到表格自动调整分配宽度影响 | number | 50 | 3.0 |
| resizable | 是否可拖动调整宽度,此时 width 必须是 number 类型 | boolean | - | 3.0 |
| responsive | 响应式 breakpoint 配置列表。未设置则始终可见。 | [Breakpoint](#Breakpoint)\[] | - | 3.0 |
| rowScope | 设置列范围 | `row` \| `rowgroup` | - | 4.0 |
| showSorterTooltip | 表头显示下一次排序的 tooltip 提示, 覆盖 table 中 `showSorterTooltip` | boolean \| [Tooltip props](/components/tooltip/#API) | true | |
| sortDirections | 支持的排序方式,取值为 `'ascend'` `'descend'` | Array | `['ascend', 'descend']` | 1.5.0 |
| sorter | 排序函数,本地排序使用一个函数(参考 [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的 compareFunction),需要服务端排序可设为 true | Function\|boolean | - | |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | boolean\|string | - | |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `null` | string | - | |
| title | 列头显示文字 | string | - | |
| width | 列宽度 | string\|number | - | |
| onFilter | 本地模式下,确定筛选的运行函数, 使用 template 或 jsx 时作为`filter`事件使用 | Function | - | |
| onFilterDropdownVisibleChange | 自定义筛选菜单可见变化时调用,使用 template 或 jsx 时作为`filterDropdownVisibleChange`事件使用 | function(visible) {} | - | |
| onFilterDropdownOpenChange | 自定义筛选菜单可见变化时调用,使用 template 或 jsx 时作为`filterDropdownOpenChange`事件使用 | function(open) {} | - | 4.0 |
#### Breakpoint
@ -280,12 +282,6 @@ return <Table rowKey="uid" />;
return <Table rowKey={record => record.uid} />;
```
## 从 v1 / v2 升级到 v3
Table 废弃了 `column.slots`, 新增 `v-slot:bodyCell`、`v-slot:headerCell`,自定义单元格,新增 `column.customFilterDropdown` `v-slot:customFilterDropdown`,自定义筛选菜单,新增了 `v-slot:customFilterIcon` 自定义筛选按钮,但 `column.slots` 还可用,我们会在下一个大版本时移除。
此外,比较重大的改动为 `dataIndex` 从支持路径嵌套如 `user.age` 改成了数组路径如 `['user', 'age']`。以解决过去属性名带 `.` 需要额外的数据转化问题。
## FAQ
### 如何在没有数据或只有一页数据时隐藏分页栏

View File

@ -49,7 +49,7 @@ export interface TableLocale {
export type SortOrder = 'descend' | 'ascend' | null;
const TableActions = tuple('paginate', 'sort', 'filter');
export type TableAction = typeof TableActions[number];
export type TableAction = (typeof TableActions)[number];
export type CompareFn<T> = (a: T, b: T, sortOrder?: SortOrder) => number;
@ -58,7 +58,6 @@ export interface ColumnFilterItem {
value: string | number | boolean;
children?: ColumnFilterItem[];
}
export interface ColumnTitleProps<RecordType> {
/** @deprecated Please use `sorterColumns` instead. */
sortOrder?: SortOrder;
@ -66,14 +65,16 @@ export interface ColumnTitleProps<RecordType> {
sortColumn?: ColumnType<RecordType>;
sortColumns?: { column: ColumnType<RecordType>; order: SortOrder }[];
filters?: Record<string, string[]>;
filters?: Record<string, FilterValue>;
}
export type ColumnTitle<RecordType> = VueNode | ((props: ColumnTitleProps<RecordType>) => VueNode);
export type FilterValue = (Key | boolean)[];
export type FilterKey = Key[] | null;
export type FilterSearchType = boolean | ((input: string, record: ColumnFilterItem) => boolean);
export type FilterSearchType<RecordType = Record<string, any>> =
| boolean
| ((input: string, record: RecordType) => boolean);
export interface FilterConfirmProps {
closeDropdown: boolean;
}
@ -85,6 +86,8 @@ export interface FilterDropdownProps<RecordType> {
confirm: (param?: FilterConfirmProps) => void;
clearFilters?: () => void;
filters?: ColumnFilterItem[];
/** Only close filterDropdown */
close: () => void;
visible: boolean;
column: ColumnType<RecordType>;
}
@ -115,13 +118,19 @@ export interface ColumnType<RecordType = DefaultRecordType>
defaultFilteredValue?: FilterValue | null;
filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode);
filterMode?: 'menu' | 'tree';
filterSearch?: FilterSearchType;
filterSearch?: FilterSearchType<ColumnFilterItem>;
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
filterDropdownVisible?: boolean;
onFilterDropdownVisibleChange?: (visible: boolean) => void;
filterDropdownOpen?: boolean;
onFilterDropdownOpenChange?: (visible: boolean) => void;
filterResetToDefaultFilteredValue?: boolean;
// Responsive
responsive?: Breakpoint[];
// Deprecated
/** @deprecated Please use `filterDropdownOpen` instead */
filterDropdownVisible?: boolean;
/** @deprecated Please use `onFilterDropdownOpenChange` instead */
onFilterDropdownVisibleChange?: (visible: boolean) => void;
}
export interface ColumnGroupType<RecordType> extends Omit<ColumnType<RecordType>, 'dataIndex'> {

View File

@ -2,7 +2,7 @@
* ColumnType which applied in antd: https://ant.design/components/table-cn/#Column
* - defaultSortOrder
* - filterDropdown
* - filterDropdownVisible
* - filterDropdownOpen
* - filtered
* - filteredValue
* - filterIcon
@ -12,7 +12,7 @@
* - sortOrder
* - sortDirections
* - onFilter
* - onFilterDropdownVisibleChange
* - onFilterDropdownOpenChange
*/
import type { CSSProperties, Ref, TdHTMLAttributes } from 'vue';