親自動手實現(xiàn)vue日歷控件
之前項目中有用到日歷控件,當(dāng)時由于時間問題,是在網(wǎng)上找到一個demo,然后二次開發(fā)的,從那時就想著自己寫一個日歷控件。這篇文章說明日歷數(shù)據(jù)的處理,去除月份天數(shù)判斷以及是否閏年判斷。
設(shè)計(以最常用的按月份的日歷)
日歷其實大家都很熟悉,一切的設(shè)計都是從功能出發(fā),這是根本。日歷的功能分為兩大塊。
- 日歷頭部:當(dāng)前年份/月份。
- 日歷主體:當(dāng)前月份的具體的日期信息。
- 日歷主體的行數(shù):現(xiàn)在我們看到的日歷基本上為6行,因為一個月最多為31天,假設(shè)當(dāng)前月的第一天為上一月最后一周的最后一天。如果是五行數(shù)據(jù)的話則只顯示了29天,這也是為什么顯示6行數(shù)據(jù)的原因。
功能點
- 日歷初始渲染日期為當(dāng)前月份
- 頭部的左右滑動,日歷數(shù)據(jù)需要顯示對應(yīng)月份的信息
- 點擊日期本身可以進(jìn)行相關(guān)數(shù)據(jù)操作,并且記錄操作內(nèi)容
- 可以根據(jù)調(diào)用這設(shè)置日歷的每周數(shù)據(jù)以星期*為開始,星期天或者星期一。
首先思考日歷的核心問題
如何獲取當(dāng)前日期的年份以及月份
/** * 獲取日歷header內(nèi)容 格式為:****年 **月 * @param {*} date */ export const getHeaderContent = function (date) { let _date = new Date(date) return dateFormat(_date, 'yyyy年 MM月') }
如何獲取當(dāng)前月份需要顯示的42條數(shù)據(jù)(6*7),這42條數(shù)據(jù)是什么呢?
這個問題的核心是:當(dāng)前月份顯示的42條數(shù)據(jù)的第一天是哪一天?
這個問題的解決思路還要從上面的設(shè)計說起,上面提到日歷主題的行數(shù)時,說到“假設(shè)當(dāng)前月的第一天為上一月最后一周的最后一天”,那么42條數(shù)據(jù)顯示的內(nèi)容的第一條數(shù)據(jù)還要根據(jù)當(dāng)前月的第一天是第一天所在周的第幾天。
舉例:2019-02-01
2月的第一天,星期五,所以當(dāng)前月日歷的第一天為2019-02-01 - 5
var date = new Date() date.setDate(date.getDate() - date.getDay() + 1) // 獲取當(dāng)前月的第一天為2019-01-28
這里有一問題是什么呢?
date.getDate()的值為0 - 6(0為周日,如果你的日歷也是將周日放在日歷的第一天,沒什么問題,可是在中國是將周日放在最后一天的),這也就意味著前面的實現(xiàn)還需要考慮日歷的放置順序,因為日歷是按照普通的周一到周日,還是周日到周一,我們獲取的當(dāng)月日歷的第一天是不同的。所以上面的代碼還要依賴于日歷的排放順序。
這里的排放順序?qū)⑹侨諝v組件的第一個可被調(diào)用者控制的參數(shù)。這里我的設(shè)想是將該參數(shù)的傳入值與date.getDay()匹配。
- 0:周日
- 1:周一
- .....
- 5:周五
- 6:周六
所以上面的公式為
date.setDate(date.getDate() - date.getDay() + x)
但是這里的x值加了之后的日期如果大于當(dāng)前月份的第一天,那就需要將當(dāng)前得到的日期數(shù)值再減去7天,這個原因就不用說明了吧。
/** * 獲取當(dāng)前月日歷的第一天 * @param {*} date */ export const getFirstDayOfCalendar = function (date, weekLabelIndex) { let _date = new Date(date) _date = new Date(_date.setDate(_date.getDate() - _date.getDay() + weekLabelIndex)) // 如果當(dāng)前日期大于當(dāng)前月第一天,則需要減去7天 if (_date > date) { _date = new Date(_date.setDate(_date.getDate() - 7)) } return _date }
接下來就好做了,只需要在當(dāng)前的日期加上加上1,每次得到下一天的日期。
左右切換月份如何設(shè)定
上面設(shè)計都是以今天為計算初始值,左右切換的初始值如何設(shè)計呢?
第一反應(yīng)是將當(dāng)前的日期的月份進(jìn)行加減1,這樣是不行的,因為如果今天是31號,那么碰到下個月只有30的時候,這樣就會碰到點擊下月,直接切換了兩個月。更別說2月這個月份天數(shù)不固定的月份。所以這里又是一個問題了。
我的解決思路是:月份點擊切換的時候,初始計算值設(shè)計為當(dāng)前月的第一天。
/** * 以傳入?yún)?shù)作為基準(zhǔn)獲取下個月的第一天日期 * @param {*} firstDayOfCurrentMonth */ export const getFirstDayOfNextMonth = function (firstDayOfCurrentMonth) { return new Date(firstDayOfCurrentMonth.getFullYear(), firstDayOfCurrentMonth.getMonth() + 1, 1) } /** * 以傳入?yún)?shù)作為基準(zhǔn)獲取上個月的第一天日期 * @param {*} firstDayOfCurrentMonth */ export const getFirstDayOfPrevMonth = function (firstDayOfCurrentMonth) { return new Date(firstDayOfCurrentMonth.getFullYear(), firstDayOfCurrentMonth.getMonth() - 1, 1) }
左右切換月份數(shù)據(jù)傳遞方式(觀察者模式)
因為對于日歷組件本身來說,header和body是屬于同一個父組件的同級組件,數(shù)據(jù)傳遞可以依賴于父組件進(jìn)行傳遞,這里我使用的是觀察者模式實現(xiàn)。
引入觀察者模式代碼:
/* * Subject * 內(nèi)部創(chuàng)建了三個方法,內(nèi)部維護(hù)了一個ObserverList。 */ // contructor function export const Subject = function () { this.observers = new ObserverList() } // addObserver: 調(diào)用內(nèi)部維護(hù)的ObserverList的add方法 Subject.prototype.addObserver = function (observer) { this.observers.add(observer) } // removeObserver: 調(diào)用內(nèi)部維護(hù)的ObserverList的removeat方法 Subject.prototype.removeObserver = function (observer) { this.observers.removeAt(this.observers.indexOf(observer, 0)) } // notify: 通知函數(shù),用于通知觀察者并且執(zhí)行update函數(shù),update是一個實現(xiàn)接口的方法,是一個通知的觸發(fā)方法。 Subject.prototype.notify = function (context) { let observerCount = this.observers.count() for (let i = 0; i < observerCount; i++) { this.observers.get(i).update(context) } } /* * ObserverList * 內(nèi)部維護(hù)了一個數(shù)組,4個方法用于數(shù)組的操作,這里相關(guān)的內(nèi)容還是屬于subject,因為ObserverList的存在是為了將subject和內(nèi)部維護(hù)的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通知消息
組件設(shè)計以及結(jié)構(gòu)
VueCalendar Component CalendarBody.vue CalendarHeader.vue lib Subject.js Util.js index.vue
當(dāng)前效果
周一為第一天:
周日為第一天
Github地址
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue-tree-chart樹形組件的實現(xiàn)(含鼠標(biāo)右擊事件)
Vue-Tree-Chart,一個Vue.js2組件,本文就詳細(xì)的介紹一下vue-tree-chart樹形組件的實現(xiàn)(含鼠標(biāo)右擊事件),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02詳細(xì)講一講vue3下會造成響應(yīng)式丟失的情況
vue3開發(fā)過程中,綁定的響應(yīng)式數(shù)據(jù)失去了響應(yīng)式,如何解決問題呢,下面這篇文章主要給大家介紹了關(guān)于vue3下會造成響應(yīng)式丟失的情況,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-0615 分鐘掌握vue-next函數(shù)式api(小結(jié))
這篇文章主要介紹了15 分鐘掌握vue-next函數(shù)式api(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10vuex 如何動態(tài)引入 store modules
這篇文章主要介紹了vuex 如何動態(tài)引入 store modules,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03vue項目中data數(shù)據(jù)之間互相訪問的實現(xiàn)
本文主要介紹了vue項目中data數(shù)據(jù)之間互相訪問的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05