DatePicker
简介
MDatePicker 是一个国际化日期选择器,基于 @internationalized/date 处理时区与本地化。支持单日期、日期范围、多日期选择,并提供按钮触发、可清空、快捷预设等多种交互形态。
用法
默认按钮触发器展示当前日期,点击打开日历面板后选择并写入 v-model:
<script setup lang="ts">
const value = ref("")
</script>
<template>
<MDatePicker v-model="value" />
</template>
range 日期范围
range 模式维护 start / end 两端日期,numberOfMonths 可同时展示双月日历:
<script setup lang="ts">
const value = ref("")
</script>
<template>
<MDatePicker v-model="value" range :number-of-months="2" />
</template>
numberOfMonths 月份数量
numberOfMonths 控制弹层内并排展示的月份数量,便于跨月选择:
<template>
<MDatePicker :number-of-months="3" />
</template>
clearable 可清空
clearable 在已有值时显示清除入口,点击后重置且不展开日历:
<template>
<MDatePicker clearable />
</template>
presets 快捷预设
presets 设为 default 会按当前模式自动生成「今天」「本周」「本月」等快捷项:
<template>
<MDatePicker range />
</template>
multiple 多日期选择
multiple 模式将多个日期保存在数组中,按钮文案可根据已选数量动态展示:
<template>
<MDatePicker multiple :button-props="{ label: '选择多个日期', color: 'primary' }" />
</template>
buttonProps 触发按钮
buttonProps 透传给触发按钮,可调整 label、color、variant 与 icon:
<template>
<MDatePicker
:button-props="{
label: '选择生日',
color: 'primary',
variant: 'outline',
icon: 'i-lucide-cake'
}"
/>
</template>
示例
继承字段上下文
放入 UFormField 后继承 size 与错误态,触发按钮按表单状态渲染:
<template>
<UFormField label="预约日期" size="xs" error="示例错误态">
<MDatePicker />
</UFormField>
</template>
融入UFieldGroup
与按钮置于 UFieldGroup 时共用尺寸与边框衔接,适合筛选栏中组合快捷操作:
<template>
<UFieldGroup size="xs">
<MDatePicker />
<UButton icon="i-lucide-calendar-check" color="neutral" variant="subtle" />
</UFieldGroup>
</template>
限制可选日期边界
minValue 与 maxValue 会禁用边界外日期,分别约束只能选择未来或过去日期:
<script setup lang="ts">
import type { CalendarDate } from '#movk/types'
const formatter = useDateFormatter()
const futureDate = shallowRef<CalendarDate>()
const pastDate = shallowRef<CalendarDate>()
</script>
<template>
<div class="space-y-4">
<UFormField label="未来日期">
<MDatePicker
v-model="futureDate"
:min-value="formatter.getToday()"
:button-props="{ label: '选择未来日期', class: 'w-full' }"
/>
</UFormField>
<UFormField label="过去日期">
<MDatePicker
v-model="pastDate"
:max-value="formatter.getToday()"
:button-props="{ label: '选择过去日期', class: 'w-full' }"
/>
</UFormField>
</div>
</template>
按规则禁用日期
isDateUnavailable 可按业务规则禁用日期(如示例中的周末):
<script setup lang="ts">
import { CalendarDate } from '#movk/composables/useDateFormatter'
import type { DateValue } from '#movk/types'
const formatter = useDateFormatter()
const date = shallowRef(new CalendarDate(2025, 11, 18))
// 禁用周末
const isDateUnavailable = (date: DateValue) => {
return formatter.isWeekend(date)
}
</script>
<template>
<MDatePicker
v-model="date"
:is-date-unavailable="isDateUnavailable"
:button-props="{ label: '仅工作日', class: 'w-full' }"
/>
</template>
自定义触发按钮文案
labelFormat 接收格式化工具和当前值,可把日期与星期信息组合为按钮 label:
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'
const isoDate = shallowRef(new CalendarDate(2025, 11, 18))
const timestampDate = shallowRef(new CalendarDate(2025, 11, 18))
const customDate = shallowRef(new CalendarDate(2025, 11, 18))
</script>
<template>
<div class="space-y-4">
<UFormField label="ISO 格式">
<MDatePicker v-model="isoDate" label-format="iso" :button-props="{ class: 'w-full' }" />
</UFormField>
<UFormField label="时间戳">
<MDatePicker v-model="timestampDate" label-format="timestamp" :button-props="{ class: 'w-full' }" />
</UFormField>
<UFormField label="自定义">
<MDatePicker
v-model="customDate"
:button-props="{ class: 'w-full' }"
:label-format="(fmt, value) => {
if (!value || !fmt.isDateValue(value)) return '选择日期'
return `${fmt.format(value)} (星期${fmt.getDayOfWeek(value)})`
}"
/>
</UFormField>
</div>
</template>
自定义快捷预设
presets 传入数组可按业务规则返回日期,每项 value 是接收 formatter 的函数:
<script setup lang="ts">
import type { DateValue } from '@movk/nuxt'
const value = shallowRef<DateValue>()
</script>
<template>
<MDatePicker
v-model="value"
placeholder="选择日期"
:presets="[
{ label: '本周一', value: f => f.getStartOfWeek(f.getToday()) as DateValue },
{ label: '本月初', value: f => f.getStartOfMonth(f.getToday()) as DateValue },
{ label: '本年初', value: f => f.getStartOfYear(f.getToday()) as DateValue }
]"
/>
</template>
API
Props
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
id | string | |
name | string | |
placeholder | '选择日期' | string |
buttonProps | ButtonProps | |
popoverProps | PopoverProps<P> | |
labelFormat | 'formatted' | "iso" | "timestamp" | "unix" | "date" | "formatted" | (formatter: { format: (date: DateValue) => string; formatRange: (start: DateValue, end: DateValue, separator?: string) => string; formatArray: (dates: DateValue[], separator?: string) => string; ... 37 more ...; timeZone: string; }, modelValue: CalendarModelValue<...>): string |
valueFormat | 'date-value' | Vv-model 投影输出格式 |
presets | "default" | DatePickerPreset<R, M>[]快捷预设 | |
locale | 'zh-CN' | string语言区域 |
formatOptions | Intl.DateTimeFormatOptions日期格式化选项 | |
timeZone | string时区标识符,默认使用本地时区 | |
type | 'date' | "date" | "month" | "year"The type of picker.
|
nextYearIcon | appConfig.ui.icons.chevronDoubleRight | anyThe icon to use for the next year control. |
nextYear | Omit<ButtonProps, LinkPropsKeys>Configure the next year button.
| |
nextMonthIcon | appConfig.ui.icons.chevronRight | anyThe icon to use for the next month control. |
nextMonth | Omit<ButtonProps, LinkPropsKeys>Configure the next month button.
| |
prevYearIcon | appConfig.ui.icons.chevronDoubleLeft | anyThe icon to use for the previous year control. |
prevYear | Omit<ButtonProps, LinkPropsKeys>Configure the prev year button.
| |
prevMonthIcon | appConfig.ui.icons.chevronLeft | anyThe icon to use for the previous month control. |
prevMonth | Omit<ButtonProps, LinkPropsKeys>Configure the prev month button.
| |
viewControl | true | boolean | Omit<ButtonProps, LinkPropsKeys>Whether to make the heading a button that switches between the day, month and year views.
Has no effect when |
color | 'primary' | "primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral" |
variant | 'solid' | "solid" | "outline" | "soft" | "subtle" |
size | 'md' | "xs" | "sm" | "md" | "lg" | "xl" |
range | RWhether or not a range of dates can be selected | |
multiple | MWhether or not multiple dates can be selected | |
defaultValue | CalendarDefaultValue<R, M> | |
defaultPlaceholder | CalendarDate | CalendarDateTime | ZonedDateTimeThe default placeholder date | |
maximumDays | numberThe maximum number of days that can be selected in a range | |
weekStartsOn | 0 | 1 | 2 | 4 | 5 | 3 | 6The day of the week to start the calendar on | |
weekdayFormat | "narrow" | "short" | "long"The format to use for the weekday strings provided via the weekdays slot prop | |
maxValue | CalendarDate | CalendarDateTime | ZonedDateTimeThe maximum date that can be selected | |
minValue | CalendarDate | CalendarDateTime | ZonedDateTimeThe minimum date that can be selected | |
numberOfMonths | numberThe number of months to display at once | |
isDateDisabled | MatcherA function that returns whether or not a date is disabled | |
isDateUnavailable | MatcherA function that returns whether or not a date is unavailable | |
isDateHighlightable | MatcherA function that returns whether or not a date is hightable | |
nextPage | (placeholder: DateValue) => DateValueA function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component. | |
prevPage | (placeholder: DateValue) => DateValueA function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component. | |
fixedDate | "start" | "end"Which part of the range should be fixed | |
isMonthDisabled | MatcherA function that returns whether or not a month is disabled | |
isMonthUnavailable | MatcherA function that returns whether or not a month is unavailable | |
isYearDisabled | MatcherA function that returns whether or not a year is disabled | |
isYearUnavailable | MatcherA function that returns whether or not a year is unavailable | |
modelValue | FormattedValue<R, M, V> | |
clearable | boolean | |
monthControls | booleanShow month controls | |
yearControls | booleanShow year controls | |
weekNumbers | boolean | |
allowNonContiguousRanges | booleanWhen combined with | |
pagedNavigation | booleanThis property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month | |
preventDeselect | booleanWhether or not to prevent the user from deselecting a date without selecting another date first | |
fixedWeeks | booleanWhether or not to always display 6 weeks in the calendar | |
disabled | booleanWhether or not the calendar is disabled | |
readonly | booleanWhether or not the calendar is readonly | |
initialFocus | booleanIf true, the calendar will focus the selected day, today, or the first day of the month depending on what is visible when the calendar is mounted | |
disableDaysOutsideCurrentView | booleanWhether or not to disable days outside the current view. | |
ui | Record<string, ClassNameValue> & { content?: SlotClass; arrow?: SlotClass; wrapper?: SlotClass; presets?: SlotClass; presetButton?: SlotClass; calendar?: SlotClass; clearIcon?: SlotClass; } |
Emits
| Event | Type |
|---|---|
update:modelValue | [value: FormattedValue<R, M, V>] |
update:placeholder | [date: DateValue] |
update:validModelValue | [date: DateRange] |
update:startValue | [date: DateValue] |
close:prevent | [] |
update:open | [value: boolean] |
Slots
| Slot | Type |
|---|---|
default | { open: boolean; } |
content | SlotProps<P> |
heading | { value: string; view: CalendarView; date: DateValue; setView: (view: CalendarView) => void; setPlaceholder: (date: DateValue) => void; } |
day | Pick<CalendarCellTriggerProps, "day"> |
week-day | { day: string; } |
month-cell | { month: DateValue; selected: boolean; disabled: boolean; } |
year-cell | { year: DateValue; selected: boolean; disabled: boolean; } |
leading | { ui: { base: (props?: Record<string, any>) => string; label: (props?: Record<string, any>) => string; leadingIcon: (props?: Record<string, any>) => string; leadingAvatar: (props?: Record<string, any>) => string; leadingAvatarSize: (props?: Record<string, any>) => string; trailingIcon: (props?: Record<string, any>) => string; }; } |
trailing | { ui: { base: (props?: Record<string, any>) => string; label: (props?: Record<string, any>) => string; leadingIcon: (props?: Record<string, any>) => string; leadingAvatar: (props?: Record<string, any>) => string; leadingAvatarSize: (props?: Record<string, any>) => string; trailingIcon: (props?: Record<string, any>) => string; }; } |
Theme
export default defineAppConfig({
ui: {
datePicker: {
slots: {
wrapper: 'flex',
presets: 'flex flex-col gap-1 p-2 border-r border-default min-w-[120px]',
presetButton: 'justify-start',
calendar: 'p-2',
clearIcon: 'cursor-pointer opacity-60 hover:opacity-100 transition-opacity'
},
variants: {
withPresets: {
true: {},
false: {
wrapper: 'block'
}
}
},
defaultVariants: {
withPresets: false
}
}
}
})