diff --git a/examples/docs/zh-cn/form.md b/examples/docs/zh-cn/form.md
index ab4589def..141fe6cc2 100644
--- a/examples/docs/zh-cn/form.md
+++ b/examples/docs/zh-cn/form.md
@@ -792,8 +792,8 @@
|---------- |-------------- |---------- |-------------------------------- |-------- |
| model | 表单数据对象 | object | — | — |
| rules | 表单验证规则 | object | — | — |
-| type | 表单类型 | string | stacked, inline, horizontal | horizontal |
-| label-align | 表单域标签的水平对齐位置 | string | right,left | right |
+| inline | 行内表单模式 | boolean | — | false |
+| label-position | 表单域标签的位置 | string | right/left/top | right |
| label-width | 表单域标签的宽度,所有的 form-item 都会继承 form 组件的 labelWidth 的值 | string | — | — |
| label-suffix | 表单域标签的后缀 | string | — | — |
diff --git a/examples/docs/zh-cn/table.md b/examples/docs/zh-cn/table.md
index 00ccd076d..6a4366d89 100644
--- a/examples/docs/zh-cn/table.md
+++ b/examples/docs/zh-cn/table.md
@@ -918,6 +918,7 @@
| cell-mouse-leave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event |
| cell-click | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
| row-click | 当某一行被点击时会触发该事件 | row, event |
+| sort-change | 当表格的排序条件发生变化的时候会触发该事件 | { column, prop, order } |
### Table Methods
| 方法名 | 说明 | 参数 |
@@ -931,7 +932,8 @@
| prop | 对应列内容的字段名,也可以使用 property 属性 | string | — | — |
| width | 对应列的宽度 | string | — | — |
| fixed | 列是否固定在左侧或者右侧,true 表示固定在左侧 | string, boolean | true, left, right | - |
-| sortable | 对应列是否可以排序 | boolean | — | false |
+| sortable | 对应列是否可以排序,如果设置为 'custom',则代表用户希望远程排序,需要监听 Table 的 sort-change 事件 | boolean, string | true, false, 'custom' | false |
+| sort-method | 对数据进行排序的时候使用的方法,仅当 sortable 设置为 true 的时候有效 | Function(a, b) | - | - |
| resizable | 对应列是否可以通过拖动改变宽度(如果需要在 el-table 上设置 border 属性为真) | boolean | — | true |
| type | 对应列的类型。如果设置了 `selection` 则显示多选框,如果设置了 `index` 则显示该行的索引(从 1 开始计算) | string | selection/index | — |
| formatter | 用来格式化内容 | Function(row, column) | — | — |
diff --git a/packages/form/src/form-item.vue b/packages/form/src/form-item.vue
index 6dbf1b003..92266f9f2 100644
--- a/packages/form/src/form-item.vue
+++ b/packages/form/src/form-item.vue
@@ -85,7 +85,6 @@
methods: {
validate(trigger, cb) {
var rules = this.getFilteredRule(trigger);
-
if (!rules || rules.length === 0) {
cb && cb();
return true;
diff --git a/packages/form/src/form.vue b/packages/form/src/form.vue
index adebb2e10..bc47ab9dd 100644
--- a/packages/form/src/form.vue
+++ b/packages/form/src/form.vue
@@ -15,7 +15,6 @@
props: {
model: Object,
rules: Object,
- type: String,
labelPosition: String,
labelWidth: String,
labelSuffix: {
@@ -35,6 +34,7 @@
this.fields[field.prop] = field;
this.fieldLength++;
});
+ /* istanbul ignore next */
this.$on('el.form.removeField', (field) => {
delete this.fields[field.prop];
this.fieldLength--;
diff --git a/packages/table/src/table-column.js b/packages/table/src/table-column.js
index 4ce4f6f29..0e235de3a 100644
--- a/packages/table/src/table-column.js
+++ b/packages/table/src/table-column.js
@@ -6,19 +6,19 @@ let columnIdSeed = 1;
const defaults = {
default: {
- direction: ''
+ order: ''
},
selection: {
width: 48,
minWidth: 48,
realWidth: 48,
- direction: ''
+ order: ''
},
index: {
width: 48,
minWidth: 48,
realWidth: 48,
- direction: ''
+ order: ''
}
};
@@ -98,9 +98,10 @@ export default {
minWidth: {},
template: String,
sortable: {
- type: Boolean,
+ type: [Boolean, String],
default: false
},
+ sortMethod: Function,
resizable: {
type: Boolean,
default: true
@@ -201,6 +202,7 @@ export default {
isColumnGroup,
align: this.align ? 'is-' + this.align : null,
sortable: this.sortable,
+ sortMethod: this.sortMethod,
resizable: this.resizable,
showTooltipWhenOverflow: this.showTooltipWhenOverflow,
formatter: this.formatter,
diff --git a/packages/table/src/table-header.js b/packages/table/src/table-header.js
index ee2d15c84..9753a0829 100644
--- a/packages/table/src/table-header.js
+++ b/packages/table/src/table-header.js
@@ -33,7 +33,7 @@ export default {
on-mousemove={ ($event) => this.handleMouseMove($event, column) }
on-mouseout={ this.handleMouseOut }
on-mousedown={ ($event) => this.handleMouseDown($event, column) }
- class={ [column.id, column.direction, column.align, this.isCellHidden(cellIndex) ? 'hidden' : ''] }>
+ class={ [column.id, column.order, column.align, this.isCellHidden(cellIndex) ? 'hidden' : ''] }>
0 ? 'highlight' : ''] }>
{
column.headerTemplate
@@ -269,26 +269,30 @@ export default {
if (!column.sortable) return;
- const sortCondition = this.store.states.sortCondition;
+ const states = this.store.states;
+ let sortProp = states.sortProp;
+ let sortOrder;
+ const sortingColumn = states.sortingColumn;
- if (sortCondition.column !== column) {
- if (sortCondition.column) {
- sortCondition.column.direction = '';
+ if (sortingColumn !== column) {
+ if (sortingColumn) {
+ sortingColumn.order = null;
}
- sortCondition.column = column;
- sortCondition.property = column.property;
+ states.sortingColumn = column;
+ sortProp = column.property;
}
- if (!column.direction) {
- column.direction = 'ascending';
- } else if (column.direction === 'ascending') {
- column.direction = 'descending';
+ if (!column.order) {
+ sortOrder = column.order = 'ascending';
+ } else if (column.order === 'ascending') {
+ sortOrder = column.order = 'descending';
} else {
- column.direction = '';
- sortCondition.column = null;
- sortCondition.property = null;
+ sortOrder = column.order = null;
+ states.sortingColumn = null;
+ sortProp = null;
}
- sortCondition.direction = column.direction === 'descending' ? -1 : 1;
+ states.sortProp = sortProp;
+ states.sortOrder = sortOrder;
this.store.commit('changeSortCondition');
}
diff --git a/packages/table/src/table-store.js b/packages/table/src/table-store.js
index e844b0478..9f37d836b 100644
--- a/packages/table/src/table-store.js
+++ b/packages/table/src/table-store.js
@@ -11,6 +11,14 @@ const getRowIdentity = (row, rowKey) => {
}
};
+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);
+};
+
const TableStore = function(table, initialState = {}) {
if (!table) {
throw new Error('Table is required.');
@@ -26,11 +34,9 @@ const TableStore = function(table, initialState = {}) {
_data: null,
filteredData: null,
data: null,
- sortCondition: {
- column: null,
- property: null,
- direction: null
- },
+ sortingColumn: null,
+ sortProp: null,
+ sortOrder: null,
isAllSelected: false,
selection: [],
reserveSelection: false,
@@ -52,7 +58,7 @@ TableStore.prototype.mutations = {
if (data && data[0] && typeof data[0].$selected === 'undefined') {
data.forEach((item) => Vue.set(item, '$selected', false));
}
- states.data = orderBy((data || []), states.sortCondition.property, states.sortCondition.direction);
+ states.data = sortData((data || []), states);
if (!states.reserveSelection) {
states.isAllSelected = false;
@@ -82,7 +88,13 @@ TableStore.prototype.mutations = {
},
changeSortCondition(states) {
- states.data = orderBy((states.filteredData || states._data || []), states.sortCondition.property, states.sortCondition.direction);
+ states.data = sortData((states.filteredData || states._data || []), states);
+
+ this.table.$emit('sort-change', {
+ column: this.states.sortingColumn,
+ prop: this.states.sortProp,
+ order: this.states.sortOrder
+ });
Vue.nextTick(() => this.table.updateScrollY());
},
@@ -113,7 +125,7 @@ TableStore.prototype.mutations = {
});
states.filteredData = data;
- states.data = orderBy(data, states.sortCondition.property, states.sortCondition.direction);
+ states.data = sortData(data, states);
Vue.nextTick(() => this.table.updateScrollY());
},
diff --git a/packages/table/src/util.js b/packages/table/src/util.js
index d7e3e1c2d..b05cd0228 100644
--- a/packages/table/src/util.js
+++ b/packages/table/src/util.js
@@ -58,14 +58,19 @@ const isObject = function(obj) {
return obj !== null && typeof obj === 'object';
};
-export const orderBy = function(array, sortKey, reverse) {
+export const orderBy = function(array, sortKey, reverse, sortMethod) {
+ if (typeof reverse === 'string') {
+ reverse = reverse === 'descending' ? -1 : 1;
+ }
if (!sortKey) {
return array;
}
const order = (reverse && reverse < 0) ? -1 : 1;
// sort on a copy to avoid mutating original array
- return array.slice().sort(function(a, b) {
+ return array.slice().sort(sortMethod ? function(a, b) {
+ return sortMethod(a, b) ? order : -order;
+ } : function(a, b) {
if (sortKey !== '$key') {
if (isObject(a) && '$value' in a) a = a.$value;
if (isObject(b) && '$value' in b) b = b.$value;
diff --git a/test/unit/specs/dropdown.spec.js b/test/unit/specs/dropdown.spec.js
index 26a0452f5..1aedc8fea 100644
--- a/test/unit/specs/dropdown.spec.js
+++ b/test/unit/specs/dropdown.spec.js
@@ -4,7 +4,7 @@ describe('Dropdown', () => {
it('create', done => {
const vm = createVue({
template: `
-
+
下拉菜单
@@ -18,19 +18,19 @@ describe('Dropdown', () => {
`
}, true);
- let dropdownElm = vm.$el;
+ let dropdown = vm.$refs.dropdown;
+ let dropdownElm = dropdown.$el;
let triggerElm = dropdownElm.children[0];
triggerEvent(triggerElm, 'mouseenter');
setTimeout(_ => {
- var dropdownMenu = document.querySelector('.dropdown-test-creat');
- expect(dropdownMenu.style.display).to.not.ok;
+ expect(dropdown.visible).to.be.true;
triggerEvent(triggerElm, 'mouseleave');
setTimeout(_ => {
- expect(dropdownMenu.style.display).to.be.equal('none');
+ expect(dropdown.visible).to.not.true;
done();
- }, 600);
+ }, 300);
}, 400);
});
it('menu click', done => {
diff --git a/test/unit/specs/form.spec.js b/test/unit/specs/form.spec.js
new file mode 100644
index 000000000..9d5639bdd
--- /dev/null
+++ b/test/unit/specs/form.spec.js
@@ -0,0 +1,577 @@
+import { createVue } from '../util';
+
+describe('Form', () => {
+ it('label width', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: ''
+ }
+ };
+ }
+ }, true);
+ expect(vm.$el.querySelector('.el-form-item__label').style.width).to.equal('80px');
+ expect(vm.$el.querySelector('.el-form-item__content').style.marginLeft).to.equal('80px');
+ done();
+ });
+ it('inline form', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: '',
+ address: ''
+ }
+ };
+ }
+ }, true);
+ expect(vm.$el.classList.contains('el-form--inline')).to.be.true;
+ done();
+ });
+ it('label position', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: '',
+ address: ''
+ }
+ };
+ }
+ }, true);
+ expect(vm.$refs.labelTop.$el.classList.contains('el-form--label-top')).to.be.true;
+ expect(vm.$refs.labelLeft.$el.classList.contains('el-form--label-left')).to.be.true;
+ done();
+ });
+ it('reset field', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: '',
+ address: '',
+ type: []
+ },
+ rules: {
+ name: [
+ { required: true, message: '请输入活动名称', trigger: 'blur' }
+ ],
+ address: [
+ { required: true, message: '请选择活动区域', trigger: 'change' }
+ ],
+ type: [
+ { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue() {
+ this.form.name = 'jack';
+ this.form.address = 'aaaa';
+ this.form.type.push('地推活动');
+ }
+ }
+ }, true);
+ vm.setValue();
+ vm.$refs.form.resetFields();
+ vm.$refs.form.$nextTick(_ => {
+ expect(vm.form.name).to.equal('');
+ expect(vm.form.address).to.equal('');
+ expect(vm.form.type.length).to.equal(0);
+ done();
+ });
+ });
+ it('form item nest', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ date1: '',
+ date2: ''
+ },
+ rules: {
+ date1: [
+ { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue() {
+ this.name = 'jack';
+ this.address = 'aaaa';
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ expect(valid).to.not.true;
+ done();
+ });
+ });
+ describe('validate', () => {
+ it('input', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: ''
+ },
+ rules: {
+ name: [
+ { required: true, message: '请输入活动名称', trigger: 'change', min: 3, max: 6 }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.name = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.not.true;
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('请输入活动名称');
+ vm.setValue('aaaaa');
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('');
+ vm.setValue('aa');
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('请输入活动名称');
+ done();
+ });
+ });
+ });
+ });
+ });
+ it('textarea', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: ''
+ },
+ rules: {
+ name: [
+ { required: true, message: '请输入活动名称', trigger: 'change', min: 3, max: 6 }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.name = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.not.true;
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('请输入活动名称');
+ vm.setValue('aaaaa');
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('');
+ vm.setValue('aa');
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('请输入活动名称');
+ done();
+ });
+ });
+ });
+ });
+ });
+ it('selector', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ region: 'shanghai'
+ },
+ rules: {
+ region: [
+ {required: true, message: '请选择活动区域', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.region = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.true;
+ vm.setValue('');
+ setTimeout(_ => {
+ expect(fields.region.error).to.equal('请选择活动区域');
+ vm.setValue('shanghai');
+
+ setTimeout(_ => {
+ expect(fields.region.error).to.equal('');
+ done();
+ }, 100);
+ }, 100);
+ });
+ });
+ it('datepicker', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ date: ''
+ },
+ rules: {
+ date: [
+ {type: 'date', required: true, message: '请选择日期', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.date = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.not.true;
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.date.error).to.equal('请选择日期');
+
+ vm.setValue(new Date());
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.date.error).to.equal('');
+ done();
+ });
+ });
+ });
+ });
+ it('timepicker', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ date: ''
+ },
+ rules: {
+ date: [
+ {type: 'date', required: true, message: '请选择时间', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.date = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.not.true;
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.date.error).to.equal('请选择时间');
+ vm.setValue(new Date());
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.date.error).to.equal('');
+ done();
+ });
+ });
+ });
+ });
+ it('checkbox group', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ type: []
+ },
+ rules: {
+ type: [
+ { type: 'array', required: true, message: '请选择活动类型', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.type = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.not.true;
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.type.error).to.equal('请选择活动类型');
+ vm.setValue(['地推活动']);
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.type.error).to.equal('');
+ done();
+ });
+ });
+ });
+ });
+ it('radio group', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ type: ''
+ },
+ rules: {
+ type: [
+ { required: true, message: '请选择活动类型', trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.type = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.not.true;
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.type.error).to.equal('请选择活动类型');
+ vm.setValue('线下场地免费');
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.type.error).to.equal('');
+ done();
+ });
+ });
+ });
+ });
+ it('validate field', done => {
+ const vm = createVue({
+ template: `
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: ''
+ },
+ rules: {
+ name: [
+ { required: true, message: '请输入活动名称', trigger: 'change', min: 3, max: 6 }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.name = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validateField('name', valid => {
+ expect(valid).to.not.true;
+ done();
+ });
+ });
+ it('custom validate', done => {
+ var checkName = (rule, value, callback) => {
+ if (value.length < 5) {
+ callback(new Error('长度至少为5'));
+ } else {
+ callback();
+ }
+ };
+ const vm = createVue({
+ template: `
+
+
+
+
+
+ `,
+ data() {
+ return {
+ form: {
+ name: ''
+ },
+ rules: {
+ name: [
+ { validator: checkName, trigger: 'change' }
+ ]
+ }
+ };
+ },
+ methods: {
+ setValue(value) {
+ this.form.name = value;
+ }
+ }
+ }, true);
+ vm.$refs.form.validate(valid => {
+ let fields = vm.$refs.form.fields;
+ expect(valid).to.not.true;
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('长度至少为5');
+ vm.setValue('aaaaaa');
+
+ vm.$refs.form.$nextTick(_ => {
+ expect(fields.name.error).to.equal('');
+ done();
+ });
+ });
+ });
+ });
+ });
+});