123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- 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: {}
- };
- }
- };
|