亲自动手实现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 相关文章推荐
js控制不同的时间段显示不同的css样式的实例代码
Nov 04 Javascript
js中AppendChild与insertBefore的用法详细解析
Dec 16 Javascript
jQuery的$.proxy()应用示例介绍
Apr 03 Javascript
浅谈javascript的Touch事件
Sep 27 Javascript
JS与jQuery实现隔行变色的方法
Sep 09 Javascript
jQuery Layer弹出层传值到父页面的实现代码
Aug 17 jQuery
layerUI下的绑定事件实例代码
Aug 17 Javascript
three.js利用卷积法如何实现物体描边效果
Nov 27 Javascript
jquery将信息遍历到界面上实例代码
Jan 21 jQuery
Vue和React有哪些区别
Sep 12 Javascript
vant 解决tab切换插件标题样式自定义的问题
Nov 13 Javascript
基于javascript实现放大镜特效
Dec 03 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 接口类与抽象类的实际作用
2009/11/26 PHP
Thinkphp搜索时首页分页和搜索页保持条件分页的方法
2014/12/05 PHP
WordPress免插件实现面包屑导航的示例代码
2020/08/20 PHP
从sohu弄下来的flash中展示图片的代码
2007/04/27 Javascript
JS中==与===操作符的比较
2009/03/21 Javascript
clientX,pageX,offsetX,x,layerX,screenX,offsetLeft区别分析
2010/03/12 Javascript
详谈 Jquery Ajax异步处理Json数据.
2011/09/09 Javascript
JQuery的$命名冲突详细解析
2013/12/28 Javascript
jQuery实现向下滑出的平滑下拉菜单效果
2015/08/21 Javascript
js贪吃蛇游戏实现思路和源码
2016/04/14 Javascript
自己动手制作基于jQuery的Web页面加载进度条插件
2016/06/03 Javascript
使用UrlConnection实现后台模拟http请求的简单实例
2017/01/04 Javascript
Vue实现web分页组件详解
2017/11/28 Javascript
解决npm安装Electron缓慢网络超时导致失败的问题
2018/02/06 Javascript
fetch 如何实现请求数据
2018/12/20 Javascript
vue-router源码之history类的浅析
2019/05/21 Javascript
vscode中eslint插件的配置(prettier配置无效)
2019/09/10 Javascript
查找Vue中下标的操作(some和findindex)
2020/08/12 Javascript
在vue中使用cookie记住用户上次选择的实例(本次例子中为下拉框)
2020/09/11 Javascript
ant-design表单处理和常用方法及自定义验证操作
2020/10/27 Javascript
Python中音频处理库pydub的使用教程
2017/06/07 Python
tornado 多进程模式解析
2018/01/15 Python
python程序运行进程、使用时间、剩余时间显示功能的实现代码
2019/07/11 Python
Pytorch保存模型用于测试和用于继续训练的区别详解
2020/01/10 Python
Spring Boot中使用IntelliJ IDEA插件EasyCode一键生成代码详细方法
2020/03/20 Python
python:解析requests返回的response(json格式)说明
2020/04/30 Python
pycharm sciview的图片另存为操作
2020/06/01 Python
Python列表的深复制和浅复制示例详解
2021/02/12 Python
CSS3样式linear-gradient的使用实例
2017/01/16 HTML / CSS
分享29个基于Bootstrap的HTML5响应式网页设计模板
2015/11/19 HTML / CSS
Stella McCartney官网:成衣、包袋、香水、内衣、童装及Adidas系列
2018/12/20 全球购物
美国现代家具购物网站:LexMod
2019/01/09 全球购物
学校社会实践活动总结
2014/07/03 职场文书
2014年教师工作总结
2014/11/10 职场文书
先进工作者个人总结
2015/02/15 职场文书
高一作文之暖冬
2019/11/09 职场文书