說明
table-header
是表頭組件届氢,較為復(fù)雜,直接看源碼解讀徐勃。
源碼解讀
import ElCheckbox from 'element-ui/packages/checkbox';
import ElTag from 'element-ui/packages/tag';
import Vue from 'vue';
import FilterPanel from './filter-panel.vue';
/**
* 獲取所有的列赶袄,因?yàn)闀星短姿允褂昧诉f歸
* @param columns 原始的所有列
* @return 所有列
*/
const getAllColumns = (columns) => {
const result = [];
columns.forEach((column) => {
if (column.children) {
result.push(column);
result.push.apply(result, getAllColumns(column.children));
} else {
result.push(column);
}
});
return result;
};
/**
* 將所有的列信息轉(zhuǎn)換成表頭的行信息
* @param originColumns 列信息
* @return 表頭信息
*/
const convertToRows = (originColumns) => {
// 最大層級
let maxLevel = 1;
// 遍歷列來判斷表頭每個單元格需要占多少格
const traverse = (column, parent) => {
if (parent) {
column.level = parent.level + 1;
if (maxLevel < column.level) {
maxLevel = column.level;
}
}
if (column.children) {
let colSpan = 0;
column.children.forEach((subColumn) => {
traverse(subColumn, column);
colSpan += subColumn.colSpan;
});
column.colSpan = colSpan;
} else {
column.colSpan = 1;
}
};
// 獲取每一列的層級
originColumns.forEach((column) => {
column.level = 1;
traverse(column);
});
const rows = [];
for (let i = 0; i < maxLevel; i++) {
rows.push([]);
}
const allColumns = getAllColumns(originColumns);
// 相同的層級作為同一行
allColumns.forEach((column) => {
if (!column.children) {
column.rowSpan = maxLevel - column.level + 1;
} else {
column.rowSpan = 1;
}
rows[column.level - 1].push(column);
});
return rows;
};
export default {
name: 'ElTableHeader',
// 渲染函數(shù)
render(h) {
// 原始列信息
const originColumns = this.store.states.originColumns;
// 表頭信息
const columnRows = convertToRows(originColumns, this.columns);
return (
// table 上的 style 是為了清除默認(rèn)的間距
<table
class="el-table__header"
cellspacing="0"
cellpadding="0"
border="0">
{/* colgroup 是用來存儲列信息的 */}
<colgroup>
{/* 列信息 */}
{
this._l(this.columns, column =>
<col
name={ column.id }
width={ column.realWidth || column.width }
/>)
}
{/* 如果左側(cè)有固定還需要記錄滾動條的寬度 */}
{
!this.fixed && this.layout.gutterWidth
? <col name="gutter" width={ this.layout.scrollY ? this.layout.gutterWidth : '' }></col>
: ''
}
</colgroup>
<thead>
{
// 按行渲染
this._l(columnRows, (columns, rowIndex) =>
<tr>
{
// 渲染每個單元格
this._l(columns, (column, cellIndex) =>
<th
// 列高
colspan={ column.colSpan }
// 行寬
rowspan={ column.rowSpan }
// 鼠標(biāo)移動事件
on-mousemove={ ($event) => this.handleMouseMove($event, column) }
// 鼠標(biāo)移出事件
on-mouseout={ this.handleMouseOut }
// 鼠標(biāo)按下事件
on-mousedown={ ($event) => this.handleMouseDown($event, column) }
// 鼠標(biāo)單擊事件
on-click={ ($event) => this.handleHeaderClick($event, column) }
class={
[
column.id,
column.order,
column.headerAlign,
column.className || '',
// 判斷是否隱藏,為了處理 fixed
rowIndex === 0 && this.isCellHidden(cellIndex, columns) ? 'is-hidden' : '',
// 判斷是不是最后一級
!column.children ? 'is-leaf' : ''
]
}>
<div
class={
[
'cell',
// 如果這列有選擇篩選條件就高亮
column.filteredValue && column.filteredValue.length > 0 ? 'highlight' : ''
]
}>
{
// 渲染單元格內(nèi)部的內(nèi)容
column.renderHeader
? column.renderHeader.call(
this._renderProxy,
h,
{
column,
$index: cellIndex,
store: this.store,
_self: this.$parent.$vnode.context
})
: column.label
}
{
// 渲染排序的標(biāo)志
column.sortable
? <span class="caret-wrapper" on-click={ ($event) => this.handleSortClick($event, column) }>
<i class="sort-caret ascending"></i>
<i class="sort-caret descending"></i>
</span>
: ''
}
{
// 渲染篩選器的箭頭
column.filterable
? <span
class="el-table__column-filter-trigger"
on-click={ ($event) => this.handleFilterClick($event, column) }>
<i
class={
[
'el-icon-arrow-down',
column.filterOpened ? 'el-icon-arrow-up' : ''
]
}>
</i>
</span>
: ''
}
</div>
</th>
)
}
{
// 彌補(bǔ)滾動條的寬度
!this.fixed && this.layout.gutterWidth
? <th class="gutter" style={{ width: this.layout.scrollY ? this.layout.gutterWidth + 'px' : '0' }}></th>
: ''
}
</tr>
)
}
</thead>
</table>
);
},
props: {
fixed: String,
store: {
required: true
},
layout: {
required: true
},
border: Boolean,
defaultSort: {
type: Object,
default() {
return {
prop: '',
order: ''
};
}
}
},
components: {
ElCheckbox,
ElTag
},
computed: {
// 判斷是不是全選了
isAllSelected() {
return this.store.states.isAllSelected;
},
// 判斷總的列數(shù)
columnsCount() {
return this.store.states.columns.length;
},
// 左側(cè)固定列數(shù)
leftFixedCount() {
return this.store.states.fixedColumns.length;
},
// 右側(cè)固定列數(shù)
rightFixedCount() {
return this.store.states.rightFixedColumns.length;
},
// 所有的列
columns() {
return this.store.states.columns;
}
},
created() {
this.filterPanels = {};
},
mounted() {
if (this.defaultSort.prop) {
const states = this.store.states;
// 排序的屬性
states.sortProp = this.defaultSort.prop;
// 升序或降序
states.sortOrder = this.defaultSort.order || 'ascending';
this.$nextTick(_ => {
for (let i = 0, length = this.columns.length; i < length; i++) {
let column = this.columns[i];
// 如果是要排序的屬性
if (column.property === states.sortProp) {
column.order = states.sortOrder;
states.sortingColumn = column;
break;
}
}
if (states.sortingColumn) {
this.store.commit('changeSortCondition');
}
});
}
},
beforeDestroy() {
const panels = this.filterPanels;
for (let prop in panels) {
// 銷毀全部的篩選面板
if (panels.hasOwnProperty(prop) && panels[prop]) {
panels[prop].$destroy(true);
}
}
},
methods: {
// 判斷單元格是否應(yīng)當(dāng)隱藏
isCellHidden(index, columns) {
// 左側(cè)固定的 wrapper 中鞋仍,那么除了固定的這些列都應(yīng)該隱藏掉
if (this.fixed === true || this.fixed === 'left') {
return index >= this.leftFixedCount;
} else if (this.fixed === 'right') { // 右側(cè)固定的 wrapper 中,規(guī)定列之前的也都應(yīng)當(dāng)隱藏
let before = 0;
for (let i = 0; i < index; i++) {
before += columns[i].colSpan;
}
return before < this.columnsCount - this.rightFixedCount;
} else { // 剩下的就是隱藏固定的列
return (index < this.leftFixedCount) || (index >= this.columnsCount - this.rightFixedCount);
}
},
// 切換全選
toggleAllSelection() {
this.store.commit('toggleAllSelection');
},
// 處理點(diǎn)擊篩選器,應(yīng)當(dāng)顯示對應(yīng)的篩選面板
handleFilterClick(event, column) {
event.stopPropagation();
const target = event.target;
const cell = target.parentNode;
const table = this.$parent;
// 查找對應(yīng)的篩選面板
let filterPanel = this.filterPanels[column.id];
// 如果存在钠龙,并且打開了,就關(guān)閉它
if (filterPanel && column.filterOpened) {
filterPanel.showPopper = false;
return;
}
// 如果不存在御铃,就創(chuàng)建它
if (!filterPanel) {
filterPanel = new Vue(FilterPanel);
this.filterPanels[column.id] = filterPanel;
filterPanel.table = table;
filterPanel.cell = cell;
filterPanel.column = column;
!this.$isServer && filterPanel.$mount(document.createElement('div'));
}
// 創(chuàng)建后打開
setTimeout(() => {
filterPanel.showPopper = true;
}, 16);
},
// 處理表頭點(diǎn)擊事件
handleHeaderClick(event, column) {
if (!column.filters && column.sortable) { // 排序
this.handleSortClick(event, column);
} else if (column.filters && !column.sortable) { // 篩選
this.handleFilterClick(event, column);
}
this.$parent.$emit('header-click', column, event);
},
// 鼠標(biāo)按下
handleMouseDown(event, column) {
if (this.$isServer) return;
// 如果這一列還有孩子直接返回碴里,應(yīng)該操作孩子的寬度
if (column.children && column.children.length > 0) return;
/* istanbul ignore if */
// 如果有拖拽的列,并且有邊框
if (this.draggingColumn && this.border) {
// 表示正在拖動
this.dragging = true;
// 顯示 resize
this.$parent.resizeProxyVisible = true;
const tableEl = this.$parent.$el;
const tableLeft = tableEl.getBoundingClientRect().left;
// 拖動列
const columnEl = this.$el.querySelector(`th.${column.id}`);
const columnRect = columnEl.getBoundingClientRect();
const minLeft = columnRect.left - tableLeft + 30;
columnEl.classList.add('noclick');
this.dragState = {
startMouseLeft: event.clientX, // 鼠標(biāo)開始位置
startLeft: columnRect.right - tableLeft, // 開始位置距離表格最左邊的距離
startColumnLeft: columnRect.left - tableLeft, // 開始時列左邊離表格最左邊的距離
tableLeft
};
// 顯示拖拽位置的線
const resizeProxy = this.$parent.$refs.resizeProxy;
resizeProxy.style.left = this.dragState.startLeft + 'px';
document.onselectstart = function() { return false; };
document.ondragstart = function() { return false; };
// 鼠標(biāo)移動的時候上真,計(jì)算移動距離并且移動輔助線
const handleMouseMove = (event) => {
const deltaLeft = event.clientX - this.dragState.startMouseLeft;
const proxyLeft = this.dragState.startLeft + deltaLeft;
resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px';
};
// 鼠標(biāo)抬起
const handleMouseUp = () => {
if (this.dragging) {
const finalLeft = parseInt(resizeProxy.style.left, 10); // 最終停止的位置
const columnWidth = finalLeft - this.dragState.startColumnLeft; // 應(yīng)該變成的列寬
column.width = column.realWidth = columnWidth; // 應(yīng)用列寬改變
this.store.scheduleLayout(); // 重新更新布局
document.body.style.cursor = '';
this.dragging = false;
this.draggingColumn = null;
this.dragState = {};
this.$parent.resizeProxyVisible = false;
}
// 移除相應(yīng)的監(jiān)聽器
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.onselectstart = null;
document.ondragstart = null;
setTimeout(function() {
columnEl.classList.remove('noclick');
}, 0);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
},
// 鼠標(biāo)移動事件
handleMouseMove(event, column) {
if (column.children && column.children.length > 0) return;
let target = event.target;
while (target && target.tagName !== 'TH') { // 尋找 th 標(biāo)簽
target = target.parentNode;
}
// 如果沒有列咬腋,或者不能改變大小
if (!column || !column.resizable) return;
// 如果正在拖動并且有邊框
if (!this.dragging && this.border) {
let rect = target.getBoundingClientRect();
const bodyStyle = document.body.style;
if (rect.width > 12 && rect.right - event.pageX < 8) {
bodyStyle.cursor = 'col-resize';
this.draggingColumn = column;
} else if (!this.dragging) {
bodyStyle.cursor = '';
this.draggingColumn = null;
}
}
},
// 鼠標(biāo)移除后
handleMouseOut() {
if (this.$isServer) return;
document.body.style.cursor = '';
},
// 切換排序順序
toggleOrder(order) {
return !order ? 'ascending' : order === 'ascending' ? 'descending' : null;
},
// 點(diǎn)擊排序
handleSortClick(event, column) {
event.stopPropagation();
// 切換排序順序
let order = this.toggleOrder(column.order);
// 尋找 TH
let target = event.target;
while (target && target.tagName !== 'TH') {
target = target.parentNode;
}
// 如果這時候有 `noclick` 類,就移除
if (target && target.tagName === 'TH') {
if (target.classList.contains('noclick')) {
target.classList.remove('noclick');
return;
}
}
// 如果不能排序就直接返回
if (!column.sortable) return;
const states = this.store.states;
let sortProp = states.sortProp;
let sortOrder;
const sortingColumn = states.sortingColumn;
// 如果排序列不是當(dāng)前列睡互,就切換成當(dāng)前列
if (sortingColumn !== column) {
if (sortingColumn) {
sortingColumn.order = null;
}
states.sortingColumn = column;
sortProp = column.property;
}
// 如果沒有順序
if (!order) {
sortOrder = column.order = null;
states.sortingColumn = null;
sortProp = null;
} else {
sortOrder = column.order = order;
}
states.sortProp = sortProp;
states.sortOrder = sortOrder;
this.store.commit('changeSortCondition');
}
},
data() {
return {
draggingColumn: null,
dragging: false,
dragState: {}
};
}
};