mirror of https://github.com/ElemeFE/element
Table: add filter feature. (#684)
parent
5a6cca14c6
commit
14495f6189
|
@ -14,6 +14,7 @@
|
||||||
- 修复 Switch 的 width 属性无效的问题
|
- 修复 Switch 的 width 属性无效的问题
|
||||||
- Table 增加 rowClassName 属性
|
- Table 增加 rowClassName 属性
|
||||||
- TableColumn 增加 fixed 属性,可选值:true, false, left, right
|
- TableColumn 增加 fixed 属性,可选值:true, false, left, right
|
||||||
|
- TableColumn 增加属性:filters、filterMultiple、filterMethod、filteredValue
|
||||||
- TableColumn[type="selection"] 增加 selectable 属性
|
- TableColumn[type="selection"] 增加 selectable 属性
|
||||||
- 修复 Input textarea 在动态赋值时 autosize 没有触发的问题
|
- 修复 Input textarea 在动态赋值时 autosize 没有触发的问题
|
||||||
- 修复 Input Number min max 属性设置后点击加减出现的崩溃的bug
|
- 修复 Input Number min max 属性设置后点击加减出现的崩溃的bug
|
||||||
|
|
|
@ -9,28 +9,32 @@
|
||||||
province: '上海',
|
province: '上海',
|
||||||
city: '普陀区',
|
city: '普陀区',
|
||||||
address: '上海市普陀区金沙江路 1518 弄',
|
address: '上海市普陀区金沙江路 1518 弄',
|
||||||
zip: 200333
|
zip: 200333,
|
||||||
|
tag: '家'
|
||||||
}, {
|
}, {
|
||||||
date: '2016-05-02',
|
date: '2016-05-02',
|
||||||
name: '王小虎',
|
name: '王小虎',
|
||||||
province: '上海',
|
province: '上海',
|
||||||
city: '普陀区',
|
city: '普陀区',
|
||||||
address: '上海市普陀区金沙江路 1518 弄',
|
address: '上海市普陀区金沙江路 1518 弄',
|
||||||
zip: 200333
|
zip: 200333,
|
||||||
|
tag: '公司'
|
||||||
}, {
|
}, {
|
||||||
date: '2016-05-04',
|
date: '2016-05-04',
|
||||||
name: '王小虎',
|
name: '王小虎',
|
||||||
province: '上海',
|
province: '上海',
|
||||||
city: '普陀区',
|
city: '普陀区',
|
||||||
address: '上海市普陀区金沙江路 1518 弄',
|
address: '上海市普陀区金沙江路 1518 弄',
|
||||||
zip: 200333
|
zip: 200333,
|
||||||
|
tag: '家'
|
||||||
}, {
|
}, {
|
||||||
date: '2016-05-01',
|
date: '2016-05-01',
|
||||||
name: '王小虎',
|
name: '王小虎',
|
||||||
province: '上海',
|
province: '上海',
|
||||||
city: '普陀区',
|
city: '普陀区',
|
||||||
address: '上海市普陀区金沙江路 1518 弄',
|
address: '上海市普陀区金沙江路 1518 弄',
|
||||||
zip: 200333
|
zip: 200333,
|
||||||
|
tag: '公司'
|
||||||
}],
|
}],
|
||||||
tableData2: [{
|
tableData2: [{
|
||||||
date: '2016-05-02',
|
date: '2016-05-02',
|
||||||
|
@ -119,6 +123,10 @@
|
||||||
return row.address;
|
return row.address;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
filterTag(value, row) {
|
||||||
|
return row.tag === value;
|
||||||
|
},
|
||||||
|
|
||||||
tableRowClassName(row, index) {
|
tableRowClassName(row, index) {
|
||||||
if (index === 1) {
|
if (index === 1) {
|
||||||
return 'info-row';
|
return 'info-row';
|
||||||
|
@ -810,6 +818,85 @@
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### 筛选
|
||||||
|
|
||||||
|
对表格进行筛选,可快速查找到自己想看的数据。
|
||||||
|
|
||||||
|
:::demo 在列中设置`filters``filter-method`属性即可开启该列的筛选,filters 是一个数组,`filter-method`是一个方法,它用于决定某些数据是否显示,会传入两个参数:`value`和`row`。
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
border
|
||||||
|
style="width: 100%">
|
||||||
|
<el-table-column
|
||||||
|
prop="date"
|
||||||
|
label="日期"
|
||||||
|
sortable
|
||||||
|
width="180">
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="name"
|
||||||
|
label="姓名"
|
||||||
|
width="180">
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="address"
|
||||||
|
label="地址"
|
||||||
|
:formatter="formatter">
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="tag"
|
||||||
|
label="标签"
|
||||||
|
width="100"
|
||||||
|
:filters="[{ text: '家', value: '家' }, { text: '公司', value: '公司' }]"
|
||||||
|
:filter-method="filterTag"
|
||||||
|
inline-template>
|
||||||
|
<el-tag :type="row.tag === '家' ? 'primary' : 'success'" close-transition>{{row.tag}}</el-tag>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tableData: [{
|
||||||
|
date: '2016-05-02',
|
||||||
|
name: '王小虎',
|
||||||
|
address: '上海市普陀区金沙江路 1518 弄',
|
||||||
|
tag: '家'
|
||||||
|
}, {
|
||||||
|
date: '2016-05-04',
|
||||||
|
name: '王小虎',
|
||||||
|
address: '上海市普陀区金沙江路 1517 弄',
|
||||||
|
tag: '公司'
|
||||||
|
}, {
|
||||||
|
date: '2016-05-01',
|
||||||
|
name: '王小虎',
|
||||||
|
address: '上海市普陀区金沙江路 1519 弄',
|
||||||
|
tag: '家'
|
||||||
|
}, {
|
||||||
|
date: '2016-05-03',
|
||||||
|
name: '王小虎',
|
||||||
|
address: '上海市普陀区金沙江路 1516 弄',
|
||||||
|
tag: '公司'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatter(row, column) {
|
||||||
|
return row.address;
|
||||||
|
},
|
||||||
|
filterTag(value, row) {
|
||||||
|
return row.tag === value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
### Table Attributes
|
### Table Attributes
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|---------- |-------------- |---------- |-------------------------------- |-------- |
|
|---------- |-------------- |---------- |-------------------------------- |-------- |
|
||||||
|
@ -852,4 +939,8 @@
|
||||||
| inline-template | 指定该属性后可以自定义 column 模板,参考多选的时间列,通过 row 获取行信息,JSX 里通过 _self 获取当前上下文。此时不需要配置 prop 属性 | — | — |
|
| inline-template | 指定该属性后可以自定义 column 模板,参考多选的时间列,通过 row 获取行信息,JSX 里通过 _self 获取当前上下文。此时不需要配置 prop 属性 | — | — |
|
||||||
| align | 对齐方式 | String | left, center, right | left |
|
| align | 对齐方式 | String | left, center, right | left |
|
||||||
| selectable | 仅对 type=selection 的列有效,类型为 Function,Function 的返回值用来决定这一行的 CheckBox 是否可以勾选 | Function(row, index) | - | - |
|
| selectable | 仅对 type=selection 的列有效,类型为 Function,Function 的返回值用来决定这一行的 CheckBox 是否可以勾选 | Function(row, index) | - | - |
|
||||||
| reserve-selection | 仅对 type=selection 的列有效,类型为 Boolean,为 true 则代表会保留之前数据的选项,需要配合 Table 的 clearSelection 方法使用。 | Boolean | - | false |
|
| reserve-selection | 仅对 type=selection 的列有效,类型为 Boolean,为 true 则代表会保留之前数据的选项,需要配合 Table 的 clearSelection 方法使用。 | Boolean | - | false |
|
||||||
|
| filters | 数据过滤的选项,数组格式,数组中的元素需要有 text 和 value 属性。 | Array[{ text, value }] | — | — |
|
||||||
|
| filter-multiple | 数据过滤的选项是否多选 | Boolean | — | true |
|
||||||
|
| filter-method | 数据过滤使用的方法,如果是多选的筛选项,对每一条数据会执行多次,任意一次返回 true 就会显示。 | Function(value, row) | — | — |
|
||||||
|
| filteredValue | 选中的数据过滤项,如果需要自定义表头过滤的渲染方式,可能会需要此属性。 | Array | — | — |
|
|
@ -0,0 +1,27 @@
|
||||||
|
var dropdowns = [];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,180 @@
|
||||||
|
<template>
|
||||||
|
<transition name="md-fade-bottom">
|
||||||
|
<div class="el-table-filter" v-if="multiple" v-show="showPopper">
|
||||||
|
<div class="el-table-filter__content">
|
||||||
|
<el-checkbox-group class="el-table-filter__checkbox-group" v-model="filteredValue">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="filter in filters"
|
||||||
|
:label="filter.value">{{ filter.text }}</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</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-show="showPopper">
|
||||||
|
<ul class="el-table-filter__list">
|
||||||
|
<li class="el-table-filter__list-item"
|
||||||
|
:class="{ 'is-active': !filterValue }"
|
||||||
|
@click="handleSelect(null)">{{ $t('el.table.clearFilter') }}</li>
|
||||||
|
<li class="el-table-filter__list-item"
|
||||||
|
v-for="filter in filters"
|
||||||
|
:label="filter.value"
|
||||||
|
:class="{ 'is-active': isActive(filter) }"
|
||||||
|
@click="handleSelect(filter.value)" >{{ filter.text }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="text/jsx">
|
||||||
|
import Popper from 'element-ui/src/utils/vue-popper';
|
||||||
|
import Locale from 'element-ui/src/mixins/locale';
|
||||||
|
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||||
|
import Dropdown from './dropdown';
|
||||||
|
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||||
|
import ElCheckboxGroup from 'element-ui/packages/checkbox-group';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'el-table-filter-panel',
|
||||||
|
|
||||||
|
mixins: [Popper, Locale],
|
||||||
|
|
||||||
|
directives: {
|
||||||
|
Clickoutside
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ElCheckbox,
|
||||||
|
ElCheckboxGroup
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'bottom-end'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
customRender(h) {
|
||||||
|
return (<div class="el-table-filter">
|
||||||
|
<div class="el-table-filter__content">
|
||||||
|
</div>
|
||||||
|
<div class="el-table-filter__bottom">
|
||||||
|
<button on-click={ this.handleConfirm }>{ this.$t('el.table.confirmFilter') }</button>
|
||||||
|
<button on-click={ this.handleReset }>{ this.$t('el.table.resetFilter') }</button>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
isActive(filter) {
|
||||||
|
return filter.value === this.filterValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOutsideClick() {
|
||||||
|
this.showPopper = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleConfirm() {
|
||||||
|
this.confirmFilter(this.filteredValue);
|
||||||
|
this.handleOutsideClick();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleReset() {
|
||||||
|
this.filteredValue = [];
|
||||||
|
this.confirmFilter(this.filteredValue);
|
||||||
|
this.handleOutsideClick();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSelect(filterValue) {
|
||||||
|
this.filterValue = filterValue;
|
||||||
|
|
||||||
|
if (filterValue) {
|
||||||
|
this.confirmFilter(this.filteredValue);
|
||||||
|
} else {
|
||||||
|
this.confirmFilter([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleOutsideClick();
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmFilter(filteredValue) {
|
||||||
|
this.table.store.commit('filterChange', {
|
||||||
|
column: this.column,
|
||||||
|
values: filteredValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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 (value) {
|
||||||
|
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.$refs.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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,22 +1,4 @@
|
||||||
import { getValueByPath, getCell } from './util';
|
import { getValueByPath, getCell, getColumnById, getColumnByCell } from './util';
|
||||||
|
|
||||||
const getColumnById = function(table, columnId) {
|
|
||||||
let column = null;
|
|
||||||
table.columns.forEach(function(item) {
|
|
||||||
if (item.id === columnId) {
|
|
||||||
column = item;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return column;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getColumnByCell = function(table, cell) {
|
|
||||||
const matches = (cell.className || '').match(/el-table_[^\s]+/gm);
|
|
||||||
if (matches) {
|
|
||||||
return getColumnById(table, matches[0]);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -19,21 +19,16 @@ const defaults = {
|
||||||
minWidth: 48,
|
minWidth: 48,
|
||||||
realWidth: 48,
|
realWidth: 48,
|
||||||
direction: ''
|
direction: ''
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
headerTemplate: function(h) { return <span>filter header</span>; },
|
|
||||||
direction: ''
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const forced = {
|
const forced = {
|
||||||
selection: {
|
selection: {
|
||||||
headerTemplate: function(h) {
|
headerTemplate: function(h) {
|
||||||
return <div><el-checkbox
|
return <el-checkbox
|
||||||
nativeOn-click={ this.toggleAllSelection }
|
nativeOn-click={ this.toggleAllSelection }
|
||||||
domProps-value={ this.isAllSelected }
|
domProps-value={ this.isAllSelected }
|
||||||
on-input={ (value) => { this.$emit('allselectedchange', value); } } />
|
on-input={ (value) => { this.$emit('allselectedchange', value); } } />;
|
||||||
</div>;
|
|
||||||
},
|
},
|
||||||
template: function(h, { row, column, store, $index }) {
|
template: function(h, { row, column, store, $index }) {
|
||||||
return <el-checkbox
|
return <el-checkbox
|
||||||
|
@ -47,7 +42,7 @@ const forced = {
|
||||||
index: {
|
index: {
|
||||||
// headerTemplate: function(h) { return <div>#</div>; },
|
// headerTemplate: function(h) { return <div>#</div>; },
|
||||||
headerTemplate: function(h, label) {
|
headerTemplate: function(h, label) {
|
||||||
return <div>{ label || '#' }</div>;
|
return label || '#';
|
||||||
},
|
},
|
||||||
template: function(h, { $index }) {
|
template: function(h, { $index }) {
|
||||||
return <div>{ $index + 1 }</div>;
|
return <div>{ $index + 1 }</div>;
|
||||||
|
@ -56,7 +51,7 @@ const forced = {
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
headerTemplate: function(h) {
|
headerTemplate: function(h) {
|
||||||
return <div>#</div>;
|
return '#';
|
||||||
},
|
},
|
||||||
template: function(h, { row, column }) {
|
template: function(h, { row, column }) {
|
||||||
return <el-tag type="primary" style="height: 16px; line-height: 16px; min-width: 40px; text-align: center">{ row[column.property] }</el-tag>;
|
return <el-tag type="primary" style="height: 16px; line-height: 16px; min-width: 40px; text-align: center">{ row[column.property] }</el-tag>;
|
||||||
|
@ -118,7 +113,13 @@ export default {
|
||||||
fixed: [Boolean, String],
|
fixed: [Boolean, String],
|
||||||
formatter: Function,
|
formatter: Function,
|
||||||
selectable: Function,
|
selectable: Function,
|
||||||
reserveSelection: Boolean
|
reserveSelection: Boolean,
|
||||||
|
filterMethod: Function,
|
||||||
|
filters: Array,
|
||||||
|
filterMultiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {},
|
render() {},
|
||||||
|
@ -205,7 +206,13 @@ export default {
|
||||||
formatter: this.formatter,
|
formatter: this.formatter,
|
||||||
selectable: this.selectable,
|
selectable: this.selectable,
|
||||||
reserveSelection: this.reserveSelection,
|
reserveSelection: this.reserveSelection,
|
||||||
fixed: this.fixed
|
fixed: this.fixed,
|
||||||
|
filterMethod: this.filterMethod,
|
||||||
|
filters: this.filters,
|
||||||
|
filterable: this.filters || this.filterMethod,
|
||||||
|
filterMultiple: this.filterMultiple,
|
||||||
|
filterOpened: false,
|
||||||
|
filteredValue: []
|
||||||
});
|
});
|
||||||
|
|
||||||
objectAssign(column, forced[type] || {});
|
objectAssign(column, forced[type] || {});
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import ElCheckbox from 'element-ui/packages/checkbox';
|
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||||
import ElTag from 'element-ui/packages/tag';
|
import ElTag from 'element-ui/packages/tag';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import FilterPanel from './filter-panel.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'el-table-header',
|
name: 'el-table-header',
|
||||||
|
@ -31,21 +33,27 @@ export default {
|
||||||
on-mousemove={ ($event) => this.handleMouseMove($event, column) }
|
on-mousemove={ ($event) => this.handleMouseMove($event, column) }
|
||||||
on-mouseout={ this.handleMouseOut }
|
on-mouseout={ this.handleMouseOut }
|
||||||
on-mousedown={ ($event) => this.handleMouseDown($event, column) }
|
on-mousedown={ ($event) => this.handleMouseDown($event, column) }
|
||||||
on-click={ ($event) => this.handleHeaderClick($event, column) }
|
|
||||||
class={ [column.id, column.direction, column.align, this.isCellHidden(cellIndex) ? 'hidden' : ''] }>
|
class={ [column.id, column.direction, column.align, this.isCellHidden(cellIndex) ? 'hidden' : ''] }>
|
||||||
|
<div class={ ['cell', column.filteredValue && column.filteredValue.length > 0 ? 'highlight' : ''] }>
|
||||||
{
|
{
|
||||||
[
|
column.headerTemplate
|
||||||
column.headerTemplate
|
? column.headerTemplate.call(this._renderProxy, h, column.label)
|
||||||
? column.headerTemplate.call(this._renderProxy, h, column.label)
|
: column.label
|
||||||
: <div>{ column.label }</div>,
|
|
||||||
column.sortable
|
|
||||||
? <div class="caret-wrapper">
|
|
||||||
<i class="sort-caret ascending"></i>
|
|
||||||
<i class="sort-caret descending"></i>
|
|
||||||
</div>
|
|
||||||
: ''
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
column.sortable
|
||||||
|
? <span class="caret-wrapper" on-click={ ($event) => this.handleHeaderClick($event, column) }>
|
||||||
|
<i class="sort-caret ascending"></i>
|
||||||
|
<i class="sort-caret descending"></i>
|
||||||
|
</span>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
{
|
||||||
|
column.filterable
|
||||||
|
? <span class="el-table__column-filter-trigger" on-click={ ($event) => this.handleFilterClick($event, column) }><i class={ ['el-icon-arrow-down', column.filterOpened ? 'el-icon-arrow-up' : ''] }></i></span>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +69,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
columns: {},
|
|
||||||
fixed: String,
|
fixed: String,
|
||||||
store: {
|
store: {
|
||||||
required: true
|
required: true
|
||||||
|
@ -99,6 +106,19 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.filterPanels = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
const panels = this.filterPanels;
|
||||||
|
for (let prop in panels) {
|
||||||
|
if (panels.hasOwnProperty(prop) && panels[prop]) {
|
||||||
|
panels[prop].$destroy(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
isCellHidden(index) {
|
isCellHidden(index) {
|
||||||
if (this.fixed === true || this.fixed === 'left') {
|
if (this.fixed === true || this.fixed === 'left') {
|
||||||
|
@ -114,6 +134,34 @@ export default {
|
||||||
this.store.commit('toggleAllSelection');
|
this.store.commit('toggleAllSelection');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleFilterClick(event, column) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const target = event.target;
|
||||||
|
const cell = target.parentNode;
|
||||||
|
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;
|
||||||
|
|
||||||
|
filterPanel.table = table;
|
||||||
|
filterPanel.cell = cell;
|
||||||
|
filterPanel.column = column;
|
||||||
|
filterPanel.$mount(document.createElement('div'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
filterPanel.showPopper = true;
|
||||||
|
}, 16);
|
||||||
|
},
|
||||||
|
|
||||||
handleMouseDown(event, column) {
|
handleMouseDown(event, column) {
|
||||||
if (this.draggingColumn && this.border) {
|
if (this.draggingColumn && this.border) {
|
||||||
this.dragging = true;
|
this.dragging = true;
|
||||||
|
@ -180,7 +228,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMouseMove(event, column) {
|
handleMouseMove(event, column) {
|
||||||
const target = event.target;
|
let target = event.target;
|
||||||
|
while (target && target.tagName !== 'TH') {
|
||||||
|
target = target.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
if (!column || !column.resizable) return;
|
if (!column || !column.resizable) return;
|
||||||
|
|
||||||
|
@ -194,7 +245,6 @@ export default {
|
||||||
} else if (!this.dragging) {
|
} else if (!this.dragging) {
|
||||||
bodyStyle.cursor = '';
|
bodyStyle.cursor = '';
|
||||||
this.draggingColumn = null;
|
this.draggingColumn = null;
|
||||||
if (column.sortable) bodyStyle.cursor = 'pointer';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import debounce from 'throttle-debounce/debounce';
|
import debounce from 'throttle-debounce/debounce';
|
||||||
import { orderBy } from './util';
|
import { orderBy, getColumnById } from './util';
|
||||||
|
|
||||||
const getRowIdentity = (row, rowKey) => {
|
const getRowIdentity = (row, rowKey) => {
|
||||||
if (!row) throw new Error('row is required when get row identity');
|
if (!row) throw new Error('row is required when get row identity');
|
||||||
|
@ -24,6 +24,7 @@ const TableStore = function(table, initialState = {}) {
|
||||||
fixedColumns: [],
|
fixedColumns: [],
|
||||||
rightFixedColumns: [],
|
rightFixedColumns: [],
|
||||||
_data: null,
|
_data: null,
|
||||||
|
filteredData: null,
|
||||||
data: null,
|
data: null,
|
||||||
sortCondition: {
|
sortCondition: {
|
||||||
column: null,
|
column: null,
|
||||||
|
@ -34,7 +35,8 @@ const TableStore = function(table, initialState = {}) {
|
||||||
selection: [],
|
selection: [],
|
||||||
reserveSelection: false,
|
reserveSelection: false,
|
||||||
selectable: null,
|
selectable: null,
|
||||||
hoverRow: null
|
hoverRow: null,
|
||||||
|
filters: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let prop in initialState) {
|
for (let prop in initialState) {
|
||||||
|
@ -80,7 +82,38 @@ TableStore.prototype.mutations = {
|
||||||
},
|
},
|
||||||
|
|
||||||
changeSortCondition(states) {
|
changeSortCondition(states) {
|
||||||
states.data = orderBy((states._data || []), states.sortCondition.property, states.sortCondition.direction);
|
states.data = orderBy((states.filteredData || states._data || []), states.sortCondition.property, states.sortCondition.direction);
|
||||||
|
|
||||||
|
Vue.nextTick(() => this.table.updateScrollY());
|
||||||
|
},
|
||||||
|
|
||||||
|
filterChange(states, options) {
|
||||||
|
let { column, values } = options;
|
||||||
|
if (values && !Array.isArray(values)) {
|
||||||
|
values = [values];
|
||||||
|
}
|
||||||
|
|
||||||
|
const prop = column.property;
|
||||||
|
if (prop) {
|
||||||
|
states.filters[column.id] = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = states._data;
|
||||||
|
const filters = states.filters;
|
||||||
|
|
||||||
|
Object.keys(filters).forEach((columnId) => {
|
||||||
|
const values = 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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
states.filteredData = data;
|
||||||
|
states.data = orderBy(data, states.sortCondition.property, states.sortCondition.direction);
|
||||||
|
|
||||||
Vue.nextTick(() => this.table.updateScrollY());
|
Vue.nextTick(() => this.table.updateScrollY());
|
||||||
},
|
},
|
||||||
|
|
|
@ -91,6 +91,7 @@
|
||||||
import throttle from 'throttle-debounce/throttle';
|
import throttle from 'throttle-debounce/throttle';
|
||||||
import debounce from 'throttle-debounce/debounce';
|
import debounce from 'throttle-debounce/debounce';
|
||||||
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
|
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
|
||||||
|
import { $t } from 'element-ui/src/locale';
|
||||||
import TableStore from './table-store';
|
import TableStore from './table-store';
|
||||||
import TableLayout from './table-layout';
|
import TableLayout from './table-layout';
|
||||||
import TableBody from './table-body';
|
import TableBody from './table-body';
|
||||||
|
@ -130,7 +131,7 @@
|
||||||
|
|
||||||
emptyText: {
|
emptyText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '暂无数据'
|
default: $t('el.table.emptyText')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -75,3 +75,21 @@ export const orderBy = function(array, sortKey, reverse) {
|
||||||
return a === b ? 0 : a > b ? order : -order;
|
return a === b ? 0 : a > b ? order : -order;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getColumnById = function(table, columnId) {
|
||||||
|
let column = null;
|
||||||
|
table.columns.forEach(function(item) {
|
||||||
|
if (item.id === columnId) {
|
||||||
|
column = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
@import "./loading.css";
|
@import "./loading.css";
|
||||||
@import "./dialog.css";
|
@import "./dialog.css";
|
||||||
@import "./table.css";
|
@import "./table.css";
|
||||||
|
@import "./table-column.css";
|
||||||
@import "./pagination.css";
|
@import "./pagination.css";
|
||||||
@import "./popover.css";
|
@import "./popover.css";
|
||||||
@import "./tooltip.css";
|
@import "./tooltip.css";
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
@import "./checkbox.css";
|
||||||
|
@import "./tag.css";
|
||||||
|
@import "./common/var.css";
|
||||||
|
|
||||||
|
@component-namespace el {
|
||||||
|
@b table-filter {
|
||||||
|
border: solid 1px #d3dce6;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: var(--dropdown-menu-box-shadow);
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 2px 0;
|
||||||
|
|
||||||
|
/** used for dropdown mode */
|
||||||
|
@e list {
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@e list-item {
|
||||||
|
line-height: 36px;
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--dropdown-menuItem-hover-fill);
|
||||||
|
color: var(--dropdown-menuItem-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@when active {
|
||||||
|
background-color: #20a0ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@e content {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@e bottom {
|
||||||
|
border-top: 1px solid #d3dce6;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #8492a6;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
padding: 0 3px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #20a0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
color: #c0ccda;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@e checkbox-group {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.el-checkbox {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-checkbox:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -213,16 +213,21 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.highlight {
|
||||||
|
color: #20a0ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& div.caret-wrapper {
|
& .caret-wrapper {
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 50%;
|
cursor: pointer;
|
||||||
transform: translateY(-50%);
|
display: inline-block;
|
||||||
right: 10px;
|
vertical-align: middle;
|
||||||
width: 10px;
|
margin-left: 5px;
|
||||||
height: 12px;
|
margin-top: -2px;
|
||||||
padding: 0;
|
width: 16px;
|
||||||
|
height: 34px;
|
||||||
overflow: initial;
|
overflow: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,19 +238,20 @@
|
||||||
border: 0;
|
border: 0;
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
left: 3px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
&.ascending {
|
&.ascending {
|
||||||
top: 0;
|
top: 11px;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
border-bottom: 5px solid #99A9BF;
|
border-bottom: 5px solid #99a9bf;
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.descending {
|
&.descending {
|
||||||
bottom: 0;
|
bottom: 11px;
|
||||||
border-top: 5px solid #99A9BF;
|
border-top: 5px solid #99a9bf;
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
|
@ -333,7 +339,12 @@
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@e column-filter-label {
|
@e column-filter-trigger {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 34px;
|
||||||
|
margin-left: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
& i {
|
& i {
|
||||||
color: #99a9bf;
|
color: #99a9bf;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,12 @@ export default {
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
preview: 'Preview',
|
preview: 'Preview',
|
||||||
continue: 'Continue'
|
continue: 'Continue'
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
emptyText: 'No Data',
|
||||||
|
confirmFilter: 'Confirm',
|
||||||
|
resetFilter: 'Reset',
|
||||||
|
clearFilter: 'All'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,6 +68,12 @@ export default {
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
preview: '查看图片',
|
preview: '查看图片',
|
||||||
continue: '继续上传'
|
continue: '继续上传'
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
emptyText: '暂无数据',
|
||||||
|
confirmFilter: '筛选',
|
||||||
|
resetFilter: '重置',
|
||||||
|
clearFilter: '全部'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,7 +54,7 @@ describe('Table', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('row data', () => {
|
it('row data', () => {
|
||||||
const cells = toArray(vm.$el.querySelectorAll('.cell'))
|
const cells = toArray(vm.$el.querySelectorAll('td .cell'))
|
||||||
.map(node => node.textContent);
|
.map(node => node.textContent);
|
||||||
|
|
||||||
expect(cells).to.eql(testDataArr);
|
expect(cells).to.eql(testDataArr);
|
||||||
|
@ -591,7 +591,7 @@ describe('Table', () => {
|
||||||
it('ascending', done => {
|
it('ascending', done => {
|
||||||
const elm = vm.$el.querySelector('.caret-wrapper');
|
const elm = vm.$el.querySelector('.caret-wrapper');
|
||||||
|
|
||||||
elm.parentNode.click();
|
elm.click();
|
||||||
setTimeout(_ => {
|
setTimeout(_ => {
|
||||||
const lastCells = vm.$el.querySelectorAll('.el-table__body-wrapper tbody tr td:last-child');
|
const lastCells = vm.$el.querySelectorAll('.el-table__body-wrapper tbody tr td:last-child');
|
||||||
expect(toArray(lastCells).map(node => node.textContent))
|
expect(toArray(lastCells).map(node => node.textContent))
|
||||||
|
@ -603,7 +603,7 @@ describe('Table', () => {
|
||||||
it('descending', done => {
|
it('descending', done => {
|
||||||
const elm = vm.$el.querySelector('.caret-wrapper');
|
const elm = vm.$el.querySelector('.caret-wrapper');
|
||||||
|
|
||||||
elm.parentNode.click();
|
elm.click();
|
||||||
setTimeout(_ => {
|
setTimeout(_ => {
|
||||||
const lastCells = vm.$el.querySelectorAll('.el-table__body-wrapper tbody tr td:last-child');
|
const lastCells = vm.$el.querySelectorAll('.el-table__body-wrapper tbody tr td:last-child');
|
||||||
expect(toArray(lastCells).map(node => node.textContent))
|
expect(toArray(lastCells).map(node => node.textContent))
|
||||||
|
|
Loading…
Reference in New Issue