DatePicker: add monthrange for type attribute (#4204) (#14487)

pull/14569/head
zxyRealm 2019-03-04 15:41:04 +08:00 committed by iamkun
parent 92c0f7a4ec
commit 2f5a489a9c
12 changed files with 1138 additions and 72 deletions

View File

@ -1,3 +1,4 @@
## DatePicker
Use Date Picker for date input.
@ -202,6 +203,74 @@ Picking a date range is supported.
:::
### Month Range
Picking a month range is supported.
:::demo When in range mode, the left and right panels are linked by default. If you want the two panels to switch current years independently, you can use the `unlink-panels` attribute.
```html
<template>
<div class="block">
<span class="demonstration">Default</span>
<el-date-picker
v-model="value15"
type="monthrange"
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">With quick options</span>
<el-date-picker
v-model="value16"
type="monthrange"
align="right"
unlink-panels
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month"
:picker-options="pickerOptions3">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
pickerOptions3: {
shortcuts: [{
text: 'This month',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: 'This year',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last 6 months',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
value15: '',
value16: ''
};
}
};
</script>
```
:::
### Default Value
If user hasn't picked a date, shows today's calendar by default. You can use `default-value` to set another date. Its value should be parsable by `new Date()`.
@ -372,7 +441,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/dates/datetime/ week/datetimerange/daterange | date |
| type | type of the picker | string | year/month/date/dates/datetime/ week/datetimerange/daterange/ monthrange | 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

@ -1,3 +1,4 @@
## DatePicker
Utilice Date Picker para introducir la fecha.
@ -204,6 +205,74 @@ Se soporta la selección de un rango de fechas.
:::
### Month Range
Picking a month range is supported.
:::demo When in range mode, the left and right panels are linked by default. If you want the two panels to switch current years independently, you can use the `unlink-panels` attribute.
```html
<template>
<div class="block">
<span class="demonstration">Default</span>
<el-date-picker
v-model="value15"
type="monthrange"
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">With quick options</span>
<el-date-picker
v-model="value16"
type="monthrange"
align="right"
unlink-panels
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month"
:picker-options="pickerOptions3">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
pickerOptions3: {
shortcuts: [{
text: 'This month',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: 'This year',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last 6 months',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
value15: '',
value16: ''
};
}
};
</script>
```
:::
### Valor por defecto
Si el usuario no ha escogido una fecha, muestra el calendario de hoy por defecto. Puede utilizar `default-value` para fijar otra fecha. Su valor debe ser definido por `new Date()`.
@ -373,7 +442,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/dates/datetime/ week/datetimerange/daterange | date |
| type | tipo de picker | string | year/month/date/dates/datetime/ week/datetimerange/daterange/ monthrange | 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

@ -1,3 +1,4 @@
## DatePicker
Utilisez le DatePicker pour les champs de dates.
@ -202,6 +203,74 @@ Vous pouvez sélectionner une plage de dates.
:::
### Month Range
Picking a month range is supported.
:::demo When in range mode, the left and right panels are linked by default. If you want the two panels to switch current years independently, you can use the `unlink-panels` attribute.
```html
<template>
<div class="block">
<span class="demonstration">Default</span>
<el-date-picker
v-model="value15"
type="monthrange"
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">With quick options</span>
<el-date-picker
v-model="value16"
type="monthrange"
align="right"
unlink-panels
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month"
:picker-options="pickerOptions3">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
pickerOptions3: {
shortcuts: [{
text: 'This month',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: 'This year',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last 6 months',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
value15: '',
value16: ''
};
}
};
</script>
```
:::
### Valeur par défaut
Si l'utilisateur n'a pas sélectionné de date, vous pouvez montrer la date d'aujourd'hui par défaut. Utilisez `default-value` pour montrer une autre date. Sa valeur doit être parsable par `new Date()`.
@ -373,7 +442,7 @@ Lorsque vous choisissez une plage de dates, vous pouvez assigner l'horaire de d
| placeholder | Le placeholder en mode normal. | string | — | — |
| start-placeholder | Le placeholder pour la date de début en mode plage de dates. | string | — | — |
| end-placeholder | Le placeholder pour la date de fin en mode plage de dates. | string | — | — |
| type | Type du picker. | string | year/month/date/dates/datetime/ week/datetimerange/daterange | date |
| type | Type du picker. | string | year/month/date/dates/datetime/ week/datetimerange/daterange/ monthrange | date |
| format | Format d'affichage dans le champ. | string | Voir [formats de date](#/fr-FR/component/date-picker#formats-de-date). | yyyy-MM-dd |
| align | Alignement. | left/center/right | left |
| popper-class | Nom de classe pour le menu déroulant du DatePicker. | string | — | — |

View File

@ -1,3 +1,4 @@
## DatePicker 日期选择器
用于选择或输入日期
@ -198,6 +199,76 @@
```
:::
### 选择月份范围
可在一个选择器中便捷地选择一个月份范围
:::demo 在选择月份范围时,默认情况下左右面板会联动。如果希望两个面板各自独立切换当前年份,可以使用`unlink-panels`属性解除联动。
```html
<template>
<div class="block">
<span class="demonstration">默认</span>
<el-date-picker
v-model="value15"
type="monthrange"
range-separator="至"
start-placeholder="开始月份"
end-placeholder="结束月份">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">带快捷选项</span>
<el-date-picker
v-model="value16"
type="monthrange"
align="right"
unlink-panels
range-separator="至"
start-placeholder="开始月份"
end-placeholder="结束月份"
:picker-options="pickerOptions3">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
pickerOptions3: {
shortcuts: [{
text: '本月',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: '今年至今',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近六个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
value15: '',
value16: ''
};
}
};
</script>
```
:::
### 日期格式
使用`format`指定输入框的格式;使用`value-format`指定绑定值的格式。
@ -323,7 +394,7 @@
| placeholder | 非范围选择时的占位内容 | string | — | — |
| start-placeholder | 范围选择时开始日期的占位内容 | string | — | — |
| end-placeholder | 范围选择时结束日期的占位内容 | string | — | — |
| type | 显示类型 | string | year/month/date/dates/ week/datetime/datetimerange/daterange | date |
| type | 显示类型 | string | year/month/date/dates/ week/datetime/datetimerange/ daterange/monthrange | 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

@ -1,46 +1,11 @@
<template>
<table @click="handleMonthTableClick" class="el-month-table">
<table @click="handleMonthTableClick" @mousemove="handleMouseMove" class="el-month-table">
<tbody>
<tr>
<td :class="getCellStyle(0)">
<a class="cell">{{ t('el.datepicker.months.jan') }}</a>
</td>
<td :class="getCellStyle(1)">
<a class="cell">{{ t('el.datepicker.months.feb') }}</a>
</td>
<td :class="getCellStyle(2)">
<a class="cell">{{ t('el.datepicker.months.mar') }}</a>
</td>
<td :class="getCellStyle(3)">
<a class="cell">{{ t('el.datepicker.months.apr') }}</a>
</td>
</tr>
<tr>
<td :class="getCellStyle(4)">
<a class="cell">{{ t('el.datepicker.months.may') }}</a>
</td>
<td :class="getCellStyle(5)">
<a class="cell">{{ t('el.datepicker.months.jun') }}</a>
</td>
<td :class="getCellStyle(6)">
<a class="cell">{{ t('el.datepicker.months.jul') }}</a>
</td>
<td :class="getCellStyle(7)">
<a class="cell">{{ t('el.datepicker.months.aug') }}</a>
</td>
</tr>
<tr>
<td :class="getCellStyle(8)">
<a class="cell">{{ t('el.datepicker.months.sep') }}</a>
</td>
<td :class="getCellStyle(9)">
<a class="cell">{{ t('el.datepicker.months.oct') }}</a>
</td>
<td :class="getCellStyle(10)">
<a class="cell">{{ t('el.datepicker.months.nov') }}</a>
</td>
<td :class="getCellStyle(11)">
<a class="cell">{{ t('el.datepicker.months.dec') }}</a>
<tr v-for="(row, key) in rows" :key="key">
<td :class="getCellStyle(cell)" v-for="(cell, key) in row" :key="key">
<div>
<a class="cell">{{ t('el.datepicker.months.' + months[cell.text]) }}</a>
</div>
</td>
</tr>
</tbody>
@ -51,7 +16,7 @@
import Locale from 'element-ui/src/mixins/locale';
import { isDate, range, getDayCountOfMonth, nextDate } from '../util';
import { hasClass } from 'element-ui/src/utils/dom';
import { arrayFindIndex, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
import { arrayFindIndex, coerceTruthyValueToArray, arrayFind } from 'element-ui/src/utils/util';
const datesInMonth = (year, month) => {
const numOfDays = getDayCountOfMonth(year, month);
@ -59,47 +24,231 @@
return range(numOfDays).map(n => nextDate(firstDay, n));
};
const clearDate = (date) => {
return new Date(date.getFullYear(), date.getMonth());
};
const getMonthTimestamp = function(time) {
if (typeof time === 'number' || typeof time === 'string') {
return clearDate(new Date(time)).getTime();
} else if (time instanceof Date) {
return clearDate(time).getTime();
} else {
return NaN;
}
};
export default {
props: {
disabledDate: {},
value: {},
selectionMode: {
default: 'month'
},
minDate: {},
maxDate: {},
defaultValue: {
validator(val) {
// null or valid Date Object
return val === null || (val instanceof Date && isDate(val));
return val === null || isDate(val) || (Array.isArray(val) && val.every(isDate));
}
},
date: {}
date: {},
rangeState: {
default() {
return {
endDate: null,
selecting: false
};
}
}
},
mixins: [Locale],
watch: {
'rangeState.endDate'(newVal) {
this.markRange(this.minDate, newVal);
},
minDate(newVal, oldVal) {
if (getMonthTimestamp(newVal) !== getMonthTimestamp(oldVal)) {
this.markRange(this.minDate, this.maxDate);
}
},
maxDate(newVal, oldVal) {
if (getMonthTimestamp(newVal) !== getMonthTimestamp(oldVal)) {
this.markRange(this.minDate, this.maxDate);
}
}
},
data() {
return {
months: ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'],
tableRows: [ [], [], [] ],
lastRow: null,
lastColumn: null
};
},
methods: {
getCellStyle(month) {
cellMatchesDate(cell, date) {
const value = new Date(date);
return this.date.getFullYear() === value.getFullYear() && Number(cell.text) === value.getMonth();
},
getCellStyle(cell) {
const style = {};
const year = this.date.getFullYear();
const today = new Date();
const month = cell.text;
const defaultValue = this.defaultValue ? Array.isArray(this.defaultValue) ? this.defaultValue : [this.defaultValue] : [];
style.disabled = typeof this.disabledDate === 'function'
? datesInMonth(year, month).every(this.disabledDate)
: false;
style.current = arrayFindIndex(coerceTruthyValueToArray(this.value), date => date.getFullYear() === year && date.getMonth() === month) >= 0;
style.today = today.getFullYear() === year && today.getMonth() === month;
style.default = this.defaultValue &&
this.defaultValue.getFullYear() === year &&
this.defaultValue.getMonth() === month;
style.default = defaultValue.some(date => this.cellMatchesDate(cell, date));
if (cell.inRange) {
style['in-range'] = true;
if (cell.start) {
style['start-date'] = true;
}
if (cell.end) {
style['end-date'] = true;
}
}
return style;
},
getMonthOfCell(month) {
const year = this.date.getFullYear();
return new Date(year, month, 1);
},
markRange(minDate, maxDate) {
minDate = getMonthTimestamp(minDate);
maxDate = getMonthTimestamp(maxDate) || minDate;
[minDate, maxDate] = [Math.min(minDate, maxDate), Math.max(minDate, maxDate)];
const rows = this.rows;
for (let i = 0, k = rows.length; i < k; i++) {
const row = rows[i];
for (let j = 0, l = row.length; j < l; j++) {
const cell = row[j];
const index = i * 4 + j;
const time = new Date(this.date.getFullYear(), index).getTime();
cell.inRange = minDate && time >= minDate && time <= maxDate;
cell.start = minDate && time === minDate;
cell.end = maxDate && time === maxDate;
}
}
},
handleMouseMove(event) {
if (!this.rangeState.selecting) return;
let target = event.target;
if (target.tagName === 'A') {
target = target.parentNode.parentNode;
}
if (target.tagName === 'DIV') {
target = target.parentNode;
}
if (target.tagName !== 'TD') return;
const row = target.parentNode.rowIndex;
const column = target.cellIndex;
// can not select disabled date
if (this.rows[row][column].disabled) return;
// only update rangeState when mouse moves to a new cell
// this avoids frequent Date object creation and improves performance
if (row !== this.lastRow || column !== this.lastColumn) {
this.lastRow = row;
this.lastColumn = column;
this.$emit('changerange', {
minDate: this.minDate,
maxDate: this.maxDate,
rangeState: {
selecting: true,
endDate: this.getMonthOfCell(row * 4 + column)
}
});
}
},
handleMonthTableClick(event) {
const target = event.target;
if (target.tagName !== 'A') return;
if (hasClass(target.parentNode, 'disabled')) return;
const column = target.parentNode.cellIndex;
const row = target.parentNode.parentNode.rowIndex;
let target = event.target;
if (target.tagName === 'A') {
target = target.parentNode.parentNode;
}
if (target.tagName === 'DIV') {
target = target.parentNode;
}
if (target.tagName !== 'TD') return;
if (hasClass(target, 'disabled')) return;
const column = target.cellIndex;
const row = target.parentNode.rowIndex;
const month = row * 4 + column;
const newDate = this.getMonthOfCell(month);
if (this.selectionMode === 'range') {
if (!this.rangeState.selecting) {
this.$emit('pick', {minDate: newDate, maxDate: null});
this.rangeState.selecting = true;
} else {
if (newDate >= this.minDate) {
this.$emit('pick', {minDate: this.minDate, maxDate: newDate});
} else {
this.$emit('pick', {minDate: newDate, maxDate: this.minDate});
}
this.rangeState.selecting = false;
}
} else {
this.$emit('pick', month);
}
}
},
computed: {
rows() {
// TODO: refactory rows / getCellClasses
const rows = this.tableRows;
const disabledDate = this.disabledDate;
const selectedDate = [];
const now = getMonthTimestamp(new Date());
for (let i = 0; i < 3; i++) {
const row = rows[i];
for (let j = 0; j < 4; j++) {
let cell = row[j];
if (!cell) {
cell = { row: i, column: j, type: 'normal', inRange: false, start: false, end: false };
}
cell.type = 'normal';
const index = i * 4 + j;
const time = new Date(this.date.getFullYear(), index).getTime();
cell.inRange = time >= getMonthTimestamp(this.minDate) && time <= getMonthTimestamp(this.maxDate);
cell.start = this.minDate && time === getMonthTimestamp(this.minDate);
cell.end = this.maxDate && time === getMonthTimestamp(this.maxDate);
const isToday = time === now;
if (isToday) {
cell.type = 'today';
}
cell.text = index;
let cellDate = new Date(time);
cell.disabled = typeof disabledDate === 'function' && disabledDate(cellDate);
cell.selected = arrayFind(selectedDate, date => date.getTime() === cellDate.getTime());
this.$set(row, j, cell);
}
}
return rows;
}
}
};
</script>

View File

@ -0,0 +1,289 @@
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-picker-panel el-date-range-picker el-popper"
:class="[{
'has-sidebar': $slots.sidebar || shortcuts
}, popperClass]">
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div class="el-picker-panel__sidebar" v-if="shortcuts">
<button
type="button"
class="el-picker-panel__shortcut"
v-for="(shortcut, key) in shortcuts"
:key="key"
@click="handleShortcutClick(shortcut)">{{shortcut.text}}</button>
</div>
<div class="el-picker-panel__body">
<div class="el-picker-panel__content el-date-range-picker__content is-left">
<div class="el-date-range-picker__header">
<button
type="button"
@click="leftPrevYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"></button>
<button
type="button"
v-if="unlinkPanels"
@click="leftNextYear"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
<div>{{ leftLabel }}</div>
</div>
<month-table
selection-mode="range"
:date="leftDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
@pick="handleRangePick">
</month-table>
</div>
<div class="el-picker-panel__content el-date-range-picker__content is-right">
<div class="el-date-range-picker__header">
<button
type="button"
v-if="unlinkPanels"
@click="rightPrevYear"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"></button>
<button
type="button"
@click="rightNextYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
<div>{{ rightLabel }}</div>
</div>
<month-table
selection-mode="range"
:date="rightDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
@pick="handleRangePick">
</month-table>
</div>
</div>
</div>
</div>
</transition>
</template>
<script type="text/babel">
import {
isDate,
modifyWithTimeString,
prevYear,
nextYear,
nextMonth
} from '../util';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import Locale from 'element-ui/src/mixins/locale';
import MonthTable from '../basic/month-table';
import ElInput from 'element-ui/packages/input';
import ElButton from 'element-ui/packages/button';
const calcDefaultValue = (defaultValue) => {
if (Array.isArray(defaultValue)) {
return [new Date(defaultValue[0]), new Date(defaultValue[1])];
} else if (defaultValue) {
return [new Date(defaultValue), nextMonth(new Date(defaultValue))];
} else {
return [new Date(), nextMonth(new Date())];
}
};
export default {
mixins: [Locale],
directives: { Clickoutside },
computed: {
btnDisabled() {
return !(this.minDate && this.maxDate && !this.selecting && this.isValidValue([this.minDate, this.maxDate]));
},
leftLabel() {
return this.leftDate.getFullYear() + ' ' + this.t('el.datepicker.year');
},
rightLabel() {
return this.rightDate.getFullYear() + ' ' + this.t('el.datepicker.year');
},
leftYear() {
return this.leftDate.getFullYear();
},
rightYear() {
return this.rightDate.getFullYear() === this.leftDate.getFullYear() ? this.leftDate.getFullYear() + 1 : this.rightDate.getFullYear();
},
enableYearArrow() {
return this.unlinkPanels && this.rightYear > this.leftYear + 1;
}
},
data() {
return {
popperClass: '',
value: [],
defaultValue: null,
defaultTime: null,
minDate: '',
maxDate: '',
leftDate: new Date(),
rightDate: nextYear(new Date()),
rangeState: {
endDate: null,
selecting: false,
row: null,
column: null
},
shortcuts: '',
visible: '',
disabledDate: '',
format: '',
arrowControl: false,
unlinkPanels: false
};
},
watch: {
value(newVal) {
if (!newVal) {
this.minDate = null;
this.maxDate = null;
} else if (Array.isArray(newVal)) {
this.minDate = isDate(newVal[0]) ? new Date(newVal[0]) : null;
this.maxDate = isDate(newVal[1]) ? new Date(newVal[1]) : null;
if (this.minDate) {
this.leftDate = this.minDate;
if (this.unlinkPanels && this.maxDate) {
const minDateYear = this.minDate.getFullYear();
const maxDateYear = this.maxDate.getFullYear();
this.rightDate = minDateYear === maxDateYear
? nextYear(this.maxDate)
: this.maxDate;
} else {
this.rightDate = nextYear(this.leftDate);
}
} else {
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextYear(this.leftDate);
}
}
},
defaultValue(val) {
if (!Array.isArray(this.value)) {
const [left, right] = calcDefaultValue(val);
this.leftDate = left;
this.rightDate = val && val[1] && left.getFullYear() !== right.getFullYear() && this.unlinkPanels
? right
: nextYear(this.leftDate);
}
}
},
methods: {
handleClear() {
this.minDate = null;
this.maxDate = null;
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextYear(this.leftDate);
this.$emit('pick', null);
},
handleChangeRange(val) {
this.minDate = val.minDate;
this.maxDate = val.maxDate;
this.rangeState = val.rangeState;
},
handleRangePick(val, close = true) {
const defaultTime = this.defaultTime || [];
const minDate = modifyWithTimeString(val.minDate, defaultTime[0]);
const maxDate = modifyWithTimeString(val.maxDate, defaultTime[1]);
if (this.maxDate === maxDate && this.minDate === minDate) {
return;
}
this.onPick && this.onPick(val);
this.maxDate = maxDate;
this.minDate = minDate;
// workaround for https://github.com/ElemeFE/element/issues/7539, should remove this block when we don't have to care about Chromium 55 - 57
setTimeout(() => {
this.maxDate = maxDate;
this.minDate = minDate;
}, 10);
if (!close) return;
this.handleConfirm();
},
handleShortcutClick(shortcut) {
if (shortcut.onClick) {
shortcut.onClick(this);
}
},
// leftPrev*, rightNext* need to take care of `unlinkPanels`
leftPrevYear() {
this.leftDate = prevYear(this.leftDate);
if (!this.unlinkPanels) {
this.rightDate = prevYear(this.rightDate);
}
},
rightNextYear() {
if (!this.unlinkPanels) {
this.leftDate = nextYear(this.leftDate);
}
this.rightDate = nextYear(this.rightDate);
},
// leftNext*, rightPrev* are called when `unlinkPanels` is true
leftNextYear() {
this.leftDate = nextYear(this.leftDate);
},
rightPrevYear() {
this.rightDate = prevYear(this.rightDate);
},
handleConfirm(visible = false) {
if (this.isValidValue([this.minDate, this.maxDate])) {
this.$emit('pick', [this.minDate, this.maxDate], visible);
}
},
isValidValue(value) {
return Array.isArray(value) &&
value && value[0] && value[1] &&
isDate(value[0]) && isDate(value[1]) &&
value[0].getTime() <= value[1].getTime() && (
typeof this.disabledDate === 'function'
? !this.disabledDate(value[0]) && !this.disabledDate(value[1])
: true
);
},
resetView() {
// NOTE: this is a hack to reset {min, max}Date on picker open.
// TODO: correct way of doing so is to refactor {min, max}Date to be dependent on value and internal selection state
// an alternative would be resetView whenever picker becomes visible, should also investigate date-panel's resetView
this.minDate = this.value && isDate(this.value[0]) ? new Date(this.value[0]) : null;
this.maxDate = this.value && isDate(this.value[0]) ? new Date(this.value[1]) : null;
}
},
components: { MonthTable, ElInput, ElButton }
};
</script>

View File

@ -114,6 +114,7 @@ const DEFAULT_FORMATS = {
week: 'yyyywWW',
timerange: 'HH:mm:ss',
daterange: 'yyyy-MM-dd',
monthrange: 'yyyy-MM',
datetimerange: 'yyyy-MM-dd HH:mm:ss',
year: 'yyyy'
};
@ -126,6 +127,7 @@ const HAVE_TRIGGER_TYPES = [
'month',
'year',
'daterange',
'monthrange',
'timerange',
'datetimerange',
'dates'
@ -213,6 +215,10 @@ const TYPE_VALUE_RESOLVER_MAP = {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
monthrange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
datetimerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER

View File

@ -1,10 +1,13 @@
import Picker from '../picker';
import DatePanel from '../panel/date';
import DateRangePanel from '../panel/date-range';
import MonthRangePanel from '../panel/month-range';
const getPanel = function(type) {
if (type === 'daterange' || type === 'datetimerange') {
return DateRangePanel;
} else if (type === 'monthrange') {
return MonthRangePanel;
}
return DatePanel;
};

View File

@ -50,8 +50,6 @@
@include when(left) {
border-right: 1px solid $--datepicker-inner-border-color;
}
@include when(right) {
.el-date-range-picker__header {
div {
@ -60,7 +58,6 @@
}
}
}
}
@include e(editors-wrap) {
box-sizing: border-box;

View File

@ -7,14 +7,22 @@
td {
text-align: center;
padding: 20px 3px;
padding: 8px 0px;
cursor: pointer;
& div {
height: 48px;
padding: 6px 0;
box-sizing: border-box;
}
&.today {
.cell {
color: $--color-primary;
font-weight: bold;
}
&.start-date .cell,
&.end-date .cell {
color: $--color-white;
}
}
&.disabled .cell {
@ -28,18 +36,45 @@
}
.cell {
width: 48px;
height: 32px;
width: 60px;
height: 36px;
display: block;
line-height: 32px;
line-height: 36px;
color: $--datepicker-color;
margin: 0 auto;
border-radius: 18px;
&:hover {
color: $--datepicker-text-hover-color;
}
}
&.in-range div {
background-color: $--datepicker-inrange-color;
&:hover {
background-color: $--datepicker-inrange-hover-color;
}
}
&.start-date div,
&.end-date div {
color: $--color-white;
}
&.start-date .cell,
&.end-date .cell {
color: $--color-white;
background-color: $--datepicker-active-color;
}
&.start-date div {
border-top-left-radius: 24px;
border-bottom-left-radius: 24px;
}
&.end-date div {
border-top-right-radius: 24px;
border-bottom-right-radius: 24px;
}
&.current:not(.disabled) .cell {
color: $--datepicker-active-color;
}

View File

@ -12,6 +12,13 @@
width: 220px;
}
@include m((monthrange)) {
&.el-input,
&.el-input__inner {
width: 300px;
}
}
@include m((daterange, timerange)) {
&.el-input,
&.el-input__inner {

View File

@ -2231,6 +2231,308 @@ describe('DatePicker', () => {
});
});
describe('type:monthrange', () => {
it('works', done => {
vm = createVue({
template: '<el-date-picker type="monthrange" v-model="value" ref="compo" />',
data() {
return {
value: ''
};
}
}, true);
const rangePicker = vm.$refs.compo;
const inputs = rangePicker.$el.querySelectorAll('input');
inputs[0].focus();
setTimeout(_ => {
const panels = rangePicker.picker.$el.querySelectorAll('.el-date-range-picker__content');
expect(Array.prototype.slice.call(panels)).to.length(2);
panels[0].querySelector('td:not(.disabled)').click();
setTimeout(_ => {
panels[1].querySelector('td:not(.disabled)').click();
setTimeout(_ => {
inputs[0].focus();
setTimeout(_ => {
// correct highlight
const startDate = rangePicker.picker.$el.querySelectorAll('.start-date');
const endDate = rangePicker.picker.$el.querySelectorAll('.end-date');
const inRangeDate = rangePicker.picker.$el.querySelectorAll('.in-range');
expect(startDate.length).to.equal(1);
expect(endDate.length).to.equal(1);
expect(inRangeDate.length).to.above(0);
// value is array
expect(vm.value).to.be.an.instanceof(Array);
// input text is something like date string
expect(inputs[0].value.length).to.equal(7);
expect(inputs[1].value.length).to.equal(7);
done();
}, DELAY);
}, DELAY);
}, DELAY);
}, DELAY);
});
it('works: reverse selection', done => {
vm = createVue({
template: '<el-date-picker type="monthrange" v-model="value" ref="compo" />',
data() {
return {
value: ''
};
}
}, true);
const rangePicker = vm.$refs.compo;
const inputs = rangePicker.$el.querySelectorAll('input');
inputs[0].focus();
setTimeout(_ => {
const panels = rangePicker.picker.$el.querySelectorAll('.el-date-range-picker__content');
expect(Array.prototype.slice.call(panels)).to.length(2);
panels[1].querySelector('td:not(.disabled)').click();
setTimeout(_ => {
panels[0].querySelector('td:not(.disabled)').click();
setTimeout(_ => {
inputs[0].focus();
setTimeout(_ => {
// correct highlight
const startDate = rangePicker.picker.$el.querySelectorAll('.start-date');
const endDate = rangePicker.picker.$el.querySelectorAll('.end-date');
const inRangeDate = rangePicker.picker.$el.querySelectorAll('.in-range');
expect(startDate.length).to.equal(1);
expect(endDate.length).to.equal(1);
expect(inRangeDate.length).to.above(0);
// value is array
expect(vm.value).to.be.an.instanceof(Array);
// input text is something like date string
expect(inputs[0].value.length).to.equal(7);
expect(inputs[1].value.length).to.equal(7);
// result array is properly ordered
expect(vm.value[0].getTime() < vm.value[1].getTime()).to.be.true;
done();
}, DELAY);
}, DELAY);
}, DELAY);
}, DELAY);
});
it('type:monthrange unlink:true', done => {
vm = createVue({
template: '<el-date-picker type="monthrange" unlink-panels v-model="value" ref="compo" />',
data() {
return {
value: [new Date(2000, 9), new Date(2000, 10)]
};
}
}, true);
const rangePicker = vm.$refs.compo;
const inputs = rangePicker.$el.querySelectorAll('input');
setTimeout(_ => {
inputs[0].focus();
setTimeout(_ => {
const panels = rangePicker.picker.$el.querySelectorAll('.el-date-range-picker__content');
const left = panels[0].querySelector('.el-date-range-picker__header');
const right = panels[1].querySelector('.is-right .el-date-range-picker__header');
const leftText = left.textContent.match(/\d+/g).map(i => Number(i));
const rightText = right.textContent.match(/\d+/g).map(i => Number(i));
expect(rightText[0] - leftText[0]).to.equal(1); // one year
done();
}, DELAY);
}, DELAY);
});
it('unlink panels', done => {
vm = createTest(DatePicker, {
type: 'monthrange',
unlinkPanels: true
}, true);
const input = vm.$el.querySelector('input');
input.click();
setTimeout(_ => {
const panels = vm.picker.$el.querySelectorAll('.el-date-range-picker__content');
expect(Array.prototype.slice.call(panels)).to.length(2);
panels[1].querySelector('.el-icon-d-arrow-right').click();
setTimeout(_ => {
const left = panels[0].querySelector('.el-date-range-picker__header');
const right = panels[1].querySelector('.is-right .el-date-range-picker__header');
const leftText = left.textContent.match(/\d+/g).map(i => Number(i));
const rightText = right.textContent.match(/\d+/g).map(i => Number(i));
expect(rightText[0] - leftText[0]).to.equal(2);
done();
}, DELAY);
}, DELAY);
});
it('daylight saving time highlight', done => {
// Run test with environment variable TZ=Australia/Sydney
// The following test uses Australian Eastern Daylight Time (AEDT)
// AEST -> AEDT shift happened on 2016-10-02 02:00:00
vm = createVue({
template: '<el-date-picker type="monthrange" v-model="value" ref="compo" />',
data() {
return {
value: [new Date(2016, 6), new Date(2016, 12)]
};
}
}, true);
const rangePicker = vm.$refs.compo;
const inputs = rangePicker.$el.querySelectorAll('input');
inputs[0].focus();
setTimeout(_ => {
const startDate = rangePicker.picker.$el.querySelectorAll('.start-date');
const endDate = rangePicker.picker.$el.querySelectorAll('.end-date');
expect(startDate.length).to.equal(1);
expect(endDate.length).to.equal(1);
done();
}, DELAY);
});
it('clear value', done => {
vm = createVue({
template: '<el-date-picker type="monthrange" v-model="value" ref="compo" />',
data() {
return {
value: [new Date(2000, 9), new Date(2000, 10)]
};
}
}, true);
vm.$el.querySelector('input').focus();
setTimeout(_ => {
vm.$refs.compo.showClose = true;
vm.$refs.compo.handleClickIcon({ stopPropagation: () => null });
setTimeout(_ => {
expect(vm.value).to.equal(null);
done();
}, DELAY);
}, DELAY);
});
it('change event', done => {
vm = createVue({
template: `
<el-date-picker
ref="compo"
v-model="value"
type="monthrange" />`,
data() {
return {
value: ''
};
}
}, true);
const spy = sinon.spy();
vm.$refs.compo.$on('change', spy);
const input = vm.$el.querySelector('input');
input.blur();
input.focus();
setTimeout(_ => {
const picker = vm.$refs.compo.picker;
setTimeout(_ => {
picker.$el.querySelector('td:not(.disabled)').click();
setTimeout(_ => {
picker.$el.querySelector('td:not(.disabled) ~ td:not(.disabled)').click();
setTimeout(_ => {
expect(spy.calledOnce).to.equal(true);
// change event is not emitted if used does not change value
// datarange also requires proper array equality check
input.blur();
input.focus();
setTimeout(_ => {
const startCell = picker.$el.querySelector('td.start-date');
const endCell = picker.$el.querySelector('td.end-date');
startCell.click();
setTimeout(_ => {
endCell.click();
setTimeout(_ => {
expect(spy.calledOnce).to.equal(true);
done();
}, DELAY);
}, DELAY);
}, DELAY);
}, DELAY);
}, DELAY);
}, DELAY);
}, DELAY);
});
describe('default value', () => {
it('single', done => {
let defaultValue = '2000-10';
let expectValue = [new Date(2000, 9), new Date(2000, 10)];
vm = createVue({
template: '<el-date-picker type="monthrange" v-model="value" ref="compo" :default-value="defaultValue" />',
data() {
return {
value: '',
defaultValue
};
}
}, true);
vm.$el.querySelector('input').focus();
setTimeout(_ => {
const $el = vm.$refs.compo.picker.$el;
const defaultEls = $el.querySelectorAll('.el-month-table td.default');
expect(defaultEls.length).to.equal(1);
defaultEls[0].click();
setTimeout(_ => {
$el.querySelector('.el-month-table td.default + td').click();
setTimeout(_ => {
expect(vm.value).to.eql(expectValue);
done();
}, DELAY);
}, DELAY);
}, DELAY);
});
it('array', done => {
let defaultValue = ['2000-01', '2000-03'];
let expectValue = [new Date(2000, 0), new Date(2000, 2)];
vm = createVue({
template: '<el-date-picker type="monthrange" v-model="value" ref="compo" :default-value="defaultValue" />',
data() {
return {
value: '',
defaultValue
};
}
}, true);
vm.$el.querySelector('input').focus();
setTimeout(_ => {
const defaultEls = vm.$refs.compo.picker.$el.querySelectorAll('.el-month-table td.default');
expect(defaultEls.length).to.equal(2);
defaultEls[0].click();
setTimeout(_ => {
defaultEls[1].click();
setTimeout(_ => {
expect(vm.value).to.eql(expectValue);
done();
}, DELAY);
}, DELAY);
}, DELAY);
});
});
});
const currentMonth = new Date(new Date().getTime());
currentMonth.setDate(1);
const chineseWeek = ['一', '二', '三', '四', '五', '六', '日'];