Date Range Picker 日期区间
用于日期区间场景的 MoriUI 组件。
导入
import { DateRangePicker } from 'moriui'
示例
概览
选择日期区间。
2024
1
20
2024
2
10
已选择:2024年1月20日 – 2024年2月10日
<script setup lang="ts">
import { shallowRef } from 'vue'
import { CalendarDate } from '@internationalized/date'
import {
DateRangePicker,
DateRangePickerCalendar,
DateRangePickerCell,
DateRangePickerCellTrigger,
DateRangePickerContent,
DateRangePickerField,
DateRangePickerGrid,
DateRangePickerGridBody,
DateRangePickerGridHead,
DateRangePickerGridRow,
DateRangePickerHeadCell,
DateRangePickerHeader,
DateRangePickerHeading,
DateRangePickerInput,
DateRangePickerNext,
DateRangePickerPrev,
DateRangePickerTrigger
} from 'moriui'
const value = shallowRef({
start: new CalendarDate(2024, 1, 20),
end: new CalendarDate(2024, 2, 10)
})
function format(date: CalendarDate | undefined): string {
if (!date)
return ''
return `${date.year}年${date.month}月${date.day}日`
}
</script>
<template>
<div class="flex flex-col items-center gap-4">
<DateRangePicker v-model="value" locale="zh-CN" class="w-[320px]">
<DateRangePickerField v-slot="{ segments }">
<DateRangePickerInput
v-for="segment in segments.start"
:key="`start-${segment.part}`"
type="start"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<span aria-hidden="true">–</span>
<DateRangePickerInput
v-for="segment in segments.end"
:key="`end-${segment.part}`"
type="end"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<DateRangePickerTrigger aria-label="选择日期范围" />
</DateRangePickerField>
<DateRangePickerContent>
<DateRangePickerCalendar v-slot="{ grid, weekDays }">
<DateRangePickerHeader>
<DateRangePickerPrev aria-label="上个月" />
<DateRangePickerHeading />
<DateRangePickerNext aria-label="下个月" />
</DateRangePickerHeader>
<DateRangePickerGrid v-for="month in grid" :key="month.value.toString()">
<DateRangePickerGridHead>
<DateRangePickerGridRow>
<DateRangePickerHeadCell v-for="weekDay in weekDays" :key="weekDay">
{{ weekDay }}
</DateRangePickerHeadCell>
</DateRangePickerGridRow>
</DateRangePickerGridHead>
<DateRangePickerGridBody>
<DateRangePickerGridRow v-for="(week, index) in month.rows" :key="index">
<DateRangePickerCell v-for="day in week" :key="day.toString()" :date="day">
<DateRangePickerCellTrigger :day="day" :month="month.value" />
</DateRangePickerCell>
</DateRangePickerGridRow>
</DateRangePickerGridBody>
</DateRangePickerGrid>
</DateRangePickerCalendar>
</DateRangePickerContent>
</DateRangePicker>
<div class="text-sm text-muted-foreground">
<template v-if="value.start && value.end">
已选择:{{ format(value.start) }} – {{ format(value.end) }}
</template>
<template v-else>
请选择日期范围
</template>
</div>
</div>
</template>
基础
最小化区间选择。
2024
4
1
2024
4
15
<script setup lang="ts">
import { shallowRef } from 'vue'
import { CalendarDate } from '@internationalized/date'
import {
DateRangePicker,
DateRangePickerCalendar,
DateRangePickerCell,
DateRangePickerCellTrigger,
DateRangePickerContent,
DateRangePickerField,
DateRangePickerGrid,
DateRangePickerGridBody,
DateRangePickerGridHead,
DateRangePickerGridRow,
DateRangePickerHeadCell,
DateRangePickerHeader,
DateRangePickerHeading,
DateRangePickerInput,
DateRangePickerNext,
DateRangePickerPrev,
DateRangePickerTrigger
} from 'moriui'
const value = shallowRef({
start: new CalendarDate(2024, 4, 1),
end: new CalendarDate(2024, 4, 15)
})
</script>
<template>
<DateRangePicker v-model="value" locale="zh-CN">
<DateRangePickerField v-slot="{ segments }">
<DateRangePickerInput
v-for="segment in segments.start"
:key="`start-${segment.part}`"
type="start"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<span aria-hidden="true">–</span>
<DateRangePickerInput
v-for="segment in segments.end"
:key="`end-${segment.part}`"
type="end"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<DateRangePickerTrigger aria-label="选择日期范围" />
</DateRangePickerField>
<DateRangePickerContent>
<DateRangePickerCalendar v-slot="{ grid, weekDays }">
<DateRangePickerHeader>
<DateRangePickerPrev aria-label="上个月" />
<DateRangePickerHeading />
<DateRangePickerNext aria-label="下个月" />
</DateRangePickerHeader>
<DateRangePickerGrid v-for="month in grid" :key="month.value.toString()">
<DateRangePickerGridHead>
<DateRangePickerGridRow>
<DateRangePickerHeadCell v-for="day in weekDays" :key="day">
{{ day }}
</DateRangePickerHeadCell>
</DateRangePickerGridRow>
</DateRangePickerGridHead>
<DateRangePickerGridBody>
<DateRangePickerGridRow v-for="(week, idx) in month.rows" :key="idx">
<DateRangePickerCell v-for="day in week" :key="day.toString()" :date="day">
<DateRangePickerCellTrigger :day="day" :month="month.value" />
</DateRangePickerCell>
</DateRangePickerGridRow>
</DateRangePickerGridBody>
</DateRangePickerGrid>
</DateRangePickerCalendar>
</DateRangePickerContent>
</DateRangePicker>
</template>
预设
快捷预设日期范围。
2026
7
16
2026
7
22
已选择:2026年7月16日 – 2026年7月22日
<script setup lang="ts">
import { shallowRef } from 'vue'
import { CalendarDate, getLocalTimeZone, today } from '@internationalized/date'
import {
DateRangePicker,
DateRangePickerCalendar,
DateRangePickerCell,
DateRangePickerCellTrigger,
DateRangePickerContent,
DateRangePickerField,
DateRangePickerGrid,
DateRangePickerGridBody,
DateRangePickerGridHead,
DateRangePickerGridRow,
DateRangePickerHeadCell,
DateRangePickerHeader,
DateRangePickerHeading,
DateRangePickerInput,
DateRangePickerNext,
DateRangePickerPrev,
DateRangePickerTrigger
} from 'moriui'
const value = shallowRef({
start: today(getLocalTimeZone()).subtract({ days: 6 }),
end: today(getLocalTimeZone())
})
const presets = [
{
label: '最近 7 天',
get: () => {
const end = today(getLocalTimeZone())
const start = end.subtract({ days: 6 })
return { start, end }
}
},
{
label: '本月',
get: () => {
const now = today(getLocalTimeZone())
const start = new CalendarDate(now.year, now.month, 1)
const end = start.add({ months: 1 }).subtract({ days: 1 })
return { start, end }
}
},
{
label: '上个月',
get: () => {
const now = today(getLocalTimeZone())
const start = new CalendarDate(now.year, now.month, 1).subtract({ months: 1 })
const end = new CalendarDate(now.year, now.month, 1).subtract({ days: 1 })
return { start, end }
}
},
{
label: '最近 30 天',
get: () => {
const end = today(getLocalTimeZone())
const start = end.subtract({ days: 29 })
return { start, end }
}
},
{
label: '最近 90 天',
get: () => {
const end = today(getLocalTimeZone())
const start = end.subtract({ days: 89 })
return { start, end }
}
},
{
label: '今年',
get: () => {
const now = today(getLocalTimeZone())
const start = new CalendarDate(now.year, 1, 1)
const end = new CalendarDate(now.year, 12, 31)
return { start, end }
}
}
]
function applyPreset(preset: typeof presets[number]) {
value.value = preset.get()
}
function format(date: CalendarDate | undefined): string {
if (!date)
return ''
return `${date.year}年${date.month}月${date.day}日`
}
</script>
<template>
<div class="flex flex-col items-center gap-4">
<DateRangePicker v-model="value" locale="zh-CN">
<DateRangePickerField v-slot="{ segments }">
<DateRangePickerInput
v-for="segment in segments.start"
:key="`start-${segment.part}`"
type="start"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<span aria-hidden="true">–</span>
<DateRangePickerInput
v-for="segment in segments.end"
:key="`end-${segment.part}`"
type="end"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<DateRangePickerTrigger aria-label="选择日期范围" />
</DateRangePickerField>
<DateRangePickerContent>
<div class="flex flex-wrap gap-1 p-2 border-b border-border">
<button
v-for="preset in presets"
:key="preset.label"
type="button"
class="text-xs px-2 py-1 rounded hover:bg-accent hover:text-accent-foreground transition-colors"
@click="applyPreset(preset)"
>
{{ preset.label }}
</button>
</div>
<DateRangePickerCalendar v-slot="{ grid, weekDays }">
<DateRangePickerHeader>
<DateRangePickerPrev aria-label="上个月" />
<DateRangePickerHeading />
<DateRangePickerNext aria-label="下个月" />
</DateRangePickerHeader>
<DateRangePickerGrid v-for="month in grid" :key="month.value.toString()">
<DateRangePickerGridHead>
<DateRangePickerGridRow>
<DateRangePickerHeadCell v-for="weekDay in weekDays" :key="weekDay">
{{ weekDay }}
</DateRangePickerHeadCell>
</DateRangePickerGridRow>
</DateRangePickerGridHead>
<DateRangePickerGridBody>
<DateRangePickerGridRow v-for="(week, index) in month.rows" :key="index">
<DateRangePickerCell v-for="day in week" :key="day.toString()" :date="day">
<DateRangePickerCellTrigger :day="day" :month="month.value" />
</DateRangePickerCell>
</DateRangePickerGridRow>
</DateRangePickerGridBody>
</DateRangePickerGrid>
</DateRangePickerCalendar>
</DateRangePickerContent>
</DateRangePicker>
<div v-if="value.start && value.end" class="text-sm text-muted-foreground">
已选择:{{ format(value.start) }} – {{ format(value.end) }}
</div>
</div>
</template>
RTL
从右到左的日期区间选择。
20
1
2024
10
2
2024
<script setup lang="ts">
import { shallowRef } from 'vue'
import { CalendarDate } from '@internationalized/date'
import {
DateRangePicker,
DateRangePickerCalendar,
DateRangePickerCell,
DateRangePickerCellTrigger,
DateRangePickerContent,
DateRangePickerField,
DateRangePickerGrid,
DateRangePickerGridBody,
DateRangePickerGridHead,
DateRangePickerGridRow,
DateRangePickerHeadCell,
DateRangePickerHeader,
DateRangePickerHeading,
DateRangePickerInput,
DateRangePickerNext,
DateRangePickerPrev,
DateRangePickerTrigger,
DirectionProvider
} from 'moriui'
const value = shallowRef({
start: new CalendarDate(2024, 1, 20),
end: new CalendarDate(2024, 2, 10)
})
</script>
<template>
<DirectionProvider dir="rtl">
<DateRangePicker v-model="value" locale="ar">
<DateRangePickerField v-slot="{ segments }">
<DateRangePickerInput
v-for="segment in segments.start"
:key="`start-${segment.part}`"
type="start"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<span aria-hidden="true">–</span>
<DateRangePickerInput
v-for="segment in segments.end"
:key="`end-${segment.part}`"
type="end"
:part="segment.part"
>
{{ segment.value }}
</DateRangePickerInput>
<DateRangePickerTrigger aria-label="اختيار نطاق التاريخ" />
</DateRangePickerField>
<DateRangePickerContent>
<DateRangePickerCalendar v-slot="{ grid, weekDays }">
<DateRangePickerHeader>
<DateRangePickerPrev aria-label="الشهر السابق" />
<DateRangePickerHeading />
<DateRangePickerNext aria-label="الشهر التالي" />
</DateRangePickerHeader>
<DateRangePickerGrid v-for="month in grid" :key="month.value.toString()">
<DateRangePickerGridHead>
<DateRangePickerGridRow>
<DateRangePickerHeadCell v-for="weekDay in weekDays" :key="weekDay">
{{ weekDay }}
</DateRangePickerHeadCell>
</DateRangePickerGridRow>
</DateRangePickerGridHead>
<DateRangePickerGridBody>
<DateRangePickerGridRow v-for="(week, index) in month.rows" :key="index">
<DateRangePickerCell v-for="day in week" :key="day.toString()" :date="day">
<DateRangePickerCellTrigger :day="day" :month="month.value" />
</DateRangePickerCell>
</DateRangePickerGridRow>
</DateRangePickerGridBody>
</DateRangePickerGrid>
</DateRangePickerCalendar>
</DateRangePickerContent>
</DateRangePicker>
</DirectionProvider>
</template>
使用说明
组合方式
DateRangePicker 为日期区间选择的包装组件,提供起止日期双输入。
RTL
方向由 DirectionProvider 控制。
变体
DateRangePicker 没有视觉变体;弹层定位由 Popper 控制。
可访问性
DateRangePicker 将 Calendar range 模式与双输入字段组合。输入应有各自的 Label 或 aria-label。
API 参考
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DateRangePicker v-model | { start: DateValue | null, end: DateValue | null } | undefined | 起止日期对象。 |
DateRangePicker placeholder | DateValue | undefined | 无选择时的占位月份。 |