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, [`${iconPrefix}-collapsed`]: expandable && !expanded,
})} })}
aria-label={expanded ? locale.collapse : locale.expand} 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 [transformBasicColumns] = useColumns(toRef(props, 'contextSlots'));
const columnTitleProps = computed(() => ({ const columnTitleProps = computed(() => {
...sorterTitleProps.value, 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); const [transformTitleColumns] = useTitleColumns(columnTitleProps);
// ========================== Pagination ========================== // ========================== Pagination ==========================
@ -413,7 +423,7 @@ const InteralTable = defineComponent<
changeEventInfo.pagination = changeEventInfo.pagination =
props.pagination === false props.pagination === false
? {} ? {}
: getPaginationParam(props.pagination, mergedPagination.value); : getPaginationParam(mergedPagination.value, props.pagination);
changeEventInfo.resetPagination = resetPagination; changeEventInfo.resetPagination = resetPagination;
}); });
@ -556,8 +566,8 @@ const InteralTable = defineComponent<
const defaultPosition = direction.value === 'rtl' ? 'left' : 'right'; const defaultPosition = direction.value === 'rtl' ? 'left' : 'right';
const { position } = mergedPagination.value; const { position } = mergedPagination.value;
if (position !== null && Array.isArray(position)) { if (position !== null && Array.isArray(position)) {
const topPos = position.find(p => p.indexOf('top') !== -1); const topPos = position.find(p => p.includes('top'));
const bottomPos = position.find(p => p.indexOf('bottom') !== -1); const bottomPos = position.find(p => p.includes('bottom'));
const isDisable = position.every(p => `${p}` === 'none'); const isDisable = position.every(p => `${p}` === 'none');
if (!topPos && !bottomPos && !isDisable) { if (!topPos && !bottomPos && !isDisable) {
bottomPaginationNode = renderPagination(defaultPosition); bottomPaginationNode = renderPagination(defaultPosition);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -135,10 +135,12 @@ function injectSorter<RecordType>(
class={classNames(`${prefixCls}-column-sorter-up`, { class={classNames(`${prefixCls}-column-sorter-up`, {
active: sorterOrder === ASCEND, active: sorterOrder === ASCEND,
})} })}
role="presentation"
/> />
); );
const downNode = sortDirections.includes(DESCEND) && ( const downNode = sortDirections.includes(DESCEND) && (
<CaretDownOutlined <CaretDownOutlined
role="presentation"
class={classNames(`${prefixCls}-column-sorter-down`, { class={classNames(`${prefixCls}-column-sorter-down`, {
active: sorterOrder === DESCEND, 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 // Inform the screen-reader so it can tell the visually impaired user which column is sorted
if (sorterOrder) { if (sorterOrder) {
if (sorterOrder === 'ascend') { cell['aria-sort'] = sorterOrder === 'ascend' ? 'ascending' : 'descending';
cell['aria-sort'] = 'ascending';
} else {
cell['aria-sort'] = 'descending';
}
} }
cell.class = classNames(cell.class, `${prefixCls}-column-has-sorters`); 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\[] | - | | | expandedRowKeys(v-model) | Current expanded row keys | string\[] | - | |
| expandedRowRender | Expanded container render for each row | Function({record, index, indent, expanded}):VNode\|v-slot | - | | | 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 | | 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" | - | | | 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` | | | expandRowByClick | Whether to expand row by clicking anywhere in the whole row | boolean | `false` | |
| footer | Table footer renderer | Function(currentPageData)\| v-slot:footer="currentPageData" | | | | 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 | | | indentSize | Indent size in pixels of tree data | number | 15 | |
| loading | Loading status of table | boolean\|[object](/components/spin) | `false` | | | 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' | | | 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 | - | | | rowClassName | Row's className | Function(record, index):string | - | |
| rowExpandable | Enable row can be expandable | (record) => boolean | - | | | 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` | | | 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 | | showExpandColumn | Show expand column | boolean | true | 3.0 |
| showHeader | Whether to show table header | boolean | `true` | | | 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 | | 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 | | 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 | | sticky | Set sticky header and scroll bar | boolean \| `{offsetHeader?: number, offsetScroll?: number, getContainer?: () => HTMLElement}` | - | 3.0 |
| summary | Summary content | v-slot:summary | - | 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 | 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 | | 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 | - | | | 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` | | | filtered | Whether the `dataSource` is filtered | boolean | `false` | |
| filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - | | | filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - | |
| filterIcon | Customized filter icon | ({filtered: boolean, column: Column}) | `false` | | | 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 | | 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 | | 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 | | 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 | | 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 | - | | | 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 | - | | | title | Title of this column | string | - | |
| width | Width of this column | string\|number | - | | | 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 | - | | | 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 #### Breakpoint
@ -275,12 +277,6 @@ return <Table rowKey="uid" />;
return <Table rowKey={record => record.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 ## FAQ
### How to hide pagination when single page or no data? ### 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\[] | - | | | expandedRowKeys(v-model) | 展开的行,控制属性 | string\[] | - | |
| expandedRowRender | 额外的展开行 | Function(record, index, indent, expanded):VNode \| v-slot:expandedRowRender="{record, index, indent, expanded}" | - | | | expandedRowRender | 额外的展开行 | Function(record, index, indent, expanded):VNode \| v-slot:expandedRowRender="{record, index, indent, expanded}" | - | |
| expandFixed | 控制展开图标是否固定,可选 true `left` `right` | boolean \| string | false | 3.0 | | expandFixed | 控制展开图标是否固定,可选 true `left` `right` | boolean \| string | false | 3.0 |
| expandColumnTitle | 自定义展开列表头 | v-slot | - | 4.0.0 |
| expandIcon | 自定义展开图标 | Function(props):VNode \| v-slot:expandIcon="props" | - | | | expandIcon | 自定义展开图标 | Function(props):VNode \| v-slot:expandIcon="props" | - | |
| expandRowByClick | 通过点击行来展开子行 | boolean | `false` | | | expandRowByClick | 通过点击行来展开子行 | boolean | `false` | |
| footer | 表格尾部 | Function(currentPageData)\|v-slot:footer="currentPageData" | | | | 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 | | | indentSize | 展示树形数据时,每层缩进的宽度,以 px 为单位 | number | 15 | |
| loading | 页面是否加载中 | boolean\|[object](/components/spin-cn) | false | | | loading | 页面是否加载中 | boolean\|[object](/components/spin-cn) | false | |
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: `确定` <br> filterReset: `重置` <br> emptyText: `暂无数据` | | | 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 | - | | | rowClassName | 表格行的类名 | Function(record, index):string | - | |
| rowExpandable | 设置是否允许行展开 | (record) => boolean | - | 3.0 | | rowExpandable | 设置是否允许行展开 | (record) => boolean | - | 3.0 |
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string\|Function(record):string | 'key' | | | 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 | | showExpandColumn | 设置是否展示行展开列 | boolean | true | 3.0 |
| showHeader | 是否显示表头 | boolean | true | | | showHeader | 是否显示表头 | boolean | true | |
| showSorterTooltip | 表头是否显示下一次排序的 tooltip 提示。当参数类型为对象时,将被设置为 Tooltip 的属性 | boolean \| [Tooltip props](/components/tooltip/) | true | 3.0 | | 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`] | | | sortDirections | 支持的排序方式,取值为 `ascend` `descend` | Array | \[`ascend`, `descend`] | |
| sticky | 设置粘性头部和滚动条 | boolean \| `{offsetHeader?: number, offsetScroll?: number, getContainer?: () => HTMLElement}` | - | 3.0 | | sticky | 设置粘性头部和滚动条 | boolean \| `{offsetHeader?: number, offsetScroll?: number, getContainer?: () => HTMLElement}` | - | 3.0 |
| summary | 总结栏 | v-slot:summary | - | 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` | - | | | defaultSortOrder | 默认排序顺序 | `ascend` \| `descend` | - | |
| ellipsis | 超过宽度将自动省略,暂不支持和排序筛选一起使用。<br />设置为 `true``{ showTitle?: boolean }` 时,表格布局将变成 `tableLayout="fixed"`。 | boolean \| { showTitle?: boolean } | false | 3.0 | | ellipsis | 超过宽度将自动省略,暂不支持和排序筛选一起使用。<br />设置为 `true``{ showTitle?: boolean }` 时,表格布局将变成 `tableLayout="fixed"`。 | boolean \| { showTitle?: boolean } | false | 3.0 |
| filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | VNode \| (props: FilterDropdownProps) => VNode | - | | | filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | VNode \| (props: FilterDropdownProps) => VNode | - | |
| filterDropdownVisible | 用于控制自定义筛选菜单是否可见 | boolean | - | | | filterDropdownOpen | 用于控制自定义筛选菜单是否可见 | boolean | - | |
| filtered | 标识数据是否经过过滤,筛选图标会高亮 | boolean | false | | | filtered | 标识数据是否经过过滤,筛选图标会高亮 | boolean | false | |
| filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - | | | filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - | |
| filterIcon | 自定义 filter 图标。 | VNode \| ({filtered: boolean, column: Column}) => vNode | false | | | 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 | | minWidth | 拖动列最小宽度,会受到表格自动调整分配宽度影响 | number | 50 | 3.0 |
| resizable | 是否可拖动调整宽度,此时 width 必须是 number 类型 | boolean | - | 3.0 | | resizable | 是否可拖动调整宽度,此时 width 必须是 number 类型 | boolean | - | 3.0 |
| responsive | 响应式 breakpoint 配置列表。未设置则始终可见。 | [Breakpoint](#Breakpoint)\[] | - | 3.0 | | responsive | 响应式 breakpoint 配置列表。未设置则始终可见。 | [Breakpoint](#Breakpoint)\[] | - | 3.0 |
| rowScope | 设置列范围 | `row` \| `rowgroup` | - | 4.0 |
| showSorterTooltip | 表头显示下一次排序的 tooltip 提示, 覆盖 table 中 `showSorterTooltip` | boolean \| [Tooltip props](/components/tooltip/#API) | true | | | showSorterTooltip | 表头显示下一次排序的 tooltip 提示, 覆盖 table 中 `showSorterTooltip` | boolean \| [Tooltip props](/components/tooltip/#API) | true | |
| sortDirections | 支持的排序方式,取值为 `'ascend'` `'descend'` | Array | `['ascend', 'descend']` | 1.5.0 | | 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 | - | | | 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 | - | | | title | 列头显示文字 | string | - | |
| width | 列宽度 | string\|number | - | | | width | 列宽度 | string\|number | - | |
| onFilter | 本地模式下,确定筛选的运行函数, 使用 template 或 jsx 时作为`filter`事件使用 | Function | - | | | onFilter | 本地模式下,确定筛选的运行函数, 使用 template 或 jsx 时作为`filter`事件使用 | Function | - | |
| onFilterDropdownVisibleChange | 自定义筛选菜单可见变化时调用,使用 template 或 jsx 时作为`filterDropdownVisibleChange`事件使用 | function(visible) {} | - | | | onFilterDropdownOpenChange | 自定义筛选菜单可见变化时调用,使用 template 或 jsx 时作为`filterDropdownOpenChange`事件使用 | function(open) {} | - | 4.0 |
#### Breakpoint #### Breakpoint
@ -280,12 +282,6 @@ return <Table rowKey="uid" />;
return <Table rowKey={record => record.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 ## FAQ
### 如何在没有数据或只有一页数据时隐藏分页栏 ### 如何在没有数据或只有一页数据时隐藏分页栏

View File

@ -49,7 +49,7 @@ export interface TableLocale {
export type SortOrder = 'descend' | 'ascend' | null; export type SortOrder = 'descend' | 'ascend' | null;
const TableActions = tuple('paginate', 'sort', 'filter'); 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; export type CompareFn<T> = (a: T, b: T, sortOrder?: SortOrder) => number;
@ -58,7 +58,6 @@ export interface ColumnFilterItem {
value: string | number | boolean; value: string | number | boolean;
children?: ColumnFilterItem[]; children?: ColumnFilterItem[];
} }
export interface ColumnTitleProps<RecordType> { export interface ColumnTitleProps<RecordType> {
/** @deprecated Please use `sorterColumns` instead. */ /** @deprecated Please use `sorterColumns` instead. */
sortOrder?: SortOrder; sortOrder?: SortOrder;
@ -66,14 +65,16 @@ export interface ColumnTitleProps<RecordType> {
sortColumn?: ColumnType<RecordType>; sortColumn?: ColumnType<RecordType>;
sortColumns?: { column: ColumnType<RecordType>; order: SortOrder }[]; 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 ColumnTitle<RecordType> = VueNode | ((props: ColumnTitleProps<RecordType>) => VueNode);
export type FilterValue = (Key | boolean)[]; export type FilterValue = (Key | boolean)[];
export type FilterKey = Key[] | null; 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 { export interface FilterConfirmProps {
closeDropdown: boolean; closeDropdown: boolean;
} }
@ -85,6 +86,8 @@ export interface FilterDropdownProps<RecordType> {
confirm: (param?: FilterConfirmProps) => void; confirm: (param?: FilterConfirmProps) => void;
clearFilters?: () => void; clearFilters?: () => void;
filters?: ColumnFilterItem[]; filters?: ColumnFilterItem[];
/** Only close filterDropdown */
close: () => void;
visible: boolean; visible: boolean;
column: ColumnType<RecordType>; column: ColumnType<RecordType>;
} }
@ -115,13 +118,19 @@ export interface ColumnType<RecordType = DefaultRecordType>
defaultFilteredValue?: FilterValue | null; defaultFilteredValue?: FilterValue | null;
filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode); filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode);
filterMode?: 'menu' | 'tree'; filterMode?: 'menu' | 'tree';
filterSearch?: FilterSearchType; filterSearch?: FilterSearchType<ColumnFilterItem>;
onFilter?: (value: string | number | boolean, record: RecordType) => boolean; onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
filterDropdownVisible?: boolean; filterDropdownOpen?: boolean;
onFilterDropdownVisibleChange?: (visible: boolean) => void; onFilterDropdownOpenChange?: (visible: boolean) => void;
filterResetToDefaultFilteredValue?: boolean; filterResetToDefaultFilteredValue?: boolean;
// Responsive // Responsive
responsive?: Breakpoint[]; 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'> { 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 * ColumnType which applied in antd: https://ant.design/components/table-cn/#Column
* - defaultSortOrder * - defaultSortOrder
* - filterDropdown * - filterDropdown
* - filterDropdownVisible * - filterDropdownOpen
* - filtered * - filtered
* - filteredValue * - filteredValue
* - filterIcon * - filterIcon
@ -12,7 +12,7 @@
* - sortOrder * - sortOrder
* - sortDirections * - sortDirections
* - onFilter * - onFilter
* - onFilterDropdownVisibleChange * - onFilterDropdownOpenChange
*/ */
import type { CSSProperties, Ref, TdHTMLAttributes } from 'vue'; import type { CSSProperties, Ref, TdHTMLAttributes } from 'vue';