Calendar
Calendar是基于 Vant 官方Calendar做的表单封装,目标是尽量兼容官方 props / slots / events,同时保留“弹层状态内置、用户不用手动管理”的交互。
与官方的差异
- 当前封装不需要也不建议手动传
show - 当前固定使用弹层模式,不暴露
poppable - 当前不对外暴露实例方法
- 字段展示区默认格式为
YYYY-MM-DD,仅在点击确认后同步modelValue
选择切换模式
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { juneEnd, marchStart } from './shared'
const form = createForm({
values: {
scheduleDate: new Date(2026, 5, 18),
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="scheduleDate"
title="选择切换模式"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
switchMode: 'year-month',
minDate: marchStart,
maxDate: juneEnd,
},
]"
/>
</div>
</FormProvider>
</template>选择单个日期 / 多个日期 / 日期区间
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { aprilEnd, marchStart } from './shared'
const form = createForm({
values: {
singleDate: new Date(2026, 2, 23),
multipleDates: [
new Date(2026, 2, 5),
new Date(2026, 2, 12),
new Date(2026, 2, 19),
],
tripRange: [new Date(2026, 2, 24), new Date(2026, 2, 28)],
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="singleDate"
title="选择单个日期"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
minDate: marchStart,
maxDate: aprilEnd,
},
]"
/>
<Field
name="multipleDates"
title="选择多个日期"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
type: 'multiple',
minDate: marchStart,
maxDate: aprilEnd,
},
]"
/>
<Field
name="tripRange"
title="选择日期区间"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
type: 'range',
minDate: marchStart,
maxDate: aprilEnd,
},
]"
/>
</div>
</FormProvider>
</template>快捷选择
vue
<script setup lang="ts">
import type { CalendarModelValue } from '@silver-formily/vant'
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { showToast } from 'vant'
import { aprilEnd, formatModelValue, marchStart } from './shared'
const form = createForm({
values: {
expressDate: null,
},
})
function onConfirm(value: CalendarModelValue) {
showToast(`已确认:${formatModelValue(value)}`)
}
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="expressDate"
title="快捷选择"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
minDate: marchStart,
maxDate: aprilEnd,
showConfirm: false,
placeholder: '选择后会立即确认',
onConfirm,
},
]"
/>
</div>
</FormProvider>
</template>自定义颜色
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { aprilEnd, marchStart } from './shared'
const form = createForm({
values: {
specialDate: new Date(2026, 3, 8),
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="specialDate"
title="自定义颜色"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
color: '#ee0a24',
minDate: marchStart,
maxDate: aprilEnd,
},
]"
/>
</div>
</FormProvider>
</template>自定义日期范围
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { marchCustomMax, marchCustomMin } from './shared'
const form = createForm({
values: {
limitedDate: new Date(2026, 2, 15),
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="limitedDate"
title="自定义日期范围"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
minDate: marchCustomMin,
maxDate: marchCustomMax,
placeholder: '仅可选择 03-10 至 03-20',
},
]"
/>
</div>
</FormProvider>
</template>自定义按钮文字
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { aprilEnd, marchStart } from './shared'
const form = createForm({
values: {
stayRange: null,
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="stayRange"
title="自定义按钮文字"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
type: 'range',
minDate: marchStart,
maxDate: aprilEnd,
confirmText: '完成',
confirmDisabledText: '请选择结束时间',
},
]"
/>
</div>
</FormProvider>
</template>自定义日期文案
vue
<script setup lang="ts">
import type { CalendarDayItem } from '@silver-formily/vant'
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { mayEnd, mayStart } from './shared'
const form = createForm({
values: {
holidayRange: [new Date(2026, 4, 1), new Date(2026, 4, 4)],
},
})
function formatter(day: CalendarDayItem) {
const month = day.date.getMonth() + 1
const date = day.date.getDate()
if (month === 5) {
if (date === 1) {
day.topInfo = '劳动节'
}
else if (date === 4) {
day.topInfo = '青年节'
}
else if (date === 11) {
day.text = '今天'
}
}
if (day.type === 'start') {
day.bottomInfo = '入住'
}
else if (day.type === 'end') {
day.bottomInfo = '离店'
}
return day
}
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="holidayRange"
title="自定义日期文案"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
type: 'range',
minDate: mayStart,
maxDate: mayEnd,
formatter,
},
]"
/>
</div>
</FormProvider>
</template>自定义插槽
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { mayEnd, mayStart } from './shared'
const form = createForm({
values: {
tripRange: [new Date(2026, 4, 18), new Date(2026, 4, 21)],
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="tripRange"
title="自定义插槽"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
type: 'range',
minDate: mayStart,
maxDate: mayEnd,
},
]"
>
<template #title>
<div class="calendar-slot-title">
行程日历
</div>
</template>
<template #bottom-info="day">
<span
v-if="day.date?.getDate?.() === 18"
class="calendar-slot-tip"
>
出发
</span>
<span
v-else-if="day.date?.getDate?.() === 21"
class="calendar-slot-tip"
>
返程
</span>
</template>
<template #footer>
<div class="calendar-slot-footer">
已透传官方插槽,可在这里放业务说明或快捷提示
</div>
</template>
</Field>
</div>
</FormProvider>
</template>
<style scoped>
.calendar-slot-title {
padding: 8px 0;
font-size: 16px;
font-weight: 600;
letter-spacing: 0.04em;
}
.calendar-slot-tip {
font-size: 10px;
color: var(--van-primary-color);
}
.calendar-slot-footer {
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
font-size: 12px;
line-height: 1.5;
color: var(--van-text-color-2);
background: linear-gradient(180deg, rgba(250, 250, 250, 0) 0%, rgba(247, 248, 250, 1) 100%);
}
</style>自定义弹出位置
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { aprilEnd, marchStart } from './shared'
const form = createForm({
values: {
asideDate: new Date(2026, 2, 27),
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="asideDate"
title="自定义弹出位置"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
position: 'right',
round: false,
minDate: marchStart,
maxDate: aprilEnd,
},
]"
/>
</div>
</FormProvider>
</template>日期区间最大范围
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { aprilEnd, marchStart } from './shared'
const form = createForm({
values: {
shortTrip: null,
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="shortTrip"
title="日期区间最大范围"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
type: 'range',
minDate: marchStart,
maxDate: aprilEnd,
maxRange: 3,
rangePrompt: '最多选择 3 天',
},
]"
/>
</div>
</FormProvider>
</template>自定义周起始日
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Calendar, FormItem } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { aprilEnd, marchStart } from './shared'
const form = createForm({
values: {
mondayFirstDate: new Date(2026, 2, 23),
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="mondayFirstDate"
title="自定义周起始日"
:decorator="[FormItem, { isLink: true }]"
:component="[
Calendar,
{
minDate: marchStart,
maxDate: aprilEnd,
firstDayOfWeek: 1,
},
]"
/>
</div>
</FormProvider>
</template>平铺展示
官方文档里还有一个依赖 poppable=false 的“平铺展示”示例,但当前封装固定为弹层模式,因此这里不提供对应 demo。
API
使用约定
- 推荐和
FormItem搭配使用,由FormItem负责 label、箭头、反馈等壳层展示 - 未确认的临时选择会在关闭弹层时回滚
- 当前不支持通过组件
ref调用官方Calendar实例方法
封装补充 Props
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
modelValue | Date | Date[] | null | 当前选中值 | - |
placeholder | string | 未选择时的展示文案 | 单选为“请选择日期”,区间为“请选择日期范围” |
displayFormatter | Function | 自定义字段展示区文案 | - |
disabled | boolean | 禁用状态 | false |
官方基础 Props
以下官方属性已透传:
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
type | enum | 选择类型 | 官方默认值 |
switchMode | enum | 切换模式 | 官方默认值 |
title | string | 日历标题 | 官方默认值 |
color | string | 主题色 | 官方默认值 |
minDate | Date | 可选择的最小日期 | 官方默认值 |
maxDate | Date | 可选择的最大日期 | 官方默认值 |
defaultDate | Date | Date[] | null | 默认选中的日期 | 官方默认值 |
rowHeight | number | string | 日期行高 | 官方默认值 |
formatter | Function | 日期格式化函数 | - |
lazyRender | boolean | 是否只渲染可视区域 | 官方默认值 |
showMark | boolean | 是否显示月份背景水印 | 官方默认值 |
showTitle | boolean | 是否展示标题 | 官方默认值 |
showSubtitle | boolean | 是否展示副标题 | 官方默认值 |
showConfirm | boolean | 是否展示确认按钮 | 官方默认值 |
readonly | boolean | 只读状态 | 官方默认值 |
confirmText | string | 确认按钮文字 | 官方默认值 |
confirmDisabledText | string | 确认按钮禁用时的文字 | 官方默认值 |
firstDayOfWeek | number | string | 周起始日 | 官方默认值 |
官方弹层 Props
虽然这里固定是弹层模式,但下列官方弹层属性仍然可用:
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
position | enum | 弹出位置 | 官方默认值 |
round | boolean | 是否显示圆角弹窗 | 官方默认值 |
closeOnPopstate | boolean | 回退时是否自动关闭 | 官方默认值 |
closeOnClickOverlay | boolean | 点击遮罩后是否关闭 | 官方默认值 |
safeAreaInsetTop | boolean | 是否开启顶部安全区适配 | 官方默认值 |
safeAreaInsetBottom | boolean | 是否开启底部安全区适配 | 官方默认值 |
teleport | string | Element | 指定挂载节点 | 官方默认值 |
官方 Range / Multiple Props
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
maxRange | number | string | 最多可选天数 | 官方默认值 |
rangePrompt | string | 超范围提示文案 | 官方默认值 |
showRangePrompt | boolean | 是否展示超范围提示 | 官方默认值 |
allowSameDay | boolean | 区间模式是否允许同一天 | 官方默认值 |
官方 Slots
以下官方插槽已转发:
| 插槽名 | 描述 | 插槽参数 |
|---|---|---|
title | 自定义标题 | - |
subtitle | 自定义日历副标题 | object |
month-title | 自定义每个月份的小标题 | object |
footer | 自定义底部区域内容 | - |
confirm-text | 自定义确认按钮内容 | object |
top-info | 自定义日期上方提示信息 | day |
bottom-info | 自定义日期下方提示信息 | day |
text | 自定义日期内容 | day |
prev-month | 自定义上个月按钮 | object |
prev-year | 自定义上一年按钮 | object |
next-month | 自定义下个月按钮 | object |
next-year | 自定义下一年按钮 | object |
Events
| 事件名 | 描述 | 回调参数 |
|---|---|---|
update:modelValue | 点击确认后同步字段值 | Function |
select | 选中任意日期时触发 | Function |
confirm | 日期选择完成后触发 | Function |
open | 弹层打开时触发 | - |
close | 弹层关闭时触发 | - |
opened | 弹层打开且动画结束后触发 | - |
closed | 弹层关闭且动画结束后触发 | - |
unselect | 多选模式取消选中时触发 | Function |
monthShow | 月份进入可视区域时触发 | Function |
overRange | 超出最大范围时触发 | - |
clickSubtitle | 点击副标题时触发 | Function |
clickDisabledDate | 点击禁用日期时触发 | Function |
clickOverlay | 点击遮罩层时触发 | Function |
panelChange | 面板切换时触发 | Function |
update:show | 弹层开关变化时触发 | Function |
参考
本页 props / slots / events 以 Vant 官方仓库当前 main 分支文档为参考: