Calendar 日期日历
用于日期日历场景的 MoriUI 组件。
导入
import { Calendar } from 'moriui'
示例
概览
单日日期选择。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate } from '@internationalized/date'
const date = shallowRef<DateValue>(new CalendarDate(2026, 7, 15))
</script>
<template>
<Calendar v-model="date" locale="zh-CN" class="rounded-lg border" />
</template>
基础
最小化的日历。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 |
<script setup lang="ts">
import { Calendar } from 'moriui'
</script>
<template>
<Calendar locale="zh-CN" class="rounded-lg border" />
</template>
多选
多次日期选择。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate } from '@internationalized/date'
const dates = shallowRef<DateValue[]>([
new CalendarDate(2026, 7, 10),
new CalendarDate(2026, 7, 15),
new CalendarDate(2026, 7, 20)
])
</script>
<template>
<Calendar
v-model="dates"
multiple
locale="zh-CN"
class="rounded-lg border"
/>
</template>
范围
选择日期区间。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 | 1 |
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
23 | 24 | 25 | 26 | 27 | 28 | 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 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate } from '@internationalized/date'
const start = new CalendarDate(2026, 1, 12)
const range: DateValue[] = []
for (let i = 0; i < 30; i++) {
range.push(start.add({ days: i }))
}
const dates = shallowRef(range)
</script>
<template>
<Calendar
v-model="dates"
multiple
:number-of-months="2"
locale="zh-CN"
class="rounded-lg border"
/>
</template>
预设
快捷预设日期。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate, getLocalTimeZone, today } from '@internationalized/date'
const date = shallowRef<DateValue>(new CalendarDate(2026, 2, 12))
const placeholder = shallowRef<DateValue>(new CalendarDate(2026, 2, 1))
const presets = [
{ label: '今天', days: 0 },
{ label: '明天', days: 1 },
{ label: '3 天后', days: 3 },
{ label: '一周后', days: 7 },
{ label: '两周后', days: 14 }
]
function selectPreset(days: number) {
const d = today(getLocalTimeZone()).add({ days })
date.value = d
placeholder.value = d.set({ day: 1 })
}
</script>
<template>
<div class="w-fit">
<Calendar
v-model="date"
v-model:placeholder="placeholder"
fixed-weeks
locale="zh-CN"
class="rounded-lg border"
/>
<div class="flex flex-wrap gap-2 border-t p-3">
<button
v-for="preset in presets"
:key="preset.days"
class="flex-1 rounded-md border px-3 py-1.5 text-sm hover:bg-accent"
@click="selectPreset(preset.days)"
>
{{ preset.label }}
</button>
</div>
</div>
</template>
时间
日期时间选择。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { Clock } from '@lucide/vue'
import { CalendarDate } from '@internationalized/date'
const date = shallowRef<DateValue>(new CalendarDate(2026, 7, 12))
</script>
<template>
<div class="w-fit">
<Calendar v-model="date" locale="zh-CN" class="rounded-lg border" />
<div class="flex items-center gap-4 border-t bg-muted/30 p-3">
<div class="flex items-center gap-2">
<label for="time-from" class="text-sm text-muted-foreground">开始</label>
<div class="flex items-center gap-1 rounded-md border bg-background px-3 py-1.5">
<input
id="time-from"
type="time"
step="1"
value="10:30:00"
class="w-20 border-none bg-transparent text-sm outline-none"
>
<Clock class="h-4 w-4 text-muted-foreground" />
</div>
</div>
<div class="flex items-center gap-2">
<label for="time-to" class="text-sm text-muted-foreground">结束</label>
<div class="flex items-center gap-1 rounded-md border bg-background px-3 py-1.5">
<input
id="time-to"
type="time"
step="1"
value="12:30:00"
class="w-20 border-none bg-transparent text-sm outline-none"
>
<Clock class="h-4 w-4 text-muted-foreground" />
</div>
</div>
</div>
</div>
</template>
标题
自定义月份标题。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate } from '@internationalized/date'
const date = shallowRef<DateValue>(new CalendarDate(2026, 7, 15))
</script>
<template>
<Calendar v-model="date" locale="zh-CN" class="rounded-lg border">
<template #heading="{ headingValue }">
<div class="flex items-center gap-1 text-sm font-medium">
<span class="text-primary">{{ headingValue }}</span>
</div>
</template>
</Calendar>
</template>
自定义日期
在日期格中展示额外信息。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
30 | 1¥100 | 2¥100 | 3¥100 | 4¥100 | 5¥100 | 6¥100 |
7¥100 | 8已选 | 9已选 | 10已选 | 11已选 | 12已选 | 13已选 |
14已选 | 15已选 | 16已选 | 17已选 | 18已选 | 19¥100 | 20¥100 |
21¥100 | 22¥100 | 23¥100 | 24¥100 | 25¥100 | 26¥100 | 27¥100 |
28¥100 | 29¥100 | 30¥100 | 31¥100 | 1 | 2 | 3 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate } from '@internationalized/date'
const start = new CalendarDate(2026, 12, 8)
const range: DateValue[] = []
for (let i = 0; i < 11; i++) {
range.push(start.add({ days: i }))
}
const dates = shallowRef<DateValue[]>(range)
</script>
<template>
<Calendar
v-model="dates"
multiple
locale="zh-CN"
class="rounded-lg border"
>
<template #day="{ dayValue, today: isToday, selected, outsideView }">
<div class="flex flex-col items-center gap-0.5">
<span>{{ dayValue }}</span>
<span
v-if="!outsideView && isToday"
class="text-[8px] font-medium text-primary"
>今天</span>
<span
v-else-if="!outsideView && selected"
class="text-[8px] font-medium text-primary-foreground"
>已选</span>
<span
v-else-if="!outsideView"
class="text-[8px] text-muted-foreground"
>¥100</span>
</div>
</template>
</Calendar>
</template>
周数
左侧显示周数。
| 一 | 二 | 三 | 四 | 五 | 六 | 日 |
|---|---|---|---|---|---|---|
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 | 31 | 1 |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate } from '@internationalized/date'
const date = shallowRef<DateValue>(new CalendarDate(2026, 1, 12))
</script>
<template>
<Calendar v-model="date" locale="zh-CN" class="rounded-lg border" />
</template>
RTL
从右到左的日历布局。
| ح | ن | ث | ر | خ | ج | س |
|---|---|---|---|---|---|---|
٢٨ | ٢٩ | ٣٠ | ١ | ٢ | ٣ | ٤ |
٥ | ٦ | ٧ | ٨ | ٩ | ١٠ | ١١ |
١٢ | ١٣ | ١٤ | ١٥ | ١٦ | ١٧ | ١٨ |
١٩ | ٢٠ | ٢١ | ٢٢ | ٢٣ | ٢٤ | ٢٥ |
٢٦ | ٢٧ | ٢٨ | ٢٩ | ٣٠ | ٣١ | ١ |
<script setup lang="ts">
import type { DateValue } from 'reka-ui'
import { shallowRef } from 'vue'
import { Calendar } from 'moriui'
import { CalendarDate } from '@internationalized/date'
const date = shallowRef<DateValue>(new CalendarDate(2026, 7, 15))
</script>
<template>
<Calendar
v-model="date"
dir="rtl"
locale="ar-SA"
class="rounded-lg border"
/>
</template>
使用说明
选择模式
Calendar 通过 multiple 属性控制单选或多选;day 插槽可自定义日期格内容。
自定义标题
heading 插槽替换默认月份/年份标题。
自定义日期格
day 插槽接收 dayValue/disabled/today/selected 属性,支持自定义渲染。
RTL
日历网格和方向键遵循 dir。
变体
Calendar 没有视觉变体;multiple 控制多选,通过 Reka UI DateField 管理日期状态。
可访问性
Calendar 复用 Reka UI Calendar 的 grid ARIA、方向键导航。每个日期格有正确标签。
API 参考
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Calendar v-model | DateValue | DateValue[] | null | undefined | 当前选择的日期。 |
Calendar multiple | boolean | false | 允许多选。 |
Calendar placeholder | DateValue | 今天 | 日历初始聚焦月份。 |
Calendar numberOfMonths | number | 1 | 同时显示的月份数量。 |
Calendar locale | string | 'en' | 日历的语言区域。 |