281 lines
7.3 KiB
Vue
Raw Normal View History

2025-04-19 15:38:48 +08:00
<template>
<div class="el-calendar">
<div class="el-calendar__header">
<div class="el-calendar__title">
{{ i18nDate }}
</div>
<div
class="el-calendar__button-group"
v-if="validatedRange.length === 0">
<el-button-group>
<el-button
type="plain"
size="mini"
@click="selectDate('prev-month')">
{{ t('el.datepicker.prevMonth') }}
</el-button>
<el-button
type="plain"
size="mini"
@click="selectDate('today')">
{{ t('el.datepicker.today') }}
</el-button>
<el-button
type="plain"
size="mini"
@click="selectDate('next-month')">
{{ t('el.datepicker.nextMonth') }}
</el-button>
</el-button-group>
</div>
</div>
<div
class="el-calendar__body"
v-if="validatedRange.length === 0"
key="no-range">
<date-table
:date="date"
:selected-day="realSelectedDay"
:first-day-of-week="realFirstDayOfWeek"
@pick="pickDay" />
</div>
<div
v-else
class="el-calendar__body"
key="has-range">
<date-table
v-for="(range, index) in validatedRange"
:key="index"
:date="range[0]"
:selected-day="realSelectedDay"
:range="range"
:hide-header="index !== 0"
:first-day-of-week="realFirstDayOfWeek"
@pick="pickDay" />
</div>
</div>
</template>
<script>
import Locale from 'element-ui/src/mixins/locale';
import fecha from 'element-ui/src/utils/date';
import ElButton from 'element-ui/packages/button';
import ElButtonGroup from 'element-ui/packages/button-group';
import DateTable from './date-table';
import { validateRangeInOneMonth } from 'element-ui/src/utils/date-util';
const validTypes = ['prev-month', 'today', 'next-month'];
const weekDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const oneDay = 86400000;
export default {
name: 'ElCalendar',
mixins: [Locale],
components: {
DateTable,
ElButton,
ElButtonGroup
},
props: {
value: [Date, String, Number],
range: {
type: Array,
validator(range) {
if (Array.isArray(range)) {
return range.length === 2 && range.every(
item => typeof item === 'string' ||
typeof item === 'number' ||
item instanceof Date);
} else {
return true;
}
}
},
firstDayOfWeek: {
type: Number,
default: 1
}
},
provide() {
return {
elCalendar: this
};
},
methods: {
pickDay(day) {
this.realSelectedDay = day;
},
selectDate(type) {
if (validTypes.indexOf(type) === -1) {
throw new Error(`invalid type ${type}`);
}
let day = '';
if (type === 'prev-month') {
day = `${this.prevMonthDatePrefix}-01`;
} else if (type === 'next-month') {
day = `${this.nextMonthDatePrefix}-01`;
} else {
day = this.formatedToday;
}
if (day === this.formatedDate) return;
this.pickDay(day);
},
toDate(val) {
if (!val) {
throw new Error('invalid val');
}
return val instanceof Date ? val : new Date(val);
},
rangeValidator(date, isStart) {
const firstDayOfWeek = this.realFirstDayOfWeek;
const expected = isStart ? firstDayOfWeek : (firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1);
const message = `${isStart ? 'start' : 'end'} of range should be ${weekDays[expected]}.`;
if (date.getDay() !== expected) {
console.warn('[ElementCalendar]', message, 'Invalid range will be ignored.');
return false;
}
return true;
}
},
computed: {
prevMonthDatePrefix() {
const temp = new Date(this.date.getTime());
temp.setDate(0);
return fecha.format(temp, 'yyyy-MM');
},
curMonthDatePrefix() {
return fecha.format(this.date, 'yyyy-MM');
},
nextMonthDatePrefix() {
const temp = new Date(this.date.getFullYear(), this.date.getMonth() + 1, 1);
return fecha.format(temp, 'yyyy-MM');
},
formatedDate() {
return fecha.format(this.date, 'yyyy-MM-dd');
},
i18nDate() {
const year = this.date.getFullYear();
const month = this.date.getMonth() + 1;
return `${year} ${this.t('el.datepicker.year')} ${this.t('el.datepicker.month' + month)}`;
},
formatedToday() {
return fecha.format(this.now, 'yyyy-MM-dd');
},
realSelectedDay: {
get() {
if (!this.value) return this.selectedDay;
return this.formatedDate;
},
set(val) {
this.selectedDay = val;
const date = new Date(val);
this.$emit('input', date);
}
},
date() {
if (!this.value) {
if (this.realSelectedDay) {
const d = this.selectedDay.split('-');
return new Date(d[0], d[1] - 1, d[2]);
} else if (this.validatedRange.length) {
return this.validatedRange[0][0];
}
return this.now;
} else {
return this.toDate(this.value);
}
},
// if range is valid, we get a two-digit array
validatedRange() {
let range = this.range;
if (!range) return [];
range = range.reduce((prev, val, index) => {
const date = this.toDate(val);
if (this.rangeValidator(date, index === 0)) {
prev = prev.concat(date);
}
return prev;
}, []);
if (range.length === 2) {
const [start, end] = range;
if (start > end) {
console.warn('[ElementCalendar]end time should be greater than start time');
return [];
}
// start time and end time in one month
if (validateRangeInOneMonth(start, end)) {
return [
[start, end]
];
}
const data = [];
let startDay = new Date(start.getFullYear(), start.getMonth() + 1, 1);
const lastDay = this.toDate(startDay.getTime() - oneDay);
if (!validateRangeInOneMonth(startDay, end)) {
console.warn('[ElementCalendar]start time and end time interval must not exceed two months');
return [];
}
// 第一个月的时间范围
data.push([
start,
lastDay
]);
// 下一月的时间范围,需要计算一下该月的第一个周起始日
const firstDayOfWeek = this.realFirstDayOfWeek;
const nextMontFirstDay = startDay.getDay();
let interval = 0;
if (nextMontFirstDay !== firstDayOfWeek) {
if (firstDayOfWeek === 0) {
interval = 7 - nextMontFirstDay;
} else {
interval = firstDayOfWeek - nextMontFirstDay;
interval = interval > 0 ? interval : 7 + interval;
}
}
startDay = this.toDate(startDay.getTime() + interval * oneDay);
if (startDay.getDate() < end.getDate()) {
data.push([
startDay,
end
]);
}
return data;
}
return [];
},
realFirstDayOfWeek() {
if (this.firstDayOfWeek < 1 || this.firstDayOfWeek > 6) {
return 0;
}
return Math.floor(this.firstDayOfWeek);
}
},
data() {
return {
selectedDay: '',
now: new Date()
};
}
};
</script>