日期范围选择器
概述
日期范围选择器组件:将可键盘编辑的双段式日期范围字段与日历范围弹层组合,既可直接在分段中键入开始与结束日期,也可点击日历开关在单一月历范围网格中一次选择两端。适用于任何需要用户选择有界区间的场景——预订住宿日期、报表窗口或筛选范围。只需要纯键入、无需弹层时,优先使用 SDateRangeField;需要带弹层的单个日期时,使用 SDatePicker。
用法
特性
- 📅 双字段 + 弹层组合——分段
DateRangeField保持可编辑,CalendarRangePopover 提供两端日期的可视化选择 - 🎚 受控 / 非受控——值为
v-model/defaultValue,弹层为open/defaultOpen,均由useControllableState支撑 - ⌨️ 完整键盘编辑——两组内分段增减/键入、跨组方向键移动,日历网格方向键导航,
Escape关闭 - ⛓ 范围校验——
minValue/maxValue/isDateUnavailable在字段与日历中同时禁用越界与不可用日期 - 🌍 本地化可访问性——日历开关与弹层对话框的默认
aria-label来自当前 locale 消息,并可通过triggerProps/popupProps覆盖 - 🧩 可组合插槽——
leading放置分段前内容、separator自定义分隔符;default 插槽 props 暴露open/close/calendarRangeProps,支持完全自定义弹层 - 🎨 细粒度样式——
ui覆盖字段及trigger/positioner/popup槽,calendarRangeUi独立作用于内嵌日历 - 🚫 禁用状态——
disabled同时阻止字段编辑与弹层打开
组件族系
SDateRangePicker——样式包装层:透传 props 给 Aria compact,注入dateRangePickerVariants(扩展dateFieldVariants),并在弹层内渲染SCalendarRangeDateRangePickerCompact(Aria)——由DateRangeFieldCompact+PopoverCompact(日历图标触发器)数据驱动组合,通过 default 插槽暴露弹层状态与calendarRangeProps;无样式使用时从@vean/aria/date-range-picker导入DateRangeFieldCompact/PopoverCompact/CalendarRangeCompact(Aria)——支撑选择器的双段字段、Popover 机制与范围日历网格
演示
基础
Selected: Sun Apr 19 2026 00:00:00 GMT+0000 (Coordinated Universal Time) to Sun Apr 26 2026 00:00:00 GMT+0000 (Coordinated Universal Time)
自定义样式
API
Aria 层
2DateRangePicker
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
class | ClassValue | — | 应用于根元素的额外类名。 |
size | ThemeSize | — | 组件的视觉尺寸。 |
ui | Partial<DateRangePickerUi> | — | 该组件的按槽类重写。 |
calendarRangeUi | Partial<CalendarRangeUi> | — | DateRangePicker 中的 CalendarRange 组件针对每个槽位的类重写。 |
dateFieldProps | BaseProps | — | — |
placement | import("@/index").Placement | — | 位置。 |
showArrow | boolean | — | 是否显示箭头。 |
triggerProps | PopoverTriggerProps | — | 转发给触发元素的属性。 |
portalProps | PopperPortalProps | — | 转发给 portal 元素的属性。 |
positionerProps | PopoverPositionerProps | — | 转发给定位器元素的属性。 |
popupProps | PopoverPopupProps | — | 转发给弹出窗口元素的属性。 |
arrowProps | PopperArrowProps | — | 转发给箭头元素的属性。 |
closeProps | PopoverCloseProps | — | 转发至 close 元素的属性。 |
dir | Direction | — | 组件的阅读方向。 |
open | boolean | — | — |
defaultOpen | boolean | — | — |
modal | boolean | — | — |
disabled | boolean | — | 该组件是否已禁用。 |
headerProps | CalendarRangeHeaderProps | — | 转发至 header 元素的属性。 |
headingProps | CalendarRangeHeadingProps | — | 传递给 heading 元素的属性。 |
prevProps | CalendarRangePrevProps | — | 转发给前一个元素的属性。 |
nextProps | CalendarRangeNextProps | — | 属性将转发给下一个元素。 |
gridProps | CalendarRangeGridProps | — | 转发给网格元素的属性。 |
gridHeadProps | CalendarRangeGridHeadProps | — | 转发给网格头元素的属性。 |
gridBodyProps | CalendarRangeGridBodyProps | — | 转发至 grid 主体元素的属性。 |
gridRowProps | CalendarRangeGridRowProps | — | 转发给网格行元素的属性。 |
headCellProps | CalendarRangeHeadCellProps | — | 转发至 head 单元格元素的属性。 |
cellProps | CalendarRangeCellProps | — | 转发给 cell 元素的属性。 |
cellTriggerProps | CalendarRangeCellTriggerProps | — | 转发给单元格触发器的属性。 |
defaultValue | DateRange | — | 默认值。 |
defaultPlaceholder | DateValue | — | 默认占位符。 |
placeholder | DateValue | — | 占位符。 |
modelValue | DateRange | — | 当前模型值。 |
allowNonContiguousRanges | boolean | — | 是否允许非连续范围。 |
pagedNavigation | boolean | — | 是否启用分页导航。 |
preventDeselect | boolean | — | 是否禁止取消选择。 |
maximumDays | number | — | 最多天数。 |
weekStartsOn | WeekStartsOn | — | 本周从...开始。 |
weekdayFormat | WeekDayFormat | — | 星期格式。 |
calendarLabel | string | — | 日历标签。 |
fixedWeeks | boolean | — | 是否为固定周数。 |
maxValue | DateValue | — | 最大值。 |
minValue | DateValue | — | 最小值。 |
locale | string | — | 语言环境。 |
numberOfMonths | number | — | 月数。 |
readonly | boolean | — | 该组件是否为只读。 |
initialFocus | boolean | — | 是否为初始焦点。 |
isDateDisabled | DateMatcher | — | 日期是否已禁用。 |
isDateUnavailable | DateMatcher | — | 日期是否不可用。 |
isDateHighlightable | DateMatcher | — | 日期是否可高亮显示。 |
nextPage | ((placeholder: DateValue) => DateValue) | — | 下一页。 |
prevPage | ((placeholder: DateValue) => DateValue) | — | 上一页。 |
disableDaysOutsideCurrentView | boolean | — | 是否禁用当前视图范围外的日期。 |
fixedDate | 'start' | 'end' | — | 固定日期。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:open | boolean | 当对话框的打开状态发生变化时调用的事件处理程序。 |
escapeKeyDown | KeyboardEvent | 当 Esc 键被按下时触发的事件处理程序。可被阻止。 |
pointerDownOutside | PointerDownOutsideEvent | 当 `pointerdown` 事件发生在 `DismissableLayer` 外部时调用的事件处理程序。可被阻止。 |
focusOutside | FocusOutsideEvent | 当焦点移出 `DismissableLayer` 时触发的事件处理程序。可被阻止。 |
interactOutside | PointerDownOutsideEvent | FocusOutsideEvent | 当在 `DismissableLayer` 外部发生交互时触发的事件处理程序。具体而言,当 `pointerdown`
事件在该区域外部发生,或焦点移出该区域时触发。此事件可被阻止。 |
openAutoFocus | Event | 在打开页面时自动聚焦时触发的事件处理程序。可被阻止。 |
closeAutoFocus | Event | 在关闭时自动聚焦时触发的事件处理程序。可被阻止。 |
placed | — | — |
update:modelValue | DateRange | 当模型值发生变化时触发。 |
update:placeholder | DateValue | 当占位符值发生变化时触发。 |
update:startValue | DateValue | null | 当起始值发生变化时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
leading | — | 在日期范围分段之前渲染的自定义内容。 |
separator | — | 在起始和结束段落组之间渲染的自定义内容。 |
DateRangePickerCompact
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
dateFieldProps | BaseProps | — | — |
placement | import("@/index").Placement | — | 位置。 |
showArrow | boolean | — | 是否显示箭头。 |
triggerProps | PopoverTriggerProps | — | 转发给触发元素的属性。 |
portalProps | PopperPortalProps | — | 转发给 portal 元素的属性。 |
positionerProps | PopoverPositionerProps | — | 转发给定位器元素的属性。 |
popupProps | PopoverPopupProps | — | 转发给弹出窗口元素的属性。 |
arrowProps | PopperArrowProps | — | 转发给箭头元素的属性。 |
closeProps | PopoverCloseProps | — | 转发至 close 元素的属性。 |
dir | Direction | — | 组件的阅读方向。 |
open | boolean | — | — |
defaultOpen | boolean | — | — |
modal | boolean | — | — |
disabled | boolean | — | 该组件是否已禁用。 |
headerProps | CalendarRangeHeaderProps | — | 转发至 header 元素的属性。 |
headingProps | CalendarRangeHeadingProps | — | 传递给 heading 元素的属性。 |
prevProps | CalendarRangePrevProps | — | 转发给前一个元素的属性。 |
nextProps | CalendarRangeNextProps | — | 属性将转发给下一个元素。 |
gridProps | CalendarRangeGridProps | — | 转发给网格元素的属性。 |
gridHeadProps | CalendarRangeGridHeadProps | — | 转发给网格头元素的属性。 |
gridBodyProps | CalendarRangeGridBodyProps | — | 转发至 grid 主体元素的属性。 |
gridRowProps | CalendarRangeGridRowProps | — | 转发给网格行元素的属性。 |
headCellProps | CalendarRangeHeadCellProps | — | 转发至 head 单元格元素的属性。 |
cellProps | CalendarRangeCellProps | — | 转发给 cell 元素的属性。 |
cellTriggerProps | CalendarRangeCellTriggerProps | — | 转发给单元格触发器的属性。 |
defaultValue | DateRange | — | 默认值。 |
defaultPlaceholder | DateValue | — | 默认占位符。 |
placeholder | DateValue | — | 占位符。 |
modelValue | DateRange | — | 当前模型值。 |
allowNonContiguousRanges | boolean | — | 是否允许非连续范围。 |
pagedNavigation | boolean | — | 是否启用分页导航。 |
preventDeselect | boolean | — | 是否禁止取消选择。 |
maximumDays | number | — | 最多天数。 |
weekStartsOn | WeekStartsOn | — | 本周从...开始。 |
weekdayFormat | WeekDayFormat | — | 星期格式。 |
calendarLabel | string | — | 日历标签。 |
fixedWeeks | boolean | — | 是否为固定周数。 |
maxValue | DateValue | — | 最大值。 |
minValue | DateValue | — | 最小值。 |
locale | string | — | 语言环境。 |
numberOfMonths | number | — | 月数。 |
readonly | boolean | — | 该组件是否为只读。 |
initialFocus | boolean | — | 是否为初始焦点。 |
isDateDisabled | DateMatcher | — | 日期是否已禁用。 |
isDateUnavailable | DateMatcher | — | 日期是否不可用。 |
isDateHighlightable | DateMatcher | — | 日期是否可高亮显示。 |
nextPage | ((placeholder: DateValue) => DateValue) | — | 下一页。 |
prevPage | ((placeholder: DateValue) => DateValue) | — | 上一页。 |
disableDaysOutsideCurrentView | boolean | — | 是否禁用当前视图范围外的日期。 |
fixedDate | 'start' | 'end' | — | 固定日期。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:open | boolean | 当对话框的打开状态发生变化时调用的事件处理程序。 |
escapeKeyDown | KeyboardEvent | 当 Esc 键被按下时触发的事件处理程序。可被阻止。 |
pointerDownOutside | PointerDownOutsideEvent | 当 `pointerdown` 事件发生在 `DismissableLayer` 外部时调用的事件处理程序。可被阻止。 |
focusOutside | FocusOutsideEvent | 当焦点移出 `DismissableLayer` 时触发的事件处理程序。可被阻止。 |
interactOutside | PointerDownOutsideEvent | FocusOutsideEvent | 当在 `DismissableLayer` 外部发生交互时触发的事件处理程序。具体而言,当 `pointerdown`
事件在该区域外部发生,或焦点移出该区域时触发。此事件可被阻止。 |
openAutoFocus | Event | 在打开页面时自动聚焦时触发的事件处理程序。可被阻止。 |
closeAutoFocus | Event | 在关闭时自动聚焦时触发的事件处理程序。可被阻止。 |
placed | — | — |
update:modelValue | DateRange | 当模型值发生变化时触发。 |
update:placeholder | DateValue | 当占位符值发生变化时触发。 |
update:startValue | DateValue | null | 当起始值发生变化时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
leading | — | 在日期范围分段之前渲染的自定义内容。 |
separator | — | 在起始和结束段落组之间渲染的自定义内容。 |
default | DateRangePickerCompactSlotProps | 为日历范围弹出窗口提供自定义内容,接收“打开/关闭”控件和日历属性。 |
注意
架构与行业对标差异
SDateRangePicker 将 props 透传给 DateRangePickerCompact,后者组合 DateRangeFieldCompact(双段 + 校验)与 PopoverCompact(lucide:calendar 图标触发器,带 aria-haspopup="dialog")。样式层通过 provideDateRangePickerUi 注入 dateRangePickerVariants 类,使嵌套的 Popover 部分获得 trigger/positioner/popup 样式,再在 default 插槽中用透传的 calendarRangeProps 渲染 SCalendarRange。选中开始与结束两天后触发 update:modelValue,两端齐备即关闭弹层。弹层为 role="dialog",默认可访问名称来自 locale popupLabel 消息;开关按钮的默认名称来自 toggle。多数对标库是「两个纯文本框 + 范围面板」形态;「双段字段 + Popover 组合」、跨组键盘焦点与 Aria/styled 分离是差异点。
| 能力 | VeanUI | Ant Design | Element Plus | Mantine | Naive UI | shadcn |
|---|---|---|---|---|---|---|
| Aria/styled 分离 | ✅ | — | — | — | — | — |
| 双段字段 + 范围弹层 | ✅ | — | — | — | — | — |
| 值受控 / 非受控 | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| 弹层受控 / 非受控 | ✅ | — | — | — | — | — |
| 键盘分段编辑 | ✅ | — | — | — | — | — |
| 跨组焦点移动 | ✅ | — | — | — | — | — |
| 日历键盘导航 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 范围校验 | ✅ | ✅ | ✅ | ✅ | ✅ | — |
isDateUnavailable | ✅ | — | — | — | — | — |
| 禁用状态 | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| locale 驱动的可访问名称 | ✅ | — | — | — | — | — |
leading / separator 插槽 | ✅ | — | — | — | — | — |
独立的 calendarRangeUi | ✅ | — | — | — | — | — |
使用注意
- 值是
DateRange——由DateValue构成的{ start, end }对象,不是string——请与SDateRangeField、SCalendarRange及日期族系其他组件搭配使用。 defaultValue/defaultOpen仅在挂载时读取——外部控制请使用v-model/open。- 开关按钮就是日历图标;其默认
aria-label(localetoggle)可通过triggerProps['aria-label']覆盖。图标本身固定——需要自定义触发器时,请用@vean/aria/date-range-picker组合DateRangePickerCompact并自行渲染 Popover 内容。 - 弹层
role="dialog"的可访问名称来自 localepopupLabel消息;可通过popupProps['aria-label']覆盖。 - 通过
dateFieldProps(如placeholder、locale、granularity)配置内嵌双段字段;字段与日历共享minValue/maxValue/isDateUnavailable。 calendarRangeUi被内嵌日历消费、不会到达 DOM——dateFieldProps同理。- 开始日期晚于结束日期会被标记无效,但值不会被自动交换——请在父级清空或纠正。
常见问题
如何用键盘选择日期范围?
Tab 进入任一分段组并键入数字或用方向键,或按下日历开关后用方向键、Home/End、PageUp/PageDown 在月历网格中导航;Enter 提交选择,Escape 关闭且不选中。两端都选定后值才会发出。
如何限制可选范围?
传入 minValue/maxValue——越界日期在日历中被禁用,字段也会标记自身 data-invalid。isDateUnavailable 接受谓词函数做任意排除,且同时作用于字段与日历。
如何配置内嵌日期范围字段?
使用 dateFieldProps——例如 { placeholder }、{ locale }、{ granularity: 'minute' } 或 { hourCycle: 12 }。字段接受的 props 与 SDateRangeField 本身一致。
如何自定义日历外观?
calendarRangeUi 独立于 ui 覆盖范围日历各槽位类。需要更深层定制时,default 插槽会收到 calendarRangeProps(外加 open/close),可以用 @vean/aria/calendar-range 的 CalendarRangeCompact 渲染自己的弹层内容。
SDateRangePicker 与 SDateRangeField 有什么区别?
SDateRangeField 是纯双段输入、无弹层——适合快速键入与表单提交。SDateRangePicker 在相同字段之上叠加日历范围 Popover,以少量体积换取可视化选择能力。