Calendar
概述
按月展示的日期网格组件,支持单选/多选、禁用与不可用日期、min/max 边界、自定义单元格渲染,以及 Compact 头部内置的月份/年份 Select 控件。适用于预订、排期或任何需要键盘可导航网格而非纯文本输入的日期选择场景。需要弹出触发器时搭配 SDatePicker;需要范围选择时使用范围型复合组件。
用法
特性
- 📅 月网格 — 一个或多个月网格(
numberOfMonths),语言环境感知的星期表头、今天与视图外标记 - 🎚 受控 / 非受控 — 支持
v-model/defaultValue单选日期或日期数组(multiple),底层由useControllableState驱动 - ✅ 多选 — 点击或键盘切换日期;
preventDeselect保持选中粘性 - ⌨️ 完整键盘支持 —
ArrowLeft/ArrowRight/ArrowUp/ArrowDown导航(RTL 反转、跨月翻页、跳过禁用日期);Enter/Space选择 - ⛓ 边界与匹配 —
minValue/maxValue限制翻页并禁用越界日期;isDateDisabled/isDateUnavailable标记任意日期 - 🧩 可组合插槽 —
day、head-cell、heading、prev、next插槽携带丰富 slot props;每个子部件支持*Props透传 - 🔤 本地化 UI —
locale、weekStartsOn、weekdayFormat;月份/年份 Select 控件携带本地化aria-label - 🗓 月份/年份控件 — Compact 头部渲染 Select 控件快速跳转月份/年份,按
minValue/maxValue禁用 - ↕️ 固定周数 —
fixedWeeks渲染一致的 6 行网格;initialFocus挂载时聚焦网格 - 🚫 禁用 / 只读 —
disabled阻止全部交互;readonly保留浏览但阻止选择
组件家族
SCalendar— 样式包装层,将 props 转发给 Aria compact 并注入calendarVariants类(12 个 slot,prev/next 复用按钮图标 recipe)CalendarCompact(Aria)—CalendarRoot+ 头部(prev/heading/next)+ 每月一个网格(grid head/body 行内的CalendarCellTrigger)的数据驱动组合;无样式用法从@vean/aria/calendar导入CalendarRoot(Aria)— 状态所有者:useControllableState管理值与占位符,useCalendar管理网格/月份翻页与校验,useCalendarState派生选中/无效状态CalendarCellTrigger/CalendarCell/CalendarGrid*(Aria)— 可编辑的日期按钮(键盘处理、焦点管理、数据属性)及其语义化网格单元格包装
演示
基础
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
禁用
| 二 | 三 | 四 | 五 | 六 | 日 | 一 |
|---|---|---|---|---|---|---|
自定义样式
| 二 | 三 | 四 | 五 | 六 | 日 | 一 |
|---|---|---|---|---|---|---|
API
Aria 层
14Calendar<M>
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
class | ClassValue | — | 应用于根元素的额外类名。 |
size | ThemeSize | — | 组件的视觉尺寸。 |
ui | Partial<CalendarUi> | — | 该组件的按槽类重写。 |
headerProps | CalendarHeaderProps | — | 转发至 header 元素的属性。 |
headingProps | CalendarHeadingProps | — | 传递给 heading 元素的属性。 |
prevProps | CalendarPrevProps | — | 转发给前一个元素的属性。 |
nextProps | CalendarNextProps | — | 属性将转发给下一个元素。 |
gridProps | CalendarGridProps | — | 转发给网格元素的属性。 |
gridHeadProps | CalendarGridHeadProps | — | 转发给网格头元素的属性。 |
gridBodyProps | CalendarGridBodyProps | — | 转发至 grid 主体元素的属性。 |
gridRowProps | CalendarGridRowProps | — | 转发给网格行元素的属性。 |
headCellProps | CalendarHeadCellProps | — | 转发至 head 单元格元素的属性。 |
cellProps | CalendarCellProps | — | 转发给 cell 元素的属性。 |
cellTriggerProps | CalendarCellTriggerProps | — | 转发给单元格触发器的属性。 |
dir | Direction | — | 组件的阅读方向。 |
locale | string | — | 语言环境。 |
modelValue | CalendarModelValue<M> | — | 当前模型值。 |
defaultValue | CalendarModelValue<M> | — | 默认值。 |
multiple | M | — | 是否支持多个值。 |
placeholder | DateValue | — | 占位符。 |
defaultPlaceholder | DateValue | — | 默认占位符。 |
disabled | boolean | — | 该组件是否已禁用。 |
readonly | boolean | — | 该组件是否为只读。 |
pagedNavigation | boolean | — | 是否启用分页导航。 |
preventDeselect | boolean | — | 是否禁止取消选择。 |
weekStartsOn | WeekStartsOn | — | 本周从...开始。 |
weekdayFormat | WeekDayFormat | — | 星期格式。 |
calendarLabel | string | — | 日历标签。 |
fixedWeeks | boolean | — | 是否为固定周数。 |
maxValue | DateValue | — | 最大值。 |
minValue | DateValue | — | 最小值。 |
numberOfMonths | number | — | 月数。 |
initialFocus | boolean | — | 是否为初始焦点。 |
isDateDisabled | DateMatcher | — | 日期是否已禁用。 |
isDateUnavailable | DateMatcher | — | 日期是否不可用。 |
nextPage | ((placeholder: DateValue) => DateValue) | — | 下一页。 |
prevPage | ((placeholder: DateValue) => DateValue) | — | 上一页。 |
disableDaysOutsideCurrentView | boolean | — | 是否禁用当前视图范围外的日期。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | CalendarModelValue<M> | 当模型值发生变化时触发。 |
update:placeholder | DateValue | 当占位符值发生变化时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
default | { modelValue: CalendarModelValue<M>; } | 默认插槽的自定义内容。 |
prev | { disabled: boolean; } | prev 槽位的自定义内容。 |
heading | CalendarHeadingSlotProps | 标题槽位的自定义内容。 |
next | { disabled: boolean; } | 下一个插槽的自定义内容。 |
head-cell | { date: Date; index: number; label: string; } | 用于“头单元格”插槽的自定义内容。 |
day | CalendarDaySlotProps | “day”时段的自定义内容。 |
CalendarCell
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
date | Date | — | 日期。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarCellTrigger
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
day | Date | — | Day。 |
month | Date | — | 月份。 |
type | ButtonType | 'button' | 按钮元素的类型。可以是 'button'、'submit' 或 'reset' 之一。 |
disabled | boolean | — | 该组件是否已禁用。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
default | CalendarCellTriggerSlotProps | 默认插槽的自定义内容。 |
CalendarCompact<M>
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
headerProps | CalendarHeaderProps | — | 转发至 header 元素的属性。 |
headingProps | CalendarHeadingProps | — | 传递给 heading 元素的属性。 |
prevProps | CalendarPrevProps | — | 转发给前一个元素的属性。 |
nextProps | CalendarNextProps | — | 属性将转发给下一个元素。 |
gridProps | CalendarGridProps | — | 转发给网格元素的属性。 |
gridHeadProps | CalendarGridHeadProps | — | 转发给网格头元素的属性。 |
gridBodyProps | CalendarGridBodyProps | — | 转发至 grid 主体元素的属性。 |
gridRowProps | CalendarGridRowProps | — | 转发给网格行元素的属性。 |
headCellProps | CalendarHeadCellProps | — | 转发至 head 单元格元素的属性。 |
cellProps | CalendarCellProps | — | 转发给 cell 元素的属性。 |
cellTriggerProps | CalendarCellTriggerProps | — | 转发给单元格触发器的属性。 |
dir | Direction | — | 组件的阅读方向。 |
locale | string | — | 语言环境。 |
modelValue | CalendarModelValue<M> | — | 当前模型值。 |
defaultValue | CalendarModelValue<M> | — | 默认值。 |
multiple | M | — | 是否支持多个值。 |
placeholder | DateValue | — | 占位符。 |
defaultPlaceholder | DateValue | — | 默认占位符。 |
disabled | boolean | — | 该组件是否已禁用。 |
readonly | boolean | — | 该组件是否为只读。 |
pagedNavigation | boolean | — | 是否启用分页导航。 |
preventDeselect | boolean | — | 是否禁止取消选择。 |
weekStartsOn | WeekStartsOn | — | 本周从...开始。 |
weekdayFormat | WeekDayFormat | — | 星期格式。 |
calendarLabel | string | — | 日历标签。 |
fixedWeeks | boolean | — | 是否为固定周数。 |
maxValue | DateValue | — | 最大值。 |
minValue | DateValue | — | 最小值。 |
numberOfMonths | number | — | 月数。 |
initialFocus | boolean | — | 是否为初始焦点。 |
isDateDisabled | DateMatcher | — | 日期是否已禁用。 |
isDateUnavailable | DateMatcher | — | 日期是否不可用。 |
nextPage | ((placeholder: DateValue) => DateValue) | — | 下一页。 |
prevPage | ((placeholder: DateValue) => DateValue) | — | 上一页。 |
disableDaysOutsideCurrentView | boolean | — | 是否禁用当前视图范围外的日期。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | CalendarModelValue<M> | 当模型值发生变化时触发。 |
update:placeholder | DateValue | 当占位符值发生变化时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
default | { modelValue: CalendarModelValue<M>; } | 默认插槽的自定义内容。 |
prev | { disabled: boolean; } | prev 槽位的自定义内容。 |
heading | CalendarHeadingSlotProps | 标题槽位的自定义内容。 |
next | { disabled: boolean; } | 下一个插槽的自定义内容。 |
head-cell | { date: Date; index: number; label: string; } | 用于“头单元格”插槽的自定义内容。 |
day | CalendarDaySlotProps | “day”时段的自定义内容。 |
CalendarGrid
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarGridBody
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarGridHead
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarGridRow
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarHeadCell
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarHeader
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarHeading
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarNext
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
nextPage | ((placeholder: DateValue) => DateValue) | — | 下一页。 |
type | ButtonType | 'button' | 按钮元素的类型。可以是 'button'、'submit' 或 'reset' 之一。 |
disabled | boolean | — | 该组件是否已禁用。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarPrev
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
prevPage | ((placeholder: DateValue) => DateValue) | — | 上一页。 |
type | ButtonType | 'button' | 按钮元素的类型。可以是 'button'、'submit' 或 'reset' 之一。 |
disabled | boolean | — | 该组件是否已禁用。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
CalendarRoot<M>
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
dir | Direction | — | 组件的阅读方向。 |
locale | string | — | 语言环境。 |
modelValue | CalendarModelValue<M> | — | 当前模型值。 |
defaultValue | CalendarModelValue<M> | — | 默认值。 |
multiple | M | — | 是否支持多个值。 |
placeholder | DateValue | — | 占位符。 |
defaultPlaceholder | DateValue | — | 默认占位符。 |
disabled | boolean | — | 该组件是否已禁用。 |
readonly | boolean | — | 该组件是否为只读。 |
pagedNavigation | boolean | — | 是否启用分页导航。 |
preventDeselect | boolean | — | 是否禁止取消选择。 |
weekStartsOn | WeekStartsOn | — | 本周从...开始。 |
weekdayFormat | WeekDayFormat | — | 星期格式。 |
calendarLabel | string | — | 日历标签。 |
fixedWeeks | boolean | — | 是否为固定周数。 |
maxValue | DateValue | — | 最大值。 |
minValue | DateValue | — | 最小值。 |
numberOfMonths | number | — | 月数。 |
initialFocus | boolean | — | 是否为初始焦点。 |
isDateDisabled | DateMatcher | — | 日期是否已禁用。 |
isDateUnavailable | DateMatcher | — | 日期是否不可用。 |
nextPage | ((placeholder: DateValue) => DateValue) | — | 下一页。 |
prevPage | ((placeholder: DateValue) => DateValue) | — | 上一页。 |
disableDaysOutsideCurrentView | boolean | — | 是否禁用当前视图范围外的日期。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | CalendarModelValue<M> | 当模型值发生变化时触发。 |
update:placeholder | DateValue | 当占位符值发生变化时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
default | CalendarRootSlotProps<M> | 默认插槽的自定义内容。 |
注意事项
架构与对标差异
CalendarRoot 通过 useControllableState 持有值(multiple 时为数组),以占位符驱动网格翻页,并把网格创建委托给 useCalendar(在 locale/周首日/占位符变化时重建网格,并以 minValue/maxValue 限制翻页)。useCalendarState 派生选中/无效状态。CalendarCellTrigger 是唯一交互部件:读取共享上下文,计算 data-focused/data-selected/data-today/data-unavailable/data-outside-view,处理 Arrow/Enter/Space 键(dir 感知方向,RTL 下 ArrowLeft/ArrowRight 互换),并在导航跨网格边界时翻到相邻月份。Compact 负责迭代月份/周/日与头部接线(prev/heading/next + 月份/年份 Select)。对标库多为单体日历面板;Aria/styled 分离、逐部件 *Props 透传与插槽驱动单元格渲染是差异点。
| 能力 | VeanUI | Ant Design | Element Plus | Mantine | Naive UI | shadcn |
|---|---|---|---|---|---|---|
| Aria/styled 分离 | ✅ | — | — | — | — | — |
| 单选 / 多选 | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| 键盘网格导航 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| RTL 方向反转 | ✅ | — | — | — | — | — |
numberOfMonths 网格 | ✅ | — | — | ✅ | — | — |
minValue/maxValue | ✅ | ✅ | ✅ | ✅ | ✅ | — |
isDateDisabled/不可用 | ✅ | ✅ | ✅ | ✅ | ✅ | — |
fixedWeeks/weekStartsOn | ✅ | — | ✅ | ✅ | — | — |
| 月份/年份 Select 控件 | ✅ | ✅ | ✅ | — | — | — |
自定义 prevPage/nextPage | ✅ | — | — | — | — | — |
逐部件 *Props 透传 | ✅ | — | — | — | — | — |
本地化 aria-label | ✅ | ✅ | ✅ | ✅ | — | — |
运行时注意事项
- 值是本地零点的原生
Date(即DateValue模型:Date或{ date, time? })。格式化/转换直接使用date-fns或Intl。 multiple会把模型形状变为DateValue[];M泛型由 prop 推断。defaultValue/defaultPlaceholder仅在挂载时读取——外部控制请使用v-model。isDateDisabled与isDateUnavailable不同:禁用日期是策略性不可选,不可用日期还会被视觉标记(默认样式line-through)。- 默认渲染当前月之外的日期;传入
disableDaysOutsideCurrentView可禁用它。 - prev/next 的
aria-label默认为 locale 消息;可通过prevProps/nextProps逐按钮覆盖。 - 导航按钮在
minValue/maxValue处自动禁用;自定义prevPage/nextPage函数参与该计算。
常见问题
如何多选日期?
传入 multiple(模板中布尔简写可用)并把 v-model 绑定到 DateValue 数组。点击切换日期;preventDeselect 阻止移除。
如何禁止今天之前的日期?
传入 minValue={new Date(2026, 0, 1)}(任意 DateValue)。越界日期被禁用,prev 按钮在边界处自动禁用。
如何同时展示多个月?
设置 numberOfMonths={2}——日历并排渲染两个月网格,跨边界时一并翻页。
为什么未处理 PageUp/PageDown?
网格导航目前覆盖方向键、Enter/Space 与跨月翻页。PageUp/PageDown/Home/End(Ant Design/Element Plus 面板具备)为已排期的增强项。
如何用此组件构建日期范围?
范围专用网格使用 SRangeCalendar;或将两个 SCalendar 以 minValue/maxValue 互相绑定另一侧的选择。