-
实现缘由
有这么一个分享的原因是,我发现很多选择日期的地方都会要求能选择“至今”,但是现在广泛使用的UI库中,都是不支持的。很奇怪,这个需求还是挺常见的,为什么都不支持呢?网上类似的控件也很难找,几乎没找到。所以我自定义了这么一款控件,基于vant picker 来实现的,但是,如果想依赖于其他的UI库,也是比较好迁移过去实现的。
-
效果图
-
代码分享
<template>
<div class="container">
<van-popup v-model="isShow" position="bottom" @close="onClosePop">
<van-picker
ref="van_picker"
:columns="columns"
show-toolbar
:title="pickerShowTitle"
@confirm="onConfirm"
@cancel="onCancle"
@change="onChange"
/>
</van-popup>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue, Watch} from 'vue-property-decorator'
@Component({
name: 'CustomDate'
})
export default class CustomDate extends Vue {
public $refs!: {
van_picker: HTMLFormElement;
};
@Prop({
type: Boolean,
default: false
})
isShowProp!: boolean;
@Prop({
type: Boolean,
default: false
})
isMultiProp!: boolean;
@Prop({
type: String,
default: ''
})
showTimeProp!: string;
@Prop({
type: String,
default: ''
})
titleProp!: string;
columns: any = [];
isShow: boolean = this.isShowProp;
isMulti: boolean = this.isMultiProp;
showTime = this.showTimeProp;
pickerTitle = this.titleProp.split('-');
pickerShowTitle = '';
multiShowTime = '';
@Watch('isShowProp', {
deep: true
})
watchisShow(data: any) {
this.isShow = data;
this.$nextTick(()=>{
this.pickerShowTitle = this.pickerTitle[0];
this.showTime = this.showTimeProp;
this.timeInit();
})
}
initLastData() {
const myDate = new Date();
const data = {
year: myDate.getFullYear(),
month: myDate.getMonth() + 1,
date: myDate.getDate(),
};
const options1 = [{ text: "至今", value: -1 }],options2 = [], options3 = [], dataFull = [];
//初始化近30年
for (let i = data.year; i > data.year - 31; i--) {
options1.push({
text: i + "",
value: i,
});
}
if (this.showTime !== '至今' && this.showTime !== '') {
const arr = this.showTime.split('-')
for (let j = 1; j <= data.month ; j++) {
options2.push({
text: j < 10 ? "0" + j : "" + j,
value: j,
});
}
// 非当前年
for (let z = 1; z <= 12; z++) {
dataFull.push({
text: z < 10 ? '0' + z : '' + z,
value: z < 10 ? '0' + z : '' + z
})
}
// 初始化当前月天数数据,限制小于等于今天
for (let j = 1; j <= data.date; j++) {
options3.push({
text: j < 10 ? "0" + j : "" + j,
value: j,
});
}
this.columns = [
{ values: options1 },
{ values: parseInt(arr[0]) < data.year ? dataFull : options2 },
{ values: options3 },
];
} else {
this.columns = [{values: options1}, {values: []},{values: []}];
}
}
onConfirm(value: any) {
let date: any = '';
value.forEach((item: any, index: any) => {
if(item) {
date += index === 0 ? ''+ item.text : '-' + item.text;
}
});
if (this.isMulti) {
if (this.pickerShowTitle !== this.pickerTitle[1]) {// 第一次选择时间
this.showTime = date;
this.pickerShowTitle = this.pickerTitle[1];
// 限制时间
this.timeLimit(this.showTime)
} else {// 第二次选择
this.multiShowTime = this.showTime + '-' + date;
this.$emit('onConfirm',this.multiShowTime);
}
} else {
this.showTime = date;
this.$emit('onConfirm',date)
}
}
onCancle() {
this.$emit('onCancle')
}
onClosePop() {
this.$emit('onCancle')
}
// 限制选择起始时间后时间
timeLimit(startTime: string) {
const myDate = new Date()
const data = {
year: myDate.getFullYear()
}
const arr = startTime.split('-')
const options1 = [{ text: "至今", value: -1 }], options2 = [], options3 = []
for (let i = data.year; i >= parseInt(arr[0]); i--) {
options1.push({
text: i + "",
value: i,
});
}
for (let j = 12; j >= parseInt(arr[1]); j--) {
options2.push({
text: j < 10 ? '0' + j : '' + j,
value: j
})
}
const isLeapYear = parseInt(arr[0]) % 4 === 0 && parseInt(arr[0]) % 100 !== 0 || parseInt(arr[0]) % 400 === 0;
const nowMonth = isNaN(parseInt(arr[0])) ? -1 : parseInt(arr[1]);
const isBigMonth = [1,3,5,7,8,10,12].indexOf(nowMonth) !== -1;
for (let k = (isBigMonth? 31: (nowMonth === 2? (isLeapYear? 29:28) : 30)); k >= parseInt(arr[2]); k--) {
options3.push({
text: k < 10 ? '0' + k : '' + k,
value: k
})
}
this.$refs.van_picker.setColumnValues(0, options1)
this.$refs.van_picker.setColumnValues(1, options2.reverse())
this.$refs.van_picker.setColumnValues(2, options3.reverse())
this.$refs.van_picker.setColumnValue(0, arr[0])
this.$refs.van_picker.setColumnValue(1, arr[1])
this.$refs.van_picker.setColumnValue(2, arr[2])
}
onChange(picker: any, values: any, index: any) {
const myDate = new Date()
const year = myDate.getFullYear()
const month = myDate.getMonth() + 1
const dataFull = [],options2 = [],options3 = []
// 滑动年份数据
if (index === 0) {
const arr = this.showTime.split('-')
if (this.isMulti && this.pickerShowTitle === this.pickerTitle[1] && parseInt(arr[0]) === values[0].value) {
this.timeLimit(this.showTime)
return;
}
for (let z = 1; z <= 12; z++) {
dataFull.push({
text: z < 10 ? '0' + z : z,
value: z
})
}
for (let j = 1; j <= month; j++) {
options2.push({
text: j < 10 ? '0' + j : j,
value: j
})
}
//如果选项变成至今,则月份选项为空
if (values[0].value === -1) {
picker.setColumnValues(1, [])
picker.setColumnValues(2, [])
return
}
//如果年选项小于当前年,则显示12个月
if (values[0].value < year) {
picker.setColumnValues(1, dataFull)
} else {
picker.setColumnValues(1, options2)
}
if (options3.length === 0) {
const isLeapYear = values[0].value % 4 === 0 && values[0].value % 100 !== 0 || values[0].value % 400 === 0;
const nowMonth = (values[0].value < year) ? dataFull[0].value : options2[0].value;
const isBigMonth = [1,3,5,7,8,10,12].indexOf(nowMonth) !== -1;
for (let j = 1; j <= (isBigMonth? 31: (nowMonth === 2? (isLeapYear? 29:28) : 30)); j++) {
options3.push({
text: j < 10 ? '0' + j : j,
value: j
})
}
picker.setColumnValues(2, options3)
}
} else if (index === 1) { // 滑动月份数据
const arr = this.showTime.split('-')
if (this.isMulti && this.pickerShowTitle === this.pickerTitle[1] && parseInt(arr[1]) === values[1].value) {
this.timeLimit(this.showTime)
return;
}
const isLeapYear = values[0].value % 4 === 0 && values[0].value % 100 !== 0 || values[0].value % 400 === 0;
const isBigMonth = [1,3,5,7,8,10,12].indexOf(values[1].value) !== -1;
for (let j = 1; j <= (isBigMonth? 31: (values[1].value === 2? (isLeapYear? 29:28) : 30)); j++) {
options3.push({
text: j < 10 ? '0' + j : j,
value: j
})
}
picker.setColumnValues(2, options3)
}
}
timeInit(){
const myDate = new Date()
const data = {
year: myDate.getFullYear(),
month: myDate.getMonth() + 1
}
const arr = this.showTime.split('-')
const options1 = [{ text: "至今", value: -1 }], dataFull = [], options2 = [], options3 = []
if (this.showTime != '至今' && this.showTime !== '') {
for (let i = data.year; i > data.year - 31; i--) {
options1.push({
text: i + "",
value: i,
});
}
// 12个月
for (let z = 1; z <= 12; z++) {
dataFull.push({
text: z < 10 ? '0' + z : '' + z,
value: z
})
}
//小于当前月的选项
for (let j = 1; j <= data.month; j++) {
options2.push({
text: j < 10 ? '0' + j : '' + j,
value: j
})
}
const isLeapYear = parseInt(arr[0]) % 4 === 0 && parseInt(arr[0]) % 100 !== 0 || parseInt(arr[0]) % 400 === 0;
const nowMonth = isNaN(parseInt(arr[0])) ? -1 : parseInt(arr[1]);
const isBigMonth = [1,3,5,7,8,10,12].indexOf(nowMonth) !== -1;
if (nowMonth !== -1) {
for (let j = 1; j <= (isBigMonth? 31: (nowMonth === 2? (isLeapYear? 29:28) : 30)); j++) {
options3.push({
text: j < 10 ? '0' + j : j + '',
value: j
})
}
}
this.$refs.van_picker.setColumnValues(0, options1)
//回填月份数据
if (parseInt(arr[0]) < data.year) {
this.$refs.van_picker.setColumnValues(1, dataFull)
} else {
this.$refs.van_picker.setColumnValues(1, options2)
}
// 回填天数数据
this.$refs.van_picker.setColumnValues(2, options3)
//回填选中的值
this.$refs.van_picker.setColumnValue(0, arr[0])
this.$refs.van_picker.setColumnValue(1, arr[1])
this.$refs.van_picker.setColumnValue(2, arr[2])
} else {
this.$refs.van_picker.setColumnIndex(0, 0, 0)
this.$refs.van_picker.setColumnValues(1, [])
this.$refs.van_picker.setColumnValues(2, [])
}
}
mounted() {
this.initLastData();
}
}
</script>
<style lang="scss">
.container {
}
</style>
-
如何使用
import CustomDate from "@/components/CustomDate.vue";
<CustomDate
@onCancle="onCusDateCancle"
@onConfirm="onCusDateConfirm"
:isShowProp="isShowPop"
:showTimeProp="showTimeProp"
titleProp="起始时间-结束时间"
isMultiProp>
</CustomDate>
titleProp: 设置标题,以"-"分隔 实现区间选择的两个标题显示
isMutiprop: 设置是否开启区间选择
-
实现的原理
主要是基于 vant picker 的 setColumnValues
来实现动态的日期显示,所以,原理其实挺简单,这里也是做一个分享,有需要的小伙伴可以拿来直接用,找不到合适的控件真的很难受。
这里感谢https://blog.youkuaiyun.com/weixin_40408338/article/details/105159765 提供了实现的思路,大家也可以看一下这位作者的文章,实现了年月的联动,比较清晰。