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