diff --git a/src/table/TBody.tsx b/src/table/TBody.tsx index 1fa26dcaf..024b77cd7 100644 --- a/src/table/TBody.tsx +++ b/src/table/TBody.tsx @@ -5,10 +5,10 @@ import pick from 'lodash/pick'; import classNames from 'classnames'; import TR, { ROW_LISTENERS, TABLE_PROPS } from './TR'; import { useLocaleReceiver } from '../locale/LocalReceiver'; -import { RowspanColspan, TableRowData, BaseTableCellParams } from './type'; import { BaseTableProps } from './interface'; import { RowAndColFixedPosition } from './hooks/useFixed'; import useClassName from './hooks/useClassName'; +import useRowspanAndColspan from './hooks/useRowspanAndColspan'; export const ROW_AND_TD_LISTENERS = ROW_LISTENERS.concat('cell-click'); export interface TableBodyProps extends BaseTableProps { @@ -61,6 +61,7 @@ export default function TBody(props: TableBodyProps) { const { data, columns } = props; const [global, t] = useLocaleReceiver('table'); const { tableFullRowClasses, tableBaseClass } = useClassName(); + const { skipSpansMap } = useRowspanAndColspan(data, columns, props.rowspanAndColspan); const tbodyClasses = useMemo(() => [tableBaseClass.body], [tableBaseClass.body]); @@ -101,28 +102,9 @@ export default function TBody(props: TableBodyProps) { ); }; - // 受合并单元格影响,部分单元格不显示 - let skipSpansMap = new Map(); - - const onTrRowspanOrColspan = (params: BaseTableCellParams, cellSpans: RowspanColspan) => { - const { rowIndex, colIndex } = params; - if (!cellSpans.rowspan && !cellSpans.colspan) return; - const maxRowIndex = rowIndex + (cellSpans.rowspan || 1); - const maxColIndex = colIndex + (cellSpans.colspan || 1); - for (let i = rowIndex; i < maxRowIndex; i++) { - for (let j = colIndex; j < maxColIndex; j++) { - if (i !== rowIndex || j !== colIndex) { - skipSpansMap.set([i, j].join(), true); - } - } - } - }; - const columnLength = columns.length; const dataLength = data.length; const trNodeList = []; - // 每次渲染清空合并单元格信息 - skipSpansMap = new Map(); const properties = [ 'rowAndColFixedPosition', @@ -145,8 +127,6 @@ export default function TBody(props: TableBodyProps) { dataLength, skipSpansMap, ...pick(props, properties), - // 遍历的同时,计算后面的节点,是否会因为合并单元格跳过渲染 - onTrRowspanOrColspan, }; if (props.onCellClick) { trProps.onCellClick = props.onCellClick; diff --git a/src/table/TR.tsx b/src/table/TR.tsx index 49ec2ae65..5b1d27e8d 100644 --- a/src/table/TR.tsx +++ b/src/table/TR.tsx @@ -8,6 +8,7 @@ import useClassName from './hooks/useClassName'; import TEllipsis from './Ellipsis'; import { BaseTableCellParams, TableRowData, RowspanColspan, TdBaseTableProps, TableScroll } from './type'; import useLazyLoad from './hooks/useLazyLoad'; +import { SkipSpansValue } from './hooks/useRowspanAndColspan'; export interface RenderTdExtra { rowAndColFixedPosition: RowAndColFixedPosition; @@ -49,8 +50,7 @@ export interface TrProps extends TrCommonProps { rowIndex?: number; dataLength?: number; rowAndColFixedPosition?: RowAndColFixedPosition; - // 属性透传,引用传值,可内部改变 - skipSpansMap?: Map; + skipSpansMap?: Map; scrollType?: string; isVirtual?: boolean; rowHeight?: number; @@ -60,7 +60,6 @@ export interface TrProps extends TrCommonProps { tableElm?: HTMLDivElement; tableContentElm?: HTMLDivElement; onRowMounted?: () => void; - onTrRowspanOrColspan?: (params: BaseTableCellParams, cellSpans: RowspanColspan) => void; } export const ROW_LISTENERS = ['click', 'dblclick', 'mouseover', 'mousedown', 'mouseenter', 'mouseleave', 'mouseup']; @@ -185,14 +184,10 @@ export default function TR(props: TrProps) { rowIndex, colIndex, }; - if (isFunction(props.rowspanAndColspan)) { - const o = props.rowspanAndColspan(params); - o?.rowspan > 1 && (cellSpans.rowspan = o.rowspan); - o?.colspan > 1 && (cellSpans.colspan = o.colspan); - props.onTrRowspanOrColspan?.(params, cellSpans); - } - const skipped = props.skipSpansMap?.get([rowIndex, colIndex].join()); - if (skipped) return null; + const spanState = props.skipSpansMap.get([rowIndex, colIndex].join()) || {}; + spanState?.rowspan > 1 && (cellSpans.rowspan = spanState.rowspan); + spanState?.colspan > 1 && (cellSpans.colspan = spanState.colspan); + if (spanState.skipped) return null; return renderTd(params, { dataLength, rowAndColFixedPosition, diff --git a/src/table/hooks/useRowspanAndColspan.ts b/src/table/hooks/useRowspanAndColspan.ts new file mode 100644 index 000000000..87e852560 --- /dev/null +++ b/src/table/hooks/useRowspanAndColspan.ts @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react'; +import { BaseTableCellParams, BaseTableCol, TableRowData, TableRowspanAndColspanFunc } from '../type'; + +export interface SkipSpansValue { + colspan?: number; + rowspan?: number; + skipped?: boolean; +} + +export default function useRowspanAndColspan( + data: TableRowData[], + columns: BaseTableCol[], + rowspanAndColspan: TableRowspanAndColspanFunc, +) { + const [skipSpansMap] = useState(new Map()); + + // 计算单元格是否跳过渲染 + const onTrRowspanOrColspan = (params: BaseTableCellParams, skipSpansValue: SkipSpansValue) => { + const { rowIndex, colIndex } = params; + if (!skipSpansValue.rowspan && !skipSpansValue.colspan) return; + const maxRowIndex = rowIndex + (skipSpansValue.rowspan || 1); + const maxColIndex = colIndex + (skipSpansValue.colspan || 1); + for (let i = rowIndex; i < maxRowIndex; i++) { + for (let j = colIndex; j < maxColIndex; j++) { + if (i !== rowIndex || j !== colIndex) { + const cellKey = [i, j].join(); + const state = skipSpansMap.get(cellKey) || {}; + state.skipped = true; + skipSpansMap.set(cellKey, state); + } + } + } + }; + + // 计算单元格是否需要设置 rowspan 和 colspan + const updateSkipSpansMap = ( + data: TableRowData[], + columns: BaseTableCol[], + rowspanAndColspan: TableRowspanAndColspanFunc, + ) => { + if (!data || !rowspanAndColspan) return; + for (let i = 0, len = data.length; i < len; i++) { + const row = data[i]; + for (let j = 0, colLen = columns.length; j < colLen; j++) { + const params = { + row, + col: columns[j], + rowIndex: i, + colIndex: j, + }; + const cellKey = [i, j].join(); + const state = skipSpansMap.get(cellKey) || {}; + const o = rowspanAndColspan(params) || {}; + if (o.rowspan > 1 || o.colspan > 1 || state.rowspan || state.colspan) { + o.rowspan > 1 && (state.rowspan = o.rowspan); + o.colspan > 1 && (state.colspan = o.colspan); + skipSpansMap.set(cellKey, state); + } + onTrRowspanOrColspan?.(params, state); + } + } + }; + + useEffect(() => { + if (!data || !rowspanAndColspan) return; + updateSkipSpansMap(data, columns, rowspanAndColspan); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data, columns]); + + return { skipSpansMap, updateSkipSpansMap }; +} diff --git a/test/ssr/__snapshots__/ssr.test.js.snap b/test/ssr/__snapshots__/ssr.test.js.snap index d2a7c1337..7ad6ccff8 100644 --- a/test/ssr/__snapshots__/ssr.test.js.snap +++ b/test/ssr/__snapshots__/ssr.test.js.snap @@ -754,7 +754,7 @@ exports[`ssr snapshot test renders ./src/table/_example/lazy.jsx correctly 1`] = exports[`ssr snapshot test renders ./src/table/_example/loading.jsx correctly 1`] = `"
集群名称
状态
管理员
描述
集群名称
状态
管理员
描述
集群名称
状态
管理员
描述
"`; -exports[`ssr snapshot test renders ./src/table/_example/merge-cells.jsx correctly 1`] = `"
平台
类型
默认值
是否必传
说明
公有
Array<any>
[]
Y
私有
String/""
Y
描述
私有
Y
复杂类型
公有
Boolean
false
N
标识符
公有
Number
-1 / N
位置
私有
Number
Number 类型
"`; +exports[`ssr snapshot test renders ./src/table/_example/merge-cells.jsx correctly 1`] = `"
平台
类型
默认值
是否必传
说明
公有
Array<any>
[]
Y
数据源
私有
String/""
""
Y
描述
私有
Object / {}
{}
Y
复杂类型
公有
Boolean
false
N
标识符
公有
Number
-1 / N
Y
位置
私有
Number
-1
N
Number 类型
"`; exports[`ssr snapshot test renders ./src/table/_example/multi-header.jsx correctly 1`] = `"
序号
汇总属性
字段1
字段2
字段3
字段4
属性及说明
字段5
平台
类型及默认值
属性
说明
类型
默认值
是否必传
字段6
字段7
描述
0共有String0字段1字段2字段3字段4
A
字段6字段7
这是一段很长很长很长的文本
字段5
1私有Number1字段1字段2字段3字段4
B
字段6字段7
这是一段很长很长很长的文本
字段5
2共有Array2字段1字段2字段3字段4
C
字段6字段7
这是一段很长很长很长的文本
字段5
3私有Object3字段1字段2字段3字段4
A
字段6字段7
这是一段很长很长很长的文本
字段5
4共有String4字段1字段2字段3字段4
B
字段6字段7
这是一段很长很长很长的文本
字段5
5私有Number5字段1字段2字段3字段4
C
字段6字段7
这是一段很长很长很长的文本
字段5
6共有Array6字段1字段2字段3字段4
A
字段6字段7
这是一段很长很长很长的文本
字段5
7私有Object7字段1字段2字段3字段4
B
字段6字段7
这是一段很长很长很长的文本
字段5
8共有String8字段1字段2字段3字段4
C
字段6字段7
这是一段很长很长很长的文本
字段5
9私有Number9字段1字段2字段3字段4
A
字段6字段7
这是一段很长很长很长的文本
字段5
10共有Array10字段1字段2字段3字段4
B
字段6字段7
这是一段很长很长很长的文本
字段5
11私有Object11字段1字段2字段3字段4
C
字段6字段7
这是一段很长很长很长的文本
字段5
12共有String12字段1字段2字段3字段4
A
字段6字段7
这是一段很长很长很长的文本
字段5
13私有Number13字段1字段2字段3字段4
B
字段6字段7
这是一段很长很长很长的文本
字段5
14共有Array14字段1字段2字段3字段4
C
字段6字段7
这是一段很长很长很长的文本
字段5
15私有Object15字段1字段2字段3字段4
A
字段6字段7
这是一段很长很长很长的文本
字段5
16共有String16字段1字段2字段3字段4
B
字段6字段7
这是一段很长很长很长的文本
字段5
17私有Number17字段1字段2字段3字段4
C
字段6字段7
这是一段很长很长很长的文本
字段5
18共有Array18字段1字段2字段3字段4
A
字段6字段7
这是一段很长很长很长的文本
字段5
19私有Object19字段1字段2字段3字段4
B
字段6字段7
这是一段很长很长很长的文本
字段5
"`;