嗯~最近做项目的时候,遇到个功能需要使用日历选择器的,有在Dclound上寻找插件,找到几个吧,但是发现其实都有一定程度的不符合需求,要么就是初次渲染若是初始时间属于过期时间不能替换样式,要么就是必须带初始选择时间,也想着给他插件做做修改,但是发现不是自己写的代码,整理起来确实蛋疼,不过也确实受到了一定的启发,所以就花了半天的时间自己封装了一个日历选择器出来,可能不算太好,但是满足了目前的项目需求,在这里做个记录。
<template>
<!-- 使用方法 -->
<!--
1.传入参数startDate为开始时间
2.传入参数endDate为结束时间
3.传入limit为需要渲染的月份数
4.点击确认导出选择的日期参数{startDate,endDate,dayNum}
-->
<!-- 布局构思 -->
<!-- 考虑能通过自适应应对高度需求 -->
<!-- 使用方法:在页面限制使用容器作为节点挂载该组件,并对节点设置heigth,以达到想要的高度,头部80rpx,底部按钮112rpx安全区域,若是要自定义样式请自行修改dateView的定位值 -->
<view class="container">
<view class="navTop">
<view class="cancel" @click="cancel()">取消</view>
<view class="title">日期选择器</view>
</view>
<!-- 周一至周日 -->
<view class="week">
<view v-for="(item,index) in weekList" :class="{sunDay:index==0}" :key="index">{{item.title}}</view>
</view>
<!-- 日期的显示容器 -->
<view class="dateView">
<view class="date" v-for="(item,index) in dateList" :key="index">
<view class="yearAndMonth">{{item.year}}年{{item.month}}月</view>
<view class="dayList">
<view v-for="(day,No) in item.dayArray" @click="chooseDate(item.year,item.month,day)" :class="['day',dealClassStyle(item.year,item.month,day)]" :key="No">
<view class="today" v-if="dealTimeString(`${item.year}-${item.month}-${day}`)==dealTimeString(today)">
<view>今天</view>
</view>
<view class="star" v-if="dealTimeString(`${item.year}-${item.month}-${day}`)==startTimeString">
<view>开始</view>
</view>
<view class="end" :class="{same:endTimeString==startTimeString}" v-if="dealTimeString(`${item.year}-${item.month}-${day}`)==endTimeString">
<view>结束</view>
</view>
<view>{{day}}</view>
</view>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="btnGroup">
<view class="reset" @click="reset()">重置</view>
<view class="confirm" @click="confirmChoose()">确认</view>
</view>
</view>
</template>
<script>
// js构思
// 1.获取当日 的 日期 , 编写function处理出渲染数据
// 2.编写function,可通过传入的年 月 获取当月的天数 (涉及处理闰年 闰月 大月 小月 ) 注:获取每月1号是周几,在获取的天数数组前推进对应上月天数空白项
// 3.编写function,可通过开始日期以及limit限制获取需要渲染的月份日历格式为{year:"xxxx-xx-xx",month:"xx",dateArray:"[1,2,3,4,5,6...]" }
// 4.编写function,可通过传入的年月日通过处理出的时间戳来给日期动态添加样式类名
// 5.编写function,选择时间时传入年月日,并处理出时间戳,替换默认开始与结束时间时间戳达到替换样式效果
export default {
props: {
// 开始日期
startDate: {
type: String,
default: ''
},
// 结束日期
endDate: {
type: String,
default: ''
},
// 限制显示的月份
limit: {
type: String || Number,
default: 6
}
},
data() {
return {
year: '', //当年
month: '', //当月
week: '', //当天是周几
day: '', //当天的号数
today: '', //当天
todayTimeString: '', //当日时间戳
startTimeString: '', //开始的时间戳,重要,用于多个地方的判断
endTimeString: '', //结束的时间戳,重要,用于多个地方的判断
timeArr:[], //选择事件时的开始/结束时间戳数组
dateList: [], //用于渲染的日历数据合集
weekList: [{ //周的抬头
title: '日',
index: 0
}, {
title: '一',
index: 1
}, {
title: '二',
index: 2
}, {
title: '三',
index: 3
}, {
title: '四',
index: 4
}, {
title: '五',
index: 5
}, {
title: '六',
index: 6
}]
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init(){
this.ajaxDate() //先处理当天的日期
this.dealStarAndEndDay() //处理传入参数
this.dealDateList() //处理出最终渲染数据
},
// 取消事件,未定义,请自行编写取消时的方法
cancel(){
},
// 重置选择
reset(){
this.timeArr = new Array() //清空数组
this.startTimeString = '' //清空开始时间的时间戳
this.endTimeString = '' //清空结束时间的时间戳
},
// 确认选择时间
confirmChoose(){
let reg = /\S/; //非空正则
if(reg.test(this.startTimeString)&®.test(this.endTimeString)){ //判断开始与结束时间不为空方能导出时间并调取selectDate传参事件
let startDate = this.dealDate(this.startTimeString)
let endDate = this.dealDate(this.endTimeString)
let dayNum = ((this.endTimeString - this.startTimeString)/1000/3600/24)+1
// 用于展示,可删除
uni.showModal({
title:'提示',
content:`你选择了${startDate}至${endDate},总共${dayNum}天`
})
// 此处调用传参
this.$emit('selectDate',{startDate:startDate,endDate:endDate,dayNum:dayNum})
}else{
uni.showModal({
title:'提示',
content:`尚未选择时间`
})
}
},
// 选择开始结束日期
chooseDate(year,month,day){
let time = `${year}-${month}-${day}`
let timeString = new Date(time).getTime()
if(timeString>=this.todayTimeString){ //选择的时间必须大于等于今天,过期时间不给选择
if(this.timeArr.length>=2){ //如果数据量大于等于2证明已是多次选择,需做判断
this.timeArr.shift(0,1) //删掉第一条时间
this.timeArr.push(timeString) //并推入新选择时间
if(this.timeArr[0]<this.timeArr[1]){ //若是[0]小于[1]
this.startTimeString = this.timeArr[0] //则[0]为开始时间
this.endTimeString = this.timeArr[1] //[1]为结束时间
}else if(this.timeArr[0]>this.timeArr[1]){ //若是[0]大于[1]
this.startTimeString = this.timeArr[1] //则[1]为开始时间
this.endTimeString = this.timeArr[0] //[0]为结束时间
}else if(this.timeArr[0]==this.timeArr[1]){
this.startTimeString = this.timeArr[0]
this.endTimeString = this.timeArr[1]
}
}else{ //若是数据量小于2,证明是第一次选择,正常将数据推入数组即可
this.timeArr.push(timeString)
this.startTimeString = this.timeArr[0]
this.endTimeString = this.timeArr[1]
}
// console.log('timeArr',this.timeArr)
}else{
uni.showToast({
title:"选择日期不能小于当天!",
icon:'none'
})
}
},
// 处理返回事件戳 用于数据对比,展示开始 结束 标签
dealTimeString(time){
return new Date(time).getTime()
},
// 处理出dateList用于最终渲染
dealDateList() {
let start;
if (this.startDate) { //如果开始时间不为空则以开始时间为开始月份的基准
start= this.startDate.replace('/','-')
} else { //否则以当日时间为开始月份的基准
start = this.today
}
let year = new Date(start).getFullYear();
let month = new Date(start).getMonth();
for (let i = 0; i < this.limit; i++) { //limit默认为渲染6个月
month++
if (month > 12) { //若是月份大于12,年份+1
month = month - 12 //月份减12
year = year + 1
}
month=month<10?'0'+month:month,
this.dateList.push({
year: year, //年
month: month, //月
dayArray: this.dealDateArray(year, month) //日数组
})
}
// console.log('列表', this.dateList)
},
// 获取传参日期后处理出当天时间戳(重要)
dealStarAndEndDay() {
let startTimeString;
let endTimeString;
// console.log('开始结束',this.startDate,this.endDate)
if (this.startDate) { //若有传开始时间,则处理出开始时间的时间戳,
startTimeString = new Date(this.startDate.replace('/','-')).getTime()
} else {
startTimeString = false
}
if (this.endDate) { //若有传结束时间,则处理出结束时间的时间戳,
endTimeString = new Date(this.endDate.replace('/','-')).getTime()
} else {
endTimeString = false
}
this.startTimeString = startTimeString;
this.endTimeString = endTimeString;
},
// 获取当日的 年-月-日
ajaxDate() {
let date = new Date()
let year = date.getFullYear()
let month = date.getMonth() + 1
let week = date.getDay()
let day = date.getDate()
this.year = year
this.month = month
this.week = week
this.day = day
month = month < 10 ? '0' + month : month,
day = day < 10 ? '0' + day : day,
this.today = `${year}-${month}-${day}`
this.todayTimeString = new Date(`${year}-${month}-${day}`).getTime()
},
// 传入年份月份返回当月天数
dealDateArray(year, month) {
let big = [1, 3, 5, 7, 8, 10, 12]; //每年的大月数组
let type; //0为闰年 1为平年
let dayNum; //当月天数
let dayArray = new Array() //处理返回的当月天数数组
if ((year % 100 != 0 && year % 4 == 0 && year % 4 != 0) || (year % 100 == 0 && year % 400 == 0)) { //非世纪年能被4整除且不能被100整除为闰年,世纪年能被400整除为闰年
// console.log('闰年')
type = 0
} else {
type = 1
// console.log('平年')
}
if (big.includes(month)) { //属于大月数组为大月,31天
dayNum = 31
// console.log('大月', dayNum)
} else {
if (month == 2) { //闰年2月29天
if (type == 0) {
dayNum = 29
// console.log('闰月', dayNum)
} else { //平年2月28天
dayNum = 28
// console.log('平月', dayNum)
}
} else { //其余小月30天
dayNum = 30
// console.log('小月', dayNum)
}
}
let No = new Date(`${year}-${month}`).getDay() // 获取每月的1号是星期几
for (let i = 0; i < No; i++) { // 补全日历空白区域,将1号对齐至周几
dayArray.unshift('') // 在数组的前面插进空字符串
}
for (let i = 1; i <= dayNum; i++) {
i = i < 10 ? '0' + i : i
dayArray.push(i)
}
return dayArray
},
// 通过对比处理日期的时间戳返回样式的类名
dealClassStyle(year,month,day) {
if(day){ //day必须存在,为空字符串不处理
let time = `${year}-${month}-${day}`
let timeString = new Date(time).getTime()
let todayTimeString = this.todayTimeString
if (timeString == this.startTimeString) { //时间戳等于开始时间时间戳
return 'startDate'
} else if (timeString == this.endTimeString) { //时间戳等于结束时间时间戳
return 'endDate'
} else
if (timeString > this.startTimeString && timeString < this.endTimeString) { //大于开始时间时间戳并小于结束时间时间戳的范围
return 'scope'
} else if (timeString == todayTimeString) { //今天
return 'toDay'
} else if (timeString < todayTimeString) { //过期时间
return 'overdue'
}
}
},
// 处理出时间的方法
dealDate(time=''){
let date = new Date(time)
let year = date.getFullYear();
let month = date.getMonth()+1;
let day = date.getDate();
month = month<10?'0'+month:month;
day = day<10?'0'+day:day;
return `${year}-${month}-${day}`
},
}
}
</script>
<style lang="less" scoped>
// 整个容器
.container {
position: relative;
height: 100%;
padding: 0 62rpx;
// 头部操作区域
.navTop {
position: relative;
display: flex;
align-items: center;
height: 80rpx;
margin-bottom: 30rpx;
// 取消按钮
.cancel {
font-size: 28rpx;
}
// 标题
.title {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
font-size: 32rpx;
line-height: 80rpx;
text-align: center;
}
}
// 周容器 周日-周六
.week {
display: flex;
align-items: center;
border-bottom: 1rpx solid #ededed;
padding-bottom: 20rpx;
margin-bottom: 30rpx;
view {
width: 14.2857%;
text-align: center;
}
}
// 年份以及月份
.yearAndMonth {
text-align: center;
margin-bottom: 30rpx;
}
// 日期视口容器
.dateView {
position: absolute; //绝对定位
top: 220rpx; //头部操作区域以及想要的margin-bottomm
bottom: 112rpx; //底部操作区域高度
left: 62rpx;
right: 62rpx;
flex: 1;
overflow: auto;
.date {
margin-bottom: 60rpx;
}
.dayList {
display: flex;
flex-wrap: wrap;
.day {
position: relative;
width: 14.2857%;
text-align: center;
color: #666;
padding: 30rpx 0;
}
.startDate,.endDate{
color: #fff;
background: linear-gradient(90deg, #FF5F32 0%, #F10E31 100%);
border-radius: 8rpx;
}
.star,.end{
position: absolute;
top:2rpx;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 20rpx;
}
.same{
bottom:-63%;
}
// 今天
.today {
position: absolute;
top:2rpx;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
color: #F10E31;
font-size: 20rpx;
view{
border-bottom: 2rpx solid #F10E31;
}
}
// 今天以前 过期
.overdue {
color: #cecece;
}
// 选择范围
.scope {
background-color: #FDDCDB;
}
}
}
// scrollbar隐藏
.dateView::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
background-color: transparent;
display: none;
}
}
/* 周日红色 */
.sunDay {
color: #fe3c3c;
}
// 底部按钮区域
.btnGroup {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
z-index: 1;
border-top: 1rpx #ededed solid;
view {
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
font-size: 32rpx;
box-sizing: border-box;
border-radius: 50rpx;
width: 328rpx;
}
:first-child {
color: #333;
border: #ededed 1rpx solid;
}
:last-child {
color: #fff;
background: linear-gradient(90deg, #FF5F32 0%, #F10E31 100%);
}
}
</style>