DatePicker: support multiple dates selection (#10650)

* DatePicker: add multiselect feature

* DatePicker: add multiselect feature

* DatePicker: add multiselect feature

* DatePicker: add multiselect feature

* DatePicker: add multiselect feature

* Datepicker: fix can't clear bug

*  Datepicker: add unit test

* Datepicker: add unit test

* Datepicker: add unit test

* Datepicker: add unit test

* Datepicker: add unit test

* Datepicker: add unit test

* DatePicker: support multiple dates selection

* reflect review comments
pull/10434/merge
杨奕 2018-04-17 14:09:43 +08:00 committed by GitHub
parent f2988cd1bd
commit 1f4adb7c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 340 additions and 97 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ npm-debug.log.*
lerna-debug.log.*
lib
.idea
.vscode
examples/element-ui
examples/pages/en-US
examples/pages/zh-CN

View File

@ -66,7 +66,8 @@
value10: '',
value11: '',
value12: '',
value13: []
value13: [],
value14: []
};
}
};
@ -88,6 +89,20 @@
}
}
.demo-date-picker .container {
flex: 1;
border-right: solid 1px #EFF2F6;
.block {
border-right: none;
&:last-child {
border-top: solid 1px #EFF2F6;
}
}
&:last-child {
border-right: none;
}
}
.demo-date-picker .demonstration {
display: block;
color: #8492a6;
@ -163,40 +178,51 @@ Basic date picker measured by 'day'.
};
</script>
```
:::
### Other measurements
You can choose week, month or year by extending the standard date picker component.
You can choose week, month, year or multiple dates by extending the standard date picker component.
:::demo
```html
<div class="block">
<span class="demonstration">Week</span>
<el-date-picker
v-model="value3"
type="week"
format="Week WW"
placeholder="Pick a week">
</el-date-picker>
<div class="container">
<div class="block">
<span class="demonstration">Week</span>
<el-date-picker
v-model="value3"
type="week"
format="Week WW"
placeholder="Pick a week">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Month</span>
<el-date-picker
v-model="value4"
type="month"
placeholder="Pick a month">
</el-date-picker>
</div>
</div>
<div class="block">
<span class="demonstration">Month</span>
<el-date-picker
v-model="value4"
type="month"
placeholder="Pick a month">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Year</span>
<el-date-picker
v-model="value5"
type="year"
placeholder="Pick a year">
</el-date-picker>
<div class="container">
<div class="block">
<span class="demonstration">Year</span>
<el-date-picker
v-model="value5"
type="year"
placeholder="Pick a year">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Dates</span>
<el-date-picker
type="dates"
v-model="value14"
placeholder="Pick one or more dates">
</el-date-picker>
</div>
</div>
<script>
@ -205,7 +231,8 @@ You can choose week, month or year by extending the standard date picker compone
return {
value3: '',
value4: '',
value5: ''
value5: '',
value14: []
};
}
};
@ -455,7 +482,7 @@ When picking a date range, you can assign the time part for start date and end d
| placeholder | placeholder in non-range mode | string | — | — |
| start-placeholder | placeholder for the start date in range mode | string | — | — |
| end-placeholder | placeholder for the end date in range mode | string | — | — |
| type | type of the picker | string | year/month/date/datetime/ week/datetimerange/daterange | date |
| type | type of the picker | string | year/month/date/dates/datetime/ week/datetimerange/daterange | date |
| format | format of the displayed value in the input box | string | see [date formats](#/en-US/component/date-picker#date-formats) | yyyy-MM-dd |
| align | alignment | left/center/right | left |
| popper-class | custom class name for DatePicker's dropdown | string | — | — |

View File

@ -66,7 +66,8 @@
value10: '',
value11: '',
value12: '',
value13: []
value13: [],
value14: []
};
}
};
@ -88,6 +89,20 @@
}
}
.demo-date-picker .container {
flex: 1;
border-right: solid 1px #EFF2F6;
.block {
border-right: none;
&:last-child {
border-top: solid 1px #EFF2F6;
}
}
&:last-child {
border-right: none;
}
}
.demo-date-picker .demonstration {
display: block;
color: #8492a6;
@ -169,35 +184,47 @@ Date Picker básico por "día".
### Otras mediciones
Puede seleccionar la semana, el mes o el año extendiendo el componente date picker estándar.
You can choose week, month, year or multiple dates by extending the standard date picker component.
:::demo
```html
<div class="block">
<span class="demonstration">Week</span>
<el-date-picker
v-model="value3"
type="week"
format="Week WW"
placeholder="Pick a week">
</el-date-picker>
<div class="container">
<div class="block">
<span class="demonstration">Week</span>
<el-date-picker
v-model="value3"
type="week"
format="Week WW"
placeholder="Pick a week">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Month</span>
<el-date-picker
v-model="value4"
type="month"
placeholder="Pick a month">
</el-date-picker>
</div>
</div>
<div class="block">
<span class="demonstration">Month</span>
<el-date-picker
v-model="value4"
type="month"
placeholder="Pick a month">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Year</span>
<el-date-picker
v-model="value5"
type="year"
placeholder="Pick a year">
</el-date-picker>
<div class="container">
<div class="block">
<span class="demonstration">Year</span>
<el-date-picker
v-model="value5"
type="year"
placeholder="Pick a year">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Dates</span>
<el-date-picker
type="dates"
v-model="value14"
placeholder="Pick one or more dates">
</el-date-picker>
</div>
</div>
<script>
@ -206,7 +233,8 @@ Puede seleccionar la semana, el mes o el año extendiendo el componente date pic
return {
value3: '',
value4: '',
value5: ''
value5: '',
value14: []
};
}
};
@ -457,7 +485,7 @@ Al seleccionar un intervalo de fechas, puede asignar la hora para la fecha de in
| placeholder | placeholder cuando el modo NO es rango | string | — | — |
| start-placeholder | placeholder para la fecha de inicio en modo rango | string | — | — |
| end-placeholder | placeholder para la fecha final en modo rango | string | — | — |
| type | tipo de picker | string | year/month/date/datetime/ week/datetimerange/daterange | date |
| type | tipo de picker | string | year/month/date/dates/datetime/ week/datetimerange/daterange | date |
| format | formato en que se muestra el valor en el input | string | ver [date formats](#/es/component/date-picker#date-formats) | yyyy-MM-dd |
| align | alineación | left/center/right | left | |
| popper-class | nombre de clase personalizada para el dropdown de DatePicker | string | — | — |

View File

@ -66,7 +66,8 @@
value10: '',
value11: '',
value12: '',
value13: []
value13: [],
value14: []
};
}
};
@ -76,6 +77,7 @@
.demo-block.demo-date-picker .source {
padding: 0;
display: flex;
flex-wrap: wrap;
}
.demo-date-picker .block {
@ -87,6 +89,20 @@
border-right: none;
}
}
.demo-date-picker .container {
flex: 1;
border-right: solid 1px #EFF2F6;
.block {
border-right: none;
&:last-child {
border-top: solid 1px #EFF2F6;
}
}
&:last-child {
border-right: none;
}
}
.demo-date-picker .demonstration {
display: block;
@ -167,35 +183,46 @@
### 其他日期单位
通过扩展基础的日期选择,可以选择周、月、年
通过扩展基础的日期选择,可以选择周、月、年或多个日期
:::demo
```html
<div class="block">
<span class="demonstration"></span>
<el-date-picker
v-model="value3"
type="week"
format="yyyy 第 WW 周"
placeholder="选择周">
</el-date-picker>
<div class="container">
<div class="block">
<span class="demonstration"></span>
<el-date-picker
v-model="value3"
type="week"
format="yyyy 第 WW 周"
placeholder="选择周">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration"></span>
<el-date-picker
v-model="value4"
type="month"
placeholder="选择月">
</el-date-picker>
</div>
</div>
<div class="block">
<span class="demonstration"></span>
<el-date-picker
v-model="value4"
type="month"
placeholder="选择月">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration"></span>
<el-date-picker
v-model="value5"
align="right"
type="year"
placeholder="选择年">
</el-date-picker>
<div class="container">
<div class="block">
<span class="demonstration"></span>
<el-date-picker
v-model="value5"
type="year"
placeholder="选择年">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">多个日期</span>
<el-date-picker
type="dates"
v-model="value14"
placeholder="选择一个或多个日期">
</el-date-picker>
</div>
</div>
<script>
@ -204,7 +231,8 @@
return {
value3: '',
value4: '',
value5: ''
value5: '',
value14: []
};
}
};
@ -221,6 +249,7 @@
<template>
<div class="block">
<span class="demonstration">默认</span>
{{value6}}
<el-date-picker
v-model="value6"
type="daterange"
@ -408,7 +437,7 @@
| placeholder | 非范围选择时的占位内容 | string | — | — |
| start-placeholder | 范围选择时开始日期的占位内容 | string | — | — |
| end-placeholder | 范围选择时结束日期的占位内容 | string | — | — |
| type | 显示类型 | string | year/month/date/week/ datetime/datetimerange/daterange | date |
| type | 显示类型 | string | year/month/date/dates/ week/datetime/datetimerange/daterange | date |
| format | 显示在输入框中的格式 | string | 见[日期格式](#/zh-CN/component/date-picker#ri-qi-ge-shi) | yyyy-MM-dd |
| align | 对齐方式 | string | left, center, right | left |
| popper-class | DatePicker 下拉框的类名 | string | — | — |

View File

@ -73,6 +73,10 @@
disabledDate: {},
selectedDate: {
type: Array
},
minDate: {},
maxDate: {},
@ -129,6 +133,7 @@
const startDate = this.startDate;
const disabledDate = this.disabledDate;
const selectedDate = this.selectedDate || this.value;
const now = clearHours(new Date());
for (let i = 0; i < 6; i++) {
@ -181,7 +186,10 @@
}
}
cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
let newDate = new Date(time);
cell.disabled = typeof disabledDate === 'function' && disabledDate(newDate);
cell.selected = Array.isArray(selectedDate) &&
selectedDate.filter(date => date.toString() === newDate.toString())[0];
this.$set(row, this.showWeekNumber ? j + 1 : j, cell);
}
@ -285,6 +293,10 @@
classes.push('disabled');
}
if (cell.selected) {
classes.push('selected');
}
return classes.join(' ');
},
@ -472,6 +484,20 @@
value: value,
date: newDate
});
} else if (selectionMode === 'dates') {
let selectedDate = this.selectedDate;
if (!cell.selected) {
selectedDate.push(newDate);
} else {
selectedDate.forEach((date, index) => {
if (date.toString() === newDate.toString()) {
selectedDate.splice(index, 1);
}
});
}
this.$emit('select', selectedDate);
}
}
}

View File

@ -90,12 +90,14 @@
<date-table
v-show="currentView === 'date'"
@pick="handleDatePick"
@select="handleDateSelect"
:selection-mode="selectionMode"
:first-day-of-week="firstDayOfWeek"
:value="new Date(value)"
:default-value="defaultValue ? new Date(defaultValue) : null"
:date="date"
:disabled-date="disabledDate">
:disabled-date="disabledDate"
:selected-date="selectedDate">
</date-table>
<year-table
v-show="currentView === 'year'"
@ -124,7 +126,8 @@
size="mini"
type="text"
class="el-picker-panel__link-btn"
@click="changeToNow">
@click="changeToNow"
v-show="selectionMode !== 'dates'">
{{ t('el.datepicker.now') }}
</el-button>
<el-button
@ -208,6 +211,8 @@
if (this.currentView !== 'year' || this.currentView !== 'month') {
this.currentView = 'month';
}
} else if (newVal === 'dates') {
this.currentView = 'date';
}
}
},
@ -234,6 +239,9 @@
emit(value, ...args) {
if (!value) {
this.$emit('pick', value, ...args);
} else if (Array.isArray(value)) {
const dates = value.map(date => this.showTime ? clearMilliseconds(date) : clearTime(date));
this.$emit('pick', dates, ...args);
} else {
this.$emit('pick', this.showTime ? clearMilliseconds(value) : clearTime(value), ...args);
}
@ -317,6 +325,12 @@
}
},
handleDateSelect(value) {
if (this.selectionMode === 'dates') {
this.selectedDate = value;
}
},
handleDatePick(value) {
if (this.selectionMode === 'day') {
this.date = this.value ? modifyDate(this.date, value.getFullYear(), value.getMonth(), value.getDate()) : modifyWithDefaultTime(value, this.defaultTime);
@ -348,8 +362,12 @@
},
confirm() {
const date = this.value ? this.date : modifyWithDefaultTime(this.date, this.defaultTime);
this.emit(date);
if (this.selectionMode === 'dates') {
this.emit(this.selectedDate);
} else {
const date = this.value ? this.date : modifyWithDefaultTime(this.date, this.defaultTime);
this.emit(date);
}
},
resetView() {
@ -467,6 +485,7 @@
visible: false,
currentView: 'date',
disabledDate: '',
selectedDate: [],
firstDayOfWeek: 7,
showWeekNumber: false,
timePickerVisible: false,
@ -495,7 +514,7 @@
},
footerVisible() {
return this.showTime;
return this.showTime || this.selectionMode === 'dates';
},
visibleTime() {

View File

@ -2,7 +2,7 @@
<el-input
class="el-date-editor"
:class="'el-date-editor--' + type"
:readonly="!editable || readonly"
:readonly="!editable || readonly || type === 'dates'"
:disabled="pickerDisabled"
:size="pickerSize"
:name="name"
@ -123,7 +123,8 @@ const HAVE_TRIGGER_TYPES = [
'year',
'daterange',
'timerange',
'datetimerange'
'datetimerange',
'dates'
];
const DATE_FORMATTER = function(value, format) {
if (format === 'timestamp') return value.getTime();
@ -242,6 +243,15 @@ const TYPE_VALUE_RESOLVER_MAP = {
return null;
}
}
},
dates: {
formatter(value, format) {
return value.map(date => DATE_FORMATTER(date, format));
},
parser(value, format) {
return (typeof value === 'string' ? value.split(', ') : value)
.map(date => date instanceof Date ? date : DATE_PARSER(date, format));
}
}
};
const PLACEMENT_MAP = {
@ -275,8 +285,10 @@ const valueEquals = function(a, b) {
const aIsArray = a instanceof Array;
const bIsArray = b instanceof Array;
if (aIsArray && bIsArray) {
return new Date(a[0]).getTime() === new Date(b[0]).getTime() &&
new Date(a[1]).getTime() === new Date(b[1]).getTime();
if (a.length !== b.length) {
return false;
}
return a.every((item, index) => new Date(item).getTime() === new Date(b[index]).getTime());
}
if (!aIsArray && !bIsArray) {
return new Date(a).getTime() === new Date(b).getTime();
@ -374,7 +386,7 @@ export default {
if (this.readonly || this.pickerDisabled) return;
if (val) {
this.showPicker();
this.valueOnOpen = this.value;
this.valueOnOpen = Array.isArray(this.value) ? [...this.value] : this.value;
} else {
this.hidePicker();
this.emitChange(this.value);
@ -394,6 +406,7 @@ export default {
handler(val) {
if (this.picker) {
this.picker.value = val;
this.picker.selectedDate = Array.isArray(val) ? val : [];
}
}
},
@ -449,6 +462,8 @@ export default {
return 'month';
} else if (this.type === 'year') {
return 'year';
} else if (this.type === 'dates') {
return 'dates';
}
return 'day';
@ -468,8 +483,14 @@ export default {
this.userInput[0] || (formattedValue && formattedValue[0]) || '',
this.userInput[1] || (formattedValue && formattedValue[1]) || ''
];
} else if (this.userInput !== null) {
return this.userInput;
} else if (formattedValue) {
return this.type === 'dates'
? formattedValue.join(', ')
: formattedValue;
} else {
return this.userInput !== null ? this.userInput : formattedValue || '';
return '';
}
},
@ -655,7 +676,18 @@ export default {
},
handleClose() {
if (!this.pickerVisible) return;
this.pickerVisible = false;
const {
type,
valueOnOpen,
valueFormat,
rangeSeparator
} = this;
if (type === 'dates' && this.picker) {
this.picker.selectedDate = parseAsFormatAndType(valueOnOpen, valueFormat, type, rangeSeparator) || valueOnOpen;
this.emitInput(this.picker.selectedDate);
}
},
handleFieldReset(initialValue) {
@ -769,6 +801,7 @@ export default {
this.picker.selectionMode = this.selectionMode;
this.picker.unlinkPanels = this.unlinkPanels;
this.picker.arrowControl = this.arrowControl || this.timeArrowControl || false;
this.picker.selectedDate = Array.isArray(this.value) && this.value || [];
this.$watch('format', (format) => {
this.picker.format = format;
});
@ -846,7 +879,7 @@ export default {
emitInput(val) {
const formatted = this.formatToValue(val);
if (!valueEquals(this.value, formatted)) {
if (!valueEquals(this.value, formatted) || this.type === 'dates') {
this.$emit('input', formatted);
}
},

View File

@ -120,6 +120,22 @@
color: $--color-text-placeholder;
}
&.selected div {
margin-left: 5px;
margin-right: 5px;
background-color: $--datepicker-inrange-color;
border-radius: 15px;
&:hover {
background-color: $--datepicker-inrange-hover-color;
}
}
&.selected span {
background-color: $--datepicker-active-color;
color: $--color-white;
border-radius: 15px;
}
&.week {
font-size: 80%;
color: $--datepicker-header-color;

View File

@ -26,6 +26,13 @@
}
}
@include m(dates) {
.el-input__inner {
text-overflow: ellipsis;
white-space: nowrap;
}
}
.el-icon-circle-close {
cursor: pointer;
}

View File

@ -1400,6 +1400,63 @@ describe('DatePicker', () => {
});
});
describe('type:dates', () => {
let vm;
beforeEach(done => {
vm = createVue({
template: '<el-date-picker type="dates" value-format="timestamp" v-model="value" ref="compo" />',
data() {
return {
value: []
};
}
}, true);
const input = vm.$el.querySelector('input');
input.blur();
input.focus();
setTimeout(done, DELAY);
});
afterEach(() => destroyVM(vm));
it('click cell', done => {
const td = vm.$refs.compo.picker.$el.querySelector('.el-date-table__row .available');
td.click();
setTimeout(_ => {
expect(vm.$refs.compo.picker.selectedDate).to.exist;
expect(vm.value.length).to.equal(1);
done();
}, DELAY);
});
it('value format', done => {
const td = vm.$refs.compo.picker.$el.querySelector('.el-date-table__row .available');
td.click();
setTimeout(_ => {
vm.$refs.compo.picker.$el.querySelector('.el-button--default').click();
setTimeout(() => {
expect(vm.$refs.compo.picker.selectedDate).to.exist;
expect(vm.value.length).to.equal(1);
done();
}, DELAY);
}, DELAY);
});
it('restore value when cancel', done => {
const td = vm.$refs.compo.picker.$el.querySelector('.el-date-table__row .available');
td.click();
setTimeout(_ => {
vm.$refs.compo.handleClose();
setTimeout(() => {
expect(vm.value.length).to.equal(0);
done();
}, DELAY);
}, DELAY);
});
});
describe('type:daterange', () => {
it('works', done => {
vm = createVue({

View File

@ -1,6 +1,6 @@
import { ElementUIComponent, ElementUIComponentSize, ElementUIHorizontalAlignment } from './component'
export type DatePickerType = 'year' | 'month' | 'date' | 'datetime' | 'week' | 'datetimerange' | 'daterange'
export type DatePickerType = 'year' | 'month' | 'date' | 'datetime' | 'week' | 'datetimerange' | 'daterange' | 'dates'
export type FirstDayOfWeek = 1 | 2 | 3 | 4 | 5 | 6 | 7
export interface DisabledDateChecker {