first
This commit is contained in:
8
packages/calendar/index.js
Normal file
8
packages/calendar/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import Calendar from './src/main';
|
||||
|
||||
/* istanbul ignore next */
|
||||
Calendar.install = function(Vue) {
|
||||
Vue.component(Calendar.name, Calendar);
|
||||
};
|
||||
|
||||
export default Calendar;
|
199
packages/calendar/src/date-table.vue
Normal file
199
packages/calendar/src/date-table.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<script>
|
||||
import fecha from 'element-ui/src/utils/date';
|
||||
import { range as rangeArr, getFirstDayOfMonth, getPrevMonthLastDays, getMonthDays, getI18nSettings, validateRangeInOneMonth } from 'element-ui/src/utils/date-util';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
selectedDay: String, // formated date yyyy-MM-dd
|
||||
range: {
|
||||
type: Array,
|
||||
validator(val) {
|
||||
if (!(val && val.length)) return true;
|
||||
const [start, end] = val;
|
||||
return validateRangeInOneMonth(start, end);
|
||||
}
|
||||
},
|
||||
date: Date,
|
||||
hideHeader: Boolean,
|
||||
firstDayOfWeek: Number
|
||||
},
|
||||
|
||||
inject: ['elCalendar'],
|
||||
|
||||
methods: {
|
||||
toNestedArr(days) {
|
||||
return rangeArr(days.length / 7).map((_, index) => {
|
||||
const start = index * 7;
|
||||
return days.slice(start, start + 7);
|
||||
});
|
||||
},
|
||||
|
||||
getFormateDate(day, type) {
|
||||
if (!day || ['prev', 'current', 'next'].indexOf(type) === -1) {
|
||||
throw new Error('invalid day or type');
|
||||
}
|
||||
let prefix = this.curMonthDatePrefix;
|
||||
if (type === 'prev') {
|
||||
prefix = this.prevMonthDatePrefix;
|
||||
} else if (type === 'next') {
|
||||
prefix = this.nextMonthDatePrefix;
|
||||
}
|
||||
day = `00${day}`.slice(-2);
|
||||
return `${prefix}-${day}`;
|
||||
},
|
||||
|
||||
getCellClass({ text, type}) {
|
||||
const classes = [type];
|
||||
if (type === 'current') {
|
||||
const date = this.getFormateDate(text, type);
|
||||
if (date === this.selectedDay) {
|
||||
classes.push('is-selected');
|
||||
}
|
||||
if (date === this.formatedToday) {
|
||||
classes.push('is-today');
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
},
|
||||
|
||||
pickDay({ text, type }) {
|
||||
const date = this.getFormateDate(text, type);
|
||||
this.$emit('pick', date);
|
||||
},
|
||||
|
||||
cellRenderProxy({ text, type }) {
|
||||
let render = this.elCalendar.$scopedSlots.dateCell;
|
||||
if (!render) return <span>{ text }</span>;
|
||||
|
||||
const day = this.getFormateDate(text, type);
|
||||
const date = new Date(day);
|
||||
const data = {
|
||||
isSelected: this.selectedDay === day,
|
||||
type: `${type}-month`,
|
||||
day
|
||||
};
|
||||
return render({ date, data });
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
WEEK_DAYS() {
|
||||
return getI18nSettings().dayNames;
|
||||
},
|
||||
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');
|
||||
},
|
||||
|
||||
formatedToday() {
|
||||
return this.elCalendar.formatedToday;
|
||||
},
|
||||
|
||||
isInRange() {
|
||||
return this.range && this.range.length;
|
||||
},
|
||||
|
||||
rows() {
|
||||
let days = [];
|
||||
// if range exists, should render days in range.
|
||||
if (this.isInRange) {
|
||||
const [start, end] = this.range;
|
||||
const currentMonthRange = rangeArr(end.getDate() - start.getDate() + 1).map((_, index) => ({
|
||||
text: start.getDate() + index,
|
||||
type: 'current'
|
||||
}));
|
||||
let remaining = currentMonthRange.length % 7;
|
||||
remaining = remaining === 0 ? 0 : 7 - remaining;
|
||||
const nextMonthRange = rangeArr(remaining).map((_, index) => ({
|
||||
text: index + 1,
|
||||
type: 'next'
|
||||
}));
|
||||
days = currentMonthRange.concat(nextMonthRange);
|
||||
} else {
|
||||
const date = this.date;
|
||||
let firstDay = getFirstDayOfMonth(date);
|
||||
firstDay = firstDay === 0 ? 7 : firstDay;
|
||||
const firstDayOfWeek = typeof this.firstDayOfWeek === 'number' ? this.firstDayOfWeek : 1;
|
||||
const prevMonthDays = getPrevMonthLastDays(date, firstDay - firstDayOfWeek).map(day => ({
|
||||
text: day,
|
||||
type: 'prev'
|
||||
}));
|
||||
const currentMonthDays = getMonthDays(date).map(day => ({
|
||||
text: day,
|
||||
type: 'current'
|
||||
}));
|
||||
days = [...prevMonthDays, ...currentMonthDays];
|
||||
const nextMonthDays = rangeArr(42 - days.length).map((_, index) => ({
|
||||
text: index + 1,
|
||||
type: 'next'
|
||||
}));
|
||||
days = days.concat(nextMonthDays);
|
||||
}
|
||||
return this.toNestedArr(days);
|
||||
},
|
||||
|
||||
weekDays() {
|
||||
const start = this.firstDayOfWeek;
|
||||
const { WEEK_DAYS } = this;
|
||||
|
||||
if (typeof start !== 'number' || start === 0) {
|
||||
return WEEK_DAYS.slice();
|
||||
} else {
|
||||
return WEEK_DAYS.slice(start).concat(WEEK_DAYS.slice(0, start));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const thead = this.hideHeader ? null : (<thead>
|
||||
{
|
||||
this.weekDays.map(day => <th key={day}>{ day }</th>)
|
||||
}
|
||||
</thead>);
|
||||
return (
|
||||
<table
|
||||
class={{
|
||||
'el-calendar-table': true,
|
||||
'is-range': this.isInRange
|
||||
}}
|
||||
cellspacing="0"
|
||||
cellpadding="0">
|
||||
{
|
||||
thead
|
||||
}
|
||||
<tbody>
|
||||
{
|
||||
this.rows.map((row, index) => <tr
|
||||
class={{
|
||||
'el-calendar-table__row': true,
|
||||
'el-calendar-table__row--hide-border': index === 0 && this.hideHeader
|
||||
}}
|
||||
key={index}>
|
||||
{
|
||||
row.map((cell, key) => <td key={key}
|
||||
class={ this.getCellClass(cell) }
|
||||
onClick={this.pickDay.bind(this, cell)}>
|
||||
<div class="el-calendar-day">
|
||||
{
|
||||
this.cellRenderProxy(cell)
|
||||
}
|
||||
</div>
|
||||
</td>)
|
||||
}
|
||||
</tr>)
|
||||
}
|
||||
</tbody>
|
||||
</table>);
|
||||
}
|
||||
};
|
||||
</script>
|
280
packages/calendar/src/main.vue
Normal file
280
packages/calendar/src/main.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<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>
|
Reference in New Issue
Block a user