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 相关文章推荐
JS.GetAllChild(element,deep,condition)使用介绍
Sep 21 Javascript
jQuery插件实现表格隔行换色且感应鼠标高亮行变色
Sep 22 Javascript
appendChild() 或 insertBefore()使用与区别介绍
Oct 11 Javascript
Javascript前端UI框架Kit使用指南之kitjs的对话框组件
Nov 28 Javascript
JavaScript调用传递变量参数的相关问题及解决办法
Nov 01 Javascript
AngularJS 日期格式化详解
Dec 23 Javascript
基于BootStrap Metronic开发框架经验小结【五】Bootstrap File Input文件上传插件的用法详解
May 12 Javascript
jQuery遍历DOM的父级元素、子级元素和同级元素的方法总结
Jul 07 Javascript
详解vuejs2.0 select 动态绑定下拉框支持多选
Apr 25 Javascript
vue 组件销毁并重置的实现
Jan 13 Javascript
微信小程序实现抖音播放效果的实例代码
Apr 11 Javascript
微信小程序实现可拖动悬浮图标(包括按钮角标的实现)
Dec 29 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通过COM使用ADODB的简单例子
2006/12/31 PHP
PHP 事务处理数据实现代码
2010/05/13 PHP
纯php打造的tab选项卡效果代码(不用js)
2010/12/29 PHP
php实现字符串首字母转换成大写的方法
2015/03/17 PHP
php获取网站根目录物理路径的几种方法(推荐)
2017/03/04 PHP
PHP关于foreach复制知识点总结
2019/01/28 PHP
Extjs4 消息框去掉关闭按钮(类似Ext.Msg.alert)
2013/04/02 Javascript
js自动生成的元素与页面原有元素发生堆叠的解决方法
2014/09/04 Javascript
JQuery限制复选框checkbox可选中个数的方法
2015/04/20 Javascript
jQuery插件jcrop+Fileapi完美实现图片上传+裁剪+预览的代码分享
2015/04/22 Javascript
js中的触发事件对象event.srcElement与event.target详解
2017/03/15 Javascript
js原生方法被覆盖,从新赋值原生的方法
2018/01/02 Javascript
使用webpack构建应用的方法步骤
2019/03/04 Javascript
解决Vue.js应用回退或刷新界面时提示用户保存修改问题
2019/11/24 Javascript
json解析大全 双引号、键值对不在一起的情况
2019/12/06 Javascript
Python使用matplotlib绘制动画的方法
2015/05/20 Python
Python读取Excel的方法实例分析
2015/07/11 Python
Python的Flask框架的简介和安装方法
2015/11/13 Python
关于Python中异常(Exception)的汇总
2017/01/18 Python
使用EduBlock轻松学习Python编程
2018/10/08 Python
python+OpenCV实现图像拼接
2020/03/05 Python
最新pycharm安装教程
2020/11/18 Python
Django自带的用户验证系统实现
2020/12/18 Python
英国最大的汽车交易网站:Auto Trader UK
2016/09/23 全球购物
英国儿童家具专卖店:GLTC
2016/09/24 全球购物
KOHLER科勒美国官网:国际著名卫浴橱柜领先品牌
2020/06/27 全球购物
销售副总经理岗位职责
2013/12/11 职场文书
优秀士兵个人事迹材料
2014/01/19 职场文书
2014年三八妇女节活动方案
2014/02/28 职场文书
大学军训感言300字
2014/03/09 职场文书
社团活动总结格式
2014/08/29 职场文书
暑期社会实践证明书
2014/11/17 职场文书
三峡导游词
2015/01/31 职场文书
会计试用期自我评价
2015/03/10 职场文书
入党自传范文2015
2015/06/26 职场文书
JS数组方法some、every和find的使用详情
2021/10/05 Javascript