vue实现一个炫酷的日历组件


Posted in Javascript onOctober 08, 2018

公司业务新开了一个商家管理微信H5移动端项目,日历控件是商家管理员查看通过日程来筛选获取某日用户的订单等数据。 如图: 假设今天为2018-09-02

vue实现一个炫酷的日历组件

90天前:

vue实现一个炫酷的日历组件

90天后;

vue实现一个炫酷的日历组件

产品需求:

  • 展示当前日期(服务器时间)前后90天,一共181天的日期。
  • 日历可以左右滑动切换月份。
  • 当月份的如果不在181天区间的,需要置灰并且不可点击。
  • 点击日历绑定的节点的外部,关闭弹窗。

涉及内容:

  1. 获取服务器时间,渲染日历数据
  2. vue-touch监听手势滑动事件
  3. ios日期兼容处理
  4. clickOutSide自定义指令
  5. mock模拟数据

开发:

参考了 基于Vue开发一个日历组件 - 掘金 日历的年月日计算方式。 核心思想:假设当前月份是二月份,根据二月和三月的1号是星期几,来对二月进行布局。(如果需要在二月显示一月和三月的日期,还需要知道一月份有多少天)

在项目开发中,为了与后台同事并行开发。项目采用来mock模拟数据来拦截接口。

日历展盘

// calendar.vue
<template>
 <div class="cp-calendar">
 <v-touch
 @swipeleft="handleNextMonth"
 @swiperight="handlePreMonth"
 class="calendar">
 
 <div class="calendar-main" >
 <span class="item-con header-item"
  v-for="(item, index) in calendarHeader"
  :key="index">{{item}}</span>

 <div :class="`item-con ${todayStyle(item.content) && 'item-con-today'} ${item.type === 'disabled' && 'disableStyle'}`"
  :style="{opacity: isChangeMonth ? 0 : 1}"
  @click.stop="handleDayClick(item)"
  v-for="(item, index) in getMonthDays(selectedYear, selectedMonth)"
  :key="item.type + item.content + `${index}`">
  <span
  :class="`main-content ${selectedDateStyle(item.content) && 'selectedColor'}`">
  {{setContent(item.content)}}</span>
  <span :class="`${selectedDateStyle(item.content) && 'item-con-point'}`" ></span>
 </div>
 </div>
 
 </v-touch>
 </div>
</template>

初始化数据 针对服务器时间进行初始数据处理

// calendar.vue
// 设置初始数据
 initData () {
 this.today = this.currentDate || getDateStr(0) // 如果没有服务器时间,拿本地时间
 this.prevDate = getDateStr(-90, this.currentDate)
 this.nextDate = getDateStr(90, this.currentDate)
 // 是否有手动选中的日期
 let selectedFullDate = this.storeSelectedFullDate
 if (!this.storeSelectedFullDate) {
  selectedFullDate = this.currentDate || getDateStr(0) // 如果没有服务器时间,拿本地时间
 }
 this.selectedYear = Number(selectedFullDate.split('-')[0])
 this.selectedMonth = Number(selectedFullDate.split('-')[1]) - 1
 this.selectedDate = Number(selectedFullDate.split('-')[2])
 this.selectedFullDate = `${this.selectedYear}-${this.selectedMonth + 1}-${this.selectedDate}`
 },
 / 渲染日期
 getMonthDays(year, month) {
 // 定义每个月的天数,如果是闰年第二月改为29天
 let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

 if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
  daysInMonth[1] = 29;
 }
 // 当月第一天为周几
 let targetDay = new Date(year, month, 1).getDay();
 let calendarDateList = [];
 let preNum = targetDay;
 let nextNum = 0;
 if (targetDay > 0) {
  // 当前月份1号前的自然周剩余日期,置空
  for (let i = 0; i < preNum; i++) {
  let obj = {
  type: 'pre',
  content: ''
  };
  calendarDateList.push(obj);
  }
 }
 // 判断当前年月份
 let formatMonth = month + 1 >= 10 ? month + 1 : '0' + (month + 1)
 this.prevYearMonthBoolean = (`${year}-${formatMonth}` === this.prevYearMonth)
 this.nextYearMonthBoolean = (`${year}-${formatMonth}` === this.nextYearMonth)
 for (let i = 0; i < daysInMonth[month]; i++) {
  // 正常显示的日期
  let obj = {
  type: 'normal',
  content: i + 1
  };
  // 判断是否为最往前或者最往后的月份,筛选出不可点击的日期
  if (this.prevYearMonthBoolean) {
  let prevDay = this.prevDate.split('-')[2]
  if (i + 1 < prevDay) {
  obj.type = 'disabled'
  }
  } else if (this.nextYearMonthBoolean) {
  let nextDay = this.nextDate.split('-')[2]
  if (i + 1 > nextDay) {
  obj.type = 'disabled'
  }
  }
  calendarDateList.push(obj);
 }

 nextNum = 6 - new Date(year, month + 1, 0).getDay()

 // 当前月份最后一天的自然周剩余日期,置空
 for (let i = 0; i < nextNum; i++) {
  let obj = {
  type: 'next',
  content: ''
  };
  calendarDateList.push(obj);
 }
 return calendarDateList;
 },
 // 设置日期
 setContent (content) {
 if (!content) return ''
 return `${this.selectedYear}-${this.tf(this.selectedMonth + 1)}-${this.tf(content)}` === this.today ? '今天' : content
 },
 // '今天'样式开关
 todayStyle (content) {
 if (!content) return false
 // Toast(`${this.selectedYear}-${this.tf(this.selectedMonth + 1)}-${this.tf(content)}`)
 return `${this.selectedYear}-${this.tf(this.selectedMonth + 1)}-${this.tf(content)}` === this.today
 },
 // 当前选中的日期样式开关
 selectedDateStyle (content) {
 if (!content) return false
 return `${this.selectedYear}-${this.selectedMonth + 1}-${content}` === this.selectedFullDate
 },
// src/config/utils.js
// 公共方法
/**
 * @param AddDayCount 必传 今天前后N天的日期
 * @param dateStr: 非必传 获取传入日期前后N天的日期:'2018-01-20'
 * @param type 非必传 'lhRili'类型格式如'2018-7-3'
 * @return 返回日期'2018/01/20'
 */
export const getDateStr = (AddDayCount, dateStr, type) => {
 // console.log('getDateStr', AddDayCount, dateStr, type)
 var dd
 if (!dateStr) {
 dd = new Date()
 } else {
 // 判断是否为IOS
 const isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);

 let formatDateStr = isIOS ? dateStr.replace(/-/g, '/') : dateStr
 dd = new Date((formatDateStr.length < 12) ? formatDateStr + ' 00:00:00' : formatDateStr)
 }
 dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期

 let y = dd.getFullYear()
 let m
 let d
 if (type === 'lhRili') {
 m = dd.getMonth() + 1
 d = dd.getDate()
 } else {
 let currentMon = (dd.getMonth() + 1)
 let getDate = dd.getDate()
 m = currentMon < 10 ? '0' + currentMon : currentMon // 获取当前月份的日期,不足10补0
 d = getDate < 10 ? '0' + getDate : getDate // 获取当前几号,不足10补0
 }

 let time = y + '-' + m + '-' + d
 return time
}

左右触摸滑动事件 判断是否月份还可以继续滑动

// calendar.vue
// 上一个月
 handlePreMonth() {
 if (this.prevYearMonthBoolean) {
  return
 }
 if (this.selectedMonth === 0) {
  this.selectedYear = this.selectedYear - 1
  this.selectedMonth = 11
  this.selectedDate = 1
 } else {
  this.selectedMonth = this.selectedMonth - 1
  this.selectedDate = 1
 }
 },
 // 下一个月
 handleNextMonth() {
 if (this.nextYearMonthBoolean) {
  return
 }
 if (this.selectedMonth === 11) {
  this.selectedYear = this.selectedYear + 1
  this.selectedMonth = 0
  this.selectedDate = 1
 } else {
  this.selectedMonth = this.selectedMonth + 1
  this.selectedDate = 1
 }
 },

vuex存储数据

// src/store/schedule.js
const schedule = {
 state: {
 selectedDate: '', // 手动点击选中的日期
 currentDate: '' // 服务器当前日期
 },

 getters: {
 getSelectedDate: state => state.selectedDate,
 getCurrentDate: state => state.currentDate
 },

 mutations: {
 SET_SELECTED_DATE: (state, data) => {
 state.selectedDate = data
 },
 SET_CURRENT_DATE: (state, data) => {
 state.currentDate = data
 }
 },

 actions: {
 setSelectedDate: ({ commit }, data) => commit('SET_SELECTED_DATE', data),
 setCurrentDate: ({ commit }, data) => commit('SET_CURRENT_DATE', data)
 }
};

export default schedule;

clickOutSide指令 指令方法监听

// src/directive/click-out-side.js
export default{
 bind (el, binding, vnode) {
 function documentHandler (e) {
 if (el.contains(e.target)) {
 return false;
 }
 if (binding.expression) {
 binding.value(e);
 }
 }
 el.__vueClickOutside__ = documentHandler;
 document.addEventListener('click', documentHandler);
 },
 unbind (el, binding) {
 document.removeEventListener('click', el.__vueClickOutside__);
 delete el.__vueClickOutside__;
 }
}

注册指令

// src/directive/index.js
import clickOutSide from './click-out-side'

const install = function (Vue) {
 Vue.directive('click-outside', clickOutSide)
}

if (window.Vue) {
 window.clickOutSide = clickOutSide
 Vue.use(install); // eslint-disable-line
}

clickOutSide.install = install
export default clickOutSide
// src/main.js
import clickOutSide from '@/directive/click-out-side/index'

Vue.use(clickOutSide)

使用方式:当某节点外部需要触发事件时,挂载到该节点上

// calendar.vue
<div class="cp-calendar" v-click-outside="spaceClick">
....
</div>

这里需要使用 fastclick 库来消除解决移动端点击事件300ms延时

// src/mian.js
import FastClick from 'fastclick' // 在移动端,手指点击一个元素,会经过:touchstart --> touchmove -> touchend --> click。

FastClick.attach(document.body);

mock数据

// src/mock/index.js
// mock数据入口
import Mock from 'mockjs'
import currentTime from './currentTime'

// 拦截接口请求
Mock.mock(/\/schedule\/getCurrentTime/, 'get', currentTime)

export default Mock
// src/mock/currentTime.js
import Mock from 'mockjs'

export default {
 getList: () => {
 return {
 'status': 'true',
 'code': '200',
 'msg': null,
 'info': {
 'currentDate': '2018-09-02'
 }
 }
 }
}
// src/main.js
// 开发环境引入mock
if (process.env.NODE_ENV === 'development') {
 require('./mock') // 需要在这里引入mock数据才可以全局拦截请求
}

坑点

  • 在微信内置浏览器中,ios的日期格式跟安卓的日期格式分别是:YY/MM/DD和YY-MM-DD。这里需要对微信内置浏览器User Agent进行判断。
  • 获取服务器时间的异步问题,把获取到的服务器时间保存在vuex里面,在calendar.vue页面监听当前日期的变化。及时将日历数据计算渲染出来。

推荐:

感兴趣的朋友可以关注小编的微信公众号【码农那点事儿】,更多网页制作特效源码及学习干货哦!!!

总结

以上所述是小编给大家介绍的vue实现一个炫酷的日历组件,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
javascript 单选框,多选框美化代码
Aug 01 Javascript
jQuery学习笔记 获取jQuery对象
Sep 19 Javascript
Javascript 绘制 sin 曲线过程附图
Aug 21 Javascript
JavaScript阻止事件冒泡示例分享
Dec 28 Javascript
jQuery动态星级评分效果实现方法
Aug 06 Javascript
基于jquery实现鼠标左右拖动滑块滑动附源码下载
Dec 23 Javascript
JavaScript数据操作_浅谈原始值和引用值的操作本质
Aug 23 Javascript
jQuery中Find选择器用法示例
Sep 21 Javascript
js实现PC端和移动端刮卡效果
Mar 27 Javascript
React简单介绍
May 24 Javascript
使用vue-cli创建项目的图文教程(新手入门篇)
May 02 Javascript
echarts实现词云自定义形状的示例代码
Feb 20 Javascript
angularJs利用$scope处理升降序的方法
Oct 08 #Javascript
Nuxt升级2.0.0时出现的问题(小结)
Oct 08 #Javascript
vue页面切换过渡transition效果
Oct 08 #Javascript
angularJs自定义过滤器实现手机号信息隐藏的方法
Oct 08 #Javascript
angular中子控制器向父控制器传值的实例
Oct 08 #Javascript
对angularjs框架下controller间的传值方法详解
Oct 08 #Javascript
对angularJs中controller控制器scope父子集作用域的实例讲解
Oct 08 #Javascript
You might like
PHP反转字符串函数strrev()函数的用法
2012/02/04 PHP
使用phpstorm和xdebug实现远程调试的方法
2015/12/29 PHP
laravel 解决paginate查询多个字段报错的问题
2019/10/22 PHP
基于Jquery+Ajax+Json实现分页显示附效果图
2014/07/30 Javascript
JavaScript设计模式之代理模式介绍
2014/12/28 Javascript
jquery获取easyui日期控件的值实现方法
2016/11/09 Javascript
使用UrlConnection实现后台模拟http请求的简单实例
2017/01/04 Javascript
js模拟支付宝密码输入框
2017/04/11 Javascript
angularjs中ng-bind-html的用法总结
2017/05/23 Javascript
Node.js中多进程模块Cluster的介绍与使用
2017/05/27 Javascript
使用JS实现气泡跟随鼠标移动的动画效果
2017/09/16 Javascript
利用SpringMVC过滤器解决vue跨域请求的问题
2018/02/10 Javascript
ES6学习笔记之map、set与数组、对象的对比
2018/03/01 Javascript
Vue2.0结合webuploader实现文件分片上传功能
2018/03/09 Javascript
JavaScript中的事件与异常捕获详析
2019/02/24 Javascript
vue实现侧边栏导航效果
2019/10/21 Javascript
[01:16]DOTA2小知识课堂 Ep.03 芒果树无伤肉山
2019/12/05 DOTA
python使用multiprocessing模块实现带回调函数的异步调用方法
2015/04/18 Python
Python中使用strip()方法删除字符串中空格的教程
2015/05/20 Python
浅谈DataFrame和SparkSql取值误区
2018/06/09 Python
对python dataframe逻辑取值的方法详解
2019/01/30 Python
python调试神器PySnooper的使用
2019/07/03 Python
python实现KNN分类算法
2019/10/16 Python
基于Python 中函数的 收集参数 机制
2019/12/21 Python
python实现密码强度校验
2020/03/18 Python
Django 解决model 反向引用中的related_name问题
2020/05/19 Python
通过实例了解Python异常处理机制底层实现
2020/07/23 Python
Python ellipsis 的用法详解
2020/11/20 Python
使用CSS变量实现炫酷惊人的悬浮效果
2019/04/26 HTML / CSS
科颜氏法国官网:Kiehl’s法国
2019/08/20 全球购物
七一表彰活动方案
2014/01/18 职场文书
缓刑人员思想汇报500字
2014/09/12 职场文书
2015年高校辅导员工作总结
2015/04/20 职场文书
2015年新农村建设指导员工作总结
2015/07/24 职场文书
八年级作文之我的母亲
2019/12/10 职场文书
oracle删除超过N天数据脚本的方法
2022/02/28 Oracle