I. Component introduction
Website links: Calendar component | Element (gitee. IO)
Calendar is used to display dates.
The date processing in Element Plus uses day.js, a 2KB library that is a lightweight version of moment.js.
1.1 attributes
- V-model: Indicates the Date type, indicating the selected Date.
- Range: Date[] indicates the range of dates to be displayed. If the range is not transmitted, the current month is default and the month can be changed.
1.2 slot
- Data-cell: the named slot is used to customize the date display content.
Second, source code analysis
2.1 Calendar component code
2.1.1 the template
<template>
<div class="el-calendar">
<div class="el-calendar__header">// Title: Format: July 2021<div class="el-calendar__title">{{ i18nDate }}</div>// Non-range mode<div v-if="validatedRange.length === 0" class="el-calendar__button-group">// In non-range mode, you can switch month // button groups: last month, today, next Month<el-button-group>
<el-button
size="mini"
@click="selectDate('prev-month')"
>
{{ t('el.datepicker.prevMonth') }}
</el-button>
<el-button size="mini" @click="selectDate('today')">
{{
t('el.datepicker.today')
}}
</el-button>
<el-button
size="mini"
@click="selectDate('next-month')"
>
{{ t('el.datepicker.nextMonth') }}
</el-button>
</el-button-group>
</div>
</div>// Table display of dates in non-range mode<div v-if="validatedRange.length === 0" class="el-calendar__body">// date-table is an internal component used to render the date table<date-table
:date="date"
:selected-day="realSelectedDay"
@pick="pickDay"
>
<template v-if="$slots.dateCell" #dateCell="data">// date-cell Named slot<slot name="dateCell" v-bind="data"></slot>
</template>
</date-table>
</div>// Range mode displays only the dates in the range<div v-else class="el-calendar__body">
<date-table
v-for="(range_, index) in validatedRange"
:key="index"
:date="range_[0]"
:selected-day="realSelectedDay"
:range="range_"
:hide-header="index ! = = 0"
@pick="pickDay"
>
<template v-if="$slots.dateCell" #dateCell="data">
<slot name="dateCell" v-bind="data"></slot>
</template>
</date-table>
</div>
</div>
</template>
Copy the code
2.1.2 script
import { t } from '@element-plus/locale'
import dayjs, { Dayjs } from 'dayjs'
import DateTable from './date-table.vue'
setup(props, ctx) {
// The selected date
const selectedDay = ref(null)
// Generate a dayJS instance with a date pointing to today
// Use the date I wrote the article as an example. Today is 2021/07/19
const now = dayjs()
// Calculate property, currently selected date, note that this is an instance of dayJS
const date: ComputedRef<Dayjs> = computed(() = > {
// No V-model case is passed in
// The default v-model attribute in vue3 is modelValue, and the default event is update:modelValue
if(! props.modelValue) {if (realSelectedDay.value) {
return realSelectedDay.value
} else if (validatedRange.value.length) {
return validatedRange.value[0] [0]}return now
} else {
// Upload the v-model information
return dayjs(props.modelValue)
}
})
// Calculate attributes, based on the selected date, calculate today of last month
// According to the date I wrote the article, it is 2021/06/19
const prevMonthDayjs = computed(() = > {
// Subtract API of dayJS makes month -1
return date.value.subtract(1.'month')})// Calculate the attributes and format them to YYYY-MM according to the selected date
const curMonthDatePrefix = computed(() = > {
return dayjs(date.value).format('YYYY-MM')})// Calculate attributes, based on the selected date, calculate today of last month
// The date I wrote the article is 2021/09/19
const nextMonthDayjs = computed(() = > {
// Use the dayjs add API to make month +1
return date.value.add(1.'month')})// Use i18n to process the current date
const i18nDate = computed(() = > {
const pickedMonth = `el.datepicker.month${date.value.format('M')}`
// T method is an internalized method of Elment Plus, compatible with VUE-I18N
return `${date.value.year()} ${t('el.datepicker.year')} ${t(pickedMonth)}`
})
// The actual selected date, through the get set mode set calculation properties, when the access to trigger get method, when the change of the set method trigger
const realSelectedDay = computed({
get() {
if(! props.modelValue)return selectedDay.value
return date.value
},
set(val: Dayjs) {
selectedDay.value = val
const result = val.toDate()
ctx.emit('input', result)
ctx.emit('update:modelValue', result)
},
})
// In range mode, validatedRange will be a two-dimensional array
const validatedRange = computed(() = > {
if(! props.range)return []
// Pass the start and end dates in the props. Range property to dayJS to generate two dayJS instances
const rangeArrDayjs = props.range.map(_= > dayjs(_))
// Deconstruct the assignment and get the dayJS instances of the start and end dates
const [startDayjs, endDayjs] = rangeArrDayjs
// The start date is greater than the end date
if (startDayjs.isAfter(endDayjs)) {
console.warn(
'[ElementCalendar]end time should be greater than start time'.)return[]}// Start date and end date are in the same month
// Use the isSame API of DayJS to determine
if (startDayjs.isSame(endDayjs, 'month')) {
// Returns a two-dimensional array containing one element
return [[
If 2021/07/19 is Monday, then the first day of the week is 2021/07/18
startDayjs.startOf('week'),
// The end of the week of the end date is a Saturday. If today is Monday on 2021/07/19, the end of the week is Saturday on 2021/07/24
endDayjs.endOf('week'),]]}else {
// If the interval between the start date and the end date is more than 1 month, an error message is displayed
if (startDayjs.add(1.'month').month() ! == endDayjs.month()) {console.warn(
'[ElementCalendar]start time and end time interval must not exceed two months'.)return[]}// The first day of the month in which the end date occurs
const endMonthFirstDay = endDayjs.startOf('month')
// [end of month] the first day of the week
const endMonthFirstWeekDay = endMonthFirstDay.startOf('week')
let endMonthStart = endMonthFirstDay
// The first day of the end month and the first day of the end month are not in the same month
// This indicates that the Monday is partly last month
if(! endMonthFirstDay.isSame(endMonthFirstWeekDay,'month')) {
endMonthStart = endMonthFirstDay.endOf('week').add(1.'day')}return [
// The first element is the date range of the starting month
[
startDayjs.startOf('week'),
startDayjs.endOf('month')],// The second element is the date range for the starting month
[
endMonthStart,
endDayjs.endOf('week'),],]}})// Switch months
const selectDate = type= > {
let day: Dayjs
if (type= = ='prev-month') {
// Last month today
day = prevMonthDayjs.value
} else if (type= = ='next-month') {
// This day next month
day = nextMonthDayjs.value
} else {
/ / today
day = now
}
// Click on the same date as the current selection, no processing
if (day.isSame(date.value, 'day')) return
pickDay(day)
}
const pickDay = (day: Dayjs) = > {
realSelectedDay.value = day
}
return {
selectedDay,
curMonthDatePrefix,
i18nDate,
realSelectedDay,
date,
validatedRange,
pickDay,
selectDate,
t,
}
},
Copy the code
2.2 Date-table component code
2.2.1 the template
<template>// Use native table tables<table
:class="{ 'el-calendar-table': true, 'is-range': isInRange }"
cellspacing="0"
cellpadding="0"
>
<thead v-if=! "" hideHeader">// Table header: Sunday ~ Saturday<th v-for="day in weekDays" :key="day">{{ day }}</th>
</thead>
<tbody>/ / table rows<tr
v-for="(row, index) in rows"
:key="index"
:class="{ 'el-calendar-table__row': true, 'el-calendar-table__row--hide-border': index === 0 && hideHeader }"
>
// column
<td
v-for="(cell, key) in row"
:key="key"
:class="getCellClass(cell)"
@click="pickDay(cell)"
>
<div class="el-calendar-day">
<slot
name="dateCell"
:data="getSlotData(cell)"
>
<span>{{ cell.text }}</span>
</slot>
</div>
</td>
</tr>
</tbody>
</table>
</template>
Copy the code
2.2.2 script
// Show only part of the core code
setup(props, ctx) {
// Get the text of the week in the current locale, for example: Sunday-Monday-Tuesday-Wednesday-Thursday-Friday-Saturday
const WEEK_DAYS = ref(dayjs().localeData().weekdaysShort())
const now = dayjs()
// Get the start day of the week in the current language. For example, in China, 1 is Monday
const firstDayOfWeek = (now as any).$locale().weekStart || 0
[Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
const weekDays = computed(() = > {
const start = firstDayOfWeek
if (start === 0) {
return WEEK_DAYS.value
} else {
return WEEK_DAYS.value
.slice(start)
.concat(WEEK_DAYS.value.slice(0, start))
}
})
// Divide a one-dimensional array into a two-dimensional array. The subarray length is 7 bits, corresponding to the days of the week
const toNestedArr = days= > {
return rangeArr(days.length / 7).map((_, index) = > {
const start = index * 7
return days.slice(start, start + 7)})}// Calculate attributes to generate table rows
const rows = computed(() = > {
let days = []
/ / range model
if (isInRange.value) {
// Get the start date of range
const [start, end] = props.range
// Generates an array for the current month
const currentMonthRange = rangeArr(
end.date() - start.date() + 1,
).map((_, index) = > ({
text: start.date() + index,
type: 'current',}))let remaining = currentMonthRange.length % 7
remaining = remaining === 0 ? 0 : 7 - remaining
// Generate an array for the next month
const nextMonthRange = rangeArr(remaining).map((_, index) = > ({
text: index + 1.type: 'next',}))// Concatenate an array
days = currentMonthRange.concat(nextMonthRange)
} else {
// Non-range mode
// The first day of the month in which the currently selected date is located
const firstDay = props.date.startOf('month').day() || 7
// Last month's date array
const prevMonthDays = getPrevMonthLastDays(
props.date,
firstDay - firstDayOfWeek,
).map(day= > ({
text: day,
type: 'prev',}))// An array of dates for the current month
const currentMonthDays = getMonthDays(props.date).map(day= > ({
text: day,
type: 'current',
}))
days = [...prevMonthDays, ...currentMonthDays]
// Next month's date array
const nextMonthDays = rangeArr(42 - days.length).map((_, index) = > ({
text: index + 1.type: 'next',}))// Concatenate an array
// In non-range mode, display data for a fixed 6 weeks per month, 6*7=42 dates. Because the maximum number of days in a month is 31, it can be covered in a maximum of 6 weeks
days = days.concat(nextMonthDays)
}
// Convert a one-dimensional array to a two-dimensional array
return toNestedArr(days)
})
// Format the date. The result of formatting is YYYY-MM-DD
const getFormattedDate = (day, type) :Dayjs= > {
let result
if (type= = ='prev') {
result = props.date.startOf('month').subtract(1.'month').date(day)
} else if (type= = ='next') {
result = props.date.startOf('month').add(1.'month').date(day)
} else {
result = props.date.date(day)
}
return result
}
// Set cell class according to the date type
const getCellClass = ({ text, type }) = > {
const classes = [type]
if (type= = ='current') {
const date_ = getFormattedDate(text, type)
if (date_.isSame(props.selectedDay, 'day')) {
classes.push('is-selected')}if (date_.isSame(now, 'day')) {
classes.push('is-today')}}return classes
}
const pickDay = ({ text, type }) = > {
const date = getFormattedDate(text, type)
ctx.emit('pick', date)
}
// Provide data to the slot
const getSlotData = ({ text, type }) = > {
const day = getFormattedDate(text, type)
return {
isSelected: day.isSame(props.selectedDay),
type: `The ${type}-month`.day: day.format('YYYY-MM-DD'),
date: day.toDate(),
}
}
// Check whether it is range mode
const isInRange = computed(() = > {
return props.range && props.range.length
})
return {
isInRange,
weekDays,
rows,
getCellClass,
pickDay,
getSlotData,
}
}
Copy the code
2.3 summarize
Element plus
The use ofdayjs
Date processing library, the library is small in size, rich in functions;- The calendar component uses table internally to generate a date table for display, which can be displayed for 6 weeks and completely cover the date of a month.
- Vue is a data-driven view that focuses on managing data.