Project:

The company’s business has opened a new business management wechat H5 mobile terminal project. The calendar control is used by the business administrator to view and screen the orders and other data of users on a certain day through the schedule. Figure: Suppose today is September 02, 2018

90 days ago:

After 90 days.

Product requirements:

  • Displays a total of 181 days 90 days before and after the current date (server time).
  • The calendar can be swiped left or right to switch months.
  • If the current month is not within the 181-day range, it needs to be grayed and cannot be clicked.
  • Click outside of the calendar bound node to close the popover.

Contents involved:

  1. Get server time, render calendar data
  2. Vue-touch listens for gesture sliding events
  3. Ios date compatibility processing
  4. ClickOutSide custom directive
  5. Mock data

Development:

Reference based on Vue development of a calendar component – mining calendar year month day calculation. Key idea: Suppose the current month is February. Arrange February according to the day of the week of February and the first day of March. (If you want to display January and March dates in February, you also need to know how many days there are in January.)

In project development, in order to develop in parallel with backend colleagues. The project uses mock data to intercept the interface.

  • Calendar show disk
// 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>
Copy the code
  • Initialization Data Initial data processing is performed for the server time
// calendar.vue // Set the initial datainitData() {this. Today = this. CurrentDate | | getDateStr (0) / / if there is no server time, PrevDate = getDateStr(-90, this.currentDate) this.nextDate = getDateStr(90, this.currentDate) // Check whether there is a manually selected datelet selectedFullDate = this.storeSelectedFullDate
        if(! Enclosing storeSelectedFullDate) {selectedFullDate = this. CurrentDate | | getDateStr (0) / / if there is no server time, } this.selectedYear = Number(selectedfulldate.split (The '-')[0])
        this.selectedMonth = Number(selectedFullDate.split(The '-')[1]) - 1
        this.selectedDate = Number(selectedFullDate.split(The '-')[2])
        this.selectedFullDate = `${this.selectedYear}-${this.selectedMonth + 1}-${this.selectedDate}}, / render date getMonthDays(year, month) {// Define the number of days in each month, or 29 in the second month of a leap yearlet 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; } // The first day of the month is the day of the weeklet targetDay = new Date(year, month, 1).getDay();
        let calendarDateList = [];
        let preNum = targetDay;
        let nextNum = 0;
        if(targetDay > 0) {// Remaining days of the nature week before the first of the current month, nullfor (let i = 0; i < preNum; i++) {
            let obj = {
              type: 'pre',
              content: ' '}; calendarDateList.push(obj); } // Determine the current year and monthlet formatMonth = month + 1 >= 10 ? month + 1 : '0' + (month + 1)
        this.prevYearMonthBoolean = (`${year}-${formatMonth}` === this.prevYearMonth)
        this.nextYearMonthBoolean = (`${year}-${formatMonth}` === this.nextYearMonth)
        for (leti = 0; i < daysInMonth[month]; I++) {// the date normally displayedlet obj = {
            type: 'normal', content: i + 1 }; // Check whether it is the most recent month or the most recent month, filter out the unclickable dateif (this.prevYearMonthBoolean) {
            let prevDay  = this.prevDate.split(The '-') [2]if (i + 1 < prevDay) {
              obj.type = 'disabled'}}else if (this.nextYearMonthBoolean) {
            let nextDay  = this.nextDate.split(The '-') [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);
        }
        returncalendarDateList; }, // Set the datesetContent (content) {
        if(! content)return ' '
        return `${this.selectedYear}-${this.tf(this.selectedMonth + 1)}-${this.tf(content)}` === this.today ? 'today' : content
      },
      // 'today'Style switch 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}, // the currently selectedDateStyle switch selectedDateStyle (content) {if(! content)return false
        return `${this.selectedYear}-${this.selectedMonth + 1}-${content}` === this.selectedFullDate
      },
Copy the code
// SRC /config/utils. Js // public method /** * @param AddDayCount must pass N days before and after today * @param dateStr: optional get N days before and after the date passed:'2018-01-20'
 * @param  typeWill not pass'lhRili'The type format is as follows'2018-7-3'
 * @returnReturn date'2018/01/20'* /export const getDateStr = (AddDayCount, dateStr, type) => {
  // console.log('getDateStr', AddDayCount, dateStr, type)
  var dd
  if(! dateStr) { dd = new Date() }else{// check whether 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) // Get the date after AddDayCountlet 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 = getDate < 10 d = getDate < 10?'0'+ getDate: getDate // get the current number, less than 10 complement 0}let time = y + The '-' + m + The '-' + d
  return time
}
Copy the code
  • The left and right touch slide event determines whether the month can continue to slide
// calendar.vue // last monthhandlePreMonth() {
        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}}, // next monthhandleNextMonth() {
        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
        }
      },
Copy the code
  • Vuex stores data
// src/store/schedule.js
const schedule = {
  state: {
    selectedDate: ' ', // Manually click on the selected date 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;
Copy the code
  • ClickOutSide directive Instructs the method to listen
// 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__; }}Copy the code

Registration instructions

// 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

Copy the code
// src/main.js
import clickOutSide from '@/directive/click-out-side/index'

Vue.use(clickOutSide)
Copy the code

Usage: Mount to a node when events need to be triggered

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

Here you need to use the FastClick library to eliminate the 300ms delay for mobile click events

// src/mian.js
import FastClick from 'fastclick'// On the mobile side, a finger clicking on an element will pass: TouchStart --> TouchMove --> TouchEnd --> click. FastClick.attach(document.body);Copy the code
  • The mock data
// SRC /mock/index.js // mock data entry import mock from'mockjs'
import currentTime from './currentTime'Mock (/\/schedule\/getCurrentTime/,'get', currentTime)

export default Mock

Copy the code
// src/mock/currentTime.js
import Mock from 'mockjs'

export default {
  getList: () => {
    return {
      'status': 'true'.'code': '200'.'msg': null,
      'info': {
        'currentDate': '2018-09-02'}}}}Copy the code
// SRC /main.js // The development environment introduces mockif (process.env.NODE_ENV === 'development') {
  require('./mock'// Mock data needs to be introduced here to intercept requests globally}Copy the code

Pit point

  • In the Built-in browser of wechat, the date formats of ios and Android are YYYY/MM/DD and YYYY-MM-DD respectively. Here, we need to judge wechat’s built-in browser User Agent.
  • The asynchronous problem of getting the server time, save the server time in vuex and listen for the current date change in the calendar.vue page. Render calendar data calculations in time.