mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-21 01:02:59 +08:00
refactor(components): refactor calendar (#4325)
Co-authored-by: Kevin <sxzz@sxzz.moe>
This commit is contained in:
parent
42430106f3
commit
8600be1cd1
@ -1,6 +1,6 @@
|
||||
import { nextTick } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Calendar from '../src/index.vue'
|
||||
import Calendar from '../src/calendar.vue'
|
||||
|
||||
const _mount = (template: string, data?, otherObj?) =>
|
||||
mount({
|
||||
|
@ -1,12 +1,7 @@
|
||||
import Calendar from './src/index.vue'
|
||||
import type { App } from 'vue'
|
||||
import type { SFCWithInstall } from '@element-plus/utils/types'
|
||||
import { withInstall } from '@element-plus/utils/with-install'
|
||||
import Calendar from './src/calendar.vue'
|
||||
|
||||
Calendar.install = (app: App): void => {
|
||||
app.component(Calendar.name, Calendar)
|
||||
}
|
||||
export const ElCalendar = withInstall(Calendar)
|
||||
export default ElCalendar
|
||||
|
||||
const _Calendar = Calendar as SFCWithInstall<typeof Calendar>
|
||||
|
||||
export default _Calendar
|
||||
export const ElCalendar = _Calendar
|
||||
export * from './src/calendar'
|
||||
|
23
packages/components/calendar/src/calendar.ts
Normal file
23
packages/components/calendar/src/calendar.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { buildProps, definePropType } from '@element-plus/utils/props'
|
||||
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
|
||||
export const calendarProps = buildProps({
|
||||
modelValue: {
|
||||
type: Date,
|
||||
},
|
||||
range: {
|
||||
type: definePropType<[Date, Date]>(Array),
|
||||
validator: (range: unknown): range is [Date, Date] =>
|
||||
Array.isArray(range) &&
|
||||
range.length === 2 &&
|
||||
range.every((item) => item instanceof Date),
|
||||
},
|
||||
} as const)
|
||||
export type CalendarProps = ExtractPropTypes<typeof calendarProps>
|
||||
|
||||
export const calendarEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (value: Date) => value instanceof Date,
|
||||
input: (value: Date) => value instanceof Date,
|
||||
}
|
||||
export type CalendarEmits = typeof calendarEmits
|
@ -49,13 +49,13 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, defineComponent } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import ElButton from '@element-plus/components/button'
|
||||
import { ElButton, ElButtonGroup } from '@element-plus/components/button'
|
||||
import { useLocale } from '@element-plus/hooks'
|
||||
import { debugWarn } from '@element-plus/utils/error'
|
||||
import DateTable from './date-table.vue'
|
||||
import type { PropType, ComputedRef } from 'vue'
|
||||
import { calendarProps, calendarEmits } from './calendar'
|
||||
|
||||
import type { ComputedRef } from 'vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
type DateType =
|
||||
@ -65,7 +65,6 @@ type DateType =
|
||||
| 'next-year'
|
||||
| 'today'
|
||||
|
||||
const { ButtonGroup: ElButtonGroup } = ElButton
|
||||
export default defineComponent({
|
||||
name: 'ElCalendar',
|
||||
|
||||
@ -75,27 +74,12 @@ export default defineComponent({
|
||||
ElButtonGroup,
|
||||
},
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Date,
|
||||
},
|
||||
range: {
|
||||
type: Array as PropType<Array<Date>>,
|
||||
validator: (range: Date): boolean => {
|
||||
if (Array.isArray(range)) {
|
||||
return (
|
||||
range.length === 2 && range.every((item) => item instanceof Date)
|
||||
)
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
},
|
||||
props: calendarProps,
|
||||
emits: calendarEmits,
|
||||
|
||||
emits: ['input', 'update:modelValue'],
|
||||
setup(props, ctx) {
|
||||
setup(props, { emit }) {
|
||||
const { t, lang } = useLocale()
|
||||
const selectedDay = ref(null)
|
||||
const selectedDay = ref<Dayjs>()
|
||||
const now = dayjs().locale(lang.value)
|
||||
|
||||
const prevMonthDayjs = computed(() => {
|
||||
@ -122,17 +106,18 @@ export default defineComponent({
|
||||
return `${date.value.year()} ${t('el.datepicker.year')} ${t(pickedMonth)}`
|
||||
})
|
||||
|
||||
const realSelectedDay = computed({
|
||||
const realSelectedDay = computed<Dayjs | undefined>({
|
||||
get() {
|
||||
if (!props.modelValue) return selectedDay.value
|
||||
return date.value
|
||||
},
|
||||
set(val: Dayjs) {
|
||||
set(val) {
|
||||
if (!val) return
|
||||
selectedDay.value = val
|
||||
const result = val.toDate()
|
||||
|
||||
ctx.emit('input', result)
|
||||
ctx.emit('update:modelValue', result)
|
||||
emit('input', result)
|
||||
emit('update:modelValue', result)
|
||||
},
|
||||
})
|
||||
|
||||
@ -152,9 +137,9 @@ export default defineComponent({
|
||||
// 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.Dayjs,
|
||||
endDayjs: dayjs.Dayjs
|
||||
) => {
|
||||
startDayjs: Dayjs,
|
||||
endDayjs: Dayjs
|
||||
): [Dayjs, Dayjs][] => {
|
||||
const firstDay = startDayjs.startOf('week')
|
||||
const lastDay = endDayjs.endOf('week')
|
||||
const firstMonth = firstDay.get('month')
|
26
packages/components/calendar/src/date-table.ts
Normal file
26
packages/components/calendar/src/date-table.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { buildProps, definePropType } from '@element-plus/utils/props'
|
||||
import { isObject } from '@element-plus/utils/util'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
export const dateTableProps = buildProps({
|
||||
selectedDay: {
|
||||
type: definePropType<Dayjs>(Object),
|
||||
},
|
||||
range: {
|
||||
type: definePropType<[Dayjs, Dayjs]>(Array),
|
||||
},
|
||||
date: {
|
||||
type: definePropType<Dayjs>(Object),
|
||||
required: true,
|
||||
},
|
||||
hideHeader: {
|
||||
type: Boolean,
|
||||
},
|
||||
} as const)
|
||||
export type DateTableProps = ExtractPropTypes<typeof dateTableProps>
|
||||
|
||||
export const dateTableEmits = {
|
||||
pick: (value: Dayjs) => isObject(value),
|
||||
}
|
||||
export type DateTableEmits = typeof dateTableEmits
|
@ -10,6 +10,7 @@
|
||||
<thead v-if="!hideHeader">
|
||||
<th v-for="day in weekDays" :key="day">{{ day }}</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(row, index) in rows"
|
||||
@ -23,7 +24,7 @@
|
||||
v-for="(cell, key) in row"
|
||||
:key="key"
|
||||
:class="getCellClass(cell)"
|
||||
@click="pickDay(cell)"
|
||||
@click="handlePickDay(cell)"
|
||||
>
|
||||
<div class="el-calendar-day">
|
||||
<slot name="dateCell" :data="getSlotData(cell)">
|
||||
@ -42,13 +43,21 @@ import dayjs from 'dayjs'
|
||||
import localeData from 'dayjs/plugin/localeData'
|
||||
import { useLocale } from '@element-plus/hooks'
|
||||
import { rangeArr } from '@element-plus/components/time-picker'
|
||||
import { dateTableProps, dateTableEmits } from './date-table'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type { PropType } from 'vue'
|
||||
dayjs.extend(localeData)
|
||||
|
||||
export const getPrevMonthLastDays = (date: Dayjs, amount) => {
|
||||
type CellType = 'next' | 'prev' | 'current'
|
||||
interface Cell {
|
||||
text: number
|
||||
type: CellType
|
||||
}
|
||||
|
||||
const WEEK_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'] as const
|
||||
|
||||
export const getPrevMonthLastDays = (date: Dayjs, count: number) => {
|
||||
const lastDay = date.subtract(1, 'month').endOf('month').date()
|
||||
return rangeArr(amount).map((_, index) => lastDay - (amount - index - 1))
|
||||
return rangeArr(count).map((_, index) => lastDay - (count - index - 1))
|
||||
}
|
||||
|
||||
export const getMonthDays = (date: Dayjs) => {
|
||||
@ -56,117 +65,65 @@ export const getMonthDays = (date: Dayjs) => {
|
||||
return rangeArr(days).map((_, index) => index + 1)
|
||||
}
|
||||
|
||||
const toNestedArr = (days: Cell[]) =>
|
||||
rangeArr(days.length / 7).map((index) => {
|
||||
const start = index * 7
|
||||
return days.slice(start, start + 7)
|
||||
})
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
selectedDay: {
|
||||
type: Object as PropType<Dayjs>,
|
||||
},
|
||||
range: {
|
||||
type: Array as PropType<Array<Dayjs>>,
|
||||
},
|
||||
date: {
|
||||
type: Object as PropType<Dayjs>,
|
||||
},
|
||||
hideHeader: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
emits: ['pick'],
|
||||
setup(props, ctx) {
|
||||
props: dateTableProps,
|
||||
emits: dateTableEmits,
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { t, lang } = useLocale()
|
||||
const WEEK_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
|
||||
|
||||
const now = dayjs().locale(lang.value)
|
||||
// todo better way to get Day.js locale object
|
||||
const firstDayOfWeek = (now as any).$locale().weekStart || 0
|
||||
const toNestedArr = (days) => {
|
||||
return rangeArr(days.length / 7).map((_, index) => {
|
||||
const start = index * 7
|
||||
return days.slice(start, start + 7)
|
||||
})
|
||||
}
|
||||
const firstDayOfWeek: number = (now as any).$locale().weekStart || 0
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
const getSlotData = ({ text, type }) => {
|
||||
const day = getFormattedDate(text, type)
|
||||
return {
|
||||
isSelected: day.isSame(props.selectedDay),
|
||||
type: `${type}-month`,
|
||||
day: day.format('YYYY-MM-DD'),
|
||||
date: day.toDate(),
|
||||
}
|
||||
}
|
||||
|
||||
const isInRange = computed(() => {
|
||||
return props.range && props.range.length
|
||||
})
|
||||
const isInRange = computed(() => !!props.range && !!props.range.length)
|
||||
|
||||
const rows = computed(() => {
|
||||
let days = []
|
||||
let days: Cell[] = []
|
||||
if (isInRange.value) {
|
||||
const [start, end] = props.range
|
||||
const currentMonthRange = rangeArr(end.date() - start.date() + 1).map(
|
||||
(_, index) => ({
|
||||
text: start.date() + index,
|
||||
type: 'current',
|
||||
})
|
||||
)
|
||||
const [start, end] = props.range!
|
||||
const currentMonthRange: Cell[] = 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
|
||||
const nextMonthRange = rangeArr(remaining).map((_, index) => ({
|
||||
const nextMonthRange: Cell[] = rangeArr(remaining).map((_, index) => ({
|
||||
text: index + 1,
|
||||
type: 'next',
|
||||
}))
|
||||
days = currentMonthRange.concat(nextMonthRange)
|
||||
} else {
|
||||
const firstDay = props.date.startOf('month').day() || 7
|
||||
const prevMonthDays = getPrevMonthLastDays(
|
||||
const prevMonthDays: Cell[] = getPrevMonthLastDays(
|
||||
props.date,
|
||||
firstDay - firstDayOfWeek
|
||||
).map((day) => ({
|
||||
text: day,
|
||||
type: 'prev',
|
||||
}))
|
||||
const currentMonthDays = getMonthDays(props.date).map((day) => ({
|
||||
text: day,
|
||||
type: 'current',
|
||||
}))
|
||||
const currentMonthDays: Cell[] = getMonthDays(props.date).map(
|
||||
(day) => ({
|
||||
text: day,
|
||||
type: 'current',
|
||||
})
|
||||
)
|
||||
days = [...prevMonthDays, ...currentMonthDays]
|
||||
const nextMonthDays = rangeArr(42 - days.length).map((_, index) => ({
|
||||
text: index + 1,
|
||||
type: 'next',
|
||||
}))
|
||||
const nextMonthDays: Cell[] = rangeArr(42 - days.length).map(
|
||||
(_, index) => ({
|
||||
text: index + 1,
|
||||
type: 'next',
|
||||
})
|
||||
)
|
||||
days = days.concat(nextMonthDays)
|
||||
}
|
||||
return toNestedArr(days)
|
||||
@ -183,12 +140,52 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
const getFormattedDate = (day: number, type: CellType): Dayjs => {
|
||||
switch (type) {
|
||||
case 'prev':
|
||||
return props.date.startOf('month').subtract(1, 'month').date(day)
|
||||
case 'next':
|
||||
return props.date.startOf('month').add(1, 'month').date(day)
|
||||
case 'current':
|
||||
return props.date.date(day)
|
||||
}
|
||||
}
|
||||
|
||||
const getCellClass = ({ text, type }: Cell) => {
|
||||
const classes: string[] = [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 handlePickDay = ({ text, type }: Cell) => {
|
||||
const date = getFormattedDate(text, type)
|
||||
emit('pick', date)
|
||||
}
|
||||
|
||||
const getSlotData = ({ text, type }: Cell) => {
|
||||
const day = getFormattedDate(text, type)
|
||||
return {
|
||||
isSelected: day.isSame(props.selectedDay),
|
||||
type: `${type}-month`,
|
||||
day: day.format('YYYY-MM-DD'),
|
||||
date: day.toDate(),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isInRange,
|
||||
weekDays,
|
||||
rows,
|
||||
getCellClass,
|
||||
pickDay,
|
||||
handlePickDay,
|
||||
getSlotData,
|
||||
}
|
||||
},
|
||||
|
@ -1,6 +1,4 @@
|
||||
export const rangeArr = (n) => {
|
||||
return Array.from(Array(n).keys())
|
||||
}
|
||||
export const rangeArr = (n: number) => Array.from(Array(n).keys())
|
||||
|
||||
export const extractDateFormat = (format: string) => {
|
||||
return format
|
||||
|
Loading…
Reference in New Issue
Block a user