亲自动手实现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实现的图片马赛克后显示并切换加文字功能
Apr 21 Javascript
仿中关村在线首页弹出式广告插件(jQuery版)
May 03 Javascript
js判断选择的时间是否大于今天的代码
Aug 20 Javascript
自己用jQuery写了一个图片的马赛克消失效果
May 04 Javascript
js图片滚动效果时间可随意设定当鼠标移上去时停止
Jun 26 Javascript
使用JS实现图片展示瀑布流效果的实例代码
Sep 12 Javascript
Bootstrap时间选择器datetimepicker和daterangepicker使用实例解析
Sep 17 Javascript
Vue.js中用webpack合并打包多个组件并实现按需加载
Feb 17 Javascript
vue-router之nuxt动态路由设置的两种方法小结
Sep 26 Javascript
详解JS判断页面是在手机端还是在PC端打开的方法
Apr 26 Javascript
vue列表单项展开收缩功能之this.$refs的详解
May 05 Javascript
Vue项目实现简单的权限控制管理功能
Jul 17 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
FleaPHP框架数据库查询条件($conditions)写法总结
2016/03/19 PHP
PHP的自定义模板引擎
2017/03/24 PHP
PHP树形结构tree类用法示例
2019/02/01 PHP
PHP设计模式(五)适配器模式Adapter实例详解【结构型】
2020/05/02 PHP
Laravel Reponse响应客户端示例详解
2020/09/03 PHP
ASP.NET jQuery 实例5 (显示CheckBoxList成员选中的内容)
2012/01/13 Javascript
地址栏传递中文参数乱码在js里用escape转码
2013/08/28 Javascript
js综合应用实例简单的表格统计
2013/09/03 Javascript
jQuery通过点击行来删除HTML表格行的实现示例
2014/09/10 Javascript
JavaScript获取页面中第一个锚定文本的方法
2015/04/03 Javascript
浅谈使用MVC模式进行JavaScript程序开发
2015/11/10 Javascript
微信小程序 location API实例详解
2016/10/02 Javascript
BootStrap中jQuery插件Carousel实现轮播广告效果
2017/03/27 jQuery
Angularjs cookie 操作实例详解
2017/09/27 Javascript
JS实现520 表白简单代码
2018/05/21 Javascript
浅谈从React渲染流程分析Diff算法
2018/09/08 Javascript
手把手教你如何使用nodejs编写cli命令行
2018/11/05 NodeJs
js实现图片局部放大效果详解
2019/03/18 Javascript
JS函数进阶之prototy用法实例分析
2020/01/15 Javascript
公众号SVG动画交互实战代码
2020/05/31 Javascript
编写Python脚本使得web页面上的代码高亮显示
2015/04/24 Python
Python中Class类用法实例分析
2015/11/12 Python
Python实现合并同一个文件夹下所有PDF文件的方法示例
2018/04/28 Python
解决Shell执行python文件,传参空格引起的问题
2018/10/30 Python
Python单元测试工具doctest和unittest使用解析
2019/09/02 Python
opencv-python 提取sift特征并匹配的实例
2019/12/09 Python
python 项目目录结构设置
2020/02/14 Python
python excel多行合并的方法
2020/12/09 Python
秋季校运动会广播稿
2014/02/23 职场文书
大学生新学期计划书
2014/04/28 职场文书
自动化专业毕业生求职信
2014/06/18 职场文书
关于读书的演讲稿1000字
2014/08/27 职场文书
2014年向国旗敬礼活动总结
2014/09/27 职场文书
2015年家长学校工作总结
2015/04/22 职场文书
导游经典开场白——导游词
2019/04/17 职场文书
SSM项目使用拦截器实现登录验证功能
2022/01/22 Java/Android