Commit 1f4adb7c authored by 杨奕's avatar 杨奕 Committed by GitHub

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
parent f2988cd1
......@@ -6,6 +6,7 @@ npm-debug.log.*
lerna-debug.log.*
lib
.idea
.vscode
examples/element-ui
examples/pages/en-US
examples/pages/zh-CN
......
......@@ -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>
<div class="block">
<span class="demonstration">Month</span>
<el-date-picker
v-model="value4"
type="month"
placeholder="Pick a month">
</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">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 | — | — |
......
......@@ -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>
<div class="block">
<span class="demonstration">Month</span>
<el-date-picker
v-model="value4"
type="month"
placeholder="Pick a month">
</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">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 | — | — |
......
......@@ -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>
<div class="block">
<span class="demonstration"></span>
<el-date-picker
v-model="value4"
type="month"
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="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 | — | — |
......
......@@ -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);
}
}
}
......
......@@ -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() {
......
......@@ -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);
}
},
......
......@@ -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;
......
......@@ -26,6 +26,13 @@
}
}
@include m(dates) {
.el-input__inner {
text-overflow: ellipsis;
white-space: nowrap;
}
}
.el-icon-circle-close {
cursor: pointer;
}
......
......@@ -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({
......
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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment