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 13 Javascript
jquery 图片轮换效果
Jul 29 Javascript
动态创建样式表在各浏览器中的差异测试代码
Sep 13 Javascript
chrome浏览器不支持onmouseleave事件的解决技巧
May 31 Javascript
jquery ajax 调用失败的原因示例介绍
Sep 27 Javascript
Bootstrap每天必学之前端开发框架
Nov 19 Javascript
微信小程序功能之全屏滚动效果的实现代码
Nov 22 Javascript
Node.js JSON模块用法实例分析
Jan 04 Javascript
vue实现图片上传预览功能
Dec 23 Javascript
Element InputNumber 计数器的实现示例
Aug 03 Javascript
关于JavaScript中异步/等待的用法与理解
Nov 18 Javascript
详解vue3中组件的非兼容变更
Mar 03 Vue.js
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
站长助手-网站web在线管理程序 v1.0 下载
2007/05/12 PHP
PHP编程基本语法快速入门手册
2016/01/07 PHP
php swoft框架实例用法
2020/12/22 PHP
40个新鲜出炉的jQuery 插件和免费教程[上]
2012/07/24 Javascript
用JQuery在网页中实现分隔条功能的代码
2012/08/09 Javascript
用jquery实现的一个超级简单的下拉菜单
2014/05/18 Javascript
jQuery过滤选择器用法分析
2015/02/10 Javascript
JavaScript显示当前文档最后修改日期的方法
2015/03/19 Javascript
jQuery mobile 移动web(6)
2015/12/20 Javascript
jQuery Form 表单提交插件之formSerialize,fieldSerialize,fieldValue,resetForm,clearForm,clearFields的应用
2016/01/23 Javascript
JSON对象 详解及实例代码
2016/10/18 Javascript
nodejs连接mongodb数据库实现增删改查
2016/12/01 NodeJs
Vue之Vue.set动态新增对象属性方法
2018/02/23 Javascript
利用QT写一个极简单的图形化Python闹钟程序
2015/04/07 Python
python+matplotlib绘制3D条形图实例代码
2018/01/17 Python
浅谈Python2、Python3相对路径、绝对路径导入方法
2018/06/22 Python
Python log模块logging记录打印用法解析
2020/01/20 Python
python GUI库图形界面开发之PyQt5开发环境配置与基础使用
2020/02/25 Python
Python爬虫防封ip的一些技巧
2020/08/06 Python
CSS3绘制圆角矩形的简单示例
2015/09/28 HTML / CSS
HTML5引入的新数组TypedArray介绍
2012/12/24 HTML / CSS
html5菜单折纸效果
2014/04/22 HTML / CSS
美国最值得信赖的宠物药房:Allivet
2019/03/23 全球购物
Java的类与C++的类有什么不同
2014/01/18 面试题
房屋改造计划书
2014/01/10 职场文书
毕业生自荐书
2014/02/02 职场文书
餐饮企业总经理岗位职责范文
2014/02/18 职场文书
美容院店长岗位职责
2014/04/08 职场文书
小学秋季运动会报道稿
2014/09/30 职场文书
学校党的群众路线教育实践活动整改措施
2014/10/25 职场文书
新生入学欢迎词
2015/01/26 职场文书
健康教育主题班会
2015/08/14 职场文书
医院岗前培训心得体会
2016/01/08 职场文书
职场干货:简历中的自我评价应该这样写!
2019/05/06 职场文书
Python中os模块的简单使用及重命名操作
2021/04/17 Python
《艾尔登法环》1.03.3补丁上线 碎星伤害调整
2022/04/07 其他游戏