日历 Calendar
按照日历形式展示数据的容器
何时使用
- 当数据是日期或按照日期划分时,例如日程、课表、价格日历等;目前支持年/月切换
参考文档
基本使用
日期区固定渲染六周(42 格),末行补齐相邻月份日期,面板 / 卡片形态一致
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
const date = ref(Date.now())
function onChange(
value: string | number,
dateOrMonth?: CalendarDateItem['dateObject'] | CalendarMonthItem['monthObject']
) {
console.log('change', value, dateOrMonth)
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Calendar v-model:value="date" @change="onChange" @panelChange="onPanelChange" />
</template>卡片模式
卡片模式以 300px 为宽度基准,容器更窄时自适应收缩
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
const cardDate = ref(Date.now())
function onChange(
value: string | number,
dateOrMonth?: CalendarDateItem['dateObject'] | CalendarMonthItem['monthObject']
) {
console.log('change', value, dateOrMonth)
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Calendar v-model:value="cardDate" display="card" @change="onChange" @panelChange="onPanelChange" />
</template>初始模式
mode 指定面板模式(月 / 年):外部变更会同步到面板,面板内切换模式不会回写该属性
1月 | 2月 | 3月 |
4月 | 5月 | 6月 |
7月 | 8月 | 9月 |
10月 | 11月 | 12月 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
const modeDate = ref(Date.now())
const displayOptions = [
{
label: 'panel',
value: 'panel'
},
{
label: 'card',
value: 'card'
}
]
const modeDisplay = ref('card')
</script>
<template>
<Flex vertical>
<Space align="center">
display:<RadioGroup :options="displayOptions" v-model:value="modeDisplay" button button-style="solid" />
</Space>
<Calendar v-model:value="modeDate" mode="year" :display="modeDisplay" />
</Flex>
</template>自定义头部内容
头部内容行由 header 属性或 #header 插槽替换
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { CalendarOutlined } from '@ant-design/icons-vue'
const headerDate = ref(Date.now())
const headerSlotDate = ref(Date.now())
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Space>
<Calendar v-model:value="headerDate" header="Custom header" display="card" @panelChange="onPanelChange" />
<Calendar v-model:value="headerSlotDate" display="card" @panelChange="onPanelChange">
<template #header> <CalendarOutlined /> 日历 </template>
</Calendar>
</Space>
</template>头部整体替换
#headerFull 插槽接管整行头部:使用后默认的年 / 月选择器与模式切换不再渲染
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
const headerFullDate = ref(new Date(2026, 9, 15).getTime())
// 插槽不提供选项数据,年月选项由使用方自行构造
const headerFullModeOptions = [
{ label: '月', value: 'month' },
{ label: '年', value: 'year' }
]
const headerFullMonthOptions = Array.from({ length: 12 }, (_, index) => ({
label: `${index + 1}月`,
value: index + 1
}))
function getHeaderFullYearOptions(year: number): { label: string; value: number }[] {
const options: { label: string; value: number }[] = []
for (let value = year - 10; value < year + 10; value++) {
options.push({ label: `${value}年`, value })
}
return options
}
// 插槽参数只读:模式与年月变更经回调写回,年月变更以当月 15 日为代表日
function onHeaderFullTypeChange(value: unknown, onTypeChange: (mode: 'month' | 'year') => void): void {
if (value === 'month' || value === 'year') onTypeChange(value)
}
function onHeaderFullYearChange(value: unknown, month: number, onChange: (value: string | number) => void): void {
onChange(new Date(Number(value), month - 1, 15).getTime())
}
function onHeaderFullMonthChange(value: unknown, year: number, onChange: (value: string | number) => void): void {
onChange(new Date(year, Number(value) - 1, 15).getTime())
}
// 标题里的「日」取自插槽 value(与 v-model 同源),无法解析时占位
function getHeaderFullDay(value: string | number | undefined): string {
if (value === undefined || value === '') return '--'
const timestamp = Number(value)
return Number.isFinite(timestamp) ? format(timestamp, 'd') : '--'
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
): void {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Calendar v-model:value="headerFullDate" display="card" @panelChange="onPanelChange">
<template #headerFull="{ year, month, mode, value, onChange, onTypeChange }">
<div class="header-full">
<div class="header-full-title">自定义头部:{{ year }} 年 {{ month }} 月 {{ getHeaderFullDay(value) }} 日</div>
<div class="header-full-actions">
<RadioGroup
:options="headerFullModeOptions"
:value="mode"
button
size="small"
button-style="solid"
@change="(value) => onHeaderFullTypeChange(value, onTypeChange)"
/>
<Select
size="small"
:options="getHeaderFullYearOptions(year)"
:value="year"
:max-display="8"
@change="(value) => onHeaderFullYearChange(value, month, onChange)"
/>
<Select
size="small"
:options="headerFullMonthOptions"
:value="month"
:max-display="8"
@change="(value) => onHeaderFullMonthChange(value, year, onChange)"
/>
</div>
</div>
</template>
</Calendar>
</template>
<style lang="less" scoped>
/* 头部整体替换用例:标题行 + 操作行,两端分布 */
.header-full {
padding: 10px;
.header-full-title {
margin-bottom: 10px;
}
.header-full-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
}
</style>自定义一周的开始
取值 0-6,0 为周一
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import type { CalendarDayOfWeek } from 'vue-amazing-ui'
const startDayOfWeekDate = ref(Date.now())
const weekOptions = [
{
label: '周一',
value: 0
},
{
label: '周二',
value: 1
},
{
label: '周三',
value: 2
},
{
label: '周四',
value: 3
},
{
label: '周五',
value: 4
},
{
label: '周六',
value: 5
},
{
label: '周日',
value: 6
}
]
const startDayOfWeek = ref<CalendarDayOfWeek>(6)
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Flex vertical>
<Space align="center">
startDayOfWeek:<RadioGroup :options="weekOptions" v-model:value="startDayOfWeek" button button-style="solid" />
</Space>
<Calendar v-model:value="startDayOfWeekDate" :start-day-of-week="startDayOfWeek" @panelChange="onPanelChange" />
</Flex>
</template>自定义展示格式
使用 dateFormat / weekFormat / monthFormat 自定义日期 / 星期 / 月的展示格式
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 01 |
02 | 03 | 04 | 05 | 06 | 07 | 08 |
09 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 01 | 02 | 03 | 04 | 05 | 06 |
| 周一 | 周二 | 周三 | 周四 | 周五 | 周六 | 周日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
一月 | 二月 | 三月 |
四月 | 五月 | 六月 |
七月 | 八月 | 九月 |
十月 | 十一月 | 十二月 |
| Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday |
|---|---|---|---|---|---|---|
26th | 27th | 28th | 29th | 30th | 31st | 1st |
2nd | 3rd | 4th | 5th | 6th | 7th | 8th |
9th | 10th | 11th | 12th | 13th | 14th | 15th |
16th | 17th | 18th | 19th | 20th | 21st | 22nd |
23rd | 24th | 25th | 26th | 27th | 28th | 29th |
30th | 1st | 2nd | 3rd | 4th | 5th | 6th |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
const dateFormatValue = ref(Date.now())
const weekFormatValue = ref(Date.now())
const monthFormatValue = ref(Date.now())
const panelFormatValue = ref(Date.now())
function cardDateFormat(date: number, timestamp: number) {
return String(date).padStart(2, '0')
}
function cardWeekFormat(defaultWeek: CalendarDefaultWeek, week: number) {
return `周${defaultWeek}`
}
function cardMonthFormat(month: number, timestamp: number) {
const chineseNumber = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
return `${chineseNumber[month - 1]}月`
}
function panelDateFormat(date: number, timestamp: number) {
return format(timestamp, 'do')
}
function panelWeekFormat(defaultWeek: CalendarDefaultWeek, week: number, timestamp: number) {
return format(timestamp, 'EEEE')
}
function panelMonthFormat(month: number, timestamp: number) {
return format(timestamp, 'MMM')
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Flex vertical>
<Space>
<Calendar
header="Date format"
display="card"
v-model:value="dateFormatValue"
:date-format="cardDateFormat"
@panelChange="onPanelChange"
/>
<Calendar
header="Week format"
display="card"
v-model:value="weekFormatValue"
:week-format="cardWeekFormat"
@panelChange="onPanelChange"
/>
<Calendar
header="Month format"
mode="year"
display="card"
v-model:value="monthFormatValue"
:month-format="cardMonthFormat"
@panelChange="onPanelChange"
/>
</Space>
<Calendar
v-model:value="panelFormatValue"
:date-format="panelDateFormat"
:week-format="panelWeekFormat"
:month-format="panelMonthFormat"
@panelChange="onPanelChange"
/>
</Flex>
</template>自定义日期格式
指定 valueFormat 后,v-model 的值为按该格式表达的字符串
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
const dateStr = ref(format(Date.now(), 'yyyy-MM-dd'))
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Flex vertical>
<Alert type="info" :message="`You selected date: ${dateStr}`" />
<Calendar v-model:value="dateStr" value-format="yyyy-MM-dd" @panelChange="onPanelChange" />
</Flex>
</template>通知事项日历
使用 #dateValue 替换日号、#dateContent 在日号下追加内容
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8日 This is warning event. This is usual event. This is volcano event |
9 | 10 | 11 | 12日 This is warning event. This is usual event. This is volcano event | 13 | 14 | 15 |
16日 This is warning event. This is usual event. This is volcano event | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
const noticeDate = ref(Date.now())
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Calendar v-model:value="noticeDate" @panelChange="onPanelChange">
<template #dateValue="{ dateObject, timestamp }">
<span v-if="[8, 12, 16].includes(dateObject.date)">{{ dateObject.date }}日</span>
</template>
<template #dateContent="{ dateObject, timestamp }">
<template v-if="[8, 12, 16].includes(dateObject.date)">
<div class="notice-item">
<Badge status="warning" />
<Ellipsis class="notice-text" :tooltip-max-width="240">This is warning event.</Ellipsis>
</div>
<div class="notice-item">
<Badge status="success" />
<Ellipsis class="notice-text" :tooltip-max-width="240">This is usual event.</Ellipsis>
</div>
<div class="notice-item">
<Badge color="volcano" />
<Ellipsis class="notice-text" :tooltip-max-width="240">This is volcano event</Ellipsis>
</div>
</template>
</template>
<template #monthValue="{ monthObject, timestamp }">
<template v-if="[1, 7].includes(monthObject.month)">
<template v-if="monthObject.month === 1">二月</template>
<template v-if="monthObject.month === 7">八月</template>
</template>
</template>
<template #monthContent="{ monthObject, timestamp }">
<template v-if="[1, 7].includes(monthObject.month)">
<div class="notice-item">
<Badge status="warning" />
<Ellipsis class="notice-text" :tooltip-max-width="240">This is warning event.</Ellipsis>
</div>
<div class="notice-item">
<Badge status="success" />
<Ellipsis class="notice-text" :tooltip-max-width="240">This is usual event.</Ellipsis>
</div>
<div class="notice-item">
<Badge color="volcano" />
<Ellipsis class="notice-text" :tooltip-max-width="240">This is volcano event</Ellipsis>
</div>
</template>
</template>
</Calendar>
</template>
<style lang="less" scoped>
/* 每条通知事项独占一行:状态圆点固定宽度,文本交给 Ellipsis 截断并悬浮提示 */
.notice-item {
display: flex;
align-items: center;
.notice-text {
flex: 1;
min-width: 0;
}
}
</style>自定义插槽
使用 #week 插槽替换星期标题
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
|---|---|---|---|---|---|---|
26日 | 27日 | 28日 | 29日 | 30日 | 31日 | 1日 |
2日 | 3日 | 4日 | 5日 | 6日 | 7日 | 8日 |
9日 | 10日 | 11日 | 12日 | 13日 | 14日 | 15日 |
16日 | 17日 | 18日 | 19日 | 20日 | 21日 | 22日 |
23日 | 24日 | 25日 | 26日 | 27日 | 28日 | 29日 |
30日 | 1日 | 2日 | 3日 | 4日 | 5日 | 6日 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
const slotDate = ref(Date.now())
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Calendar v-model:value="slotDate" @panelChange="onPanelChange">
<template #week="{ defaultWeek, week, timestamp }">
{{ format(timestamp, 'EEE') }}
</template>
<template #dateValue="{ dateObject, timestamp }"> {{ dateObject.date }}日 </template>
<template #monthValue="{ monthObject, timestamp }">
{{ format(timestamp, 'MMMM') }}
</template>
</Calendar>
</template>整格替换
使用 #dateFull / #monthFull 替换整个单元格;整格由业务自绘,选中 / 今天等状态需在插槽内自行表达
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
1月 | 2月 | 3月 |
4月 | 5月 | 6月 |
7月 | 8月 | 9月 |
10月 | 11月 | 12月 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
const fullCellDate = ref(new Date(2026, 9, 15).getTime())
const fullMonthDate = ref(new Date(2026, 9, 15).getTime())
// 整格替换后组件不再注入选中样式,示例按选中值自行判断
function isFullCellSelected(timestamp: number): boolean {
return format(timestamp, 'yyyy-MM-dd') === format(fullCellDate.value, 'yyyy-MM-dd')
}
// 月份整格改为按年月粒度判断
function isFullMonthSelected(timestamp: number): boolean {
return format(timestamp, 'yyyy-MM') === format(fullMonthDate.value, 'yyyy-MM')
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
): void {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Calendar v-model:value="fullCellDate" @panelChange="onPanelChange">
<template #dateFull="{ dateObject, timestamp, isCurrentDate }">
<div
class="full-cell"
:class="{
'full-cell-today': isCurrentDate,
'full-cell-selected': isFullCellSelected(timestamp)
}"
>
{{ dateObject.date }}
</div>
</template>
</Calendar>
<Calendar v-model:value="fullMonthDate" mode="year" @panelChange="onPanelChange">
<template #monthFull="{ monthObject, timestamp, isCurrent }">
<div
class="full-cell"
:class="{
'full-cell-today': isCurrent,
'full-cell-selected': isFullMonthSelected(timestamp)
}"
>
{{ monthObject.month + 1 }}月
</div>
</template>
</Calendar>
</template>
<style lang="less" scoped>
/* 整格替换用例的单元格:整格由业务自绘(浅底圆角块),色值取语义变量以便跟随明暗模式 */
.full-cell {
display: flex;
align-items: center;
justify-content: center;
height: 26px;
margin: 0 4px;
color: var(--va-text-color-1);
background: var(--va-fill-color-4);
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: var(--va-fill-color-2);
}
}
/* 今天:主色描边;选中:主色实心 + 白字(整格替换下由插槽内容自行表达) */
.full-cell-today {
color: var(--va-cp-calendar-primary-color, var(--va-primary-color));
box-shadow: inset 0 0 0 1px var(--va-cp-calendar-primary-color, var(--va-primary-color));
}
.full-cell-selected {
color: #ffffff;
background: var(--va-cp-calendar-primary-color, var(--va-primary-color));
&:hover {
background: var(--va-cp-calendar-primary-color, var(--va-primary-color));
}
}
</style>选择功能
select 事件第二参为 { source },区分为年 / 月 / 日 / 头部自定义四种来源
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
import { useMessage } from 'vue-amazing-ui'
const selectDate = ref(new Date(2030, 9, 6).getTime())
const message = useMessage()
function onSelect(value: string | number | undefined, info: { source: 'year' | 'month' | 'date' | 'customize' }) {
console.log('select', value, info)
if (typeof value === 'number') message.success(format(value, 'yyyy-MM-dd'))
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Flex vertical>
<Alert type="info" :message="`You selected date: ${format(selectDate, 'yyyy-MM-dd')}`" />
<Calendar v-model:value="selectDate" @select="onSelect" @panelChange="onPanelChange" />
</Flex>
</template>禁用日期
disabledDate 入参为时间戳,返回 true 表示该日期不可选;年 / 月选择器与月格只替换年月、保留原「日」,该日不可选(命中 disabledDate 或落在 validRange 外)时收敛到同月最近的可用日
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 1 | 2 | 3 | 4 | 5 | 6 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format, subDays, addDays } from 'date-fns'
import { useMessage } from 'vue-amazing-ui'
const disableDate = ref(Date.now())
const disableWeekendDate = ref(Date.now())
const message = useMessage()
const displayOptions = [
{
label: 'panel',
value: 'panel'
},
{
label: 'card',
value: 'card'
}
]
const disabledDisplay = ref('panel')
function disabledDate(timestamp: number): boolean {
if (subDays(Date.now(), 4).getTime() < timestamp && timestamp < addDays(Date.now(), 3).getTime()) {
return true
}
return false
}
function disabledWeekend(timestamp: number): boolean {
return ['6', '7'].includes(format(timestamp, 'i'))
}
function onSelect(value: string | number | undefined, info: { source: 'year' | 'month' | 'date' | 'customize' }) {
console.log('select', value, info)
if (typeof value === 'number') message.success(format(value, 'yyyy-MM-dd'))
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Flex vertical>
<Space align="center">
display:<RadioGroup :options="displayOptions" v-model:value="disabledDisplay" button button-style="solid" />
</Space>
<Space>
<Flex vertical>
<Alert type="info" message="禁用指定日期 (以今天为准的前三天至后三天,含当天)" />
<Calendar
v-model:value="disableDate"
:disabled-date="disabledDate"
:display="disabledDisplay"
@select="onSelect"
@panelChange="onPanelChange"
/>
</Flex>
<Flex vertical>
<Alert type="info" message="禁用所有周末 (星期六 & 星期日)" />
<Calendar
v-model:value="disableWeekendDate"
:disabled-date="disabledWeekend"
:display="disabledDisplay"
@select="onSelect"
@panelChange="onPanelChange"
/>
</Flex>
</Space>
</Flex>
</template>可选日期区间
validRange 限定可选区间,区间外的日期不可选,年 / 月选择器的可选项一并收窄;字符串入参按 valueFormat 解析
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
import { useMessage } from 'vue-amazing-ui'
const rangeDate = ref(new Date(2026, 9, 15).getTime())
const validRangeValue: [number, number] = [new Date(2026, 9, 10).getTime(), new Date(2026, 9, 20).getTime()]
const message = useMessage()
function onSelect(value: string | number | undefined, info: { source: 'year' | 'month' | 'date' | 'customize' }) {
console.log('select', value, info)
if (typeof value === 'number') message.success(format(value, 'yyyy-MM-dd'))
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
): void {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Flex vertical>
<Alert type="info" message="仅允许选择 2026-10-10 ~ 2026-10-20,区间外的日期不可选" />
<Calendar v-model:value="rangeDate" :valid-range="validRangeValue" @select="onSelect" @panelChange="onPanelChange" />
</Flex>
</template>整层禁用
整月都被 disabledDate 禁用时,月份面板(mode="year")的月份格与年 / 月选择器的对应项一并禁用
1月 | 2月 | 3月 |
4月 | 5月 | 6月 |
7月 | 8月 | 9月 |
10月 | 11月 | 12月 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
import { format } from 'date-fns'
const wholeMonthDate = ref(Date.now())
function disabledMarch(timestamp: number) {
return format(timestamp, 'yyyy-MM') === '2026-03'
}
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
): void {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<Calendar v-model:value="wholeMonthDate" mode="year" :disabled-date="disabledMarch" @panelChange="onPanelChange" />
</template>键盘导航
面板可聚焦,键盘直接移动选中日期;日期面板按日粒度移动,月份面板(mode="year")按月粒度移动;目标不可选时该次移动不生效
- 日期面板:← / → 一天、↑ / ↓ 一周、PageUp / PageDown 一月、Ctrl(macOS 为 Cmd)+ ← / → 一年
- 月份面板:← / → 一月、↑ / ↓ 一行(三个月)、Ctrl(macOS 为 Cmd)+ ← / → 一年(翻页键不响应)
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
Show Code
<script lang="ts" setup>
import { ref } from 'vue'
const keyboardDate = ref(new Date(2026, 9, 15).getTime())
function onPanelChange(
value: string | number | undefined,
info: { year: number; month?: number },
mode: 'month' | 'year'
) {
console.log('panelChange', value, info, mode)
}
</script>
<template>
<ul class="keyboard-tips">
<li>日期面板:← / → 一天、↑ / ↓ 一周、PageUp / PageDown 一月、Ctrl(macOS 为 Cmd)+ ← / → 一年</li>
<li>月份面板:← / → 一月、↑ / ↓ 一行(三个月)、Ctrl(macOS 为 Cmd)+ ← / → 一年(翻页键不响应)</li>
</ul>
<Calendar v-model:value="keyboardDate" @panelChange="onPanelChange" />
</template>
<style lang="less" scoped>
.keyboard-tips {
margin: 0;
padding-left: 20px;
color: var(--va-text-color-2);
li {
line-height: 1.8;
}
}
</style>APIs
Calendar Props
受控 / 非受控:
value传入即受控(值以父为准,组件只上报update:value);不传则由组件自持,交互照常生效,可用defaultValue指定初值(同时决定初始面板日期)。
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| value v-model | 当前被选中的日期;传入即受控,不传时组件自持(可用 defaultValue 指定初值) | string | number | undefined |
| defaultValue | 非受控初始值:同时决定初始面板日期与初始选中值,受控 value 优先 | string | number | undefined |
| valueFormat | 被选中日期的格式,默认为时间戳;参考 format | string | undefined |
| header | 自定义日历头部内容 | string | undefined |
| display | 日历展示方式,面板 / 卡片 | 'panel' | 'card' | 'panel' |
| mode | 面板模式(受控):月 / 年 | 'month' | 'year' | 'month' |
| startDayOfWeek | 一周的开始是星期几,0-6,0 是周一(越界值按 7 取模收敛) | CalendarDayOfWeek | 0 |
| disabledDate | 不可选择的日期 | (timestamp: number) => boolean | undefined |
| validRange | 可选日期区间,区间外的日期不可选,同时收窄年 / 月选择器的可选项;字符串入参按 valueFormat 解析(未指定时按时间戳数字串解析,无法解析则忽略该区间) | [string | number, string | number] | undefined |
| dateFormat | 自定义日期展示格式 | (date: number, timestamp: number) => string | undefined |
| weekFormat | 自定义星期展示格式 | (defaultWeek: CalendarDefaultWeek, week: number, timestamp: number) => string | undefined |
| monthFormat | 自定义月展示格式 | (month: number, timestamp: number) => string | undefined |
| yearSelectProps | 年选择器 props,参考 Select Props | SelectProps | {} |
| monthSelectProps | 月选择器 props,参考 Select Props | SelectProps | {} |
| modeRadioProps | 模式切换器 props,参考 RadioGroup Props | RadioGroupProps | {} |
CalendarDayOfWeek Type
| 名称 | 值 |
|---|---|
| DayOfWeek | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
CalendarDefaultWeek Type
| 名称 | 值 |
|---|---|
| DefaultWeek | '一' | '二' | '三' | '四' | '五' | '六' | '日' |
CalendarDateItem Type
| 名称 | 说明 | 类型 |
|---|---|---|
| type | 类型 | 'date' |
| dateObject | 日期对象 | { date: number, month: number, year: number } |
| timestamp | 当天开始的时间戳 | number |
| inCurrentMonth | 是否在当前月 | boolean |
| isCurrentDate | 是否为今天 | boolean |
CalendarMonthItem Type
| 名称 | 说明 | 类型 |
|---|---|---|
| type | 类型 | 'month' |
| monthObject | 月份对象 | { month: number, year: number } |
| timestamp | 当月开始的时间戳 | number |
| isCurrent | 是否为当前月 | boolean |
CalendarSelectInfo Type
| 名称 | 说明 | 类型 |
|---|---|---|
| source | 选择来源:年选择器 / 月(选择器或月面板格)/ 日期面板格 / 头部整体替换 | 'year' | 'month' | 'date' | 'customize' |
Slots
| 名称 | 说明 | 用法 |
|---|---|---|
| header | 自定义日历头部内容 | v-slot:header |
| headerFull | 替换整个头部:使用后默认的年 / 月选择器与模式切换不再渲染,插槽提供当前面板年月 / 模式、当前选中值与写回回调 | v-slot:headerFull="{ year, month, mode, value, onChange, onTypeChange }" |
| week | 自定义周展示 | v-slot:week="{ defaultWeek, week, timestamp }" |
| dateValue | 自定义日期展示 | v-slot:dateValue="{ type, dateObject, timestamp, inCurrentMonth, isCurrentDate }" |
| dateContent | 自定义日期内容展示 | v-slot:dateContent="{ type, dateObject, timestamp, inCurrentMonth, isCurrentDate }" |
| dateFull | 替换整个日期单元格,插槽参数为 CalendarDateItem | v-slot:dateFull="{ type, dateObject, timestamp, inCurrentMonth, isCurrentDate }" |
| monthValue | 自定义月份展示 | v-slot:monthValue="{ type, monthObject, timestamp, isCurrent }" |
| monthContent | 自定义月份内容展示 | v-slot:monthContent="{ type, monthObject, timestamp, isCurrent }" |
| monthFull | 替换整个月份单元格,插槽参数为 CalendarMonthItem | v-slot:monthFull="{ type, monthObject, timestamp, isCurrent }" |
Events
| 名称 | 说明 | 类型 |
|---|---|---|
| change | 日期变化时的回调(重复点击已选中的日期不触发) | (value: string | number, dateObject?: CalendarDateItem['dateObject'] | CalendarMonthItem['monthObject']) => void |
| panelChange | 面板定位变化时的回调(切换年 / 月选择器、切换模式、跨月选择均会触发) | (value: string | number | undefined, info: { year: number, month?: number }, mode: 'month' | 'year') => void |
| select | 选择日期回调,第二参携带来源信息 | (value: string | number | undefined, info: CalendarSelectInfo) => void |