之前項目中有用到日歷控件,當時由于時間問題,是在網上找到一個demo,然后二次開發的,從那時就想著自己寫一個日歷控件。這篇文章說明日歷數據的處理,去除月份天數判斷以及是否閏年判斷。
設計(以最常用的按月份的日歷)
日歷其實大家都很熟悉,一切的設計都是從功能出發,這是根本。日歷的功能分為兩大塊。
功能點
首先思考日歷的核心問題
如何獲取當前日期的年份以及月份
/**
* 獲取日歷header內容 格式為:****年 **月
* @param {*} date
*/
export const getHeaderContent = function (date) {
let _date = new Date(date)
return dateFormat(_date, 'yyyy年 MM月')
}
如何獲取當前月份需要顯示的42條數據(6*7),這42條數據是什么呢?
這個問題的核心是:當前月份顯示的42條數據的第一天是哪一天?
這個問題的解決思路還要從上面的設計說起,上面提到日歷主題的行數時,說到“假設當前月的第一天為上一月最后一周的最后一天”,那么42條數據顯示的內容的第一條數據還要根據當前月的第一天是第一天所在周的第幾天。
舉例:2019-02-01
2月的第一天,星期五,所以當前月日歷的第一天為2019-02-01 - 5
var date = new Date() date.setDate(date.getDate() - date.getDay() + 1) // 獲取當前月的第一天為2019-01-28
這里有一問題是什么呢?
date.getDate()的值為0 - 6(0為周日,如果你的日歷也是將周日放在日歷的第一天,沒什么問題,可是在中國是將周日放在最后一天的),這也就意味著前面的實現還需要考慮日歷的放置順序,因為日歷是按照普通的周一到周日,還是周日到周一,我們獲取的當月日歷的第一天是不同的。所以上面的代碼還要依賴于日歷的排放順序。
這里的排放順序將是日歷組件的第一個可被調用者控制的參數。這里我的設想是將該參數的傳入值與date.getDay()匹配。
所以上面的公式為
date.setDate(date.getDate() - date.getDay() + x)
但是這里的x值加了之后的日期如果大于當前月份的第一天,那就需要將當前得到的日期數值再減去7天,這個原因就不用說明了吧。
/**
* 獲取當前月日歷的第一天
* @param {*} date
*/
export const getFirstDayOfCalendar = function (date, weekLabelIndex) {
let _date = new Date(date)
_date = new Date(_date.setDate(_date.getDate() - _date.getDay() + weekLabelIndex))
// 如果當前日期大于當前月第一天,則需要減去7天
if (_date > date) {
_date = new Date(_date.setDate(_date.getDate() - 7))
}
return _date
}
接下來就好做了,只需要在當前的日期加上加上1,每次得到下一天的日期。
左右切換月份如何設定
上面設計都是以今天為計算初始值,左右切換的初始值如何設計呢?
第一反應是將當前的日期的月份進行加減1,這樣是不行的,因為如果今天是31號,那么碰到下個月只有30的時候,這樣就會碰到點擊下月,直接切換了兩個月。更別說2月這個月份天數不固定的月份。所以這里又是一個問題了。
我的解決思路是:月份點擊切換的時候,初始計算值設計為當前月的第一天。
/**
* 以傳入參數作為基準獲取下個月的第一天日期
* @param {*} firstDayOfCurrentMonth
*/
export const getFirstDayOfNextMonth = function (firstDayOfCurrentMonth) {
return new Date(firstDayOfCurrentMonth.getFullYear(), firstDayOfCurrentMonth.getMonth() + 1, 1)
}
/**
* 以傳入參數作為基準獲取上個月的第一天日期
* @param {*} firstDayOfCurrentMonth
*/
export const getFirstDayOfPrevMonth = function (firstDayOfCurrentMonth) {
return new Date(firstDayOfCurrentMonth.getFullYear(), firstDayOfCurrentMonth.getMonth() - 1, 1)
}
左右切換月份數據傳遞方式(觀察者模式)
因為對于日歷組件本身來說,header和body是屬于同一個父組件的同級組件,數據傳遞可以依賴于父組件進行傳遞,這里我使用的是觀察者模式實現。
引入觀察者模式代碼:
/*
* Subject
* 內部創建了三個方法,內部維護了一個ObserverList。
*/
// contructor function
export const Subject = function () {
this.observers = new ObserverList()
}
// addObserver: 調用內部維護的ObserverList的add方法
Subject.prototype.addObserver = function (observer) {
this.observers.add(observer)
}
// removeObserver: 調用內部維護的ObserverList的removeat方法
Subject.prototype.removeObserver = function (observer) {
this.observers.removeAt(this.observers.indexOf(observer, 0))
}
// notify: 通知函數,用于通知觀察者并且執行update函數,update是一個實現接口的方法,是一個通知的觸發方法。
Subject.prototype.notify = function (context) {
let observerCount = this.observers.count()
for (let i = 0; i < observerCount; i++) {
this.observers.get(i).update(context)
}
}
/*
* ObserverList
* 內部維護了一個數組,4個方法用于數組的操作,這里相關的內容還是屬于subject,因為ObserverList的存在是為了將subject和內部維護的observers分離開來,清晰明了的作用。
*/
function ObserverList () {
this.observerList = []
}
ObserverList.prototype.add = function (obj) {
return this.observerList.push(obj)
}
ObserverList.prototype.count = function () {
return this.observerList.length
}
ObserverList.prototype.get = function (index) {
if (index > -1 && index < this.observerList.length) {
return this.observerList[index]
}
}
ObserverList.prototype.indexOf = function (obj, startIndex) {
let i = startIndex
while (i < this.observerList.length) {
if (this.observerList[i] === obj) {
return i
}
i++
}
return -1
}
ObserverList.prototype.removeAt = function (index) {
this.observerList.splice(index, 1)
}
/*
* The Observer
* 提供更新接口,為想要得到通知消息的主體提供接口。
*/
export const Observer = function () {
this.update = function () {
// ...
}
}
CalendarBody觀察者注冊:

CalendarHeader通知消息

組件設計以及結構
VueCalendar Component CalendarBody.vue CalendarHeader.vue lib Subject.js Util.js index.vue
當前效果
周一為第一天:

周日為第一天

Github地址
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。