mirror of
https://github.com/element-plus/element-plus.git
synced 2025-02-23 11:59:34 +08:00
refactor(components): [calendar] (#10158)
* Extract calendar common logic to dedicated file. * Reorganize code for better readability.
This commit is contained in:
parent
715384bf4e
commit
0a73b244cb
@ -6,7 +6,6 @@ import {
|
|||||||
} from '@element-plus/utils'
|
} from '@element-plus/utils'
|
||||||
import { INPUT_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
import { INPUT_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||||
import type { ExtractPropTypes } from 'vue'
|
import type { ExtractPropTypes } from 'vue'
|
||||||
import type Calendar from './calendar.vue'
|
|
||||||
|
|
||||||
export type CalendarDateType =
|
export type CalendarDateType =
|
||||||
| 'prev-month'
|
| 'prev-month'
|
||||||
@ -34,5 +33,3 @@ export const calendarEmits = {
|
|||||||
[INPUT_EVENT]: (value: Date) => isDate(value),
|
[INPUT_EVENT]: (value: Date) => isDate(value),
|
||||||
}
|
}
|
||||||
export type CalendarEmits = typeof calendarEmits
|
export type CalendarEmits = typeof calendarEmits
|
||||||
|
|
||||||
export type CalendarInstance = InstanceType<typeof Calendar>
|
|
||||||
|
@ -52,18 +52,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useSlots } from 'vue'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { ElButton, ElButtonGroup } from '@element-plus/components/button'
|
import { ElButton, ElButtonGroup } from '@element-plus/components/button'
|
||||||
import { useDeprecated, useLocale, useNamespace } from '@element-plus/hooks'
|
import { useNamespace } from '@element-plus/hooks'
|
||||||
import { debugWarn } from '@element-plus/utils'
|
|
||||||
import { INPUT_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
|
||||||
import DateTable from './date-table.vue'
|
import DateTable from './date-table.vue'
|
||||||
|
import { useCalendar } from './use-calendar'
|
||||||
import { calendarEmits, calendarProps } from './calendar'
|
import { calendarEmits, calendarProps } from './calendar'
|
||||||
|
|
||||||
import type { CalendarDateType } from './calendar'
|
const ns = useNamespace('calendar')
|
||||||
import type { ComputedRef } from 'vue'
|
|
||||||
import type { Dayjs } from 'dayjs'
|
|
||||||
|
|
||||||
const COMPONENT_NAME = 'ElCalendar'
|
const COMPONENT_NAME = 'ElCalendar'
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -73,182 +69,16 @@ defineOptions({
|
|||||||
const props = defineProps(calendarProps)
|
const props = defineProps(calendarProps)
|
||||||
const emit = defineEmits(calendarEmits)
|
const emit = defineEmits(calendarEmits)
|
||||||
|
|
||||||
const solts = useSlots()
|
const {
|
||||||
const ns = useNamespace('calendar')
|
calculateValidatedDateRange,
|
||||||
const { t, lang } = useLocale()
|
date,
|
||||||
|
pickDay,
|
||||||
const selectedDay = ref<Dayjs>()
|
realSelectedDay,
|
||||||
const now = dayjs().locale(lang.value)
|
selectDate,
|
||||||
|
t,
|
||||||
const realSelectedDay = computed<Dayjs | undefined>({
|
i18nDate,
|
||||||
get() {
|
validatedRange,
|
||||||
if (!props.modelValue) return selectedDay.value
|
} = useCalendar(props, emit, COMPONENT_NAME)
|
||||||
return date.value
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
if (!val) return
|
|
||||||
selectedDay.value = val
|
|
||||||
const result = val.toDate()
|
|
||||||
|
|
||||||
emit(INPUT_EVENT, result)
|
|
||||||
emit(UPDATE_MODEL_EVENT, result)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// if range is valid, we get a two-digit array
|
|
||||||
const validatedRange = computed(() => {
|
|
||||||
if (!props.range) return []
|
|
||||||
const rangeArrDayjs = props.range.map((_) => dayjs(_).locale(lang.value))
|
|
||||||
const [startDayjs, endDayjs] = rangeArrDayjs
|
|
||||||
if (startDayjs.isAfter(endDayjs)) {
|
|
||||||
debugWarn(COMPONENT_NAME, 'end time should be greater than start time')
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
if (startDayjs.isSame(endDayjs, 'month')) {
|
|
||||||
// same month
|
|
||||||
return calculateValidatedDateRange(startDayjs, endDayjs)
|
|
||||||
} else {
|
|
||||||
// two months
|
|
||||||
if (startDayjs.add(1, 'month').month() !== endDayjs.month()) {
|
|
||||||
debugWarn(
|
|
||||||
COMPONENT_NAME,
|
|
||||||
'start time and end time interval must not exceed two months'
|
|
||||||
)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return calculateValidatedDateRange(startDayjs, endDayjs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const date: ComputedRef<Dayjs> = computed(() => {
|
|
||||||
if (!props.modelValue) {
|
|
||||||
if (realSelectedDay.value) {
|
|
||||||
return realSelectedDay.value
|
|
||||||
} else if (validatedRange.value.length) {
|
|
||||||
return validatedRange.value[0][0]
|
|
||||||
}
|
|
||||||
return now
|
|
||||||
} else {
|
|
||||||
return dayjs(props.modelValue).locale(lang.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const prevMonthDayjs = computed(() => date.value.subtract(1, 'month').date(1))
|
|
||||||
const nextMonthDayjs = computed(() => date.value.add(1, 'month').date(1))
|
|
||||||
const prevYearDayjs = computed(() => date.value.subtract(1, 'year').date(1))
|
|
||||||
const nextYearDayjs = computed(() => date.value.add(1, 'year').date(1))
|
|
||||||
|
|
||||||
const i18nDate = computed(() => {
|
|
||||||
const pickedMonth = `el.datepicker.month${date.value.format('M')}`
|
|
||||||
return `${date.value.year()} ${t('el.datepicker.year')} ${t(pickedMonth)}`
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://github.com/element-plus/element-plus/issues/3155
|
|
||||||
// Calculate the validate date range according to the start and end dates
|
|
||||||
const calculateValidatedDateRange = (
|
|
||||||
startDayjs: Dayjs,
|
|
||||||
endDayjs: Dayjs
|
|
||||||
): [Dayjs, Dayjs][] => {
|
|
||||||
const firstDay = startDayjs.startOf('week')
|
|
||||||
const lastDay = endDayjs.endOf('week')
|
|
||||||
const firstMonth = firstDay.get('month')
|
|
||||||
const lastMonth = lastDay.get('month')
|
|
||||||
|
|
||||||
// Current mouth
|
|
||||||
if (firstMonth === lastMonth) {
|
|
||||||
return [[firstDay, lastDay]]
|
|
||||||
}
|
|
||||||
// Two adjacent months
|
|
||||||
else if (firstMonth + 1 === lastMonth) {
|
|
||||||
const firstMonthLastDay = firstDay.endOf('month')
|
|
||||||
const lastMonthFirstDay = lastDay.startOf('month')
|
|
||||||
|
|
||||||
// Whether the last day of the first month and the first day of the last month is in the same week
|
|
||||||
const isSameWeek = firstMonthLastDay.isSame(lastMonthFirstDay, 'week')
|
|
||||||
const lastMonthStartDay = isSameWeek
|
|
||||||
? lastMonthFirstDay.add(1, 'week')
|
|
||||||
: lastMonthFirstDay
|
|
||||||
|
|
||||||
return [
|
|
||||||
[firstDay, firstMonthLastDay],
|
|
||||||
[lastMonthStartDay.startOf('week'), lastDay],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
// Three consecutive months (compatible: 2021-01-30 to 2021-02-28)
|
|
||||||
else if (
|
|
||||||
firstMonth + 2 === lastMonth ||
|
|
||||||
(firstMonth + 1) % 11 === lastMonth
|
|
||||||
) {
|
|
||||||
const firstMonthLastDay = firstDay.endOf('month')
|
|
||||||
const secondMonthFirstDay = firstDay.add(1, 'month').startOf('month')
|
|
||||||
|
|
||||||
// Whether the last day of the first month and the second month is in the same week
|
|
||||||
const secondMonthStartDay = firstMonthLastDay.isSame(
|
|
||||||
secondMonthFirstDay,
|
|
||||||
'week'
|
|
||||||
)
|
|
||||||
? secondMonthFirstDay.add(1, 'week')
|
|
||||||
: secondMonthFirstDay
|
|
||||||
|
|
||||||
const secondMonthLastDay = secondMonthStartDay.endOf('month')
|
|
||||||
const lastMonthFirstDay = lastDay.startOf('month')
|
|
||||||
|
|
||||||
// Whether the last day of the second month and the last day of the last month is in the same week
|
|
||||||
const lastMonthStartDay = secondMonthLastDay.isSame(
|
|
||||||
lastMonthFirstDay,
|
|
||||||
'week'
|
|
||||||
)
|
|
||||||
? lastMonthFirstDay.add(1, 'week')
|
|
||||||
: lastMonthFirstDay
|
|
||||||
|
|
||||||
return [
|
|
||||||
[firstDay, firstMonthLastDay],
|
|
||||||
[secondMonthStartDay.startOf('week'), secondMonthLastDay],
|
|
||||||
[lastMonthStartDay.startOf('week'), lastDay],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
// Other cases
|
|
||||||
else {
|
|
||||||
debugWarn(
|
|
||||||
COMPONENT_NAME,
|
|
||||||
'start time and end time interval must not exceed two months'
|
|
||||||
)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pickDay = (day: Dayjs) => {
|
|
||||||
realSelectedDay.value = day
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectDate = (type: CalendarDateType) => {
|
|
||||||
let day: Dayjs
|
|
||||||
if (type === 'prev-month') {
|
|
||||||
day = prevMonthDayjs.value
|
|
||||||
} else if (type === 'next-month') {
|
|
||||||
day = nextMonthDayjs.value
|
|
||||||
} else if (type === 'prev-year') {
|
|
||||||
day = prevYearDayjs.value
|
|
||||||
} else if (type === 'next-year') {
|
|
||||||
day = nextYearDayjs.value
|
|
||||||
} else {
|
|
||||||
day = now
|
|
||||||
}
|
|
||||||
|
|
||||||
if (day.isSame(date.value, 'day')) return
|
|
||||||
pickDay(day)
|
|
||||||
}
|
|
||||||
|
|
||||||
useDeprecated(
|
|
||||||
{
|
|
||||||
from: '"dateCell"',
|
|
||||||
replacement: '"date-cell"',
|
|
||||||
scope: 'ElCalendar',
|
|
||||||
version: '2.3.0',
|
|
||||||
ref: 'https://element-plus.org/en-US/component/calendar.html#slots',
|
|
||||||
type: 'Slot',
|
|
||||||
},
|
|
||||||
computed(() => !!solts.dateCell)
|
|
||||||
)
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
/** @description currently selected date */
|
/** @description currently selected date */
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import type DateTable from './date-table.vue'
|
import type DateTable from './date-table.vue'
|
||||||
|
import type Calendar from './calendar.vue'
|
||||||
|
|
||||||
export type DateTableInstance = InstanceType<typeof DateTable>
|
export type DateTableInstance = InstanceType<typeof DateTable>
|
||||||
export type CalendarDateTableInstance = DateTableInstance
|
export type CalendarDateTableInstance = DateTableInstance
|
||||||
|
export type CalendarInstance = InstanceType<typeof Calendar>
|
||||||
|
200
packages/components/calendar/src/use-calendar.ts
Normal file
200
packages/components/calendar/src/use-calendar.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import { computed, ref, useSlots } from 'vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { useDeprecated, useLocale } from '@element-plus/hooks'
|
||||||
|
import { debugWarn } from '@element-plus/utils'
|
||||||
|
import { INPUT_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
|
||||||
|
|
||||||
|
import type { ComputedRef, SetupContext } from 'vue'
|
||||||
|
import type { Dayjs } from 'dayjs'
|
||||||
|
import type { CalendarDateType, CalendarEmits, CalendarProps } from './calendar'
|
||||||
|
|
||||||
|
export const useCalendar = (
|
||||||
|
props: CalendarProps,
|
||||||
|
emit: SetupContext<CalendarEmits>['emit'],
|
||||||
|
componentName: string
|
||||||
|
) => {
|
||||||
|
const solts = useSlots()
|
||||||
|
const { t, lang } = useLocale()
|
||||||
|
|
||||||
|
const selectedDay = ref<Dayjs>()
|
||||||
|
const now = dayjs().locale(lang.value)
|
||||||
|
|
||||||
|
const realSelectedDay = computed<Dayjs | undefined>({
|
||||||
|
get() {
|
||||||
|
if (!props.modelValue) return selectedDay.value
|
||||||
|
return date.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
if (!val) return
|
||||||
|
selectedDay.value = val
|
||||||
|
const result = val.toDate()
|
||||||
|
|
||||||
|
emit(INPUT_EVENT, result)
|
||||||
|
emit(UPDATE_MODEL_EVENT, result)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// if range is valid, we get a two-digit array
|
||||||
|
const validatedRange = computed(() => {
|
||||||
|
if (!props.range) return []
|
||||||
|
const rangeArrDayjs = props.range.map((_) => dayjs(_).locale(lang.value))
|
||||||
|
const [startDayjs, endDayjs] = rangeArrDayjs
|
||||||
|
if (startDayjs.isAfter(endDayjs)) {
|
||||||
|
debugWarn(componentName, 'end time should be greater than start time')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (startDayjs.isSame(endDayjs, 'month')) {
|
||||||
|
// same month
|
||||||
|
return calculateValidatedDateRange(startDayjs, endDayjs)
|
||||||
|
} else {
|
||||||
|
// two months
|
||||||
|
if (startDayjs.add(1, 'month').month() !== endDayjs.month()) {
|
||||||
|
debugWarn(
|
||||||
|
componentName,
|
||||||
|
'start time and end time interval must not exceed two months'
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return calculateValidatedDateRange(startDayjs, endDayjs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const date: ComputedRef<Dayjs> = computed(() => {
|
||||||
|
if (!props.modelValue) {
|
||||||
|
if (realSelectedDay.value) {
|
||||||
|
return realSelectedDay.value
|
||||||
|
} else if (validatedRange.value.length) {
|
||||||
|
return validatedRange.value[0][0]
|
||||||
|
}
|
||||||
|
return now
|
||||||
|
} else {
|
||||||
|
return dayjs(props.modelValue).locale(lang.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const prevMonthDayjs = computed(() => date.value.subtract(1, 'month').date(1))
|
||||||
|
const nextMonthDayjs = computed(() => date.value.add(1, 'month').date(1))
|
||||||
|
const prevYearDayjs = computed(() => date.value.subtract(1, 'year').date(1))
|
||||||
|
const nextYearDayjs = computed(() => date.value.add(1, 'year').date(1))
|
||||||
|
|
||||||
|
const i18nDate = computed(() => {
|
||||||
|
const pickedMonth = `el.datepicker.month${date.value.format('M')}`
|
||||||
|
return `${date.value.year()} ${t('el.datepicker.year')} ${t(pickedMonth)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://github.com/element-plus/element-plus/issues/3155
|
||||||
|
// Calculate the validate date range according to the start and end dates
|
||||||
|
const calculateValidatedDateRange = (
|
||||||
|
startDayjs: Dayjs,
|
||||||
|
endDayjs: Dayjs
|
||||||
|
): [Dayjs, Dayjs][] => {
|
||||||
|
const firstDay = startDayjs.startOf('week')
|
||||||
|
const lastDay = endDayjs.endOf('week')
|
||||||
|
const firstMonth = firstDay.get('month')
|
||||||
|
const lastMonth = lastDay.get('month')
|
||||||
|
|
||||||
|
// Current mouth
|
||||||
|
if (firstMonth === lastMonth) {
|
||||||
|
return [[firstDay, lastDay]]
|
||||||
|
}
|
||||||
|
// Two adjacent months
|
||||||
|
else if (firstMonth + 1 === lastMonth) {
|
||||||
|
const firstMonthLastDay = firstDay.endOf('month')
|
||||||
|
const lastMonthFirstDay = lastDay.startOf('month')
|
||||||
|
|
||||||
|
// Whether the last day of the first month and the first day of the last month is in the same week
|
||||||
|
const isSameWeek = firstMonthLastDay.isSame(lastMonthFirstDay, 'week')
|
||||||
|
const lastMonthStartDay = isSameWeek
|
||||||
|
? lastMonthFirstDay.add(1, 'week')
|
||||||
|
: lastMonthFirstDay
|
||||||
|
|
||||||
|
return [
|
||||||
|
[firstDay, firstMonthLastDay],
|
||||||
|
[lastMonthStartDay.startOf('week'), lastDay],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// Three consecutive months (compatible: 2021-01-30 to 2021-02-28)
|
||||||
|
else if (
|
||||||
|
firstMonth + 2 === lastMonth ||
|
||||||
|
(firstMonth + 1) % 11 === lastMonth
|
||||||
|
) {
|
||||||
|
const firstMonthLastDay = firstDay.endOf('month')
|
||||||
|
const secondMonthFirstDay = firstDay.add(1, 'month').startOf('month')
|
||||||
|
|
||||||
|
// Whether the last day of the first month and the second month is in the same week
|
||||||
|
const secondMonthStartDay = firstMonthLastDay.isSame(
|
||||||
|
secondMonthFirstDay,
|
||||||
|
'week'
|
||||||
|
)
|
||||||
|
? secondMonthFirstDay.add(1, 'week')
|
||||||
|
: secondMonthFirstDay
|
||||||
|
|
||||||
|
const secondMonthLastDay = secondMonthStartDay.endOf('month')
|
||||||
|
const lastMonthFirstDay = lastDay.startOf('month')
|
||||||
|
|
||||||
|
// Whether the last day of the second month and the last day of the last month is in the same week
|
||||||
|
const lastMonthStartDay = secondMonthLastDay.isSame(
|
||||||
|
lastMonthFirstDay,
|
||||||
|
'week'
|
||||||
|
)
|
||||||
|
? lastMonthFirstDay.add(1, 'week')
|
||||||
|
: lastMonthFirstDay
|
||||||
|
|
||||||
|
return [
|
||||||
|
[firstDay, firstMonthLastDay],
|
||||||
|
[secondMonthStartDay.startOf('week'), secondMonthLastDay],
|
||||||
|
[lastMonthStartDay.startOf('week'), lastDay],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// Other cases
|
||||||
|
else {
|
||||||
|
debugWarn(
|
||||||
|
componentName,
|
||||||
|
'start time and end time interval must not exceed two months'
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickDay = (day: Dayjs) => {
|
||||||
|
realSelectedDay.value = day
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectDate = (type: CalendarDateType) => {
|
||||||
|
const dateMap: Record<CalendarDateType, Dayjs> = {
|
||||||
|
'prev-month': prevMonthDayjs.value,
|
||||||
|
'next-month': nextMonthDayjs.value,
|
||||||
|
'prev-year': prevYearDayjs.value,
|
||||||
|
'next-year': nextYearDayjs.value,
|
||||||
|
today: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
const day = dateMap[type]
|
||||||
|
|
||||||
|
if (!day.isSame(date.value, 'day')) {
|
||||||
|
pickDay(day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useDeprecated(
|
||||||
|
{
|
||||||
|
from: '"dateCell"',
|
||||||
|
replacement: '"date-cell"',
|
||||||
|
scope: 'ElCalendar',
|
||||||
|
version: '2.3.0',
|
||||||
|
ref: 'https://element-plus.org/en-US/component/calendar.html#slots',
|
||||||
|
type: 'Slot',
|
||||||
|
},
|
||||||
|
computed(() => !!solts.dateCell)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
calculateValidatedDateRange,
|
||||||
|
date,
|
||||||
|
realSelectedDay,
|
||||||
|
pickDay,
|
||||||
|
selectDate,
|
||||||
|
validatedRange,
|
||||||
|
t,
|
||||||
|
i18nDate,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user