UNPKG

element-ui

Version:

A Component Library for Vue.js.

511 lines (439 loc) 15.5 kB
import Vue from 'vue'; import { hasClass, addClass, removeClass } from 'element-ui/src/utils/dom'; import ElCheckbox from 'element-ui/packages/checkbox'; import FilterPanel from './filter-panel.vue'; import LayoutObserver from './layout-observer'; import { mapStates } from './store/helper'; 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; }; 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', mixins: [LayoutObserver], render(h) { const originColumns = this.store.states.originColumns; const columnRows = convertToRows(originColumns, this.columns); // 是否拥有多级表头 const isGroup = columnRows.length > 1; if (isGroup) this.$parent.isGroup = true; return ( <table class="el-table__header" cellspacing="0" cellpadding="0" border="0"> <colgroup> { this.columns.map(column => <col name={ column.id } key={column.id} />) } { this.hasGutter ? <col name="gutter" /> : '' } </colgroup> <thead class={ [{ 'is-group': isGroup, 'has-gutter': this.hasGutter }] }> { this._l(columnRows, (columns, rowIndex) => <tr style={ this.getHeaderRowStyle(rowIndex) } class={ this.getHeaderRowClass(rowIndex) } > { columns.map((column, cellIndex) => (<th colspan={ column.colSpan } rowspan={ column.rowSpan } on-mousemove={ ($event) => this.handleMouseMove($event, column) } on-mouseout={ this.handleMouseOut } on-mousedown={ ($event) => this.handleMouseDown($event, column) } on-click={ ($event) => this.handleHeaderClick($event, column) } on-contextmenu={ ($event) => this.handleHeaderContextMenu($event, column) } style={ this.getHeaderCellStyle(rowIndex, cellIndex, columns, column) } class={ this.getHeaderCellClass(rowIndex, cellIndex, columns, column) } key={ column.id }> <div class={ ['cell', column.filteredValue && column.filteredValue.length > 0 ? 'highlight' : '', column.labelClassName] }> { column.renderHeader ? column.renderHeader.call(this._renderProxy, h, { column, $index: cellIndex, store: this.store, _self: this.$parent.$vnode.context }) : column.label } { column.sortable ? (<span class="caret-wrapper" on-click={ ($event) => this.handleSortClick($event, column) }> <i class="sort-caret ascending" on-click={ ($event) => this.handleSortClick($event, column, 'ascending') }> </i> <i class="sort-caret descending" on-click={ ($event) => this.handleSortClick($event, column, '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>)) } { this.hasGutter ? <th class="gutter"></th> : '' } </tr> ) } </thead> </table> ); }, props: { fixed: String, store: { required: true }, border: Boolean, defaultSort: { type: Object, default() { return { prop: '', order: '' }; } } }, components: { ElCheckbox }, computed: { table() { return this.$parent; }, hasGutter() { return !this.fixed && this.tableLayout.gutterWidth; }, ...mapStates({ columns: 'columns', isAllSelected: 'isAllSelected', leftFixedLeafCount: 'fixedLeafColumnsLength', rightFixedLeafCount: 'rightFixedLeafColumnsLength', columnsCount: states => states.columns.length, leftFixedCount: states => states.fixedColumns.length, rightFixedCount: states => states.rightFixedColumns.length }) }, created() { this.filterPanels = {}; }, mounted() { // nextTick 是有必要的 https://github.com/ElemeFE/element/pull/11311 this.$nextTick(() => { const { prop, order } = this.defaultSort; const init = true; this.store.commit('sort', { prop, order, init }); }); }, beforeDestroy() { const panels = this.filterPanels; for (let prop in panels) { if (panels.hasOwnProperty(prop) && panels[prop]) { panels[prop].$destroy(true); } } }, methods: { isCellHidden(index, columns) { let start = 0; for (let i = 0; i < index; i++) { start += columns[i].colSpan; } const after = start + columns[index].colSpan - 1; if (this.fixed === true || this.fixed === 'left') { return after >= this.leftFixedLeafCount; } else if (this.fixed === 'right') { return start < this.columnsCount - this.rightFixedLeafCount; } else { return (after < this.leftFixedLeafCount) || (start >= this.columnsCount - this.rightFixedLeafCount); } }, getHeaderRowStyle(rowIndex) { const headerRowStyle = this.table.headerRowStyle; if (typeof headerRowStyle === 'function') { return headerRowStyle.call(null, { rowIndex }); } return headerRowStyle; }, getHeaderRowClass(rowIndex) { const classes = []; const headerRowClassName = this.table.headerRowClassName; if (typeof headerRowClassName === 'string') { classes.push(headerRowClassName); } else if (typeof headerRowClassName === 'function') { classes.push(headerRowClassName.call(null, { rowIndex })); } return classes.join(' '); }, getHeaderCellStyle(rowIndex, columnIndex, row, column) { const headerCellStyle = this.table.headerCellStyle; if (typeof headerCellStyle === 'function') { return headerCellStyle.call(null, { rowIndex, columnIndex, row, column }); } return headerCellStyle; }, getHeaderCellClass(rowIndex, columnIndex, row, column) { const classes = [column.id, column.order, column.headerAlign, column.className, column.labelClassName]; if (rowIndex === 0 && this.isCellHidden(columnIndex, row)) { classes.push('is-hidden'); } if (!column.children) { classes.push('is-leaf'); } if (column.sortable) { classes.push('is-sortable'); } const headerCellClassName = this.table.headerCellClassName; if (typeof headerCellClassName === 'string') { classes.push(headerCellClassName); } else if (typeof headerCellClassName === 'function') { classes.push(headerCellClassName.call(null, { rowIndex, columnIndex, row, column })); } return classes.join(' '); }, toggleAllSelection(event) { event.stopPropagation(); this.store.commit('toggleAllSelection'); }, handleFilterClick(event, column) { event.stopPropagation(); const target = event.target; let cell = target.tagName === 'TH' ? target : target.parentNode; if (hasClass(cell, 'noclick')) return; cell = cell.querySelector('.el-table__column-filter-trigger') || cell; const table = this.$parent; let filterPanel = this.filterPanels[column.id]; if (filterPanel && column.filterOpened) { filterPanel.showPopper = false; return; } if (!filterPanel) { filterPanel = new Vue(FilterPanel); this.filterPanels[column.id] = filterPanel; if (column.filterPlacement) { filterPanel.placement = column.filterPlacement; } filterPanel.table = table; filterPanel.cell = cell; filterPanel.column = column; !this.$isServer && filterPanel.$mount(document.createElement('div')); } setTimeout(() => { filterPanel.showPopper = true; }, 16); }, handleHeaderClick(event, column) { if (!column.filters && column.sortable) { this.handleSortClick(event, column); } else if (column.filterable && !column.sortable) { this.handleFilterClick(event, column); } this.$parent.$emit('header-click', column, event); }, handleHeaderContextMenu(event, column) { this.$parent.$emit('header-contextmenu', column, event); }, handleMouseDown(event, column) { if (this.$isServer) return; if (column.children && column.children.length > 0) return; /* istanbul ignore if */ if (this.draggingColumn && this.border) { this.dragging = true; this.$parent.resizeProxyVisible = true; const table = this.$parent; const tableEl = table.$el; const tableLeft = tableEl.getBoundingClientRect().left; const columnEl = this.$el.querySelector(`th.${column.id}`); const columnRect = columnEl.getBoundingClientRect(); const minLeft = columnRect.left - tableLeft + 30; addClass(columnEl, 'noclick'); this.dragState = { startMouseLeft: event.clientX, startLeft: columnRect.right - tableLeft, startColumnLeft: columnRect.left - tableLeft, tableLeft }; const resizeProxy = table.$refs.resizeProxy; resizeProxy.style.left = this.dragState.startLeft + 'px'; document.onselectstart = function() { return false; }; document.ondragstart = function() { return false; }; const handleMouseMove = (event) => { const deltaLeft = event.clientX - this.dragState.startMouseLeft; const proxyLeft = this.dragState.startLeft + deltaLeft; resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px'; }; const handleMouseUp = () => { if (this.dragging) { const { startColumnLeft, startLeft } = this.dragState; const finalLeft = parseInt(resizeProxy.style.left, 10); const columnWidth = finalLeft - startColumnLeft; column.width = column.realWidth = columnWidth; table.$emit('header-dragend', column.width, startLeft - startColumnLeft, column, event); this.store.scheduleLayout(); document.body.style.cursor = ''; this.dragging = false; this.draggingColumn = null; this.dragState = {}; table.resizeProxyVisible = false; } document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); document.onselectstart = null; document.ondragstart = null; setTimeout(function() { removeClass(columnEl, 'noclick'); }, 0); }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } }, handleMouseMove(event, column) { if (column.children && column.children.length > 0) return; let target = event.target; while (target && target.tagName !== 'TH') { 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'; if (hasClass(target, 'is-sortable')) { target.style.cursor = 'col-resize'; } this.draggingColumn = column; } else if (!this.dragging) { bodyStyle.cursor = ''; if (hasClass(target, 'is-sortable')) { target.style.cursor = 'pointer'; } this.draggingColumn = null; } } }, handleMouseOut() { if (this.$isServer) return; document.body.style.cursor = ''; }, toggleOrder({ order, sortOrders }) { if (order === '') return sortOrders[0]; const index = sortOrders.indexOf(order || null); return sortOrders[index > sortOrders.length - 2 ? 0 : index + 1]; }, handleSortClick(event, column, givenOrder) { event.stopPropagation(); let order = column.order === givenOrder ? null : (givenOrder || this.toggleOrder(column)); let target = event.target; while (target && target.tagName !== 'TH') { target = target.parentNode; } if (target && target.tagName === 'TH') { if (hasClass(target, 'noclick')) { removeClass(target, 'noclick'); return; } } if (!column.sortable) return; const states = this.store.states; let sortProp = states.sortProp; let sortOrder; const sortingColumn = states.sortingColumn; if (sortingColumn !== column || (sortingColumn === column && sortingColumn.order === null)) { if (sortingColumn) { sortingColumn.order = null; } states.sortingColumn = column; sortProp = column.property; } if (!order) { sortOrder = column.order = null; } else { sortOrder = column.order = order; } states.sortProp = sortProp; states.sortOrder = sortOrder; this.store.commit('changeSortCondition'); } }, data() { return { draggingColumn: null, dragging: false, dragState: {} }; } };