日历 Calendar 
按照日历形式展示数据的容器
何时使用 
- 当数据是日期或按照日期划分时,例如日程、课表、价格日历等;目前支持年/月切换
 
参考文档 
基本使用 
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
const date = ref(Date.now())
watchEffect(() => {
  console.log('date', date.value)
})
function onChange(
  date: string | number,
  dateOrMonth: CalendarDateItem['dateObject'] | CalendarMonthItem['monthObject']
) {
  console.log('change', date, dateOrMonth)
}
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, info, mode)
}
</script>
<template>
  <Calendar v-model:value="date" @change="onChange" @panelChange="onPanelChange" />
</template>卡片模式 
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
const cardDate = ref(Date.now())
watchEffect(() => {
  console.log('cardDate', cardDate.value)
})
function onChange(
  date: string | number,
  dateOrMonth: CalendarDateItem['dateObject'] | CalendarMonthItem['monthObject']
) {
  console.log('change', date, dateOrMonth)
}
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, info, mode)
}
</script>
<template>
  <Calendar v-model:value="cardDate" display="card" @change="onChange" @panelChange="onPanelChange" />
</template>初始模式 
 display:
1月  | 2月  | 3月  | 
4月  | 5月  | 6月  | 
7月  | 8月  | 9月  | 
10月  | 11月  | 12月  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
const modeDate = ref(Date.now())
const displayOptions = [
  {
    label: 'panel',
    value: 'panel'
  },
  {
    label: 'card',
    value: 'card'
  }
]
const modeDisplay = ref('card')
watchEffect(() => {
  console.log('modeDate', modeDate.value)
})
</script>
<template>
  <Flex vertical>
    <Space align="center">
      display:<Radio :options="displayOptions" v-model:value="modeDisplay" button button-style="solid" />
    </Space>
    <Calendar v-model:value="modeDate" mode="year" :display="modeDisplay" />
  </Flex>
</template>自定义头部内容 
Custom header
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
 日历 
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import { CalendarOutlined } from '@ant-design/icons-vue'
const headerDate = ref(Date.now())
watchEffect(() => {
  console.log('headerDate', headerDate.value)
})
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, info, mode)
}
</script>
<template>
  <Space>
    <Calendar v-model:value="headerDate" header="Custom header" display="card" @panelChange="onPanelChange" />
    <Calendar v-model:value="headerDate" display="card" @panelChange="onPanelChange">
      <template #header> <CalendarOutlined /> 日历 </template>
    </Calendar>
  </Space>
</template>自定义一周的开始 
 startDayOfWeek:
| 日 | 一 | 二 | 三 | 四 | 五 | 六 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import type { CalendarDayOfWeek } from 'composed-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)
watchEffect(() => {
  console.log('startDayOfWeekDate', startDayOfWeekDate.value)
})
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, info, mode)
}
</script>
<template>
  <Flex vertical>
    <Space align="center">
      startDayOfWeek:<Radio :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>自定义展示格式 
使用 weekFormat / dateFormat / monthFormat 自定义日期/星期/月的展示格式
Date format
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Week format
| 周一 | 周二 | 周三 | 周四 | 周五 | 周六 | 周日 | 
|---|---|---|---|---|---|---|
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  | 
Month format
一月  | 二月  | 三月  | 
四月  | 五月  | 六月  | 
七月  | 八月  | 九月  | 
十月  | 十一月  | 十二月  | 
| Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import { format } from 'date-fns'
const formatDate = ref(Date.now())
watchEffect(() => {
  console.log('formatDate', formatDate.value)
})
function cardDateFormat(date: number, timestamp: number) {
  return String(date).padStart(2, '0')
}
function cardWeekFormat(defaultWeek: CalendarDefaultWeek, week: CalendarDayOfWeek) {
  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: CalendarDayOfWeek, timestamp: number) {
  return format(timestamp, 'EEEE')
}
function panelMonthFormat(month: number, timestamp: number) {
  return format(timestamp, 'MMM')
}
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, info, mode)
}
</script>
<template>
  <Flex vertical>
    <Space>
      <Calendar
        header="Date format"
        display="card"
        v-model:value="formatDate"
        :date-format="cardDateFormat"
        @panelChange="onPanelChange"
      />
      <Calendar
        header="Week format"
        display="card"
        v-model:value="formatDate"
        :week-format="cardWeekFormat"
        @panelChange="onPanelChange"
      />
      <Calendar
        header="Month format"
        mode="year"
        display="card"
        v-model:value="formatDate"
        :month-format="cardMonthFormat"
        @panelChange="onPanelChange"
      />
    </Space>
    <Calendar
      v-model:value="formatDate"
      :date-format="panelDateFormat"
      :week-format="panelWeekFormat"
      :month-format="panelMonthFormat"
      @panelChange="onPanelChange"
    />
  </Flex>
</template>通知事项日历 
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
const noticeDate = ref(Date.now())
watchEffect(() => {
  console.log('noticeDate', noticeDate.value)
})
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, 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)">
        <Badge status="warning" text="This is warning event." />
        <Badge status="success" text="This is usual event." />
        <Badge color="volcano" text="This is volcano event" />
      </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)">
        <Badge status="warning" text="This is warning event." />
        <Badge status="success" text="This is usual event." />
        <Badge color="volcano" text="This is volcano event" />
      </template>
    </template>
  </Calendar>
</template>
<style lang="less" scoped>
.badge-wrap {
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
</style>自定义插槽 
| Mon | Tue | Wed | Thu | Fri | Sat | Sun | 
|---|---|---|---|---|---|---|
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日   | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import { format } from 'date-fns'
const slotDate = ref(Date.now())
watchEffect(() => {
  console.log('slotDate', slotDate.value)
})
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, 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>选择功能 
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import { format } from 'date-fns'
const selectDate = ref(new Date('2030-10-06').getTime())
const message = ref()
watchEffect(() => {
  console.log('selectDate', selectDate.value)
})
function onSelect(date: string | number, source: 'date' | 'month') {
  console.log('select', date, source)
  message.value.success(format(date, 'yyyy-MM-dd'))
}
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, 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>
  <Message ref="message" />
</template>禁用日期 
 display:
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import { format, subDays, addDays } from 'date-fns'
const disableDate = ref(Date.now())
const message = ref()
const displayOptions = [
  {
    label: 'panel',
    value: 'panel'
  },
  {
    label: 'card',
    value: 'card'
  }
]
const disabledDisplay = ref('panel')
watchEffect(() => {
  console.log('disableDate', disableDate.value)
})
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(date: string | number, source: 'date' | 'month') {
  console.log('select', date, source)
  message.value.success(format(date, 'yyyy-MM-dd'))
}
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, info, mode)
}
</script>
<template>
  <Flex vertical>
    <Space align="center">
      display:<Radio :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="disableDate"
          :disabled-date="disabledWeekend"
          :display="disabledDisplay"
          @select="onSelect"
          @panelChange="onPanelChange"
        />
      </Flex>
    </Space>
  </Flex>
  <Message ref="message" />
</template>自定义日期格式 
| 一 | 二 | 三 | 四 | 五 | 六 | 日 | 
|---|---|---|---|---|---|---|
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  | 
Show Code
vue
<script lang="ts" setup>
import { ref, watchEffect } from 'vue'
import { format } from 'date-fns'
const dateStr = ref(format(Date.now(), 'yyyy-MM-dd'))
watchEffect(() => {
  console.log('dateStr', dateStr.value)
})
function onPanelChange(date: string | number, info: { year: number; month?: number }, mode: 'month' | 'year') {
  console.log('panelChange', date, 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>APIs 
Calendar 
| 参数 | 说明 | 类型 | 默认值 | 
|---|---|---|---|
| display | 日历展示方式,面板/卡片 | 'panel' | 'card' | 'panel' | 
| mode | 初始模式 | 'month' | 'year' | 'month' | 
| header | 自定义日历头部内容 | string | slot | undefined | 
| yearSelectProps | 年选择器 props,参考 Select Props | object | {} | 
| monthSelectProps | 月选择器 props,参考 Select Props | object | {} | 
| modeRadioProps | 模式切换器 props,参考 Radio Props | object | {} | 
| startDayOfWeek | 一周的开始是星期几,0-6,0 是周一 | DayOfWeek | 0 | 
| dateStrip | 日历面板默认会显示六周的日期,当最后一周的日期不包含当月日期时,是否去掉 | boolean | true | 
| dateFormat | 自定义日期展示格式 | (date: number, timestamp: number) => string | undefined | 
| weekFormat | 自定义星期展示格式 (defaultWeek: DefaultWeek, week: number, timestamp: number) => string | undefined | |
| monthFormat | 自定义月展示格式 | (month: number, timestamp: number) => string | undefined | 
| disabledDate | 不可选择的日期 | (timestamp: number) => boolean | undefined | 
| valueFormat | 被选中日期的格式,默认为时间戳;参考 format | string | undefined | 
| value  v-model  | 当前被选中的日期 | string | number | undefined | 
DayOfWeek Type 
| 名称 | 值 | 
|---|---|
| DayOfWeek | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 
DefaultWeek Type 
| 名称 | 值 | 
|---|---|
| DefaultWeek | '一' | '二' | '三' | '四' | '五' | '六' | '日' | 
DateItem Type 
| 名称 | 说明 | 类型 | 默认值 | 
|---|---|---|---|
| type | 类型 | 'date' | undefined | 
| dateObject | 日期对象 | { date: number, month: number, year: number } | undefined | 
| timestamp | 当天开始的时间戳 | number | undefined | 
| inCurrentMonth | 是否在当前月 | boolean | undefined | 
| isCurrentDate | 是否为今天 | boolean | undefined | 
MonthItem Type 
| 名称 | 说明 | 类型 | 默认值 | 
|---|---|---|---|
| type | 类型 | 'month' | undefined | 
| monthObject | 月份对象 | { month: number, year: number } | undefined | 
| timestamp | 当月开始的时间戳 | number | undefined | 
| isCurrent | 是否为当前月 | boolean | undefined | 
Slots 
| 名称 | 说明 | 类型 | 
|---|---|---|
| header | 自定义日历头部内容 | v-slot:header | 
| 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 }" | 
| monthValue | 自定义月份展示 | v-slot:dateValue="{ type, monthObject, timestamp, isCurrent }" | 
| monthContent | 自定义月份内容展示 | v-slot:dateContent="{ type, monthObject, timestamp, isCurrent }" | 
Events 
| 名称 | 说明 | 类型 | 
|---|---|---|
| change | 日期变化时的回调 | (date: string | number, dateOrMonth: DateItem['dateObject'] | MonthItem['monthObject']) => void | 
| panelChange | 日期面板变化的回调 | (date: string | number, info: { year: number, month?: number }, mode: 'month' | 'year') => void | 
| select | 选择日期回调,包含来源信息 | (date: string | number, source: 'date' | 'month') => void |