news 2026/11/2 2:08:07

Nuxt UI InputDate 组件实战指南:日期输入、范围选择与日期选择器的完整解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nuxt UI InputDate 组件实战指南:日期输入、范围选择与日期选择器的完整解析
  • 前端
  • UI组件

【免费下载链接】ui

The Intuitive Vue UI Library powered by Reka UI & Tailwind CSS.

项目地址:https://gitcode.com/gh_mirrors/ui4/ui
点击查看免费下载

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-elevated
  • soft:focus:bg-accented/50 group-hover:focus:bg-accented
  • subtle:focus:bg-accented
  • ghost: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):

  1. blur 时校验:设置validateOn: ['blur']后,焦点离开整个输入框(而非单个段)才触发校验并显示错误信息;
  2. 段间焦点移动不触发 blur:当焦点从"年"段移动到"月"段时,不会误触发 blur 校验。组件源码中用relatedTarget判断焦点是否仍在组件内部(见 InputDate.vue);
  3. 值更新后会通过emitFormChange/emitFormInput同步表单状态(见 InputDate.vue)。

API 速查:Props、Slots 与 Emits

Props

Prop类型默认值说明
modelValueDateValue/DateRange—受控的选中日期(v-model)
defaultValueDateValue/DateRange—非受控的初始日期
rangebooleanfalse是否选择日期范围
colorprimary|neutral| 主题色primary组件颜色
variantoutline|soft|subtle|ghost|noneoutline组件变体
sizexs|sm|md|lg|xlmd组件尺寸
highlightbooleanfalse高亮环形(ring)颜色
fixedbooleanfalse所有断点保持移动端字号
iconstring—内部图标(默认leading)
leading-icon/trailing-iconstring—指定位置的图标
separator-iconstringui.icons.minus范围分隔图标
avatarAvatarProps—内部头像
disabledbooleanfalse禁用组件
autofocusbooleanfalse挂载后自动聚焦第一个段
autofocus-delaynumber0自动聚焦延迟(毫秒)
min-value/max-valueDateValue—可选日期上下限
is-date-unavailable(date: DateValue) => boolean—标记不可用日期
readonlybooleanfalse只读模式
asstring|Componentdiv根元素渲染为指定元素
uiPartial<Slots>—按槽位覆盖主题样式

其余 prop(如placeholder、locale、calendar等)直接继承 Reka UIDateFieldRootProps/DateRangeFieldRootProps,见 InputDate.vue。

Slots

插槽说明
leading左侧内容区,覆盖默认图标/头像
default输入段区域的自定义内容
trailing右侧内容区(例如放置弹出日历按钮)
separator范围分隔符区域

插槽类型定义见 InputDate.vue。

Emits

事件载荷说明
update:modelValueDateValue/DateRange选中日期变化时触发
changeEvent值提交变更时触发
blurFocusEvent焦点离开整个输入框时触发
focusFocusEvent焦点进入整个输入框时触发

测试用例专门验证了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.

项目地址:https://gitcode.com/gh_mirrors/ui4/ui
点击查看免费下载
上一篇:OpenMed HIPAA 合规检查清单实战指南:逐项对照 45 CFR Part 164 安全与隐私规则
下一篇:nRF Connect 示例中的 Matter CLI:Zephyr Shell 命令完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 13:32:47

Bun vs npm 实战对比:一次跨 libc 依赖踩坑带来的迁移笔记

本文基于一个多前端子项目从 npm/pnpm 迁移到 Bun 的过程整理。 文中速度数据为同一台 x86_64 机器、同一份依赖清单下的参考量级&#xff0c;用于直观感受差距&#xff0c;不是实验室级精确基准。 适合读者&#xff1a;正在考虑或已经使用 Bun&#xff0c;遇到原生依赖、跨平台…

作者头像 李华
网站建设 2026/10/19 13:32:46

使用 psql 按数据库分别记录查询历史:\set HISTFILE 变量详解

文档教程知识库 【免费下载链接】til :memo: Today I Learned 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ti/til 点击查看 免费下载 导读 默认情况下&#xff0c;psql 会把你在会话中输入的所有 SQL 查询和反斜杠命令统一记录在 ~/.psql_history 一个文件里&…

作者头像 李华
网站建设 2026/10/19 13:32:48

生产告警分级与降噪:P0才打电话的决策树

先判断用户有没有受到影响,再选电话、值班群或日报。静默要有到期时间,不要删掉唯一的信号。 目录 一条告警该不该存在 等级和渠道 降噪的四种做法 Prometheus 可复制示例 开工前只读盘点 静默、抑制和回滚 回滚成本 反序会怎样 Checklist 与变更单 前言 告警疲劳通常不是阈…

作者头像 李华
网站建设 2026/10/30 1:20:32

ASP.NET微信商城分销直销源码:部署、数据表与佣金计算避坑指南

简介&#xff1a;这是基于ASP.NET的多用户微信商城分销直销平台完整源码&#xff0c;面向需要快速搭建微信商城或开展公众号分销运营的开发团队。平台支持多公众号集中管理&#xff0c;每个公众号可配置5个子账号&#xff0c;涵盖自定义菜单、关注回复、客户管理、二维码链式推…

作者头像 李华