feat(components): [table] add row-expandable prop (#23140)

* feat(components): [table] add row-expandable prop

* Update table.md

* fix(components): [table] refine rowExpandable and update expandable docs

* fix(components): [table] refine rowExpandable

---------

Co-authored-by: anhao <wzanh@sinopec.com>
This commit is contained in:
An Hao
2026-01-28 05:59:45 +08:00
committed by GitHub
parent 48fee03a13
commit 38df8de99c
8 changed files with 66 additions and 15 deletions

View File

@@ -305,6 +305,7 @@ table/tooltip-formatter
| tooltip-formatter ^(2.9.4) | customize tooltip content when using `show-overflow-tooltip` | ^[Function]`(data: { row: any, column: TableColumnCtx<T>, cellValue: any }) => VNode \| string` | — |
| preserve-expanded-content ^(2.9.7) | whether to preserve expanded row content in DOM when collapsed | ^[boolean] | false |
| native-scrollbar ^(2.10.5) | whether to use native scrollbars | ^[boolean] | false |
| row-expandable ^(2.14.0) | enable expandable rows, works when the table has a column type="expand" | ^[Function]`(row: any, index: number) => boolean` | — |
### Table Events
@@ -396,12 +397,12 @@ table/tooltip-formatter
### Table-column Slots
| Name | Description | Type |
| -------------------- | --------------------------------- | ------------------------------------------------------------------ |
| default | Custom content for table columns | ^[object]`{ row: any, column: TableColumnCtx<T>, $index: number }` |
| header | Custom content for table header | ^[object]`{ column: TableColumnCtx<T>, $index: number }` |
| filter-icon ^(2.7.8) | Custom content for filter icon | ^[object]`{ filterOpened: boolean }` |
| expand ^(2.10.0) | Custom content for expand columns | ^[object]`{ expanded: boolean }` |
| Name | Description | Type |
| -------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
| default | Custom content for table columns | ^[object]`{ row: any, column: TableColumnCtx<T>, $index: number }` |
| header | Custom content for table header | ^[object]`{ column: TableColumnCtx<T>, $index: number }` |
| filter-icon ^(2.7.8) | Custom content for filter icon | ^[object]`{ filterOpened: boolean }` |
| expand ^(2.10.0) | Custom content for expand columns. The `expandable` property is supported starting from v2.14.0. | ^[object]`{ expanded: boolean, expandable: boolean }` |
## Type Declarations

View File

@@ -907,6 +907,9 @@ describe('table column', () => {
refreshData() {
this.testData = getTestData()
},
expandable(row) {
return row.id % 2 === 0
},
},
})
}
@@ -918,6 +921,20 @@ describe('table column', () => {
wrapper.unmount()
})
it('should respect row-expandable prop', async () => {
const wrapper = createInstance(
':row-expandable="expandable" default-expand-all'
)
await doubleWait()
expect(
wrapper.findAll('.el-table__expand-icon--expanded').length
).toEqual(2)
expect(
wrapper.findAll('.el-table__expand-icon.is-disabled').length
).toEqual(3)
wrapper.unmount()
})
describe('preserve-expanded-content', () => {
it('should lose expanded state when refreshing data without preserve-expanded-content', async () => {
const wrapper = createInstance() // No preserve-expanded-content prop

View File

@@ -127,11 +127,13 @@ export const cellForced = {
row,
store,
expanded,
$index,
}: {
column: TableColumnCtx<T>
row: T
store: Store<T>
expanded: boolean
$index: number
}) {
const { ns } = store
const classes = [ns.e('expand-icon')]
@@ -143,10 +145,16 @@ export const cellForced = {
e.stopPropagation()
store.toggleRowExpansion(row)
}
const isRowExpandable =
store.states.rowExpandable.value?.(row, $index) ?? true
if (!isRowExpandable) {
classes.push(ns.is('disabled'))
}
return h(
'button',
{
type: 'button',
disabled: !isRowExpandable,
'aria-label': store.t(
expanded ? 'el.table.collapseRowLabel' : 'el.table.expandRowLabel'
),
@@ -160,6 +168,7 @@ export const cellForced = {
return [
column.renderExpand({
expanded,
expandable: isRowExpandable,
}),
]
}

View File

@@ -9,28 +9,36 @@ function useExpand<T extends DefaultRow>(watcherData: WatcherPropsData<T>) {
const instance = getCurrentInstance() as Table<T>
const defaultExpandAll = ref(false)
const expandRows: Ref<T[]> = ref([])
const canRowExpand = (row: T, index: number) => {
const expandableFn = instance.store.states.rowExpandable.value
return expandableFn?.(row, index) ?? true
}
const updateExpandRows = () => {
const data = watcherData.data.value || []
const rowKey = watcherData.rowKey.value
if (defaultExpandAll.value) {
expandRows.value = data.slice()
expandRows.value = instance.store.states.rowExpandable.value
? data.filter(canRowExpand)
: data.slice()
} else if (rowKey) {
// TODO这里的代码可以优化
const expandRowsMap = getKeysMap(expandRows.value, rowKey)
expandRows.value = data.reduce((prev: T[], row: T) => {
expandRows.value = data.filter((row, index) => {
const rowId = getRowIdentity(row, rowKey)
const rowInfo = expandRowsMap[rowId]
if (rowInfo) {
prev.push(row)
}
return prev
}, [])
return !!expandRowsMap[rowId] && canRowExpand(row, index)
})
} else {
expandRows.value = []
}
}
const toggleRowExpansion = (row: T, expanded?: boolean) => {
const dataArr = watcherData.data.value || []
const rowIndex = dataArr.indexOf(row)
if (rowIndex > -1 && !canRowExpand(row, rowIndex)) return
const changed = toggleRowStatus(
expandRows.value,
row,
@@ -53,7 +61,7 @@ function useExpand<T extends DefaultRow>(watcherData: WatcherPropsData<T>) {
const keysMap = getKeysMap(data, rowKey)
expandRows.value = rowKeys.reduce((prev: T[], cur) => {
const info = keysMap[cur]
if (info) {
if (info && canRowExpand(info.row, info.index)) {
prev.push(info.row)
}
return prev

View File

@@ -9,6 +9,7 @@ import type { DefaultRow, Table, TableProps } from '../table/defaults'
const InitialStateMap = {
rowKey: 'rowKey',
defaultExpandAll: 'defaultExpandAll',
rowExpandable: 'rowExpandable',
selectOnIndeterminate: 'selectOnIndeterminate',
indent: 'indent',
lazy: 'lazy',

View File

@@ -82,6 +82,8 @@ function useWatcher<T extends DefaultRow>() {
const reserveSelection = ref(false)
const selectOnIndeterminate = ref(false)
const selectable: Ref<((row: T, index: number) => boolean) | null> = ref(null)
const rowExpandable: Ref<((row: T, index: number) => boolean) | null> =
ref(null)
const filters: Ref<StoreFilter> = ref({})
const filteredData: Ref<T[] | null> = ref(null)
const sortingColumn: Ref<TableColumnCtx<T> | null> = ref(null)
@@ -577,6 +579,7 @@ function useWatcher<T extends DefaultRow>() {
reserveSelection,
selectOnIndeterminate,
selectable,
rowExpandable,
filters,
filteredData,
sortingColumn,

View File

@@ -132,6 +132,7 @@ interface TableProps<T extends DefaultRow> {
emptyText?: string
expandRowKeys?: Array<string>
defaultExpandAll?: boolean
rowExpandable?: (row: T, index: number) => boolean
defaultSort?: Sort
tooltipEffect?: string
tooltipOptions?: TableOverflowTooltipOptions
@@ -323,6 +324,12 @@ export default {
* @description whether expand all rows by default, works when the table has a column type="expand" or contains tree structure data
*/
defaultExpandAll: Boolean,
/**
* @description enable expandable rows, works when the table has a column type="expand"
*/
rowExpandable: {
type: Function as PropType<TableProps<any>['rowExpandable']>,
},
/**
* @description set the default sort column and order. property `prop` is used to set default sort column, property `order` is used to set default sort order
*/

View File

@@ -87,6 +87,11 @@
width: min(23px, 100%);
height: 23px;
@include when(disabled) {
color: getCssVar('disabled-text-color');
cursor: not-allowed;
}
@include m(expanded) {
transform: rotate(90deg);
}