From a6a270b44aa4030aa2197e91abd400e542f208ae Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Thu, 16 Feb 2023 19:23:44 +0800 Subject: [PATCH] refactor: table --- components/_util/extendsObject.ts | 21 +++++++ components/table/ExpandIcon.tsx | 1 + components/table/Table.tsx | 22 +++++-- .../table/__tests__/Table.filter.test.js | 8 +-- .../__tests__/Table.rowSelection.test.js | 2 +- components/table/demo/custom-filter-panel.vue | 4 +- .../table/hooks/useFilter/FilterDropdown.tsx | 63 +++++++++++++++---- .../table/hooks/useFilter/FilterWrapper.tsx | 13 +++- components/table/hooks/useFilter/index.tsx | 30 +++++++-- components/table/hooks/usePagination.ts | 26 ++------ components/table/hooks/useSelection.tsx | 10 ++- components/table/hooks/useSorter.tsx | 8 +-- components/table/index.en-US.md | 18 +++--- components/table/index.zh-CN.md | 18 +++--- components/table/interface.tsx | 23 ++++--- components/vc-table/interface.ts | 4 +- 16 files changed, 175 insertions(+), 96 deletions(-) create mode 100644 components/_util/extendsObject.ts diff --git a/components/_util/extendsObject.ts b/components/_util/extendsObject.ts new file mode 100644 index 000000000..3f6959ce4 --- /dev/null +++ b/components/_util/extendsObject.ts @@ -0,0 +1,21 @@ +type RecordType = Record; + +function extendsObject(...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; diff --git a/components/table/ExpandIcon.tsx b/components/table/ExpandIcon.tsx index 6f877473f..30fb15cc9 100644 --- a/components/table/ExpandIcon.tsx +++ b/components/table/ExpandIcon.tsx @@ -32,6 +32,7 @@ function renderExpandIcon(locale: TableLocale) { [`${iconPrefix}-collapsed`]: expandable && !expanded, })} aria-label={expanded ? locale.collapse : locale.expand} + aria-expanded={expanded} /> ); }; diff --git a/components/table/Table.tsx b/components/table/Table.tsx index a52e829e4..978c9227e 100644 --- a/components/table/Table.tsx +++ b/components/table/Table.tsx @@ -383,9 +383,19 @@ const InteralTable = defineComponent< const [transformBasicColumns] = useColumns(toRef(props, 'contextSlots')); - const columnTitleProps = computed(() => ({ - ...sorterTitleProps.value, - })); + const columnTitleProps = computed(() => { + const mergedFilters: Record = {}; + 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); diff --git a/components/table/__tests__/Table.filter.test.js b/components/table/__tests__/Table.filter.test.js index 8247425cd..5bc1a7341 100644 --- a/components/table/__tests__/Table.filter.test.js +++ b/components/table/__tests__/Table.filter.test.js @@ -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, }, ], }), diff --git a/components/table/__tests__/Table.rowSelection.test.js b/components/table/__tests__/Table.rowSelection.test.js index 867c1ad5e..4336ded02 100644 --- a/components/table/__tests__/Table.rowSelection.test.js +++ b/components/table/__tests__/Table.rowSelection.test.js @@ -525,7 +525,7 @@ describe('Table.rowSelection', () => { value: 'Lucy', }, ], - filterDropdownVisible: true, + filterDropdownOpen: true, onFilter: (value, record) => record.name.indexOf(value) === 0, }, ]; diff --git a/components/table/demo/custom-filter-panel.vue b/components/table/demo/custom-filter-panel.vue index 4f5c987d1..200aafbc3 100644 --- a/components/table/demo/custom-filter-panel.vue +++ b/components/table/demo/custom-filter-panel.vue @@ -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(); diff --git a/components/table/hooks/useFilter/FilterDropdown.tsx b/components/table/hooks/useFilter/FilterDropdown.tsx index 75b29444f..476b434ba 100644 --- a/components/table/hooks/useFilter/FilterDropdown.tsx +++ b/components/table/hooks/useFilter/FilterDropdown.tsx @@ -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 { tablePrefixCls: string; prefixCls: string; @@ -108,7 +109,7 @@ export interface FilterDropdownProps { filterState?: FilterState; filterMultiple: boolean; filterMode?: 'menu' | 'tree'; - filterSearch?: FilterSearchType; + filterSearch?: FilterSearchType; columnKey: Key; triggerFilter: (filterState: FilterState) => void; locale: TableLocale; @@ -136,7 +137,29 @@ export default defineComponent>({ 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>({ 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>({ }); // ======================= 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>({ 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>({ // 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>({ return isEqual( (props.column.defaultFilteredValue || []).map(key => String(key)), selectedKeys, + true, ); } @@ -464,6 +498,9 @@ export default defineComponent>({ 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>({ diff --git a/components/table/hooks/useFilter/FilterWrapper.tsx b/components/table/hooks/useFilter/FilterWrapper.tsx index 7819b8ab1..97dd02b0f 100644 --- a/components/table/hooks/useFilter/FilterWrapper.tsx +++ b/components/table/hooks/useFilter/FilterWrapper.tsx @@ -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 }) => ( -
e.stopPropagation()}>{slots.default?.()}
+
e.stopPropagation()} onKeydown={onKeyDown}> + {slots.default?.()} +
); export default FilterDropdownMenuWrapper; diff --git a/components/table/hooks/useFilter/index.tsx b/components/table/hooks/useFilter/index.tsx index 7348a2ddd..6eaf3a1d3 100644 --- a/components/table/hooks/useFilter/index.tsx +++ b/components/table/hooks/useFilter/index.tsx @@ -74,9 +74,9 @@ function injectFilter( dropdownPrefixCls: string, columns: ColumnsType, filterStates: FilterState[], - triggerFilter: (filterState: FilterState) => void, - getPopupContainer: GetPopupContainer | undefined, locale: TableLocale, + triggerFilter: (filterState: FilterState) => void, + getPopupContainer?: GetPopupContainer | undefined, pos?: string, ): ColumnsType { return columns.map((column, index) => { @@ -121,9 +121,9 @@ function injectFilter( dropdownPrefixCls, newColumn.children, filterStates, + locale, triggerFilter, getPopupContainer, - locale, columnPos, ), }; @@ -217,7 +217,9 @@ function useFilter({ 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({ // 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({ dropdownPrefixCls.value, innerColumns, mergedFilterStates.value, + locale.value, triggerFilter, getPopupContainer.value, - locale.value, ); }; return [transformColumns, mergedFilterStates, filters]; diff --git a/components/table/hooks/usePagination.ts b/components/table/hooks/usePagination.ts index dfdbc8b8f..acd295559 100644 --- a/components/table/hooks/usePagination.ts +++ b/components/table/hooks/usePagination.ts @@ -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(...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, paginationRef: Ref, @@ -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 }; }), diff --git a/components/table/hooks/useSelection.tsx b/components/table/hooks/useSelection.tsx index 3bf6bd3f5..22e30931b 100644 --- a/components/table/hooks/useSelection.tsx +++ b/components/table/hooks/useSelection.tsx @@ -55,10 +55,7 @@ export type INTERNAL_SELECTION_ITEM = | typeof SELECTION_INVERT | typeof SELECTION_NONE; -function flattenData( - data: RecordType[] | undefined, - childrenColumnName: string, -): RecordType[] { +function flattenData(childrenColumnName: string, data: RecordType[]): RecordType[] { let list: RecordType[] = []; (data || []).forEach(record => { list.push(record); @@ -66,7 +63,7 @@ function flattenData( if (record && typeof record === 'object' && childrenColumnName in record) { list = [ ...list, - ...flattenData((record as any)[childrenColumnName], childrenColumnName), + ...flattenData(childrenColumnName, (record as any)[childrenColumnName]), ]; } }); @@ -130,7 +127,7 @@ export default function useSelection( // 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( } onChange={onSelectAllChange} disabled={flattedDataLength.value === 0 || allDisabled} + aria-label={customizeSelections ? 'Custom selection' : 'Select all'} skipGroup /> {customizeSelections} diff --git a/components/table/hooks/useSorter.tsx b/components/table/hooks/useSorter.tsx index 81ff6ce82..8f307af7e 100644 --- a/components/table/hooks/useSorter.tsx +++ b/components/table/hooks/useSorter.tsx @@ -135,10 +135,12 @@ function injectSorter( class={classNames(`${prefixCls}-column-sorter-up`, { active: sorterOrder === ASCEND, })} + role="presentation" /> ); const downNode = sortDirections.includes(DESCEND) && ( ( // 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`); diff --git a/components/table/index.en-US.md b/components/table/index.en-US.md index 3e0aebbe7..d5570f5d8 100644 --- a/components/table/index.en-US.md +++ b/components/table/index.en-US.md @@ -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'
filterReset: 'Reset'
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.
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.
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 ; return
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? diff --git a/components/table/index.zh-CN.md b/components/table/index.zh-CN.md index 3eda9f16f..94c965161 100644 --- a/components/table/index.zh-CN.md +++ b/components/table/index.zh-CN.md @@ -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: `确定`
filterReset: `重置`
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 | 超过宽度将自动省略,暂不支持和排序筛选一起使用。
设置为 `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
; return
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 ### 如何在没有数据或只有一页数据时隐藏分页栏 diff --git a/components/table/interface.tsx b/components/table/interface.tsx index fcd407783..7270dee0a 100644 --- a/components/table/interface.tsx +++ b/components/table/interface.tsx @@ -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 = (a: T, b: T, sortOrder?: SortOrder) => number; @@ -58,7 +58,6 @@ export interface ColumnFilterItem { value: string | number | boolean; children?: ColumnFilterItem[]; } - export interface ColumnTitleProps { /** @deprecated Please use `sorterColumns` instead. */ sortOrder?: SortOrder; @@ -66,14 +65,16 @@ export interface ColumnTitleProps { sortColumn?: ColumnType; sortColumns?: { column: ColumnType; order: SortOrder }[]; - filters?: Record; + filters?: Record; } export type ColumnTitle = VueNode | ((props: ColumnTitleProps) => VueNode); export type FilterValue = (Key | boolean)[]; export type FilterKey = Key[] | null; -export type FilterSearchType = boolean | ((input: string, record: ColumnFilterItem) => boolean); +export type FilterSearchType> = + | boolean + | ((input: string, record: RecordType) => boolean); export interface FilterConfirmProps { closeDropdown: boolean; } @@ -85,6 +86,8 @@ export interface FilterDropdownProps { confirm: (param?: FilterConfirmProps) => void; clearFilters?: () => void; filters?: ColumnFilterItem[]; + /** Only close filterDropdown */ + close: () => void; visible: boolean; column: ColumnType; } @@ -115,13 +118,19 @@ export interface ColumnType defaultFilteredValue?: FilterValue | null; filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode); filterMode?: 'menu' | 'tree'; - filterSearch?: FilterSearchType; + filterSearch?: FilterSearchType; 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 extends Omit, 'dataIndex'> { diff --git a/components/vc-table/interface.ts b/components/vc-table/interface.ts index 4f2b95815..3338daa35 100644 --- a/components/vc-table/interface.ts +++ b/components/vc-table/interface.ts @@ -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';