Vue寫一個日曆

基礎回顧

關於 Date APIjavascript

getFulleYear(); // 年
getMonth(); // 月, 0-11
getDate();  // 日,也就是幾號
getDay();   // 星期幾,0-6
new Date(2019,2,10);  // 實際上就是 2019-03-10
new Date(2019,2,0);   // 其實是 2019-02-28, 也就是2月份的最後一天
new Date(2019,2,-1);  // 其實是 2019-02-27, 也就是2月份的倒數次日

var d = new Date();
d.setTime( new Date(2018,1,1).getTime() );   
d.getFullYear();   // 2018 , setTime 容許傳入毫秒數來更改實例對象
複製代碼

咱們須要注意一點,new Date 時,month 和 date 是能夠傳入負數的。css

分析

作成啥樣子呢,如圖: html

怎麼作呢,一步步來分析:vue

①:計算出當前月份有多少天,遍歷顯示出來java

②:點擊上一月,下一月時,改變月份,並從新執行第一步git

③:計算月初和月末分別對應星期幾,並補全月初和月末的空位。如上圖的 xx 位置。github

第1、二步是很好處理的,主要是第三步,有些複雜。app

我不想拼接 dom,因此就採用 vue來開發了,省時省力。dom

開始吧。函數

第一步和第二步

寫好 html 和 css

<div id="app">
    <div class="calendar-wrapper">
        <div class="calendar-toolbar">
            <div class="prev" @click="prevMonth">上個月</div>
            <div class="current">{{ currentDateStr }}</div>
            <div class="next" @click="nextMonth">下個月</div>
        </div>
        <div class="calendar-inner">
            <div class="calendar-item" v-for="(item, index) of calendarList" :key="index">
                {{ item.date }}
            </div>
        </div>
    </div>
</div>
複製代碼

css 我就略過了

編寫 js 邏輯

感受前兩步是比較簡單的,我就直接貼js吧,加了部分註釋,但願不難讀。

new Vue({
    el: '#app',
    data() {
        return {
            current: {},   // 當前時間
            calendarList: [],  // 用於遍歷顯示
            shareDate: new Date() // 享元模式,用來作優化的
        }
    },
    computed: {
        // 顯示當前時間
        currentDateStr() {
            let { year, month } = this.current;
            return `${year}${this.pad(month + 1)}月`;
        }
    },
    mounted() {
        this.init();
    },
    methods: {
        init() {
            // 初始化當前時間
            this.setCurrent();
            this.calendarCreator();
        },
        // 判斷當前月有多少天
        getDaysByMonth(year, month) {
            return new Date(year, month + 1, 0).getDate();
        },
        // 對小於 10 的數字,前面補 0
        pad(str) {
            return str < 10 ? `0${str}` : str;
        },
        // 點擊上一月
        prevMonth() {
            this.current.month--;
            // 由於 month的變化 會超出 0-11 的範圍, 因此須要從新計算
            this.correctCurrent();
            // 生成新日期
            this.calendarCreator();
        },
        // 點擊下一月
        nextMonth() {
            this.current.month++;
            // 由於 month的變化 會超出 0-11 的範圍, 因此須要從新計算
            this.correctCurrent();
            // 生成新日期
            this.calendarCreator();
        },
        // 格式化時間,與主邏輯無關
        stringify(year, month, date) {
            let str = [year, this.pad(month + 1), this.pad(date)].join('-');
            return str;
        },
        // 設置或初始化 current
        setCurrent(d = new Date()) {
            let year = d.getFullYear();
            let month = d.getMonth();
            let date = d.getDate();
            this.current = {
                year,
                month,
                date
            }
        },
        // 修正 current
        correctCurrent() {
            let { year, month, date } = this.current;

            let maxDate = this.getDaysByMonth(year, month);
            // 預防其餘月跳轉到2月,2月最多隻有29天,沒有30-31
            date = Math.min(maxDate, date);

            let instance = new Date(year, month, date);
            this.setCurrent(instance);
        },
        // 生成日期
        calendarCreator() {
            // 一天有多少毫秒
            const oneDayMS = 24 * 60 * 60 * 1000;

            let list = [];
            let { year, month } = this.current;

            // 當前月,第一天和最後一天的毫秒數
            let begin = new Date(year, month, 1).getTime();
            let end = new Date(year, month + 1, 0).getTime();

            while (begin <= end) {
                // 享元模式,避免重複 new Date
                this.shareDate.setTime(begin);
                let year = this.shareDate.getFullYear();
                let curMonth = this.shareDate.getMonth();
                let date = this.shareDate.getDate();
                list.push({
                    year: year,
                    month: curMonth,
                    date: date,
                    value: this.stringify(year, curMonth, date)
                });
                begin += oneDayMS;
            }
                
            this.calendarList = list;
        }
    },
});
複製代碼

第三步 - 顯示星期,補全空位

這一步,咱們要作的有兩點:

①:把星期列表顯示出來,

②:調整 methods 中的 calendarCreator 函數,顯示正確的星期順序。

爲了解決這個問題,咱們須要增長兩個函數,判斷當前月第一天是星期幾,和最後一天是星期幾。這樣才能正確顯示。

咱們注意一下 calendarCreator 函數,裏面有個 beginend 變量,分別表明當前月份第一天和最後一天的毫秒數。聰明的讀者應該已經發現,若是須要補全的話,咱們能夠在begin的基礎上,再往前移動幾天(減去這幾天的毫秒數),在end的基礎上,再日後移動幾天(加上這幾天的毫秒數),就能夠實現這個效果了。具體移動多少,須要咱們經過計算獲得。

咱們先完成 星期一至星期日的顯示

<div class="calendar-week">
    <div class="week-item" v-for="item of weekList" :key="item">{{ item }}</div>
</div>
複製代碼
data() {
    return {
        // 省略部分代碼...
        weekList: ['一', '二', '三', '四', '五', '六', '日'],    // 新增
    }
},
複製代碼

再往 methods 裏增長兩個函數:

// 當前月的第一天是星期幾
getFirstDayByMonths(year, month) {
    return new Date(year, month, 1).getDay();
},
// 當前月的最後一天是星期幾
getLastDayByMonth(year, month) {
    return new Date(year, month + 1, 0).getDay();
},
複製代碼

調整 calendarCreator 函數,主要是調整 beginend 的計算方式:

calendarCreator() {
    // 省略部分代碼...
    
    // 當前月份第一天是星期幾, 0-6
    let firstDay = this.getFirstDayByMonths(year, month);
    // 填充多少天,由於我將星期日放到最後了,因此須要另外調整下 
    let prefixDaysLen = firstDay === 0 ? 6 : firstDay - 1;
    // 向前移動以後的毫秒數
    let begin = new Date(year, month, 1).getTime() - (oneDayMS * prefixDaysLen);
    
    // 當前月份最後一天是星期幾, 0-6
    let lastDay = this.getLastDayByMonth(year, month);
    // 填充多少天,由於我將星期日放到最後了,因此須要另外調整下 
    let suffixDaysLen = lastDay === 0 ? 0 : 7 - lastDay;
    // 向後移動以後的毫秒數
    let end = new Date(year, month + 1, 0).getTime() + (oneDayMS * suffixDaysLen);
     
    // 省略部分代碼...
}
複製代碼

對於不屬於當前月的日期,咱們須要將其置灰,因此須要調整下 calendarCreatorwhile 循環,

calendarList 數據加一個 disable 屬性,而後在 html 那裏記得加樣式處理:

list.push({
    year: year,
    month: curMonth,
    date: date,
    disable: curMonth !== month,  // 新增 disable 屬性
    value: this.stringify(year, curMonth, date)
});
複製代碼
:class="[item.disable ? 'disabled' : '']"
複製代碼

至此,一個簡單的日曆就開發完了。如圖:

寫在最後

css 我就不美化了,懶。。。

一些點擊事件呀,hover效果呀,啥的,看官能夠根據業務需求自行加上。

至於,改變年份,思想也是相似的。

但願能給有須要的讀者帶來幫助。

源碼在此 vue-calendar

參考

MDN

相關文章
相關標籤/搜索