展示类

日历

日历组件,允许以日/周/月视图展示对应事件

代码演示

如何引入

<script setup lang="ts">
    import { Calendar } from '@fibrous/semi-ui-vue';
</script>

日视图

日视图的日历模板,可通过 showCurrTime 控制是否显示当前时间的位置红线。

全天
上午1时
上午2时
上午3时
上午4时
上午5时
上午6时
上午7时
上午8时
上午9时
上午10时
上午11时
下午12时
下午1时
下午2时
下午3时
下午4时
下午5时
下午6时
下午7时
下午8时
下午9时
下午10时
下午11时
代码
<script setup lang="ts">
    import { Calendar } from '@fibrous/semi-ui-vue';

    // A fixed date keeps the server render and the browser on the same first frame
    const displayValue = new Date(2026, 9, 25);
</script>

<template>
    <Calendar :displayValue="displayValue" mode="day" />
</template>

周视图

周视图的日历模板,可通过 showCurrTime 控制是否显示当前时间的位置红线。

10月
25周日
26周一
27周二
28周三
29周四
30周五
31周六
全天
上午1时
上午2时
上午3时
上午4时
上午5时
上午6时
上午7时
上午8时
上午9时
上午10时
上午11时
下午12时
下午1时
下午2时
下午3时
下午4时
下午5时
下午6时
下午7时
下午8时
下午9时
下午10时
下午11时
代码
<script setup lang="ts">
    import { Calendar } from '@fibrous/semi-ui-vue';

    // A fixed date keeps the server render and the browser on the same first frame
    const displayValue = new Date(2026, 9, 25);
</script>

<template>
    <Calendar :displayValue="displayValue" mode="week" />
</template>

月视图

月视图的日历模板。

周日
周一
周二
周三
周四
周五
周六
27
28
29
30
10月 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
代码
<script setup lang="ts">
    import { Calendar } from '@fibrous/semi-ui-vue';

    // A fixed date keeps the server render and the browser on the same first frame
    const displayValue = new Date(2026, 9, 25);
</script>

<template>
    <Calendar :displayValue="displayValue" mode="month" />
</template>

设置周起始日

可以通过 weekStartsOn 设置周几作为每周第一天,0 代表周日,1 代表周一,以此类推。默认为周日。对月视图、周视图生效。

周日
周一
周二
周三
周四
周五
周六
27
28
29
30
10月 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
代码
<script setup lang="ts">
    import { ref } from 'vue';
    import { Calendar, Radio, RadioGroup, type CalendarWeekStartsOn, type RadioValue } from '@fibrous/semi-ui-vue';

    const weekStartsOn = ref<RadioValue | undefined>(0);
    const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
    // A fixed date keeps the server render and the browser on the same first frame
    const displayValue = new Date(2026, 9, 25);
</script>

<template>
    <div>
        <RadioGroup v-model="weekStartsOn" aria-label="周起始日" name="demo-radio-group-vertical" type="button">
            <Radio v-for="(day, index) in days" :key="day" :value="index">{{ day }}</Radio>
        </RadioGroup>
        <Calendar :displayValue="displayValue" mode="month" :style="{ marginTop: '20px' }" :weekStartsOn="weekStartsOn as CalendarWeekStartsOn" />
    </div>
</template>

多日视图

多日视图模式。 range 必传,左闭右开。

9月
26周六
27周日
28周一
29周二
30周三
全天
上午1时
上午2时
上午3时
上午4时
上午5时
上午6时
上午7时
上午8时
上午9时
上午10时
上午11时
下午12时
下午1时
下午2时
下午3时
下午4时
下午5时
下午6时
下午7时
下午8时
下午9时
下午10时
下午11时
代码
<script setup lang="ts">
    import { Calendar } from '@fibrous/semi-ui-vue';

    const range = [new Date(2020, 8, 26), new Date(2020, 8, 31)];
</script>

<template>
    <Calendar mode="range" :range="range" />
</template>

事件渲染用法

通过 events 传入需要渲染的事件,events 是一个由 event objects 组成的数组,具体形式请参考 Event Object。

事件的内容默认渲染 children 文本;需要自定义 DOM 时用 #event="{ event }" 作用域插槽,event 是 events 中的原对象,可以带任意自定义字段。同一事件在多处显示(如跨天事件)时,插槽每处各调用一次。





7月
21周日
22周一
23周二
24周三
25周四
26周五
27周六
全天
  • 6月25日 14:45 ~ 7月26日 6:18
  • 7月18日 10:00 ~ 7月30日 8:00
  • 7月19日 20:00 ~ 7月23日 14:00
  • 7月25日 8:00 ~ 7月27日 6:00
  • 7月21日 6:00 ~ 7月25日 6:00
  • 7月26日 10:00 ~ 7月27日 16:00
  • 7月22日 全天
  • 7月22日 9:00 ~ 7月23日 23:00
上午1时
上午2时
上午3时
上午4时
上午5时
上午6时
上午7时
上午8时
上午9时
上午10时
上午11时
下午12时
下午1时
下午2时
下午3时
下午4时
下午5时
下午6时
下午7时
下午8时
下午9时
下午10时
下午11时
  • 7月23日 8:32
  • 7月23日 14:30-20:00
代码
<script setup lang="ts">
    import { computed, ref, type CSSProperties } from 'vue';
    import { Calendar, DatePicker, Radio, RadioGroup, type CalendarEventObject, type CalendarMode, type RadioValue } from '@fibrous/semi-ui-vue';

    const mode = ref<RadioValue | undefined>('week');
    const displayValue = ref<Date | undefined>(new Date(2019, 6, 23, 8, 32, 0));

    const onChangeDate = (value: Date | Date[] | undefined) => {
        if (!Array.isArray(value)) displayValue.value = value;
    };

    const dailyEventStyle: CSSProperties = {
        borderRadius: '3px',
        boxSizing: 'border-box',
        border: 'var(--semi-color-primary) 1px solid',
        padding: '10px',
        backgroundColor: 'var(--semi-color-primary-light-default)',
        height: '100%',
        overflow: 'hidden',
    };
    const allDayStyle: CSSProperties = {
        borderRadius: '3px',
        boxSizing: 'border-box',
        border: 'var(--semi-color-bg-1) 1px solid',
        padding: '2px 4px',
        backgroundColor: 'var(--semi-color-primary-light-active)',
        height: '100%',
        overflow: 'hidden',
    };
    // `daily` marks the events that keep the padded style outside the month view; any extra key is passed to #event.
    const events: CalendarEventObject[] = [
        {
            key: '0',
            start: new Date(2019, 5, 25, 14, 45, 0),
            end: new Date(2019, 6, 26, 6, 18, 0),
            children: '6月25日 14:45 ~ 7月26日 6:18',
        },
        {
            key: '1',
            start: new Date(2019, 6, 18, 10, 0, 0),
            end: new Date(2019, 6, 30, 8, 0, 0),
            children: '7月18日 10:00 ~ 7月30日 8:00',
        },
        {
            key: '2',
            start: new Date(2019, 6, 19, 20, 0, 0),
            end: new Date(2019, 6, 23, 14, 0, 0),
            children: '7月19日 20:00 ~ 7月23日 14:00',
        },
        {
            key: '3',
            start: new Date(2019, 6, 21, 6, 0, 0),
            end: new Date(2019, 6, 25, 6, 0, 0),
            children: '7月21日 6:00 ~ 7月25日 6:00',
        },
        { key: '4', allDay: true, start: new Date(2019, 6, 22, 8, 0, 0), children: '7月22日 全天' },
        {
            key: '5',
            start: new Date(2019, 6, 22, 9, 0, 0),
            end: new Date(2019, 6, 23, 23, 0, 0),
            children: '7月22日 9:00 ~ 7月23日 23:00',
        },
        {
            key: '6',
            daily: true,
            start: new Date(2019, 6, 23, 8, 32, 0),
            end: new Date(2019, 6, 23, 8, 42, 0),
            children: '7月23日 8:32',
        },
        {
            key: '7',
            daily: true,
            start: new Date(2019, 6, 23, 14, 30, 0),
            end: new Date(2019, 6, 23, 20, 0, 0),
            children: '7月23日 14:30-20:00',
        },
        {
            key: '8',
            start: new Date(2019, 6, 25, 8, 0, 0),
            end: new Date(2019, 6, 27, 6, 0, 0),
            children: '7月25日 8:00 ~ 7月27日 6:00',
        },
        {
            key: '9',
            start: new Date(2019, 6, 26, 10, 0, 0),
            end: new Date(2019, 6, 27, 16, 0, 0),
            children: '7月26日 10:00 ~ 7月27日 16:00',
        },
    ];
    const range = computed(() => (mode.value === 'range' ? [new Date(2019, 6, 23), new Date(2019, 6, 26)] : []));
</script>

<template>
    <RadioGroup v-model="mode" type="button">
        <Radio value="day">日视图</Radio>
        <Radio value="week">周视图</Radio>
        <Radio value="month">月视图</Radio>
        <Radio value="range">多日视图</Radio>
    </RadioGroup>
    <br />
    <br />
    <DatePicker :modelValue="displayValue" @update:modelValue="onChangeDate" />
    <br />
    <br />
    <Calendar :displayValue="displayValue" :events="events" :height="400" :minEventHeight="40" :mode="mode as CalendarMode" :range="range">
        <template #event="{ event }">
            <div :style="event.daily && mode !== 'month' ? dailyEventStyle : allDayStyle">{{ event.children }}</div>
        </template>
    </Calendar>
</template>

自定义渲染

通过 #dateGrid="{ date, dateString }" 作用域插槽可以自定义渲染日期单元格/列。需要使用绝对定位。

自定义渲染事件

7月
21周日
22周一
23周二
24周三
25周四
26周五
27周六
全天
上午1时
上午2时
上午3时
上午4时
上午5时
上午6时
上午7时
上午8时
上午9时
上午10时
上午11时
下午12时
下午1时
下午2时
下午3时
下午4时
下午5时
下午6时
下午7时
下午8时
下午9时
下午10时
下午11时
吃饭 🍰
睡觉 😪
打豆豆 🎮
代码
<script setup lang="ts">
    import type { CSSProperties } from 'vue';
    import { Calendar } from '@fibrous/semi-ui-vue';

    const dailyEventStyle: CSSProperties = {
        position: 'absolute',
        left: '0',
        right: '0',
        borderRadius: '3px',
        boxSizing: 'border-box',
        border: 'var(--semi-color-primary) 1px solid',
        padding: '10px',
        backgroundColor: 'var(--semi-color-primary-light-default)',
        overflow: 'hidden',
    };
    const displayValue = new Date(2019, 6, 23, 8, 32, 0);
    const targetDateString = new Date(2019, 6, 23).toString();
</script>

<template>
    <Calendar :displayValue="displayValue" :height="700" mode="week">
        <template #dateGrid="{ dateString }">
            <template v-if="dateString === targetDateString">
                <div :style="{ ...dailyEventStyle, top: '500px', height: '50px' }">吃饭 🍰</div>
                <div :style="{ ...dailyEventStyle, top: '0', height: '400px' }">睡觉 😪</div>
                <div :style="{ ...dailyEventStyle, top: '700px', height: '100px' }">打豆豆 🎮</div>
            </template>
        </template>
    </Calendar>
</template>

自定义渲染单元格样式

可以通过 #dateGrid 自定义单元格的背景,月视图的文字 zIndex 默认为 3,如需完全覆盖单元格可以设置更大的 zIndex 来实现。

周日
周一
周二
周三
周四
周五
周六
30
7月 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
8月 1日
2
3
代码
<script setup lang="ts">
    import type { CSSProperties } from 'vue';
    import { Calendar } from '@fibrous/semi-ui-vue';

    const importantDate: CSSProperties = {
        position: 'absolute',
        left: '0',
        right: '0',
        top: '0',
        bottom: '0',
        backgroundColor: 'var(--semi-color-danger-light-default)',
    };
    const displayValue = new Date(2019, 6, 23, 8, 32, 0);
    const importDates = [new Date(2019, 6, 2), new Date(2019, 6, 8), new Date(2019, 6, 19), new Date(2019, 6, 23)];
    const importDateStrings = importDates.map((date) => date.toString());
</script>

<template>
    <Calendar :displayValue="displayValue" :height="700" mode="month">
        <template #dateGrid="{ dateString }">
            <div v-if="importDateStrings.includes(dateString)" :style="importantDate" />
        </template>
    </Calendar>
</template>

自定义日期文案

可以通过 #dateDisplay="{ date }" 自定义日期文案。

5月
14
15
16
17
18
19
20
全天
上午1时
上午2时
上午3时
上午4时
上午5时
上午6时
上午7时
上午8时
上午9时
上午10时
上午11时
下午12时
下午1时
下午2时
下午3时
下午4时
下午5时
下午6时
下午7时
下午8时
下午9时
下午10时
下午11时
代码
<script setup lang="ts">
    import { Avatar, Calendar } from '@fibrous/semi-ui-vue';

    const displayValue = new Date(2023, 4, 14);
    const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo', 'lime'] as const;
</script>

<template>
    <Calendar :displayValue="displayValue" :height="400" mode="week">
        <template #dateDisplay="{ date }">
            <div :style="{ paddingBlock: '6px' }">
                <Avatar :color="colors[date.getDay()]" size="small">{{ date.getDate() }}</Avatar>
            </div>
        </template>
    </Calendar>
</template>

API 参考

Calendar

Props

属性说明类型默认值
displayValue展示日期,未传时为当前日期并在午夜更新。DatesemiGlobalDefault("Calendar", "displayValue")
rangerange 模式下展示的日期范围,左闭右开;range 模式必传。Date[]semiGlobalDefault("Calendar", "range")
events渲染的事件,格式见 Event Object。EventObject[]semiDefault("Calendar", "events", [])
mode视图模式,可选 day、week、month、range,变化后立即切换视图。CalendarModesemiDefault("Calendar", "mode", "week")
showCurrTime日、周、多日视图下显示当前时间的红线。booleansemiDefault("Calendar", "showCurrTime", true)
weekStartsOn以周几作为每周第一天,0 代表周日,1 代表周一,以此类推;对月视图、周视图生效。WeekStartsOnsemiDefault("Calendar", "weekStartsOn", 0)
scrollTop日、周、多日视图下展示内容的默认滚动高度。numbersemiDefault("Calendar", "scrollTop", 400)
markWeekend以灰色区分周末列和工作日。booleansemiDefault("Calendar", "markWeekend", false)
minEventHeight日视图、多日视图以及周视图下事件的最小高度,单位为像素。numbersemiGlobalDefault("Calendar", "minEventHeight")
width日历宽度,数字按像素处理。string | numbersemiGlobalDefault("Calendar", "width")
height日历高度,数字按像素处理。string | numbersemiDefault("Calendar", "height", 600)

Events

事件说明回调参数
click单击日期格时触发,参数为事件和该格的日期;日、周、多日视图以半小时为单位,月视图以日为单位。[event: MouseEvent, value: Date]
close月视图下展示全部事件的卡片关闭时触发;关闭按钮或再次点击「还有 N 项」传点击事件,Esc 传键盘事件,点击外部或组件在 KeepAlive 中停用时传 null。[event: MouseEvent | KeyboardEvent | null]
moreClick月视图下点击「还有 N 项」打开卡片时触发,参数为事件、日期和被折叠的事件数;再次点击收起卡片时不触发。[event: MouseEvent, date: Date, remaining: number]

Slots

插槽说明作用域参数
header自定义头部内容。any
event自定义事件内容,参数 event 为 events 中的原对象,同一事件在多处显示时每处各调用一次;未提供时渲染 children 文本。CalendarEventSlotProps
dateDisplay自定义日期文案(周、多日视图的列标题与月视图的每个日期格),日视图不使用。CalendarDateDisplaySlotProps
dateGrid自定义单元格或列的内容,参数为日期和 date.toString() 得到的 dateString,需要绝对定位。CalendarDateGridSlotProps
timeDisplay自定义日、周、多日视图下的时间文案,参数 time 为小时数。CalendarTimeDisplaySlotProps
allDayEvents自定义日、多日、周视图顶部的全天事件区域,参数 events 为传入的全部事件,需自行筛选。CalendarAllDayEventsSlotProps

Exposes

名称说明类型
无

class、style 与原生属性经 attrs 落在当前视图的根元素上;aria-label 优先于默认的表格名称。

Event Object

events 是一个 event object 组成的数组,event object 约定格式如下:
起始时间和结束时间至少传入一个才会被视为有效事件,两者都缺失的事件会被跳过并给出警告。全天事件只传结束时间时,以结束时间当天为起始;非全天事件只传一端时,补足 1 小时(不跨过当天)。结束时间区间为半开:非全天事件的结束时间恰为 0 点时,只显示在开始当天,不会在次日多出一段。

属性说明类型默认值
allDay全天事件booleanfalse
children展示内容,未提供 #event 插槽时渲染为文本string | number-
end事情结束的时间Date-
keyrequired 且要求唯一string-
start事情起始的时间Date-
其他字段自定义字段原样保留,供 #event 插槽读取(如颜色、标题)any-

Accessibility

键盘和焦点

  • 日、周、多日视图可以滚动,视图本身是一个 Tab 停靠点,用方向键滚动
  • 月视图中折叠事件的「还有 N 项」是按钮,Enter 或 空格 打开当天的事件卡片,焦点移到卡片的关闭按钮;按 Esc 或关闭按钮关闭卡片,焦点回到「还有 N 项」
  • 日期格与时间槽的点击(React 的 onClick,Vue 的 @click)只响应指针,没有键盘方式

文案规范

  • 当需要显示时间时,12 小时制和 24 小时制都是可以使用的
  • 如果采用12小时制,需要搭配 AM/PM 一起使用,具体内容可参考 时间规范
  • 关于月份、星期、时间的缩写使用规则,可参考 缩写规范

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$color-calendar-bg-activevar(--semi-color-primary)今日标识颜色
$color-calendar-bgvar(--semi-color-bg-2)日历背景颜色
$color-calendar-text-activevar(--semi-color-bg-1)日历文字颜色 - 今日
$color-calendar_curr-bordervar(--semi-color-danger)当前时间标识线颜色
$color-calendar_currCircle-bg-defaultvar(--semi-color-danger)当前时间标识原点颜色
$color-calendar_date-text-defaultvar(--semi-color-text-0)日历文字颜色 - 当月
$color-calendar_day-bordervar(--semi-color-border)日历描边颜色
$color-calendar_day-text-defaultvar(--semi-color-text-2)日历文字颜色 - 非当月
$color-calendar_sticky-bgvar(--semi-color-bg-2)日历背景颜色 - 吸顶部分
$color-calendar_weekend-bgvar(--semi-color-fill-0)日历背景颜色 - 非工作日
$width-calendar_day-minWidth78px
$height-calendar_day_grid60px
$width-calendar_day_grid-minWidth130px
$height-calendar_time_grid$height-calendar_day_grid * 0.5
$height-calendar_allDay26px
$height-calendar_allDay-minHeight26px
$width-calendar_currCircle8px
$width-calendar_tag_col-minWidth70px
$height-calendar_month100%
$height-calendar_month_week_skeletion100%
$height-calendar_month_grid_wrappercalc(100% - 27px)
$width-calendar_card220px
$width-calendar_today_date24px
$height-calendar_today_date24px
$height-calendar_body_li24px
$height-calendar_day_scroll_wrappercalc(100% - 28px)
$height-calendar_week_scroll_wrappercalc(100% - 55px)
$height-calendar_month_skeletion_li100%
$height-calendar_month_grid_col100%
$height-calendar_month_week_row_event_month1em
$width-calendar-border-default1px
$width-calendar-border-thick1px
$spacing-calendar_ul_li-padding0
$spacing-calendar_ul_li-margin0
$spacing-calendar_sticky_top-top0
$spacing-calendar_sticky_top-right0
$spacing-calendar_sticky_top-left0
$spacing-calendar_sticky_left-left0
$spacing-calendar_event_day-left0
$spacing-calendar_event_day-right0
$spacing-calendar_custom_container-top0
$spacing-calendar_custom_container-right0
$spacing-calendar_custom_container-bottom0
$spacing-calendar_custom_container-left0
$spacing-calendar_currCircle-marginTop-4px
$spacing-calendar_currLine-left0
$spacing-calendar_currLine-right0
$spacing-calendar_allDay_tag-paddingRight8px
$spacing-calendar_allDay_content-paddingLeft0
$spacing-calendar_month_event_card_wrapper-paddingTop2px
$spacing-calendar_grid_row_span_child-paddingY0
$spacing-calendar_grid_row_span_child-paddingX4px
$spacing-calendar_skeleton_li_child-paddingTop4px
$spacing-calendar_month_event_card_close-marginRight-4px
$spacing-calendar_header_info_date-marginTop4px
$spacing-calendar_time-paddingRight8px
$spacing-calendar_time_item_span-top-10px
$spacing-calendar_tag_child-paddingRight8px
$spacing-calendar_skeletion_grid_row_li-paddingRight8px
$spacing-calendar_month_event_card_wrapper-right8px
$spacing-calendar_month_event_card_content-paddingY12px
$spacing-calendar_month_event_card_content-paddingX0
$spacing-calendar_month_event_card_ul_li-margin0
$spacing-calendar_month_event_card_ul_li-padding0
$spacing-calendar_header-marginY12px
$spacing-calendar_header-marginX20px
$spacing-calendar_body-padingY0
$spacing-calendar_body-padingX16px
$spacing-calendar_event_items-top31px
$spacing-calendar_event_items-right0
$spacing-calendar_event_items-bottom20px
$spacing-calendar_event_items-left0
$spacing-calendar_month_skeletion-left0
$spacing-calendar_month_skeletion-right0
$spacing-calendar_month_date-right4px
$radius-calendar_currCirclevar(--semi-border-radius-circle)日历选中日期标识圆角
$radius-calendar_today_datevar(--semi-border-radius-circle)日历选中日期标识圆角
$font-calendar_allDay_tag-lineHeight$height-calendar_allDay日历全天标识文字行高
$font-calendar_today_date-lineHeight24px
$font-calendar_day_allDay-lineHeight26px
$font-calendar_day_allDay-fontSize26px
$font-calendar_month_day-fontSize24px
$z-calendar_stickyTop20
$z-calendar_item5
$z-calendar_stickyLeft10
$z-calendar_curr8
$z-calendar_line3