-
-
Notifications
You must be signed in to change notification settings - Fork 614
feat: support rowspan expanded #1278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 24 commits
45a6c49
b80b72a
8565bfa
532e524
239af54
b5fc268
094972a
2f15e97
5f41e75
668ef08
ff959d9
92106b5
c3a21ff
0b53334
d91920e
dd7d053
2ed330a
e606eed
1cb0013
92432af
227581c
337c5c9
0bcf3bf
a63d6a7
adf710c
fa25174
dc67020
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
title: expandedRowSpan | ||
nav: | ||
title: Demo | ||
path: /demo | ||
--- | ||
|
||
<code src="../examples/expandedRowSpan.tsx"></code> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
title: expandedSticky | ||
nav: | ||
title: Demo | ||
path: /demo | ||
--- | ||
|
||
<code src="../examples/expandedSticky.tsx"></code> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import Table from 'rc-table'; | ||
import '../../assets/index.less'; | ||
import type { ColumnsType } from '@/interface'; | ||
|
||
const columns: ColumnsType = [ | ||
{ | ||
title: '手机号', | ||
dataIndex: 'a', | ||
colSpan: 2, | ||
width: 100, | ||
onCell: (_, index) => { | ||
const props: React.TdHTMLAttributes<HTMLTableCellElement> = {}; | ||
if (index === 0) props.rowSpan = 1; | ||
if (index === 1) props.rowSpan = 4; | ||
if (index === 2) props.rowSpan = 0; | ||
if (index === 3) props.rowSpan = 0; | ||
if (index === 4) props.rowSpan = 0; | ||
if (index === 5) props.rowSpan = undefined; | ||
return props; | ||
}, | ||
}, | ||
{ title: '电话', dataIndex: 'b', colSpan: 0, width: 100 }, | ||
Table.EXPAND_COLUMN, | ||
{ title: 'Name', dataIndex: 'c', width: 100 }, | ||
{ title: 'Address', dataIndex: 'd', width: 200 }, | ||
]; | ||
|
||
const data = [ | ||
{ a: '12313132132', b: '0571-43243256', c: '小二', d: '文零西路', e: 'Male', key: 'z' }, | ||
{ a: '13812340987', b: '0571-12345678', c: '张三', d: '文一西路', e: 'Male', key: 'a' }, | ||
{ a: '13812340987', b: '0571-12345678', c: '张夫人', d: '文一西路', e: 'Female', key: 'b' }, | ||
{ a: '13812340987', b: '0571-099877', c: '李四', d: '文二西路', e: 'Male', key: 'c' }, | ||
{ a: '13812340987', b: '0571-099877', c: '李四', d: '文二西路', e: 'Male', key: 'd' }, | ||
{ a: '1381200008888', b: '0571-099877', c: '王五', d: '文二西路', e: 'Male', key: 'e' }, | ||
]; | ||
|
||
const Demo = () => ( | ||
<div> | ||
<h2>expanded & rowSpan</h2> | ||
<Table<Record<string, any>> | ||
rowKey="key" | ||
columns={columns} | ||
data={data} | ||
expandable={{ expandedRowRender: record => <p style={{ margin: 0 }}>{record.key}</p> }} | ||
className="table" | ||
/> | ||
</div> | ||
); | ||
|
||
export default Demo; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React, { useState } from 'react'; | ||
import type { ColumnType } from 'rc-table'; | ||
import Table from 'rc-table'; | ||
import '../../assets/index.less'; | ||
|
||
const Demo = () => { | ||
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly React.Key[]>([]); | ||
|
||
const columns: ColumnType<Record<string, any>>[] = [ | ||
// { title: '分割', dataIndex: 'ca' }, | ||
{ | ||
title: '手机号', | ||
dataIndex: 'a', | ||
width: 100, | ||
fixed: 'left', | ||
onCell: (_, index) => { | ||
const props: React.TdHTMLAttributes<HTMLTableCellElement> = {}; | ||
if (index === 0) props.rowSpan = 1; | ||
if (index === 1) props.rowSpan = 2; | ||
if (index === 2) props.rowSpan = 0; | ||
return props; | ||
}, | ||
}, | ||
Table.EXPAND_COLUMN, | ||
{ title: 'Name', dataIndex: 'c' }, | ||
{ title: 'Address', fixed: 'right', dataIndex: 'd', width: 200 }, | ||
]; | ||
|
||
return ( | ||
<div | ||
style={{ | ||
height: 10000, | ||
}} | ||
> | ||
<h2>expanded & sticky</h2> | ||
<Table<Record<string, any>> | ||
rowKey="key" | ||
sticky | ||
scroll={{ x: 800 }} | ||
columns={columns} | ||
data={[ | ||
{ key: 'a', a: '12313132132', c: '小二', d: '文零西路' }, | ||
{ key: 'b', a: '13812340987', c: '张三', d: '文一西路' }, | ||
{ key: 'c', a: '13812340987', c: '张夫', d: '文二西路' }, | ||
]} | ||
expandable={{ | ||
expandedRowKeys, | ||
onExpandedRowsChange: keys => setExpandedRowKeys(keys), | ||
expandedRowRender: record => <p style={{ margin: 0 }}>{record.key}</p>, | ||
}} | ||
className="table" | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Demo; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,7 +7,7 @@ | |||||||||||||||||||||||||||||||||||||||||
import type { ColumnType, CustomizeComponent } from '../interface'; | ||||||||||||||||||||||||||||||||||||||||||
import ExpandedRow from './ExpandedRow'; | ||||||||||||||||||||||||||||||||||||||||||
import { computedExpandedClassName } from '../utils/expandUtil'; | ||||||||||||||||||||||||||||||||||||||||||
import { TableProps } from '..'; | ||||||||||||||||||||||||||||||||||||||||||
import type { TableProps } from '..'; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
export interface BodyRowProps<RecordType> { | ||||||||||||||||||||||||||||||||||||||||||
record: RecordType; | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -22,6 +22,7 @@ | |||||||||||||||||||||||||||||||||||||||||
scopeCellComponent: CustomizeComponent; | ||||||||||||||||||||||||||||||||||||||||||
indent?: number; | ||||||||||||||||||||||||||||||||||||||||||
rowKey: React.Key; | ||||||||||||||||||||||||||||||||||||||||||
rowKeys: React.Key[]; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// ================================================================================== | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -33,6 +34,7 @@ | |||||||||||||||||||||||||||||||||||||||||
colIndex: number, | ||||||||||||||||||||||||||||||||||||||||||
indent: number, | ||||||||||||||||||||||||||||||||||||||||||
index: number, | ||||||||||||||||||||||||||||||||||||||||||
rowKeys: React.Key[], | ||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||
const { | ||||||||||||||||||||||||||||||||||||||||||
record, | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -46,6 +48,8 @@ | |||||||||||||||||||||||||||||||||||||||||
expanded, | ||||||||||||||||||||||||||||||||||||||||||
hasNestChildren, | ||||||||||||||||||||||||||||||||||||||||||
onTriggerExpand, | ||||||||||||||||||||||||||||||||||||||||||
expandable, | ||||||||||||||||||||||||||||||||||||||||||
expandedKeys, | ||||||||||||||||||||||||||||||||||||||||||
} = rowInfo; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
const key = columnsKey[colIndex]; | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -74,6 +78,21 @@ | |||||||||||||||||||||||||||||||||||||||||
let additionalCellProps: React.TdHTMLAttributes<HTMLElement>; | ||||||||||||||||||||||||||||||||||||||||||
if (column.onCell) { | ||||||||||||||||||||||||||||||||||||||||||
additionalCellProps = column.onCell(record, index); | ||||||||||||||||||||||||||||||||||||||||||
const { rowSpan } = additionalCellProps; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// For expandable row with rowSpan, | ||||||||||||||||||||||||||||||||||||||||||
// We should increase the rowSpan if the row is expanded | ||||||||||||||||||||||||||||||||||||||||||
if (expandable) { | ||||||||||||||||||||||||||||||||||||||||||
let currentRowSpan = rowSpan; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
for (let i = index; i < index + rowSpan; i += 1) { | ||||||||||||||||||||||||||||||||||||||||||
const rowKey = rowKeys[i]; | ||||||||||||||||||||||||||||||||||||||||||
if (expandedKeys.has(rowKey)) { | ||||||||||||||||||||||||||||||||||||||||||
currentRowSpan += 1; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
additionalCellProps.rowSpan = currentRowSpan; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -84,9 +103,31 @@ | |||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// ================================================================================== | ||||||||||||||||||||||||||||||||||||||||||
// == getCellProps == | ||||||||||||||||||||||||||||||||||||||||||
// ================================================================================== | ||||||||||||||||||||||||||||||||||||||||||
const getOffsetData = ( | ||||||||||||||||||||||||||||||||||||||||||
columnsData: { | ||||||||||||||||||||||||||||||||||||||||||
column: ColumnType<any>; | ||||||||||||||||||||||||||||||||||||||||||
cell: { additionalCellProps: React.TdHTMLAttributes<HTMLElement> }; | ||||||||||||||||||||||||||||||||||||||||||
}[], | ||||||||||||||||||||||||||||||||||||||||||
) => { | ||||||||||||||||||||||||||||||||||||||||||
let offsetWidth = 0; | ||||||||||||||||||||||||||||||||||||||||||
let offsetColumn = 0; | ||||||||||||||||||||||||||||||||||||||||||
let isRowSpanEnd = false; | ||||||||||||||||||||||||||||||||||||||||||
columnsData.forEach(item => { | ||||||||||||||||||||||||||||||||||||||||||
if (!isRowSpanEnd) { | ||||||||||||||||||||||||||||||||||||||||||
const { column, cell } = item; | ||||||||||||||||||||||||||||||||||||||||||
if (cell.additionalCellProps.rowSpan !== undefined) { | ||||||||||||||||||||||||||||||||||||||||||
offsetColumn += 1; | ||||||||||||||||||||||||||||||||||||||||||
if (typeof column.width === 'number') { | ||||||||||||||||||||||||||||||||||||||||||
offsetWidth = offsetWidth + (column.width ?? 0); | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||
isRowSpanEnd = true; | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||
return { offsetWidth, offsetColumn }; | ||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
function BodyRow<RecordType extends { children?: readonly RecordType[] }>( | ||||||||||||||||||||||||||||||||||||||||||
props: BodyRowProps<RecordType>, | ||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -107,9 +148,11 @@ | |||||||||||||||||||||||||||||||||||||||||
rowComponent: RowComponent, | ||||||||||||||||||||||||||||||||||||||||||
cellComponent, | ||||||||||||||||||||||||||||||||||||||||||
scopeCellComponent, | ||||||||||||||||||||||||||||||||||||||||||
rowKeys, | ||||||||||||||||||||||||||||||||||||||||||
} = props; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
const rowInfo = useRowInfo(record, rowKey, index, indent); | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
const { | ||||||||||||||||||||||||||||||||||||||||||
prefixCls, | ||||||||||||||||||||||||||||||||||||||||||
flattenColumns, | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -134,6 +177,17 @@ | |||||||||||||||||||||||||||||||||||||||||
// 此时如果 level > 1 则说明是 expandedRow, 一样需要附加 computedExpandedRowClassName | ||||||||||||||||||||||||||||||||||||||||||
const expandedClsName = computedExpandedClassName(expandedRowClassName, record, index, indent); | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
const { columnsData, offsetData } = React.useMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||
// eslint-disable-next-line @typescript-eslint/no-shadow | ||||||||||||||||||||||||||||||||||||||||||
const columnsData = flattenColumns.map((column: ColumnType<RecordType>, colIndex) => { | ||||||||||||||||||||||||||||||||||||||||||
const cell = getCellProps(rowInfo, column, colIndex, indent, index, rowKeys); | ||||||||||||||||||||||||||||||||||||||||||
return { column, cell }; | ||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||
// eslint-disable-next-line @typescript-eslint/no-shadow | ||||||||||||||||||||||||||||||||||||||||||
const offsetData = getOffsetData(columnsData); | ||||||||||||||||||||||||||||||||||||||||||
return { columnsData, offsetData }; | ||||||||||||||||||||||||||||||||||||||||||
}, [flattenColumns, indent, index, rowInfo, rowKeys]); | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// ======================== Base tr row ======================== | ||||||||||||||||||||||||||||||||||||||||||
const baseRowNode = ( | ||||||||||||||||||||||||||||||||||||||||||
<RowComponent | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -155,16 +209,11 @@ | |||||||||||||||||||||||||||||||||||||||||
...styles.row, | ||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||
{flattenColumns.map((column: ColumnType<RecordType>, colIndex) => { | ||||||||||||||||||||||||||||||||||||||||||
{columnsData.map(item => { | ||||||||||||||||||||||||||||||||||||||||||
const { column, cell } = item; | ||||||||||||||||||||||||||||||||||||||||||
const { render, dataIndex, className: columnClassName } = column; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps( | ||||||||||||||||||||||||||||||||||||||||||
rowInfo, | ||||||||||||||||||||||||||||||||||||||||||
column, | ||||||||||||||||||||||||||||||||||||||||||
colIndex, | ||||||||||||||||||||||||||||||||||||||||||
indent, | ||||||||||||||||||||||||||||||||||||||||||
index, | ||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||
const { key, fixedInfo, appendCellNode, additionalCellProps } = cell; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||
<Cell<RecordType> | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -207,7 +256,8 @@ | |||||||||||||||||||||||||||||||||||||||||
prefixCls={prefixCls} | ||||||||||||||||||||||||||||||||||||||||||
component={RowComponent} | ||||||||||||||||||||||||||||||||||||||||||
cellComponent={cellComponent} | ||||||||||||||||||||||||||||||||||||||||||
colSpan={flattenColumns.length} | ||||||||||||||||||||||||||||||||||||||||||
offsetWidth={offsetData.offsetWidth} | ||||||||||||||||||||||||||||||||||||||||||
colSpan={flattenColumns.length - offsetData.offsetColumn} | ||||||||||||||||||||||||||||||||||||||||||
isEmpty={false} | ||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+259
to
261
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 确保 colSpan 不会为负值 当计算展开行的 <ExpandedRow
expanded={expanded}
className={cls(
`${prefixCls}-expanded-row`,
`${prefixCls}-expanded-row-level-${indent + 1}`,
expandedClsName,
)}
prefixCls={prefixCls}
component={RowComponent}
cellComponent={cellComponent}
offsetWidth={offsetData.offsetWidth}
- colSpan={flattenColumns.length - offsetData.offsetColumn}
+ colSpan={Math.max(1, flattenColumns.length - offsetData.offsetColumn)}
isEmpty={false}
> 当 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||
{expandContent} | ||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
实现行合并与行展开的协同处理
这段代码实现了行展开时动态调整 rowSpan 的核心逻辑,但缺少测试覆盖。
代码存在以下隐患:
rowSpan
是否为有效值 (>0)i
是否超出rowKeys
数组边界🏁 Script executed:
Length of output: 288
🏁 Script executed:
Length of output: 715
校验 rowSpan 有效性并防止数组越界
在
src/Body/BodyRow.tsx
(约 81–95 行)中,这段逻辑负责在展开行时动态调整rowSpan
,但存在以下隐患需要修复并补充测试:rowSpan
为 0 或负数的情况rowKeys
长度的索引建议修改如下:
· 请在
tests/
下新增BodyRow.spec.tsx
,覆盖以下场景:rowSpan
为 0、1、>1expandedKeys
集合下的展开行为index + rowSpan
超出rowKeys.length
)📝 Committable suggestion
🧰 Tools
🪛 GitHub Check: codecov/patch
[warning] 86-86: src/Body/BodyRow.tsx#L86
Added line #L86 was not covered by tests
[warning] 88-95: src/Body/BodyRow.tsx#L88-L95
Added lines #L88 - L95 were not covered by tests
🤖 Prompt for AI Agents