亲自动手实现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 相关文章推荐
jquery的ajax请求全面了解
Mar 20 Javascript
JQuery动态添加和删除表格行的方法
Mar 09 Javascript
简单谈谈javascript Date类型
Sep 06 Javascript
JS中如何实现点击a标签返回页面顶部的问题
Jan 19 Javascript
详解bootstrap的modal-remote两种加载方式【强化】
Jan 27 Javascript
JS打开摄像头并截图上传示例
Feb 18 Javascript
对象不支持indexOf属性或方法的解决方法(必看)
May 28 Javascript
9种使用Chrome Firefox 自带调试工具调试javascript技巧
Dec 22 Javascript
浅谈Vue服务端渲染框架Nuxt的那些事
Dec 21 Javascript
vue2配置scss的方法步骤
Jun 06 Javascript
vue.js实现左边导航切换右边内容
Oct 21 Javascript
分享几个JavaScript运算符的使用技巧
Apr 24 Javascript
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单例模式示例分享
2015/02/12 PHP
PHP 将dataurl转成图片image方法总结
2016/10/14 PHP
Yii2实现中国省市区三级联动实例
2017/02/08 PHP
PHP培训要多少钱
2017/06/06 PHP
PHP使用SOAP调用API操作示例
2018/12/25 PHP
用JQuery 实现AJAX加载XML并解析的脚本
2009/07/25 Javascript
javascript多种数据类型表格排序代码分析
2010/09/11 Javascript
js中indexof的用法详细解析
2013/12/24 Javascript
JS的encodeURI和java的URLDecoder.decode使用介绍
2014/05/08 Javascript
node.js中的http.request.end方法使用说明
2014/12/10 Javascript
推荐一款jQuery插件模板
2015/01/09 Javascript
JavaScript判断表单提交时哪个radio按钮被选中的方法
2015/03/21 Javascript
图解JavaScript中的this关键字
2020/05/28 Javascript
Jquery-data的三种用法
2017/04/18 jQuery
AngularJS实现页面跳转后自动弹出对话框实例代码
2017/08/02 Javascript
浅谈node中的cluster集群
2018/06/02 Javascript
详解Bootstrap 学习(一)入门
2019/04/12 Javascript
javascript随机变色实例代码
2019/10/15 Javascript
async/await让异步操作同步执行的方法详解
2019/11/01 Javascript
[55:18]Liquid vs Chaos 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
Python中关键字nonlocal和global的声明与解析
2017/03/12 Python
python读取文本绘制动态速度曲线
2018/06/21 Python
Python 正则表达式爬虫使用案例解析
2019/09/23 Python
Python 使用 prettytable 库打印表格美化输出功能
2019/12/26 Python
python 安装impala包步骤
2020/03/28 Python
Python接口测试文件上传实例解析
2020/05/22 Python
详解CSS3弹性伸缩盒
2020/09/21 HTML / CSS
利用HTML5中的Canvas绘制一张笑脸的教程
2015/05/07 HTML / CSS
美国派对用品及装饰品网上商店:Shindigz
2016/07/30 全球购物
美国受欢迎的眼影品牌:BH Cosmetics
2016/10/25 全球购物
迪卡侬英国官网:Decathlon英国
2017/04/08 全球购物
学生会竞选自荐信
2013/10/12 职场文书
SQL写法--行行比较
2021/08/23 SQL Server
Python办公自动化解决world文件批量转换
2021/09/15 Python
php将xml转化对象的实例详解
2021/11/17 PHP
三星 3nm 芯片将于第二季度开始量产
2022/04/29 数码科技