- 前端
- UI组件
【免费下载链接】ui
The Intuitive Vue UI Library powered by Reka UI & Tailwind CSS.
UInputDate是 Nuxt UI(本仓库ui项目)内置的日期输入组件,基于 Reka UI 的DateField/DateRangeField构建,并通过@internationalized/date提供基于 locale 的日期格式化能力。本文以官方文档 InputDate 文档 为骨架,结合 组件源码、主题配置 与 测试用例,完整讲解该组件的双向绑定、范围选择、外观变体、表单集成、日期限制以及用UCalendar+UPopover组装完整日期选择器的全部实战方案。
快速上手:v-model 双向绑定
UInputDate的核心用法是通过v-model指令控制选中日期。组件内部将值托管给 Reka UI 的DateField.Root,因此在 Vue 模板中直接使用即可:
<script setup lang="ts"> import { CalendarDate } from '@internationalized/date' const modelValue = shallowRef(new CalendarDate(2022, 2, 3)) </script> <template> <UInputDate v-model="modelValue" /> </template>如果你不需要控制组件状态,也可以使用default-valueprop 设置初始值:
<script setup lang="ts"> import { CalendarDate } from '@internationalized/date' const defaultValue = new CalendarDate(2022, 2, 6) </script> <template> <UInputDate :default-value="defaultValue" /> </template>从源码的类型定义可以看出,这两个 prop 的类型是泛型的——当range为true时,值的类型自动切换为日期范围对象(见 InputDate.vue):
type InputDateDefaultValue<R extends boolean = false> = R extends true ? DateRangeFieldRootProps['defaultValue'] : DateFieldRootProps['defaultValue'] type InputDateModelValue<R extends boolean = false> = (R extends true ? DateRangeFieldRootProps['modelValue'] : DateFieldRootProps['modelValue']) | undefined关于日期值的类型
UInputDate的modelValue并不是普通的Date对象,而是@internationalized/date包中的DateValue类型(通常由CalendarDate类创建)。该类型同时携带日历系统和时区信息,是组件实现 locale 感知格式化的基础。在 测试用例 中可以确认这一点:
['with modelValue', { props: { modelValue: new CalendarDate(2025, 1, 1) } }], ['with range and modelValue', { props: { range: true, modelValue: { start: new CalendarDate(2025, 1, 1), end: new CalendarDate(2025, 1, 15) } } }]本地化(locale)对日期格式的影响
官方文档明确指出:该组件使用@internationalized/date包进行 locale 感知格式化,日期格式由App组件的localeprop 决定。在 app.md 中同样有说明——locale同时控制 Calendar、InputDate、InputTime 等组件的日期/时间格式。
在 Nuxt 项目中,通过<UApp>传入 locale 即可全局生效:
<script setup lang="ts"> import { fr } from '@nuxt/ui/locale' </script> <template> <UApp :locale="fr"> <NuxtPage /> </UApp> </template>每个 locale 都带有一个code属性(如en、en-GB、fr),该属性直接决定日期格式。详见 Nuxt i18n 集成文档 与 Vue i18n 集成文档。如果需要自定义语言,可以使用defineLocale组合式函数创建专属 locale。
范围选择:range prop
设置range为true即可让组件支持选择一段日期范围。此时v-model的值变为{ start: DateValue, end: DateValue }结构:
<script setup lang="ts"> import { CalendarDate } from '@internationalized/date' const modelValue = shallowRef({ start: new CalendarDate(2022, 2, 3), end: new CalendarDate(2022, 2, 20) }) </script> <template> <UInputDate v-model="modelValue" range /> </template>源码中,组件会根据range的值在DateRangeField与DateField之间动态切换底层根组件(见 InputDate.vue):
const DateField = computed(() => props.range ? RangeDateField : SingleDateField)同时,模板会根据segments是否为数组判断当前是单日期还是范围模式:范围模式下会渲染"起始段 + 分隔图标 + 结束段"三段结构,中间的分隔符由separatorIcon控制(见 InputDate.vue)。
外观定制:Color、Variant 与 Size
UInputDate的视觉样式与UInput保持一致,通过color、variant、size三个 prop 控制。
Color
使用colorprop 改变组件颜色。默认值为primary(源码InputDateProps中声明@defaultValue 'primary',见 InputDate.vue):
<UInputDate color="neutral" highlight />颜色集合来自全局主题配置options.theme.colors,同时固定提供neutral选项(见 input.ts)。
Variant
使用variantprop 切换组件变体,可用值与UInput一致,包括outline(默认)、soft、subtle、ghost、none:
<UInputDate variant="subtle" />主题配置中为每种 variant 定义了段(segment)的聚焦反馈样式(见 input-date.ts),例如:
outline:focus:bg-elevatedsoft:focus:bg-accented/50 group-hover:focus:bg-accentedsubtle:focus:bg-accentedghost:focus:bg-elevated group-hover:focus:bg-accented
Size
使用sizeprop 控制尺寸,可选xs、sm、md(默认)、lg、xl:
<UInputDate size="xl" />主题中为每种尺寸分别配置了段间距与各段(day/month/year)的固定宽度(见 input-date.ts),例如md尺寸下:day段宽w-9、month段宽w-9、year段宽w-11,段间距为gap-0.5。
此外,UInputDate还支持两个 Input 系组件独有的外观 prop:
highlight:像聚焦状态一样高亮环形(ring)颜色,通常搭配color使用。fixed:在所有断点上保持移动端文字大小(默认情况下md断点以上文字会随尺寸放大,见 input.ts)。
图标:icon、leading 与 trailing
使用iconprop 在 InputDate 内部显示一个 Icon 图标:
<UInputDate icon="i-lucide-calendar" />官方文档补充说明:使用leading和trailingprop 可以设置图标位置;使用leading-icon和trailing-iconprop 可以为两个位置设置不同图标。这些能力由useComponentIcons组合式函数统一处理(见 InputDate.vue),底层复用 Input 系组件的图标机制。
范围分隔图标:separator-icon
在range模式下,起始日期与结束日期之间默认渲染一个i-lucide-minus图标作为分隔符。使用separator-iconprop 可以替换它:
<UInputDate range separator-icon="i-lucide-arrow-right" />separatorIcon的默认值取自全局配置appConfig.ui.icons.minus(见 InputDate.vue)。这意味着你可以在全局层面统一定制这个分隔图标:
- Nuxt:在
app.config.ts中通过ui.icons.minus键配置; - Vue:在
vite.config.ts中通过ui.icons.minus键配置。
头像:avatar prop
UInputDate支持在内部显示一个 Avatar,适合"表单字段 + 用户信息"的组合场景:
<UInputDate size="md" variant="outline" :avatar="{ src: 'https://github.com/vuejs.png', loading: 'lazy' }" />源码中,当传入avatar时会优先渲染UAvatar而不是图标(见 InputDate.vue),头像尺寸由leadingAvatarSize槽位样式决定。
禁用状态:disabled
设置disabledprop 即可禁用组件:
<UInputDate disabled />禁用状态下段输入框不可编辑、不可聚焦,主题通过data-disabled:cursor-not-allowed与data-disabled:opacity-75提供视觉反馈(见 input-date.ts)。该状态同样可以被外层UFormField或UFieldGroup向下传递(useFormField/useFieldGroup的disabled计算逻辑见 InputDate.vue)。
实战示例:限制、禁用与组合日期选择器
标记不可用日期(is-date-unavailable)
使用is-date-unavailableprop,传入一个接收DateValue、返回布尔值的函数,即可把特定日期标记为不可选。官方示例将每月 10 日到 16 日设为不可用(完整示例见 InputDateUnavailableDatesExample.vue):
<script setup lang="ts"> import type { DateValue } from '@internationalized/date' import { CalendarDate } from '@internationalized/date' const modelValue = shallowRef({ start: new CalendarDate(2022, 1, 1), end: new CalendarDate(2022, 1, 9) }) const isDateUnavailable = (date: DateValue) => { return date.day >= 10 && date.day <= 16 } </script> <template> <UInputDate v-model="modelValue" :is-date-unavailable="isDateUnavailable" range /> </template>该 prop 直接透传给 Reka UI 的DateFieldRootProps(见 InputDate.vue),会在用户选择时阻止不可用日期。
限制最小/最大日期(min-value / max-value)
使用min-value和max-valueprops 限制可选日期范围,例如只允许 2023 年 9 月内的日期(完整示例见 InputDateMinMaxDatesExample.vue):
<script setup lang="ts"> import { CalendarDate } from '@internationalized/date' const modelValue = shallowRef(new CalendarDate(2023, 9, 10)) const minDate = new CalendarDate(2023, 9, 1) const maxDate = new CalendarDate(2023, 9, 30) </script> <template> <UInputDate v-model="modelValue" :min-value="minDate" :max-value="maxDate" /> </template>测试用例中同样验证了minValue/maxValue与isDateUnavailable的渲染路径(见 InputDate.spec.ts)。
组装完整的日期选择器(Calendar + Popover)
UInputDate本身是分段(segment)输入框,若要实现弹出式日历选择,官方推荐用 Calendar + Popover 组合(完整示例见 InputDateDatePickerExample.vue):
<script setup lang="ts"> import { CalendarDate } from '@internationalized/date' const inputDate = useTemplateRef('inputDate') const modelValue = shallowRef(new CalendarDate(2022, 1, 10)) </script> <template> <UInputDate ref="inputDate" v-model="modelValue"> <template #trailing> <UPopover :reference="inputDate?.inputsRef[3]?.$el"> <UButton color="neutral" variant="link" size="sm" icon="i-lucide-calendar" aria-label="Select a date" class="px-0" /> <template #content> <UCalendar v-model="modelValue" class="p-2" /> </template> </UPopover> </template> </UInputDate> </template>关键点:组件通过defineExpose暴露了inputsRef数组(见 InputDate.vue),里面按顺序存放各个日期段(day/month/year 等)的 DOM 实例,因此可以用inputsRef[3]?.$el精确定位某个段作为UPopover的锚点。
组装日期范围选择器
将range与双月 Calendar 组合,即可得到范围选择器(完整示例见 InputDateDateRangePickerExample.vue):
<script setup lang="ts"> import { CalendarDate } from '@internationalized/date' const inputDate = useTemplateRef('inputDate') const modelValue = shallowRef({ start: new CalendarDate(2022, 1, 10), end: new CalendarDate(2022, 1, 20) }) </script> <template> <UInputDate ref="inputDate" v-model="modelValue" range> <template #trailing> <UPopover :reference="inputDate?.inputsRef[0]?.$el"> <UButton color="neutral" variant="link" size="sm" icon="i-lucide-calendar" aria-label="Select a date range" class="px-0" /> <template #content> <UCalendar v-model="modelValue" class="p-2" :number-of-months="2" range /> </template> </UPopover> </template> </UInputDate> </template>范围模式下inputsRef[0]指向起始日期的第一个段,将其作为 Popover 锚点即可让弹出层与输入框起始位置对齐。
表单集成:与 UFormField 协同校验
UInputDate通过useFormField与useFieldGroup深度集成到表单体系(见 InputDate.vue),因此可以直接放进UFormField中使用:
<UFormField name="value"> <UInputDate id="input" v-model="state.value" /> </UFormField>测试用例验证了三个关键行为(见 InputDate.spec.ts):
- blur 时校验:设置
validateOn: ['blur']后,焦点离开整个输入框(而非单个段)才触发校验并显示错误信息; - 段间焦点移动不触发 blur:当焦点从"年"段移动到"月"段时,不会误触发 blur 校验。组件源码中用
relatedTarget判断焦点是否仍在组件内部(见 InputDate.vue); - 值更新后会通过
emitFormChange/emitFormInput同步表单状态(见 InputDate.vue)。
API 速查:Props、Slots 与 Emits
Props
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue | DateValue/DateRange | — | 受控的选中日期(v-model) |
defaultValue | DateValue/DateRange | — | 非受控的初始日期 |
range | boolean | false | 是否选择日期范围 |
color | primary|neutral| 主题色 | primary | 组件颜色 |
variant | outline|soft|subtle|ghost|none | outline | 组件变体 |
size | xs|sm|md|lg|xl | md | 组件尺寸 |
highlight | boolean | false | 高亮环形(ring)颜色 |
fixed | boolean | false | 所有断点保持移动端字号 |
icon | string | — | 内部图标(默认leading) |
leading-icon/trailing-icon | string | — | 指定位置的图标 |
separator-icon | string | ui.icons.minus | 范围分隔图标 |
avatar | AvatarProps | — | 内部头像 |
disabled | boolean | false | 禁用组件 |
autofocus | boolean | false | 挂载后自动聚焦第一个段 |
autofocus-delay | number | 0 | 自动聚焦延迟(毫秒) |
min-value/max-value | DateValue | — | 可选日期上下限 |
is-date-unavailable | (date: DateValue) => boolean | — | 标记不可用日期 |
readonly | boolean | false | 只读模式 |
as | string|Component | div | 根元素渲染为指定元素 |
ui | Partial<Slots> | — | 按槽位覆盖主题样式 |
其余 prop(如placeholder、locale、calendar等)直接继承 Reka UIDateFieldRootProps/DateRangeFieldRootProps,见 InputDate.vue。
Slots
| 插槽 | 说明 |
|---|---|
leading | 左侧内容区,覆盖默认图标/头像 |
default | 输入段区域的自定义内容 |
trailing | 右侧内容区(例如放置弹出日历按钮) |
separator | 范围分隔符区域 |
插槽类型定义见 InputDate.vue。
Emits
| 事件 | 载荷 | 说明 |
|---|---|---|
update:modelValue | DateValue/DateRange | 选中日期变化时触发 |
change | Event | 值提交变更时触发 |
blur | FocusEvent | 焦点离开整个输入框时触发 |
focus | FocusEvent | 焦点进入整个输入框时触发 |
测试用例专门验证了update:modelValue在单日期与范围模式下的事件载荷(见 InputDate.spec.ts),以及focus/blur不会在段间切换时误触发(见 InputDate.spec.ts)。
主题定制:从源码理解样式结构
UInputDate的主题定义在 src/theme/input-date.ts,并通过 src/theme/index.ts 导出。它继承input主题的槽位与变体,再叠加自身的段(segment)相关样式。
主题暴露以下可覆盖的槽位(slot):
root:外层容器base:整体容器(group relative inline-flex items-center rounded-md select-none)segment:单个日期段输入(占位符data-placeholder:text-dimmed、字面量段data-[segment=literal]:text-muted、校验失败data-invalid:text-error、禁用态等均由该槽位控制)separatorIcon:范围分隔图标(默认size-4 text-muted)
值得注意的细节是:主题中的replaceFocus函数会将focus:替换为has-focus:、focus-visible:替换为has-focus-visible:(见 input-date.ts),使段输入框的聚焦样式基于祖先容器的:has()状态生效——这保证了任意一个段聚焦时整个输入框都呈现聚焦外观。
自定义方式与 Nuxt UI 其他组件一致,通过uiprop 按槽位覆盖即可:
<UInputDate :ui="{ segment: 'text-sm' }" />或在 Nuxt 的app.config.ts中全局覆盖ui.inputDate键,在 Vue 的vite.config.ts中全局覆盖对应主题键。
无障碍与可测试性
组件在每个日期段上渲染为带data-slot="segment"与data-segment属性的可聚焦元素(见 InputDate.vue),支持键盘分段编辑与方向键导航(由 Reka UI 提供)。测试套件在range模式下执行了vitest-axe无障碍断言并确认无违规(见 InputDate.spec.ts)。
组件的段编辑模型非常适合键盘输入——用户可以直接键入年、月、日数字,配合方向键调整数值,这也正是UInputDate区别于纯弹出式UCalendar的核心价值:它把"快速键盘录入"与"可视化选择"两种交互统一在了同一个受控值上。
- 前端
- UI组件
【免费下载链接】ui
The Intuitive Vue UI Library powered by Reka UI & Tailwind CSS.
相关推荐
antd-mobile CalendarPicker 日历选择器完全指南:日期与日期范围选择实战
antd mobile CalendarPicker 日历选择器完全指南:日期与日期范围选择实战 CalendarPicker 是 antd mobile(本项
UI组件前端移动开发Animated高级技巧:实现复杂动画序列与交互响应
Animated高级技巧:实现复杂动画序列与交互响应 Animated是React和React Native生态中强大的声明式动画库,它让开发者能够轻松创建流畅
vue-pure-admin日期选择器:DatePicker日期范围选择
vue pure admin日期选择器:DatePicker日期范围选择 还在为后台管理系统中复杂的日期范围筛选而头疼吗?vue pure admin基于Ele
前端企业应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考