1.业务背景
在工作场景中,面临这样一个业务需求:构建一个工作量填报系统,该系统的核心功能之一,是能够在每月的既定时间推送工作量填报待办卡片给用户。
管理员可通过系统灵活配置推送时间,精确到每月的具体日期、小时、分钟以及秒。
为达成上述功能,系统需实现一个每月的日时分秒选择器。
2.实现效果
3.实现思路
查阅 Element Plus官方文档 后,Element Plus的 Date Picker 没有直接可实现该业务场景时间格式的类型。
Date picker 可选的type类型:
可以使用type='datetime'的 el-date-picker,使用CSS隐藏掉年月选择栏和星期行,将格式format设为'DD日 HH:mm:ss'。这样用户直接观感上只选择了日时分秒。最后按后端参数格式要求取其中的日时分秒请求接口就可以了。
4.源码及细节
el-date-picker:
<template>
<el-form :model="formData">
<el-row :gutter="20">
<el-col :span="4">
<el-form-item prop="start" label="开始时间">
<el-date-picker
v-model="formData.start"
type="datetime"
placeholder="请选择每月开始时间"
popper-class="only-date-picker-popper"
date-format="DD日"
time-format="HH:mm:ss"
format="DD日 HH:mm:ss"
:default-value="formatDate()"
@change="(val) => onOnlyDateTimeChange(val, 'start')"
/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item prop="end" label="结束时间">
<el-date-picker
v-model="formData.end"
type="datetime"
placeholder="请选择每月结束时间"
popper-class="only-date-picker-popper"
date-format="DD日"
time-format="HH:mm:ss"
format="DD日 HH:mm:ss"
:default-value="formatDate()"
@change="(val) => onOnlyDateTimeChange(val, 'end')"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup>
import { ref } from 'vue'
const formData = ref({ start: "", end: "" })
</script>
css:
需隐藏的部分:
.only-date-picker-poper{
.el-date-picker__header{
display:none !important;
}
.el-picker-panel__content{
tr:first-child{
display:none;
}
.next-month{
visibility:hidden;
}
.prev-month{
visibility:hidden;
}
}
}
细节完善: 为了使在外观上看起来整齐,选择的固定年月2024年1月。该月的1号在星期一,并且日选择器需要保证该月必须有31天。该月也符合此条件。
<script setup>
//...
const DEFAULT_YEAR = '2024'
const DEFAULT_MONTH = '01'
const formatDate = (day = 1, time = "00:00:00") => {
if (!day || !time) {
return null
}
const dayString = typeof day === "number" ? day.toString() : day;
return new Date(
`${DEFAULT_YEAR}-${DEFAULT_MONTH}-${dayString.padStart(2, "0")} ${time}`
)
}
</script>
虽然在css上隐藏掉了年月选择器,但是通过键盘方向键操作仍可跳到其余年月。这样在使用时并不会有什么问题,因为后端只需要日的值。只是用户下次打开的1号不在顶格,样式不好看。因此加上一层保险:监听el-date-picker的change时间,将年月赋值回默认值。
<script setup>
//...
const onOnlyDateTimeChange = (val, prop) => {
const hour = val.getHours().toString().padStart(2, "0")
const minute = val.getMinutes().toString().padStart(2, "0")
const second = val.getSeconds().toString().padStart(2, "0")
const day = val.getDate()
formData.value[prop] = formatDate(day, `${hour}:${minute}:${second}`)
}
</script>
完整VUE代码:
<template>
<el-form :model="formData">
<el-row :gutter="20">
<el-col :span="4">
<el-form-item prop="start" label="开始时间">
<el-date-picker
v-model="formData.start"
type="datetime"
placeholder="请选择每月开始时间"
popper-class="only-date-picker-popper"
date-format="DD日"
time-format="HH:mm:ss"
format="DD日 HH:mm:ss"
:default-value="formatDate()"
@change="(val) => onOnlyDateTimeChange(val, 'start')"
/>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item prop="end" label="结束时间">
<el-date-picker
v-model="formData.end"
type="datetime"
placeholder="请选择每月结束时间"
popper-class="only-date-picker-popper"
date-format="DD日"
time-format="HH:mm:ss"
format="DD日 HH:mm:ss"
:default-value="formatDate()"
@change="(val) => onOnlyDateTimeChange(val, 'end')"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup>
import { ref } from "vue";
const formData = ref({ start: "", end: "" });
const DEFAULT_YEAR = "2024";
const DEFAULT_MONTH = "01";
const formatDate = (day = 1, time = "00:00:00") => {
if (!day || !time) {
return null
}
const dayString = typeof day === "number" ? day.toString() : day;
return new Date(
`${DEFAULT_YEAR}-${DEFAULT_MONTH}-${dayString.padStart(2, "0")} ${time}`
)
}
const onOnlyDateTimeChange = (val, prop) => {
const hour = val.getHours().toString().padStart(2, "0")
const minute = val.getMinutes().toString().padStart(2, "0")
const second = val.getSeconds().toString().padStart(2, "0")
const day = val.getDate()
formData.value[prop] = formatDate(day, `${hour}:${minute}:${second}`)
}
</script>
<style lang="scss">
.only-date-picker-popper {
.el-date-picker__header {
display: none !important;
}
.el-picker-panel__content {
tr:first-child {
display: none;
}
.next-month {
visibility: hidden;
}
.prev-month {
visibility: hidden;
}
}
}
</style>