说明
table.vue
是最终将各个组件拼接起来的地方,较为简单,因此不进行赘述,请确保看过一下几个分解的部分:
- Element分析(工具篇)——Table
- Element分析(工具篇)——TableStore
- Element分析(工具篇)——TableLayout
- Element分析(组件篇)——FilterPanel
- Element分析(组件篇)——TableHeader
- Element分析(组件篇)——TableColumn
- Element分析(组件篇)——TableBody
源码解读
首先是 template
部分。
<template>
<div class="el-table"
:class="{
'el-table--fit': fit,
'el-table--striped': stripe,
'el-table--border': border,
'el-table--fluid-height': maxHeight,
'el-table--enable-row-hover': !store.states.isComplex,
'el-table--enable-row-transition': true || (store.states.data || []).length !== 0 && (store.states.data || []).length < 100
}"
@mouseleave="handleMouseLeave($event)">
<!-- 容纳 table 内容 -->
<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
<!-- 表头 -->
<div class="el-table__header-wrapper" ref="headerWrapper" v-if="showHeader">
<table-header
:store="store"
:layout="layout"
:border="border"
:default-sort="defaultSort"
:style="{ width: layout.bodyWidth ? layout.bodyWidth + 'px' : '' }">
</table-header>
</div>
<!-- 表格主体,非固定列 -->
<div
class="el-table__body-wrapper"
ref="bodyWrapper"
:style="[bodyHeight]">
<table-body
:context="context"
:store="store"
:layout="layout"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{ width: bodyWidth }">
</table-body>
<div
:style="{ width: bodyWidth }"
class="el-table__empty-block"
v-if="!data || data.length === 0">
<span class="el-table__empty-text">
<slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot>
</span>
</div>
</div>
<!-- 左侧固定 -->
<div class="el-table__fixed" ref="fixedWrapper"
v-if="fixedColumns.length > 0"
:style="[
{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' },
fixedHeight
]">
<div class="el-table__fixed-header-wrapper" ref="fixedHeaderWrapper" v-if="showHeader">
<table-header
fixed="left"
:border="border"
:store="store"
:layout="layout"
:style="{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }"></table-header>
</div>
<div class="el-table__fixed-body-wrapper" ref="fixedBodyWrapper"
:style="[
{ top: layout.headerHeight + 'px' },
fixedBodyHeight
]">
<table-body
fixed="left"
:store="store"
:layout="layout"
:highlight="highlightCurrentRow"
:row-class-name="rowClassName"
:row-style="rowStyle"
:style="{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }">
</table-body>
</div>
</div>
<!-- 右侧固定 -->
<div class="el-table__fixed-right" ref="rightFixedWrapper"
v-if="rightFixedColumns.length > 0"
:style="[
{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' },
{ right: layout.scrollY ? (border ? layout.gutterWidth : (layout.gutterWidth || 1)) + 'px' : '' },
fixedHeight
]">
<div class="el-table__fixed-header-wrapper" ref="rightFixedHeaderWrapper" v-if="showHeader">
<table-header
fixed="right"
:border="border"
:store="store"
:layout="layout"
:style="{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }"></table-header>
</div>
<div class="el-table__fixed-body-wrapper" ref="rightFixedBodyWrapper"
:style="[
{ top: layout.headerHeight + 'px' },
fixedBodyHeight
]">
<table-body
fixed="right"
:store="store"
:layout="layout"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }">
</table-body>
</div>
</div>
<!-- 用来弥补滚动条 -->
<div
class="el-table__fixed-right-patch"
v-if="rightFixedColumns.length > 0"
:style="{
width: layout.scrollY ? layout.gutterWidth + 'px' : '0',
height: layout.headerHeight + 'px'
}">
</div>
<!-- 用来监听 resize -->
<div
class="el-table__column-resize-proxy"
ref="resizeProxy"
v-show="resizeProxyVisible">
</div>
</div>
</template>
其次是 script
的部分:
import ElCheckbox from 'element-ui/packages/checkbox';
import throttle from 'throttle-debounce/throttle';
import debounce from 'throttle-debounce/debounce';
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
import Locale from 'element-ui/src/mixins/locale';
import TableStore from './table-store';
import TableLayout from './table-layout';
import TableBody from './table-body';
import TableHeader from './table-header';
import { mousewheel } from './util';
let tableIdSeed = 1;
export default {
name: 'ElTable',
mixins: [Locale],
props: {
data: {
type: Array,
default: function() {
return [];
}
},
width: [String, Number],
height: [String, Number],
maxHeight: [String, Number],
fit: {
type: Boolean,
default: true
},
stripe: Boolean,
border: Boolean,
rowKey: [String, Function],
context: {},
showHeader: {
type: Boolean,
default: true
},
rowClassName: [String, Function],
rowStyle: [Object, Function],
highlightCurrentRow: Boolean,
currentRowKey: [String, Number],
emptyText: String,
expandRowKeys: Array,
defaultExpandAll: Boolean,
defaultSort: Object
},
components: {
TableHeader,
TableBody,
ElCheckbox
},
methods: {
// 切换选中的行
toggleRowSelection(row, selected) {
this.store.toggleRowSelection(row, selected);
this.store.updateAllSelected();
},
// 清空选中行
clearSelection() {
this.store.clearSelection();
},
// 处理鼠标离开
handleMouseLeave() {
this.store.commit('setHoverRow', null);
if (this.hoverState) this.hoverState = null;
},
// 更新是否有纵向滚动
updateScrollY() {
this.layout.updateScrollY();
},
// 绑定相关的时间监听
bindEvents() {
const { headerWrapper } = this.$refs;
const refs = this.$refs;
// 同步三个 body 的滚动
this.bodyWrapper.addEventListener('scroll', function() {
if (headerWrapper) headerWrapper.scrollLeft = this.scrollLeft;
if (refs.fixedBodyWrapper) refs.fixedBodyWrapper.scrollTop = this.scrollTop;
if (refs.rightFixedBodyWrapper) refs.rightFixedBodyWrapper.scrollTop = this.scrollTop;
});
// 表头的鼠标滚动变成横向滚动
if (headerWrapper) {
mousewheel(headerWrapper, throttle(16, event => {
const deltaX = event.deltaX;
if (deltaX > 0) {
this.bodyWrapper.scrollLeft += 10;
} else {
this.bodyWrapper.scrollLeft -= 10;
}
}));
}
// 如果自适应的话,resize 的时候重新布局
if (this.fit) {
this.windowResizeListener = throttle(50, () => {
if (this.$ready) this.doLayout();
});
addResizeListener(this.$el, this.windowResizeListener);
}
},
// 布局
doLayout() {
this.store.updateColumns();
this.layout.update();
this.updateScrollY();
this.$nextTick(() => {
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() {
this.tableId = 'el-table_' + tableIdSeed + '_';
this.debouncedLayout = debounce(50, () => this.doLayout());
},
computed: {
bodyWrapper() {
return this.$refs.bodyWrapper;
},
shouldUpdateHeight() {
return typeof this.height === 'number' ||
this.fixedColumns.length > 0 ||
this.rightFixedColumns.length > 0;
},
selection() {
return this.store.selection;
},
columns() {
return this.store.states.columns;
},
tableData() {
return this.store.states.data;
},
fixedColumns() {
return this.store.states.fixedColumns;
},
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.maxHeight) + 'px'
};
}
return style;
},
bodyWidth() {
const { bodyWidth, scrollY, gutterWidth } = this.layout;
return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : '';
},
fixedBodyHeight() {
let style = {};
if (this.height) {
style = {
height: this.layout.fixedBodyHeight ? this.layout.fixedBodyHeight + 'px' : ''
};
} else if (this.maxHeight) {
let maxHeight = this.layout.scrollX ? this.maxHeight - this.layout.gutterWidth : this.maxHeight;
if (this.showHeader) {
maxHeight -= this.layout.headerHeight;
}
style = {
'max-height': maxHeight + 'px'
};
}
return style;
},
fixedHeight() {
let style = {};
if (this.maxHeight) {
style = {
bottom: (this.layout.scrollX && this.data.length) ? this.layout.gutterWidth + 'px' : ''
};
} else {
style = {
height: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : ''
};
}
return style;
}
},
watch: {
// 高度改变时,重新设定高度
height(value) {
this.layout.setHeight(value);
},
// 当前行的 key 改变时,重新设置
currentRowKey(newVal) {
this.store.setCurrentRowKey(newVal);
},
// 更新数据
data: {
immediate: true,
handler(val) {
this.store.commit('setData', val);
}
},
// 更改打开的行的 key
expandRowKeys(newVal) {
this.store.setExpandRowKeys(newVal);
}
},
destroyed() {
// 移除 resize 监听
if (this.windowResizeListener) removeResizeListener(this.$el, this.windowResizeListener);
},
mounted() {
this.bindEvents();
this.doLayout();
this.$ready = true;
},
data() {
// 状态管理
const store = new TableStore(this, {
rowKey: this.rowKey,
defaultExpandAll: this.defaultExpandAll
});
// 布局管理
const layout = new TableLayout({
store,
table: this,
fit: this.fit,
showHeader: this.showHeader
});
return {
store,
layout,
renderExpanded: null,
resizeProxyVisible: false
};
}
};