之前项目中有用到日历控件,当时由于时间问题,是在网上找到一个demo,然后二次开发的,从那时就想着自己写一个日历控件。这篇文章说明日历数据的处理,去除月份天数判断以及是否闰年判断。
设计(以最常用的按月份的日历)
日历其实大家都很熟悉,一切的设计都是从功能出发,这是根本。日历的功能分为两大块。
- 日历头部:当前年份/月份。
- 日历主体:当前月份的具体的日期信息。
- 日历主体的行数:现在我们看到的日历基本上为6行,因为一个月最多为31天,假设当前月的第一天为上一月最后一周的最后一天。如果是五行数据的话则只显示了29天,这也是为什么显示6行数据的原因。
功能点
- 日历初始渲染日期为当前月份
- 头部的左右滑动,日历数据需要显示对应月份的信息
- 点击日期本身可以进行相关数据操作,并且记录操作内容
- 可以根据调用这设置日历的每周数据以星期*为开始,星期天或者星期一。
首先思考日历的核心问题
如何获取当前日期的年份以及月份
/** * 获取日历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()匹配。
- 0:周日
- 1:周一
- .....
- 5:周五
- 6:周六
所以上面的公式为
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地址
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]