refactor: table

pull/4639/head
tangjinzhou 2021-09-06 16:54:27 +08:00
parent a64ed0d623
commit 915100e3bb
18 changed files with 285 additions and 91 deletions

View File

@ -0,0 +1,42 @@
import { isRef, reactive } from 'vue';
import type { Ref } from 'vue';
type MaybeRef<T> = T | Ref<T>;
/**
* Converts ref to reactive.
*
* @see https://vueuse.org/toReactive
* @param objectRef A ref of object
*/
export function toReactive<T extends object>(objectRef: MaybeRef<T>): T {
if (!isRef(objectRef)) return reactive(objectRef) as T;
const proxy = new Proxy(
{},
{
get(_, p, receiver) {
return Reflect.get(objectRef.value, p, receiver);
},
set(_, p, value) {
(objectRef.value as any)[p] = value;
return true;
},
deleteProperty(_, p) {
return Reflect.deleteProperty(objectRef.value, p);
},
has(_, p) {
return Reflect.has(objectRef.value, p);
},
ownKeys() {
return Object.keys(objectRef.value);
},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true,
};
},
},
);
return reactive(proxy) as T;
}

View File

@ -50,6 +50,7 @@ import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import omit from '../_util/omit'; import omit from '../_util/omit';
import { initDefaultProps } from '../_util/props-util'; import { initDefaultProps } from '../_util/props-util';
import { ContextSlots, useProvideSlots } from './context';
export type { ColumnsType, TablePaginationConfig }; export type { ColumnsType, TablePaginationConfig };
@ -106,46 +107,78 @@ export interface TableProps<RecordType = DefaultRecordType>
sortDirections?: SortOrder[]; sortDirections?: SortOrder[];
showSorterTooltip?: boolean | TooltipProps; showSorterTooltip?: boolean | TooltipProps;
} }
const tableProps = () => { const tableProps = () => {
return { return {
prefixCls: { type: String as PropType<string> }, prefixCls: { type: String as PropType<string>, default: undefined },
columns: { type: Array as PropType<ColumnsType> }, columns: { type: Array as PropType<ColumnsType>, default: undefined },
rowKey: { type: [String, Function] as PropType<TableProps['rowKey']> }, rowKey: { type: [String, Function] as PropType<TableProps['rowKey']>, default: undefined },
tableLayout: { type: String as PropType<TableProps['tableLayout']> }, tableLayout: { type: String as PropType<TableProps['tableLayout']>, default: undefined },
rowClassName: { type: String as PropType<TableProps['rowClassName']> }, rowClassName: { type: String as PropType<TableProps['rowClassName']>, default: undefined },
title: { type: Function as PropType<TableProps['title']> }, title: { type: Function as PropType<TableProps['title']>, default: undefined },
footer: { type: Function as PropType<TableProps['footer']> }, footer: { type: Function as PropType<TableProps['footer']>, default: undefined },
id: { type: String as PropType<TableProps['id']> }, id: { type: String as PropType<TableProps['id']>, default: undefined },
showHeader: { type: Boolean as PropType<TableProps['showHeader']> }, showHeader: { type: Boolean as PropType<TableProps['showHeader']>, default: undefined },
components: { type: Object as PropType<TableProps['components']> }, components: { type: Object as PropType<TableProps['components']>, default: undefined },
customRow: { type: Function as PropType<TableProps['customRow']> }, customRow: { type: Function as PropType<TableProps['customRow']>, default: undefined },
customHeaderRow: { type: Function as PropType<TableProps['customHeaderRow']> }, customHeaderRow: {
direction: { type: String as PropType<TableProps['direction']> }, type: Function as PropType<TableProps['customHeaderRow']>,
expandFixed: { type: Boolean as PropType<TableProps['expandFixed']> }, default: undefined,
expandColumnWidth: { type: Number as PropType<TableProps['expandColumnWidth']> }, },
expandedRowKeys: { type: Array as PropType<TableProps['expandedRowKeys']> }, direction: { type: String as PropType<TableProps['direction']>, default: undefined },
defaultExpandedRowKeys: { type: Array as PropType<TableProps['defaultExpandedRowKeys']> }, expandFixed: { type: Boolean as PropType<TableProps['expandFixed']>, default: undefined },
expandedRowRender: { type: Function as PropType<TableProps['expandedRowRender']> }, expandColumnWidth: {
expandRowByClick: { type: Boolean as PropType<TableProps['expandRowByClick']> }, type: Number as PropType<TableProps['expandColumnWidth']>,
expandIcon: { type: Function as PropType<TableProps['expandIcon']> }, default: undefined,
onExpand: { type: Function as PropType<TableProps['onExpand']> }, },
onExpandedRowsChange: { type: Function as PropType<TableProps['onExpandedRowsChange']> }, expandedRowKeys: { type: Array as PropType<TableProps['expandedRowKeys']>, default: undefined },
defaultExpandAllRows: { type: Boolean as PropType<TableProps['defaultExpandAllRows']> }, defaultExpandedRowKeys: {
indentSize: { type: Number as PropType<TableProps['indentSize']> }, type: Array as PropType<TableProps['defaultExpandedRowKeys']>,
expandIconColumnIndex: { type: Number as PropType<TableProps['expandIconColumnIndex']> }, default: undefined,
expandedRowClassName: { type: Function as PropType<TableProps['expandedRowClassName']> }, },
childrenColumnName: { type: String as PropType<TableProps['childrenColumnName']> }, expandedRowRender: {
rowExpandable: { type: Function as PropType<TableProps['rowExpandable']> }, type: Function as PropType<TableProps['expandedRowRender']>,
sticky: { type: String as PropType<TableProps['sticky']> }, default: undefined,
},
expandRowByClick: {
type: Boolean as PropType<TableProps['expandRowByClick']>,
default: undefined,
},
expandIcon: { type: Function as PropType<TableProps['expandIcon']>, default: undefined },
onExpand: { type: Function as PropType<TableProps['onExpand']>, default: undefined },
onExpandedRowsChange: {
type: Function as PropType<TableProps['onExpandedRowsChange']>,
default: undefined,
},
defaultExpandAllRows: {
type: Boolean as PropType<TableProps['defaultExpandAllRows']>,
default: undefined,
},
indentSize: { type: Number as PropType<TableProps['indentSize']>, default: undefined },
expandIconColumnIndex: {
type: Number as PropType<TableProps['expandIconColumnIndex']>,
default: undefined,
},
expandedRowClassName: {
type: Function as PropType<TableProps['expandedRowClassName']>,
default: undefined,
},
childrenColumnName: {
type: String as PropType<TableProps['childrenColumnName']>,
default: undefined,
},
rowExpandable: { type: Function as PropType<TableProps['rowExpandable']>, default: undefined },
sticky: { type: String as PropType<TableProps['sticky']>, default: undefined },
dropdownPrefixCls: String, dropdownPrefixCls: String,
dataSource: { type: Array as PropType<RcTableProps['data']> }, dataSource: { type: Array as PropType<RcTableProps['data']>, default: undefined },
pagination: { type: [Boolean, Object] as PropType<false | TablePaginationConfig> }, pagination: {
loading: { type: [Boolean, Object] as PropType<false | SpinProps> }, type: [Boolean, Object] as PropType<false | TablePaginationConfig>,
size: { type: String as PropType<SizeType> }, default: undefined,
},
loading: { type: [Boolean, Object] as PropType<false | SpinProps>, default: undefined },
size: { type: String as PropType<SizeType>, default: undefined },
bordered: Boolean, bordered: Boolean,
locale: { type: Object as PropType<TableLocale> }, locale: { type: Object as PropType<TableLocale>, default: undefined },
onChange: { onChange: {
type: Function as PropType< type: Function as PropType<
@ -156,30 +189,53 @@ const tableProps = () => {
extra: TableCurrentDataSource, extra: TableCurrentDataSource,
) => void ) => void
>, >,
default: undefined,
}, },
rowSelection: { type: Object as PropType<TableRowSelection> }, rowSelection: { type: Object as PropType<TableRowSelection>, default: undefined },
getPopupContainer: { type: Function as PropType<GetPopupContainer> }, getPopupContainer: { type: Function as PropType<GetPopupContainer>, default: undefined },
scroll: { scroll: {
type: Object as PropType< type: Object as PropType<
RcTableProps['scroll'] & { RcTableProps['scroll'] & {
scrollToFirstRowOnChange?: boolean; scrollToFirstRowOnChange?: boolean;
} }
>, >,
default: undefined,
},
sortDirections: { type: Array as PropType<SortOrder[]>, default: undefined },
showSorterTooltip: {
type: [Boolean, Object] as PropType<boolean | TooltipProps>,
default: undefined,
},
contextSlots: {
type: Object as PropType<ContextSlots>,
}, },
sortDirections: { type: Array as PropType<SortOrder[]> },
showSorterTooltip: { type: [Boolean, Object] as PropType<boolean | TooltipProps> },
}; };
}; };
const InteralTable = defineComponent<TableProps>({ const InteralTable = defineComponent<
TableProps & {
contextSlots: ContextSlots;
}
>({
name: 'InteralTable', name: 'InteralTable',
props: initDefaultProps(tableProps(), { props: initDefaultProps(tableProps(), {
rowKey: 'key', rowKey: 'key',
}) as any, }) as any,
inheritAttrs: false, inheritAttrs: false,
emits: [], emits: [],
slots: ['emptyText', 'expandIcon', 'title', 'footer', 'summary'], slots: [
'emptyText',
'expandIcon',
'title',
'footer',
'summary',
'expandedRowRender',
'bodyCell',
'headerCell',
'customFilterIcon',
'customFilterDropdown',
],
setup(props, { attrs, slots, emit }) { setup(props, { attrs, slots, emit }) {
devWarning( devWarning(
!(typeof props.rowKey === 'function' && props.rowKey.length > 1), !(typeof props.rowKey === 'function' && props.rowKey.length > 1),
@ -187,6 +243,8 @@ const InteralTable = defineComponent<TableProps>({
'`index` parameter of `rowKey` function is deprecated. There is no guarantee that it will work as expected.', '`index` parameter of `rowKey` function is deprecated. There is no guarantee that it will work as expected.',
); );
useProvideSlots(computed(() => props.contextSlots));
const screens = useBreakpoint(); const screens = useBreakpoint();
const mergedColumns = computed(() => { const mergedColumns = computed(() => {
@ -339,7 +397,7 @@ const InteralTable = defineComponent<TableProps>({
const columnTitleProps = computed(() => ({ const columnTitleProps = computed(() => ({
...sorterTitleProps.value, ...sorterTitleProps.value,
})); }));
const [transformTitleColumns] = useTitleColumns(columnTitleProps); const [transformTitleColumns] = useTitleColumns(columnTitleProps, toRef(props, 'contextSlots'));
// ========================== Pagination ========================== // ========================== Pagination ==========================
const onPaginationChange = (current: number, pageSize: number) => { const onPaginationChange = (current: number, pageSize: number) => {
@ -445,10 +503,12 @@ const InteralTable = defineComponent<TableProps>({
return typeof props.indentSize === 'number' ? props.indentSize : 15; return typeof props.indentSize === 'number' ? props.indentSize : 15;
}); });
const transformColumns = (innerColumns: ColumnsType<any>): ColumnsType<any> => const transformColumns = (innerColumns: ColumnsType<any>): ColumnsType<any> => {
transformTitleColumns( const res = transformTitleColumns(
transformSelectionColumns(transformFilterColumns(transformSorterColumns(innerColumns))), transformSelectionColumns(transformFilterColumns(transformSorterColumns(innerColumns))),
); );
return res;
};
return () => { return () => {
const { const {
@ -522,6 +582,7 @@ const InteralTable = defineComponent<TableProps>({
<Spin spinning={false} {...spinProps}> <Spin spinning={false} {...spinProps}>
{topPaginationNode} {topPaginationNode}
<RcTable <RcTable
{...attrs}
{...tableProps} {...tableProps}
expandIconColumnIndex={expandIconColumnIndex.value} expandIconColumnIndex={expandIconColumnIndex.value}
indentSize={indentSize.value} indentSize={indentSize.value}
@ -562,7 +623,16 @@ const Table = defineComponent<TableProps>({
setup(_props, { attrs, slots }) { setup(_props, { attrs, slots }) {
return () => { return () => {
const columns = (attrs.columns || convertChildrenToColumns(slots.default?.())) as ColumnsType; const columns = (attrs.columns || convertChildrenToColumns(slots.default?.())) as ColumnsType;
return <InteralTable {...attrs} columns={columns || []} v-slots={slots} />; console.log('slots', slots);
return (
<InteralTable
{...attrs}
columns={columns || []}
expandedRowRender={slots.expandedRowRender}
contextSlots={{ ...slots }} // use new object, slot
v-slots={slots}
/>
);
}; };
}, },
}); });

View File

@ -0,0 +1,29 @@
import type { ComputedRef, InjectionKey } from 'vue';
import { computed } from 'vue';
import { inject, provide } from 'vue';
export type ContextSlots = {
emptyText?: (...args: any[]) => void;
expandIcon?: (...args: any[]) => void;
title?: (...args: any[]) => void;
footer?: (...args: any[]) => void;
summary?: (...args: any[]) => void;
bodyCell?: (...args: any[]) => void;
headerCell?: (...args: any[]) => void;
customFilterIcon?: (...args: any[]) => void;
customFilterDropdown?: (...args: any[]) => void;
// 兼容 2.x 的 columns slots 配置
[key: string]: (...args: any[]) => void;
};
export type ContextProps = ComputedRef<ContextSlots>;
export const ContextKey: InjectionKey<ContextProps> = Symbol('ContextProps');
export const useProvideSlots = (props: ContextProps) => {
provide(ContextKey, props);
};
export const useInjectSlots = () => {
return inject(ContextKey, computed(() => ({})) as ContextProps);
};

View File

@ -18,6 +18,7 @@ import type { FilterState } from '.';
import { computed, defineComponent, onBeforeUnmount, ref } from 'vue'; import { computed, defineComponent, onBeforeUnmount, ref } from 'vue';
import classNames from '../../../_util/classNames'; import classNames from '../../../_util/classNames';
import useConfigInject from '../../../_util/hooks/useConfigInject'; import useConfigInject from '../../../_util/hooks/useConfigInject';
import { useInjectSlots } from '../../context';
const { SubMenu, Item: MenuItem } = Menu; const { SubMenu, Item: MenuItem } = Menu;
@ -119,6 +120,7 @@ export default defineComponent<FilterDropdownProps<any>>({
'getPopupContainer', 'getPopupContainer',
] as any, ] as any,
setup(props, { slots }) { setup(props, { slots }) {
const contextSlots = useInjectSlots();
const filterDropdownVisible = computed(() => props.column.filterDropdownVisible); const filterDropdownVisible = computed(() => props.column.filterDropdownVisible);
const visible = ref(false); const visible = ref(false);
const filtered = computed( const filtered = computed(
@ -294,9 +296,11 @@ export default defineComponent<FilterDropdownProps<any>>({
let filterIcon; let filterIcon;
if (typeof column.filterIcon === 'function') { if (typeof column.filterIcon === 'function') {
filterIcon = column.filterIcon(filtered.value); filterIcon = column.filterIcon({ filtered: filtered.value, column });
} else if (column.filterIcon) { } else if (column.filterIcon) {
filterIcon = column.filterIcon; filterIcon = column.filterIcon;
} else if (contextSlots.value.customFilterIcon) {
filterIcon = contextSlots.value.customFilterIcon({ filtered: filtered.value, column });
} else { } else {
filterIcon = <FilterFilled />; filterIcon = <FilterFilled />;
} }

View File

@ -143,6 +143,7 @@ function generateFilterInfo<RecordType>(filterStates: FilterState<RecordType>[])
const currentFilters: Record<string, FilterValue | null> = {}; const currentFilters: Record<string, FilterValue | null> = {};
filterStates.forEach(({ key, filteredKeys, column }) => { filterStates.forEach(({ key, filteredKeys, column }) => {
console.log(column);
const { filters, filterDropdown } = column; const { filters, filterDropdown } = column;
if (filterDropdown) { if (filterDropdown) {
currentFilters[key] = filteredKeys || null; currentFilters[key] = filteredKeys || null;

View File

@ -41,6 +41,7 @@ export default function useLazyKVMap<RecordType>(
}, },
{ {
deep: false, deep: false,
immediate: true,
}, },
); );
function getRecordByKey(key: Key): RecordType { function getRecordByKey(key: Key): RecordType {

View File

@ -81,17 +81,19 @@ export default function useSelection<RecordType>(
): [TransformColumns<RecordType>, Ref<Set<Key>>] { ): [TransformColumns<RecordType>, Ref<Set<Key>>] {
// ======================== Caches ======================== // ======================== Caches ========================
const preserveRecordsRef = ref(new Map<Key, RecordType>()); const preserveRecordsRef = ref(new Map<Key, RecordType>());
const mergedRowSelection = computed(() => rowSelectionRef.value || {});
// ========================= Keys ========================= // ========================= Keys =========================
const [mergedSelectedKeys, setMergedSelectedKeys] = useMergedState( const [mergedSelectedKeys, setMergedSelectedKeys] = useMergedState(
rowSelectionRef.value.selectedRowKeys || rowSelectionRef.value.defaultSelectedRowKeys || [], mergedRowSelection.value.selectedRowKeys ||
mergedRowSelection.value.defaultSelectedRowKeys ||
[],
{ {
value: computed(() => rowSelectionRef.value.selectedRowKeys), value: computed(() => mergedRowSelection.value.selectedRowKeys),
}, },
); );
const keyEntities = computed(() => const keyEntities = computed(() =>
rowSelectionRef.value.checkStrictly mergedRowSelection.value.checkStrictly
? { keyEntities: null } ? { keyEntities: null }
: convertDataToEntities(configRef.data.value as unknown as DataNode[], { : convertDataToEntities(configRef.data.value as unknown as DataNode[], {
externalGetKey: configRef.getRowKey.value as any, externalGetKey: configRef.getRowKey.value as any,
@ -108,7 +110,7 @@ export default function useSelection<RecordType>(
const checkboxPropsMap = computed(() => { const checkboxPropsMap = computed(() => {
const map = new Map<Key, Partial<CheckboxProps>>(); const map = new Map<Key, Partial<CheckboxProps>>();
const getRowKey = configRef.getRowKey.value; const getRowKey = configRef.getRowKey.value;
const getCheckboxProps = rowSelectionRef.value.getCheckboxProps; const getCheckboxProps = mergedRowSelection.value.getCheckboxProps;
flattedData.value.forEach((record, index) => { flattedData.value.forEach((record, index) => {
const key = getRowKey(record, index); const key = getRowKey(record, index);
const checkboxProps = (getCheckboxProps ? getCheckboxProps(record) : null) || {}; const checkboxProps = (getCheckboxProps ? getCheckboxProps(record) : null) || {};
@ -132,13 +134,13 @@ export default function useSelection<RecordType>(
!!checkboxPropsMap.value.get(configRef.getRowKey.value(r))?.disabled; !!checkboxPropsMap.value.get(configRef.getRowKey.value(r))?.disabled;
const selectKeysState = computed(() => { const selectKeysState = computed(() => {
if (rowSelectionRef.value.checkStrictly) { if (mergedRowSelection.value.checkStrictly) {
return [mergedSelectedKeys.value || [], []]; return [mergedSelectedKeys.value || [], []];
} }
const { checkedKeys, halfCheckedKeys } = conductCheck( const { checkedKeys, halfCheckedKeys } = conductCheck(
mergedSelectedKeys.value, mergedSelectedKeys.value,
true, true,
keyEntities as any, keyEntities.value,
isCheckboxDisabled as any, isCheckboxDisabled as any,
); );
return [checkedKeys || [], halfCheckedKeys]; return [checkedKeys || [], halfCheckedKeys];
@ -149,13 +151,13 @@ export default function useSelection<RecordType>(
const derivedSelectedKeySet = computed<Set<Key>>(() => { const derivedSelectedKeySet = computed<Set<Key>>(() => {
const keys = const keys =
rowSelectionRef.value.type === 'radio' mergedRowSelection.value.type === 'radio'
? derivedSelectedKeys.value.slice(0, 1) ? derivedSelectedKeys.value.slice(0, 1)
: derivedSelectedKeys.value; : derivedSelectedKeys.value;
return new Set(keys); return new Set(keys);
}); });
const derivedHalfSelectedKeySet = computed(() => const derivedHalfSelectedKeySet = computed(() =>
rowSelectionRef.value.type === 'radio' ? new Set() : new Set(derivedHalfSelectedKeys.value), mergedRowSelection.value.type === 'radio' ? new Set() : new Set(derivedHalfSelectedKeys.value),
); );
// Save last selected key to enable range selection // Save last selected key to enable range selection
@ -171,7 +173,7 @@ export default function useSelection<RecordType>(
const setSelectedKeys = (keys: Key[]) => { const setSelectedKeys = (keys: Key[]) => {
let availableKeys: Key[]; let availableKeys: Key[];
let records: RecordType[]; let records: RecordType[];
const { preserveSelectedRowKeys, onChange: onSelectionChange } = rowSelectionRef.value || {}; const { preserveSelectedRowKeys, onChange: onSelectionChange } = mergedRowSelection.value;
const { getRecordByKey } = configRef; const { getRecordByKey } = configRef;
if (preserveSelectedRowKeys) { if (preserveSelectedRowKeys) {
// Keep key if mark as preserveSelectedRowKeys // Keep key if mark as preserveSelectedRowKeys
@ -213,7 +215,7 @@ export default function useSelection<RecordType>(
// ====================== Selections ====================== // ====================== Selections ======================
// Trigger single `onSelect` event // Trigger single `onSelect` event
const triggerSingleSelection = (key: Key, selected: boolean, keys: Key[], event: Event) => { const triggerSingleSelection = (key: Key, selected: boolean, keys: Key[], event: Event) => {
const { onSelect } = rowSelectionRef.value || {}; const { onSelect } = mergedRowSelection.value;
const { getRecordByKey } = configRef || {}; const { getRecordByKey } = configRef || {};
if (onSelect) { if (onSelect) {
const rows = keys.map(k => getRecordByKey(k)); const rows = keys.map(k => getRecordByKey(k));
@ -224,7 +226,7 @@ export default function useSelection<RecordType>(
}; };
const mergedSelections = computed(() => { const mergedSelections = computed(() => {
const { onSelectInvert, onSelectNone, selections, hideSelectAll } = rowSelectionRef.value || {}; const { onSelectInvert, onSelectNone, selections, hideSelectAll } = mergedRowSelection.value;
const { data, pageData, getRowKey, locale: tableLocale } = configRef; const { data, pageData, getRowKey, locale: tableLocale } = configRef;
@ -300,7 +302,7 @@ export default function useSelection<RecordType>(
renderCell: customizeRenderCell, renderCell: customizeRenderCell,
hideSelectAll, hideSelectAll,
checkStrictly = true, checkStrictly = true,
} = rowSelectionRef.value || {}; } = mergedRowSelection.value;
const { const {
prefixCls, prefixCls,
@ -376,7 +378,7 @@ export default function useSelection<RecordType>(
</Menu> </Menu>
); );
customizeSelections = ( customizeSelections = (
<div class={`${prefixCls}-selection-extra`}> <div class={`${prefixCls.value}-selection-extra`}>
<Dropdown overlay={menu} getPopupContainer={getPopupContainer.value}> <Dropdown overlay={menu} getPopupContainer={getPopupContainer.value}>
<span> <span>
<DownOutlined /> <DownOutlined />
@ -401,7 +403,7 @@ export default function useSelection<RecordType>(
const allDisabledSomeChecked = allDisabled && allDisabledData.some(({ checked }) => checked); const allDisabledSomeChecked = allDisabled && allDisabledData.some(({ checked }) => checked);
title = !hideSelectAll && ( title = !hideSelectAll && (
<div class={`${prefixCls}-selection`}> <div class={`${prefixCls.value}-selection`}>
<Checkbox <Checkbox
checked={ checked={
!allDisabled ? !!flattedDataLength.value && checkedCurrentAll : allDisabledAndChecked !allDisabled ? !!flattedDataLength.value && checkedCurrentAll : allDisabledAndChecked
@ -539,7 +541,7 @@ export default function useSelection<RecordType>(
const result = conductCheck( const result = conductCheck(
[...originCheckedKeys, key], [...originCheckedKeys, key],
true, true,
keyEntities as any, keyEntities.value,
isCheckboxDisabled as any, isCheckboxDisabled as any,
); );
const { checkedKeys, halfCheckedKeys } = result; const { checkedKeys, halfCheckedKeys } = result;
@ -552,7 +554,7 @@ export default function useSelection<RecordType>(
nextCheckedKeys = conductCheck( nextCheckedKeys = conductCheck(
Array.from(tempKeySet), Array.from(tempKeySet),
{ checked: false, halfCheckedKeys }, { checked: false, halfCheckedKeys },
keyEntities as any, keyEntities.value,
isCheckboxDisabled as any, isCheckboxDisabled as any,
).checkedKeys; ).checkedKeys;
} }
@ -583,15 +585,15 @@ export default function useSelection<RecordType>(
// Columns // Columns
const selectionColumn = { const selectionColumn = {
width: selectionColWidth, width: selectionColWidth,
className: `${prefixCls}-selection-column`, className: `${prefixCls.value}-selection-column`,
title: rowSelectionRef.value.columnTitle || title, title: mergedRowSelection.value.columnTitle || title,
render: renderSelectionCell, render: renderSelectionCell,
[INTERNAL_COL_DEFINE]: { [INTERNAL_COL_DEFINE]: {
class: `${prefixCls}-selection-col`, class: `${prefixCls.value}-selection-col`,
}, },
}; };
if (expandType.value === 'row' && columns.length && !expandIconColumnIndex) { if (expandType.value === 'row' && columns.length && !expandIconColumnIndex.value) {
const [expandColumn, ...restColumns] = columns; const [expandColumn, ...restColumns] = columns;
const selectionFixed = fixed || getFixedType(restColumns[0]); const selectionFixed = fixed || getFixedType(restColumns[0]);
if (selectionFixed) { if (selectionFixed) {

View File

@ -1,18 +1,28 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { ContextSlots } from '../context';
import type { TransformColumns, ColumnTitleProps, ColumnsType } from '../interface'; import type { TransformColumns, ColumnTitleProps, ColumnsType } from '../interface';
import { renderColumnTitle } from '../util'; import { renderColumnTitle } from '../util';
function fillTitle<RecordType>( function fillTitle<RecordType>(
columns: ColumnsType<RecordType>, columns: ColumnsType<RecordType>,
columnTitleProps: ColumnTitleProps<RecordType>, columnTitleProps: ColumnTitleProps<RecordType>,
contextColumns: Ref<ContextSlots>,
) { ) {
const $slots = contextColumns.value;
return columns.map(column => { return columns.map(column => {
const cloneColumn = { ...column }; const cloneColumn = { ...column };
const { slots = {} } = cloneColumn;
cloneColumn.title = renderColumnTitle(column.title, columnTitleProps); Object.keys(slots).forEach(key => {
const name = slots[key];
if (cloneColumn[key] === undefined && $slots[name]) {
cloneColumn[key] = $slots[name];
}
});
cloneColumn.title = renderColumnTitle(cloneColumn.title, columnTitleProps);
if ('children' in cloneColumn) { if ('children' in cloneColumn) {
cloneColumn.children = fillTitle(cloneColumn.children, columnTitleProps); cloneColumn.children = fillTitle(cloneColumn.children, columnTitleProps, contextColumns);
} }
return cloneColumn; return cloneColumn;
@ -21,9 +31,10 @@ function fillTitle<RecordType>(
export default function useTitleColumns<RecordType>( export default function useTitleColumns<RecordType>(
columnTitleProps: Ref<ColumnTitleProps<RecordType>>, columnTitleProps: Ref<ColumnTitleProps<RecordType>>,
contextColumns: Ref<ContextSlots>,
): [TransformColumns<RecordType>] { ): [TransformColumns<RecordType>] {
const filledColumns = (columns: ColumnsType<RecordType>) => const filledColumns = (columns: ColumnsType<RecordType>) =>
fillTitle(columns, columnTitleProps.value); fillTitle(columns, columnTitleProps.value, contextColumns);
return [filledColumns]; return [filledColumns];
} }

View File

@ -106,7 +106,7 @@ export interface ColumnType<RecordType = DefaultRecordType> extends RcColumnType
filterMultiple?: boolean; filterMultiple?: boolean;
filteredValue?: FilterValue | null; filteredValue?: FilterValue | null;
defaultFilteredValue?: FilterValue | null; defaultFilteredValue?: FilterValue | null;
filterIcon?: VueNode | ((filtered: boolean) => VueNode); filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode);
onFilter?: (value: string | number | boolean, record: RecordType) => boolean; onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
filterDropdownVisible?: boolean; filterDropdownVisible?: boolean;
onFilterDropdownVisibleChange?: (visible: boolean) => void; onFilterDropdownVisibleChange?: (visible: boolean) => void;

View File

@ -149,6 +149,7 @@ export default defineComponent<BodyRowProps<unknown>>({
return ( return (
<Cell <Cell
cellType="body"
class={columnClassName} class={columnClassName}
ellipsis={column.ellipsis} ellipsis={column.ellipsis}
align={column.align} align={column.align}
@ -193,7 +194,12 @@ export default defineComponent<BodyRowProps<unknown>>({
// ======================== Expand Row ========================= // ======================== Expand Row =========================
let expandRowNode; let expandRowNode;
if (rowSupportExpand.value && (expandRended.value || expanded.value)) { if (rowSupportExpand.value && (expandRended.value || expanded.value)) {
const expandContent = expandedRowRender(record, index, indent + 1, expanded.value); const expandContent = expandedRowRender({
record,
index,
indent: indent + 1,
expanded: expanded.value,
});
const computedExpandedRowClassName = const computedExpandedRowClassName =
expandedRowClassName && expandedRowClassName(record, index, indent); expandedRowClassName && expandedRowClassName(record, index, indent);
expandRowNode = ( expandRowNode = (

View File

@ -14,6 +14,7 @@ import type {
CellEllipsisType, CellEllipsisType,
} from '../interface'; } from '../interface';
import { getPathValue, validateValue } from '../utils/valueUtil'; import { getPathValue, validateValue } from '../utils/valueUtil';
import { useInjectSlots } from '../../table/context';
function isRenderCell<RecordType = DefaultRecordType>( function isRenderCell<RecordType = DefaultRecordType>(
data: RenderedCell<RecordType>, data: RenderedCell<RecordType>,
@ -53,6 +54,8 @@ export interface CellProps<RecordType = DefaultRecordType> {
isSticky?: boolean; isSticky?: boolean;
column?: ColumnType<RecordType>; column?: ColumnType<RecordType>;
cellType?: 'header' | 'body';
} }
export default defineComponent<CellProps>({ export default defineComponent<CellProps>({
name: 'Cell', name: 'Cell',
@ -62,7 +65,6 @@ export default defineComponent<CellProps>({
'index', 'index',
'dataIndex', 'dataIndex',
'customRender', 'customRender',
'children',
'component', 'component',
'colSpan', 'colSpan',
'rowSpan', 'rowSpan',
@ -79,9 +81,11 @@ export default defineComponent<CellProps>({
'rowType', 'rowType',
'isSticky', 'isSticky',
'column', 'column',
'cellType',
] as any, ] as any,
slots: ['appendNode'], slots: ['appendNode'],
setup(props, { slots }) { setup(props, { slots }) {
const contextSlots = useInjectSlots();
return () => { return () => {
const { const {
prefixCls, prefixCls,
@ -105,6 +109,7 @@ export default defineComponent<CellProps>({
rowType, rowType,
isSticky, isSticky,
column, column,
cellType,
} = props; } = props;
const cellPrefixCls = `${prefixCls}-cell`; const cellPrefixCls = `${prefixCls}-cell`;
@ -112,8 +117,11 @@ export default defineComponent<CellProps>({
let cellProps: CellType; let cellProps: CellType;
let childNode; let childNode;
const children = slots.default?.(); const children = slots.default?.();
if (validateValue(children)) { if (validateValue(children) || cellType === 'header') {
childNode = children; childNode = children;
if (cellType === 'header' && contextSlots.value.headerCell) {
childNode = contextSlots.value.headerCell({ title: column.title, index, column });
}
} else { } else {
const value = getPathValue(record, dataIndex); const value = getPathValue(record, dataIndex);
@ -129,6 +137,10 @@ export default defineComponent<CellProps>({
childNode = renderData; childNode = renderData;
} }
} }
if (cellType === 'body' && contextSlots.value.bodyCell) {
childNode = contextSlots.value.bodyCell({ text: value, value, record, index, column });
}
} }
// Not crash if final `childNode` is not validate ReactNode // Not crash if final `childNode` is not validate ReactNode

View File

@ -92,6 +92,7 @@ export interface HeaderProps<RecordType = DefaultRecordType> {
export default defineComponent<HeaderProps>({ export default defineComponent<HeaderProps>({
name: 'Header', name: 'Header',
inheritAttrs: false,
props: ['columns', 'flattenColumns', 'stickyOffsets', 'customHeaderRow'] as any, props: ['columns', 'flattenColumns', 'stickyOffsets', 'customHeaderRow'] as any,
setup(props) { setup(props) {
const tableContext = useInjectTable(); const tableContext = useInjectTable();

View File

@ -77,6 +77,7 @@ export default defineComponent<RowProps>({
return ( return (
<Cell <Cell
{...cell} {...cell}
cellType="header"
ellipsis={column.ellipsis} ellipsis={column.ellipsis}
align={column.align} align={column.align}
component={CellComponent} component={CellComponent}

View File

@ -54,6 +54,7 @@ import VCResizeObserver from '../vc-resize-observer';
import { useProvideTable } from './context/TableContext'; import { useProvideTable } from './context/TableContext';
import { useProvideBody } from './context/BodyContext'; import { useProvideBody } from './context/BodyContext';
import { useProvideResize } from './context/ResizeContext'; import { useProvideResize } from './context/ResizeContext';
import { getDataAndAriaProps } from './utils/legacyUtil';
// Used for conditions cache // Used for conditions cache
const EMPTY_DATA = []; const EMPTY_DATA = [];
@ -178,7 +179,8 @@ export default defineComponent<TableProps>({
'internalRefs', 'internalRefs',
'canExpandable', 'canExpandable',
] as any, ] as any,
setup(props, { slots, emit }) { inheritAttrs: false,
setup(props, { attrs, slots, emit }) {
const mergedData = computed(() => props.data || EMPTY_DATA); const mergedData = computed(() => props.data || EMPTY_DATA);
const hasData = computed(() => !!mergedData.value.length); const hasData = computed(() => !!mergedData.value.length);
@ -749,9 +751,10 @@ export default defineComponent<TableProps>({
</div> </div>
); );
} }
const ariaProps = getDataAndAriaProps(attrs);
let fullTable = ( let fullTable = (
<div <div
{...ariaProps}
class={classNames(prefixCls, { class={classNames(prefixCls, {
[`${prefixCls}-rtl`]: direction === 'rtl', [`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-ping-left`]: pingedLeft.value, [`${prefixCls}-ping-left`]: pingedLeft.value,
@ -765,7 +768,9 @@ export default defineComponent<TableProps>({
[`${prefixCls}-has-fix-right`]: [`${prefixCls}-has-fix-right`]:
flattenColumns.value[columnCount.value - 1] && flattenColumns.value[columnCount.value - 1] &&
flattenColumns.value[columnCount.value - 1].fixed === 'right', flattenColumns.value[columnCount.value - 1].fixed === 'right',
[attrs.class as string]: attrs.class,
})} })}
style={attrs.style}
id={id} id={id}
ref={fullTableRef} ref={fullTableRef}
> >

View File

@ -20,12 +20,12 @@ export interface TableContextProps {
summaryCollect: (uniKey: string, fixed: boolean | string) => void; summaryCollect: (uniKey: string, fixed: boolean | string) => void;
} }
export const BodyContextKey: InjectionKey<TableContextProps> = Symbol('TableContextProps'); export const TableContextKey: InjectionKey<TableContextProps> = Symbol('TableContextProps');
export const useProvideTable = (props: TableContextProps) => { export const useProvideTable = (props: TableContextProps) => {
provide(BodyContextKey, props); provide(TableContextKey, props);
}; };
export const useInjectTable = () => { export const useInjectTable = () => {
return inject(BodyContextKey, {} as TableContextProps); return inject(TableContextKey, {} as TableContextProps);
}; };

View File

@ -66,6 +66,14 @@ interface ColumnSharedType<RecordType> {
customHeaderCell?: GetComponentProps<ColumnsType<RecordType>[number]>; customHeaderCell?: GetComponentProps<ColumnsType<RecordType>[number]>;
ellipsis?: CellEllipsisType; ellipsis?: CellEllipsisType;
align?: AlignType; align?: AlignType;
/** @deprecated Please use `v-slot:filterIcon` `v-slot:bodyCell` `v-slot:headerCell` instead */
slots?: {
filterIcon?: string;
filterDropdown?: string;
customRender?: string;
title?: string;
};
} }
export interface ColumnGroupType<RecordType> extends ColumnSharedType<RecordType> { export interface ColumnGroupType<RecordType> extends ColumnSharedType<RecordType> {
@ -176,12 +184,12 @@ export interface LegacyExpandableProps<RecordType> {
rowExpandable?: (record: RecordType) => boolean; rowExpandable?: (record: RecordType) => boolean;
} }
export type ExpandedRowRender<ValueType> = ( export type ExpandedRowRender<ValueType> = (opt: {
record: ValueType, record: ValueType;
index: number, index: number;
indent: number, indent: number;
expanded: boolean, expanded: boolean;
) => any; }) => any;
export interface RenderExpandIconProps<RecordType> { export interface RenderExpandIconProps<RecordType> {
prefixCls: string; prefixCls: string;

View File

@ -421,8 +421,8 @@ export default defineComponent({
let titleNode: any; let titleNode: any;
if (typeof title === 'function') { if (typeof title === 'function') {
titleNode = title(renderArgsData.value); titleNode = title(renderArgsData.value);
} else if (contextSlots.titleRender) { } else if (contextSlots.valuetitleRender) {
titleNode = contextSlots.titleRender(renderArgsData.value); titleNode = contextSlots.valuetitleRender(renderArgsData.value);
} else { } else {
titleNode = title; titleNode = title;
} }

View File

@ -1,8 +1,5 @@
<template> <template>
<a-table :columns="columns" :data-source="data"> <a-table :columns="columns" :data-source="data">
<template #name="{ text }">
<a>{{ text }}</a>
</template>
<template #customTitle> <template #customTitle>
<span> <span>
<smile-outlined /> <smile-outlined />
@ -36,9 +33,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { SmileOutlined, DownOutlined } from '@ant-design/icons-vue'; import { SmileOutlined, DownOutlined } from '@ant-design/icons-vue';
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
const columns = [ const columns = [
{ {
title: 'Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
slots: { title: 'customTitle', customRender: 'name' }, slots: { title: 'customTitle', customRender: 'name' },
@ -96,9 +94,12 @@ export default defineComponent({
DownOutlined, DownOutlined,
}, },
setup() { setup() {
const test = ref('111');
window.test = test;
return { return {
data, data,
columns, columns,
test,
}; };
}, },
}); });