chushihua
This commit is contained in:
+679
@@ -0,0 +1,679 @@
|
||||
<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,
|
||||
'has-time': showTime
|
||||
}, 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-date-range-picker__time-header" v-if="showTime">
|
||||
<span class="el-date-range-picker__editors-wrap">
|
||||
<span class="el-date-range-picker__time-picker-wrap">
|
||||
<el-input
|
||||
size="small"
|
||||
:disabled="rangeState.selecting"
|
||||
ref="minInput"
|
||||
:placeholder="t('el.datepicker.startDate')"
|
||||
class="el-date-range-picker__editor"
|
||||
:value="minVisibleDate"
|
||||
@input="val => handleDateInput(val, 'min')"
|
||||
@change="val => handleDateChange(val, 'min')" />
|
||||
</span>
|
||||
<span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMinTimeClose">
|
||||
<el-input
|
||||
size="small"
|
||||
class="el-date-range-picker__editor"
|
||||
:disabled="rangeState.selecting"
|
||||
:placeholder="t('el.datepicker.startTime')"
|
||||
:value="minVisibleTime"
|
||||
@focus="minTimePickerVisible = true"
|
||||
@input="val => handleTimeInput(val, 'min')"
|
||||
@change="val => handleTimeChange(val, 'min')" />
|
||||
<time-picker
|
||||
ref="minTimePicker"
|
||||
@pick="handleMinTimePick"
|
||||
:time-arrow-control="arrowControl"
|
||||
:visible="minTimePickerVisible"
|
||||
@mounted="$refs.minTimePicker.format=timeFormat">
|
||||
</time-picker>
|
||||
</span>
|
||||
</span>
|
||||
<span class="el-icon-arrow-right"></span>
|
||||
<span class="el-date-range-picker__editors-wrap is-right">
|
||||
<span class="el-date-range-picker__time-picker-wrap">
|
||||
<el-input
|
||||
size="small"
|
||||
class="el-date-range-picker__editor"
|
||||
:disabled="rangeState.selecting"
|
||||
:placeholder="t('el.datepicker.endDate')"
|
||||
:value="maxVisibleDate"
|
||||
:readonly="!minDate"
|
||||
@input="val => handleDateInput(val, 'max')"
|
||||
@change="val => handleDateChange(val, 'max')" />
|
||||
</span>
|
||||
<span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMaxTimeClose">
|
||||
<el-input
|
||||
size="small"
|
||||
class="el-date-range-picker__editor"
|
||||
:disabled="rangeState.selecting"
|
||||
:placeholder="t('el.datepicker.endTime')"
|
||||
:value="maxVisibleTime"
|
||||
:readonly="!minDate"
|
||||
@focus="minDate && (maxTimePickerVisible = true)"
|
||||
@input="val => handleTimeInput(val, 'max')"
|
||||
@change="val => handleTimeChange(val, 'max')" />
|
||||
<time-picker
|
||||
ref="maxTimePicker"
|
||||
@pick="handleMaxTimePick"
|
||||
:time-arrow-control="arrowControl"
|
||||
:visible="maxTimePickerVisible"
|
||||
@mounted="$refs.maxTimePicker.format=timeFormat">
|
||||
</time-picker>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<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"
|
||||
@click="leftPrevMonth"
|
||||
class="el-picker-panel__icon-btn el-icon-arrow-left"></button>
|
||||
<button
|
||||
type="button"
|
||||
@click="leftNextYear"
|
||||
v-if="unlinkPanels"
|
||||
:disabled="!enableYearArrow"
|
||||
:class="{ 'is-disabled': !enableYearArrow }"
|
||||
class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
|
||||
<button
|
||||
type="button"
|
||||
@click="leftNextMonth"
|
||||
v-if="unlinkPanels"
|
||||
:disabled="!enableMonthArrow"
|
||||
:class="{ 'is-disabled': !enableMonthArrow }"
|
||||
class="el-picker-panel__icon-btn el-icon-arrow-right"></button>
|
||||
<div>{{ leftLabel }}</div>
|
||||
</div>
|
||||
<date-table
|
||||
selection-mode="range"
|
||||
:date="leftDate"
|
||||
:default-value="defaultValue"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:range-state="rangeState"
|
||||
:disabled-date="disabledDate"
|
||||
@changerange="handleChangeRange"
|
||||
:first-day-of-week="firstDayOfWeek"
|
||||
@pick="handleRangePick">
|
||||
</date-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"
|
||||
@click="rightPrevYear"
|
||||
v-if="unlinkPanels"
|
||||
:disabled="!enableYearArrow"
|
||||
:class="{ 'is-disabled': !enableYearArrow }"
|
||||
class="el-picker-panel__icon-btn el-icon-d-arrow-left"></button>
|
||||
<button
|
||||
type="button"
|
||||
@click="rightPrevMonth"
|
||||
v-if="unlinkPanels"
|
||||
:disabled="!enableMonthArrow"
|
||||
:class="{ 'is-disabled': !enableMonthArrow }"
|
||||
class="el-picker-panel__icon-btn el-icon-arrow-left"></button>
|
||||
<button
|
||||
type="button"
|
||||
@click="rightNextYear"
|
||||
class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
|
||||
<button
|
||||
type="button"
|
||||
@click="rightNextMonth"
|
||||
class="el-picker-panel__icon-btn el-icon-arrow-right"></button>
|
||||
<div>{{ rightLabel }}</div>
|
||||
</div>
|
||||
<date-table
|
||||
selection-mode="range"
|
||||
:date="rightDate"
|
||||
:default-value="defaultValue"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:range-state="rangeState"
|
||||
:disabled-date="disabledDate"
|
||||
@changerange="handleChangeRange"
|
||||
:first-day-of-week="firstDayOfWeek"
|
||||
@pick="handleRangePick">
|
||||
</date-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-picker-panel__footer" v-if="showTime">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
class="el-picker-panel__link-btn"
|
||||
@click="handleClear">
|
||||
{{ t('el.datepicker.clear') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
size="mini"
|
||||
class="el-picker-panel__link-btn"
|
||||
:disabled="btnDisabled"
|
||||
@click="handleConfirm(false)">
|
||||
{{ t('el.datepicker.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script type="text/babel">
|
||||
import {
|
||||
formatDate,
|
||||
parseDate,
|
||||
isDate,
|
||||
modifyDate,
|
||||
modifyTime,
|
||||
modifyWithTimeString,
|
||||
prevYear,
|
||||
nextYear,
|
||||
prevMonth,
|
||||
nextMonth,
|
||||
extractDateFormat,
|
||||
extractTimeFormat
|
||||
} from '../util';
|
||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import TimePicker from './time';
|
||||
import DateTable from '../basic/date-table';
|
||||
import ElInput from 'element-ui/packages/input';
|
||||
import ElButton from 'element-ui/packages/button';
|
||||
|
||||
const advanceDate = (date, amount) => {
|
||||
return new Date(new Date(date).getTime() + amount);
|
||||
};
|
||||
|
||||
const calcDefaultValue = (defaultValue) => {
|
||||
if (Array.isArray(defaultValue)) {
|
||||
return [new Date(defaultValue[0]), new Date(defaultValue[1])];
|
||||
} else if (defaultValue) {
|
||||
return [new Date(defaultValue), advanceDate(defaultValue, 24 * 60 * 60 * 1000)];
|
||||
} else {
|
||||
return [new Date(), advanceDate(Date.now(), 24 * 60 * 60 * 1000)];
|
||||
}
|
||||
};
|
||||
|
||||
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') + ' ' + this.t(`el.datepicker.month${ this.leftDate.getMonth() + 1 }`);
|
||||
},
|
||||
|
||||
rightLabel() {
|
||||
return this.rightDate.getFullYear() + ' ' + this.t('el.datepicker.year') + ' ' + this.t(`el.datepicker.month${ this.rightDate.getMonth() + 1 }`);
|
||||
},
|
||||
|
||||
leftYear() {
|
||||
return this.leftDate.getFullYear();
|
||||
},
|
||||
|
||||
leftMonth() {
|
||||
return this.leftDate.getMonth();
|
||||
},
|
||||
|
||||
leftMonthDate() {
|
||||
return this.leftDate.getDate();
|
||||
},
|
||||
|
||||
rightYear() {
|
||||
return this.rightDate.getFullYear();
|
||||
},
|
||||
|
||||
rightMonth() {
|
||||
return this.rightDate.getMonth();
|
||||
},
|
||||
|
||||
rightMonthDate() {
|
||||
return this.rightDate.getDate();
|
||||
},
|
||||
|
||||
minVisibleDate() {
|
||||
if (this.dateUserInput.min !== null) return this.dateUserInput.min;
|
||||
if (this.minDate) return formatDate(this.minDate, this.dateFormat);
|
||||
return '';
|
||||
},
|
||||
|
||||
maxVisibleDate() {
|
||||
if (this.dateUserInput.max !== null) return this.dateUserInput.max;
|
||||
if (this.maxDate || this.minDate) return formatDate(this.maxDate || this.minDate, this.dateFormat);
|
||||
return '';
|
||||
},
|
||||
|
||||
minVisibleTime() {
|
||||
if (this.timeUserInput.min !== null) return this.timeUserInput.min;
|
||||
if (this.minDate) return formatDate(this.minDate, this.timeFormat);
|
||||
return '';
|
||||
},
|
||||
|
||||
maxVisibleTime() {
|
||||
if (this.timeUserInput.max !== null) return this.timeUserInput.max;
|
||||
if (this.maxDate || this.minDate) return formatDate(this.maxDate || this.minDate, this.timeFormat);
|
||||
return '';
|
||||
},
|
||||
|
||||
timeFormat() {
|
||||
if (this.format) {
|
||||
return extractTimeFormat(this.format);
|
||||
} else {
|
||||
return 'HH:mm:ss';
|
||||
}
|
||||
},
|
||||
|
||||
dateFormat() {
|
||||
if (this.format) {
|
||||
return extractDateFormat(this.format);
|
||||
} else {
|
||||
return 'yyyy-MM-dd';
|
||||
}
|
||||
},
|
||||
|
||||
enableMonthArrow() {
|
||||
const nextMonth = (this.leftMonth + 1) % 12;
|
||||
const yearOffset = this.leftMonth + 1 >= 12 ? 1 : 0;
|
||||
return this.unlinkPanels && new Date(this.leftYear + yearOffset, nextMonth) < new Date(this.rightYear, this.rightMonth);
|
||||
},
|
||||
|
||||
enableYearArrow() {
|
||||
return this.unlinkPanels && this.rightYear * 12 + this.rightMonth - (this.leftYear * 12 + this.leftMonth + 1) >= 12;
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
popperClass: '',
|
||||
value: [],
|
||||
defaultValue: null,
|
||||
defaultTime: null,
|
||||
minDate: '',
|
||||
maxDate: '',
|
||||
leftDate: new Date(),
|
||||
rightDate: nextMonth(new Date()),
|
||||
rangeState: {
|
||||
endDate: null,
|
||||
selecting: false,
|
||||
row: null,
|
||||
column: null
|
||||
},
|
||||
showTime: false,
|
||||
shortcuts: '',
|
||||
visible: '',
|
||||
disabledDate: '',
|
||||
firstDayOfWeek: 7,
|
||||
minTimePickerVisible: false,
|
||||
maxTimePickerVisible: false,
|
||||
format: '',
|
||||
arrowControl: false,
|
||||
unlinkPanels: false,
|
||||
dateUserInput: {
|
||||
min: null,
|
||||
max: null
|
||||
},
|
||||
timeUserInput: {
|
||||
min: null,
|
||||
max: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
minDate(val) {
|
||||
this.dateUserInput.min = null;
|
||||
this.timeUserInput.min = null;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.maxTimePicker && this.maxDate && this.maxDate < this.minDate) {
|
||||
const format = 'HH:mm:ss';
|
||||
this.$refs.maxTimePicker.selectableRange = [
|
||||
[
|
||||
parseDate(formatDate(this.minDate, format), format),
|
||||
parseDate('23:59:59', format)
|
||||
]
|
||||
];
|
||||
}
|
||||
});
|
||||
if (val && this.$refs.minTimePicker) {
|
||||
this.$refs.minTimePicker.date = val;
|
||||
this.$refs.minTimePicker.value = val;
|
||||
}
|
||||
},
|
||||
|
||||
maxDate(val) {
|
||||
this.dateUserInput.max = null;
|
||||
this.timeUserInput.max = null;
|
||||
if (val && this.$refs.maxTimePicker) {
|
||||
this.$refs.maxTimePicker.date = val;
|
||||
this.$refs.maxTimePicker.value = val;
|
||||
}
|
||||
},
|
||||
|
||||
minTimePickerVisible(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.minTimePicker.date = this.minDate;
|
||||
this.$refs.minTimePicker.value = this.minDate;
|
||||
this.$refs.minTimePicker.adjustSpinners();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
maxTimePickerVisible(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.maxTimePicker.date = this.maxDate;
|
||||
this.$refs.maxTimePicker.value = this.maxDate;
|
||||
this.$refs.maxTimePicker.adjustSpinners();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
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 minDateMonth = this.minDate.getMonth();
|
||||
const maxDateYear = this.maxDate.getFullYear();
|
||||
const maxDateMonth = this.maxDate.getMonth();
|
||||
this.rightDate = minDateYear === maxDateYear && minDateMonth === maxDateMonth
|
||||
? nextMonth(this.maxDate)
|
||||
: this.maxDate;
|
||||
} else {
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
}
|
||||
} else {
|
||||
this.leftDate = calcDefaultValue(this.defaultValue)[0];
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
defaultValue(val) {
|
||||
if (!Array.isArray(this.value)) {
|
||||
const [left, right] = calcDefaultValue(val);
|
||||
this.leftDate = left;
|
||||
this.rightDate = val && val[1] && this.unlinkPanels
|
||||
? right
|
||||
: nextMonth(this.leftDate);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClear() {
|
||||
this.minDate = null;
|
||||
this.maxDate = null;
|
||||
this.leftDate = calcDefaultValue(this.defaultValue)[0];
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
this.$emit('pick', null);
|
||||
},
|
||||
|
||||
handleChangeRange(val) {
|
||||
this.minDate = val.minDate;
|
||||
this.maxDate = val.maxDate;
|
||||
this.rangeState = val.rangeState;
|
||||
},
|
||||
|
||||
handleDateInput(value, type) {
|
||||
this.dateUserInput[type] = value;
|
||||
if (value.length !== this.dateFormat.length) return;
|
||||
const parsedValue = parseDate(value, this.dateFormat);
|
||||
|
||||
if (parsedValue) {
|
||||
if (typeof this.disabledDate === 'function' &&
|
||||
this.disabledDate(new Date(parsedValue))) {
|
||||
return;
|
||||
}
|
||||
if (type === 'min') {
|
||||
this.minDate = modifyDate(this.minDate || new Date(), parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
|
||||
this.leftDate = new Date(parsedValue);
|
||||
if (!this.unlinkPanels) {
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
}
|
||||
} else {
|
||||
this.maxDate = modifyDate(this.maxDate || new Date(), parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
|
||||
this.rightDate = new Date(parsedValue);
|
||||
if (!this.unlinkPanels) {
|
||||
this.leftDate = prevMonth(parsedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleDateChange(value, type) {
|
||||
const parsedValue = parseDate(value, this.dateFormat);
|
||||
if (parsedValue) {
|
||||
if (type === 'min') {
|
||||
this.minDate = modifyDate(this.minDate, parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
|
||||
if (this.minDate > this.maxDate) {
|
||||
this.maxDate = this.minDate;
|
||||
}
|
||||
} else {
|
||||
this.maxDate = modifyDate(this.maxDate, parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
|
||||
if (this.maxDate < this.minDate) {
|
||||
this.minDate = this.maxDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleTimeInput(value, type) {
|
||||
this.timeUserInput[type] = value;
|
||||
if (value.length !== this.timeFormat.length) return;
|
||||
const parsedValue = parseDate(value, this.timeFormat);
|
||||
|
||||
if (parsedValue) {
|
||||
if (type === 'min') {
|
||||
this.minDate = modifyTime(this.minDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
|
||||
this.$nextTick(_ => this.$refs.minTimePicker.adjustSpinners());
|
||||
} else {
|
||||
this.maxDate = modifyTime(this.maxDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
|
||||
this.$nextTick(_ => this.$refs.maxTimePicker.adjustSpinners());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleTimeChange(value, type) {
|
||||
const parsedValue = parseDate(value, this.timeFormat);
|
||||
if (parsedValue) {
|
||||
if (type === 'min') {
|
||||
this.minDate = modifyTime(this.minDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
|
||||
if (this.minDate > this.maxDate) {
|
||||
this.maxDate = this.minDate;
|
||||
}
|
||||
this.$refs.minTimePicker.value = this.minDate;
|
||||
this.minTimePickerVisible = false;
|
||||
} else {
|
||||
this.maxDate = modifyTime(this.maxDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
|
||||
if (this.maxDate < this.minDate) {
|
||||
this.minDate = this.maxDate;
|
||||
}
|
||||
this.$refs.maxTimePicker.value = this.minDate;
|
||||
this.maxTimePickerVisible = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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 || this.showTime) return;
|
||||
this.handleConfirm();
|
||||
},
|
||||
|
||||
handleShortcutClick(shortcut) {
|
||||
if (shortcut.onClick) {
|
||||
shortcut.onClick(this);
|
||||
}
|
||||
},
|
||||
|
||||
handleMinTimePick(value, visible, first) {
|
||||
this.minDate = this.minDate || new Date();
|
||||
if (value) {
|
||||
this.minDate = modifyTime(this.minDate, value.getHours(), value.getMinutes(), value.getSeconds());
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
this.minTimePickerVisible = visible;
|
||||
}
|
||||
|
||||
if (!this.maxDate || this.maxDate && this.maxDate.getTime() < this.minDate.getTime()) {
|
||||
this.maxDate = new Date(this.minDate);
|
||||
}
|
||||
},
|
||||
|
||||
handleMinTimeClose() {
|
||||
this.minTimePickerVisible = false;
|
||||
},
|
||||
|
||||
handleMaxTimePick(value, visible, first) {
|
||||
if (this.maxDate && value) {
|
||||
this.maxDate = modifyTime(this.maxDate, value.getHours(), value.getMinutes(), value.getSeconds());
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
this.maxTimePickerVisible = visible;
|
||||
}
|
||||
|
||||
if (this.maxDate && this.minDate && this.minDate.getTime() > this.maxDate.getTime()) {
|
||||
this.minDate = new Date(this.maxDate);
|
||||
}
|
||||
},
|
||||
|
||||
handleMaxTimeClose() {
|
||||
this.maxTimePickerVisible = false;
|
||||
},
|
||||
|
||||
// leftPrev*, rightNext* need to take care of `unlinkPanels`
|
||||
leftPrevYear() {
|
||||
this.leftDate = prevYear(this.leftDate);
|
||||
if (!this.unlinkPanels) {
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
}
|
||||
},
|
||||
|
||||
leftPrevMonth() {
|
||||
this.leftDate = prevMonth(this.leftDate);
|
||||
if (!this.unlinkPanels) {
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
}
|
||||
},
|
||||
|
||||
rightNextYear() {
|
||||
if (!this.unlinkPanels) {
|
||||
this.leftDate = nextYear(this.leftDate);
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
} else {
|
||||
this.rightDate = nextYear(this.rightDate);
|
||||
}
|
||||
},
|
||||
|
||||
rightNextMonth() {
|
||||
if (!this.unlinkPanels) {
|
||||
this.leftDate = nextMonth(this.leftDate);
|
||||
this.rightDate = nextMonth(this.leftDate);
|
||||
} else {
|
||||
this.rightDate = nextMonth(this.rightDate);
|
||||
}
|
||||
},
|
||||
|
||||
// leftNext*, rightPrev* are called when `unlinkPanels` is true
|
||||
leftNextYear() {
|
||||
this.leftDate = nextYear(this.leftDate);
|
||||
},
|
||||
|
||||
leftNextMonth() {
|
||||
this.leftDate = nextMonth(this.leftDate);
|
||||
},
|
||||
|
||||
rightPrevYear() {
|
||||
this.rightDate = prevYear(this.rightDate);
|
||||
},
|
||||
|
||||
rightPrevMonth() {
|
||||
this.rightDate = prevMonth(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: { TimePicker, DateTable, ElInput, ElButton }
|
||||
};
|
||||
</script>
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-top" @after-enter="handleEnter" @after-leave="handleLeave">
|
||||
<div
|
||||
v-show="visible"
|
||||
class="el-picker-panel el-date-picker el-popper"
|
||||
:class="[{
|
||||
'has-sidebar': $slots.sidebar || shortcuts,
|
||||
'has-time': showTime
|
||||
}, 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-date-picker__time-header" v-if="showTime">
|
||||
<span class="el-date-picker__editor-wrap">
|
||||
<el-input
|
||||
:placeholder="t('el.datepicker.selectDate')"
|
||||
:value="visibleDate"
|
||||
size="small"
|
||||
@input="val => userInputDate = val"
|
||||
@change="handleVisibleDateChange" />
|
||||
</span>
|
||||
<span class="el-date-picker__editor-wrap" v-clickoutside="handleTimePickClose">
|
||||
<el-input
|
||||
ref="input"
|
||||
@focus="timePickerVisible = true"
|
||||
:placeholder="t('el.datepicker.selectTime')"
|
||||
:value="visibleTime"
|
||||
size="small"
|
||||
@input="val => userInputTime = val"
|
||||
@change="handleVisibleTimeChange" />
|
||||
<time-picker
|
||||
ref="timepicker"
|
||||
:time-arrow-control="arrowControl"
|
||||
@pick="handleTimePick"
|
||||
:visible="timePickerVisible"
|
||||
@mounted="proxyTimePickerDataProperties">
|
||||
</time-picker>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="el-date-picker__header"
|
||||
:class="{ 'el-date-picker__header--bordered': currentView === 'year' || currentView === 'month' }"
|
||||
v-show="currentView !== 'time'">
|
||||
<button
|
||||
type="button"
|
||||
@click="prevYear"
|
||||
:aria-label="t(`el.datepicker.prevYear`)"
|
||||
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left">
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="prevMonth"
|
||||
v-show="currentView === 'date'"
|
||||
:aria-label="t(`el.datepicker.prevMonth`)"
|
||||
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-arrow-left">
|
||||
</button>
|
||||
<span
|
||||
@click="showYearPicker"
|
||||
role="button"
|
||||
class="el-date-picker__header-label">{{ yearLabel }}</span>
|
||||
<span
|
||||
@click="showMonthPicker"
|
||||
v-show="currentView === 'date'"
|
||||
role="button"
|
||||
class="el-date-picker__header-label"
|
||||
:class="{ active: currentView === 'month' }">{{t(`el.datepicker.month${ month + 1 }`)}}</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="nextYear"
|
||||
:aria-label="t(`el.datepicker.nextYear`)"
|
||||
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right">
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="nextMonth"
|
||||
v-show="currentView === 'date'"
|
||||
:aria-label="t(`el.datepicker.nextMonth`)"
|
||||
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-arrow-right">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="el-picker-panel__content">
|
||||
<date-table
|
||||
v-show="currentView === 'date'"
|
||||
@pick="handleDatePick"
|
||||
:selection-mode="selectionMode"
|
||||
:first-day-of-week="firstDayOfWeek"
|
||||
:value="value"
|
||||
:default-value="defaultValue ? new Date(defaultValue) : null"
|
||||
:date="date"
|
||||
:disabled-date="disabledDate">
|
||||
</date-table>
|
||||
<year-table
|
||||
v-show="currentView === 'year'"
|
||||
@pick="handleYearPick"
|
||||
:value="value"
|
||||
:default-value="defaultValue ? new Date(defaultValue) : null"
|
||||
:date="date"
|
||||
:disabled-date="disabledDate">
|
||||
</year-table>
|
||||
<month-table
|
||||
v-show="currentView === 'month'"
|
||||
@pick="handleMonthPick"
|
||||
:value="value"
|
||||
:default-value="defaultValue ? new Date(defaultValue) : null"
|
||||
:date="date"
|
||||
:disabled-date="disabledDate">
|
||||
</month-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="el-picker-panel__footer"
|
||||
v-show="footerVisible && currentView === 'date'">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
class="el-picker-panel__link-btn"
|
||||
@click="changeToNow"
|
||||
v-show="selectionMode !== 'dates'">
|
||||
{{ t('el.datepicker.now') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
size="mini"
|
||||
class="el-picker-panel__link-btn"
|
||||
@click="confirm">
|
||||
{{ t('el.datepicker.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script type="text/babel">
|
||||
import {
|
||||
formatDate,
|
||||
parseDate,
|
||||
getWeekNumber,
|
||||
isDate,
|
||||
modifyDate,
|
||||
modifyTime,
|
||||
modifyWithTimeString,
|
||||
clearMilliseconds,
|
||||
clearTime,
|
||||
prevYear,
|
||||
nextYear,
|
||||
prevMonth,
|
||||
nextMonth,
|
||||
changeYearMonthAndClampDate,
|
||||
extractDateFormat,
|
||||
extractTimeFormat,
|
||||
timeWithinRange
|
||||
} from '../util';
|
||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import ElInput from 'element-ui/packages/input';
|
||||
import ElButton from 'element-ui/packages/button';
|
||||
import TimePicker from './time';
|
||||
import YearTable from '../basic/year-table';
|
||||
import MonthTable from '../basic/month-table';
|
||||
import DateTable from '../basic/date-table';
|
||||
|
||||
export default {
|
||||
mixins: [Locale],
|
||||
|
||||
directives: { Clickoutside },
|
||||
|
||||
watch: {
|
||||
showTime(val) {
|
||||
/* istanbul ignore if */
|
||||
if (!val) return;
|
||||
this.$nextTick(_ => {
|
||||
const inputElm = this.$refs.input.$el;
|
||||
if (inputElm) {
|
||||
this.pickerWidth = inputElm.getBoundingClientRect().width + 10;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
value(val) {
|
||||
if (this.selectionMode === 'dates' && this.value) return;
|
||||
if (isDate(val)) {
|
||||
this.date = new Date(val);
|
||||
} else {
|
||||
this.date = this.getDefaultValue();
|
||||
}
|
||||
},
|
||||
|
||||
defaultValue(val) {
|
||||
if (!isDate(this.value)) {
|
||||
this.date = val ? new Date(val) : new Date();
|
||||
}
|
||||
},
|
||||
|
||||
timePickerVisible(val) {
|
||||
if (val) this.$nextTick(() => this.$refs.timepicker.adjustSpinners());
|
||||
},
|
||||
|
||||
selectionMode(newVal) {
|
||||
if (newVal === 'month') {
|
||||
/* istanbul ignore next */
|
||||
if (this.currentView !== 'year' || this.currentView !== 'month') {
|
||||
this.currentView = 'month';
|
||||
}
|
||||
} else if (newVal === 'dates') {
|
||||
this.currentView = 'date';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
proxyTimePickerDataProperties() {
|
||||
const format = timeFormat => {this.$refs.timepicker.format = timeFormat;};
|
||||
const value = value => {this.$refs.timepicker.value = value;};
|
||||
const date = date => {this.$refs.timepicker.date = date;};
|
||||
const selectableRange = selectableRange => {this.$refs.timepicker.selectableRange = selectableRange;};
|
||||
|
||||
this.$watch('value', value);
|
||||
this.$watch('date', date);
|
||||
this.$watch('selectableRange', selectableRange);
|
||||
|
||||
format(this.timeFormat);
|
||||
value(this.value);
|
||||
date(this.date);
|
||||
selectableRange(this.selectableRange);
|
||||
},
|
||||
|
||||
handleClear() {
|
||||
this.date = this.getDefaultValue();
|
||||
this.$emit('pick', null);
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
this.userInputDate = null;
|
||||
this.userInputTime = null;
|
||||
},
|
||||
|
||||
// resetDate() {
|
||||
// this.date = new Date(this.date);
|
||||
// },
|
||||
|
||||
showMonthPicker() {
|
||||
this.currentView = 'month';
|
||||
},
|
||||
|
||||
showYearPicker() {
|
||||
this.currentView = 'year';
|
||||
},
|
||||
|
||||
// XXX: 没用到
|
||||
// handleLabelClick() {
|
||||
// if (this.currentView === 'date') {
|
||||
// this.showMonthPicker();
|
||||
// } else if (this.currentView === 'month') {
|
||||
// this.showYearPicker();
|
||||
// }
|
||||
// },
|
||||
|
||||
prevMonth() {
|
||||
this.date = prevMonth(this.date);
|
||||
},
|
||||
|
||||
nextMonth() {
|
||||
this.date = nextMonth(this.date);
|
||||
},
|
||||
|
||||
prevYear() {
|
||||
if (this.currentView === 'year') {
|
||||
this.date = prevYear(this.date, 10);
|
||||
} else {
|
||||
this.date = prevYear(this.date);
|
||||
}
|
||||
},
|
||||
|
||||
nextYear() {
|
||||
if (this.currentView === 'year') {
|
||||
this.date = nextYear(this.date, 10);
|
||||
} else {
|
||||
this.date = nextYear(this.date);
|
||||
}
|
||||
},
|
||||
|
||||
handleShortcutClick(shortcut) {
|
||||
if (shortcut.onClick) {
|
||||
shortcut.onClick(this);
|
||||
}
|
||||
},
|
||||
|
||||
handleTimePick(value, visible, first) {
|
||||
if (isDate(value)) {
|
||||
const newDate = this.value
|
||||
? modifyTime(this.value, value.getHours(), value.getMinutes(), value.getSeconds())
|
||||
: modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
|
||||
this.date = newDate;
|
||||
this.emit(this.date, true);
|
||||
} else {
|
||||
this.emit(value, true);
|
||||
}
|
||||
if (!first) {
|
||||
this.timePickerVisible = visible;
|
||||
}
|
||||
},
|
||||
|
||||
handleTimePickClose() {
|
||||
this.timePickerVisible = false;
|
||||
},
|
||||
|
||||
handleMonthPick(month) {
|
||||
if (this.selectionMode === 'month') {
|
||||
this.date = modifyDate(this.date, this.year, month, 1);
|
||||
this.emit(this.date);
|
||||
} else {
|
||||
this.date = changeYearMonthAndClampDate(this.date, this.year, month);
|
||||
// TODO: should emit intermediate value ??
|
||||
// this.emit(this.date);
|
||||
this.currentView = 'date';
|
||||
}
|
||||
},
|
||||
|
||||
handleDatePick(value) {
|
||||
if (this.selectionMode === 'day') {
|
||||
let newDate = this.value
|
||||
? modifyDate(this.value, value.getFullYear(), value.getMonth(), value.getDate())
|
||||
: modifyWithTimeString(value, this.defaultTime);
|
||||
// change default time while out of selectableRange
|
||||
if (!this.checkDateWithinRange(newDate)) {
|
||||
newDate = modifyDate(this.selectableRange[0][0], value.getFullYear(), value.getMonth(), value.getDate());
|
||||
}
|
||||
this.date = newDate;
|
||||
this.emit(this.date, this.showTime);
|
||||
} else if (this.selectionMode === 'week') {
|
||||
this.emit(value.date);
|
||||
} else if (this.selectionMode === 'dates') {
|
||||
this.emit(value, true); // set false to keep panel open
|
||||
}
|
||||
},
|
||||
|
||||
handleYearPick(year) {
|
||||
if (this.selectionMode === 'year') {
|
||||
this.date = modifyDate(this.date, year, 0, 1);
|
||||
this.emit(this.date);
|
||||
} else {
|
||||
this.date = changeYearMonthAndClampDate(this.date, year, this.month);
|
||||
// TODO: should emit intermediate value ??
|
||||
// this.emit(this.date, true);
|
||||
this.currentView = 'month';
|
||||
}
|
||||
},
|
||||
|
||||
changeToNow() {
|
||||
// NOTE: not a permanent solution
|
||||
// consider disable "now" button in the future
|
||||
if ((!this.disabledDate || !this.disabledDate(new Date())) && this.checkDateWithinRange(new Date())) {
|
||||
this.date = new Date();
|
||||
this.emit(this.date);
|
||||
}
|
||||
},
|
||||
|
||||
confirm() {
|
||||
if (this.selectionMode === 'dates') {
|
||||
this.emit(this.value);
|
||||
} else {
|
||||
// value were emitted in handle{Date,Time}Pick, nothing to update here
|
||||
// deal with the scenario where: user opens the picker, then confirm without doing anything
|
||||
const value = this.value
|
||||
? this.value
|
||||
: modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
|
||||
this.date = new Date(value); // refresh date
|
||||
this.emit(value);
|
||||
}
|
||||
},
|
||||
|
||||
resetView() {
|
||||
if (this.selectionMode === 'month') {
|
||||
this.currentView = 'month';
|
||||
} else if (this.selectionMode === 'year') {
|
||||
this.currentView = 'year';
|
||||
} else {
|
||||
this.currentView = 'date';
|
||||
}
|
||||
},
|
||||
|
||||
handleEnter() {
|
||||
document.body.addEventListener('keydown', this.handleKeydown);
|
||||
},
|
||||
|
||||
handleLeave() {
|
||||
this.$emit('dodestroy');
|
||||
document.body.removeEventListener('keydown', this.handleKeydown);
|
||||
},
|
||||
|
||||
handleKeydown(event) {
|
||||
const keyCode = event.keyCode;
|
||||
const list = [38, 40, 37, 39];
|
||||
if (this.visible && !this.timePickerVisible) {
|
||||
if (list.indexOf(keyCode) !== -1) {
|
||||
this.handleKeyControl(keyCode);
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
if (keyCode === 13 && this.userInputDate === null && this.userInputTime === null) { // Enter
|
||||
this.emit(this.date, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleKeyControl(keyCode) {
|
||||
const mapping = {
|
||||
'year': {
|
||||
38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setFullYear(date.getFullYear() + step)
|
||||
},
|
||||
'month': {
|
||||
38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setMonth(date.getMonth() + step)
|
||||
},
|
||||
'week': {
|
||||
38: -1, 40: 1, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step * 7)
|
||||
},
|
||||
'day': {
|
||||
38: -7, 40: 7, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step)
|
||||
}
|
||||
};
|
||||
const mode = this.selectionMode;
|
||||
const year = 3.1536e10;
|
||||
const now = this.date.getTime();
|
||||
const newDate = new Date(this.date.getTime());
|
||||
while (Math.abs(now - newDate.getTime()) <= year) {
|
||||
const map = mapping[mode];
|
||||
map.offset(newDate, map[keyCode]);
|
||||
if (typeof this.disabledDate === 'function' && this.disabledDate(newDate)) {
|
||||
continue;
|
||||
}
|
||||
this.date = newDate;
|
||||
this.$emit('pick', newDate, true);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleVisibleTimeChange(value) {
|
||||
const time = parseDate(value, this.timeFormat);
|
||||
if (time && this.checkDateWithinRange(time)) {
|
||||
this.date = modifyDate(time, this.year, this.month, this.monthDate);
|
||||
this.userInputTime = null;
|
||||
this.$refs.timepicker.value = this.date;
|
||||
this.timePickerVisible = false;
|
||||
this.emit(this.date, true);
|
||||
}
|
||||
},
|
||||
|
||||
handleVisibleDateChange(value) {
|
||||
const date = parseDate(value, this.dateFormat);
|
||||
if (date) {
|
||||
if (typeof this.disabledDate === 'function' && this.disabledDate(date)) {
|
||||
return;
|
||||
}
|
||||
this.date = modifyTime(date, this.date.getHours(), this.date.getMinutes(), this.date.getSeconds());
|
||||
this.userInputDate = null;
|
||||
this.resetView();
|
||||
this.emit(this.date, true);
|
||||
}
|
||||
},
|
||||
|
||||
isValidValue(value) {
|
||||
return value && !isNaN(value) && (
|
||||
typeof this.disabledDate === 'function'
|
||||
? !this.disabledDate(value)
|
||||
: true
|
||||
) && this.checkDateWithinRange(value);
|
||||
},
|
||||
|
||||
getDefaultValue() {
|
||||
// if default-value is set, return it
|
||||
// otherwise, return now (the moment this method gets called)
|
||||
return this.defaultValue ? new Date(this.defaultValue) : new Date();
|
||||
},
|
||||
|
||||
checkDateWithinRange(date) {
|
||||
return this.selectableRange.length > 0
|
||||
? timeWithinRange(date, this.selectableRange, this.format || 'HH:mm:ss')
|
||||
: true;
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
TimePicker, YearTable, MonthTable, DateTable, ElInput, ElButton
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
popperClass: '',
|
||||
date: new Date(),
|
||||
value: '',
|
||||
defaultValue: null, // use getDefaultValue() for time computation
|
||||
defaultTime: null,
|
||||
showTime: false,
|
||||
selectionMode: 'day',
|
||||
shortcuts: '',
|
||||
visible: false,
|
||||
currentView: 'date',
|
||||
disabledDate: '',
|
||||
selectableRange: [],
|
||||
firstDayOfWeek: 7,
|
||||
showWeekNumber: false,
|
||||
timePickerVisible: false,
|
||||
format: '',
|
||||
arrowControl: false,
|
||||
userInputDate: null,
|
||||
userInputTime: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
year() {
|
||||
return this.date.getFullYear();
|
||||
},
|
||||
|
||||
month() {
|
||||
return this.date.getMonth();
|
||||
},
|
||||
|
||||
week() {
|
||||
return getWeekNumber(this.date);
|
||||
},
|
||||
|
||||
monthDate() {
|
||||
return this.date.getDate();
|
||||
},
|
||||
|
||||
footerVisible() {
|
||||
return this.showTime || this.selectionMode === 'dates';
|
||||
},
|
||||
|
||||
visibleTime() {
|
||||
if (this.userInputTime !== null) {
|
||||
return this.userInputTime;
|
||||
} else {
|
||||
return formatDate(this.value || this.defaultValue, this.timeFormat);
|
||||
}
|
||||
},
|
||||
|
||||
visibleDate() {
|
||||
if (this.userInputDate !== null) {
|
||||
return this.userInputDate;
|
||||
} else {
|
||||
return formatDate(this.value || this.defaultValue, this.dateFormat);
|
||||
}
|
||||
},
|
||||
|
||||
yearLabel() {
|
||||
const yearTranslation = this.t('el.datepicker.year');
|
||||
if (this.currentView === 'year') {
|
||||
const startYear = Math.floor(this.year / 10) * 10;
|
||||
if (yearTranslation) {
|
||||
return startYear + ' ' + yearTranslation + ' - ' + (startYear + 9) + ' ' + yearTranslation;
|
||||
}
|
||||
return startYear + ' - ' + (startYear + 9);
|
||||
}
|
||||
return this.year + ' ' + yearTranslation;
|
||||
},
|
||||
|
||||
timeFormat() {
|
||||
if (this.format) {
|
||||
return extractTimeFormat(this.format);
|
||||
} else {
|
||||
return 'HH:mm:ss';
|
||||
}
|
||||
},
|
||||
|
||||
dateFormat() {
|
||||
if (this.format) {
|
||||
return extractDateFormat(this.format);
|
||||
} else {
|
||||
return 'yyyy-MM-dd';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<transition
|
||||
name="el-zoom-in-top"
|
||||
@after-leave="$emit('dodestroy')">
|
||||
<div
|
||||
v-show="visible"
|
||||
class="el-time-range-picker el-picker-panel el-popper"
|
||||
:class="popperClass">
|
||||
<div class="el-time-range-picker__content">
|
||||
<div class="el-time-range-picker__cell">
|
||||
<div class="el-time-range-picker__header">{{ t('el.datepicker.startTime') }}</div>
|
||||
<div
|
||||
:class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
|
||||
class="el-time-range-picker__body el-time-panel__content">
|
||||
<time-spinner
|
||||
ref="minSpinner"
|
||||
:show-seconds="showSeconds"
|
||||
:am-pm-mode="amPmMode"
|
||||
@change="handleMinChange"
|
||||
:arrow-control="arrowControl"
|
||||
@select-range="setMinSelectionRange"
|
||||
:date="minDate">
|
||||
</time-spinner>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-time-range-picker__cell">
|
||||
<div class="el-time-range-picker__header">{{ t('el.datepicker.endTime') }}</div>
|
||||
<div
|
||||
:class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
|
||||
class="el-time-range-picker__body el-time-panel__content">
|
||||
<time-spinner
|
||||
ref="maxSpinner"
|
||||
:show-seconds="showSeconds"
|
||||
:am-pm-mode="amPmMode"
|
||||
@change="handleMaxChange"
|
||||
:arrow-control="arrowControl"
|
||||
@select-range="setMaxSelectionRange"
|
||||
:date="maxDate">
|
||||
</time-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="el-time-panel__footer">
|
||||
<button
|
||||
type="button"
|
||||
class="el-time-panel__btn cancel"
|
||||
@click="handleCancel()">{{ t('el.datepicker.cancel') }}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="el-time-panel__btn confirm"
|
||||
@click="handleConfirm()"
|
||||
:disabled="btnDisabled">{{ t('el.datepicker.confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script type="text/babel">
|
||||
import {
|
||||
parseDate,
|
||||
limitTimeRange,
|
||||
modifyDate,
|
||||
clearMilliseconds,
|
||||
timeWithinRange
|
||||
} from '../util';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import TimeSpinner from '../basic/time-spinner';
|
||||
|
||||
const MIN_TIME = parseDate('00:00:00', 'HH:mm:ss');
|
||||
const MAX_TIME = parseDate('23:59:59', 'HH:mm:ss');
|
||||
|
||||
const minTimeOfDay = function(date) {
|
||||
return modifyDate(MIN_TIME, date.getFullYear(), date.getMonth(), date.getDate());
|
||||
};
|
||||
|
||||
const maxTimeOfDay = function(date) {
|
||||
return modifyDate(MAX_TIME, date.getFullYear(), date.getMonth(), date.getDate());
|
||||
};
|
||||
|
||||
// increase time by amount of milliseconds, but within the range of day
|
||||
const advanceTime = function(date, amount) {
|
||||
return new Date(Math.min(date.getTime() + amount, maxTimeOfDay(date).getTime()));
|
||||
};
|
||||
|
||||
export default {
|
||||
mixins: [Locale],
|
||||
|
||||
components: { TimeSpinner },
|
||||
|
||||
computed: {
|
||||
showSeconds() {
|
||||
return (this.format || '').indexOf('ss') !== -1;
|
||||
},
|
||||
|
||||
offset() {
|
||||
return this.showSeconds ? 11 : 8;
|
||||
},
|
||||
|
||||
spinner() {
|
||||
return this.selectionRange[0] < this.offset ? this.$refs.minSpinner : this.$refs.maxSpinner;
|
||||
},
|
||||
|
||||
btnDisabled() {
|
||||
return this.minDate.getTime() > this.maxDate.getTime();
|
||||
},
|
||||
amPmMode() {
|
||||
if ((this.format || '').indexOf('A') !== -1) return 'A';
|
||||
if ((this.format || '').indexOf('a') !== -1) return 'a';
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
popperClass: '',
|
||||
minDate: new Date(),
|
||||
maxDate: new Date(),
|
||||
value: [],
|
||||
oldValue: [new Date(), new Date()],
|
||||
defaultValue: null,
|
||||
format: 'HH:mm:ss',
|
||||
visible: false,
|
||||
selectionRange: [0, 2],
|
||||
arrowControl: false
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(value) {
|
||||
if (Array.isArray(value)) {
|
||||
this.minDate = new Date(value[0]);
|
||||
this.maxDate = new Date(value[1]);
|
||||
} else {
|
||||
if (Array.isArray(this.defaultValue)) {
|
||||
this.minDate = new Date(this.defaultValue[0]);
|
||||
this.maxDate = new Date(this.defaultValue[1]);
|
||||
} else if (this.defaultValue) {
|
||||
this.minDate = new Date(this.defaultValue);
|
||||
this.maxDate = advanceTime(new Date(this.defaultValue), 60 * 60 * 1000);
|
||||
} else {
|
||||
this.minDate = new Date();
|
||||
this.maxDate = advanceTime(new Date(), 60 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.oldValue = this.value;
|
||||
this.$nextTick(() => this.$refs.minSpinner.emitSelectRange('hours'));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClear() {
|
||||
this.$emit('pick', null);
|
||||
},
|
||||
|
||||
handleCancel() {
|
||||
this.$emit('pick', this.oldValue);
|
||||
},
|
||||
|
||||
handleMinChange(date) {
|
||||
this.minDate = clearMilliseconds(date);
|
||||
this.handleChange();
|
||||
},
|
||||
|
||||
handleMaxChange(date) {
|
||||
this.maxDate = clearMilliseconds(date);
|
||||
this.handleChange();
|
||||
},
|
||||
|
||||
handleChange() {
|
||||
if (this.isValidValue([this.minDate, this.maxDate])) {
|
||||
this.$refs.minSpinner.selectableRange = [[minTimeOfDay(this.minDate), this.maxDate]];
|
||||
this.$refs.maxSpinner.selectableRange = [[this.minDate, maxTimeOfDay(this.maxDate)]];
|
||||
this.$emit('pick', [this.minDate, this.maxDate], true);
|
||||
}
|
||||
},
|
||||
|
||||
setMinSelectionRange(start, end) {
|
||||
this.$emit('select-range', start, end, 'min');
|
||||
this.selectionRange = [start, end];
|
||||
},
|
||||
|
||||
setMaxSelectionRange(start, end) {
|
||||
this.$emit('select-range', start, end, 'max');
|
||||
this.selectionRange = [start + this.offset, end + this.offset];
|
||||
},
|
||||
|
||||
handleConfirm(visible = false) {
|
||||
const minSelectableRange = this.$refs.minSpinner.selectableRange;
|
||||
const maxSelectableRange = this.$refs.maxSpinner.selectableRange;
|
||||
|
||||
this.minDate = limitTimeRange(this.minDate, minSelectableRange, this.format);
|
||||
this.maxDate = limitTimeRange(this.maxDate, maxSelectableRange, this.format);
|
||||
|
||||
this.$emit('pick', [this.minDate, this.maxDate], visible);
|
||||
},
|
||||
|
||||
adjustSpinners() {
|
||||
this.$refs.minSpinner.adjustSpinners();
|
||||
this.$refs.maxSpinner.adjustSpinners();
|
||||
},
|
||||
|
||||
changeSelectionRange(step) {
|
||||
const list = this.showSeconds ? [0, 3, 6, 11, 14, 17] : [0, 3, 8, 11];
|
||||
const mapping = ['hours', 'minutes'].concat(this.showSeconds ? ['seconds'] : []);
|
||||
const index = list.indexOf(this.selectionRange[0]);
|
||||
const next = (index + step + list.length) % list.length;
|
||||
const half = list.length / 2;
|
||||
if (next < half) {
|
||||
this.$refs.minSpinner.emitSelectRange(mapping[next]);
|
||||
} else {
|
||||
this.$refs.maxSpinner.emitSelectRange(mapping[next - half]);
|
||||
}
|
||||
},
|
||||
|
||||
isValidValue(date) {
|
||||
return Array.isArray(date) &&
|
||||
timeWithinRange(this.minDate, this.$refs.minSpinner.selectableRange) &&
|
||||
timeWithinRange(this.maxDate, this.$refs.maxSpinner.selectableRange);
|
||||
},
|
||||
|
||||
handleKeydown(event) {
|
||||
const keyCode = event.keyCode;
|
||||
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
|
||||
|
||||
// Left or Right
|
||||
if (keyCode === 37 || keyCode === 39) {
|
||||
const step = mapping[keyCode];
|
||||
this.changeSelectionRange(step);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Up or Down
|
||||
if (keyCode === 38 || keyCode === 40) {
|
||||
const step = mapping[keyCode];
|
||||
this.spinner.scrollDown(step);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-top" @before-enter="handleMenuEnter" @after-leave="$emit('dodestroy')">
|
||||
<div
|
||||
ref="popper"
|
||||
v-show="visible"
|
||||
:style="{ width: width + 'px' }"
|
||||
:class="popperClass"
|
||||
class="el-picker-panel time-select el-popper">
|
||||
<el-scrollbar noresize wrap-class="el-picker-panel__content">
|
||||
<div class="time-select-item"
|
||||
v-for="item in items"
|
||||
:class="{ selected: value === item.value, disabled: item.disabled, default: item.value === defaultValue }"
|
||||
:disabled="item.disabled"
|
||||
:key="item.value"
|
||||
@click="handleClick(item)">{{ item.value }}</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script type="text/babel">
|
||||
import ElScrollbar from 'element-ui/packages/scrollbar';
|
||||
import scrollIntoView from 'element-ui/src/utils/scroll-into-view';
|
||||
|
||||
const parseTime = function(time) {
|
||||
const values = (time || '').split(':');
|
||||
if (values.length >= 2) {
|
||||
const hours = parseInt(values[0], 10);
|
||||
const minutes = parseInt(values[1], 10);
|
||||
|
||||
return {
|
||||
hours,
|
||||
minutes
|
||||
};
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return null;
|
||||
};
|
||||
|
||||
const compareTime = function(time1, time2) {
|
||||
const value1 = parseTime(time1);
|
||||
const value2 = parseTime(time2);
|
||||
|
||||
const minutes1 = value1.minutes + value1.hours * 60;
|
||||
const minutes2 = value2.minutes + value2.hours * 60;
|
||||
|
||||
if (minutes1 === minutes2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return minutes1 > minutes2 ? 1 : -1;
|
||||
};
|
||||
|
||||
const formatTime = function(time) {
|
||||
return (time.hours < 10 ? '0' + time.hours : time.hours) + ':' + (time.minutes < 10 ? '0' + time.minutes : time.minutes);
|
||||
};
|
||||
|
||||
const nextTime = function(time, step) {
|
||||
const timeValue = parseTime(time);
|
||||
const stepValue = parseTime(step);
|
||||
|
||||
const next = {
|
||||
hours: timeValue.hours,
|
||||
minutes: timeValue.minutes
|
||||
};
|
||||
|
||||
next.minutes += stepValue.minutes;
|
||||
next.hours += stepValue.hours;
|
||||
|
||||
next.hours += Math.floor(next.minutes / 60);
|
||||
next.minutes = next.minutes % 60;
|
||||
|
||||
return formatTime(next);
|
||||
};
|
||||
|
||||
export default {
|
||||
components: { ElScrollbar },
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!val) return;
|
||||
this.$nextTick(() => this.scrollToOption());
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(item) {
|
||||
if (!item.disabled) {
|
||||
this.$emit('pick', item.value);
|
||||
}
|
||||
},
|
||||
|
||||
handleClear() {
|
||||
this.$emit('pick', null);
|
||||
},
|
||||
|
||||
scrollToOption(selector = '.selected') {
|
||||
const menu = this.$refs.popper.querySelector('.el-picker-panel__content');
|
||||
scrollIntoView(menu, menu.querySelector(selector));
|
||||
},
|
||||
|
||||
handleMenuEnter() {
|
||||
const selected = this.items.map(item => item.value).indexOf(this.value) !== -1;
|
||||
const hasDefault = this.items.map(item => item.value).indexOf(this.defaultValue) !== -1;
|
||||
const option = (selected && '.selected') || (hasDefault && '.default') || '.time-select-item:not(.disabled)';
|
||||
this.$nextTick(() => this.scrollToOption(option));
|
||||
},
|
||||
|
||||
scrollDown(step) {
|
||||
const items = this.items;
|
||||
const length = items.length;
|
||||
let total = items.length;
|
||||
let index = items.map(item => item.value).indexOf(this.value);
|
||||
while (total--) {
|
||||
index = (index + step + length) % length;
|
||||
if (!items[index].disabled) {
|
||||
this.$emit('pick', items[index].value, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isValidValue(date) {
|
||||
return this.items.filter(item => !item.disabled).map(item => item.value).indexOf(date) !== -1;
|
||||
},
|
||||
|
||||
handleKeydown(event) {
|
||||
const keyCode = event.keyCode;
|
||||
if (keyCode === 38 || keyCode === 40) {
|
||||
const mapping = { 40: 1, 38: -1 };
|
||||
const offset = mapping[keyCode.toString()];
|
||||
this.scrollDown(offset);
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
popperClass: '',
|
||||
start: '09:00',
|
||||
end: '18:00',
|
||||
step: '00:30',
|
||||
value: '',
|
||||
defaultValue: '',
|
||||
visible: false,
|
||||
minTime: '',
|
||||
maxTime: '',
|
||||
width: 0
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
items() {
|
||||
const start = this.start;
|
||||
const end = this.end;
|
||||
const step = this.step;
|
||||
|
||||
const result = [];
|
||||
|
||||
if (start && end && step) {
|
||||
let current = start;
|
||||
while (compareTime(current, end) <= 0) {
|
||||
result.push({
|
||||
value: current,
|
||||
disabled: compareTime(current, this.minTime || '-1:-1') <= 0 ||
|
||||
compareTime(current, this.maxTime || '100:100') >= 0
|
||||
});
|
||||
current = nextTime(current, step);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
|
||||
<div
|
||||
v-show="visible"
|
||||
class="el-time-panel el-popper"
|
||||
:class="popperClass">
|
||||
<div class="el-time-panel__content" :class="{ 'has-seconds': showSeconds }">
|
||||
<time-spinner
|
||||
ref="spinner"
|
||||
@change="handleChange"
|
||||
:arrow-control="useArrow"
|
||||
:show-seconds="showSeconds"
|
||||
:am-pm-mode="amPmMode"
|
||||
@select-range="setSelectionRange"
|
||||
:date="date">
|
||||
</time-spinner>
|
||||
</div>
|
||||
<div class="el-time-panel__footer">
|
||||
<button
|
||||
type="button"
|
||||
class="el-time-panel__btn cancel"
|
||||
@click="handleCancel">{{ t('el.datepicker.cancel') }}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="el-time-panel__btn"
|
||||
:class="{confirm: !disabled}"
|
||||
@click="handleConfirm()">{{ t('el.datepicker.confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script type="text/babel">
|
||||
import { limitTimeRange, isDate, clearMilliseconds, timeWithinRange } from '../util';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import TimeSpinner from '../basic/time-spinner';
|
||||
|
||||
export default {
|
||||
mixins: [Locale],
|
||||
|
||||
components: {
|
||||
TimeSpinner
|
||||
},
|
||||
|
||||
props: {
|
||||
visible: Boolean,
|
||||
timeArrowControl: Boolean
|
||||
},
|
||||
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.oldValue = this.value;
|
||||
this.$nextTick(() => this.$refs.spinner.emitSelectRange('hours'));
|
||||
} else {
|
||||
this.needInitAdjust = true;
|
||||
}
|
||||
},
|
||||
|
||||
value(newVal) {
|
||||
let date;
|
||||
if (newVal instanceof Date) {
|
||||
date = limitTimeRange(newVal, this.selectableRange, this.format);
|
||||
} else if (!newVal) {
|
||||
date = this.defaultValue ? new Date(this.defaultValue) : new Date();
|
||||
}
|
||||
|
||||
this.date = date;
|
||||
if (this.visible && this.needInitAdjust) {
|
||||
this.$nextTick(_ => this.adjustSpinners());
|
||||
this.needInitAdjust = false;
|
||||
}
|
||||
},
|
||||
|
||||
selectableRange(val) {
|
||||
this.$refs.spinner.selectableRange = val;
|
||||
},
|
||||
|
||||
defaultValue(val) {
|
||||
if (!isDate(this.value)) {
|
||||
this.date = val ? new Date(val) : new Date();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
popperClass: '',
|
||||
format: 'HH:mm:ss',
|
||||
value: '',
|
||||
defaultValue: null,
|
||||
date: new Date(),
|
||||
oldValue: new Date(),
|
||||
selectableRange: [],
|
||||
selectionRange: [0, 2],
|
||||
disabled: false,
|
||||
arrowControl: false,
|
||||
needInitAdjust: true
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
showSeconds() {
|
||||
return (this.format || '').indexOf('ss') !== -1;
|
||||
},
|
||||
useArrow() {
|
||||
return this.arrowControl || this.timeArrowControl || false;
|
||||
},
|
||||
amPmMode() {
|
||||
if ((this.format || '').indexOf('A') !== -1) return 'A';
|
||||
if ((this.format || '').indexOf('a') !== -1) return 'a';
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleCancel() {
|
||||
this.$emit('pick', this.oldValue, false);
|
||||
},
|
||||
|
||||
handleChange(date) {
|
||||
// this.visible avoids edge cases, when use scrolls during panel closing animation
|
||||
if (this.visible) {
|
||||
this.date = clearMilliseconds(date);
|
||||
// if date is out of range, do not emit
|
||||
if (this.isValidValue(this.date)) {
|
||||
this.$emit('pick', this.date, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setSelectionRange(start, end) {
|
||||
this.$emit('select-range', start, end);
|
||||
this.selectionRange = [start, end];
|
||||
},
|
||||
|
||||
handleConfirm(visible = false, first) {
|
||||
if (first) return;
|
||||
const date = clearMilliseconds(limitTimeRange(this.date, this.selectableRange, this.format));
|
||||
this.$emit('pick', date, visible, first);
|
||||
},
|
||||
|
||||
handleKeydown(event) {
|
||||
const keyCode = event.keyCode;
|
||||
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
|
||||
|
||||
// Left or Right
|
||||
if (keyCode === 37 || keyCode === 39) {
|
||||
const step = mapping[keyCode];
|
||||
this.changeSelectionRange(step);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Up or Down
|
||||
if (keyCode === 38 || keyCode === 40) {
|
||||
const step = mapping[keyCode];
|
||||
this.$refs.spinner.scrollDown(step);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
isValidValue(date) {
|
||||
return timeWithinRange(date, this.selectableRange, this.format);
|
||||
},
|
||||
|
||||
adjustSpinners() {
|
||||
return this.$refs.spinner.adjustSpinners();
|
||||
},
|
||||
|
||||
changeSelectionRange(step) {
|
||||
const list = [0, 3].concat(this.showSeconds ? [6] : []);
|
||||
const mapping = ['hours', 'minutes'].concat(this.showSeconds ? ['seconds'] : []);
|
||||
const index = list.indexOf(this.selectionRange[0]);
|
||||
const next = (index + step + list.length) % list.length;
|
||||
this.$refs.spinner.emitSelectRange(mapping[next]);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => this.handleConfirm(true, true));
|
||||
this.$emit('mounted');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user