mirror of
https://gitee.com/ElemeFE/element.git
synced 2024-12-03 12:48:45 +08:00
Table: optimize performance (#21330)
* Table: optimize performance * Table: fix sync height
This commit is contained in:
parent
6baf6dc706
commit
13d48cf511
@ -35,12 +35,13 @@ export const cellForced = {
|
||||
on-input={ this.toggleAllSelection }
|
||||
value={ this.isAllSelected } />;
|
||||
},
|
||||
renderCell: function(h, { row, column, store, $index }) {
|
||||
renderCell: function(h, { row, column, isSelected, store, $index }) {
|
||||
return <el-checkbox
|
||||
nativeOn-click={ (event) => event.stopPropagation() }
|
||||
value={ store.isSelected(row) }
|
||||
value={ isSelected }
|
||||
disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
|
||||
on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
|
||||
on-input={ () => { store.commit('rowSelectedChanged', row); } }
|
||||
/>;
|
||||
},
|
||||
sortable: false,
|
||||
resizable: false
|
||||
@ -67,9 +68,9 @@ export const cellForced = {
|
||||
renderHeader: function(h, { column }) {
|
||||
return column.label || '';
|
||||
},
|
||||
renderCell: function(h, { row, store }) {
|
||||
renderCell: function(h, { row, store, isExpanded }) {
|
||||
const classes = ['el-table__expand-icon'];
|
||||
if (store.states.expandRows.indexOf(row) > -1) {
|
||||
if (isExpanded) {
|
||||
classes.push('el-table__expand-icon--expanded');
|
||||
}
|
||||
const callback = function(e) {
|
||||
|
@ -98,6 +98,7 @@ Watcher.prototype.mutations = {
|
||||
}
|
||||
|
||||
this.updateTableScrollY();
|
||||
this.syncFixedTableRowHeight();
|
||||
},
|
||||
|
||||
filterChange(states, options) {
|
||||
@ -111,6 +112,7 @@ Watcher.prototype.mutations = {
|
||||
}
|
||||
|
||||
this.updateTableScrollY();
|
||||
this.syncFixedTableRowHeight();
|
||||
},
|
||||
|
||||
toggleAllSelection() {
|
||||
@ -144,4 +146,8 @@ Watcher.prototype.updateTableScrollY = function() {
|
||||
Vue.nextTick(this.table.updateScrollY);
|
||||
};
|
||||
|
||||
Watcher.prototype.syncFixedTableRowHeight = function() {
|
||||
Vue.nextTick(() => this.table.layout.syncFixedTableRowHeight());
|
||||
};
|
||||
|
||||
export default Watcher;
|
||||
|
@ -6,6 +6,7 @@ import ElTooltip from 'element-ui/packages/tooltip';
|
||||
import debounce from 'throttle-debounce/debounce';
|
||||
import LayoutObserver from './layout-observer';
|
||||
import { mapStates } from './store/helper';
|
||||
import TableRow from './table-row.js';
|
||||
|
||||
export default {
|
||||
name: 'ElTableBody',
|
||||
@ -14,7 +15,8 @@ export default {
|
||||
|
||||
components: {
|
||||
ElCheckbox,
|
||||
ElTooltip
|
||||
ElTooltip,
|
||||
TableRow
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -39,16 +41,25 @@ export default {
|
||||
border="0">
|
||||
<colgroup>
|
||||
{
|
||||
this.columns.map(column => <col name={ column.id } key={column.id} />)
|
||||
this.columns
|
||||
.filter((column, index) => !(this.columnsHidden[index] && this.fixed))
|
||||
.map(column => <col name={column.id} key={column.id} />)
|
||||
}
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{
|
||||
data.reduce((acc, row) => {
|
||||
return acc.concat(this.wrappedRowRender(row, acc.length));
|
||||
const isSelected = this.store.isSelected(row);
|
||||
const isExpanded = this.store.states.expandRows.indexOf(row) > -1;
|
||||
return acc.concat(this.wrappedRowRender({
|
||||
row,
|
||||
$index: acc.length,
|
||||
isSelected,
|
||||
isExpanded
|
||||
}));
|
||||
}, [])
|
||||
}
|
||||
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip>
|
||||
<el-tooltip effect={this.table.tooltipEffect} placement="top" ref="tooltip" content={this.tooltipContent}></el-tooltip>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
@ -71,6 +82,10 @@ export default {
|
||||
hasExpandColumn: states => states.columns.some(({ type }) => type === 'expand')
|
||||
}),
|
||||
|
||||
columnsHidden() {
|
||||
return this.columns.map((column, index) => this.isColumnHidden(index));
|
||||
},
|
||||
|
||||
firstDefaultColumnIndex() {
|
||||
return arrayFindIndex(this.columns, ({ type }) => type === 'default');
|
||||
}
|
||||
@ -238,7 +253,7 @@ export default {
|
||||
|
||||
if (cell) {
|
||||
const column = getColumnByCell(table, cell);
|
||||
const hoverState = table.hoverState = {cell, column, row};
|
||||
const hoverState = table.hoverState = { cell, column, row };
|
||||
table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
|
||||
}
|
||||
|
||||
@ -314,9 +329,18 @@ export default {
|
||||
table.$emit(`row-${name}`, row, column, event);
|
||||
},
|
||||
|
||||
rowRender(row, $index, treeRowData) {
|
||||
getRowHeight(rowKey) {
|
||||
const { fixed } = this;
|
||||
if (!fixed) {
|
||||
return null;
|
||||
}
|
||||
const height = (this.tableLayout.fixedColumnsBodyRowsHeight || {})[rowKey];
|
||||
return typeof height === 'number' ? `${height}px` : height;
|
||||
},
|
||||
|
||||
rowRender({ row, $index, treeRowData, isSelected, isExpanded }) {
|
||||
const { treeIndent, columns, firstDefaultColumnIndex } = this;
|
||||
const columnsHidden = columns.map((column, index) => this.isColumnHidden(index));
|
||||
|
||||
const rowClasses = this.getRowClass(row, $index);
|
||||
let display = true;
|
||||
if (treeRowData) {
|
||||
@ -328,76 +352,51 @@ export default {
|
||||
let displayStyle = display ? null : {
|
||||
display: 'none'
|
||||
};
|
||||
return (<tr
|
||||
style={ [displayStyle, this.getRowStyle(row, $index)] }
|
||||
class={ rowClasses }
|
||||
key={ this.getKeyOfRow(row, $index) }
|
||||
on-dblclick={ ($event) => this.handleDoubleClick($event, row) }
|
||||
on-click={ ($event) => this.handleClick($event, row) }
|
||||
on-contextmenu={ ($event) => this.handleContextMenu($event, row) }
|
||||
on-mouseenter={ _ => this.handleMouseEnter($index) }
|
||||
on-mouseleave={ this.handleMouseLeave }>
|
||||
{
|
||||
columns.map((column, cellIndex) => {
|
||||
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
|
||||
if (!rowspan || !colspan) {
|
||||
return null;
|
||||
}
|
||||
const columnData = { ...column };
|
||||
columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex);
|
||||
const data = {
|
||||
store: this.store,
|
||||
_self: this.context || this.table.$vnode.context,
|
||||
column: columnData,
|
||||
row,
|
||||
$index
|
||||
};
|
||||
if (cellIndex === firstDefaultColumnIndex && treeRowData) {
|
||||
data.treeNode = {
|
||||
indent: treeRowData.level * treeIndent,
|
||||
level: treeRowData.level
|
||||
};
|
||||
if (typeof treeRowData.expanded === 'boolean') {
|
||||
data.treeNode.expanded = treeRowData.expanded;
|
||||
// 表明是懒加载
|
||||
if ('loading' in treeRowData) {
|
||||
data.treeNode.loading = treeRowData.loading;
|
||||
}
|
||||
if ('noLazyChildren' in treeRowData) {
|
||||
data.treeNode.noLazyChildren = treeRowData.noLazyChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<td
|
||||
style={ this.getCellStyle($index, cellIndex, row, column) }
|
||||
class={ this.getCellClass($index, cellIndex, row, column) }
|
||||
rowspan={ rowspan }
|
||||
colspan={ colspan }
|
||||
on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) }
|
||||
on-mouseleave={ this.handleCellMouseLeave }>
|
||||
{
|
||||
column.renderCell.call(
|
||||
this._renderProxy,
|
||||
this.$createElement,
|
||||
data,
|
||||
columnsHidden[cellIndex]
|
||||
)
|
||||
}
|
||||
</td>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tr>);
|
||||
const height = this.getRowHeight($index);
|
||||
const heightStyle = height ? {
|
||||
height
|
||||
} : null;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
style={[displayStyle, this.getRowStyle(row, $index), heightStyle]}
|
||||
class={rowClasses}
|
||||
key={this.getKeyOfRow(row, $index)}
|
||||
nativeOn-dblclick={($event) => this.handleDoubleClick($event, row)}
|
||||
nativeOn-click={($event) => this.handleClick($event, row)}
|
||||
nativeOn-contextmenu={($event) => this.handleContextMenu($event, row)}
|
||||
nativeOn-mouseenter={_ => this.handleMouseEnter($index)}
|
||||
nativeOn-mouseleave={this.handleMouseLeave}
|
||||
columns={columns}
|
||||
row={row}
|
||||
index={$index}
|
||||
store={this.store}
|
||||
context={this.context || this.table.$vnode.context}
|
||||
firstDefaultColumnIndex={firstDefaultColumnIndex}
|
||||
treeRowData={treeRowData}
|
||||
treeIndent={treeIndent}
|
||||
columnsHidden={this.columnsHidden}
|
||||
getSpan={this.getSpan}
|
||||
getColspanRealWidth={this.getColspanRealWidth}
|
||||
getCellStyle={this.getCellStyle}
|
||||
getCellClass={this.getCellClass}
|
||||
handleCellMouseEnter={this.handleCellMouseEnter}
|
||||
handleCellMouseLeave={this.handleCellMouseLeave}
|
||||
isSelected={isSelected}
|
||||
isExpanded={isExpanded}
|
||||
fixed={this.fixed}
|
||||
>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
|
||||
wrappedRowRender(row, $index) {
|
||||
wrappedRowRender({ row, $index, isSelected, isExpanded }) {
|
||||
const store = this.store;
|
||||
const { isRowExpanded, assertRowKey } = store;
|
||||
const { treeData, lazyTreeNodeMap, childrenColumnName, rowKey } = store.states;
|
||||
if (this.hasExpandColumn && isRowExpanded(row)) {
|
||||
const renderExpanded = this.table.renderExpanded;
|
||||
const tr = this.rowRender(row, $index);
|
||||
const tr = this.rowRender({ row, $index, isSelected, isExpanded });
|
||||
if (!renderExpanded) {
|
||||
console.error('[Element Error]renderExpanded is required.');
|
||||
return tr;
|
||||
@ -430,7 +429,7 @@ export default {
|
||||
treeRowData.loading = cur.loading;
|
||||
}
|
||||
}
|
||||
const tmp = [this.rowRender(row, $index, treeRowData)];
|
||||
const tmp = [this.rowRender({ row, $index, treeRowData, isSelected, isExpanded })];
|
||||
// 渲染嵌套数据
|
||||
if (cur) {
|
||||
// currentRow 记录的是 index,所以还需主动增加 TreeTable 的 index
|
||||
@ -464,7 +463,7 @@ export default {
|
||||
}
|
||||
}
|
||||
i++;
|
||||
tmp.push(this.rowRender(node, $index + i, innerTreeRowData));
|
||||
tmp.push(this.rowRender({ row: node, $index: $index + i, treeRowData: innerTreeRowData, isSelected, isExpanded }));
|
||||
if (cur) {
|
||||
const nodes = lazyTreeNodeMap[childKey] || node[childrenColumnName];
|
||||
traverse(nodes, cur);
|
||||
@ -478,7 +477,7 @@ export default {
|
||||
}
|
||||
return tmp;
|
||||
} else {
|
||||
return this.rowRender(row, $index);
|
||||
return this.rowRender({ row, $index, isSelected, isExpanded });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ class TableLayout {
|
||||
this.bodyHeight = null; // Table Height - Table Header Height
|
||||
this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height
|
||||
this.gutterWidth = scrollbarWidth();
|
||||
this.fixedColumnsBodyRowsHeight = {};
|
||||
|
||||
for (let name in options) {
|
||||
if (options.hasOwnProperty(name)) {
|
||||
@ -113,11 +114,40 @@ class TableLayout {
|
||||
|
||||
const noData = !(this.store.states.data && this.store.states.data.length);
|
||||
this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;
|
||||
|
||||
setTimeout(() => {
|
||||
this.syncFixedTableRowHeight();
|
||||
});
|
||||
this.updateScrollY();
|
||||
this.notifyObservers('scrollable');
|
||||
}
|
||||
|
||||
syncFixedTableRowHeight() {
|
||||
const fixedColumns = this.store.states.fixedColumns;
|
||||
const rightFixedColumns = this.store.states.rightFixedColumns;
|
||||
if (fixedColumns.length + rightFixedColumns.length === 0) {
|
||||
return;
|
||||
}
|
||||
const { bodyWrapper } = this.table.$refs;
|
||||
const tableRect = bodyWrapper.getBoundingClientRect();
|
||||
|
||||
if (tableRect.height !== undefined && tableRect.height <= 0) {
|
||||
return;
|
||||
}
|
||||
const bodyRows = bodyWrapper.querySelectorAll('.el-table__row') || [];
|
||||
|
||||
const fixedColumnsBodyRowsHeight = [].reduce.call(
|
||||
bodyRows,
|
||||
(acc, row, index) => {
|
||||
const height =
|
||||
row.getBoundingClientRect().height || 'auto';
|
||||
acc[index] = height;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
this.fixedColumnsBodyRowsHeight = fixedColumnsBodyRowsHeight;
|
||||
};
|
||||
|
||||
headerDisplayNone(elm) {
|
||||
if (!elm) return true;
|
||||
let headerChild = elm;
|
||||
|
100
packages/table/src/table-row.js
Normal file
100
packages/table/src/table-row.js
Normal file
@ -0,0 +1,100 @@
|
||||
export default {
|
||||
name: 'ElTableRow',
|
||||
props: [
|
||||
'columns',
|
||||
'row',
|
||||
'index',
|
||||
'isSelected',
|
||||
'isExpanded',
|
||||
'store',
|
||||
'context',
|
||||
'firstDefaultColumnIndex',
|
||||
'treeRowData',
|
||||
'treeIndent',
|
||||
'columnsHidden',
|
||||
'getSpan',
|
||||
'getColspanRealWidth',
|
||||
'getCellStyle',
|
||||
'getCellClass',
|
||||
'handleCellMouseLeave',
|
||||
'handleCellMouseEnter',
|
||||
'fixed'
|
||||
],
|
||||
render() {
|
||||
const {
|
||||
columns,
|
||||
row,
|
||||
index: $index,
|
||||
store,
|
||||
context,
|
||||
firstDefaultColumnIndex,
|
||||
treeRowData,
|
||||
treeIndent,
|
||||
columnsHidden = [],
|
||||
isSelected,
|
||||
isExpanded
|
||||
} = this;
|
||||
|
||||
return (
|
||||
<tr>
|
||||
{
|
||||
columns.map((column, cellIndex) => {
|
||||
if (columnsHidden[cellIndex] && this.fixed) {
|
||||
return null;
|
||||
}
|
||||
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
|
||||
if (!rowspan || !colspan) {
|
||||
return null;
|
||||
}
|
||||
const columnData = { ...column };
|
||||
columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex);
|
||||
const data = {
|
||||
store,
|
||||
isSelected,
|
||||
isExpanded,
|
||||
_self: context,
|
||||
column: columnData,
|
||||
row,
|
||||
$index
|
||||
};
|
||||
if (cellIndex === firstDefaultColumnIndex && treeRowData) {
|
||||
data.treeNode = {
|
||||
indent: treeRowData.level * treeIndent,
|
||||
level: treeRowData.level
|
||||
};
|
||||
if (typeof treeRowData.expanded === 'boolean') {
|
||||
data.treeNode.expanded = treeRowData.expanded;
|
||||
// 表明是懒加载
|
||||
if ('loading' in treeRowData) {
|
||||
data.treeNode.loading = treeRowData.loading;
|
||||
}
|
||||
if ('noLazyChildren' in treeRowData) {
|
||||
data.treeNode.noLazyChildren = treeRowData.noLazyChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<td
|
||||
style={this.getCellStyle($index, cellIndex, row, column)}
|
||||
class={this.getCellClass($index, cellIndex, row, column)}
|
||||
rowspan={rowspan}
|
||||
colspan={colspan}
|
||||
on-mouseenter={($event) => this.handleCellMouseEnter($event, row)}
|
||||
on-mouseleave={this.handleCellMouseLeave}
|
||||
>
|
||||
{
|
||||
column.renderCell.call(
|
||||
this._renderProxy,
|
||||
this.$createElement,
|
||||
data,
|
||||
columnsHidden[cellIndex]
|
||||
)
|
||||
}
|
||||
</td>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
};
|
@ -115,7 +115,7 @@
|
||||
:row-class-name="rowClassName"
|
||||
:row-style="rowStyle"
|
||||
:style="{
|
||||
width: bodyWidth
|
||||
width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
|
||||
}">
|
||||
</table-body>
|
||||
<div
|
||||
@ -176,7 +176,7 @@
|
||||
:row-style="rowStyle"
|
||||
:highlight="highlightCurrentRow"
|
||||
:style="{
|
||||
width: bodyWidth
|
||||
width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '',
|
||||
}">
|
||||
</table-body>
|
||||
<div
|
||||
|
Loading…
Reference in New Issue
Block a user