chushihua

This commit is contained in:
li
2026-01-26 23:20:48 +08:00
commit 7b5aab0206
22521 changed files with 3300090 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
import Vue from 'vue';
var dropdowns = [];
!Vue.prototype.$isServer && document.addEventListener('click', function(event) {
dropdowns.forEach(function(dropdown) {
var target = event.target;
if (!dropdown || !dropdown.$el) return;
if (target === dropdown.$el || dropdown.$el.contains(target)) {
return;
}
dropdown.handleOutsideClick && dropdown.handleOutsideClick(event);
});
});
export default {
open(instance) {
if (instance) {
dropdowns.push(instance);
}
},
close(instance) {
var index = dropdowns.indexOf(instance);
if (index !== -1) {
dropdowns.splice(instance, 1);
}
}
};
+203
View File
@@ -0,0 +1,203 @@
<template>
<transition name="el-zoom-in-top">
<div
class="el-table-filter"
v-if="multiple"
v-clickoutside="handleOutsideClick"
v-show="showPopper">
<div class="el-table-filter__content">
<el-scrollbar wrap-class="el-table-filter__wrap">
<el-checkbox-group class="el-table-filter__checkbox-group" v-model="filteredValue">
<el-checkbox
v-for="filter in filters"
:key="filter.value"
:label="filter.value">{{ filter.text }}</el-checkbox>
</el-checkbox-group>
</el-scrollbar>
</div>
<div class="el-table-filter__bottom">
<button @click="handleConfirm"
:class="{ 'is-disabled': filteredValue.length === 0 }"
:disabled="filteredValue.length === 0">{{ t('el.table.confirmFilter') }}</button>
<button @click="handleReset">{{ t('el.table.resetFilter') }}</button>
</div>
</div>
<div
class="el-table-filter"
v-else
v-clickoutside="handleOutsideClick"
v-show="showPopper">
<ul class="el-table-filter__list">
<li class="el-table-filter__list-item"
:class="{ 'is-active': filterValue === undefined || filterValue === null }"
@click="handleSelect(null)">{{ t('el.table.clearFilter') }}</li>
<li class="el-table-filter__list-item"
v-for="filter in filters"
:label="filter.value"
:key="filter.value"
:class="{ 'is-active': isActive(filter) }"
@click="handleSelect(filter.value)" >{{ filter.text }}</li>
</ul>
</div>
</transition>
</template>
<script type="text/babel">
import Popper from 'element-ui/src/utils/vue-popper';
import { PopupManager } from 'element-ui/src/utils/popup';
import Locale from 'element-ui/src/mixins/locale';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import Dropdown from './dropdown';
import ElCheckbox from 'element-ui/packages/checkbox';
import ElCheckboxGroup from 'element-ui/packages/checkbox-group';
export default {
name: 'ElTableFilterPanel',
mixins: [Popper, Locale],
directives: {
Clickoutside
},
components: {
ElCheckbox,
ElCheckboxGroup
},
props: {
placement: {
type: String,
default: 'bottom-end'
}
},
customRender(h) {
return (<div class="el-table-filter">
<div class="el-table-filter__content">
</div>
<div class="el-table-filter__bottom">
<button on-click={ this.handleConfirm }>{ this.t('el.table.confirmFilter') }</button>
<button on-click={ this.handleReset }>{ this.t('el.table.resetFilter') }</button>
</div>
</div>);
},
methods: {
isActive(filter) {
return filter.value === this.filterValue;
},
handleOutsideClick() {
setTimeout(() => {
this.showPopper = false;
}, 16);
},
handleConfirm() {
this.confirmFilter(this.filteredValue);
this.handleOutsideClick();
},
handleReset() {
this.filteredValue = [];
this.confirmFilter(this.filteredValue);
this.handleOutsideClick();
},
handleSelect(filterValue) {
this.filterValue = filterValue;
if ((typeof filterValue !== 'undefined') && (filterValue !== null)) {
this.confirmFilter(this.filteredValue);
} else {
this.confirmFilter([]);
}
this.handleOutsideClick();
},
confirmFilter(filteredValue) {
this.table.store.commit('filterChange', {
column: this.column,
values: filteredValue
});
this.table.store.updateAllSelected();
}
},
data() {
return {
table: null,
cell: null,
column: null
};
},
computed: {
filters() {
return this.column && this.column.filters;
},
filterValue: {
get() {
return (this.column.filteredValue || [])[0];
},
set(value) {
if (this.filteredValue) {
if ((typeof value !== 'undefined') && (value !== null)) {
this.filteredValue.splice(0, 1, value);
} else {
this.filteredValue.splice(0, 1);
}
}
}
},
filteredValue: {
get() {
if (this.column) {
return this.column.filteredValue || [];
}
return [];
},
set(value) {
if (this.column) {
this.column.filteredValue = value;
}
}
},
multiple() {
if (this.column) {
return this.column.filterMultiple;
}
return true;
}
},
mounted() {
this.popperElm = this.$el;
this.referenceElm = this.cell;
this.table.bodyWrapper.addEventListener('scroll', () => {
this.updatePopper();
});
this.$watch('showPopper', (value) => {
if (this.column) this.column.filterOpened = value;
if (value) {
Dropdown.open(this);
} else {
Dropdown.close(this);
}
});
},
watch: {
showPopper(val) {
if (val === true && parseInt(this.popperJS._popper.style.zIndex, 10) < PopupManager.zIndex) {
this.popperJS._popper.style.zIndex = PopupManager.nextZIndex();
}
}
}
};
</script>
+68
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';
}
}
}
};
+360
View File
@@ -0,0 +1,360 @@
import { getCell, getColumnByCell, getRowIdentity } from './util';
import { getStyle, hasClass } from 'element-ui/src/utils/dom';
import ElCheckbox from 'element-ui/packages/checkbox';
import ElTooltip from 'element-ui/packages/tooltip';
import debounce from 'throttle-debounce/debounce';
import LayoutObserver from './layout-observer';
export default {
name: 'ElTableBody',
mixins: [LayoutObserver],
components: {
ElCheckbox,
ElTooltip
},
props: {
store: {
required: true
},
stripe: Boolean,
context: {},
rowClassName: [String, Function],
rowStyle: [Object, Function],
fixed: String,
highlight: Boolean
},
render(h) {
const columnsHidden = this.columns.map((column, index) => this.isColumnHidden(index));
return (
<table
class="el-table__body"
cellspacing="0"
cellpadding="0"
border="0">
<colgroup>
{
this._l(this.columns, column => <col name={ column.id } />)
}
</colgroup>
<tbody>
{
this._l(this.data, (row, $index) =>
[<tr
style={ this.rowStyle ? this.getRowStyle(row, $index) : null }
key={ this.table.rowKey ? this.getKeyOfRow(row, $index) : $index }
on-dblclick={ ($event) => this.handleDoubleClick($event, row) }
on-click={ ($event) => this.handleClick($event, row) }
on-contextmenu={ ($event) => this.handleContextMenu($event, row) }
on-mouseenter={ _ => this.handleMouseEnter($index) }
on-mouseleave={ _ => this.handleMouseLeave() }
class={ [this.getRowClass(row, $index)] }>
{
this._l(this.columns, (column, cellIndex) => {
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
if (!rowspan || !colspan) {
return '';
} else {
return (
<td
style={ this.getCellStyle($index, cellIndex, row, column) }
class={ this.getCellClass($index, cellIndex, row, column) }
rowspan={ rowspan }
colspan={ colspan }
on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) }
on-mouseleave={ this.handleCellMouseLeave }>
{
column.renderCell.call(
this._renderProxy,
h,
{
row,
column,
$index,
store: this.store,
_self: this.context || this.table.$vnode.context
},
columnsHidden[cellIndex]
)
}
</td>
);
}
})
}
</tr>,
this.store.isRowExpanded(row)
? (<tr>
<td colspan={ this.columns.length } class="el-table__expanded-cell">
{ this.table.renderExpanded ? this.table.renderExpanded(h, { row, $index, store: this.store }) : ''}
</td>
</tr>)
: ''
]
).concat(
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip>
)
}
</tbody>
</table>
);
},
computed: {
table() {
return this.$parent;
},
data() {
return this.store.states.data;
},
columnsCount() {
return this.store.states.columns.length;
},
leftFixedLeafCount() {
return this.store.states.fixedLeafColumnsLength;
},
rightFixedLeafCount() {
return this.store.states.rightFixedLeafColumnsLength;
},
leftFixedCount() {
return this.store.states.fixedColumns.length;
},
rightFixedCount() {
return this.store.states.rightFixedColumns.length;
},
columns() {
return this.store.states.columns;
}
},
data() {
return {
tooltipContent: ''
};
},
created() {
this.activateTooltip = debounce(50, tooltip => tooltip.handleShowPopper());
},
methods: {
getKeyOfRow(row, index) {
const rowKey = this.table.rowKey;
if (rowKey) {
return getRowIdentity(row, rowKey);
}
return index;
},
isColumnHidden(index) {
if (this.fixed === true || this.fixed === 'left') {
return index >= this.leftFixedLeafCount;
} else if (this.fixed === 'right') {
return index < this.columnsCount - this.rightFixedLeafCount;
} else {
return (index < this.leftFixedLeafCount) || (index >= this.columnsCount - this.rightFixedLeafCount);
}
},
getSpan(row, column, rowIndex, columnIndex) {
let rowspan = 1;
let colspan = 1;
const fn = this.table.spanMethod;
if (typeof fn === 'function') {
const result = fn({
row,
column,
rowIndex,
columnIndex
});
if (Array.isArray(result)) {
rowspan = result[0];
colspan = result[1];
} else if (typeof result === 'object') {
rowspan = result.rowspan;
colspan = result.colspan;
}
}
return {
rowspan,
colspan
};
},
getRowStyle(row, rowIndex) {
const rowStyle = this.table.rowStyle;
if (typeof rowStyle === 'function') {
return rowStyle.call(null, {
row,
rowIndex
});
}
return rowStyle;
},
getRowClass(row, rowIndex) {
const classes = ['el-table__row'];
if (this.table.highlightCurrentRow && row === this.store.states.currentRow) {
classes.push('current-row');
}
if (rowIndex === this.store.states.hoverRow) {
classes.push('hover-row');
}
if (this.stripe && rowIndex % 2 === 1) {
classes.push('el-table__row--striped');
}
const rowClassName = this.table.rowClassName;
if (typeof rowClassName === 'string') {
classes.push(rowClassName);
} else if (typeof rowClassName === 'function') {
classes.push(rowClassName.call(null, {
row,
rowIndex
}));
}
if (this.store.states.expandRows.indexOf(row) > -1) {
classes.push('expanded');
}
return classes.join(' ');
},
getCellStyle(rowIndex, columnIndex, row, column) {
const cellStyle = this.table.cellStyle;
if (typeof cellStyle === 'function') {
return cellStyle.call(null, {
rowIndex,
columnIndex,
row,
column
});
}
return cellStyle;
},
getCellClass(rowIndex, columnIndex, row, column) {
const classes = [column.id, column.align, column.className];
if (this.isColumnHidden(columnIndex)) {
classes.push('is-hidden');
}
const cellClassName = this.table.cellClassName;
if (typeof cellClassName === 'string') {
classes.push(cellClassName);
} else if (typeof cellClassName === 'function') {
classes.push(cellClassName.call(null, {
rowIndex,
columnIndex,
row,
column
}));
}
return classes.join(' ');
},
handleCellMouseEnter(event, row) {
const table = this.table;
const cell = getCell(event);
if (cell) {
const column = getColumnByCell(table, cell);
const hoverState = table.hoverState = {cell, column, row};
table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
}
// 判断是否text-overflow, 如果是就显示tooltip
const cellChild = event.target.querySelector('.cell');
if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
return;
}
// use range width instead of scrollWidth to determine whether the text is overflowing
// to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
const range = document.createRange();
range.setStart(cellChild, 0);
range.setEnd(cellChild, cellChild.childNodes.length);
const rangeWidth = range.getBoundingClientRect().width;
const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
(parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
if ((rangeWidth + padding > cellChild.offsetWidth || cellChild.scrollWidth > cellChild.offsetWidth) && this.$refs.tooltip) {
const tooltip = this.$refs.tooltip;
// TODO 会引起整个 Table 的重新渲染,需要优化
this.tooltipContent = cell.innerText || cell.textContent;
tooltip.referenceElm = cell;
tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none');
tooltip.doDestroy();
tooltip.setExpectedState(true);
this.activateTooltip(tooltip);
}
},
handleCellMouseLeave(event) {
const tooltip = this.$refs.tooltip;
if (tooltip) {
tooltip.setExpectedState(false);
tooltip.handleClosePopper();
}
const cell = getCell(event);
if (!cell) return;
const oldHoverState = this.table.hoverState || {};
this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
},
handleMouseEnter(index) {
this.store.commit('setHoverRow', index);
},
handleMouseLeave() {
this.store.commit('setHoverRow', null);
},
handleContextMenu(event, row) {
this.handleEvent(event, row, 'contextmenu');
},
handleDoubleClick(event, row) {
this.handleEvent(event, row, 'dblclick');
},
handleClick(event, row) {
this.store.commit('setCurrentRow', row);
this.handleEvent(event, row, 'click');
},
handleEvent(event, row, name) {
const table = this.table;
const cell = getCell(event);
let column;
if (cell) {
column = getColumnByCell(table, cell);
if (column) {
table.$emit(`cell-${name}`, row, column, cell, event);
}
}
table.$emit(`row-${name}`, row, column, event);
},
handleExpandClick(row, e) {
e.stopPropagation();
this.store.toggleRowExpansion(row);
}
}
};
+462
View File
@@ -0,0 +1,462 @@
import ElCheckbox from 'element-ui/packages/checkbox';
import ElTag from 'element-ui/packages/tag';
import objectAssign from 'element-ui/src/utils/merge';
import { getPropByPath } from 'element-ui/src/utils/util';
let columnIdSeed = 1;
const defaults = {
default: {
order: ''
},
selection: {
width: 48,
minWidth: 48,
realWidth: 48,
order: '',
className: 'el-table-column--selection'
},
expand: {
width: 48,
minWidth: 48,
realWidth: 48,
order: ''
},
index: {
width: 48,
minWidth: 48,
realWidth: 48,
order: ''
}
};
const forced = {
selection: {
renderHeader: function(h, { store }) {
return <el-checkbox
disabled={ store.states.data && store.states.data.length === 0 }
indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
nativeOn-click={ this.toggleAllSelection }
value={ this.isAllSelected } />;
},
renderCell: function(h, { row, column, store, $index }) {
return <el-checkbox
nativeOn-click={ (event) => event.stopPropagation() }
value={ store.isSelected(row) }
disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
},
sortable: false,
resizable: false
},
index: {
renderHeader: function(h, { column }) {
return column.label || '#';
},
renderCell: function(h, { $index, column }) {
let i = $index + 1;
const index = column.index;
if (typeof index === 'number') {
i = $index + index;
} else if (typeof index === 'function') {
i = index($index);
}
return <div>{ i }</div>;
},
sortable: false
},
expand: {
renderHeader: function(h, { column }) {
return column.label || '';
},
renderCell: function(h, { row, store }, proxy) {
const expanded = store.states.expandRows.indexOf(row) > -1;
return <div class={ 'el-table__expand-icon ' + (expanded ? 'el-table__expand-icon--expanded' : '') }
on-click={ e => proxy.handleExpandClick(row, e) }>
<i class='el-icon el-icon-arrow-right'></i>
</div>;
},
sortable: false,
resizable: false,
className: 'el-table__expand-column'
}
};
const getDefaultColumn = function(type, options) {
const column = {};
objectAssign(column, defaults[type || 'default']);
for (let name in options) {
if (options.hasOwnProperty(name)) {
const value = options[name];
if (typeof value !== 'undefined') {
column[name] = value;
}
}
}
if (!column.minWidth) {
column.minWidth = 80;
}
column.realWidth = column.width === undefined ? column.minWidth : column.width;
return column;
};
const DEFAULT_RENDER_CELL = function(h, { row, column, $index }) {
const property = column.property;
const value = property && getPropByPath(row, property).v;
if (column && column.formatter) {
return column.formatter(row, column, value, $index);
}
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 {
name: 'ElTableColumn',
props: {
type: {
type: String,
default: 'default'
},
label: String,
className: String,
labelClassName: String,
property: String,
prop: String,
width: {},
minWidth: {},
renderHeader: Function,
sortable: {
type: [String, Boolean],
default: false
},
sortMethod: Function,
sortBy: [String, Function, Array],
resizable: {
type: Boolean,
default: true
},
context: {},
columnKey: String,
align: String,
headerAlign: String,
showTooltipWhenOverflow: Boolean,
showOverflowTooltip: Boolean,
fixed: [Boolean, String],
formatter: Function,
selectable: Function,
reserveSelection: Boolean,
filterMethod: Function,
filteredValue: Array,
filters: Array,
filterPlacement: String,
filterMultiple: {
type: Boolean,
default: true
},
index: [Number, Function],
sortOrders: {
type: Array,
default() {
return ['ascending', 'descending', null];
},
validator(val) {
return val.every(order => ['ascending', 'descending', null].indexOf(order) > -1);
}
}
},
data() {
return {
isSubColumn: false,
columns: []
};
},
beforeCreate() {
this.row = {};
this.column = {};
this.$index = 0;
},
components: {
ElCheckbox,
ElTag
},
computed: {
owner() {
let parent = this.$parent;
while (parent && !parent.tableId) {
parent = parent.$parent;
}
return parent;
},
columnOrTableParent() {
let parent = this.$parent;
while (parent && !parent.tableId && !parent.columnId) {
parent = parent.$parent;
}
return parent;
}
},
created() {
this.customRender = this.$options.render;
this.$options.render = h => h('div', this.$slots.default);
let parent = this.columnOrTableParent;
let owner = this.owner;
this.isSubColumn = owner !== parent;
this.columnId = (parent.tableId || parent.columnId) + '_column_' + columnIdSeed++;
let type = this.type;
const width = parseWidth(this.width);
const minWidth = parseMinWidth(this.minWidth);
let isColumnGroup = false;
let column = getDefaultColumn(type, {
id: this.columnId,
columnKey: this.columnKey,
label: this.label,
className: this.className,
labelClassName: this.labelClassName,
property: this.prop || this.property,
type,
renderCell: null,
renderHeader: this.renderHeader,
minWidth,
width,
isColumnGroup,
context: this.context,
align: this.align ? 'is-' + this.align : null,
headerAlign: this.headerAlign ? 'is-' + this.headerAlign : (this.align ? 'is-' + this.align : null),
sortable: this.sortable === '' ? true : this.sortable,
sortMethod: this.sortMethod,
sortBy: this.sortBy,
resizable: this.resizable,
showOverflowTooltip: this.showOverflowTooltip || this.showTooltipWhenOverflow,
formatter: this.formatter,
selectable: this.selectable,
reserveSelection: this.reserveSelection,
fixed: this.fixed === '' ? true : this.fixed,
filterMethod: this.filterMethod,
filters: this.filters,
filterable: this.filters || this.filterMethod,
filterMultiple: this.filterMultiple,
filterOpened: false,
filteredValue: this.filteredValue || [],
filterPlacement: this.filterPlacement || '',
index: this.index,
sortOrders: this.sortOrders
});
let source = forced[type] || {};
Object.keys(source).forEach((prop) => {
let value = source[prop];
if (value !== undefined) {
if (prop === 'renderHeader') {
if (type === 'selection' && column[prop]) {
console.warn('[Element Warn][TableColumn]Selection column doesn\'t allow to set render-header function.');
} else {
value = column[prop] || value;
}
}
column[prop] = prop === 'className' ? `${column[prop]} ${value}` : value;
}
});
// Deprecation warning for renderHeader property
if (this.renderHeader) {
console.warn('[Element Warn][TableColumn]Comparing to render-header, scoped-slot header is easier to use. We recommend users to use scoped-slot header.');
}
this.columnConfig = column;
let renderCell = column.renderCell;
let _self = this;
if (type === 'expand') {
owner.renderExpanded = function(h, data) {
return _self.$scopedSlots.default
? _self.$scopedSlots.default(data)
: _self.$slots.default;
};
column.renderCell = function(h, data) {
return <div class="cell">{ renderCell(h, data, this._renderProxy) }</div>;
};
return;
}
column.renderCell = function(h, data) {
if (_self.$scopedSlots.default) {
renderCell = () => _self.$scopedSlots.default(data);
}
if (!renderCell) {
renderCell = DEFAULT_RENDER_CELL;
}
return _self.showOverflowTooltip || _self.showTooltipWhenOverflow
? <div class="cell el-tooltip" style={ {width: (data.column.realWidth || data.column.width) - 1 + 'px'} }>{ renderCell(h, data) }</div>
: <div class="cell">{ renderCell(h, data) }</div>;
};
},
destroyed() {
if (!this.$parent) return;
const parent = this.$parent;
this.owner.store.commit('removeColumn', this.columnConfig, this.isSubColumn ? parent.columnConfig : null);
},
watch: {
label(newVal) {
if (this.columnConfig) {
this.columnConfig.label = newVal;
}
},
prop(newVal) {
if (this.columnConfig) {
this.columnConfig.property = newVal;
}
},
property(newVal) {
if (this.columnConfig) {
this.columnConfig.property = newVal;
}
},
filters(newVal) {
if (this.columnConfig) {
this.columnConfig.filters = newVal;
}
},
filterMultiple(newVal) {
if (this.columnConfig) {
this.columnConfig.filterMultiple = newVal;
}
},
align(newVal) {
if (this.columnConfig) {
this.columnConfig.align = newVal ? 'is-' + newVal : null;
if (!this.headerAlign) {
this.columnConfig.headerAlign = newVal ? 'is-' + newVal : null;
}
}
},
headerAlign(newVal) {
if (this.columnConfig) {
this.columnConfig.headerAlign = 'is-' + (newVal ? newVal : this.align);
}
},
width(newVal) {
if (this.columnConfig) {
this.columnConfig.width = parseWidth(newVal);
this.owner.store.scheduleLayout();
}
},
minWidth(newVal) {
if (this.columnConfig) {
this.columnConfig.minWidth = parseMinWidth(newVal);
this.owner.store.scheduleLayout();
}
},
fixed(newVal) {
if (this.columnConfig) {
this.columnConfig.fixed = newVal;
this.owner.store.scheduleLayout(true);
}
},
sortable(newVal) {
if (this.columnConfig) {
this.columnConfig.sortable = newVal;
}
},
index(newVal) {
if (this.columnConfig) {
this.columnConfig.index = newVal;
}
},
formatter(newVal) {
if (this.columnConfig) {
this.columnConfig.formatter = newVal;
}
},
className(newVal) {
if (this.columnConfig) {
this.columnConfig.className = newVal;
}
},
labelClassName(newVal) {
if (this.columnConfig) {
this.columnConfig.labelClassName = newVal;
}
}
},
mounted() {
const owner = this.owner;
const parent = this.columnOrTableParent;
let columnIndex;
if (!this.isSubColumn) {
columnIndex = [].indexOf.call(parent.$refs.hiddenColumns.children, this.$el);
} else {
columnIndex = [].indexOf.call(parent.$el.children, this.$el);
}
if (this.$scopedSlots.header) {
if (this.type === 'selection') {
console.warn('[Element Warn][TableColumn]Selection column doesn\'t allow to set scoped-slot header.');
} else {
this.columnConfig.renderHeader = (h, scope) => this.$scopedSlots.header(scope);
}
}
owner.store.commit('insertColumn', this.columnConfig, columnIndex, this.isSubColumn ? parent.columnConfig : null);
}
};
+157
View File
@@ -0,0 +1,157 @@
import LayoutObserver from './layout-observer';
export default {
name: 'ElTableFooter',
mixins: [LayoutObserver],
render(h) {
let sums = [];
if (this.summaryMethod) {
sums = this.summaryMethod({ columns: this.columns, data: this.store.states.data });
} else {
this.columns.forEach((column, index) => {
if (index === 0) {
sums[index] = this.sumText;
return;
}
const values = this.store.states.data.map(item => Number(item[column.property]));
const precisions = [];
let notNumber = true;
values.forEach(value => {
if (!isNaN(value)) {
notNumber = false;
let decimal = ('' + value).split('.')[1];
precisions.push(decimal ? decimal.length : 0);
}
});
const precision = Math.max.apply(null, precisions);
if (!notNumber) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return parseFloat((prev + curr).toFixed(Math.min(precision, 20)));
} else {
return prev;
}
}, 0);
} else {
sums[index] = '';
}
});
}
return (
<table
class="el-table__footer"
cellspacing="0"
cellpadding="0"
border="0">
<colgroup>
{
this._l(this.columns, column => <col name={ column.id } />)
}
{
this.hasGutter ? <col name="gutter" /> : ''
}
</colgroup>
<tbody class={ [{ 'has-gutter': this.hasGutter }] }>
<tr>
{
this._l(this.columns, (column, cellIndex) =>
<td
colspan={ column.colSpan }
rowspan={ column.rowSpan }
class={ [column.id, column.headerAlign, column.className || '', this.isCellHidden(cellIndex, this.columns, column) ? 'is-hidden' : '', !column.children ? 'is-leaf' : '', column.labelClassName] }>
<div class={ ['cell', column.labelClassName] }>
{
sums[cellIndex]
}
</div>
</td>
)
}
{
this.hasGutter ? <th class="gutter"></th> : ''
}
</tr>
</tbody>
</table>
);
},
props: {
fixed: String,
store: {
required: true
},
summaryMethod: Function,
sumText: String,
border: Boolean,
defaultSort: {
type: Object,
default() {
return {
prop: '',
order: ''
};
}
}
},
computed: {
table() {
return this.$parent;
},
isAllSelected() {
return this.store.states.isAllSelected;
},
columnsCount() {
return this.store.states.columns.length;
},
leftFixedCount() {
return this.store.states.fixedColumns.length;
},
leftFixedLeafCount() {
return this.store.states.fixedLeafColumnsLength;
},
rightFixedLeafCount() {
return this.store.states.rightFixedLeafColumnsLength;
},
rightFixedCount() {
return this.store.states.rightFixedColumns.length;
},
columns() {
return this.store.states.columns;
},
hasGutter() {
return !this.fixed && this.tableLayout.gutterWidth;
}
},
methods: {
isCellHidden(index, columns, column) {
if (this.fixed === true || this.fixed === 'left') {
return index >= this.leftFixedLeafCount;
} else if (this.fixed === 'right') {
let before = 0;
for (let i = 0; i < index; i++) {
before += columns[i].colSpan;
}
return before < this.columnsCount - this.rightFixedLeafCount;
} else if (!this.fixed && column.fixed) { // hide cell when footer instance is not fixed and column is fixed
return true;
} else {
return (index < this.leftFixedCount) || (index >= this.columnsCount - this.rightFixedCount);
}
}
}
};
+524
View File
@@ -0,0 +1,524 @@
import { hasClass, addClass, removeClass } from 'element-ui/src/utils/dom';
import ElCheckbox from 'element-ui/packages/checkbox';
import ElTag from 'element-ui/packages/tag';
import Vue from 'vue';
import FilterPanel from './filter-panel.vue';
import LayoutObserver from './layout-observer';
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._l(this.columns, column => <col name={ 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) }
>
{
this._l(columns, (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,
ElTag
},
computed: {
table() {
return this.$parent;
},
isAllSelected() {
return this.store.states.isAllSelected;
},
columnsCount() {
return this.store.states.columns.length;
},
leftFixedCount() {
return this.store.states.fixedColumns.length;
},
rightFixedCount() {
return this.store.states.rightFixedColumns.length;
},
leftFixedLeafCount() {
return this.store.states.fixedLeafColumnsLength;
},
rightFixedLeafCount() {
return this.store.states.rightFixedLeafColumnsLength;
},
columns() {
return this.store.states.columns;
},
hasGutter() {
return !this.fixed && this.tableLayout.gutterWidth;
}
},
created() {
this.filterPanels = {};
},
mounted() {
const { prop, order } = this.defaultSort;
this.store.commit('sort', { prop, order });
},
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;
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;
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: {}
};
}
};
+231
View File
@@ -0,0 +1,231 @@
import scrollbarWidth from 'element-ui/src/utils/scrollbar-width';
import Vue from 'vue';
class TableLayout {
constructor(options) {
this.observers = [];
this.table = null;
this.store = null;
this.columns = null;
this.fit = true;
this.showHeader = true;
this.height = null;
this.scrollX = false;
this.scrollY = false;
this.bodyWidth = null;
this.fixedWidth = null;
this.rightFixedWidth = null;
this.tableHeight = null;
this.headerHeight = 44; // Table Header Height
this.appendHeight = 0; // Append Slot Height
this.footerHeight = 44; // Table Footer Height
this.viewportHeight = null; // Table Height - Scroll Bar Height
this.bodyHeight = null; // Table Height - Table Header Height
this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height
this.gutterWidth = scrollbarWidth();
for (let name in options) {
if (options.hasOwnProperty(name)) {
this[name] = options[name];
}
}
if (!this.table) {
throw new Error('table is required for Table Layout');
}
if (!this.store) {
throw new Error('store is required for Table Layout');
}
}
updateScrollY() {
const height = this.height;
if (typeof height !== 'string' && typeof height !== 'number') return;
const bodyWrapper = this.table.bodyWrapper;
if (this.table.$el && bodyWrapper) {
const body = bodyWrapper.querySelector('.el-table__body');
this.scrollY = body.offsetHeight > this.bodyHeight;
}
}
setHeight(value, prop = 'height') {
if (Vue.prototype.$isServer) return;
const el = this.table.$el;
if (typeof value === 'string' && /^\d+$/.test(value)) {
value = Number(value);
}
this.height = value;
if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop));
if (typeof value === 'number') {
el.style[prop] = value + 'px';
this.updateElsHeight();
} else if (typeof value === 'string') {
el.style[prop] = value;
this.updateElsHeight();
}
}
setMaxHeight(value) {
return this.setHeight(value, 'max-height');
}
updateElsHeight() {
if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight());
const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs;
this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0;
if (this.showHeader && !headerWrapper) return;
const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight;
if (this.showHeader && headerWrapper.offsetWidth > 0 && (this.table.columns || []).length > 0 && headerHeight < 2) {
return Vue.nextTick(() => this.updateElsHeight());
}
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');
}
getFlattenColumns() {
const flattenColumns = [];
const columns = this.table.columns;
columns.forEach((column) => {
if (column.isColumnGroup) {
flattenColumns.push.apply(flattenColumns, column.columns);
} else {
flattenColumns.push(column);
}
});
return flattenColumns;
}
updateColumnsWidth() {
if (Vue.prototype.$isServer) return;
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');
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) {
flattenColumns.forEach((column) => {
bodyMinWidth += column.width || column.minWidth || 80;
});
const scrollYWidth = this.scrollY ? this.gutterWidth : 0;
if (bodyMinWidth <= bodyWidth - scrollYWidth) { // DON'T HAVE SCROLL BAR
this.scrollX = false;
const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth;
if (flexColumns.length === 1) {
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth;
} else {
const allColumnsWidth = flexColumns.reduce((prev, column) => prev + (column.minWidth || 80), 0);
const flexWidthPerPixel = totalFlexWidth / allColumnsWidth;
let noneFirstWidth = 0;
flexColumns.forEach((column, index) => {
if (index === 0) return;
const flexWidth = Math.floor((column.minWidth || 80) * flexWidthPerPixel);
noneFirstWidth += flexWidth;
column.realWidth = (column.minWidth || 80) + flexWidth;
});
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth;
}
} else { // HAVE HORIZONTAL SCROLL BAR
this.scrollX = true;
flexColumns.forEach(function(column) {
column.realWidth = column.minWidth;
});
}
this.bodyWidth = Math.max(bodyMinWidth, bodyWidth);
this.table.resizeState.width = this.bodyWidth;
} else {
flattenColumns.forEach((column) => {
if (!column.width && !column.minWidth) {
column.realWidth = 80;
} else {
column.realWidth = column.width || column.minWidth;
}
bodyMinWidth += column.realWidth;
});
this.scrollX = bodyMinWidth > bodyWidth;
this.bodyWidth = bodyMinWidth;
}
const fixedColumns = this.store.states.fixedColumns;
if (fixedColumns.length > 0) {
let fixedWidth = 0;
fixedColumns.forEach(function(column) {
fixedWidth += column.realWidth || column.width;
});
this.fixedWidth = fixedWidth;
}
const rightFixedColumns = this.store.states.rightFixedColumns;
if (rightFixedColumns.length > 0) {
let rightFixedWidth = 0;
rightFixedColumns.forEach(function(column) {
rightFixedWidth += column.realWidth || column.width;
});
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}.`);
}
});
}
}
export default TableLayout;
+647
View File
@@ -0,0 +1,647 @@
import Vue from 'vue';
import debounce from 'throttle-debounce/debounce';
import merge from 'element-ui/src/utils/merge';
import { orderBy, getColumnById, getRowIdentity, getColumnByKey } from './util';
const sortData = (data, states) => {
const sortingColumn = states.sortingColumn;
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
return data;
}
return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy);
};
const getKeysMap = function(array, rowKey) {
const arrayMap = {};
(array || []).forEach((row, index) => {
arrayMap[getRowIdentity(row, rowKey)] = { row, index };
});
return arrayMap;
};
const toggleRowSelection = function(states, row, selected) {
let changed = false;
const selection = states.selection;
const index = selection.indexOf(row);
if (typeof selected === 'undefined') {
if (index === -1) {
selection.push(row);
changed = true;
} else {
selection.splice(index, 1);
changed = true;
}
} else {
if (selected && index === -1) {
selection.push(row);
changed = true;
} else if (!selected && index > -1) {
selection.splice(index, 1);
changed = true;
}
}
return changed;
};
const toggleRowExpansion = function(states, row, expanded) {
let changed = false;
const expandRows = states.expandRows;
if (typeof expanded !== 'undefined') {
const index = expandRows.indexOf(row);
if (expanded) {
if (index === -1) {
expandRows.push(row);
changed = true;
}
} else {
if (index !== -1) {
expandRows.splice(index, 1);
changed = true;
}
}
} else {
const index = expandRows.indexOf(row);
if (index === -1) {
expandRows.push(row);
changed = true;
} else {
expandRows.splice(index, 1);
changed = true;
}
}
return changed;
};
const TableStore = function(table, initialState = {}) {
if (!table) {
throw new Error('Table is required.');
}
this.table = table;
this.states = {
rowKey: null,
_columns: [],
originColumns: [],
columns: [],
fixedColumns: [],
rightFixedColumns: [],
leafColumns: [],
fixedLeafColumns: [],
rightFixedLeafColumns: [],
leafColumnsLength: 0,
fixedLeafColumnsLength: 0,
rightFixedLeafColumnsLength: 0,
isComplex: false,
filteredData: null,
data: null,
sortingColumn: null,
sortProp: null,
sortOrder: null,
isAllSelected: false,
selection: [],
reserveSelection: false,
selectable: null,
currentRow: null,
hoverRow: null,
filters: {},
expandRows: [],
defaultExpandAll: false,
selectOnIndeterminate: false
};
this._toggleAllSelection = debounce(10, function(states) {
const data = states.data || [];
if (data.length === 0) return;
const selection = this.states.selection;
// when only some rows are selected (but not all), select or deselect all of them
// depending on the value of selectOnIndeterminate
const value = states.selectOnIndeterminate
? !states.isAllSelected
: !(states.isAllSelected || selection.length);
let selectionChanged = false;
data.forEach((item, index) => {
if (states.selectable) {
if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
selectionChanged = true;
}
} else {
if (toggleRowSelection(states, item, value)) {
selectionChanged = true;
}
}
});
const table = this.table;
if (selectionChanged) {
table.$emit('selection-change', selection ? selection.slice() : []);
}
table.$emit('select-all', selection);
states.isAllSelected = value;
});
for (let prop in initialState) {
if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
this.states[prop] = initialState[prop];
}
}
};
TableStore.prototype.mutations = {
setData(states, data) {
const dataInstanceChanged = states._data !== data;
states._data = data;
Object.keys(states.filters).forEach((columnId) => {
const values = states.filters[columnId];
if (!values || values.length === 0) return;
const column = getColumnById(this.states, columnId);
if (column && column.filterMethod) {
data = data.filter((row) => {
return values.some(value => column.filterMethod.call(null, value, row, column));
});
}
});
states.filteredData = data;
states.data = sortData((data || []), states);
this.updateCurrentRow();
const rowKey = states.rowKey;
if (!states.reserveSelection) {
if (dataInstanceChanged) {
this.clearSelection();
} else {
this.cleanSelection();
}
this.updateAllSelected();
} else {
if (rowKey) {
const selection = states.selection;
const selectedMap = getKeysMap(selection, rowKey);
states.data.forEach((row) => {
const rowId = getRowIdentity(row, rowKey);
const rowInfo = selectedMap[rowId];
if (rowInfo) {
selection[rowInfo.index] = row;
}
});
this.updateAllSelected();
} else {
console.warn('WARN: rowKey is required when reserve-selection is enabled.');
}
}
const defaultExpandAll = states.defaultExpandAll;
if (defaultExpandAll) {
this.states.expandRows = (states.data || []).slice(0);
} else if (rowKey) {
// update expandRows to new rows according to rowKey
const ids = getKeysMap(this.states.expandRows, rowKey);
let expandRows = [];
for (const row of states.data) {
const rowId = getRowIdentity(row, rowKey);
if (ids[rowId]) {
expandRows.push(row);
}
}
this.states.expandRows = expandRows;
} else {
// clear the old rows
this.states.expandRows = [];
}
Vue.nextTick(() => this.table.updateScrollY());
},
changeSortCondition(states, options) {
states.data = sortData((states.filteredData || states._data || []), states);
if (!options || !options.silent) {
this.table.$emit('sort-change', {
column: this.states.sortingColumn,
prop: this.states.sortProp,
order: this.states.sortOrder
});
}
Vue.nextTick(() => this.table.updateScrollY());
},
sort(states, options) {
const { prop, order } = options;
if (prop) {
states.sortProp = prop;
states.sortOrder = order || 'ascending';
Vue.nextTick(() => {
for (let i = 0, length = states.columns.length; i < length; i++) {
let column = states.columns[i];
if (column.property === states.sortProp) {
column.order = states.sortOrder;
states.sortingColumn = column;
break;
}
}
if (states.sortingColumn) {
this.commit('changeSortCondition');
}
});
}
},
filterChange(states, options) {
let { column, values, silent, multi } = options;
if (values && !Array.isArray(values)) {
values = [values];
}
const filters = {};
if (multi) {
column.forEach(col => {
states.filters[col.id] = values;
filters[col.columnKey || col.id] = values;
});
} else {
const prop = column.property;
if (prop) {
states.filters[column.id] = values;
filters[column.columnKey || column.id] = values;
}
}
let data = states._data;
Object.keys(states.filters).forEach((columnId) => {
const values = states.filters[columnId];
if (!values || values.length === 0) return;
const column = getColumnById(this.states, columnId);
if (column && column.filterMethod) {
data = data.filter((row) => {
return values.some(value => column.filterMethod.call(null, value, row, column));
});
}
});
states.filteredData = data;
states.data = sortData(data, states);
if (!silent) {
this.table.$emit('filter-change', filters);
}
Vue.nextTick(() => this.table.updateScrollY());
},
insertColumn(states, column, index, parent) {
let array = states._columns;
if (parent) {
array = parent.children;
if (!array) array = parent.children = [];
}
if (typeof index !== 'undefined') {
array.splice(index, 0, column);
} else {
array.push(column);
}
if (column.type === 'selection') {
states.selectable = column.selectable;
states.reserveSelection = column.reserveSelection;
}
if (this.table.$ready) {
this.updateColumns(); // hack for dynamics insert column
this.scheduleLayout();
}
},
removeColumn(states, column, parent) {
let array = states._columns;
if (parent) {
array = parent.children;
if (!array) array = parent.children = [];
}
if (array) {
array.splice(array.indexOf(column), 1);
}
if (this.table.$ready) {
this.updateColumns(); // hack for dynamics remove column
this.scheduleLayout();
}
},
setHoverRow(states, row) {
states.hoverRow = row;
},
setCurrentRow(states, row) {
const oldCurrentRow = states.currentRow;
states.currentRow = row;
if (oldCurrentRow !== row) {
this.table.$emit('current-change', row, oldCurrentRow);
}
},
rowSelectedChanged(states, row) {
const changed = toggleRowSelection(states, row);
const selection = states.selection;
if (changed) {
const table = this.table;
table.$emit('selection-change', selection ? selection.slice() : []);
table.$emit('select', selection, row);
}
this.updateAllSelected();
},
toggleAllSelection(state) {
this._toggleAllSelection(state);
}
};
const doFlattenColumns = (columns) => {
const result = [];
columns.forEach((column) => {
if (column.children) {
result.push.apply(result, doFlattenColumns(column.children));
} else {
result.push(column);
}
});
return result;
};
TableStore.prototype.updateColumns = function() {
const states = this.states;
const _columns = states._columns || [];
states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
_columns[0].fixed = true;
states.fixedColumns.unshift(_columns[0]);
}
const notFixedColumns = _columns.filter(column => !column.fixed);
states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns);
const leafColumns = doFlattenColumns(notFixedColumns);
const fixedLeafColumns = doFlattenColumns(states.fixedColumns);
const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns);
states.leafColumnsLength = leafColumns.length;
states.fixedLeafColumnsLength = fixedLeafColumns.length;
states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length;
states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns);
states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
};
TableStore.prototype.isSelected = function(row) {
return (this.states.selection || []).indexOf(row) > -1;
};
TableStore.prototype.clearSelection = function() {
const states = this.states;
states.isAllSelected = false;
const oldSelection = states.selection;
if (states.selection.length) {
states.selection = [];
}
if (oldSelection.length > 0) {
this.table.$emit('selection-change', states.selection ? states.selection.slice() : []);
}
};
TableStore.prototype.setExpandRowKeys = function(rowKeys) {
const expandRows = [];
const data = this.states.data;
const rowKey = this.states.rowKey;
if (!rowKey) throw new Error('[Table] prop row-key should not be empty.');
const keysMap = getKeysMap(data, rowKey);
rowKeys.forEach((key) => {
const info = keysMap[key];
if (info) {
expandRows.push(info.row);
}
});
this.states.expandRows = expandRows;
};
TableStore.prototype.toggleRowSelection = function(row, selected) {
const changed = toggleRowSelection(this.states, row, selected);
if (changed) {
this.table.$emit('selection-change', this.states.selection ? this.states.selection.slice() : []);
}
};
TableStore.prototype.toggleRowExpansion = function(row, expanded) {
const changed = toggleRowExpansion(this.states, row, expanded);
if (changed) {
this.table.$emit('expand-change', row, this.states.expandRows);
this.scheduleLayout();
}
};
TableStore.prototype.isRowExpanded = function(row) {
const { expandRows = [], rowKey } = this.states;
if (rowKey) {
const expandMap = getKeysMap(expandRows, rowKey);
return !!expandMap[getRowIdentity(row, rowKey)];
}
return expandRows.indexOf(row) !== -1;
};
TableStore.prototype.cleanSelection = function() {
const selection = this.states.selection || [];
const data = this.states.data;
const rowKey = this.states.rowKey;
let deleted;
if (rowKey) {
deleted = [];
const selectedMap = getKeysMap(selection, rowKey);
const dataMap = getKeysMap(data, rowKey);
for (let key in selectedMap) {
if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
deleted.push(selectedMap[key].row);
}
}
} else {
deleted = selection.filter((item) => {
return data.indexOf(item) === -1;
});
}
deleted.forEach((deletedItem) => {
selection.splice(selection.indexOf(deletedItem), 1);
});
if (deleted.length) {
this.table.$emit('selection-change', selection ? selection.slice() : []);
}
};
TableStore.prototype.clearFilter = function(columnKeys) {
const states = this.states;
const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs;
let panels = {};
if (tableHeader) panels = merge(panels, tableHeader.filterPanels);
if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels);
if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels);
const keys = Object.keys(panels);
if (!keys.length) return;
if (typeof columnKeys === 'string') {
columnKeys = [columnKeys];
}
if (Array.isArray(columnKeys)) {
const columns = columnKeys.map(key => getColumnByKey(states, key));
keys.forEach(key => {
const column = columns.find(col => col.id === key);
if (column) {
panels[key].filteredValue = [];
}
});
this.commit('filterChange', {
column: columns,
value: [],
silent: true,
multi: true
});
} else {
keys.forEach(key => {
panels[key].filteredValue = [];
});
states.filters = {};
this.commit('filterChange', {
column: {},
values: [],
silent: true
});
}
};
TableStore.prototype.clearSort = function() {
const states = this.states;
if (!states.sortingColumn) return;
states.sortingColumn.order = null;
states.sortProp = null;
states.sortOrder = null;
this.commit('changeSortCondition', {
silent: true
});
};
TableStore.prototype.updateAllSelected = function() {
const states = this.states;
const { selection, rowKey, selectable, data } = states;
if (!data || data.length === 0) {
states.isAllSelected = false;
return;
}
let selectedMap;
if (rowKey) {
selectedMap = getKeysMap(states.selection, rowKey);
}
const isSelected = function(row) {
if (selectedMap) {
return !!selectedMap[getRowIdentity(row, rowKey)];
} else {
return selection.indexOf(row) !== -1;
}
};
let isAllSelected = true;
let selectedCount = 0;
for (let i = 0, j = data.length; i < j; i++) {
const item = data[i];
const isRowSelectable = selectable && selectable.call(null, item, i);
if (!isSelected(item)) {
if (!selectable || isRowSelectable) {
isAllSelected = false;
break;
}
} else {
selectedCount++;
}
}
if (selectedCount === 0) isAllSelected = false;
states.isAllSelected = isAllSelected;
};
TableStore.prototype.scheduleLayout = function(updateColumns) {
if (updateColumns) {
this.updateColumns();
}
this.table.debouncedUpdateLayout();
};
TableStore.prototype.setCurrentRowKey = function(key) {
const states = this.states;
const rowKey = states.rowKey;
if (!rowKey) throw new Error('[Table] row-key should not be empty.');
const data = states.data || [];
const keysMap = getKeysMap(data, rowKey);
const info = keysMap[key];
states.currentRow = info ? info.row : null;
};
TableStore.prototype.updateCurrentRow = function() {
const states = this.states;
const table = this.table;
const data = states.data || [];
const oldCurrentRow = states.currentRow;
if (data.indexOf(oldCurrentRow) === -1) {
if (states.rowKey && oldCurrentRow) {
let newCurrentRow = null;
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item && item[states.rowKey] === oldCurrentRow[states.rowKey]) {
newCurrentRow = item;
break;
}
}
if (newCurrentRow) {
states.currentRow = newCurrentRow;
return;
}
}
states.currentRow = null;
if (states.currentRow !== oldCurrentRow) {
table.$emit('current-change', null, oldCurrentRow);
}
}
};
TableStore.prototype.commit = function(name, ...args) {
const mutations = this.mutations;
if (mutations[name]) {
mutations[name].apply(this, [this.states].concat(args));
} else {
throw new Error(`Action not found: ${name}`);
}
};
export default TableStore;
+660
View File
@@ -0,0 +1,660 @@
<template>
<div class="el-table"
:class="[{
'el-table--fit': fit,
'el-table--striped': stripe,
'el-table--border': border || isGroup,
'el-table--hidden': isHidden,
'el-table--group': isGroup,
'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-transition': (store.states.data || []).length !== 0 && (store.states.data || []).length < 100
}, tableSize ? `el-table--${ tableSize }` : '']"
@mouseleave="handleMouseLeave($event)">
<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
<div
v-if="showHeader"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__header-wrapper"
ref="headerWrapper">
<table-header
ref="tableHeader"
:store="store"
:border="border"
:default-sort="defaultSort"
:style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}">
</table-header>
</div>
<div
class="el-table__body-wrapper"
ref="bodyWrapper"
:class="[layout.scrollX ? `is-scrolling-${scrollPosition}` : 'is-scrolling-none']"
:style="[bodyHeight]">
<table-body
:context="context"
:store="store"
:stripe="stripe"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{
width: bodyWidth
}">
</table-body>
<div
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
v-if="$slots.append"
class="el-table__append-wrapper"
ref="appendWrapper">
<slot name="append"></slot>
</div>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__footer-wrapper"
ref="footerWrapper">
<table-footer
:store="store"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:default-sort="defaultSort"
:style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}">
</table-footer>
</div>
<div
v-if="fixedColumns.length > 0"
v-mousewheel="handleFixedMousewheel"
class="el-table__fixed"
ref="fixedWrapper"
:style="[{
width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
},
fixedHeight]">
<div
v-if="showHeader"
class="el-table__fixed-header-wrapper"
ref="fixedHeaderWrapper" >
<table-header
ref="fixedTableHeader"
fixed="left"
:border="border"
:store="store"
:style="{
width: bodyWidth
}"></table-header>
</div>
<div
class="el-table__fixed-body-wrapper"
ref="fixedBodyWrapper"
:style="[{
top: layout.headerHeight + 'px'
},
fixedBodyHeight]">
<table-body
fixed="left"
:store="store"
:stripe="stripe"
:highlight="highlightCurrentRow"
:row-class-name="rowClassName"
:row-style="rowStyle"
:style="{
width: bodyWidth
}">
</table-body>
<div
v-if="$slots.append"
class="el-table__append-gutter"
:style="{
height: layout.appendHeight + 'px'
}"></div>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
class="el-table__fixed-footer-wrapper"
ref="fixedFooterWrapper">
<table-footer
fixed="left"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:store="store"
:style="{
width: bodyWidth
}"></table-footer>
</div>
</div>
<div
v-if="rightFixedColumns.length > 0"
v-mousewheel="handleFixedMousewheel"
class="el-table__fixed-right"
ref="rightFixedWrapper"
:style="[{
width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '',
right: layout.scrollY ? (border ? layout.gutterWidth : (layout.gutterWidth || 0)) + 'px' : ''
},
fixedHeight]">
<div v-if="showHeader"
class="el-table__fixed-header-wrapper"
ref="rightFixedHeaderWrapper">
<table-header
ref="rightFixedTableHeader"
fixed="right"
:border="border"
:store="store"
:style="{
width: bodyWidth
}"></table-header>
</div>
<div
class="el-table__fixed-body-wrapper"
ref="rightFixedBodyWrapper"
:style="[{
top: layout.headerHeight + 'px'
},
fixedBodyHeight]">
<table-body
fixed="right"
:store="store"
:stripe="stripe"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{
width: bodyWidth
}">
</table-body>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
class="el-table__fixed-footer-wrapper"
ref="rightFixedFooterWrapper">
<table-footer
fixed="right"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:store="store"
:style="{
width: bodyWidth
}"></table-footer>
</div>
</div>
<div
v-if="rightFixedColumns.length > 0"
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>
</template>
<script type="text/babel">
import ElCheckbox from 'element-ui/packages/checkbox';
import debounce from 'throttle-debounce/debounce';
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
import Mousewheel from 'element-ui/src/directives/mousewheel';
import Locale from 'element-ui/src/mixins/locale';
import Migrating from 'element-ui/src/mixins/migrating';
import TableStore from './table-store';
import TableLayout from './table-layout';
import TableBody from './table-body';
import TableHeader from './table-header';
import TableFooter from './table-footer';
let tableIdSeed = 1;
export default {
name: 'ElTable',
mixins: [Locale, Migrating],
directives: {
Mousewheel
},
props: {
data: {
type: Array,
default: function() {
return [];
}
},
size: String,
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
},
showSummary: Boolean,
sumText: String,
summaryMethod: Function,
rowClassName: [String, Function],
rowStyle: [Object, Function],
cellClassName: [String, Function],
cellStyle: [Object, Function],
headerRowClassName: [String, Function],
headerRowStyle: [Object, Function],
headerCellClassName: [String, Function],
headerCellStyle: [Object, Function],
highlightCurrentRow: Boolean,
currentRowKey: [String, Number],
emptyText: String,
expandRowKeys: Array,
defaultExpandAll: Boolean,
defaultSort: Object,
tooltipEffect: String,
spanMethod: Function,
selectOnIndeterminate: {
type: Boolean,
default: true
}
},
components: {
TableHeader,
TableFooter,
TableBody,
ElCheckbox
},
methods: {
getMigratingConfig() {
return {
events: {
expand: 'expand is renamed to expand-change'
}
};
},
setCurrentRow(row) {
this.store.commit('setCurrentRow', row);
},
toggleRowSelection(row, selected) {
this.store.toggleRowSelection(row, selected);
this.store.updateAllSelected();
},
toggleRowExpansion(row, expanded) {
this.store.toggleRowExpansion(row, expanded);
},
clearSelection() {
this.store.clearSelection();
},
clearFilter(columnKeys) {
this.store.clearFilter(columnKeys);
},
clearSort() {
this.store.clearSort();
},
handleMouseLeave() {
this.store.commit('setHoverRow', null);
if (this.hoverState) this.hoverState = null;
},
updateScrollY() {
this.layout.updateScrollY();
this.layout.updateColumnsWidth();
},
handleFixedMousewheel(event, data) {
const bodyWrapper = this.bodyWrapper;
if (Math.abs(data.spinY) > 0) {
const currentScrollTop = bodyWrapper.scrollTop;
if (data.pixelY < 0 && currentScrollTop !== 0) {
event.preventDefault();
}
if (data.pixelY > 0 && bodyWrapper.scrollHeight - bodyWrapper.clientHeight > currentScrollTop) {
event.preventDefault();
}
bodyWrapper.scrollTop += Math.ceil(data.pixelY / 5);
} else {
bodyWrapper.scrollLeft += Math.ceil(data.pixelX / 5);
}
},
handleHeaderFooterMousewheel(event, data) {
const { pixelX, pixelY } = data;
if (Math.abs(pixelX) >= Math.abs(pixelY)) {
event.preventDefault();
this.bodyWrapper.scrollLeft += data.pixelX / 5;
}
},
bindEvents() {
const { headerWrapper, footerWrapper } = this.$refs;
const refs = this.$refs;
let self = this;
this.bodyWrapper.addEventListener('scroll', function() {
if (headerWrapper) headerWrapper.scrollLeft = this.scrollLeft;
if (footerWrapper) footerWrapper.scrollLeft = this.scrollLeft;
if (refs.fixedBodyWrapper) refs.fixedBodyWrapper.scrollTop = this.scrollTop;
if (refs.rightFixedBodyWrapper) refs.rightFixedBodyWrapper.scrollTop = this.scrollTop;
const maxScrollLeftPosition = this.scrollWidth - this.offsetWidth - 1;
const scrollLeft = this.scrollLeft;
if (scrollLeft >= maxScrollLeftPosition) {
self.scrollPosition = 'right';
} else if (scrollLeft === 0) {
self.scrollPosition = 'left';
} else {
self.scrollPosition = 'middle';
}
});
if (this.fit) {
addResizeListener(this.$el, this.resizeListener);
}
},
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 || this.shouldUpdateHeight) && oldHeight !== height) {
shouldUpdateLayout = true;
}
if (shouldUpdateLayout) {
this.resizeState.width = width;
this.resizeState.height = height;
this.doLayout();
}
},
doLayout() {
this.layout.updateColumnsWidth();
if (this.shouldUpdateHeight) {
this.layout.updateElsHeight();
}
},
sort(prop, order) {
this.store.commit('sort', { prop, order });
},
toggleAllSelection() {
this.store.commit('toggleAllSelection');
}
},
created() {
this.tableId = 'el-table_' + tableIdSeed++;
this.debouncedUpdateLayout = debounce(50, () => this.doLayout());
},
computed: {
tableSize() {
return this.size || (this.$ELEMENT || {}).size;
},
bodyWrapper() {
return this.$refs.bodyWrapper;
},
shouldUpdateHeight() {
return this.height ||
this.maxHeight ||
this.fixedColumns.length > 0 ||
this.rightFixedColumns.length > 0;
},
selection() {
return this.store.states.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;
},
bodyWidth() {
const { bodyWidth, scrollY, gutterWidth } = this.layout;
return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : '';
},
bodyHeight() {
if (this.height) {
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' : ''
};
} else if (this.maxHeight) {
let maxHeight = this.layout.scrollX ? this.maxHeight - this.layout.gutterWidth : this.maxHeight;
if (this.showHeader) {
maxHeight -= this.layout.headerHeight;
}
maxHeight -= this.layout.footerHeight;
return {
'max-height': maxHeight + 'px'
};
}
return {};
},
fixedHeight() {
if (this.maxHeight) {
if (this.showSummary) {
return {
bottom: 0
};
}
return {
bottom: (this.layout.scrollX && this.data.length) ? this.layout.gutterWidth + 'px' : ''
};
} else {
if (this.showSummary) {
return {
height: this.layout.tableHeight ? this.layout.tableHeight + 'px' : ''
};
}
return {
height: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : ''
};
}
}
},
watch: {
height: {
immediate: true,
handler(value) {
this.layout.setHeight(value);
}
},
maxHeight: {
immediate: true,
handler(value) {
this.layout.setMaxHeight(value);
}
},
currentRowKey(newVal) {
this.store.setCurrentRowKey(newVal);
},
data: {
immediate: true,
handler(value) {
this.store.commit('setData', value);
if (this.$ready) {
this.$nextTick(() => {
this.doLayout();
});
}
}
},
expandRowKeys: {
immediate: true,
handler(newVal) {
if (newVal) {
this.store.setExpandRowKeys(newVal);
}
}
}
},
destroyed() {
if (this.resizeListener) removeResizeListener(this.$el, this.resizeListener);
},
mounted() {
this.bindEvents();
this.store.updateColumns();
this.doLayout();
this.resizeState = {
width: this.$el.offsetWidth,
height: this.$el.offsetHeight
};
// init filters
this.store.states.columns.forEach(column => {
if (column.filteredValue && column.filteredValue.length) {
this.store.commit('filterChange', {
column,
values: column.filteredValue,
silent: true
});
}
});
this.$ready = true;
},
data() {
const store = new TableStore(this, {
rowKey: this.rowKey,
defaultExpandAll: this.defaultExpandAll,
selectOnIndeterminate: this.selectOnIndeterminate
});
const layout = new TableLayout({
store,
table: this,
fit: this.fit,
showHeader: this.showHeader
});
return {
layout,
store,
isHidden: false,
renderExpanded: null,
resizeProxyVisible: false,
resizeState: {
width: null,
height: null
},
// 是否拥有多级表头
isGroup: false,
scrollPosition: 'left'
};
}
};
</script>
+122
View File
@@ -0,0 +1,122 @@
import { getValueByPath } from 'element-ui/src/utils/util';
export const getCell = function(event) {
let cell = event.target;
while (cell && cell.tagName.toUpperCase() !== 'HTML') {
if (cell.tagName.toUpperCase() === 'TD') {
return cell;
}
cell = cell.parentNode;
}
return null;
};
const isObject = function(obj) {
return obj !== null && typeof obj === 'object';
};
export const orderBy = function(array, sortKey, reverse, sortMethod, sortBy) {
if (!sortKey && !sortMethod && (!sortBy || Array.isArray(sortBy) && !sortBy.length)) {
return array;
}
if (typeof reverse === 'string') {
reverse = reverse === 'descending' ? -1 : 1;
} else {
reverse = (reverse && reverse < 0) ? -1 : 1;
}
const getKey = sortMethod ? null : function(value, index) {
if (sortBy) {
if (!Array.isArray(sortBy)) {
sortBy = [sortBy];
}
return sortBy.map(function(by) {
if (typeof by === 'string') {
return getValueByPath(value, by);
} else {
return by(value, index, array);
}
});
}
if (sortKey !== '$key') {
if (isObject(value) && '$value' in value) value = value.$value;
}
return [isObject(value) ? getValueByPath(value, sortKey) : value];
};
const compare = function(a, b) {
if (sortMethod) {
return sortMethod(a.value, b.value);
}
for (let i = 0, len = a.key.length; i < len; i++) {
if (a.key[i] < b.key[i]) {
return -1;
}
if (a.key[i] > b.key[i]) {
return 1;
}
}
return 0;
};
return array.map(function(value, index) {
return {
value: value,
index: index,
key: getKey ? getKey(value, index) : null
};
}).sort(function(a, b) {
let order = compare(a, b);
if (!order) {
// make stable https://en.wikipedia.org/wiki/Sorting_algorithm#Stability
order = a.index - b.index;
}
return order * reverse;
}).map(item => item.value);
};
export const getColumnById = function(table, columnId) {
let column = null;
table.columns.forEach(function(item) {
if (item.id === columnId) {
column = item;
}
});
return column;
};
export const getColumnByKey = function(table, columnKey) {
let column = null;
for (let i = 0; i < table.columns.length; i++) {
const item = table.columns[i];
if (item.columnKey === columnKey) {
column = item;
break;
}
}
return column;
};
export const getColumnByCell = function(table, cell) {
const matches = (cell.className || '').match(/el-table_[^\s]+/gm);
if (matches) {
return getColumnById(table, matches[0]);
}
return null;
};
export const getRowIdentity = (row, rowKey) => {
if (!row) throw new Error('row is required when get row identity');
if (typeof rowKey === 'string') {
if (rowKey.indexOf('.') < 0) {
return row[rowKey];
}
let key = rowKey.split('.');
let current = row;
for (let i = 0; i < key.length; i++) {
current = current[key[i]];
}
return current;
} else if (typeof rowKey === 'function') {
return rowKey.call(null, row);
}
};