uniapp/vue 日历选择器

嗯~最近做项目的时候,遇到个功能需要使用日历选择器的,有在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)&&reg.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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容