前言
最近遇到一个需求:需要限制报表下载的日期范围,统一为半个月,防止用户选择范围过广,导致报表生成缓慢。
项目中使用的element-ui的el-date-picker日期选择组件。
实现思路
- 1.由于页面中有12处需要修改,所以需要对el-date-picker进行二次封装。
- 2.el-date-picker提供的picker-options中的disabledDate属性可以对可选日期进行动态禁用与启用。
- 3.disabledDate属性只能针对选择日期的情况,当用户手动输入日期时,需要进行拦截处理。
具体实现
声明了全局组件HalfMonthPicker
组件模板定义
组件中使用v-bind="listeners"进行了属性和事件透传,方便后期维护。
<el-date-picker
v-bind="$attrs"
v-on="$listeners"
:type="type"
:range-separator="rangeSeparator"
:start-placeholder="startPlaceholder"
:end-placeholder="endPlaceholder"
:value="value"
:picker-options="pickerOptions"></el-date-picker>
定义props属性 type
el-date-picker目前支持的范围属性为daterange和datetimerange,内部使用了校验函数,确保传入的参数值,默认为daterange。
props: {
type: {
type: String,
default: 'daterange',
validator(val) {
return ['daterange', 'datetimerange'].includes(val)
}
}
}
设置默认属性值
由于页面中组件使用时都传入了自定义的range-separator,start-placeholder,和end-placeholder,其属性值相同,所以在组件内部定义了props,并设置了默认值,避免页面中重复定义。
props: {
rangeSeparator: {
type: String,
default: '至'
},
startPlaceholder: {
type: String,
default: '起止日期'
},
endPlaceholder: {
type: String,
default: '截止日期'
}
}
定义核心计算属性pickerOptions
基本变量定义
currentFirstDate
首个选中的日期
currentFirstTime
首个选中的日期毫秒数
currentMinTime
小于currentFirstTime半个月的毫秒数
currentMaxTime
大于currentFirstTime半个月的毫秒数
currentTime
disabledDate函数传入的日期值的毫秒数,用于判断是否在currentMinTime与currentMaxTime之间
shortcuts快捷选项
此处按照产品需求,内置了最近一周和最近半个月的快捷选项。
disabledDate实现
如果存在currentFirstDate,将小于半月或超出半月的日期置为不可选中。
如果不存在,则皆可选。
onPick注意事项
1.当maxDate和minDate都不存在
将currentFirstDate置为null,不限制日期选择
2.minDate存在,而maxDate不存在
currentFirstDate = minDate,并限制日期选择为前后半个月
3.maxDate和minDate同时存在
考虑如下情况,type为datetimerange时,即使选中了开始日期和结束日期,如果没有点确定,弹框并不会消失,此时应该可以重新选择新的开始日期,所以需要将currentFirstDate置为null,不在限制日期的选择。
未置为null | 置为null |
---|---|
computed: {
pickerOptions() {
const { value } = this
// 选中的首个日期
let currentFirstDate = Array.isArray(value) && value.length === 1 && value[0] ? value[0] : null
// 选中的首个日期毫秒数
let currentFirstTime = null
// 当前日期毫秒数
let currentTime = null
// 当前可选最小日期毫秒数
let currentMinTime = null
// 当前可选最大日期毫秒数
let currentMaxTime = null
return {
// 快捷选项
shortcuts: [
{
text: '最近一周',
onClick(picker) {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 6)
picker.$emit('pick', [start, end])
}
},
{
text: '最近半月',
onClick(picker) {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 14)
picker.$emit('pick', [start, end])
}
}
],
// 禁选逻辑
disabledDate(date) {
if (currentFirstDate) {
currentFirstTime = new Date(currentFirstDate).getTime()
currentTime = new Date(date).getTime()
currentMinTime = currentFirstTime - 3600 * 1000 * 24 * 15
currentMaxTime = currentFirstTime + 3600 * 1000 * 24 * 15
// 确保当前可选日期在半月之间
return !(currentTime > currentMinTime && currentTime < currentMaxTime)
}
return false
},
// 选中项
onPick({ maxDate, minDate }) {
// console.log(maxDate)
currentFirstDate = !maxDate ? minDate : null
}
}
}
}
处理手动输入的情况
考虑如下场景,当用户选择在input框中手动输入日期时,pickerOptions中的disabledDate禁选属性便无法对用户产生拦截作用,此时便需要通过监听value属性的变化,当newVal间隔大于半个月时,重置回oldVal。
watch: {
// 处理手动输入的情况
value: {
handler(newVal, oldVal) {
if (Array.isArray(newVal) && newVal.length === 2) {
if (this.compareHalfMonthInterval(newVal[0], newVal[1])) {
this.$message.warning('日期间隔不能超过半个月')
// 重置为oldVal或null
this.$emit('input', oldVal || null)
}
}
},
immediate: true
}
},
methods: {
/**
* 比较时间是否超出半个月
* @param {*} startDate 开始时间
* @param {*} endDate 结束时间
* @returns { boolean } true为超出半个月
*/
compareHalfMonthInterval(startDate, endDate) {
if (!startDate || !endDate) {
return false
}
const startTime = new Date(startDate).getTime()
const endTime = new Date(endDate).getTime()
return (endTime - startTime) > 3600 * 1000 * 24 * 15
}
}
页面中使用
使用前 | 使用后 |
---|---|
使用效果
选择开始日期 | 选择结束日期 |
---|---|
完整代码实现
<template>
<el-date-picker
v-bind="$attrs"
v-on="$listeners"
:type="type"
:range-separator="rangeSeparator"
:start-placeholder="startPlaceholder"
:end-placeholder="endPlaceholder"
:value="value"
:picker-options="pickerOptions"></el-date-picker>
</template>
<script>
export default {
name: 'HalfMonthPicker',
props: {
type: {
type: String,
default: 'daterange',
validator(val) {
return ['daterange', 'datetimerange'].includes(val)
}
},
value: {
type: Array,
default: () => []
},
rangeSeparator: {
type: String,
default: '至'
},
startPlaceholder: {
type: String,
default: '起止日期'
},
endPlaceholder: {
type: String,
default: '截止日期'
}
},
computed: {
pickerOptions() {
const { value } = this
// 选中的首个日期
let currentFirstDate = Array.isArray(value) && value.length === 1 && value[0] ? value[0] : null
// 选中的首个日期毫秒数
let currentFirstTime = null
// 当前日期毫秒数
let currentTime = null
// 当前可选最小日期毫秒数
let currentMinTime = null
// 当前可选最大日期毫秒数
let currentMaxTime = null
return {
// 快捷选项
shortcuts: [
{
text: '最近一周',
onClick(picker) {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 6)
picker.$emit('pick', [start, end])
}
},
{
text: '最近半月',
onClick(picker) {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 14)
picker.$emit('pick', [start, end])
}
}
],
// 禁选逻辑
disabledDate(date) {
if (currentFirstDate) {
currentFirstTime = new Date(currentFirstDate).getTime()
currentTime = new Date(date).getTime()
currentMinTime = currentFirstTime - 3600 * 1000 * 24 * 15
currentMaxTime = currentFirstTime + 3600 * 1000 * 24 * 15
// 确保当前可选日期在半月之间
return !(currentTime > currentMinTime && currentTime < currentMaxTime)
}
return false
},
// 选中项
onPick({ maxDate, minDate }) {
// console.log(maxDate)
currentFirstDate = !maxDate ? minDate : null
}
}
}
},
watch: {
// 处理手动输入的情况
value: {
handler(newVal, oldVal) {
if (Array.isArray(newVal) && newVal.length === 2) {
if (this.compareHalfMonthInterval(newVal[0], newVal[1])) {
this.$message.warning('日期间隔不能超过半个月')
// 重置为oldVal或null
this.$emit('input', oldVal || null)
}
}
},
immediate: true
}
},
methods: {
/**
* 比较时间是否超出半个月
* @param {*} startDate 开始时间
* @param {*} endDate 结束时间
* @returns { boolean } true为超出半个月
*/
compareHalfMonthInterval(startDate, endDate) {
if (!startDate || !endDate) {
return false
}
const startTime = new Date(startDate).getTime()
const endTime = new Date(endDate).getTime()
return (endTime - startTime) > 3600 * 1000 * 24 * 15
}
}
}
</script>