1. 程式人生 > 實用技巧 >用vuejs仿寫一個移動端日曆元件

用vuejs仿寫一個移動端日曆元件

仿寫一個日曆元件,有些粗糙,需要優化的地方歡迎提出!

參考文章:

https://www.jianshu.com/p/67acaaf7d2f7

https://blog.csdn.net/zxb89757/article/details/103579415?ops_request_misc=%7B%22request%5Fid%22%3A%22160359079019195264707225%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=160359079019195264707225&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2

allfirst_rank_v2~rank_v28-13-103579415.pc_first_rank_v2_rank_v28&utm_term=%E5%B0%81%E8%A3%85%E6%97%A5%E5%8E%86%E7%BB%84%E4%BB%B6&spm=1018.2118.3001.4187

https://www.jianshu.com/p/612cd47b966d

功能

  • 不展開時,滑動切換周
  • 展開時,滑動切換月
  • 預設選擇當天
  • 切換月份後將月份傳遞給父元件(也可以是其他資料)

要點

移動端左右滑動

安裝外掛

npm install vue-touch@next --save

在main.js 中 引入:

import VueTouch from 'vue-touch'
Vue.use(VueTouch, {name: 'v-touch'})
VueTouch.config.swipe = {
  threshold: 100 //手指左右滑動距離
}

使用

<v-touch @swipeleft="onSwipeLeft"@swiperight="onSwipeRight" tag="div">
	(你的元件)
</v-touch>

獲取每月的天數

首先列出每月的天數(2月除外)

monthDay:[31,'',31,30,31,30,31,31,30,31,30,31],

接著判斷2月是否為閏月再將天數插入該陣列

this.February=this.isLeapYear(this.year)?29:28//獲取二月份的天數
this.monthDay.splice(1,1,this.February) //插入二月份的天數
isLeapYear(year){
	return year%4==0&&year%100!==0||year%400==0
},

最後傳入月份的索引,迴圈每個月的天數

<p v-for="(item,idx) in (monthDay[this.month-1] || 30)" 
    :key="item.id">
    {{item}}
</p>

補充前後空格

<p v-for="item in headDays" :key="item.id" class="grey">{{item}}</p>
//中間是當月有效天
<p v-for="item in tailDays" :key="item.id" class="grey">{{item}}</p>
getWholeMonth(){
	//獲取某年某月的第一天,由於new Date的月份按索引判斷,所以-1
	let firstDay = new Date(this.year,this.month-1,1) 
    //獲取前方空格數
    if(firstDay.getDay() == 0){
        this.spaceDay = 6
    } else {
        this.spaceDay = firstDay.getDay() - 1
    }
    this.getPrevDays() //補前方空格
    this.getCells()    //補後方空格
},
//補前方空格
getPrevDays(){
	//this.month表示的是月份,
    //如果當前月為一月份,獲取十二月份的天數並傳過去。所以傳索引11
    if(this.month==1){
        this.getHeadDays(this.monthDay[11])
    }else{
        this.getHeadDays(this.monthDay[this.month-2])
    }
},
//補後方空格-中間函式-獲取方格數
getCells(){
	let cells=this.spaceDay+this.monthDay[this.month-1]
    //餘數不能為0(否則就補一行了),cells%7獲取餘數
    //一週有7天,假設餘數為2,那麼後方沒有補的空格就位7-2
    if(7-cells%7!==0){
   		this.getTailDays(7-cells%7)
    }
},
//補後方空格
getTailDays(end){
	this.tailDays=this.supDays.slice(0,end)
},

其中:

leftDays:[31,30,29,28,27,26,25,24,23,22], //用於擷取的陣列 補前方空格
supDays:[1,2,3,4,5,6,7],                  //用於擷取的陣列 補後方空格

切換月與周

切換月,實質上就是改變變數month,讓其動態獲取monthDay中的天數

切換周,實質上就是移動日曆的上下位置,當展開時,位置在第一行;當未展開時,動態改變位置,其中weekRow是變數:

<div class="relative" :class="[visible?'row-1':'row-'+weekRow]">(被包裹的日曆)</div>
.row-1{
    top:0
}
.row-2{
    top:-2.4em
}
.row-3{
    top:-4.8em
}
.row-4{
    top:-7.2em
}
.row-5{
    top:-9.6em
}
.row-6{
    top:-12em
}

以左滑為例

onSwipeLeft(){
    //1.展開的情況下 滑動切換月份
    if(this.visible){
        if(this.month==12){
            this.year++
            this.month=1
        }else{
            this.month++
        }
        this.getWholeMonth()
    }else{
    //2.未展開的情況下 滑動切換周
        this.getWholeMonth()//先獲取當前行
        //當前周小於行數時,切換下一週
        if(this.weekRow<this.rows){
            this.weekRow++
        }else{
        //當前周等於行數時,切換下一個月份,當前周變成第一週。
        //由於要切到第一週,所以不用獲取下個月的行
            if(this.month==12){
                this.year++
                this.month=1
                this.weekRow=1
            }else{
                this.month++
                this.weekRow=1
            }
            this.getWholeMonth()//由於更換了月,所以呼叫該函式補空格
        }
    }   
        
},

父元件監聽子元件資料變化

子元件

updated(){
    this.$emit('monthChange', this.month);
},

父元件

<Calendar ref="calendar"  @monthChange="updateMonth"/>
current:1//月份

mounted(){
	this.current=this.$refs.calendar.month
},
methods:{
    updateMonth(month){
    	this.current=month
    },
},

完整原始碼

子元件

<template>
  <div>
    <div class="calendar" :class="[!visible?'hidden':'']" >
        <div class="flex_sb cellbox">
            <p v-for="item in weekList" :key="item.id" class="week">{{item}}</p>
        </div>

        <v-touch @swipeleft="onSwipeLeft" @swiperight="onSwipeRight" tag="div">
            <div class="flex_sb cellbox border relative"  :class="[visible?'row-1':'row-'+weekRow]">
                <p v-for="item in headDays" :key="item.id" class="grey">{{item}}</p>
                <p v-for="(item,idx) in (monthDay[this.month-1] || 30)" 
                    @click="setDay(idx)" 
                    :class="idx==activeDay?'active':''"
                    class="relative"
                    :key="item.id">
                    {{item}}
                </p>
                <p v-for="item in tailDays" :key="item.id" class="grey">{{item}}</p>
            </div>
    </v-touch>
    </div>
    <div>
        <van-icon name="arrow-down" v-if="!visible" @click="visible=true"/>
        <van-icon name="arrow-up" @click="visible=false" v-else/>
    </div>
  </div>
</template>

<script>
export default {
    data(){
        return{
            year:'',        //年
            month:'',       //月
            day:'',         //日
            weekList:['一','二','三','四','五','六','日'],
            monthDay:[31,'',31,30,31,30,31,31,30,31,30,31],
            February:'',    //判斷2月份的天數
            
            spaceDay: '',   //當月日期前方的空格數
            leftDays:[31,30,29,28,27,26,25,24,23,22], //用於擷取的陣列 補前方空格
            supDays:[1,2,3,4,5,6,7],                  //用於擷取的陣列 補後方空格
            headDays:[],    //上個月月尾日期
            tailDays:[],    //下個月月頭日期

            activeDay: '',  //選中的日期 索引
            visible:false,  //判斷日曆是否展開
            weekRow:2,     //當前周 用於按周切換
            rows:''        //當前月的週數
        }
    },
    created(){
        this.getTheCurrentDate() //獲取當前日期(年月日)
        this.February=this.isLeapYear(this.year)?29:28//獲取二月份的天數
        this.monthDay.splice(1,1,this.February) //插入二月份的天數
        this.getWholeMonth() //獲取完整月份日曆
        this.defaultSet()
    },
    updated(){
        //監聽滑動事件後月份的變化,將月份傳給父元件
        this.$emit('monthChange', this.month);
    },
    methods:{
        //判斷是否為閏年
        isLeapYear(year){
            return year%4==0&&year%100!==0||year%400==0
        },

        //獲取當前日期
        getTheCurrentDate(){
            let current=new Date()
            this.year = current.getFullYear()
            this.month = current.getMonth() + 1
            this.day = current.getDate()
        },

        //預設選擇當前日期
        defaultSet(){
            //展示周時,獲取當日的行數
            if(!this.visible){
               this.weekRow=Math.ceil((this.spaceDay+this.day)/7)
            }
            this.setDay(this.day-1)
        },

        //獲取空格被填充過的完整的月
        getWholeMonth(){
            let firstDay = new Date(this.year,this.month-1,1) //獲取某年某月的第一天,由於new Date的月份按索引判斷,所以-1
            //獲取前方空格數
            if(firstDay.getDay() == 0){
                this.spaceDay = 6
            } else {
                this.spaceDay = firstDay.getDay() - 1
            }
            this.getPrevDays() //補前方空格
            this.getCells()    //補後方空格
        },
        
        //獲取上個月的天數 並呼叫函式補充開頭空格
        getPrevDays(){
            //this.month表示的是月份,
            //如果當前月為一月份,獲取十二月份的天數並傳過去。所以傳索引11
            if(this.month==1){
                this.getHeadDays(this.monthDay[11])
            }else{
                this.getHeadDays(this.monthDay[this.month-2])
            }
        },
        //補開頭空格
        getHeadDays(end){
            if(end==31){
                this.headDays=this.leftDays.slice(0,this.spaceDay).reverse()
            }else if(end==30){
                this.headDays=this.leftDays.slice(1,this.spaceDay+1).reverse()
            }else if(end==29){
                this.headDays=this.leftDays.slice(2,this.spaceDay+2).reverse()
            }else if(end==28){
                this.headDays=this.leftDays.slice(3,this.spaceDay+3).reverse()
            }
        },
        //獲取月份方格數,用於補後方空格 並獲取行/重新獲取行
        getCells(){
            let cells=this.spaceDay+this.monthDay[this.month-1]
            //餘數不能為0(否則就補一行了),cells%7獲取餘數
            //一週有7天,假設餘數為2,那麼後方沒有補的空格就位7-2
            if(7-cells%7!==0){
                this.getTailDays(7-cells%7)
            }
            //向上取整
            this.rows=Math.ceil(cells/7)
        },
        //補後方空格
        getTailDays(end){
            this.tailDays=this.supDays.slice(0,end)
        },

        //選取特定日期
        setDay(idx){
            this.activeDay = idx
            this.day = idx + 1
            console.log('選擇的日期是'+this.year+' '+this.month+' '+this.day)
        },

        //右滑 下一個
        onSwipeLeft(){
            //1.展開的情況下 滑動切換月份
            if(this.visible){
                if(this.month==12){
                    this.year++
                    this.month=1
                }else{
                    this.month++
                }
                this.activeDay = 0
                this.getWholeMonth()
            }else{
            //2.未展開的情況下 滑動切換周
                this.getWholeMonth()//先獲取當前行
                //當前周小於行數時,切換下一週
                if(this.weekRow<this.rows){
                    this.weekRow++
                }else{
                //當前周等於行數時,切換下一個月份,當前周變成第一週。
                //由於要切到第一週,所以不用獲取下個月的行
                    if(this.month==12){
                        this.year++
                        this.month=1
                        this.weekRow=1
                    }else{
                        this.month++
                        this.weekRow=1
                    }
                    this.getWholeMonth()//由於更換了月,所以呼叫該函式補空格
                }
            }   
                
        },
        //左滑 上一個
        onSwipeRight(){
            //1.展開的情況下 滑動切換月份
            if(this.visible){
                if(this.month==1){
                    this.year--
                    this.month=12
                }else{
                    this.month--
                }
                this.activeDay = 0
                this.getWholeMonth()
            }else{
            //2.未展開的情況下 滑動切換周
                //當前周大於1時,切換上一週
                if(this.weekRow>1){
                    this.weekRow--
                }else{
                //當前周等於1時,切換上一個月,並把當前周變成上個月的最後一週
                    if(this.month==1){
                        this.year--
                        //成功切換到上個月
                        this.month=12
                        //呼叫該函式重新獲取行數
                        this.getWholeMonth()
                        this.weekRow=this.rows
                    }else{
                        this.month--
                        this.getWholeMonth()
                        this.weekRow=this.rows
                    }  
                }
            }
            
        },
    }
}
</script>

<style lang="scss" scoped>
.calendar{
    font-size: .8em;
    width: 80%;
    margin: 0 auto;
    height: auto;
    .flex_sb{
        display: flex;
        justify-content:space-between;
    }
    .grey{
        background-color: rgb(247, 244, 244);
    }
    .relative{
        position: relative;
    }
    &.hidden{
        height: 4.8em;
        overflow: hidden;
    }

    .week{
        z-index: 10;
        background: #fff;
    }
    .cellbox{
        flex-wrap: wrap;
        margin: 0;
        p{
            display: inline-block;
            width:14.28%;
            height:2.4em;
            line-height: 2.4em;
            box-sizing: border-box;
            margin: 0;
            &.active{
                color: #eee;
                background-color: #409EFF;
            }
        }
    }
    .border{
        p{
            border: 1px solid #eee;
        }
    }
    .row-1{
        top:0
    }
    .row-2{
        top:-2.4em
    }
    .row-3{
        top:-4.8em
    }
    .row-4{
        top:-7.2em
    }
    .row-5{
        top:-9.6em
    }
    .row-6{
        top:-12em
    }

}


</style>

父元件:

<van-cell :title="monthTitle" />
<Calendar ref="calendar"  @monthChange="updateMonth"/>

<script>
import Calendar from './../../components/calendar'
	export default{
		components:{
			Calendar
		},
		data(){
			return{
				current:8
			}
		},
		mounted(){
			this.current=this.$refs.calendar.month
		},
		methods:{
			//只要子元件資料變化了,就呼叫該函式
			updateMonth(month){
				this.current=month
			},
		},

		computed:{
			monthTitle(){
				return "出勤月報"+"("+this.month.current+"月)"
			}
		}
	}
</script>