first
This commit is contained in:
8
packages/table/index.js
Normal file
8
packages/table/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import ElTable from './src/table';
|
||||
|
||||
/* istanbul ignore next */
|
||||
ElTable.install = function(Vue) {
|
||||
Vue.component(ElTable.name, ElTable);
|
||||
};
|
||||
|
||||
export default ElTable;
|
123
packages/table/src/config.js
Normal file
123
packages/table/src/config.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { getPropByPath } from 'element-ui/src/utils/util';
|
||||
|
||||
export const cellStarts = {
|
||||
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: ''
|
||||
}
|
||||
};
|
||||
|
||||
// 这些选项不应该被覆盖
|
||||
export const cellForced = {
|
||||
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 }) {
|
||||
const classes = ['el-table__expand-icon'];
|
||||
if (store.states.expandRows.indexOf(row) > -1) {
|
||||
classes.push('el-table__expand-icon--expanded');
|
||||
}
|
||||
const callback = function(e) {
|
||||
e.stopPropagation();
|
||||
store.toggleRowExpansion(row);
|
||||
};
|
||||
return (<div class={ classes }
|
||||
on-click={callback}>
|
||||
<i class='el-icon el-icon-arrow-right'></i>
|
||||
</div>);
|
||||
},
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
className: 'el-table__expand-column'
|
||||
}
|
||||
};
|
||||
|
||||
export function defaultRenderCell(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;
|
||||
}
|
||||
|
||||
export function treeCellPrefix(h, { row, treeNode, store }) {
|
||||
if (!treeNode) return null;
|
||||
const ele = [];
|
||||
const callback = function(e) {
|
||||
e.stopPropagation();
|
||||
store.loadOrToggle(row);
|
||||
};
|
||||
if (treeNode.indent) {
|
||||
ele.push(<span class="el-table__indent" style={{'padding-left': treeNode.indent + 'px'}}></span>);
|
||||
}
|
||||
if (typeof treeNode.expanded === 'boolean' && !treeNode.noLazyChildren) {
|
||||
const expandClasses = ['el-table__expand-icon', treeNode.expanded ? 'el-table__expand-icon--expanded' : ''];
|
||||
let iconClasses = ['el-icon-arrow-right'];
|
||||
if (treeNode.loading) {
|
||||
iconClasses = ['el-icon-loading'];
|
||||
}
|
||||
ele.push(<div class={ expandClasses }
|
||||
on-click={ callback }>
|
||||
<i class={ iconClasses }></i>
|
||||
</div>);
|
||||
} else {
|
||||
ele.push(<span class="el-table__placeholder"></span>);
|
||||
}
|
||||
return ele;
|
||||
}
|
28
packages/table/src/dropdown.js
Normal file
28
packages/table/src/dropdown.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
194
packages/table/src/filter-panel.vue
Normal file
194
packages/table/src/filter-panel.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<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';
|
||||
import ElScrollbar from 'element-ui/packages/scrollbar';
|
||||
|
||||
export default {
|
||||
name: 'ElTableFilterPanel',
|
||||
|
||||
mixins: [Popper, Locale],
|
||||
|
||||
directives: {
|
||||
Clickoutside
|
||||
},
|
||||
|
||||
components: {
|
||||
ElCheckbox,
|
||||
ElCheckboxGroup,
|
||||
ElScrollbar
|
||||
},
|
||||
|
||||
props: {
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-end'
|
||||
}
|
||||
},
|
||||
|
||||
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
packages/table/src/layout-observer.js
Normal file
68
packages/table/src/layout-observer.js
Normal 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(layout) {
|
||||
const cols = this.$el.querySelectorAll('colgroup > col');
|
||||
if (!cols.length) return;
|
||||
const flattenColumns = layout.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';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
76
packages/table/src/store/current.js
Normal file
76
packages/table/src/store/current.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { arrayFind } from 'element-ui/src/utils/util';
|
||||
import { getRowIdentity } from '../util';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
// 不可响应的,设置 currentRowKey 时,data 不一定存在,也许无法算出正确的 currentRow
|
||||
// 把该值缓存一下,当用户点击修改 currentRow 时,把该值重置为 null
|
||||
_currentRowKey: null,
|
||||
currentRow: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
setCurrentRowKey(key) {
|
||||
this.assertRowKey();
|
||||
this.states._currentRowKey = key;
|
||||
this.setCurrentRowByKey(key);
|
||||
},
|
||||
|
||||
restoreCurrentRowKey() {
|
||||
this.states._currentRowKey = null;
|
||||
},
|
||||
|
||||
setCurrentRowByKey(key) {
|
||||
const { states } = this;
|
||||
const { data = [], rowKey } = states;
|
||||
let currentRow = null;
|
||||
if (rowKey) {
|
||||
currentRow = arrayFind(data, item => getRowIdentity(item, rowKey) === key);
|
||||
}
|
||||
states.currentRow = currentRow;
|
||||
},
|
||||
|
||||
updateCurrentRow(currentRow) {
|
||||
const { states, table } = this;
|
||||
const oldCurrentRow = states.currentRow;
|
||||
if (currentRow && currentRow !== oldCurrentRow) {
|
||||
states.currentRow = currentRow;
|
||||
table.$emit('current-change', currentRow, oldCurrentRow);
|
||||
return;
|
||||
}
|
||||
if (!currentRow && oldCurrentRow) {
|
||||
states.currentRow = null;
|
||||
table.$emit('current-change', null, oldCurrentRow);
|
||||
}
|
||||
},
|
||||
|
||||
updateCurrentRowData() {
|
||||
const { states, table } = this;
|
||||
const { rowKey, _currentRowKey } = states;
|
||||
// data 为 null 时,解构时的默认值会被忽略
|
||||
const data = states.data || [];
|
||||
const oldCurrentRow = states.currentRow;
|
||||
|
||||
// 当 currentRow 不在 data 中时尝试更新数据
|
||||
if (data.indexOf(oldCurrentRow) === -1 && oldCurrentRow) {
|
||||
if (rowKey) {
|
||||
const currentRowKey = getRowIdentity(oldCurrentRow, rowKey);
|
||||
this.setCurrentRowByKey(currentRowKey);
|
||||
} else {
|
||||
states.currentRow = null;
|
||||
}
|
||||
if (states.currentRow === null) {
|
||||
table.$emit('current-change', null, oldCurrentRow);
|
||||
}
|
||||
} else if (_currentRowKey) {
|
||||
// 把初始时下设置的 rowKey 转化成 rowData
|
||||
this.setCurrentRowByKey(_currentRowKey);
|
||||
this.restoreCurrentRowKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
65
packages/table/src/store/expand.js
Normal file
65
packages/table/src/store/expand.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { toggleRowStatus, getKeysMap, getRowIdentity } from '../util';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
defaultExpandAll: false,
|
||||
expandRows: []
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateExpandRows() {
|
||||
const { data = [], rowKey, defaultExpandAll, expandRows } = this.states;
|
||||
if (defaultExpandAll) {
|
||||
this.states.expandRows = data.slice();
|
||||
} else if (rowKey) {
|
||||
// TODO:这里的代码可以优化
|
||||
const expandRowsMap = getKeysMap(expandRows, rowKey);
|
||||
this.states.expandRows = data.reduce((prev, row) => {
|
||||
const rowId = getRowIdentity(row, rowKey);
|
||||
const rowInfo = expandRowsMap[rowId];
|
||||
if (rowInfo) {
|
||||
prev.push(row);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
} else {
|
||||
this.states.expandRows = [];
|
||||
}
|
||||
},
|
||||
|
||||
toggleRowExpansion(row, expanded) {
|
||||
const changed = toggleRowStatus(this.states.expandRows, row, expanded);
|
||||
if (changed) {
|
||||
this.table.$emit('expand-change', row, this.states.expandRows.slice());
|
||||
this.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
setExpandRowKeys(rowKeys) {
|
||||
this.assertRowKey();
|
||||
// TODO:这里的代码可以优化
|
||||
const { data, rowKey } = this.states;
|
||||
const keysMap = getKeysMap(data, rowKey);
|
||||
this.states.expandRows = rowKeys.reduce((prev, cur) => {
|
||||
const info = keysMap[cur];
|
||||
if (info) {
|
||||
prev.push(info.row);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
},
|
||||
|
||||
isRowExpanded(row) {
|
||||
const { expandRows = [], rowKey } = this.states;
|
||||
if (rowKey) {
|
||||
const expandMap = getKeysMap(expandRows, rowKey);
|
||||
return !!expandMap[getRowIdentity(row, rowKey)];
|
||||
}
|
||||
return expandRows.indexOf(row) !== -1;
|
||||
}
|
||||
}
|
||||
};
|
41
packages/table/src/store/helper.js
Normal file
41
packages/table/src/store/helper.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import Store from './index';
|
||||
import debounce from 'throttle-debounce/debounce';
|
||||
|
||||
export function createStore(table, initialState = {}) {
|
||||
if (!table) {
|
||||
throw new Error('Table is required.');
|
||||
}
|
||||
|
||||
const store = new Store();
|
||||
store.table = table;
|
||||
// fix https://github.com/ElemeFE/element/issues/14075
|
||||
// related pr https://github.com/ElemeFE/element/pull/14146
|
||||
store.toggleAllSelection = debounce(10, store._toggleAllSelection);
|
||||
Object.keys(initialState).forEach(key => {
|
||||
store.states[key] = initialState[key];
|
||||
});
|
||||
return store;
|
||||
}
|
||||
|
||||
export function mapStates(mapper) {
|
||||
const res = {};
|
||||
Object.keys(mapper).forEach(key => {
|
||||
const value = mapper[key];
|
||||
let fn;
|
||||
if (typeof value === 'string') {
|
||||
fn = function() {
|
||||
return this.store.states[value];
|
||||
};
|
||||
} else if (typeof value === 'function') {
|
||||
fn = function() {
|
||||
return value.call(this, this.store.states);
|
||||
};
|
||||
} else {
|
||||
console.error('invalid value type');
|
||||
}
|
||||
if (fn) {
|
||||
res[key] = fn;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
147
packages/table/src/store/index.js
Normal file
147
packages/table/src/store/index.js
Normal file
@@ -0,0 +1,147 @@
|
||||
import Vue from 'vue';
|
||||
import Watcher from './watcher';
|
||||
import { arrayFind } from 'element-ui/src/utils/util';
|
||||
|
||||
Watcher.prototype.mutations = {
|
||||
setData(states, data) {
|
||||
const dataInstanceChanged = states._data !== data;
|
||||
states._data = data;
|
||||
|
||||
this.execQuery();
|
||||
// 数据变化,更新部分数据。
|
||||
// 没有使用 computed,而是手动更新部分数据 https://github.com/vuejs/vue/issues/6660#issuecomment-331417140
|
||||
this.updateCurrentRowData();
|
||||
this.updateExpandRows();
|
||||
if (states.reserveSelection) {
|
||||
this.assertRowKey();
|
||||
this.updateSelectionByRowKey();
|
||||
} else {
|
||||
if (dataInstanceChanged) {
|
||||
this.clearSelection();
|
||||
} else {
|
||||
this.cleanSelection();
|
||||
}
|
||||
}
|
||||
this.updateAllSelected();
|
||||
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
sort(states, options) {
|
||||
const { prop, order, init } = options;
|
||||
if (prop) {
|
||||
const column = arrayFind(states.columns, column => column.property === prop);
|
||||
if (column) {
|
||||
column.order = order;
|
||||
this.updateSort(column, prop, order);
|
||||
this.commit('changeSortCondition', { init });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
changeSortCondition(states, options) {
|
||||
// 修复 pr https://github.com/ElemeFE/element/pull/15012 导致的 bug
|
||||
const { sortingColumn: column, sortProp: prop, sortOrder: order } = states;
|
||||
if (order === null) {
|
||||
states.sortingColumn = null;
|
||||
states.sortProp = null;
|
||||
}
|
||||
const ingore = { filter: true };
|
||||
this.execQuery(ingore);
|
||||
|
||||
if (!options || !(options.silent || options.init)) {
|
||||
this.table.$emit('sort-change', {
|
||||
column,
|
||||
prop,
|
||||
order
|
||||
});
|
||||
}
|
||||
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
filterChange(states, options) {
|
||||
let { column, values, silent } = options;
|
||||
const newFilters = this.updateFilters(column, values);
|
||||
|
||||
this.execQuery();
|
||||
|
||||
if (!silent) {
|
||||
this.table.$emit('filter-change', newFilters);
|
||||
}
|
||||
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
toggleAllSelection() {
|
||||
this.toggleAllSelection();
|
||||
},
|
||||
|
||||
rowSelectedChanged(states, row) {
|
||||
this.toggleRowSelection(row);
|
||||
this.updateAllSelected();
|
||||
},
|
||||
|
||||
setHoverRow(states, row) {
|
||||
states.hoverRow = row;
|
||||
},
|
||||
|
||||
setCurrentRow(states, row) {
|
||||
this.updateCurrentRow(row);
|
||||
}
|
||||
};
|
||||
|
||||
Watcher.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}`);
|
||||
}
|
||||
};
|
||||
|
||||
Watcher.prototype.updateTableScrollY = function() {
|
||||
Vue.nextTick(this.table.updateScrollY);
|
||||
};
|
||||
|
||||
export default Watcher;
|
208
packages/table/src/store/tree.js
Normal file
208
packages/table/src/store/tree.js
Normal file
@@ -0,0 +1,208 @@
|
||||
import { walkTreeNode, getRowIdentity } from '../util';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
// defaultExpandAll 存在于 expand.js 中,这里不重复添加
|
||||
// 在展开行中,expandRowKeys 会被转化成 expandRows,expandRowKeys 这个属性只是记录了 TreeTable 行的展开
|
||||
// TODO: 拆分为独立的 TreeTable,统一用法
|
||||
expandRowKeys: [],
|
||||
treeData: {},
|
||||
indent: 16,
|
||||
lazy: false,
|
||||
lazyTreeNodeMap: {},
|
||||
lazyColumnIdentifier: 'hasChildren',
|
||||
childrenColumnName: 'children'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 嵌入型的数据,watch 无法是检测到变化 https://github.com/ElemeFE/element/issues/14998
|
||||
// TODO: 使用 computed 解决该问题,是否会造成性能问题?
|
||||
// @return { id: { level, children } }
|
||||
normalizedData() {
|
||||
if (!this.states.rowKey) return {};
|
||||
const data = this.states.data || [];
|
||||
return this.normalize(data);
|
||||
},
|
||||
// @return { id: { children } }
|
||||
// 针对懒加载的情形,不处理嵌套数据
|
||||
normalizedLazyNode() {
|
||||
const { rowKey, lazyTreeNodeMap, lazyColumnIdentifier } = this.states;
|
||||
const keys = Object.keys(lazyTreeNodeMap);
|
||||
const res = {};
|
||||
if (!keys.length) return res;
|
||||
keys.forEach(key => {
|
||||
if (lazyTreeNodeMap[key].length) {
|
||||
const item = { children: [] };
|
||||
lazyTreeNodeMap[key].forEach(row => {
|
||||
const currentRowKey = getRowIdentity(row, rowKey);
|
||||
item.children.push(currentRowKey);
|
||||
if (row[lazyColumnIdentifier] && !res[currentRowKey]) {
|
||||
res[currentRowKey] = { children: [] };
|
||||
}
|
||||
});
|
||||
res[key] = item;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
normalizedData: 'updateTreeData',
|
||||
normalizedLazyNode: 'updateTreeData'
|
||||
},
|
||||
|
||||
methods: {
|
||||
normalize(data) {
|
||||
const {
|
||||
childrenColumnName,
|
||||
lazyColumnIdentifier,
|
||||
rowKey,
|
||||
lazy
|
||||
} = this.states;
|
||||
const res = {};
|
||||
walkTreeNode(
|
||||
data,
|
||||
(parent, children, level) => {
|
||||
const parentId = getRowIdentity(parent, rowKey);
|
||||
if (Array.isArray(children)) {
|
||||
res[parentId] = {
|
||||
children: children.map(row => getRowIdentity(row, rowKey)),
|
||||
level
|
||||
};
|
||||
} else if (lazy) {
|
||||
// 当 children 不存在且 lazy 为 true,该节点即为懒加载的节点
|
||||
res[parentId] = {
|
||||
children: [],
|
||||
lazy: true,
|
||||
level
|
||||
};
|
||||
}
|
||||
},
|
||||
childrenColumnName,
|
||||
lazyColumnIdentifier
|
||||
);
|
||||
return res;
|
||||
},
|
||||
|
||||
updateTreeData() {
|
||||
const nested = this.normalizedData;
|
||||
const normalizedLazyNode = this.normalizedLazyNode;
|
||||
const keys = Object.keys(nested);
|
||||
const newTreeData = {};
|
||||
if (keys.length) {
|
||||
const {
|
||||
treeData: oldTreeData,
|
||||
defaultExpandAll,
|
||||
expandRowKeys,
|
||||
lazy
|
||||
} = this.states;
|
||||
const rootLazyRowKeys = [];
|
||||
const getExpanded = (oldValue, key) => {
|
||||
const included =
|
||||
defaultExpandAll ||
|
||||
(expandRowKeys && expandRowKeys.indexOf(key) !== -1);
|
||||
return !!((oldValue && oldValue.expanded) || included);
|
||||
};
|
||||
// 合并 expanded 与 display,确保数据刷新后,状态不变
|
||||
keys.forEach(key => {
|
||||
const oldValue = oldTreeData[key];
|
||||
const newValue = { ...nested[key] };
|
||||
newValue.expanded = getExpanded(oldValue, key);
|
||||
if (newValue.lazy) {
|
||||
const { loaded = false, loading = false } = oldValue || {};
|
||||
newValue.loaded = !!loaded;
|
||||
newValue.loading = !!loading;
|
||||
rootLazyRowKeys.push(key);
|
||||
}
|
||||
newTreeData[key] = newValue;
|
||||
});
|
||||
// 根据懒加载数据更新 treeData
|
||||
const lazyKeys = Object.keys(normalizedLazyNode);
|
||||
if (lazy && lazyKeys.length && rootLazyRowKeys.length) {
|
||||
lazyKeys.forEach(key => {
|
||||
const oldValue = oldTreeData[key];
|
||||
const lazyNodeChildren = normalizedLazyNode[key].children;
|
||||
if (rootLazyRowKeys.indexOf(key) !== -1) {
|
||||
// 懒加载的 root 节点,更新一下原有的数据,原来的 children 一定是空数组
|
||||
if (newTreeData[key].children.length !== 0) {
|
||||
throw new Error('[ElTable]children must be an empty array.');
|
||||
}
|
||||
newTreeData[key].children = lazyNodeChildren;
|
||||
} else {
|
||||
const { loaded = false, loading = false } = oldValue || {};
|
||||
newTreeData[key] = {
|
||||
lazy: true,
|
||||
loaded: !!loaded,
|
||||
loading: !!loading,
|
||||
expanded: getExpanded(oldValue, key),
|
||||
children: lazyNodeChildren,
|
||||
level: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.states.treeData = newTreeData;
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
updateTreeExpandKeys(value) {
|
||||
this.states.expandRowKeys = value;
|
||||
this.updateTreeData();
|
||||
},
|
||||
|
||||
toggleTreeExpansion(row, expanded) {
|
||||
this.assertRowKey();
|
||||
|
||||
const { rowKey, treeData } = this.states;
|
||||
const id = getRowIdentity(row, rowKey);
|
||||
const data = id && treeData[id];
|
||||
if (id && data && ('expanded' in data)) {
|
||||
const oldExpanded = data.expanded;
|
||||
expanded = typeof expanded === 'undefined' ? !data.expanded : expanded;
|
||||
treeData[id].expanded = expanded;
|
||||
if (oldExpanded !== expanded) {
|
||||
this.table.$emit('expand-change', row, expanded);
|
||||
}
|
||||
this.updateTableScrollY();
|
||||
}
|
||||
},
|
||||
|
||||
loadOrToggle(row) {
|
||||
this.assertRowKey();
|
||||
const { lazy, treeData, rowKey } = this.states;
|
||||
const id = getRowIdentity(row, rowKey);
|
||||
const data = treeData[id];
|
||||
if (lazy && data && 'loaded' in data && !data.loaded) {
|
||||
this.loadData(row, id, data);
|
||||
} else {
|
||||
this.toggleTreeExpansion(row);
|
||||
}
|
||||
},
|
||||
|
||||
loadData(row, key, treeNode) {
|
||||
const { load } = this.table;
|
||||
const { lazyTreeNodeMap, treeData } = this.states;
|
||||
if (load && !treeData[key].loaded) {
|
||||
treeData[key].loading = true;
|
||||
load(row, treeNode, data => {
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('[ElTable] data must be an array');
|
||||
}
|
||||
treeData[key].loading = false;
|
||||
treeData[key].loaded = true;
|
||||
treeData[key].expanded = true;
|
||||
if (data.length) {
|
||||
this.$set(lazyTreeNodeMap, key, data);
|
||||
}
|
||||
this.table.$emit('expand-change', row, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
381
packages/table/src/store/watcher.js
Normal file
381
packages/table/src/store/watcher.js
Normal file
@@ -0,0 +1,381 @@
|
||||
import Vue from 'vue';
|
||||
import merge from 'element-ui/src/utils/merge';
|
||||
import { getKeysMap, getRowIdentity, getColumnById, getColumnByKey, orderBy, toggleRowStatus } from '../util';
|
||||
import expand from './expand';
|
||||
import current from './current';
|
||||
import tree from './tree';
|
||||
|
||||
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 doFlattenColumns = (columns) => {
|
||||
const result = [];
|
||||
columns.forEach((column) => {
|
||||
if (column.children) {
|
||||
result.push.apply(result, doFlattenColumns(column.children));
|
||||
} else {
|
||||
result.push(column);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
// 3.0 版本后要求必须设置该属性
|
||||
rowKey: null,
|
||||
|
||||
// 渲染的数据来源,是对 table 中的 data 过滤排序后的结果
|
||||
data: [],
|
||||
|
||||
// 是否包含固定列
|
||||
isComplex: false,
|
||||
|
||||
// 列
|
||||
_columns: [], // 不可响应的
|
||||
originColumns: [],
|
||||
columns: [],
|
||||
fixedColumns: [],
|
||||
rightFixedColumns: [],
|
||||
leafColumns: [],
|
||||
fixedLeafColumns: [],
|
||||
rightFixedLeafColumns: [],
|
||||
leafColumnsLength: 0,
|
||||
fixedLeafColumnsLength: 0,
|
||||
rightFixedLeafColumnsLength: 0,
|
||||
|
||||
// 选择
|
||||
isAllSelected: false,
|
||||
selection: [],
|
||||
reserveSelection: false,
|
||||
selectOnIndeterminate: false,
|
||||
selectable: null,
|
||||
|
||||
// 过滤
|
||||
filters: {}, // 不可响应的
|
||||
filteredData: null,
|
||||
|
||||
// 排序
|
||||
sortingColumn: null,
|
||||
sortProp: null,
|
||||
sortOrder: null,
|
||||
|
||||
hoverRow: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
mixins: [expand, current, tree],
|
||||
|
||||
methods: {
|
||||
// 检查 rowKey 是否存在
|
||||
assertRowKey() {
|
||||
const rowKey = this.states.rowKey;
|
||||
if (!rowKey) throw new Error('[ElTable] prop row-key is required');
|
||||
},
|
||||
|
||||
// 更新列
|
||||
updateColumns() {
|
||||
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;
|
||||
},
|
||||
|
||||
// 更新 DOM
|
||||
scheduleLayout(needUpdateColumns) {
|
||||
if (needUpdateColumns) {
|
||||
this.updateColumns();
|
||||
}
|
||||
this.table.debouncedUpdateLayout();
|
||||
},
|
||||
|
||||
// 选择
|
||||
isSelected(row) {
|
||||
const { selection = [] } = this.states;
|
||||
return selection.indexOf(row) > -1;
|
||||
},
|
||||
|
||||
clearSelection() {
|
||||
const states = this.states;
|
||||
states.isAllSelected = false;
|
||||
const oldSelection = states.selection;
|
||||
if (oldSelection.length) {
|
||||
states.selection = [];
|
||||
this.table.$emit('selection-change', []);
|
||||
}
|
||||
},
|
||||
|
||||
cleanSelection() {
|
||||
const states = this.states;
|
||||
const { data, rowKey, selection } = states;
|
||||
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 => data.indexOf(item) === -1);
|
||||
}
|
||||
if (deleted.length) {
|
||||
const newSelection = selection.filter(item => deleted.indexOf(item) === -1);
|
||||
states.selection = newSelection;
|
||||
this.table.$emit('selection-change', newSelection.slice());
|
||||
}
|
||||
},
|
||||
|
||||
toggleRowSelection(row, selected, emitChange = true) {
|
||||
const changed = toggleRowStatus(this.states.selection, row, selected);
|
||||
if (changed) {
|
||||
const newSelection = (this.states.selection || []).slice();
|
||||
// 调用 API 修改选中值,不触发 select 事件
|
||||
if (emitChange) {
|
||||
this.table.$emit('select', newSelection, row);
|
||||
}
|
||||
this.table.$emit('selection-change', newSelection);
|
||||
}
|
||||
},
|
||||
|
||||
_toggleAllSelection() {
|
||||
const states = this.states;
|
||||
const { data = [], selection } = states;
|
||||
// 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);
|
||||
states.isAllSelected = value;
|
||||
|
||||
let selectionChanged = false;
|
||||
data.forEach((row, index) => {
|
||||
if (states.selectable) {
|
||||
if (states.selectable.call(null, row, index) && toggleRowStatus(selection, row, value)) {
|
||||
selectionChanged = true;
|
||||
}
|
||||
} else {
|
||||
if (toggleRowStatus(selection, row, value)) {
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (selectionChanged) {
|
||||
this.table.$emit('selection-change', selection ? selection.slice() : []);
|
||||
}
|
||||
this.table.$emit('select-all', selection);
|
||||
},
|
||||
|
||||
updateSelectionByRowKey() {
|
||||
const states = this.states;
|
||||
const { selection, rowKey, data } = states;
|
||||
const selectedMap = getKeysMap(selection, rowKey);
|
||||
data.forEach(row => {
|
||||
const rowId = getRowIdentity(row, rowKey);
|
||||
const rowInfo = selectedMap[rowId];
|
||||
if (rowInfo) {
|
||||
selection[rowInfo.index] = row;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateAllSelected() {
|
||||
const states = this.states;
|
||||
const { selection, rowKey, selectable } = states;
|
||||
// data 为 null 时,解构时的默认值会被忽略
|
||||
const data = states.data || [];
|
||||
if (data.length === 0) {
|
||||
states.isAllSelected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedMap;
|
||||
if (rowKey) {
|
||||
selectedMap = getKeysMap(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;
|
||||
},
|
||||
|
||||
// 过滤与排序
|
||||
updateFilters(columns, values) {
|
||||
if (!Array.isArray(columns)) {
|
||||
columns = [columns];
|
||||
}
|
||||
const states = this.states;
|
||||
const filters = {};
|
||||
columns.forEach(col => {
|
||||
states.filters[col.id] = values;
|
||||
filters[col.columnKey || col.id] = values;
|
||||
});
|
||||
|
||||
return filters;
|
||||
},
|
||||
|
||||
updateSort(column, prop, order) {
|
||||
if (this.states.sortingColumn && this.states.sortingColumn !== column) {
|
||||
this.states.sortingColumn.order = null;
|
||||
}
|
||||
this.states.sortingColumn = column;
|
||||
this.states.sortProp = prop;
|
||||
this.states.sortOrder = order;
|
||||
},
|
||||
|
||||
execFilter() {
|
||||
const states = this.states;
|
||||
const { _data, filters } = states;
|
||||
let data = _data;
|
||||
|
||||
Object.keys(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;
|
||||
},
|
||||
|
||||
execSort() {
|
||||
const states = this.states;
|
||||
states.data = sortData(states.filteredData, states);
|
||||
},
|
||||
|
||||
// 根据 filters 与 sort 去过滤 data
|
||||
execQuery(ignore) {
|
||||
if (!(ignore && ignore.filter)) {
|
||||
this.execFilter();
|
||||
}
|
||||
this.execSort();
|
||||
},
|
||||
|
||||
clearFilter(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) {
|
||||
// TODO: 优化这里的代码
|
||||
panels[key].filteredValue = [];
|
||||
}
|
||||
});
|
||||
this.commit('filterChange', {
|
||||
column: columns,
|
||||
values: [],
|
||||
silent: true,
|
||||
multi: true
|
||||
});
|
||||
} else {
|
||||
keys.forEach(key => {
|
||||
// TODO: 优化这里的代码
|
||||
panels[key].filteredValue = [];
|
||||
});
|
||||
|
||||
states.filters = {};
|
||||
this.commit('filterChange', {
|
||||
column: {},
|
||||
values: [],
|
||||
silent: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
clearSort() {
|
||||
const states = this.states;
|
||||
if (!states.sortingColumn) return;
|
||||
|
||||
this.updateSort(null, null, null);
|
||||
this.commit('changeSortCondition', {
|
||||
silent: true
|
||||
});
|
||||
},
|
||||
|
||||
// 适配层,expand-row-keys 在 Expand 与 TreeTable 中都有使用
|
||||
setExpandRowKeysAdapter(val) {
|
||||
// 这里会触发额外的计算,但为了兼容性,暂时这么做
|
||||
this.setExpandRowKeys(val);
|
||||
this.updateTreeExpandKeys(val);
|
||||
},
|
||||
|
||||
// 展开行与 TreeTable 都要使用
|
||||
toggleRowExpansionAdapter(row, expanded) {
|
||||
const hasExpandColumn = this.states.columns.some(({ type }) => type === 'expand');
|
||||
if (hasExpandColumn) {
|
||||
this.toggleRowExpansion(row, expanded);
|
||||
} else {
|
||||
this.toggleTreeExpansion(row, expanded);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
483
packages/table/src/table-body.js
Normal file
483
packages/table/src/table-body.js
Normal file
@@ -0,0 +1,483 @@
|
||||
import { arrayFindIndex } from 'element-ui/src/utils/util';
|
||||
import { getCell, getColumnByCell, getRowIdentity } from './util';
|
||||
import { getStyle, hasClass, removeClass, addClass } 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';
|
||||
import { mapStates } from './store/helper';
|
||||
|
||||
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 data = this.data || [];
|
||||
return (
|
||||
<table
|
||||
class="el-table__body"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0">
|
||||
<colgroup>
|
||||
{
|
||||
this.columns.map(column => <col name={ column.id } key={column.id} />)
|
||||
}
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{
|
||||
data.reduce((acc, row) => {
|
||||
return acc.concat(this.wrappedRowRender(row, acc.length));
|
||||
}, [])
|
||||
}
|
||||
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
},
|
||||
|
||||
computed: {
|
||||
table() {
|
||||
return this.$parent;
|
||||
},
|
||||
|
||||
...mapStates({
|
||||
data: 'data',
|
||||
columns: 'columns',
|
||||
treeIndent: 'indent',
|
||||
leftFixedLeafCount: 'fixedLeafColumnsLength',
|
||||
rightFixedLeafCount: 'rightFixedLeafColumnsLength',
|
||||
columnsCount: states => states.columns.length,
|
||||
leftFixedCount: states => states.fixedColumns.length,
|
||||
rightFixedCount: states => states.rightFixedColumns.length,
|
||||
hasExpandColumn: states => states.columns.some(({ type }) => type === 'expand')
|
||||
}),
|
||||
|
||||
firstDefaultColumnIndex() {
|
||||
return arrayFindIndex(this.columns, ({ type }) => type === 'default');
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
// don't trigger getter of currentRow in getCellClass. see https://jsfiddle.net/oe2b4hqt/
|
||||
// update DOM manually. see https://github.com/ElemeFE/element/pull/13954/files#diff-9b450c00d0a9dec0ffad5a3176972e40
|
||||
'store.states.hoverRow'(newVal, oldVal) {
|
||||
if (!this.store.states.isComplex || this.$isServer) return;
|
||||
let raf = window.requestAnimationFrame;
|
||||
if (!raf) {
|
||||
raf = (fn) => setTimeout(fn, 16);
|
||||
}
|
||||
raf(() => {
|
||||
const rows = this.$el.querySelectorAll('.el-table__row');
|
||||
const oldRow = rows[oldVal];
|
||||
const newRow = rows[newVal];
|
||||
if (oldRow) {
|
||||
removeClass(oldRow, 'hover-row');
|
||||
}
|
||||
if (newRow) {
|
||||
addClass(newRow, 'hover-row');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
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 || null;
|
||||
},
|
||||
|
||||
getRowClass(row, rowIndex) {
|
||||
const classes = ['el-table__row'];
|
||||
if (this.table.highlightCurrentRow && row === this.store.states.currentRow) {
|
||||
classes.push('current-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;
|
||||
},
|
||||
|
||||
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(' ');
|
||||
},
|
||||
|
||||
getColspanRealWidth(columns, colspan, index) {
|
||||
if (colspan < 1) {
|
||||
return columns[index].realWidth;
|
||||
}
|
||||
const widthArr = columns.map(({ realWidth }) => realWidth).slice(index, index + colspan);
|
||||
return widthArr.reduce((acc, width) => acc + width, -1);
|
||||
},
|
||||
|
||||
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: debounce(30, function(index) {
|
||||
this.store.commit('setHoverRow', index);
|
||||
}),
|
||||
|
||||
handleMouseLeave: debounce(30, function() {
|
||||
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);
|
||||
},
|
||||
|
||||
rowRender(row, $index, treeRowData) {
|
||||
const { treeIndent, columns, firstDefaultColumnIndex } = this;
|
||||
const columnsHidden = columns.map((column, index) => this.isColumnHidden(index));
|
||||
const rowClasses = this.getRowClass(row, $index);
|
||||
let display = true;
|
||||
if (treeRowData) {
|
||||
rowClasses.push('el-table__row--level-' + treeRowData.level);
|
||||
display = treeRowData.display;
|
||||
}
|
||||
// 指令 v-show 会覆盖 row-style 中 display
|
||||
// 使用 :style 代替 v-show https://github.com/ElemeFE/element/issues/16995
|
||||
let displayStyle = display ? null : {
|
||||
display: 'none'
|
||||
};
|
||||
return (<tr
|
||||
style={ [displayStyle, this.getRowStyle(row, $index)] }
|
||||
class={ rowClasses }
|
||||
key={ this.getKeyOfRow(row, $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 }>
|
||||
{
|
||||
columns.map((column, cellIndex) => {
|
||||
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
|
||||
if (!rowspan || !colspan) {
|
||||
return null;
|
||||
}
|
||||
const columnData = { ...column };
|
||||
columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex);
|
||||
const data = {
|
||||
store: this.store,
|
||||
_self: this.context || this.table.$vnode.context,
|
||||
column: columnData,
|
||||
row,
|
||||
$index
|
||||
};
|
||||
if (cellIndex === firstDefaultColumnIndex && treeRowData) {
|
||||
data.treeNode = {
|
||||
indent: treeRowData.level * treeIndent,
|
||||
level: treeRowData.level
|
||||
};
|
||||
if (typeof treeRowData.expanded === 'boolean') {
|
||||
data.treeNode.expanded = treeRowData.expanded;
|
||||
// 表明是懒加载
|
||||
if ('loading' in treeRowData) {
|
||||
data.treeNode.loading = treeRowData.loading;
|
||||
}
|
||||
if ('noLazyChildren' in treeRowData) {
|
||||
data.treeNode.noLazyChildren = treeRowData.noLazyChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
this.$createElement,
|
||||
data,
|
||||
columnsHidden[cellIndex]
|
||||
)
|
||||
}
|
||||
</td>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tr>);
|
||||
},
|
||||
|
||||
wrappedRowRender(row, $index) {
|
||||
const store = this.store;
|
||||
const { isRowExpanded, assertRowKey } = store;
|
||||
const { treeData, lazyTreeNodeMap, childrenColumnName, rowKey } = store.states;
|
||||
if (this.hasExpandColumn && isRowExpanded(row)) {
|
||||
const renderExpanded = this.table.renderExpanded;
|
||||
const tr = this.rowRender(row, $index);
|
||||
if (!renderExpanded) {
|
||||
console.error('[Element Error]renderExpanded is required.');
|
||||
return tr;
|
||||
}
|
||||
// 使用二维数组,避免修改 $index
|
||||
return [[
|
||||
tr,
|
||||
<tr key={'expanded-row__' + tr.key}>
|
||||
<td colspan={ this.columnsCount } class="el-table__expanded-cell">
|
||||
{ renderExpanded(this.$createElement, { row, $index, store: this.store }) }
|
||||
</td>
|
||||
</tr>]];
|
||||
} else if (Object.keys(treeData).length) {
|
||||
assertRowKey();
|
||||
// TreeTable 时,rowKey 必须由用户设定,不使用 getKeyOfRow 计算
|
||||
// 在调用 rowRender 函数时,仍然会计算 rowKey,不太好的操作
|
||||
const key = getRowIdentity(row, rowKey);
|
||||
let cur = treeData[key];
|
||||
let treeRowData = null;
|
||||
if (cur) {
|
||||
treeRowData = {
|
||||
expanded: cur.expanded,
|
||||
level: cur.level,
|
||||
display: true
|
||||
};
|
||||
if (typeof cur.lazy === 'boolean') {
|
||||
if (typeof cur.loaded === 'boolean' && cur.loaded) {
|
||||
treeRowData.noLazyChildren = !(cur.children && cur.children.length);
|
||||
}
|
||||
treeRowData.loading = cur.loading;
|
||||
}
|
||||
}
|
||||
const tmp = [this.rowRender(row, $index, treeRowData)];
|
||||
// 渲染嵌套数据
|
||||
if (cur) {
|
||||
// currentRow 记录的是 index,所以还需主动增加 TreeTable 的 index
|
||||
let i = 0;
|
||||
const traverse = (children, parent) => {
|
||||
if (!(children && children.length && parent)) return;
|
||||
children.forEach(node => {
|
||||
// 父节点的 display 状态影响子节点的显示状态
|
||||
const innerTreeRowData = {
|
||||
display: parent.display && parent.expanded,
|
||||
level: parent.level + 1
|
||||
};
|
||||
const childKey = getRowIdentity(node, rowKey);
|
||||
if (childKey === undefined || childKey === null) {
|
||||
throw new Error('for nested data item, row-key is required.');
|
||||
}
|
||||
cur = { ...treeData[childKey] };
|
||||
// 对于当前节点,分成有无子节点两种情况。
|
||||
// 如果包含子节点的,设置 expanded 属性。
|
||||
// 对于它子节点的 display 属性由它本身的 expanded 与 display 共同决定。
|
||||
if (cur) {
|
||||
innerTreeRowData.expanded = cur.expanded;
|
||||
// 懒加载的某些节点,level 未知
|
||||
cur.level = cur.level || innerTreeRowData.level;
|
||||
cur.display = !!(cur.expanded && innerTreeRowData.display);
|
||||
if (typeof cur.lazy === 'boolean') {
|
||||
if (typeof cur.loaded === 'boolean' && cur.loaded) {
|
||||
innerTreeRowData.noLazyChildren = !(cur.children && cur.children.length);
|
||||
}
|
||||
innerTreeRowData.loading = cur.loading;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
tmp.push(this.rowRender(node, $index + i, innerTreeRowData));
|
||||
if (cur) {
|
||||
const nodes = lazyTreeNodeMap[childKey] || node[childrenColumnName];
|
||||
traverse(nodes, cur);
|
||||
}
|
||||
});
|
||||
};
|
||||
// 对于 root 节点,display 一定为 true
|
||||
cur.display = true;
|
||||
const nodes = lazyTreeNodeMap[key] || row[childrenColumnName];
|
||||
traverse(nodes, cur);
|
||||
}
|
||||
return tmp;
|
||||
} else {
|
||||
return this.rowRender(row, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
319
packages/table/src/table-column.js
Normal file
319
packages/table/src/table-column.js
Normal file
@@ -0,0 +1,319 @@
|
||||
import { cellStarts, cellForced, defaultRenderCell, treeCellPrefix } from './config';
|
||||
import { mergeOptions, parseWidth, parseMinWidth, compose } from './util';
|
||||
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||
|
||||
let columnIdSeed = 1;
|
||||
|
||||
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: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
sortMethod: Function,
|
||||
sortBy: [String, Function, Array],
|
||||
resizable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
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: []
|
||||
};
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
realWidth() {
|
||||
return parseWidth(this.width);
|
||||
},
|
||||
|
||||
realMinWidth() {
|
||||
return parseMinWidth(this.minWidth);
|
||||
},
|
||||
|
||||
realAlign() {
|
||||
return this.align ? 'is-' + this.align : null;
|
||||
},
|
||||
|
||||
realHeaderAlign() {
|
||||
return this.headerAlign ? 'is-' + this.headerAlign : this.realAlign;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getPropsData(...props) {
|
||||
return props.reduce((prev, cur) => {
|
||||
if (Array.isArray(cur)) {
|
||||
cur.forEach((key) => {
|
||||
prev[key] = this[key];
|
||||
});
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
},
|
||||
|
||||
getColumnElIndex(children, child) {
|
||||
return [].indexOf.call(children, child);
|
||||
},
|
||||
|
||||
setColumnWidth(column) {
|
||||
if (this.realWidth) {
|
||||
column.width = this.realWidth;
|
||||
}
|
||||
if (this.realMinWidth) {
|
||||
column.minWidth = this.realMinWidth;
|
||||
}
|
||||
if (!column.minWidth) {
|
||||
column.minWidth = 80;
|
||||
}
|
||||
column.realWidth = column.width === undefined ? column.minWidth : column.width;
|
||||
return column;
|
||||
},
|
||||
|
||||
setColumnForcedProps(column) {
|
||||
// 对于特定类型的 column,某些属性不允许设置
|
||||
const type = column.type;
|
||||
const source = cellForced[type] || {};
|
||||
Object.keys(source).forEach(prop => {
|
||||
let value = source[prop];
|
||||
if (value !== undefined) {
|
||||
column[prop] = prop === 'className' ? `${column[prop]} ${value}` : value;
|
||||
}
|
||||
});
|
||||
return column;
|
||||
},
|
||||
|
||||
setColumnRenders(column) {
|
||||
// renderHeader 属性不推荐使用。
|
||||
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.');
|
||||
} else if (column.type !== 'selection') {
|
||||
column.renderHeader = (h, scope) => {
|
||||
const renderHeader = this.$scopedSlots.header;
|
||||
return renderHeader ? renderHeader(scope) : column.label;
|
||||
};
|
||||
}
|
||||
|
||||
let originRenderCell = column.renderCell;
|
||||
// TODO: 这里的实现调整
|
||||
if (column.type === 'expand') {
|
||||
// 对于展开行,renderCell 不允许配置的。在上一步中已经设置过,这里需要简单封装一下。
|
||||
column.renderCell = (h, data) => (<div class="cell">
|
||||
{ originRenderCell(h, data) }
|
||||
</div>);
|
||||
this.owner.renderExpanded = (h, data) => {
|
||||
return this.$scopedSlots.default
|
||||
? this.$scopedSlots.default(data)
|
||||
: this.$slots.default;
|
||||
};
|
||||
} else {
|
||||
originRenderCell = originRenderCell || defaultRenderCell;
|
||||
// 对 renderCell 进行包装
|
||||
column.renderCell = (h, data) => {
|
||||
let children = null;
|
||||
if (this.$scopedSlots.default) {
|
||||
children = this.$scopedSlots.default(data);
|
||||
} else {
|
||||
children = originRenderCell(h, data);
|
||||
}
|
||||
const prefix = treeCellPrefix(h, data);
|
||||
const props = {
|
||||
class: 'cell',
|
||||
style: {}
|
||||
};
|
||||
if (column.showOverflowTooltip) {
|
||||
props.class += ' el-tooltip';
|
||||
props.style = {width: (data.column.realWidth || data.column.width) - 1 + 'px'};
|
||||
}
|
||||
return (<div { ...props }>
|
||||
{ prefix }
|
||||
{ children }
|
||||
</div>);
|
||||
};
|
||||
}
|
||||
return column;
|
||||
},
|
||||
|
||||
registerNormalWatchers() {
|
||||
const props = ['label', 'property', 'filters', 'filterMultiple', 'sortable', 'index', 'formatter', 'className', 'labelClassName', 'showOverflowTooltip'];
|
||||
// 一些属性具有别名
|
||||
const aliases = {
|
||||
prop: 'property',
|
||||
realAlign: 'align',
|
||||
realHeaderAlign: 'headerAlign',
|
||||
realWidth: 'width'
|
||||
};
|
||||
const allAliases = props.reduce((prev, cur) => {
|
||||
prev[cur] = cur;
|
||||
return prev;
|
||||
}, aliases);
|
||||
|
||||
Object.keys(allAliases).forEach(key => {
|
||||
const columnKey = aliases[key];
|
||||
|
||||
this.$watch(key, (newVal) => {
|
||||
this.columnConfig[columnKey] = newVal;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
registerComplexWatchers() {
|
||||
const props = ['fixed'];
|
||||
const aliases = {
|
||||
realWidth: 'width',
|
||||
realMinWidth: 'minWidth'
|
||||
};
|
||||
const allAliases = props.reduce((prev, cur) => {
|
||||
prev[cur] = cur;
|
||||
return prev;
|
||||
}, aliases);
|
||||
|
||||
Object.keys(allAliases).forEach(key => {
|
||||
const columnKey = aliases[key];
|
||||
|
||||
this.$watch(key, (newVal) => {
|
||||
this.columnConfig[columnKey] = newVal;
|
||||
const updateColumns = columnKey === 'fixed';
|
||||
this.owner.store.scheduleLayout(updateColumns);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ElCheckbox
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.row = {};
|
||||
this.column = {};
|
||||
this.$index = 0;
|
||||
this.columnId = '';
|
||||
},
|
||||
|
||||
created() {
|
||||
const parent = this.columnOrTableParent;
|
||||
this.isSubColumn = this.owner !== parent;
|
||||
this.columnId = (parent.tableId || parent.columnId) + '_column_' + columnIdSeed++;
|
||||
|
||||
const type = this.type || 'default';
|
||||
const sortable = this.sortable === '' ? true : this.sortable;
|
||||
const defaults = {
|
||||
...cellStarts[type],
|
||||
id: this.columnId,
|
||||
type: type,
|
||||
property: this.prop || this.property,
|
||||
align: this.realAlign,
|
||||
headerAlign: this.realHeaderAlign,
|
||||
showOverflowTooltip: this.showOverflowTooltip || this.showTooltipWhenOverflow,
|
||||
// filter 相关属性
|
||||
filterable: this.filters || this.filterMethod,
|
||||
filteredValue: [],
|
||||
filterPlacement: '',
|
||||
isColumnGroup: false,
|
||||
filterOpened: false,
|
||||
// sort 相关属性
|
||||
sortable: sortable,
|
||||
// index 列
|
||||
index: this.index
|
||||
};
|
||||
|
||||
const basicProps = ['columnKey', 'label', 'className', 'labelClassName', 'type', 'renderHeader', 'formatter', 'fixed', 'resizable'];
|
||||
const sortProps = ['sortMethod', 'sortBy', 'sortOrders'];
|
||||
const selectProps = ['selectable', 'reserveSelection'];
|
||||
const filterProps = ['filterMethod', 'filters', 'filterMultiple', 'filterOpened', 'filteredValue', 'filterPlacement'];
|
||||
|
||||
let column = this.getPropsData(basicProps, sortProps, selectProps, filterProps);
|
||||
column = mergeOptions(defaults, column);
|
||||
|
||||
// 注意 compose 中函数执行的顺序是从右到左
|
||||
const chains = compose(this.setColumnRenders, this.setColumnWidth, this.setColumnForcedProps);
|
||||
column = chains(column);
|
||||
|
||||
this.columnConfig = column;
|
||||
|
||||
// 注册 watcher
|
||||
this.registerNormalWatchers();
|
||||
this.registerComplexWatchers();
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const owner = this.owner;
|
||||
const parent = this.columnOrTableParent;
|
||||
const children = this.isSubColumn ? parent.$el.children : parent.$refs.hiddenColumns.children;
|
||||
const columnIndex = this.getColumnElIndex(children, this.$el);
|
||||
|
||||
owner.store.commit('insertColumn', this.columnConfig, columnIndex, this.isSubColumn ? parent.columnConfig : null);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if (!this.$parent) return;
|
||||
const parent = this.$parent;
|
||||
this.owner.store.commit('removeColumn', this.columnConfig, this.isSubColumn ? parent.columnConfig : null);
|
||||
},
|
||||
|
||||
render(h) {
|
||||
// slots 也要渲染,需要计算合并表头
|
||||
return h('div', this.$slots.default);
|
||||
}
|
||||
};
|
153
packages/table/src/table-footer.js
Normal file
153
packages/table/src/table-footer.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import LayoutObserver from './layout-observer';
|
||||
import { mapStates } from './store/helper';
|
||||
|
||||
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.columns.map(column => <col name={ column.id } key={column.id} />)
|
||||
}
|
||||
{
|
||||
this.hasGutter ? <col name="gutter" /> : ''
|
||||
}
|
||||
</colgroup>
|
||||
<tbody class={ [{ 'has-gutter': this.hasGutter }] }>
|
||||
<tr>
|
||||
{
|
||||
this.columns.map((column, cellIndex) => <td
|
||||
key={cellIndex}
|
||||
colspan={ column.colSpan }
|
||||
rowspan={ column.rowSpan }
|
||||
class={ this.getRowClasses(column, cellIndex) }>
|
||||
<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;
|
||||
},
|
||||
|
||||
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
|
||||
})
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
getRowClasses(column, cellIndex) {
|
||||
const classes = [column.id, column.align, column.labelClassName];
|
||||
if (column.className) {
|
||||
classes.push(column.className);
|
||||
}
|
||||
if (this.isCellHidden(cellIndex, this.columns, column)) {
|
||||
classes.push('is-hidden');
|
||||
}
|
||||
if (!column.children) {
|
||||
classes.push('is-leaf');
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
};
|
510
packages/table/src/table-header.js
Normal file
510
packages/table/src/table-header.js
Normal file
@@ -0,0 +1,510 @@
|
||||
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: {}
|
||||
};
|
||||
}
|
||||
};
|
250
packages/table/src/table-layout.js
Normal file
250
packages/table/src/table-layout.js
Normal file
@@ -0,0 +1,250 @@
|
||||
import Vue from 'vue';
|
||||
import scrollbarWidth from 'element-ui/src/utils/scrollbar-width';
|
||||
import { parseHeight } from './util';
|
||||
|
||||
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 (height === null) return false;
|
||||
const bodyWrapper = this.table.bodyWrapper;
|
||||
if (this.table.$el && bodyWrapper) {
|
||||
const body = bodyWrapper.querySelector('.el-table__body');
|
||||
const prevScrollY = this.scrollY;
|
||||
const scrollY = body.offsetHeight > this.bodyHeight;
|
||||
this.scrollY = scrollY;
|
||||
return prevScrollY !== scrollY;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setHeight(value, prop = 'height') {
|
||||
if (Vue.prototype.$isServer) return;
|
||||
const el = this.table.$el;
|
||||
value = parseHeight(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) {
|
||||
this.setHeight(value, 'max-height');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// fix issue (https://github.com/ElemeFE/element/pull/16956)
|
||||
const headerTrElm = headerWrapper ? headerWrapper.querySelector('.el-table__header tr') : null;
|
||||
const noneHeader = this.headerDisplayNone(headerTrElm);
|
||||
|
||||
const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight;
|
||||
if (this.showHeader && !noneHeader && headerWrapper.offsetWidth > 0 && (this.table.columns || []).length > 0 && headerHeight < 2) {
|
||||
return Vue.nextTick(() => this.updateElsHeight());
|
||||
}
|
||||
const tableHeight = this.tableHeight = this.table.$el.clientHeight;
|
||||
const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0;
|
||||
if (this.height !== null) {
|
||||
this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0);
|
||||
}
|
||||
this.fixedBodyHeight = this.scrollX ? (this.bodyHeight - this.gutterWidth) : this.bodyHeight;
|
||||
|
||||
const noData = !(this.store.states.data && this.store.states.data.length);
|
||||
this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;
|
||||
|
||||
this.updateScrollY();
|
||||
this.notifyObservers('scrollable');
|
||||
}
|
||||
|
||||
headerDisplayNone(elm) {
|
||||
if (!elm) return true;
|
||||
let headerChild = elm;
|
||||
while (headerChild.tagName !== 'DIV') {
|
||||
if (getComputedStyle(headerChild).display === 'none') {
|
||||
return true;
|
||||
}
|
||||
headerChild = headerChild.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
694
packages/table/src/table.vue
Normal file
694
packages/table/src/table.vue
Normal file
@@ -0,0 +1,694 @@
|
||||
<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="emptyBlockStyle">
|
||||
<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
|
||||
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="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, throttle } from 'throttle-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 { createStore, mapStates } from './store/helper';
|
||||
import TableLayout from './table-layout';
|
||||
import TableBody from './table-body';
|
||||
import TableHeader from './table-header';
|
||||
import TableFooter from './table-footer';
|
||||
import { parseHeight } from './util';
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
indent: {
|
||||
type: Number,
|
||||
default: 16
|
||||
},
|
||||
|
||||
treeProps: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
hasChildren: 'hasChildren',
|
||||
children: 'children'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
lazy: Boolean,
|
||||
|
||||
load: Function
|
||||
},
|
||||
|
||||
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, false);
|
||||
this.store.updateAllSelected();
|
||||
},
|
||||
|
||||
toggleRowExpansion(row, expanded) {
|
||||
this.store.toggleRowExpansionAdapter(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() {
|
||||
const changed = this.layout.updateScrollY();
|
||||
if (changed) {
|
||||
this.layout.notifyObservers('scrollable');
|
||||
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)) {
|
||||
this.bodyWrapper.scrollLeft += data.pixelX / 5;
|
||||
}
|
||||
},
|
||||
|
||||
// TODO 使用 CSS transform
|
||||
syncPostion: throttle(20, function() {
|
||||
const { scrollLeft, scrollTop, offsetWidth, scrollWidth } = this.bodyWrapper;
|
||||
const { headerWrapper, footerWrapper, fixedBodyWrapper, rightFixedBodyWrapper } = this.$refs;
|
||||
if (headerWrapper) headerWrapper.scrollLeft = scrollLeft;
|
||||
if (footerWrapper) footerWrapper.scrollLeft = scrollLeft;
|
||||
if (fixedBodyWrapper) fixedBodyWrapper.scrollTop = scrollTop;
|
||||
if (rightFixedBodyWrapper) rightFixedBodyWrapper.scrollTop = scrollTop;
|
||||
const maxScrollLeftPosition = scrollWidth - offsetWidth - 1;
|
||||
if (scrollLeft >= maxScrollLeftPosition) {
|
||||
this.scrollPosition = 'right';
|
||||
} else if (scrollLeft === 0) {
|
||||
this.scrollPosition = 'left';
|
||||
} else {
|
||||
this.scrollPosition = 'middle';
|
||||
}
|
||||
}),
|
||||
|
||||
bindEvents() {
|
||||
this.bodyWrapper.addEventListener('scroll', this.syncPostion, { passive: true });
|
||||
if (this.fit) {
|
||||
addResizeListener(this.$el, this.resizeListener);
|
||||
}
|
||||
},
|
||||
|
||||
unbindEvents() {
|
||||
this.bodyWrapper.removeEventListener('scroll', this.syncPostion, { passive: true });
|
||||
if (this.fit) {
|
||||
removeResizeListener(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() {
|
||||
if (this.shouldUpdateHeight) {
|
||||
this.layout.updateElsHeight();
|
||||
}
|
||||
this.layout.updateColumnsWidth();
|
||||
},
|
||||
|
||||
sort(prop, order) {
|
||||
this.store.commit('sort', { prop, order });
|
||||
},
|
||||
|
||||
toggleAllSelection() {
|
||||
this.store.commit('toggleAllSelection');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
bodyWidth() {
|
||||
const { bodyWidth, scrollY, gutterWidth } = this.layout;
|
||||
return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : '';
|
||||
},
|
||||
|
||||
bodyHeight() {
|
||||
const { headerHeight = 0, bodyHeight, footerHeight = 0} = this.layout;
|
||||
if (this.height) {
|
||||
return {
|
||||
height: bodyHeight ? bodyHeight + 'px' : ''
|
||||
};
|
||||
} else if (this.maxHeight) {
|
||||
const maxHeight = parseHeight(this.maxHeight);
|
||||
if (typeof maxHeight === 'number') {
|
||||
return {
|
||||
'max-height': (maxHeight - footerHeight - (this.showHeader ? headerHeight : 0)) + 'px'
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
fixedBodyHeight() {
|
||||
if (this.height) {
|
||||
return {
|
||||
height: this.layout.fixedBodyHeight ? this.layout.fixedBodyHeight + 'px' : ''
|
||||
};
|
||||
} else if (this.maxHeight) {
|
||||
let maxHeight = parseHeight(this.maxHeight);
|
||||
if (typeof maxHeight === 'number') {
|
||||
maxHeight = this.layout.scrollX ? maxHeight - this.layout.gutterWidth : 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' : ''
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
emptyBlockStyle() {
|
||||
if (this.data && this.data.length) return null;
|
||||
let height = '100%';
|
||||
if (this.layout.appendHeight) {
|
||||
height = `calc(100% - ${this.layout.appendHeight}px)`;
|
||||
}
|
||||
return {
|
||||
width: this.bodyWidth,
|
||||
height
|
||||
};
|
||||
},
|
||||
|
||||
...mapStates({
|
||||
selection: 'selection',
|
||||
columns: 'columns',
|
||||
tableData: 'data',
|
||||
fixedColumns: 'fixedColumns',
|
||||
rightFixedColumns: 'rightFixedColumns'
|
||||
})
|
||||
},
|
||||
|
||||
watch: {
|
||||
height: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.layout.setHeight(value);
|
||||
}
|
||||
},
|
||||
|
||||
maxHeight: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.layout.setMaxHeight(value);
|
||||
}
|
||||
},
|
||||
|
||||
currentRowKey: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (!this.rowKey) return;
|
||||
this.store.setCurrentRowKey(value);
|
||||
}
|
||||
},
|
||||
|
||||
data: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.store.commit('setData', value);
|
||||
}
|
||||
},
|
||||
|
||||
expandRowKeys: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.store.setExpandRowKeysAdapter(newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.tableId = 'el-table_' + tableIdSeed++;
|
||||
this.debouncedUpdateLayout = debounce(50, () => this.doLayout());
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.unbindEvents();
|
||||
},
|
||||
|
||||
data() {
|
||||
const { hasChildren = 'hasChildren', children = 'children' } = this.treeProps;
|
||||
this.store = createStore(this, {
|
||||
rowKey: this.rowKey,
|
||||
defaultExpandAll: this.defaultExpandAll,
|
||||
selectOnIndeterminate: this.selectOnIndeterminate,
|
||||
// TreeTable 的相关配置
|
||||
indent: this.indent,
|
||||
lazy: this.lazy,
|
||||
lazyColumnIdentifier: hasChildren,
|
||||
childrenColumnName: children
|
||||
});
|
||||
const layout = new TableLayout({
|
||||
store: this.store,
|
||||
table: this,
|
||||
fit: this.fit,
|
||||
showHeader: this.showHeader
|
||||
});
|
||||
return {
|
||||
layout,
|
||||
isHidden: false,
|
||||
renderExpanded: null,
|
||||
resizeProxyVisible: false,
|
||||
resizeState: {
|
||||
width: null,
|
||||
height: null
|
||||
},
|
||||
// 是否拥有多级表头
|
||||
isGroup: false,
|
||||
scrollPosition: 'left'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
255
packages/table/src/util.js
Normal file
255
packages/table/src/util.js
Normal file
@@ -0,0 +1,255 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
export const getKeysMap = function(array, rowKey) {
|
||||
const arrayMap = {};
|
||||
(array || []).forEach((row, index) => {
|
||||
arrayMap[getRowIdentity(row, rowKey)] = { row, index };
|
||||
});
|
||||
return arrayMap;
|
||||
};
|
||||
|
||||
function hasOwn(obj, key) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||
}
|
||||
|
||||
export function mergeOptions(defaults, config) {
|
||||
const options = {};
|
||||
let key;
|
||||
for (key in defaults) {
|
||||
options[key] = defaults[key];
|
||||
}
|
||||
for (key in config) {
|
||||
if (hasOwn(config, key)) {
|
||||
const value = config[key];
|
||||
if (typeof value !== 'undefined') {
|
||||
options[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
export function parseWidth(width) {
|
||||
if (width !== undefined) {
|
||||
width = parseInt(width, 10);
|
||||
if (isNaN(width)) {
|
||||
width = null;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
export function parseMinWidth(minWidth) {
|
||||
if (typeof minWidth !== 'undefined') {
|
||||
minWidth = parseWidth(minWidth);
|
||||
if (isNaN(minWidth)) {
|
||||
minWidth = 80;
|
||||
}
|
||||
}
|
||||
return minWidth;
|
||||
};
|
||||
|
||||
export function parseHeight(height) {
|
||||
if (typeof height === 'number') {
|
||||
return height;
|
||||
}
|
||||
if (typeof height === 'string') {
|
||||
if (/^\d+(?:px)?$/.test(height)) {
|
||||
return parseInt(height, 10);
|
||||
} else {
|
||||
return height;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://github.com/reduxjs/redux/blob/master/src/compose.js
|
||||
export function compose(...funcs) {
|
||||
if (funcs.length === 0) {
|
||||
return arg => arg;
|
||||
}
|
||||
if (funcs.length === 1) {
|
||||
return funcs[0];
|
||||
}
|
||||
return funcs.reduce((a, b) => (...args) => a(b(...args)));
|
||||
}
|
||||
|
||||
export function toggleRowStatus(statusArr, row, newVal) {
|
||||
let changed = false;
|
||||
const index = statusArr.indexOf(row);
|
||||
const included = index !== -1;
|
||||
|
||||
const addRow = () => {
|
||||
statusArr.push(row);
|
||||
changed = true;
|
||||
};
|
||||
const removeRow = () => {
|
||||
statusArr.splice(index, 1);
|
||||
changed = true;
|
||||
};
|
||||
|
||||
if (typeof newVal === 'boolean') {
|
||||
if (newVal && !included) {
|
||||
addRow();
|
||||
} else if (!newVal && included) {
|
||||
removeRow();
|
||||
}
|
||||
} else {
|
||||
if (included) {
|
||||
removeRow();
|
||||
} else {
|
||||
addRow();
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
export function walkTreeNode(root, cb, childrenKey = 'children', lazyKey = 'hasChildren') {
|
||||
const isNil = (array) => !(Array.isArray(array) && array.length);
|
||||
|
||||
function _walker(parent, children, level) {
|
||||
cb(parent, children, level);
|
||||
children.forEach(item => {
|
||||
if (item[lazyKey]) {
|
||||
cb(item, null, level + 1);
|
||||
return;
|
||||
}
|
||||
const children = item[childrenKey];
|
||||
if (!isNil(children)) {
|
||||
_walker(item, children, level + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
root.forEach(item => {
|
||||
if (item[lazyKey]) {
|
||||
cb(item, null, 0);
|
||||
return;
|
||||
}
|
||||
const children = item[childrenKey];
|
||||
if (!isNil(children)) {
|
||||
_walker(item, children, 0);
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user