diff --git a/src/table/hooks/useRowspanAndColspan.ts b/src/table/hooks/useRowspanAndColspan.ts new file mode 100644 index 0000000000..1d97595a0a --- /dev/null +++ b/src/table/hooks/useRowspanAndColspan.ts @@ -0,0 +1,74 @@ +import { ref, watch, Ref } from 'vue'; +import { BaseTableCellParams, BaseTableCol, TableRowData, TableRowspanAndColspanFunc } from '../type'; + +export interface SkipSpansValue { + colspan?: number; + rowspan?: number; + skipped?: boolean; +} + +export default function useRowspanAndColspan( + data: Ref, + columns: Ref[]>, + rowspanAndColspan: TableRowspanAndColspanFunc, +) { + const skipSpansMap = ref(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.value.get(cellKey) || {}; + state.skipped = true; + skipSpansMap.value.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.value.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.value.set(cellKey, state); + } + onTrRowspanOrColspan?.(params, state); + } + } + }; + + watch( + () => [data.value, columns.value], + () => { + if (!data || !rowspanAndColspan) return; + updateSkipSpansMap(data.value, columns.value, rowspanAndColspan); + }, + { immediate: true }, + ); + + return { skipSpansMap, updateSkipSpansMap }; +} diff --git a/src/table/tbody.tsx b/src/table/tbody.tsx index 6e8beb00e9..d371652187 100644 --- a/src/table/tbody.tsx +++ b/src/table/tbody.tsx @@ -1,15 +1,15 @@ -import { defineComponent, computed, PropType, SetupContext } from 'vue'; +import { defineComponent, computed, PropType, SetupContext, toRefs } from 'vue'; import camelCase from 'lodash/camelCase'; import get from 'lodash/get'; import pick from 'lodash/pick'; import TrElement, { ROW_LISTENERS, TABLE_PROPS } from './tr'; import { useConfig } from '../hooks/useConfig'; -import { RowspanColspan, TableRowData, BaseTableCellParams } from './type'; import { BaseTableProps, RowAndColFixedPosition } from './interface'; import { useTNodeJSX } from '../hooks/tnode'; import useClassName from './hooks/useClassName'; import baseTableProps from './base-table-props'; import { TNodeReturnValue } from '../common'; +import useRowspanAndColspan from './hooks/useRowspanAndColspan'; export const ROW_AND_TD_LISTENERS = ROW_LISTENERS.concat('cell-click'); export interface TableBodyProps extends BaseTableProps { @@ -87,8 +87,10 @@ export default defineComponent({ // eslint-disable-next-line setup(props: TableBodyProps, { emit }: SetupContext) { const renderTNode = useTNodeJSX(); + const { data, columns } = toRefs(props); const { t, global } = useConfig('table'); const { tableFullRowClasses, tableBaseClass } = useClassName(); + const { skipSpansMap } = useRowspanAndColspan(data, columns, props.rowspanAndColspan); const tbodyClasses = computed(() => [tableBaseClass.body]); @@ -99,6 +101,7 @@ export default defineComponent({ tableFullRowClasses, tbodyClasses, tableBaseClass, + skipSpansMap, }; }, @@ -139,28 +142,9 @@ export default defineComponent({ ); }; - // 受合并单元格影响,部分单元格不显示 - 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 = this.columns.length; const dataLength = this.data.length; const trNodeList: TNodeReturnValue[] = []; - // 每次渲染清空合并单元格信息 - skipSpansMap = new Map(); const properties = [ 'rowAndColFixedPosition', @@ -181,10 +165,9 @@ export default defineComponent({ columns: this.columns, rowIndex, dataLength, - skipSpansMap, + skipSpansMap: this.skipSpansMap, ...pick(this.$props, properties), // 遍历的同时,计算后面的节点,是否会因为合并单元格跳过渲染 - onTrRowspanOrColspan, }; if (this.onCellClick) { trProps.onCellClick = this.onCellClick; diff --git a/src/table/tr.tsx b/src/table/tr.tsx index b5253f4d03..50b8da0ae6 100644 --- a/src/table/tr.tsx +++ b/src/table/tr.tsx @@ -23,6 +23,7 @@ import { BaseTableCellParams, TableRowData, RowspanColspan, TdPrimaryTableProps, import baseTableProps from './base-table-props'; import useLazyLoad from './hooks/useLazyLoad'; import { RowAndColFixedPosition } from './interface'; +import { SkipSpansValue } from './hooks/useRowspanAndColspan'; export interface RenderTdExtra { rowAndColFixedPosition: RowAndColFixedPosition; @@ -64,8 +65,7 @@ export interface TrProps extends TrCommonProps { rowIndex: number; dataLength: number; rowAndColFixedPosition?: RowAndColFixedPosition; - // 属性透传,引用传值,可内部改变 - skipSpansMap?: Map; + skipSpansMap?: Map; scrollType?: string; isVirtual?: boolean; rowHeight?: number; @@ -74,7 +74,6 @@ export interface TrProps extends TrCommonProps { tableElm?: any; // HTMLDivElement tableContentElm?: any; - onTrRowspanOrColspan?: (params: PrimaryTableCellParams, cellSpans: RowspanColspan) => void; } export const ROW_LISTENERS = ['click', 'dblclick', 'mouseover', 'mousedown', 'mouseenter', 'mouseleave', 'mouseup']; @@ -107,8 +106,6 @@ export default defineComponent({ rowAndColFixedPosition: Map as PropType, // 合并单元格,是否跳过渲染 skipSpansMap: Map as PropType, - // 扫描到 rowspan 或者 colspan 时触发 - onTrRowspanOrColspan: Function as PropType, ...pick(baseTableProps, TABLE_PROPS), scrollType: String, rowHeight: Number, @@ -273,14 +270,10 @@ export default defineComponent({ rowIndex, colIndex, }; - if (isFunction(this.rowspanAndColspan)) { - const o = this.rowspanAndColspan(params); - o?.rowspan > 1 && (cellSpans.rowspan = o.rowspan); - o?.colspan > 1 && (cellSpans.colspan = o.colspan); - this.onTrRowspanOrColspan?.(params, cellSpans); - } - const skipped = this.skipSpansMap?.get([rowIndex, colIndex].join()); - if (skipped) return null; + const spanState = this.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 this.renderTd(params, { dataLength, rowAndColFixedPosition,