diff --git a/es/table/src/body.js b/es/table/src/body.js index c90d6b4d575bd0b238762497323970100f55f4e1..a008df6ff9360069d7bcf9124a930ab93704e575 100644 --- a/es/table/src/body.js +++ b/es/table/src/body.js @@ -94,6 +94,48 @@ export default defineVxeComponent({ * 渲染列 */ const renderTdColumn = (seq, rowid, fixedType, isOptimizeMode, rowLevel, row, rowIndex, $rowIndex, _rowIndex, column, $columnIndex, columns, items) => { + // [perf] 视口外单元格 & 固定列隐藏单元格 早期返回 + const { tableData: _td, tableColumn: _tc, dragRow: _dr, overflowX: _ox, overflowY: _oy, scrollXLoad: _sxl, scrollYLoad: _syl, mergeBodyFlag: _mbf, isAllOverflow: _iao } = tableReactData; + const { fullColumnIdData: _fcid, mergeBodyList: _mbl, scrollXStore: _sxs, scrollYStore: _sys } = tableInternalData; + const _vyOpts = computeVirtualYOpts.value; + const _vxOpts = computeVirtualXOpts.value; + const _treeConfig = tableProps.treeConfig; + // [perf-1] 固定列区域中,非当前区域的列是隐藏的,直接返回空占位 td + // 例如:渲染左固定区域时,中间列和右固定列都是隐藏的 + // 注意:必须排除有合并单元格的情况,因为合并可能跨越固定/非固定边界 + const _fixedHidden = _ox && (fixedType ? column.fixed !== fixedType : !!column.fixed); + if (_fixedHidden && _iao && !(_mbf && _mbl.length) && !tableProps.spanMethod) { + return h('td', { + class: 'vxe-table--column vxe-body--column col--ellipsis fixed--hidden', + key: column.id, + colid: column.id + }, [h('div', { key: 'tc', class: 'vxe-cell c--ellipsis' })]); + } + // [perf-2] 虚拟滚动视口外单元格早期返回 + if (!(_mbf && _mbl.length) && !tableProps.spanMethod) { + if (!_dr || getRowid($xeTable, _dr) !== rowid) { + let _canSkip = false; + if (_oy && _syl && _td.length > 16 && !_treeConfig && !_vyOpts.immediate) { + if (_rowIndex < _sys.visibleStartIndex - _sys.preloadSize || _rowIndex > _sys.visibleEndIndex + _sys.preloadSize) { + _canSkip = true; + } + } + if (!_canSkip && _ox && _sxl && _tc.length > 10 && !_vxOpts.immediate && !column.fixed) { + const _colRest = _fcid[column.id] || {}; + const _ci = _colRest._index; + if (_ci < _sxs.visibleStartIndex - _sxs.preloadSize || _ci > _sxs.visibleEndIndex + _sxs.preloadSize) { + _canSkip = true; + } + } + if (_canSkip) { + return h('td', { + class: 'vxe-table--column vxe-body--column col--ellipsis is--progress', + key: column.id, + colid: column.id + }, [h('div', { key: 'tc', class: 'vxe-cell c--ellipsis' })]); + } + } + } const $xeGrid = $xeTable.xeGrid; const $xeGantt = $xeTable.xeGantt; const { columnKey, resizable: allResizable, showOverflow: allShowOverflow, border, height, treeConfig, cellClassName: allCellClassName, cellStyle, align: allAlign, spanMethod, mouseConfig, editConfig, editRules, tooltipConfig, padding: allPadding } = tableProps; @@ -218,14 +260,7 @@ export default defineVxeComponent({ if (isRowDragCell) { tdOns.onMouseup = $xeTable.triggerCellMouseupEvent; } - // 点击事件处理 - tdOns.onClick = (evnt) => { - $xeTable.triggerCellClickEvent(evnt, cellParams); - }; - // 双击事件处理 - tdOns.onDblclick = (evnt) => { - $xeTable.triggerCellDblclickEvent(evnt, cellParams); - }; + // [perf] onClick/onDblclick 已迁移到 tbody 事件委托,不再为每个 td 创建闭包 let isMergeCell = false; let mergeColspan = 1; let mergeRowspan = 1; @@ -461,21 +496,7 @@ export default defineVxeComponent({ let _rowIndex = -1; const hasRowGroupAggregate = isRowGroupStatus && row.isAggregate; const trOn = {}; - // 当前行事件 - if (rowOpts.isHover || highlightHoverRow) { - trOn.onMouseover = (evnt) => { - if (isVMScrollProcess()) { - return; - } - $xeTable.triggerHoverEvent(evnt, { row, rowIndex }); - }; - trOn.onMouseleave = () => { - if (isVMScrollProcess()) { - return; - } - $xeTable.clearHoverRow(); - }; - } + // [perf] 行 hover 事件已迁移到 tbody 事件委托 if (rowRest) { rowIndex = rowRest.index; _rowIndex = rowRest._index; @@ -804,7 +825,81 @@ export default defineVxeComponent({ * 内容 */ h('tbody', { - ref: refBodyTBody + ref: refBodyTBody, + // [perf] 事件委托:用单个 handler 替代每个 td 的 onClick/onDblclick 闭包 + onClick(evnt) { + const td = evnt.target.closest('.vxe-body--column'); + if (!td) return; + const tr = td.closest('.vxe-body--row'); + if (!tr) return; + const colid = td.getAttribute('colid'); + const rowid = tr.getAttribute('rowid'); + if (!colid || !rowid) return; + const { fullAllDataRowIdData, fullColumnIdData, afterFullData } = tableInternalData; + const rowRest = fullAllDataRowIdData[rowid]; + const colRest = fullColumnIdData[colid]; + if (!rowRest || !colRest) return; + const cellParams = { + $table: $xeTable, $grid: $xeTable.xeGrid, $gantt: $xeTable.xeGantt, + isEdit: false, seq: rowRest.seq, rowid, + row: rowRest.row, rowIndex: rowRest.index, + $rowIndex: rowRest.$index, _rowIndex: rowRest._index, + column: colRest.column, columnIndex: colRest.index, + $columnIndex: colRest._index, _columnIndex: colRest._index, + fixed: fixedType, source: sourceType, type: renderType, + isHidden: false, level: rowRest.level, + visibleData: afterFullData, data: tableReactData.tableData, + items: tableReactData.tableData + }; + $xeTable.triggerCellClickEvent(evnt, cellParams); + }, + onDblclick(evnt) { + const td = evnt.target.closest('.vxe-body--column'); + if (!td) return; + const tr = td.closest('.vxe-body--row'); + if (!tr) return; + const colid = td.getAttribute('colid'); + const rowid = tr.getAttribute('rowid'); + if (!colid || !rowid) return; + const { fullAllDataRowIdData, fullColumnIdData, afterFullData } = tableInternalData; + const rowRest = fullAllDataRowIdData[rowid]; + const colRest = fullColumnIdData[colid]; + if (!rowRest || !colRest) return; + const cellParams = { + $table: $xeTable, $grid: $xeTable.xeGrid, $gantt: $xeTable.xeGantt, + isEdit: false, seq: rowRest.seq, rowid, + row: rowRest.row, rowIndex: rowRest.index, + $rowIndex: rowRest.$index, _rowIndex: rowRest._index, + column: colRest.column, columnIndex: colRest.index, + $columnIndex: colRest._index, _columnIndex: colRest._index, + fixed: fixedType, source: sourceType, type: renderType, + isHidden: false, level: rowRest.level, + visibleData: afterFullData, data: tableReactData.tableData, + items: tableReactData.tableData + }; + $xeTable.triggerCellDblclickEvent(evnt, cellParams); + }, + // [perf] 行 hover 事件委托 + onMouseover(evnt) { + if (isVMScrollProcess()) return; + const { highlightHoverRow } = tableProps; + const rowOpts = computeRowOpts.value; + if (!(rowOpts.isHover || highlightHoverRow)) return; + const tr = evnt.target.closest('.vxe-body--row'); + if (!tr) return; + const rowid = tr.getAttribute('rowid'); + if (!rowid) return; + const rowRest = tableInternalData.fullAllDataRowIdData[rowid]; + if (!rowRest) return; + $xeTable.triggerHoverEvent(evnt, { row: rowRest.row, rowIndex: rowRest.index }); + }, + onMouseleave(evnt) { + if (isVMScrollProcess()) return; + const { highlightHoverRow } = tableProps; + const rowOpts = computeRowOpts.value; + if (!(rowOpts.isHover || highlightHoverRow)) return; + $xeTable.clearHoverRow(); + } }, renderRows(fixedType, isOptimizeMode, renderDataList, renderColumnList)) ]), h('div', { diff --git a/es/table/src/table.js b/es/table/src/table.js index de124a25e90ece13490b880b5a73e5e4d1ab3a37..493318a575e9c82ad912b3de301073a5d522db93 100644 --- a/es/table/src/table.js +++ b/es/table/src/table.js @@ -11761,7 +11761,8 @@ export default defineVxeComponent({ if (isRollX) { evnt.preventDefault(); internalData.inWheelScroll = true; - if (browseObj.firefox || browseObj.safari) { + // [perf] 虚拟X滚动模式下也走直接赋值,跳过 rAF 延迟 + if (browseObj.firefox || browseObj.safari || scrollXLoad) { const currLeftNum = scrollLeft; setScrollLeft(xHandleEl, currLeftNum); setScrollLeft(bodyScrollElem, currLeftNum); @@ -11796,7 +11797,10 @@ export default defineVxeComponent({ if (isRollY) { evnt.preventDefault(); internalData.inWheelScroll = true; - if (browseObj.firefox || browseObj.safari) { + // [perf] 虚拟滚动模式下,所有浏览器都走直接赋值路径 + // 原始代码在 Chrome 上使用 scrollTopTo 缓动动画,每个 wheel 事件产生 ~6 帧 rAF 回调 + // 每帧都设置 5 个元素的 scrollTop + 触发虚拟滚动数据更新,导致严重卡顿 + if (browseObj.firefox || browseObj.safari || scrollYLoad) { const currTopNum = scrollTop; setScrollTop(yHandleEl, currTopNum); setScrollTop(bodyScrollElem, currTopNum);