vue写一个炫酷的日历组件

栏目: JavaScript · 发布时间: 5年前

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

公司业务新开了一个商家管理微信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页面监听当前日期的变化。及时将日历数据计算渲染出来。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精通HTML

精通HTML

Paul Haine / 杨明军 / 人民邮电出版社 / 2008-2 / 35.00元

本书深入地探讨了(X)HTML及相关技术包括CSS、微格式、语义网等,重点阐述了如何在恰当的时候使用恰当的标签,全书始终贯彻现代的Web设计理念,从而使读者可以学习如何充分利用各种标记提供的多样性,创建语义丰富和结构合理的网站。 本书适合具备初步HTML和CSS知识的Web设计开发人员阅读。一起来看看 《精通HTML》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具