🕓 自定義時間范圍選擇組件使用教程(基于 Vue 3 + Element Plus)
? 一個靈活實用的時間范圍選擇器,支持開始時間、結束時間、快捷時間選項、本地雙向綁定、插槽擴展等功能。
–
📘 一、功能介紹
該組件基于 Element Plus
的 <el-date-picker>
和 <el-select>
封裝,支持以下特性:
v-model:startTime
和v-model:endTime
雙向綁定;- 常用時間快捷選項:今天、昨天、本月、上月等;
- 自定義插槽
before
,可拓展前置內容; - 自動識別當前選擇是否為快捷項并高亮;
- 支持國際化
$t()
; - 可自定義初始選中項;
- 樣式美觀、可無縫集成至查詢表單。
🧱 二、組件源碼
📂 components/TimeSeparation.vue
<template>
<template><slot name="before" /><el-date-pickerclass="timeSeparationClassStart"style="width:160px"v-model="startTime"type="datetime"format="YYYY-MM-DD HH:mm:ss"value-format="YYYY-MM-DD HH:mm:ss":default-time="new Date(2000, 1, 1, 0, 0, 0)" /><span class="timeSeparationClassCenter">-</span><el-date-pickerclass="timeSeparationClassEnd"style="width:160px"v-model="endTime"type="datetime"format="YYYY-MM-DD HH:mm:ss"value-format="YYYY-MM-DD HH:mm:ss":default-time="new Date(2000, 1, 1, 23, 59, 59)" /><el-select v-model="dateRangeType" class="timeSeparationClass_after" style="width:100px" @change="changeDateRange"><el-optionv-for="dict in dateList":key="dict.value":label="$t(dict.label)":value="dict.value" /></el-select>
</template>
<script setup>
<script setup>
import { useVModel } from '@vueuse/core'
import i18n from '@/i18n'
import { parseTime } from '@/utils/ruoyi'const emit = defineEmits(['update:startTime', 'update:endTime', 'change'])const props = defineProps({startTime: String,endTime: String,defaultTime: Number, // 默認快捷選中類型showAfter: { type: Boolean, default: true }
})// 雙向綁定
const startTime = useVModel(props, 'startTime', emit)
const endTime = useVModel(props, 'endTime', emit)
const dateRangeType = ref(props.defaultTime)function changeDateRange(e) {switch (e) {case 1: setDaysAgo(7); break;case 2: setThisMonth(); break;case 4: setToday(); break;case 5: setYesterday(); break;case 6: setLastMonth(); break;}
}// 日期處理
function setDaysAgo(days) {const now = new Date();const start = new Date(now)start.setDate(start.getDate() - (days - 1))start.setHours(0, 0, 0, 0)now.setHours(23, 59, 59, 999)startTime.value = parseTime(start)endTime.value = parseTime(now)
}function setToday() {const now = new Date();const start = new Date(now)start.setHours(0, 0, 0, 0)now.setHours(23, 59, 59, 999)startTime.value = parseTime(start)endTime.value = parseTime(now)
}function setYesterday() {const start = new Date()const end = new Date()start.setDate(start.getDate() - 1)end.setDate(end.getDate() - 1)start.setHours(0, 0, 0, 0)end.setHours(23, 59, 59, 999)startTime.value = parseTime(start)endTime.value = parseTime(end)
}function setThisMonth() {const now = new Date()const start = new Date(now.getFullYear(), now.getMonth(), 1)const end = new Date(now.getFullYear(), now.getMonth() + 1, 0)end.setHours(23, 59, 59, 999)startTime.value = parseTime(start)endTime.value = parseTime(end)
}function setLastMonth() {const now = new Date()const start = new Date(now.getFullYear(), now.getMonth() - 1, 1)const end = new Date(now.getFullYear(), now.getMonth(), 0)end.setHours(23, 59, 59, 999)startTime.value = parseTime(start)endTime.value = parseTime(end)
}watch([() => startTime.value, () => endTime.value], ([newStart, newEnd]) => {const ranges = [{ num: 2, range: getTimeRange('本月') },{ num: 4, range: getTimeRange('今天') },{ num: 5, range: getTimeRange('昨天') },{ num: 6, range: getTimeRange('上月') },]const nowRange = [newStart, newEnd]const matched = ranges.find(i => JSON.stringify(i.range) === JSON.stringify(nowRange))dateRangeType.value = matched ? matched.num : undefinedemit('change')
})function getTimeRange(type) {const now = new Date()let start = new Date(), end = new Date()switch (type) {case '上月':start = new Date(now.getFullYear(), now.getMonth() - 1, 1)end = new Date(now.getFullYear(), now.getMonth(), 0)breakcase '本月':start = new Date(now.getFullYear(), now.getMonth(), 1)end = new Date(now.getFullYear(), now.getMonth() + 1, 0)breakcase '今天':start.setHours(0, 0, 0, 0)end.setHours(23, 59, 59, 999)breakcase '昨天':start.setDate(start.getDate() - 1)end.setDate(end.getDate() - 1)start.setHours(0, 0, 0, 0)end.setHours(23, 59, 59, 999)break}return [parseTime(start), parseTime(end)]
}const { t } = i18n.global
const dateList = [{ label: t('this_month'), value: 2 },{ label: t('today'), value: 4 },{ label: t('yesterday'), value: 5 },{ label: t('last_month'), value: 6 }
]
</script>
<style scoped lang="scss">
.timeSeparationClassCenter {display: flex;align-items: center;background-color: #fff;box-shadow:inset 0 1px 0 0 var(--el-input-border-color),inset 0 -1px 0 0 var(--el-input-border-color);
}.timeSeparationClassStart .el-input__wrapper {border-radius: var(--el-border-radius-base) 0 0 var(--el-border-radius-base);box-shadow: inset 1px 0 0 0 var(--el-input-border-color);
}.timeSeparationClassEnd .el-input__wrapper {border-radius: 0;box-shadow: inset -1px 0 0 0 var(--el-input-border-color);
}.timeSeparationClass_after .el-select__wrapper {border-radius: 0 var(--el-border-radius-base) var(--el-border-radius-base) 0;background-color: var(--el-fill-color-light);
}
🚀 三、使用示例
<template><TimeSeparationv-model:startTime="queryParams.startTime"v-model:endTime="queryParams.endTime":default-time="2"@change="onDateChange"/>
</template><script setup>
import TimeSeparation from '@/components/TimeSeparation.vue'
const queryParams = reactive({startTime: '',endTime: ''
})
function onDateChange() {console.log('時間范圍變化:', queryParams)
}
</script>
📚 四、Props & Emits API 文檔
Props
參數名 | 類型 | 默認值 | 說明 |
---|---|---|---|
startTime | string | '' | 外部綁定開始時間 |
endTime | string | '' | 外部綁定結束時間 |
defaultTime | number | 無 | 初始選中快捷選項(如 2:本月) |
showAfter | boolean | true | 是否顯示快捷時間選擇 |
Emits
事件名 | 參數 | 說明 |
---|---|---|
update:startTime | string | 綁定用 v-model:startTime |
update:endTime | string | 綁定用 v-model:endTime |
change | 無 | 時間范圍變更回調 |
🧩 五、拓展建議(可選)
拓展方向 | 建議實現方法 |
---|---|
動態傳入快捷項 | 增加 shortcuts: Array prop |
時間范圍校驗 | 內置日期合法性校驗 + disabledDate |
表單集成支持 | 接入 <el-form-item> + rules |
可讀性文本輸出 | 增加 displayRange: computed 返回“xx 至 yy” |