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 相关文章推荐
jquery遍历checkbox的注意事项说明
Feb 21 Javascript
Javascript封装DOMContentLoaded事件实例
Jun 12 Javascript
AngularJS数据源的多种获取方式汇总
Feb 02 Javascript
jQuery基础_入门必看知识点
Jul 04 Javascript
Node做中转服务器转发接口
Oct 18 Javascript
Express系列之multer上传的使用
Oct 27 Javascript
vue 运用mock数据的示例代码
Nov 07 Javascript
详解bootstrap导航栏.nav与.navbar区别
Nov 23 Javascript
详解微信小程序网络请求接口封装实例
May 02 Javascript
使用layui日期控件laydate对开始和结束时间进行联动控制的方法
Sep 06 Javascript
VueCli4项目配置反向代理proxy的方法步骤
May 17 Javascript
Vue项目前后端联调(使用proxyTable实现跨域方式)
Jul 18 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中Cannot send session cache limiter 的问题的方法
2007/04/27 PHP
利用php下载xls文件(自己动手写的)
2014/04/18 PHP
Symfony2学习笔记之模板用法详解
2016/03/17 PHP
屏蔽PHP默认设置中的Notice警告的方法
2016/05/20 PHP
php 读取文件夹下所有图片、文件的实例
2018/10/17 PHP
php操作redis命令及代码实例大全
2020/11/19 PHP
jQuery 全选效果实现代码
2009/03/23 Javascript
javascript实现checkbox复选框实例代码
2016/01/10 Javascript
正则表达式(语法篇推荐)
2016/06/24 Javascript
JavaScript中闭包之浅析解读(必看篇)
2016/08/25 Javascript
浅谈js中几种实用的跨域方法原理详解
2016/12/02 Javascript
Bootstrap 填充Json数据的实例代码
2017/01/11 Javascript
原生js实现下拉框功能(支持键盘事件)
2017/01/13 Javascript
微信小程序 弹框和模态框实现代码
2017/03/10 Javascript
node.js调用Chrome浏览器打开链接地址的方法
2017/05/17 Javascript
nodeJS实现简单网页爬虫功能的实例(分享)
2017/06/08 NodeJs
JS实现的简单折叠展开动画效果示例
2018/04/28 Javascript
js纯前端实现腾讯cos文件上传功能的示例代码
2019/05/14 Javascript
js之切换全屏和退出全屏实现代码实例
2019/09/09 Javascript
js实现车辆管理系统
2020/08/26 Javascript
Win7上搭建Cocos2d-x 3.1.1开发环境
2014/07/03 Python
Python如何实现守护进程的方法示例
2017/02/08 Python
Python爬虫之xlml解析库(全面了解)
2017/08/08 Python
Python+tkinter模拟“记住我”自动登录实例代码
2018/01/16 Python
深入浅析python 中的匿名函数
2018/05/21 Python
python3+selenium实现126邮箱登陆并发送邮件功能
2019/01/23 Python
Pyinstaller 打包发布经验总结
2020/06/02 Python
python集合能干吗
2020/07/19 Python
Html5 实现微信分享及自定义内容的流程
2019/08/20 HTML / CSS
HTML5移动端手机网站开发流程
2016/04/25 HTML / CSS
YSL圣罗兰美妆英国官网:Yves Saint Laurent Beauty UK
2019/08/03 全球购物
C有"按引用传递"吗
2016/09/06 面试题
经理秘书求职自荐信范文
2014/03/23 职场文书
员工安全承诺书
2014/05/22 职场文书
个人先进事迹材料
2014/12/29 职场文书
2015年学校教育教学工作总结
2015/04/22 职场文书