Appearance
BizDateRange 快捷日期范围
业务语义的时间窗选择:预设快捷项(今日 / 昨日 / 本周 / 上周 / 本月 / 上月 / 本季度 / 今年 / 近7天 / 近30天,相对日期语义——每次计算以当天为基准)+ 自定义范围,picker(预设进下拉面板快捷栏)与 segmented(按钮组 + 自定义范围)两种形态,date / datetime / month 三种粒度,值跨度上限守护。
适用于仪表盘时间窗、数据导出时段、监控告警时段、列表工具栏时间筛选等查询表单之外的范围选择场景;查询表单内可直接用 BizSearchForm 的 date-range 字段开启 shortcuts。
基础用法
至
默认预设集 + 自定义范围。点面板左侧快捷项立即生效;手工选范围时视为「自定义」,change 第二个参数返回
null。<script setup lang="ts">
/** 场景:仪表盘时间窗——默认预设(今日 / 本周 / 本月 / 近7天…)+ 自定义范围 */
import { ref } from 'vue'
import { BizDateRange } from 'vue3-ep-biz-lib'
import type { BizDateRangeValue } from 'vue3-ep-biz-lib'
const range = ref<BizDateRangeValue>(null)
const text = (value: BizDateRangeValue): string => {
if (value === null || value === undefined) return '(未选择)'
const fmt = (item: Date | string): string => {
const date = item instanceof Date ? item : new Date(item)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
}
return `${fmt(value[0])} ~ ${fmt(value[1])}`
}
</script>
<template>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange v-model="range" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">当前值:{{ text(range) }}</span>
</div>
</template>
显示代码
自定义预设集合
至
presets 传数组完全替换默认集;预设 range 是函数,命中判定按「日截断后两端一致」。<script setup lang="ts">
/** 场景:运营复盘——自定义预设集合(近3天 / 近15天 / 本月至今 / 上半月),命中与自定义均可回显 */
import { ref } from 'vue'
import { BizDateRange, startOfDay, addDays } from 'vue3-ep-biz-lib'
import type { BizDateRangePreset } from 'vue3-ep-biz-lib'
const presets: BizDateRangePreset[] = [
{
key: 'last3Days',
label: '近3天',
range: (): [Date, Date] => {
const day = startOfDay(new Date())
return [addDays(day, -2), day]
},
},
{
key: 'last15Days',
label: '近15天',
range: (): [Date, Date] => {
const day = startOfDay(new Date())
return [addDays(day, -14), day]
},
},
{
key: 'monthToDate',
label: '本月至今',
range: (): [Date, Date] => {
const day = startOfDay(new Date())
return [new Date(day.getFullYear(), day.getMonth(), 1), day]
},
},
{
key: 'last30Days',
label: '近30天',
range: (): [Date, Date] => {
const day = startOfDay(new Date())
return [addDays(day, -29), day]
},
},
]
const range = ref<[Date, Date] | null>(null)
const lastPreset = ref<string>('(自定义 / 未选择)')
const onChange = (_value: unknown, presetKey: string | null): void => {
lastPreset.value = presetKey ?? '自定义范围'
}
</script>
<template>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange v-model="range" :presets="presets" @change="onChange" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">命中预设:{{ lastPreset }}</span>
</div>
</template>
显示代码
按钮组形态
至
mode="segmented":预设渲染为按钮组,右侧相邻自定义范围选择器;点预设高亮,改自定义范围则取消高亮。适合工具栏 / 图表卡头部。<script setup lang="ts">
/** 场景:列表工具栏——按钮组形态,预设一键切换 + 相邻自定义范围 */
import { ref } from 'vue'
import { BizDateRange } from 'vue3-ep-biz-lib'
import type { BizDateRangePreset } from 'vue3-ep-biz-lib'
const presets: BizDateRangePreset[] = [
{ key: 'today', label: '今日', range: () => [start(), start()] },
{ key: 'thisWeek', label: '本周', range: () => week() },
{ key: 'thisMonth', label: '本月', range: () => month() },
{ key: 'last30', label: '近30天', range: () => [add(start(), -29), start()] },
]
function start(): Date {
const now = new Date()
return new Date(now.getFullYear(), now.getMonth(), now.getDate())
}
function add(base: Date, days: number): Date {
const next = new Date(base)
next.setDate(next.getDate() + days)
return next
}
function week(): [Date, Date] {
const monday = add(start(), -((new Date().getDay() + 6) % 7))
return [monday, add(monday, 6)]
}
function month(): [Date, Date] {
const day = start()
return [new Date(day.getFullYear(), day.getMonth(), 1), day]
}
const range = ref<[Date, Date] | null>(null)
</script>
<template>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange v-model="range" mode="segmented" :presets="presets" size="small" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
{{ range ? `${range[0].toLocaleDateString('zh-CN')} ~ ${range[1].toLocaleDateString('zh-CN')}` : '(未选择)' }}
</span>
</div>
</template>
显示代码
datetime 粒度
至
granularity="datetime" 面板带时分秒;预设可给出整日口径(00:00:00 ~ 23:59:59)或滑动窗口口径(近24小时)。<script setup lang="ts">
/** 场景:监控时段——datetime 粒度(日时分秒),预设取整日与近 24 小时两种口径 */
import { ref } from 'vue'
import { BizDateRange, startOfDay, endOfDay } from 'vue3-ep-biz-lib'
import type { BizDateRangePreset } from 'vue3-ep-biz-lib'
const presets: BizDateRangePreset[] = [
{
key: 'today',
label: '今日',
range: (): [Date, Date] => {
const now = new Date()
return [startOfDay(now), endOfDay(now)]
},
},
{
key: 'yesterday',
label: '昨日',
range: (): [Date, Date] => {
const base = startOfDay(new Date())
base.setDate(base.getDate() - 1)
return [base, endOfDay(base)]
},
},
{
key: 'last24h',
label: '近24小时',
range: (): [Date, Date] => {
const now = new Date()
return [new Date(now.getTime() - 24 * 3600 * 1000), now]
},
},
]
const range = ref<[Date, Date] | null>(null)
</script>
<template>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange v-model="range" granularity="datetime" :presets="presets" value-format="YYYY-MM-DD HH:mm:ss" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
{{ range ? `${range[0]} ~ ${range[1]}` : '(未选择)' }}
</span>
</div>
</template>
显示代码
month 粒度
至
granularity="month" 月粒度(monthrange),配合月边界预设(近 3 / 6 / 12 个月)。<script setup lang="ts">
/** 场景:经营月报——month 粒度,近 3 / 6 / 12 个月预设 */
import { ref } from 'vue'
import { BizDateRange } from 'vue3-ep-biz-lib'
import type { BizDateRangePreset } from 'vue3-ep-biz-lib'
const monthRange = (months: number): [Date, Date] => {
const now = new Date()
return [new Date(now.getFullYear(), now.getMonth() - months + 1, 1), new Date(now.getFullYear(), now.getMonth(), 1)]
}
const presets: BizDateRangePreset[] = [
{ key: 'last3', label: '近3个月', range: () => monthRange(3) },
{ key: 'last6', label: '近6个月', range: () => monthRange(6) },
{ key: 'last12', label: '近12个月', range: () => monthRange(12) },
]
const range = ref<[Date, Date] | null>(null)
</script>
<template>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange v-model="range" granularity="month" :presets="presets" value-format="YYYY-MM" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
{{ range ? `${range[0]} ~ ${range[1]}` : '(未选择)' }}
</span>
</div>
</template>
显示代码
值格式(字符串 / 时间戳)
至
至
value-format 透传 el-date-picker 语义:'YYYY-MM-DD' 输出字符串二元组,'x' 输出毫秒时间戳字符串——直接对接后端查询参数。<script setup lang="ts">
/** 场景:对接后端接口——值格式:字符串 'YYYY-MM-DD' 与毫秒时间戳 'x' 两种 */
import { ref } from 'vue'
import { BizDateRange } from 'vue3-ep-biz-lib'
const stringRange = ref<[string, string] | null>(null)
const stampRange = ref<[string, string] | null>(null)
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 16px">
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange v-model="stringRange" value-format="YYYY-MM-DD" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">字符串值:{{ stringRange ?? '(未选择)' }}</span>
</div>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange v-model="stampRange" value-format="x" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
时间戳值:{{ stampRange ? `${stampRange[0]} ~ ${stampRange[1]}` : '(未选择)' }}
</span>
</div>
</div>
</template>
显示代码
范围限制
至
disabled-date 禁用未来日期;max-span-days 跨度上限守护——超限时不提交(受控值不变即回显回退)并抛出 exceed 事件,由业务决定如何提示。<script setup lang="ts">
/** 场景:报表导出——禁用未来日期 + 跨度上限 31 天(超限不提交,exceed 事件提示) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizDateRange } from 'vue3-ep-biz-lib'
const range = ref<[Date, Date] | null>(null)
const disableFuture = (date: Date): boolean => date.getTime() > Date.now()
const onExceed = (spanDays: number, maxSpanDays: number): void => {
ElMessage.warning(`所选跨度 ${spanDays} 天,超过上限 ${maxSpanDays} 天,已回退`)
}
</script>
<template>
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange
v-model="range"
:disabled-date="disableFuture"
:max-span-days="31"
@exceed="onExceed"
/>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">仅能选择过去且不超过 31 天</span>
</div>
</template>
显示代码
受控与按此刻重算
至
默认值取「近7天」;
refresh() 以此刻重算并重新提交当前命中的相对预设(跨天停留页面的看板刷新场景);setPreset(key) / getActivePreset() 可程序化切换。<script setup lang="ts">
/** 场景:数据看板——默认近7天,refresh() 按此刻重算相对预设,趋势条随范围跨度变化 */
import { computed, ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizDateRange, createDateRangePresets } from 'vue3-ep-biz-lib'
import type { BizDateRangeInstance, BizDateRangeValue } from 'vue3-ep-biz-lib'
const tableRef = ref<BizDateRangeInstance>()
const range = ref<BizDateRangeValue>(createDateRangePresets()[8].range())
const spanDays = computed<number>(() => {
const value = range.value
if (value === null || value === undefined) return 0
const [a, b] = value as [Date, Date]
return Math.floor((b.getTime() - a.getTime()) / 86400000) + 1
})
/** 随跨度生成的模拟趋势(确定性伪随机,保证同值同图) */
const bars = computed<number[]>(() => {
const count = Math.min(Math.max(spanDays.value, 1), 30)
return Array.from({ length: count }, (_, i) => 30 + ((i * 37 + count * 11) % 65))
})
const onRefresh = (): void => {
tableRef.value?.refresh()
ElMessage.success('已按此刻重算相对预设')
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 16px">
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
<BizDateRange ref="tableRef" v-model="range" />
<ElButton size="small" @click="onRefresh">按此刻重算</ElButton>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">跨度 {{ spanDays }} 天</span>
</div>
<div style="display: flex; align-items: flex-end; gap: 4px; height: 96px; padding: 8px 12px; border-radius: 6px; background: var(--el-fill-color-light)">
<div
v-for="(bar, index) in bars"
:key="index"
:style="{ height: `${bar}%`, width: '12px', borderRadius: '2px', backgroundColor: 'var(--el-color-primary-light-3)' }"
/>
</div>
</div>
</template>
显示代码
搜索表单集成
{}<script setup lang="ts">
/** 场景:列表筛选——BizSearchForm 的 date-range 字段开启 shortcuts,查询表单同样拥有业务快捷项 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizSearchForm } from 'vue3-ep-biz-lib'
import type { BizSearchField } from 'vue3-ep-biz-lib'
const fields: BizSearchField[] = [
{ prop: 'keyword', label: '关键词', placeholder: '订单号 / 客户' },
{
prop: 'payTime',
label: '支付时间',
type: 'date-range',
shortcuts: true,
defaultValue: [],
},
{ prop: 'channel', label: '渠道', type: 'select', options: [
{ label: '全部', value: '' },
{ label: '线上商城', value: 'online' },
{ label: '门店', value: 'offline' },
] },
]
const values = ref<Record<string, unknown>>({})
const onSearch = (payload: Record<string, unknown>): void => {
values.value = payload
ElMessage.success('已查询(见下方参数)')
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<BizSearchForm :fields="fields" @search="onSearch" />
<pre style="margin: 0; padding: 8px 12px; font-size: 12px; border-radius: 6px; background: var(--el-fill-color-light)">{{ JSON.stringify(values, null, 2) }}</pre>
</div>
</template>
显示代码
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| model-value / v-model | [Date, Date] | [string, string] | null | — | 选中范围;设置 value-format 后为字符串二元组 |
| granularity | 'date' | 'datetime' | 'month' | 'date' | 粒度 |
| presets | boolean | BizDateRangePreset[] | true | 预设快捷项:true 默认集,false 关闭,数组完全自定义 |
| mode | 'picker' | 'segmented' | 'picker' | 形态:面板快捷栏 / 按钮组 + 自定义 |
| value-format | string | — | el-date-picker 的 value-format(如 'YYYY-MM-DD' / 'x') |
| max-span-days | number | 0 | 值跨度上限(天),超限不提交并发 exceed;0 不限制 |
| size | 'small' | 'default' | 'large' | — | 尺寸 |
| disabled | boolean | false | 是否禁用 |
| clearable | boolean | true | 是否可清空 |
| start-placeholder | string | '开始日期' | 起始占位文案 |
| end-placeholder | string | '结束日期' | 截止占位文案 |
| unlink-panels | boolean | false | 范围选择时左右面板独立切换 |
| disabled-date | (date: Date) => boolean | — | 禁用日期判定 |
其余属性(format、popper-class、range-separator 覆盖等)透传 el-date-picker(picker 形态)/ 根节点(segmented 形态)。
事件
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:model-value | (value: BizDateRangeValue) | 值变化 |
| change | (value: BizDateRangeValue, presetKey: string | null) | 值提交;presetKey 为命中预设标识,自定义范围为 null |
| exceed | (spanDays: number, maxSpanDays: number) | 跨度超限被拦截时触发,值未提交 |
插槽
| 插槽名 | 说明 |
|---|---|
| — | 无插槽 |
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
| setPreset | (key: string) => boolean | 按预设标识应用范围;未找到返回 false |
| getActivePreset | () => BizDateRangePreset | null | 当前命中的预设项;自定义范围为 null |
| refresh | () => void | 按此刻重算并重新提交当前命中的相对预设 |
| focus | () => void | 聚焦内部选择器 |
| blur | () => void | 使内部选择器失焦 |