DateRangePicker 日期范围选择器

输入或者从日历中选择日期区间。

2026
9
4
2026
9
12

值:2026-09-04 至 2026-09-12

<script setup lang="ts">
  import { ref } from 'vue'
  import { DateRangePicker, Stack, Text, type DateRangeValue } from '@hina-ui/vue'

  const range = ref<DateRangeValue | null>({ start: '2026-09-04', end: '2026-09-12' })
</script>

<template>
  <Stack gap="sm" align="start">
    <DateRangePicker v-model="range" clearable aria-label="活动期间" class="w-96" />
    <Text tone="muted" size="sm">
      值:{{ range ? `${range.start ?? '空'} 至 ${range.end ?? '空'}` : '空' }}
    </Text>
  </Stack>
</template>
vue

用法

import { DateRangePicker } from '@hina-ui/vue'
ts

日期范围选择器把 DateRangeField 的分段输入面与 RangeCalendar 组合在一起:可以直接在各段里输入开始与结束日期,也可以点击末尾的按钮打开日历选择。v-model 绑定 { start, end } 对象,两个字段都是 YYYY-MM-DD 格式的 ISO 8601 字符串,与两者一致。未声明的属性都会传给包裹各段的组元素,请用 aria-label 或者 aria-labelledby 命名。

年
月
日
年
月
日

值:空

<script setup lang="ts">
  import { ref } from 'vue'
  import { DateRangePicker, Stack, Text, type DateRangeValue } from '@hina-ui/vue'

  const range = ref<DateRangeValue | null>(null)
</script>

<template>
  <Stack gap="sm" align="start">
    <DateRangePicker v-model="range" aria-label="发售期间" class="w-96" />
    <Text tone="muted" size="sm">
      值:{{ range ? `${range.start ?? '空'} 至 ${range.end ?? '空'}` : '空' }}
    </Text>
  </Stack>
</template>
vue

示例

范围与不可选的日期

min 与 max 同时约束输入与日历,unavailable 把某些日期标为不可选,maximumDays 限制区间最多包含的天数。

2026
9
8
2026
9
11
<script setup lang="ts">
  import { ref } from 'vue'
  import { DateRangePicker, type DateRangeValue } from '@hina-ui/vue'

  const range = ref<DateRangeValue | null>({ start: '2026-09-08', end: '2026-09-11' })
  const weekend = (value: string) => [0, 6].includes(new Date(`${value}T00:00`).getDay())
</script>

<template>
  <DateRangePicker
    v-model="range"
    min="2026-09-01"
    max="2026-09-30"
    :maximum-days="7"
    :unavailable="weekend"
    aria-label="活动期间"
    class="w-96"
  />
</template>
vue

可清除

clearable 在各段之后加一个清除按钮,一次清空两侧,清空后值为 null。

2026
9
4
2026
9
12
<script setup lang="ts">
  import { ref } from 'vue'
  import { DateRangePicker, type DateRangeValue } from '@hina-ui/vue'

  const range = ref<DateRangeValue | null>({ start: '2026-09-04', end: '2026-09-12' })
</script>

<template>
  <DateRangePicker v-model="range" clearable aria-label="活动期间" class="w-96" />
</template>
vue

尺寸

size 有 sm、md、lg 三档,与同档输入框等高,浮层里的日历使用同一档尺寸。

2026
9
4
2026
9
12
2026
9
4
2026
9
12
2026
9
4
2026
9
12
<script setup lang="ts">
  import { DateRangePicker, Stack } from '@hina-ui/vue'

  const range = { start: '2026-09-04', end: '2026-09-12' }
</script>

<template>
  <Stack class="w-96">
    <DateRangePicker size="sm" :model-value="range" aria-label="小号" />
    <DateRangePicker size="md" :model-value="range" aria-label="中号" />
    <DateRangePicker size="lg" :model-value="range" aria-label="大号" />
  </Stack>
</template>
vue

状态

invalid 给输入面加上警示色,disabled 禁用整组,readonly 只读。variant="secondary" 是放在 surface 之内的扁平形态。

2026
9
4
2026
9
12
2026
9
4
2026
9
12
2026
9
4
2026
9
12
2026
9
4
2026
9
12
<script setup lang="ts">
  import { DateRangePicker, Stack } from '@hina-ui/vue'

  const range = { start: '2026-09-04', end: '2026-09-12' }
</script>

<template>
  <Stack class="w-96">
    <DateRangePicker :model-value="range" invalid aria-label="校验未通过" />
    <DateRangePicker :model-value="range" disabled aria-label="已禁用" />
    <DateRangePicker :model-value="range" readonly aria-label="只读" />
    <DateRangePicker :model-value="range" variant="secondary" aria-label="扁平形态" />
  </Stack>
</template>
vue

在表单中

放进 FormField 后,标签关联到输入区,错误信息由字段渲染;校验规则与提交交给 Form。区间完整性的校验写在对象层,错误才会落到这个字段上。

年
月
日
年
月
日
<script setup lang="ts">
  import { reactive, ref } from 'vue'
  import * as v from 'valibot'
  import { Button, DateRangePicker, Form, FormField, Text, type DateRangeValue } from '@hina-ui/vue'

  const schema = v.object({
    trip: v.pipe(
      v.nullable(v.object({ start: v.nullable(v.string()), end: v.nullable(v.string()) })),
      v.check(value => !!value?.start && !!value?.end, '请选择完整的出行日期'),
    ),
  })

  const values = reactive({ trip: null as DateRangeValue | null })
  const saved = ref('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    saved.value = JSON.stringify(data)
  }
</script>

<template>
  <Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
    <FormField name="trip" label="出行日期" required>
      <DateRangePicker v-model="values.trip" />
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">保存</Button>
    <Text v-if="saved" tone="muted" size="sm">已保存:{{ saved }}</Text>
  </Form>
</template>
vue

行为

  • 点击末尾的按钮打开日历,焦点落在当前区间的开始日期。在日历里点第一个日期确定开始,此时值更新为开始日期加 null,日历保持打开;点第二个日期确定结束,值更新后关闭日历,焦点回到按钮。
  • 日历是模态浮层:打开期间焦点保持在日历里,页面停止滚动,点击日历以外的任何地方都会关闭它,包括输入面本身。
  • Esc 关闭日历,焦点回到按钮。在日历的月份与年份视图里,Esc 先逐级返回。
  • 日历在输入面下方展开,与输入面的左缘对齐。

无障碍

  • 各段与日历的无障碍语义分别见 DateRangeField 与 RangeCalendar。
  • 打开日历的按钮带有语言包给出的名称与 aria-expanded。
  • 通过 aria-label 或者 aria-labelledby 为整组命名。

API

Props

属性
类型
默认值
说明
modelValue
DateRangeValue | null
null
选中的区间,两端都是 YYYY-MM-DD
open
boolean
false
日历是否打开,v-model:open
placeholder
string
今天
空值时各段与日历的起点
min
string
—
可选范围的下限
max
string
—
可选范围的上限
maximumDays
number
—
区间最多包含的天数
unavailable
(date: string) => boolean
—
判定某一天是否不可选
weekStartsOn
0 | 1 | 2 | 3 | 4 | 5 | 6
随语言
一周从周几开始
weekdayFormat
'narrow' | 'short'
'narrow'
星期名称的格式
fixedWeeks
boolean
true
日历是否固定显示六周
clearable
boolean
false
是否显示清除按钮
readonly
boolean
false
是否只读
name
string
—
表单字段名
variant
'primary' | 'secondary'
'primary'
形态
size
'sm' | 'md' | 'lg'
'md'
尺寸
disabled
boolean
false
是否禁用
invalid
boolean
false
是否处于校验未通过状态
class
string
—
追加至根元素的类名

DateRangeValue 是 { start: string | null; end: string | null },可以从包入口导入。

插槽

插槽
说明
leading
前置附属格的内容

事件

事件
参数
说明
update:modelValue
value: DateRangeValue | null
值变化
update:open
open: boolean
日历开合变化
clear
—
点击清除