Skip to content

日历 Calendar ​

赞助

按照日历形式展示数据的容器

何时使用 ​

  • 当数据是日期或按照日期划分时,例如日程、课表、价格日历等;目前支持年/月切换

参考文档 ​

基本使用 ​

日期区固定渲染六周(42 格),末行补齐相邻月份日期,面板 / 卡片形态一致


2026年
2026年
11月
11月
一二三四五六日
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
vue
<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 为宽度基准,容器更窄时自适应收缩


2026年
2026年
11月
11月
一二三四五六日
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
vue
<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 指定面板模式(月 / 年):外部变更会同步到面板,面板内切换模式不会回写该属性


display:
2026年
2026年
1月
2月
3月
4月
5月
6月
7月
8月
9月
10月
11月
12月
Show Code
vue
<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 插槽替换


Custom header
2026年
2026年
11月
11月
一二三四五六日
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
日历
2026年
2026年
11月
11月
一二三四五六日
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
vue
<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 插槽接管整行头部:使用后默认的年 / 月选择器与模式切换不再渲染


自定义头部:2026 年 10 月 15 日
2026年
2026年
10月
10月
一二三四五六日
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
vue
<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 为周一


startDayOfWeek:
2026年
2026年
11月
11月
日一二三四五六
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
vue
<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 自定义日期 / 星期 / 月的展示格式


Date format
2026年
2026年
11月
11月
一二三四五六日
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
Week format
2026年
2026年
11月
11月
周一周二周三周四周五周六周日
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
Month format
2026年
2026年
一月
二月
三月
四月
五月
六月
七月
八月
九月
十月
十一月
十二月
2026年
2026年
11月
11月
MondayTuesdayWednesdayThursdayFridaySaturdaySunday
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
vue
<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 的值为按该格式表达的字符串


You selected date: 2026-11-07
2026年
2026年
11月
11月
一二三四五六日
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
vue
<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 在日号下追加内容


2026年
2026年
11月
11月
一二三四五六日
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
vue
<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 插槽替换星期标题


2026年
2026年
11月
11月
MonTueWedThuFriSatSun
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
vue
<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 替换整个单元格;整格由业务自绘,选中 / 今天等状态需在插槽内自行表达


2026年
2026年
10月
10月
一二三四五六日
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

2026年
2026年
1月
2月
3月
4月
5月
6月
7月
8月
9月
10月
11月
12月
Show Code
vue
<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 },区分为年 / 月 / 日 / 头部自定义四种来源


You selected date: 2030-10-06
2030年
2030年
10月
10月
一二三四五六日
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
vue
<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 外)时收敛到同月最近的可用日


display:
禁用指定日期 (以今天为准的前三天至后三天,含当天)
2026年
2026年
11月
11月
一二三四五六日
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
禁用所有周末 (星期六 & 星期日)
2026年
2026年
11月
11月
一二三四五六日
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
vue
<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 解析


仅允许选择 2026-10-10 ~ 2026-10-20,区间外的日期不可选
2026年
2026年
10月
10月
一二三四五六日
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
vue
<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")的月份格与年 / 月选择器的对应项一并禁用


2026年
2026年
1月
2月
3月
4月
5月
6月
7月
8月
9月
10月
11月
12月
Show Code
vue
<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)+ ← / → 一年(翻页键不响应)
2026年
2026年
10月
10月
一二三四五六日
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
vue
<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 | numberundefined
defaultValue非受控初始值:同时决定初始面板日期与初始选中值,受控 value 优先string | numberundefined
valueFormat被选中日期的格式,默认为时间戳;参考 formatstringundefined
header自定义日历头部内容stringundefined
display日历展示方式,面板 / 卡片'panel' | 'card''panel'
mode面板模式(受控):月 / 年'month' | 'year''month'
startDayOfWeek一周的开始是星期几,0-6,0 是周一(越界值按 7 取模收敛)CalendarDayOfWeek0
disabledDate不可选择的日期(timestamp: number) => booleanundefined
validRange可选日期区间,区间外的日期不可选,同时收窄年 / 月选择器的可选项;字符串入参按 valueFormat 解析(未指定时按时间戳数字串解析,无法解析则忽略该区间)[string | number, string | number]undefined
dateFormat自定义日期展示格式(date: number, timestamp: number) => stringundefined
weekFormat自定义星期展示格式(defaultWeek: CalendarDefaultWeek, week: number, timestamp: number) => stringundefined
monthFormat自定义月展示格式(month: number, timestamp: number) => stringundefined
yearSelectProps年选择器 props,参考 Select PropsSelectProps{}
monthSelectProps月选择器 props,参考 Select PropsSelectProps{}
modeRadioProps模式切换器 props,参考 RadioGroup PropsRadioGroupProps{}

CalendarDayOfWeek Type ​

名称值
DayOfWeek0 | 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替换整个日期单元格,插槽参数为 CalendarDateItemv-slot:dateFull="{ type, dateObject, timestamp, inCurrentMonth, isCurrentDate }"
monthValue自定义月份展示v-slot:monthValue="{ type, monthObject, timestamp, isCurrent }"
monthContent自定义月份内容展示v-slot:monthContent="{ type, monthObject, timestamp, isCurrent }"
monthFull替换整个月份单元格,插槽参数为 CalendarMonthItemv-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

Released under the MIT License.