DatePicker

View source
基于国际化标准的日期选择器组件。

简介

MDatePicker 是一个国际化日期选择器,基于 @internationalized/date 处理时区与本地化。支持单日期、日期范围、多日期选择,并提供按钮触发、可清空、快捷预设等多种交互形态。

基于 Nuxt UI 的 Calendar 组件封装
使用 @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>

限制可选日期边界

minValuemaxValue 会禁用边界外日期,分别约束只能选择未来或过去日期:

<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'any

The element or component this component should render as.

idstring
namestring
placeholder'选择日期'string
buttonPropsButtonProps
popoverPropsPopoverProps<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'V

v-model 投影输出格式

presets"default" | DatePickerPreset<R, M>[]

快捷预设

locale'zh-CN'string

语言区域

formatOptionsIntl.DateTimeFormatOptions

日期格式化选项

timeZonestring

时区标识符,默认使用本地时区

type'date'"date" | "month" | "year"

The type of picker.

  • date renders a day calendar whose heading can switch to a month then year view.
  • month renders a standalone month picker.
  • year renders a standalone year picker.
nextYearIconappConfig.ui.icons.chevronDoubleRightany

The icon to use for the next year control.

nextYearOmit<ButtonProps, LinkPropsKeys>

Configure the next year button. { color: 'neutral', variant: 'ghost' }

nextMonthIconappConfig.ui.icons.chevronRightany

The icon to use for the next month control.

nextMonthOmit<ButtonProps, LinkPropsKeys>

Configure the next month button. { color: 'neutral', variant: 'ghost' }

prevYearIconappConfig.ui.icons.chevronDoubleLeftany

The icon to use for the previous year control.

prevYearOmit<ButtonProps, LinkPropsKeys>

Configure the prev year button. { color: 'neutral', variant: 'ghost' }

prevMonthIconappConfig.ui.icons.chevronLeftany

The icon to use for the previous month control.

prevMonthOmit<ButtonProps, LinkPropsKeys>

Configure the prev month button. { color: 'neutral', variant: 'ghost' }

viewControltrueboolean | Omit<ButtonProps, LinkPropsKeys>

Whether to make the heading a button that switches between the day, month and year views. Has no effect when type is year. Can be an object to override the button props. { color: 'neutral', variant: 'ghost', block: true }

color'primary'"primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral"
variant'solid'"solid" | "outline" | "soft" | "subtle"
size'md'"xs" | "sm" | "md" | "lg" | "xl"
rangeR

Whether or not a range of dates can be selected

multipleM

Whether or not multiple dates can be selected

defaultValueCalendarDefaultValue<R, M>
defaultPlaceholderCalendarDate | CalendarDateTime | ZonedDateTime

The default placeholder date

maximumDaysnumber

The maximum number of days that can be selected in a range

weekStartsOn0 | 1 | 2 | 4 | 5 | 3 | 6

The 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

maxValueCalendarDate | CalendarDateTime | ZonedDateTime

The maximum date that can be selected

minValueCalendarDate | CalendarDateTime | ZonedDateTime

The minimum date that can be selected

numberOfMonthsnumber

The number of months to display at once

isDateDisabledMatcher

A function that returns whether or not a date is disabled

isDateUnavailableMatcher

A function that returns whether or not a date is unavailable

isDateHighlightableMatcher

A function that returns whether or not a date is hightable

nextPage(placeholder: DateValue) => DateValue

A function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component.

prevPage(placeholder: DateValue) => DateValue

A 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

isMonthDisabledMatcher

A function that returns whether or not a month is disabled

isMonthUnavailableMatcher

A function that returns whether or not a month is unavailable

isYearDisabledMatcher

A function that returns whether or not a year is disabled

isYearUnavailableMatcher

A function that returns whether or not a year is unavailable

modelValueFormattedValue<R, M, V>
clearableboolean
monthControlsboolean

Show month controls

yearControlsboolean

Show year controls

weekNumbersboolean
allowNonContiguousRangesboolean

When combined with isDateUnavailable, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.

pagedNavigationboolean

This property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month

preventDeselectboolean

Whether or not to prevent the user from deselecting a date without selecting another date first

fixedWeeksboolean

Whether or not to always display 6 weeks in the calendar

disabledboolean

Whether or not the calendar is disabled

readonlyboolean

Whether or not the calendar is readonly

initialFocusboolean

If 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

disableDaysOutsideCurrentViewboolean

Whether or not to disable days outside the current view.

uiRecord<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; }
contentSlotProps<P>
heading{ value: string; view: CalendarView; date: DateValue; setView: (view: CalendarView) => void; setPlaceholder: (date: DateValue) => void; }
dayPick<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

app.config.ts
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
      }
    }
  }
})

Changelog

No recent changes
Copyright © 2025 - 2026 YiXuan - MIT License