亲自动手实现vue日历控件


Posted in Javascript onJune 26, 2019

之前项目中有用到日历控件,当时由于时间问题,是在网上找到一个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观察者注册:

亲自动手实现vue日历控件

CalendarHeader通知消息

亲自动手实现vue日历控件

组件设计以及结构

VueCalendar
 
 Component
 
 CalendarBody.vue
 CalendarHeader.vue
 
 lib
 
 Subject.js 
 Util.js
 index.vue

当前效果

周一为第一天:

亲自动手实现vue日历控件

周日为第一天

亲自动手实现vue日历控件

Github地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用JavaScript调用WebService的示例
Apr 07 Javascript
actionscript与javascript的区别
May 25 Javascript
getComputedStyle与currentStyle获取样式(style/class)
Mar 19 Javascript
javascript限制文本框输入值类型的方法
May 07 Javascript
JS+CSS实现美化的下拉列表框效果
Aug 11 Javascript
jQuery实现的简单百分比进度条效果示例
Aug 01 Javascript
xtemplate node.js 的使用方法实例解析
Aug 22 Javascript
详解VUE单页应用骨架屏方案
Jan 17 Javascript
vue 进阶之实现父子组件间的传值
Apr 26 Javascript
详解Vue中CSS样式穿透问题
Sep 12 Javascript
node.js中path路径模块的使用方法实例分析
Feb 13 Javascript
vue实现Toast组件轻提示
Apr 10 Vue.js
js设置鼠标悬停改变背景色实现详解
Jun 26 #Javascript
ES6 Generator函数的应用实例分析
Jun 26 #Javascript
vue实现简单的日历效果
Sep 24 #Javascript
vue实现记事本功能
Jun 26 #Javascript
ES6 Set结构的应用实例分析
Jun 26 #Javascript
vue.js实现备忘录demo
Jun 26 #Javascript
ES6 Map结构的应用实例分析
Jun 26 #Javascript
You might like
让你成为更出色的PHP开发者的10个技巧
2011/02/25 PHP
在WordPress的文章编辑器中设置默认内容的方法
2015/12/29 PHP
php对xml文件的增删改查操作实现方法分析
2017/05/19 PHP
VSCode+PHPstudy配置PHP开发环境的步骤详解
2020/08/20 PHP
解密效果
2006/06/23 Javascript
Prototype1.5 rc2版指南最后一篇之Position
2007/01/10 Javascript
IE8 chrome中table隔行换色解决办法
2010/07/09 Javascript
基于javascript如何传递特殊字符
2015/11/30 Javascript
jQuery中 $ 符号的冲突问题及解决方案
2016/11/04 Javascript
js中常用的Math方法总结
2017/01/12 Javascript
webpack配置文件和常用配置项介绍
2017/04/28 Javascript
vue+axios新手实践实现登陆的示例代码
2018/06/06 Javascript
浅谈angularJS2中的界面跳转方法
2018/08/31 Javascript
Python MySQLdb Linux下安装笔记
2015/05/09 Python
python通过opencv实现批量剪切图片
2017/11/13 Python
Python自然语言处理之词干,词形与最大匹配算法代码详解
2017/11/16 Python
深入浅析Python中的yield关键字
2018/01/24 Python
对django views中 request, response的常用操作详解
2019/07/17 Python
详解Python绘图Turtle库
2019/10/12 Python
Python基于pip实现离线打包过程详解
2020/05/15 Python
python 常用日期处理-- datetime 模块的使用
2020/09/02 Python
CSS+jQuery实现的在线答题功能
2015/04/25 HTML / CSS
CSS3实现翘边的阴影效果的代码示例
2016/06/13 HTML / CSS
美国护肤咨询及美容产品电商:Askderm
2017/02/24 全球购物
世界上第一个水枕头:Mediflow
2018/12/06 全球购物
Notino法国:购买香水和化妆品
2019/04/15 全球购物
屈臣氏泰国官网:Watsons TH
2021/02/23 全球购物
教师旷工检讨书
2014/01/18 职场文书
《小草和大树》教学反思
2014/02/16 职场文书
计算机网络专业自荐信
2014/07/04 职场文书
毕业生自荐信范文
2015/03/05 职场文书
幼儿园开学通知
2015/04/24 职场文书
七一活动主持词
2015/06/29 职场文书
USB TYPE-C 或将成为所有智能手机充电标准
2022/04/21 数码科技
MySQL如何修改字段类型和字段长度
2022/06/10 MySQL
SpringCloud超详细讲解Feign声明式服务调用
2022/06/21 Java/Android