Table: improve performance (#9426)

* improve table render time in some condition

* Update table.vue
pull/9566/head
FuryBean 2018-01-30 20:37:23 +08:00 committed by 杨奕
parent b27f340561
commit 9b9384214b
9 changed files with 415 additions and 229 deletions

View File

@ -0,0 +1,68 @@
export default {
created() {
this.tableLayout.addObserver(this);
},
destroyed() {
this.tableLayout.removeObserver(this);
},
computed: {
tableLayout() {
let layout = this.layout;
if (!layout && this.table) {
layout = this.table.layout;
}
if (!layout) {
throw new Error('Can not find table layout.');
}
return layout;
}
},
mounted() {
this.onColumnsChange(this.tableLayout);
this.onScrollableChange(this.tableLayout);
},
updated() {
if (this.__updated__) return;
this.onColumnsChange(this.tableLayout);
this.onScrollableChange(this.tableLayout);
this.__updated__ = true;
},
methods: {
onColumnsChange() {
const cols = this.$el.querySelectorAll('colgroup > col');
if (!cols.length) return;
const flattenColumns = this.tableLayout.getFlattenColumns();
const columnsMap = {};
flattenColumns.forEach((column) => {
columnsMap[column.id] = column;
});
for (let i = 0, j = cols.length; i < j; i++) {
const col = cols[i];
const name = col.getAttribute('name');
const column = columnsMap[name];
if (column) {
col.setAttribute('width', column.realWidth || column.width);
}
}
},
onScrollableChange(layout) {
const cols = this.$el.querySelectorAll('colgroup > col[name=gutter]');
for (let i = 0, j = cols.length; i < j; i++) {
const col = cols[i];
col.setAttribute('width', layout.scrollY ? layout.gutterWidth : '0');
}
const ths = this.$el.querySelectorAll('th.gutter');
for (let i = 0, j = ths.length; i < j; i++) {
const th = ths[i];
th.style.width = layout.scrollY ? layout.gutterWidth + 'px' : '0';
th.style.display = layout.scrollY ? '' : 'none';
}
}
}
};

View File

@ -3,8 +3,13 @@ import { hasClass, addClass, removeClass } from 'element-ui/src/utils/dom';
import ElCheckbox from 'element-ui/packages/checkbox'; import ElCheckbox from 'element-ui/packages/checkbox';
import ElTooltip from 'element-ui/packages/tooltip'; import ElTooltip from 'element-ui/packages/tooltip';
import debounce from 'throttle-debounce/debounce'; import debounce from 'throttle-debounce/debounce';
import LayoutObserver from './layout-observer';
export default { export default {
name: 'ElTableBody',
mixins: [LayoutObserver],
components: { components: {
ElCheckbox, ElCheckbox,
ElTooltip ElTooltip
@ -16,9 +21,6 @@ export default {
}, },
stripe: Boolean, stripe: Boolean,
context: {}, context: {},
layout: {
required: true
},
rowClassName: [String, Function], rowClassName: [String, Function],
rowStyle: [Object, Function], rowStyle: [Object, Function],
fixed: String, fixed: String,
@ -35,11 +37,7 @@ export default {
border="0"> border="0">
<colgroup> <colgroup>
{ {
this._l(this.columns, column => this._l(this.columns, column => <col name={ column.id } />)
<col
name={ column.id }
width={ column.realWidth || column.width }
/>)
} }
</colgroup> </colgroup>
<tbody> <tbody>
@ -112,9 +110,6 @@ export default {
} }
}) })
} }
{
!this.fixed && this.layout.scrollY && this.layout.gutterWidth ? <td class="gutter" /> : ''
}
</tr>, </tr>,
this.store.isRowExpanded(row) this.store.isRowExpanded(row)
? (<tr> ? (<tr>
@ -344,7 +339,7 @@ export default {
if (hasClass(cellChild, 'el-tooltip') && cellChild.scrollWidth > cellChild.offsetWidth && this.$refs.tooltip) { if (hasClass(cellChild, 'el-tooltip') && cellChild.scrollWidth > cellChild.offsetWidth && this.$refs.tooltip) {
const tooltip = this.$refs.tooltip; const tooltip = this.$refs.tooltip;
// TODO 会引起整个 Table 的重新渲染,需要优化
this.tooltipContent = cell.textContent || cell.innerText; this.tooltipContent = cell.textContent || cell.innerText;
tooltip.referenceElm = cell; tooltip.referenceElm = cell;
tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none'); tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none');
@ -363,7 +358,7 @@ export default {
const cell = getCell(event); const cell = getCell(event);
if (!cell) return; if (!cell) return;
const oldHoverState = this.table.hoverState; const oldHoverState = this.table.hoverState || {};
this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event); this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
}, },

View File

@ -116,6 +116,26 @@ const DEFAULT_RENDER_CELL = function(h, { row, column }) {
return value; return value;
}; };
const parseWidth = (width) => {
if (width !== undefined) {
width = parseInt(width, 10);
if (isNaN(width)) {
width = null;
}
}
return width;
};
const parseMinWidth = (minWidth) => {
if (minWidth !== undefined) {
minWidth = parseInt(minWidth, 10);
if (isNaN(minWidth)) {
minWidth = 80;
}
}
return minWidth;
};
export default { export default {
name: 'ElTableColumn', name: 'ElTableColumn',
@ -205,25 +225,12 @@ export default {
let parent = this.columnOrTableParent; let parent = this.columnOrTableParent;
let owner = this.owner; let owner = this.owner;
this.isSubColumn = owner !== parent; this.isSubColumn = owner !== parent;
this.columnId = (parent.tableId || (parent.columnId + '_')) + 'column_' + columnIdSeed++; this.columnId = (parent.tableId || parent.columnId) + '_column_' + columnIdSeed++;
let type = this.type; let type = this.type;
let width = this.width; const width = parseWidth(this.width);
if (width !== undefined) { const minWidth = parseMinWidth(this.minWidth);
width = parseInt(width, 10);
if (isNaN(width)) {
width = null;
}
}
let minWidth = this.minWidth;
if (minWidth !== undefined) {
minWidth = parseInt(minWidth, 10);
if (isNaN(minWidth)) {
minWidth = 80;
}
}
let isColumnGroup = false; let isColumnGroup = false;
@ -353,14 +360,14 @@ export default {
width(newVal) { width(newVal) {
if (this.columnConfig) { if (this.columnConfig) {
this.columnConfig.width = newVal; this.columnConfig.width = parseWidth(newVal);
this.owner.store.scheduleLayout(); this.owner.store.scheduleLayout();
} }
}, },
minWidth(newVal) { minWidth(newVal) {
if (this.columnConfig) { if (this.columnConfig) {
this.columnConfig.minWidth = newVal; this.columnConfig.minWidth = parseMinWidth(newVal);
this.owner.store.scheduleLayout(); this.owner.store.scheduleLayout();
} }
}, },
@ -368,7 +375,7 @@ export default {
fixed(newVal) { fixed(newVal) {
if (this.columnConfig) { if (this.columnConfig) {
this.columnConfig.fixed = newVal; this.columnConfig.fixed = newVal;
this.owner.store.scheduleLayout(); this.owner.store.scheduleLayout(true);
} }
}, },

View File

@ -1,6 +1,10 @@
import LayoutObserver from './layout-observer';
export default { export default {
name: 'ElTableFooter', name: 'ElTableFooter',
mixins: [LayoutObserver],
render(h) { render(h) {
const sums = []; const sums = [];
this.columns.forEach((column, index) => { this.columns.forEach((column, index) => {
@ -41,16 +45,10 @@ export default {
border="0"> border="0">
<colgroup> <colgroup>
{ {
this._l(this.columns, column => this._l(this.columns, column => <col name={ column.id } />)
<col
name={ column.id }
width={ column.realWidth || column.width }
/>)
} }
{ {
!this.fixed && this.layout.gutterWidth this.hasGutter ? <col name="gutter" /> : ''
? <col name="gutter" width={ this.layout.scrollY ? this.layout.gutterWidth : '' }></col>
: ''
} }
</colgroup> </colgroup>
<tbody class={ [{ 'has-gutter': this.hasGutter }] }> <tbody class={ [{ 'has-gutter': this.hasGutter }] }>
@ -70,9 +68,7 @@ export default {
) )
} }
{ {
this.hasGutter this.hasGutter ? <th class="gutter"></th> : ''
? <td class="gutter" style={{ width: this.layout.scrollY ? this.layout.gutterWidth + 'px' : '0' }}></td>
: ''
} }
</tr> </tr>
</tbody> </tbody>
@ -85,9 +81,6 @@ export default {
store: { store: {
required: true required: true
}, },
layout: {
required: true
},
summaryMethod: Function, summaryMethod: Function,
sumText: String, sumText: String,
border: Boolean, border: Boolean,
@ -103,6 +96,10 @@ export default {
}, },
computed: { computed: {
table() {
return this.$parent;
},
isAllSelected() { isAllSelected() {
return this.store.states.isAllSelected; return this.store.states.isAllSelected;
}, },
@ -124,7 +121,7 @@ export default {
}, },
hasGutter() { hasGutter() {
return !this.fixed && this.layout.gutterWidth; return !this.fixed && this.tableLayout.gutterWidth;
} }
}, },

View File

@ -3,6 +3,7 @@ import ElCheckbox from 'element-ui/packages/checkbox';
import ElTag from 'element-ui/packages/tag'; import ElTag from 'element-ui/packages/tag';
import Vue from 'vue'; import Vue from 'vue';
import FilterPanel from './filter-panel.vue'; import FilterPanel from './filter-panel.vue';
import LayoutObserver from './layout-observer';
const getAllColumns = (columns) => { const getAllColumns = (columns) => {
const result = []; const result = [];
@ -65,13 +66,14 @@ const convertToRows = (originColumns) => {
export default { export default {
name: 'ElTableHeader', name: 'ElTableHeader',
mixins: [LayoutObserver],
render(h) { render(h) {
const originColumns = this.store.states.originColumns; const originColumns = this.store.states.originColumns;
const columnRows = convertToRows(originColumns, this.columns); const columnRows = convertToRows(originColumns, this.columns);
// 是否拥有多级表头 // 是否拥有多级表头
const isGroup = columnRows.length > 1; const isGroup = columnRows.length > 1;
if (isGroup) this.$parent.isGroup = true; if (isGroup) this.$parent.isGroup = true;
return ( return (
<table <table
class="el-table__header" class="el-table__header"
@ -80,16 +82,10 @@ export default {
border="0"> border="0">
<colgroup> <colgroup>
{ {
this._l(this.columns, column => this._l(this.columns, column => <col name={ column.id } />)
<col
name={ column.id }
width={ column.realWidth || column.width }
/>)
} }
{ {
!this.fixed && this.layout.gutterWidth this.hasGutter ? <col name="gutter" /> : ''
? <col name="gutter" width={ this.layout.scrollY ? this.layout.gutterWidth : '' }></col>
: ''
} }
</colgroup> </colgroup>
<thead class={ [{ 'is-group': isGroup, 'has-gutter': this.hasGutter }] }> <thead class={ [{ 'is-group': isGroup, 'has-gutter': this.hasGutter }] }>
@ -137,12 +133,7 @@ export default {
) )
} }
{ {
this.hasGutter this.hasGutter ? <th class="gutter"></th> : ''
? <th class="gutter" style={{
width: this.layout.scrollY ? this.layout.gutterWidth + 'px' : '0',
display: this.layout.scrollY ? '' : 'none'
}}></th>
: ''
} }
</tr> </tr>
) )
@ -157,9 +148,6 @@ export default {
store: { store: {
required: true required: true
}, },
layout: {
required: true
},
border: Boolean, border: Boolean,
defaultSort: { defaultSort: {
type: Object, type: Object,
@ -211,7 +199,7 @@ export default {
}, },
hasGutter() { hasGutter() {
return !this.fixed && this.layout.gutterWidth; return !this.fixed && this.tableLayout.gutterWidth;
} }
}, },

View File

@ -1,7 +1,9 @@
import scrollbarWidth from 'element-ui/src/utils/scrollbar-width'; import scrollbarWidth from 'element-ui/src/utils/scrollbar-width';
import Vue from 'vue';
class TableLayout { class TableLayout {
constructor(options) { constructor(options) {
this.observers = [];
this.table = null; this.table = null;
this.store = null; this.store = null;
this.columns = null; this.columns = null;
@ -43,7 +45,7 @@ class TableLayout {
const bodyWrapper = this.table.bodyWrapper; const bodyWrapper = this.table.bodyWrapper;
if (this.table.$el && bodyWrapper) { if (this.table.$el && bodyWrapper) {
const body = bodyWrapper.querySelector('.el-table__body'); const body = bodyWrapper.querySelector('.el-table__body');
this.scrollY = body.offsetHeight > bodyWrapper.offsetHeight; this.scrollY = body.offsetHeight > this.bodyHeight;
} }
} }
@ -52,19 +54,19 @@ class TableLayout {
if (typeof value === 'string' && /^\d+$/.test(value)) { if (typeof value === 'string' && /^\d+$/.test(value)) {
value = Number(value); value = Number(value);
} }
this.height = value; this.height = value;
if (!el) return; if (!el && value) return Vue.nextTick(() => this.setHeight(value, prop));
if (typeof value === 'number') { if (typeof value === 'number') {
el.style[prop] = value + 'px'; el.style[prop] = value + 'px';
this.updateHeight(); this.updateElsHeight();
} else if (typeof value === 'string') { } else if (typeof value === 'string') {
if (value === '') { if (value === '') {
el.style[prop] = ''; el.style[prop] = '';
} }
this.updateHeight(); this.updateElsHeight();
} }
} }
@ -72,37 +74,33 @@ class TableLayout {
return this.setHeight(value, 'max-height'); return this.setHeight(value, 'max-height');
} }
updateHeight() { updateElsHeight() {
const height = this.tableHeight = this.table.$el.clientHeight; if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight());
const noData = !this.table.data || this.table.data.length === 0;
const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs; const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs;
const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0;
this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0; this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0;
if (this.showHeader && !headerWrapper) return; if (this.showHeader && !headerWrapper) return;
if (!this.showHeader) { const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight;
this.headerHeight = 0; if (this.showHeader && headerWrapper.offsetWidth > 0 && headerHeight < 2) {
if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) { return Vue.nextTick(() => this.updateElsHeight());
this.bodyHeight = height - footerHeight + (footerWrapper ? 1 : 0);
}
this.fixedBodyHeight = this.scrollX ? height - this.gutterWidth : height;
} else {
const headerHeight = this.headerHeight = headerWrapper.offsetHeight;
const bodyHeight = height - headerHeight - footerHeight + (footerWrapper ? 1 : 0);
if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) {
this.bodyHeight = bodyHeight;
}
this.fixedBodyHeight = this.scrollX ? bodyHeight - this.gutterWidth : bodyHeight;
} }
this.viewportHeight = this.scrollX ? height - (noData ? 0 : this.gutterWidth) : height; const tableHeight = this.tableHeight = this.table.$el.clientHeight;
if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) {
const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0;
this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0);
}
this.fixedBodyHeight = this.scrollX ? this.bodyHeight - this.gutterWidth : this.bodyHeight;
const noData = !this.table.data || this.table.data.length === 0;
this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;
this.updateScrollY();
this.notifyObservers('scrollable');
} }
update() { getFlattenColumns() {
const fit = this.fit;
const columns = this.table.columns;
const bodyWidth = this.table.$el.clientWidth;
let bodyMinWidth = 0;
const flattenColumns = []; const flattenColumns = [];
const columns = this.table.columns;
columns.forEach((column) => { columns.forEach((column) => {
if (column.isColumnGroup) { if (column.isColumnGroup) {
flattenColumns.push.apply(flattenColumns, column.columns); flattenColumns.push.apply(flattenColumns, column.columns);
@ -111,8 +109,21 @@ class TableLayout {
} }
}); });
return flattenColumns;
}
updateColumnsWidth() {
const fit = this.fit;
const bodyWidth = this.table.$el.clientWidth;
let bodyMinWidth = 0;
const flattenColumns = this.getFlattenColumns();
let flexColumns = flattenColumns.filter((column) => typeof column.width !== 'number'); let flexColumns = flattenColumns.filter((column) => typeof column.width !== 'number');
flattenColumns.forEach((column) => { // Clean those columns whose width changed from flex to unflex
if (typeof column.width === 'number' && column.realWidth) column.realWidth = null;
});
if (flexColumns.length > 0 && fit) { if (flexColumns.length > 0 && fit) {
flattenColumns.forEach((column) => { flattenColumns.forEach((column) => {
bodyMinWidth += column.width || column.minWidth || 80; bodyMinWidth += column.width || column.minWidth || 80;
@ -169,7 +180,7 @@ class TableLayout {
if (fixedColumns.length > 0) { if (fixedColumns.length > 0) {
let fixedWidth = 0; let fixedWidth = 0;
fixedColumns.forEach(function(column) { fixedColumns.forEach(function(column) {
fixedWidth += column.realWidth; fixedWidth += column.realWidth || column.width;
}); });
this.fixedWidth = fixedWidth; this.fixedWidth = fixedWidth;
@ -179,11 +190,40 @@ class TableLayout {
if (rightFixedColumns.length > 0) { if (rightFixedColumns.length > 0) {
let rightFixedWidth = 0; let rightFixedWidth = 0;
rightFixedColumns.forEach(function(column) { rightFixedColumns.forEach(function(column) {
rightFixedWidth += column.realWidth; rightFixedWidth += column.realWidth || column.width;
}); });
this.rightFixedWidth = rightFixedWidth; this.rightFixedWidth = rightFixedWidth;
} }
this.notifyObservers('columns');
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notifyObservers(event) {
const observers = this.observers;
observers.forEach((observer) => {
switch (event) {
case 'columns':
observer.onColumnsChange(this);
break;
case 'scrollable':
observer.onScrollableChange(this);
break;
default:
throw new Error(`Table Layout don't have event ${event}.`);
}
});
} }
} }

View File

@ -94,7 +94,6 @@ const TableStore = function(table, initialState = {}) {
fixedLeafColumnsLength: 0, fixedLeafColumnsLength: 0,
rightFixedLeafColumnsLength: 0, rightFixedLeafColumnsLength: 0,
isComplex: false, isComplex: false,
_data: null,
filteredData: null, filteredData: null,
data: null, data: null,
sortingColumn: null, sortingColumn: null,
@ -137,15 +136,6 @@ TableStore.prototype.mutations = {
states.filteredData = data; states.filteredData = data;
states.data = sortData((data || []), states); states.data = sortData((data || []), states);
// states.data.forEach((item) => {
// if (!item.$extra) {
// Object.defineProperty(item, '$extra', {
// value: {},
// enumerable: false
// });
// }
// });
this.updateCurrentRow(); this.updateCurrentRow();
if (!states.reserveSelection) { if (!states.reserveSelection) {
@ -252,8 +242,10 @@ TableStore.prototype.mutations = {
states.reserveSelection = column.reserveSelection; states.reserveSelection = column.reserveSelection;
} }
this.updateColumns(); // hack for dynamics insert column if (this.table.$ready) {
this.scheduleLayout(); this.updateColumns(); // hack for dynamics insert column
this.scheduleLayout();
}
}, },
removeColumn(states, column, parent) { removeColumn(states, column, parent) {
@ -266,8 +258,10 @@ TableStore.prototype.mutations = {
array.splice(array.indexOf(column), 1); array.splice(array.indexOf(column), 1);
} }
this.updateColumns(); // hack for dynamics remove column if (this.table.$ready) {
this.scheduleLayout(); this.updateColumns(); // hack for dynamics remove column
this.scheduleLayout();
}
}, },
setHoverRow(states, row) { setHoverRow(states, row) {
@ -370,7 +364,9 @@ TableStore.prototype.clearSelection = function() {
const states = this.states; const states = this.states;
states.isAllSelected = false; states.isAllSelected = false;
const oldSelection = states.selection; const oldSelection = states.selection;
states.selection = []; if (states.selection.length) {
states.selection = [];
}
if (oldSelection.length > 0) { if (oldSelection.length > 0) {
this.table.$emit('selection-change', states.selection ? states.selection.slice() : []); this.table.$emit('selection-change', states.selection ? states.selection.slice() : []);
} }
@ -531,8 +527,11 @@ TableStore.prototype.updateAllSelected = function() {
states.isAllSelected = isAllSelected; states.isAllSelected = isAllSelected;
}; };
TableStore.prototype.scheduleLayout = function() { TableStore.prototype.scheduleLayout = function(updateColumns) {
this.table.debouncedLayout(); if (updateColumns) {
this.updateColumns();
}
this.table.debouncedUpdateLayout();
}; };
TableStore.prototype.setCurrentRowKey = function(key) { TableStore.prototype.setCurrentRowKey = function(key) {

View File

@ -7,154 +7,213 @@
'el-table--hidden': isHidden, 'el-table--hidden': isHidden,
'el-table--group': isGroup, 'el-table--group': isGroup,
'el-table--fluid-height': maxHeight, 'el-table--fluid-height': maxHeight,
'el-table--scrollable-x': layout.scrollX,
'el-table--scrollable-y': layout.scrollY,
'el-table--enable-row-hover': !store.states.isComplex, 'el-table--enable-row-hover': !store.states.isComplex,
'el-table--enable-row-transition': (store.states.data || []).length !== 0 && (store.states.data || []).length < 100 'el-table--enable-row-transition': (store.states.data || []).length !== 0 && (store.states.data || []).length < 100
}, tableSize ? `el-table--${ tableSize }` : '']" }, tableSize ? `el-table--${ tableSize }` : '']"
@mouseleave="handleMouseLeave($event)"> @mouseleave="handleMouseLeave($event)">
<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div> <div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
<div class="el-table__header-wrapper" ref="headerWrapper" v-if="showHeader" v-mousewheel="handleHeaderFooterMousewheel"> <div
v-if="showHeader"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__header-wrapper"
ref="headerWrapper">
<table-header <table-header
ref="tableHeader" ref="tableHeader"
:store="store" :store="store"
:layout="layout"
:border="border" :border="border"
:default-sort="defaultSort" :default-sort="defaultSort"
:style="{ width: layout.bodyWidth ? layout.bodyWidth + 'px' : '' }"> :style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}">
</table-header> </table-header>
</div> </div>
<div <div
class="el-table__body-wrapper" class="el-table__body-wrapper"
ref="bodyWrapper" ref="bodyWrapper"
:class="[layout.scrollX ? `is-scroll-${scrollPosition}` : 'is-scroll-none']" :class="[layout.scrollX ? `is-scrolling-${scrollPosition}` : 'is-scrolling-none']"
:style="[bodyHeight]"> :style="[bodyHeight]">
<table-body <table-body
:context="context" :context="context"
:store="store" :store="store"
:stripe="stripe" :stripe="stripe"
:layout="layout"
:row-class-name="rowClassName" :row-class-name="rowClassName"
:row-style="rowStyle" :row-style="rowStyle"
:highlight="highlightCurrentRow" :highlight="highlightCurrentRow"
:style="{ width: bodyWidth }"> :style="{
width: bodyWidth
}">
</table-body> </table-body>
<div :style="{ width: bodyWidth }" class="el-table__empty-block" v-if="!data || data.length === 0"> <div
<span class="el-table__empty-text"><slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot></span> v-if="!data || data.length === 0"
class="el-table__empty-block"
ref="emptyBlock"
:style="{
width: bodyWidth
}">
<span class="el-table__empty-text">
<slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot>
</span>
</div> </div>
<div class="el-table__append-wrapper" ref="appendWrapper" v-if="$slots.append"> <div
v-if="$slots.append"
class="el-table__append-wrapper"
ref="appendWrapper">
<slot name="append"></slot> <slot name="append"></slot>
</div> </div>
</div> </div>
<div class="el-table__footer-wrapper" ref="footerWrapper" v-if="showSummary" v-show="data && data.length > 0" v-mousewheel="handleHeaderFooterMousewheel"> <div
v-if="showSummary"
v-show="data && data.length > 0"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__footer-wrapper"
ref="footerWrapper">
<table-footer <table-footer
:store="store" :store="store"
:layout="layout"
:border="border" :border="border"
:sum-text="sumText || t('el.table.sumText')" :sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod" :summary-method="summaryMethod"
:default-sort="defaultSort" :default-sort="defaultSort"
:style="{ width: layout.bodyWidth ? layout.bodyWidth + 'px' : '' }"> :style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}">
</table-footer> </table-footer>
</div> </div>
<div class="el-table__fixed" ref="fixedWrapper" <div
v-if="fixedColumns.length > 0" v-if="fixedColumns.length > 0"
v-mousewheel="handleFixedMousewheel" v-mousewheel="handleFixedMousewheel"
:style="[ class="el-table__fixed"
{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }, ref="fixedWrapper"
fixedHeight :style="[{
]"> width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
<div class="el-table__fixed-header-wrapper" ref="fixedHeaderWrapper" v-if="showHeader"> },
fixedHeight]">
<div
v-if="showHeader"
class="el-table__fixed-header-wrapper"
ref="fixedHeaderWrapper" >
<table-header <table-header
ref="fixedTableHeader" ref="fixedTableHeader"
fixed="left" fixed="left"
:border="border" :border="border"
:store="store" :store="store"
:layout="layout" :style="{
:style="{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }"></table-header> width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
}"></table-header>
</div> </div>
<div <div
class="el-table__fixed-body-wrapper" class="el-table__fixed-body-wrapper"
ref="fixedBodyWrapper" ref="fixedBodyWrapper"
:style="[ :style="[{
{ top: layout.headerHeight + 'px' }, top: layout.headerHeight + 'px'
fixedBodyHeight },
]"> fixedBodyHeight]">
<table-body <table-body
fixed="left" fixed="left"
:store="store" :store="store"
:stripe="stripe" :stripe="stripe"
:layout="layout"
:highlight="highlightCurrentRow" :highlight="highlightCurrentRow"
:row-class-name="rowClassName" :row-class-name="rowClassName"
:row-style="rowStyle" :row-style="rowStyle"
:style="{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }"> :style="{
width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
}">
</table-body> </table-body>
<div class="el-table__append-gutter" :style="{ height: layout.appendHeight + 'px' }" v-if="$slots.append"></div> <div
v-if="$slots.append"
class="el-table__append-gutter"
:style="{
height: layout.appendHeight + 'px'
}"></div>
</div> </div>
<div class="el-table__fixed-footer-wrapper" ref="fixedFooterWrapper" v-if="showSummary" v-show="data && data.length > 0"> <div
v-if="showSummary"
v-show="data && data.length > 0"
class="el-table__fixed-footer-wrapper"
ref="fixedFooterWrapper">
<table-footer <table-footer
fixed="left" fixed="left"
:border="border" :border="border"
:sum-text="sumText || t('el.table.sumText')" :sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod" :summary-method="summaryMethod"
:store="store" :store="store"
:layout="layout" :style="{
:style="{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }"></table-footer> width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
}"></table-footer>
</div> </div>
</div> </div>
<div class="el-table__fixed-right" ref="rightFixedWrapper" <div
v-if="rightFixedColumns.length > 0" v-if="rightFixedColumns.length > 0"
v-mousewheel="handleFixedMousewheel" v-mousewheel="handleFixedMousewheel"
:style="[ class="el-table__fixed-right"
{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }, ref="rightFixedWrapper"
{ right: layout.scrollY ? (border ? layout.gutterWidth : (layout.gutterWidth || 0)) + 'px' : '' }, :style="[{
fixedHeight width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '',
]"> right: layout.scrollY ? (border ? layout.gutterWidth : (layout.gutterWidth || 0)) + 'px' : ''
<div class="el-table__fixed-header-wrapper" ref="rightFixedHeaderWrapper" v-if="showHeader"> },
fixedHeight]">
<div v-if="showHeader"
class="el-table__fixed-header-wrapper"
ref="rightFixedHeaderWrapper">
<table-header <table-header
ref="rightFixedTableHeader" ref="rightFixedTableHeader"
fixed="right" fixed="right"
:border="border" :border="border"
:store="store" :store="store"
:layout="layout" :style="{
:style="{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }"></table-header> width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : ''
}"></table-header>
</div> </div>
<div class="el-table__fixed-body-wrapper" ref="rightFixedBodyWrapper" <div
:style="[ class="el-table__fixed-body-wrapper"
{ top: layout.headerHeight + 'px' }, ref="rightFixedBodyWrapper"
fixedBodyHeight :style="[{
]"> top: layout.headerHeight + 'px'
},
fixedBodyHeight]">
<table-body <table-body
fixed="right" fixed="right"
:store="store" :store="store"
:stripe="stripe" :stripe="stripe"
:layout="layout"
:row-class-name="rowClassName" :row-class-name="rowClassName"
:row-style="rowStyle" :row-style="rowStyle"
:highlight="highlightCurrentRow" :highlight="highlightCurrentRow"
:style="{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }"> :style="{
width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : ''
}">
</table-body> </table-body>
</div> </div>
<div class="el-table__fixed-footer-wrapper" ref="rightFixedFooterWrapper" v-if="showSummary" v-show="data && data.length > 0"> <div
v-if="showSummary"
v-show="data && data.length > 0"
class="el-table__fixed-footer-wrapper"
ref="rightFixedFooterWrapper">
<table-footer <table-footer
fixed="right" fixed="right"
:border="border" :border="border"
:sum-text="sumText || t('el.table.sumText')" :sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod" :summary-method="summaryMethod"
:store="store" :store="store"
:layout="layout" :style="{
:style="{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }"></table-footer> width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : ''
}"></table-footer>
</div> </div>
</div> </div>
<div class="el-table__fixed-right-patch" <div
v-if="rightFixedColumns.length > 0" v-if="rightFixedColumns.length > 0"
:style="{ width: layout.scrollY ? layout.gutterWidth + 'px' : '0', height: layout.headerHeight + 'px' }"></div> class="el-table__fixed-right-patch"
ref="rightFixedPatch"
:style="{
width: layout.scrollY ? layout.gutterWidth + 'px' : '0',
height: layout.headerHeight + 'px'
}"></div>
<div class="el-table__column-resize-proxy" ref="resizeProxy" v-show="resizeProxyVisible"></div> <div class="el-table__column-resize-proxy" ref="resizeProxy" v-show="resizeProxyVisible"></div>
</div> </div>
</template> </template>
<script type="text/babel"> <script type="text/babel">
import ElCheckbox from 'element-ui/packages/checkbox'; import ElCheckbox from 'element-ui/packages/checkbox';
import throttle from 'throttle-debounce/throttle';
import debounce from 'throttle-debounce/debounce'; import debounce from 'throttle-debounce/debounce';
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'; import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
import Mousewheel from 'element-ui/src/directives/mousewheel'; import Mousewheel from 'element-ui/src/directives/mousewheel';
@ -346,32 +405,44 @@
}); });
if (this.fit) { if (this.fit) {
this.windowResizeListener = throttle(50, () => { addResizeListener(this.$el, this.resizeListener);
if (this.$ready) this.doLayout(); }
}); },
addResizeListener(this.$el, this.windowResizeListener);
resizeListener() {
if (!this.$ready) return;
let shouldUpdateLayout = false;
const el = this.$el;
const { width: oldWidth, height: oldHeight } = this.resizeState;
const width = el.offsetWidth;
if (oldWidth !== width) {
shouldUpdateLayout = true;
}
const height = el.offsetHeight;
if (this.height && oldHeight !== height) {
shouldUpdateLayout = true;
}
if (shouldUpdateLayout) {
this.resizeState.width = width;
this.resizeState.height = height;
this.doLayout();
} }
}, },
doLayout() { doLayout() {
this.store.updateColumns(); if (this.shouldUpdateHeight) {
this.updateScrollY(); this.layout.updateElsHeight();
this.layout.update(); }
this.$nextTick(() => { this.layout.updateColumnsWidth();
if (this.height) {
this.layout.setHeight(this.height);
} else if (this.maxHeight) {
this.layout.setMaxHeight(this.maxHeight);
} else if (this.shouldUpdateHeight) {
this.layout.updateHeight();
}
});
} }
}, },
created() { created() {
this.tableId = 'el-table_' + tableIdSeed + '_'; this.tableId = 'el-table_' + tableIdSeed++;
this.debouncedLayout = debounce(50, () => this.doLayout()); this.debouncedUpdateLayout = debounce(50, () => this.doLayout());
}, },
computed: { computed: {
@ -384,7 +455,7 @@
}, },
shouldUpdateHeight() { shouldUpdateHeight() {
return typeof this.height === 'number' || return this.height ||
this.fixedColumns.length > 0 || this.fixedColumns.length > 0 ||
this.rightFixedColumns.length > 0; this.rightFixedColumns.length > 0;
}, },
@ -409,34 +480,29 @@
return this.store.states.rightFixedColumns; return this.store.states.rightFixedColumns;
}, },
bodyHeight() {
let style = {};
if (this.height) {
style = {
height: this.layout.bodyHeight ? this.layout.bodyHeight + 'px' : ''
};
} else if (this.maxHeight) {
style = {
'max-height': (this.showHeader
? this.maxHeight - this.layout.headerHeight - this.layout.footerHeight
: this.maxHeight - this.layout.footerHeight) + 'px'
};
}
return style;
},
bodyWidth() { bodyWidth() {
const { bodyWidth, scrollY, gutterWidth } = this.layout; const { bodyWidth, scrollY, gutterWidth } = this.layout;
return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : ''; return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : '';
}, },
fixedBodyHeight() { bodyHeight() {
let style = {};
if (this.height) { if (this.height) {
style = { return {
height: this.layout.bodyHeight ? this.layout.bodyHeight + 'px' : ''
};
} else if (this.maxHeight) {
return {
'max-height': (this.showHeader
? this.maxHeight - this.layout.headerHeight - this.layout.footerHeight
: this.maxHeight - this.layout.footerHeight) + 'px'
};
}
return {};
},
fixedBodyHeight() {
if (this.height) {
return {
height: this.layout.fixedBodyHeight ? this.layout.fixedBodyHeight + 'px' : '' height: this.layout.fixedBodyHeight ? this.layout.fixedBodyHeight + 'px' : ''
}; };
} else if (this.maxHeight) { } else if (this.maxHeight) {
@ -448,38 +514,40 @@
maxHeight -= this.layout.footerHeight; maxHeight -= this.layout.footerHeight;
style = { return {
'max-height': maxHeight + 'px' 'max-height': maxHeight + 'px'
}; };
} }
return style; return {};
}, },
fixedHeight() { fixedHeight() {
let style = {};
if (this.maxHeight) { if (this.maxHeight) {
style = { return {
bottom: (this.layout.scrollX && this.data.length) ? this.layout.gutterWidth + 'px' : '' bottom: (this.layout.scrollX && this.data.length) ? this.layout.gutterWidth + 'px' : ''
}; };
} else { } else {
style = { return {
height: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : '' height: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : ''
}; };
} }
return style;
} }
}, },
watch: { watch: {
height(value) { height: {
this.layout.setHeight(value); immediate: true,
handler(value) {
this.layout.setHeight(value);
}
}, },
maxHeight(value) { maxHeight: {
this.layout.setMaxHeight(value); immediate: true,
handler(value) {
this.layout.setMaxHeight(value);
}
}, },
currentRowKey(newVal) { currentRowKey(newVal) {
@ -488,8 +556,8 @@
data: { data: {
immediate: true, immediate: true,
handler(val) { handler(value) {
this.store.commit('setData', val); this.store.commit('setData', value);
if (this.$ready) { if (this.$ready) {
this.$nextTick(() => { this.$nextTick(() => {
this.doLayout(); this.doLayout();
@ -509,13 +577,19 @@
}, },
destroyed() { destroyed() {
if (this.windowResizeListener) removeResizeListener(this.$el, this.windowResizeListener); if (this.resizeListener) removeResizeListener(this.$el, this.resizeListener);
}, },
mounted() { mounted() {
this.bindEvents(); this.bindEvents();
this.store.updateColumns();
this.doLayout(); this.doLayout();
this.resizeState = {
width: this.$el.offsetWidth,
height: this.$el.offsetHeight
};
// init filters // init filters
this.store.states.columns.forEach(column => { this.store.states.columns.forEach(column => {
if (column.filteredValue && column.filteredValue.length) { if (column.filteredValue && column.filteredValue.length) {
@ -542,11 +616,15 @@
showHeader: this.showHeader showHeader: this.showHeader
}); });
return { return {
store,
layout, layout,
store,
isHidden: false, isHidden: false,
renderExpanded: null, renderExpanded: null,
resizeProxyVisible: false, resizeProxyVisible: false,
resizeState: {
width: null,
height: null
},
// //
isGroup: false, isGroup: false,
scrollPosition: 'left' scrollPosition: 'left'

View File

@ -83,6 +83,18 @@
} }
} }
@include m(scrollable-x) {
.el-table__body-wrapper {
overflow-x: auto;
}
}
@include m(scrollable-y) {
.el-table__body-wrapper {
overflow-y: auto;
}
}
thead { thead {
color: $--table-header-color; color: $--table-header-color;
font-weight: 500; font-weight: 500;
@ -296,6 +308,7 @@
top: 0; top: 0;
left: 0; left: 0;
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden;
box-shadow: $--table-fixed-box-shadow; box-shadow: $--table-fixed-box-shadow;
&::before { &::before {
@ -372,6 +385,7 @@
@include e((header, body, footer)) { @include e((header, body, footer)) {
table-layout: fixed; table-layout: fixed;
border-collapse: separate;
} }
@include e((header-wrapper, footer-wrapper)) { @include e((header-wrapper, footer-wrapper)) {
@ -384,36 +398,36 @@
} }
@include e(body-wrapper) { @include e(body-wrapper) {
overflow: auto; overflow: hidden;
position: relative; position: relative;
@include when(scroll-none) { @include when(scrolling-none) {
~ .el-table__fixed, ~ .el-table__fixed,
~ .el-table__fixed-right { ~ .el-table__fixed-right {
box-shadow: none; box-shadow: none;
} }
} }
@include when(scroll-left) { @include when(scrolling-left) {
~ .el-table__fixed { ~ .el-table__fixed {
box-shadow: none; box-shadow: none;
} }
} }
@include when(scroll-right) { @include when(scrolling-right) {
~ .el-table__fixed-right { ~ .el-table__fixed-right {
box-shadow: none; box-shadow: none;
} }
} }
.el-table--border { .el-table--border {
@include when(scroll-right) { @include when(scrolling-right) {
~ .el-table__fixed-right { ~ .el-table__fixed-right {
border-left: $--table-border; border-left: $--table-border;
} }
} }
@include when(scroll-left) { @include when(scrolling-left) {
~ .el-table__fixed { ~ .el-table__fixed {
border-right: $--table-border; border-right: $--table-border;
} }