1. Demonstration of results

1.1 Date switching

1.2 Click on a multiple-choice

1.3 Sliding multi-select

2. Implement basic date rendering

2.1 train of thought

2.1.1 Date object methods to be used

  1. new Date(2020, 4, 1).getDay()Calculates the day of the week that was passed in, and returns the value0 means Sunday.1 means MondayAnd so on…
  2. new Date(2020, 4, 0).getDate()Calculate the total number of days in that month for the incoming month,The third parameter is 0 to calculate the total number of days!!

2.1.2 Analyze the calendar date structure

  1. A date table with six rows and seven columns42This is fixed (important!).
  2. In general, the dates of last month, this month and next month are displayed, but in the special case where the first day of the month falls on a Monday, only the dates of this month and next month are displayed.
  3. Also note that in January and December each year, the year before and the year after, respectively, should be- 1or+ 1

2.1.3 How to calculate the date of last month

When the first day of the month is not a Monday, the date of the previous month must be displayed

  1. Calculate the first day of the month (n.0 <= n <= 6), then it will be shown last monthn-1God, watch outnTo zero,nIt’s going to be 7
  2. Count the number of days in a month
  3. Cycle add date – cycle start: days in the last month -n + 2, cycle end: <= days in the last month

2.1.4 How to calculate the date of next month

  1. The current date total 42 minusThe days of the monthsubtractingn-1, get the number of days to show in the next month
  2. Loop add date: start of loop: 1, end of loop: <= result of first step

2.1.5 2d array rendering generates calendar

The above steps generate the last month’s date and the next month’s date and the current month’s date, and the combination of the three arrays is just a one-dimensional array of length 42. But our table is structured like TR -> TD, so we have to convert a one-dimensional array into a two-dimensional array in order to iterate through the basic calendar style.

2.2 Implementation Code

2.2.1 Calendar. Vue

<template>
  <div class="calendar">
    <table class="calendar-table">
      <thead>
        <tr>
          <th v-for="(item, i) in weeks" :key="i">{{ item }}</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(dates, i) in res" :key="i">
          <td
            v-for="(item, index) in dates"
            :key="index"
            :class="{notCurMonth: ! item.isCurMonth, currentDay: item.date === curDate}"
          >
            <! -- <span>{{ item.date.split('-').slice(1).join('-') }}</span> -->
            <span>{{ item.date }}</span>
            <slot :data="item" />
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
Copy the code
<script>
	data() {
	  return {
	    weeks: ['一'.'二'.'三'.'four'.'five'.'六'.'day'].curYear: new Date().getFullYear(), / / the current year
	    curMonth: new Date().getMonth(), / / the current month
	    days: 0.// Total number of days in the current month
	    curDate: parseTime(new Date().getTime()), // Current date In yyyY-MM-DD format, which is used to match the current date
	    prevDays: [].// The date not displayed in the previous month of the current month
	    rearDays: [].// Display date not next month of the current month
	    curDays: [].// Date of the current month
	    showDays: [].// A total of 42 dates are displayed
	    res: [].// A two-dimensional array}},created() {
		// Render the current month by default
		this.handleGetDays(this.curYear, this.curMonth)
	},
	methods() {
	   handleGetDays(year, month) {
	      this.showDays = []
	      this.days = getDaysInMonth(year, month) // Get the number of days in the month
	      let firstDayOfWeek = new Date(year, month, 1).getDay() // Get the day of the month [0, 6]
	      if (firstDayOfWeek === 0) { // Sunday is 0 Monday is 1, and so on
	        firstDayOfWeek = 7
	      }
              
	      this.prevDays = handleCrateDate(year, month, 1, firstDayOfWeek, 'prev')
              this.curDays = handleCrateDate(year, month, 1.this.days)
	      this.rearDays = handleCrateDate(year, month, 1.42 - this.days - (firstDayOfWeek - 1), 'rear') 
	      
	      this.showDays.unshift(... this.prevDays)this.showDays.push(... this.curDays)this.showDays.push(... this.rearDays)this.res = this.handleFormatDates(this.showDays)
	   },
	   handleFormatDates(arr, size = 7) { // Pass in the original array of length 42, and finally convert it to a two-dimensional array
	      const arr2 = []
	      for (let i = 0; i < size; i++) {
	        const temp = arr.slice(i * size, i * size + size)
	        arr2.push(temp)
	      }
	      console.log(arr2)
	      return arr2
	  },
	}
</script>
Copy the code

2.2.2 index. Js

// Get the number of days in the month
export const getDaysInMonth = (year, month) = > {
  const day = new Date(year, month + 1.0)
  return day.getDate()
}

// Create an array of dates in YYYY-MM-DD format
export const handleCrateDate = (year, month, start, end, type) = > {
  const arr = []
  if (type === 'prev') { / / in January
    if (start === end) return []
    const daysInLastMonth = getDaysInMonth(year, month - 1) // Get the number of days in the previous month
    for (let i = daysInLastMonth - end + 2; i <= daysInLastMonth; i++) {
      arr.push({
        date: parseTime(new Date(year, month - 1, i)),
        isCurMonth: false}}})else if (type === 'rear') { / / next January
    for (let i = start; i <= end; i++) {
      arr.push({
        date: parseTime(new Date(year, month + 1, i)),
        isCurMonth: false}}})else { / / this month
    for (let i = start; i <= end; i++) {
      arr.push({
        date: parseTime(new Date(year, month, i)),
        isCurMonth: true}}})return arr
}
export function parseTime(time, cFormat) {
  if (arguments.length === 0| |! time) {return null
  }
  const format = cFormat || '{y}-{m}-{d}'
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    if ((typeof time === 'string')) {
      if ((/ ^ [0-9] + $/.test(time))) {
        // support "1548221490638"
        time = parseInt(time)
      } else {
        // support safari
        // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
        time = time.replace(new RegExp(/-/gm), '/')}}if ((typeof time === 'number') && (time.toString().length === 10)) {
      time = time * 1000
    }
    date = new Date(time)
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1.d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  const time_str = format.replace(/{([ymdhisa])+}/g.(result, key) = > {
    const value = formatObj[key]
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') { return ['day'.'一'.'二'.'三'.'four'.'five'.'六'][value] }
    return value.toString().padStart(2.'0')})return time_str
}
Copy the code

2.2.3 Generate the most basic calendar

ps: notCurMonthandcurrentDayThese two class names write their own, the example is not posted, otherwise it will all be the same color.

3. Switch the date and year

3.1 train of thought

Listen for changes in the drop-down box. The handleGetDays method is then called. Finally post the complete code

4. Select the date (click, swipe)

4.1 Click Select

4.4.1 thinking

  1. tdBind the click event, passing in three parameters:item– > value,i-> Row,j– > in the column
  2. throughres[i][j]You can get one of the items that you’re currently clicking onisSelectedI’m going to invert the property, and I’m going to determine itsisSelectedWhether it istrueIf so, push the current item intoselectedDatesArray to perform the de-redo operation (Array.from(new Set(this.selectedDates))). If not, find the index for that item, and clickselectedDatesDelete it from array ( this.selectedDates.splice(this.selectedDates.indexOf(item.date), 1))
  3. Post the complete code at the end

4.2 Slide Selection

2 ideas

  1. In slide mode, the first click is the start, the second click is the end, and the index of the position of the two clicks existsmoveIndexArray, and the array length can only be2, when the array length is2, the array is emptied and the index is restored.Array in ascending order!! . It is convenient to intercept later
  2. To establish acanMoveState, after the first click, you can slide, after the second click, you can’t slide. The defaultcanMovefortruewhenmoveIndex.length === 1.canMoveforfalseAnd whenmoveIndex.length === 2.canMovefortrue
  3. Sliding withmouseoverListening, because the trigger frequency is low, is also incomingitem.i.jThree parameters, and the index calculation method of the element over which the mouse hover isi * 7 + j
  4. The total array selected isthis.selectedDates = this.showDays.slice(this.moveIndex[0], this.moveIndex[1] + 1)
  5. The traversal loop adds one of these elements between the first click index and the second click indexisRangeSelectedState, used to add color. The index of the first click and the last click ismoveIndex[0]i * 7 + jThey can be styled separately to distinguish them.

5. The change of the beginning of the week

5.1 Switching the Chinese characters of the table header

Initially defined header is fixed: weekes: [‘ a ‘, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘, ‘].

When we get the start date of the week passed in, we can reorder the array using splice and unshift.

This.weeks. Unshift (… this.weeks.splice(this.startOfWeek – 1))

5.2 Switching table contents

5.2.1 ideas

  1. Defines an object that enumerates Roman and Chinese numerals for dates
const obj = {
    1: '一'.2: '二'.3: '三'.4: 'four'.5: 'five'.6: '六'.0: 'day'
  }
Copy the code
  1. Get Chinese from the beginning of the month (‘ one ‘,…)
  2. Then useindexOfMethod get the beginning date of the month in ChineseweeksThe index in the array
  3. Get the index out even if the previous month to display the number of days, passedhandleGetDay()functions

The code is attached at the back

6. Call from parent component

6.1 the Attributes

parameter instructions type An optional value The default value
can-select Whether to enable date selection Boolean true/false false
selectMode Select mode: click/swipe String click/move click
start-of-week Week beginning day Number [1, 7] 1
width The width of the entire calendar String 70%
tbodyHeight Height of date String 60px

6.2 Events

The event name instructions parameter
dateSelected Select date trigger when the user is enabled selection

7. Complete code

7.1 Calendar. Vue

<template>
  <div class="calendar">
    <div class="select">
      <el-form inline>
        <el-form-item>
          <el-select v-model="curYear" placeholder="Please select">
            <el-option v-for="item in yearOptions" :key="item.key" :value="item.value" :label="item.label" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-select v-model="curMonth" placeholder="Please select">
            <el-option v-for="item in monthOptions" :key="item.key" :value="item.value" :label="item.label" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuickChange('prev')">On January</el-button>
          <el-button type="primary" @click="handleQuickChange('next')">Next month</el-button>
        </el-form-item>
      </el-form>
    </div>

    <table class="calendar-table" :style="{width}">
      <thead>
        <tr>
          <th v-for="(item, i) in weeks" :key="i">{{ item }}</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(dates, i) in res" :key="i" :style="{height: tbodyHeight}">
          <td
            v-for="(item, index) in dates"
            :key="index"
            :class="{notCurMonth: ! item.isCurMonth, currentDay: item.date === curDate, selectDay: item.isSelected, rangeSelectd: item.isRangeSelected, weekend: item.isWeekend}"
            @click="handleItemClick(item, i, index)"
            @mouseover="handleItemMove(item, i, index)"
          >
            <! -- <span>{{ item.date.split('-').slice(1).join('-') }}</span> -->
            <span>{{ item.date }}</span>
            <slot :data="item" />
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import { getDaysInMonth, handleCrateDate, handleCreateDatePicker, parseTime } from '.. /utils/index'
export default {
  components: {},props: {
    'selectMode': {
      type: String.default: 'click'
    },
    'startOfWeek': {
      type: Number.default: 1
    },
    canSelect: {
      type: Boolean.default: false
    },
    width: {
      type: String.default: '70%'
    },
    tbodyHeight: {
      type: String.default: '60px'}},data() {
    return {
      monthOptions: [].yearOptions: [].weeks: ['一'.'二'.'三'.'four'.'five'.'六'.'day'].curYear: new Date().getFullYear(), / / the current year
      curMonth: new Date().getMonth(), / / the current month
      days: 0.// Total number of days in the current month
      curDate: parseTime(new Date().getTime()), // Current date In yyyY-MM-DD format, which is used to match the current date
      prevDays: [].// The date not displayed in the previous month of the current month
      rearDays: [].// Display date not next month of the current month
      curDays: [].// Date of the current month
      showDays: [].// A total of 42 dates are displayed
      res: [].// A two-dimensional array
      selectedDates: [].// The selected date
      selectedMode: false.// True means click, false means swipe
      moveIndex: [].// Two, the first is the start, the second is the end
      canMove: false // Can trigger a slide when the moveIndex array has a value}},computed: {},watch: {
    curMonth: {
      handler(val) {
        this.handleGetDays(this.curYear, val, this.startOfWeek)
      }
    },
    curYear: {
      handler(val) {
        this.handleGetDays(val, this.curMonth, this.startOfWeek)
      }
    }
  },
  created() {
    this.weeks.unshift(... this.weeks.splice(this.startOfWeek - 1))  // Render the header
    this.handleGetDays(this.curYear, this.curMonth, this.startOfWeek) // Render table contents
    this.selectedMode = this.selectMode === 'click' // Determine the selection mode
  },
  mounted() {
    this.monthOptions = handleCreateDatePicker().months
    this.yearOptions = handleCreateDatePicker().years
    if (localStorage.selectedDates) this.selectedDates = JSON.parse(localStorage.selectedDates)
  },
  methods: {
    handleGetDays(year, month, startOfWeek) {
      this.showDays = []
      this.days = getDaysInMonth(year, month)
      let firstDayOfWeek = new Date(`${year}-${month + 1}- 01 `).getDay()
        
      // Start date of processing week
      const obj = {
        1: '一'.2: '二'.3: '三'.4: 'four'.5: 'five'.6: '六'.0: 'day'
      }
      const firstDayInCN = obj[firstDayOfWeek]
      const index = this.weeks.indexOf(firstDayInCN)

      this.prevDays = handleCrateDate(year, month, 1, index + 1.'prev')
      this.rearDays = handleCrateDate(year, month, 1.42 - this.days - (index), 'rear')
      this.curDays = handleCrateDate(year, month, 1.this.days)
      
      this.showDays.unshift(... this.prevDays)this.showDays.push(... this.curDays)this.showDays.push(... this.rearDays)this.res = this.handleFormatDates(this.showDays)
    },
    handleFormatDates(arr, size = 7) { // Pass in the original array of length 42, and finally convert it to a two-dimensional array
      const arr2 = []
      for (let i = 0; i < size; i++) {
        const temp = arr.slice(i * size, i * size + size)
        arr2.push(temp)
      }
      return arr2
    },
    handleItemClick(item, i, j) {
      if (!this.canSelect) return
      if (this.selectedMode) {
        this.$nextTick(() = > {
          this.res[i][j].isSelected = !this.res[i][j].isSelected
          if (this.res[i][j].isSelected) {
            this.selectedDates.push(this.res[i][j].date)
            this.selectedDates = Array.from(new Set(this.selectedDates))
          } else {
            this.selectedDates.splice(this.selectedDates.indexOf(item.date), 1)}this.$emit('dateSelected'.this.selectedDates)
        })
      } else {
        // In slide mode, the first click is the start and the second click is the end
        const index = i * 7 + j
        this.canMove = true
        if (this.moveIndex.length === 1) {
          this.canMove = false
        }
        if (this.moveIndex.length === 2) {
          this.showDays.forEach(item= > {
            item.isSelected = false
            item.isRangeSelected = false
          })
          this.canMove = true
          this.moveIndex.length = 0
        }
        this.moveIndex.push(index)
        this.moveIndex.sort((a, b) = > a - b)
        this.selectedDates = this.showDays.slice(this.moveIndex[0].this.moveIndex[1] + 1)
        this.selectedDates.length ! = =0 && this.$emit('dateSelected'.this.selectedDates)
      }
    },
    handleItemMove(data, i, j) {
      if (this.canMove && !this.selectedMode) {
        const index = i * 7 + j
        this.showDays.forEach(item= > {
          item.isSelected = false
          item.isRangeSelected = false
        })
        // Make the first and last dates highlighted in blue
        this.showDays[index].isSelected = true
        this.showDays[this.moveIndex[0]].isSelected = true

        // In different cases, if the index of the user's mouse cursor is smaller than the index of the start date, do if else
        if (this.moveIndex[0] < index) {
          for (let i = this.moveIndex[0] + 1; i < index; i++) {
            this.showDays[i].isRangeSelected = true}}else {
          for (let i = index + 1; i < this.moveIndex[0]; i++) {
            this.showDays[i].isRangeSelected = true}}}},handleQuickChange(type) {
      if (type === 'prev') {
        this.curMonth--
        console.log(this.curMonth)
        if (this.curMonth === -1) {
          this.curMonth = 11
          this.curYear -= 1}}else if (type === 'next') {
        this.curMonth++
        if (this.curMonth === 12) {
          this.curMonth = 0
          this.curYear += 1
        }
      }
    }
  }
}
</script>

<style scoped lang="scss">
.calendar{
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}
.calendar-table{
  table-layout: fixed;
  border-collapse: collapse;
  transition:.3s;
  thead tr{
    height: 50px;
  }
  tbody tr{&:first-child td{
      border-top: 1px solid #08a8a0;
    }
    td{
      cursor: pointer;
      border-right: 1px solid #08a8a0;
      border-bottom: 1px solid #08a8a0;
      &:first-child{
        border-left: 1px solid #08a8a0; }}}}.notCurMonth{
  color: #C0C4CC;
}
.currentDay{
  color: #fff;
  background-color: #08a8a0;
}
.selectDay{
  color: #fff;
  background-color: #409EFF;
}
.rangeSelectd{
  color: # 606266;
  background-color: #dee2e9;
}
.weekend{
  color: #F73131;
}
</style>

Copy the code

The index of 7.2 utils/js

/* eslint-disable camelcase */
/* eslint-disable no-unused-vars */

// Get the number of days in the month
export const getDaysInMonth = (year, month) = > {
  const day = new Date(year, month + 1.0)
  return day.getDate()
}

// Create date A date other than the current month is created in yyyY-MM-DD format
export const handleCrateDate = (year, month, start, end, type) = > {
  const arr = []
  if (type === 'prev') { / / in January
    if (start === end) return []
    const daysInLastMonth = getDaysInMonth(year, month - 1) // Get the number of days in the previous month
    console.log('The current month is${month + 1}Month, last Month${month}The days of the month are${daysInLastMonth}Day `)
    for (let i = daysInLastMonth - end + 2; i <= daysInLastMonth; i++) {
      arr.push({
        // date: `${month === 0 ? year - 1 : year}-${(month + 1) < 10 ? month === 0 ? 12 : `0${month}` : month}-${i < 10 ? `0${i}` : i}`,
        date: parseTime(new Date(year, month - 1, i)),
        isCurMonth: false.isSelected: false.isRangeSelected: false}}})else if (type === 'rear') { / / next January
    for (let i = start; i <= end; i++) {
      arr.push({
        // date: `${month === 11 ? year + 1 : year}-${(month + 1) < 9 ? `0${month + 2}` : month + 2 <= 12 ? month + 2 : (month + 2) % 12 < 10 ? `0${(month + 2) % 12}` : (month + 2) % 12}-${i < 10 ? `0${i}` : i}`,
        date: parseTime(new Date(year, month + 1, i)),
        isCurMonth: false.isSelected: false.isRangeSelected: false}}})else { / / this month
    for (let i = start; i <= end; i++) {
      arr.push({
        // date: `${year}-${(month + 1) < 10 ? `0${month + 1}` : month + 1}-${i < 10 ? `0${i}` : i}`,
        date: parseTime(new Date(year, month, i)),
        isCurMonth: true.isSelected: false.isRangeSelected: false}}})// console.log(arr)
  return arr
}

export const handleCreateDatePicker = () = > {
  const years = []
  const months = []
  for (let i = 1970; i <= 2099; i++) {
    years.push({
      label: `${i}Years `.value: i
    })
  }
  for (let i = 0; i <= 11; i++) {
    months.push({
      label: `${i + 1}Month `.value: i
    })
  }
  return {
    years,
    months
  }
}

/**
 * Parse the time to string
 * @param {(Object|string|number)} time
 * @param {string} cFormat
 * @returns {string | null}* /
export function parseTime(time, cFormat) {
  if (arguments.length === 0| |! time) {return null
  }
  const format = cFormat || '{y}-{m}-{d}'
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    if ((typeof time === 'string')) {
      if ((/ ^ [0-9] + $/.test(time))) {
        // support "1548221490638"
        time = parseInt(time)
      } else {
        // support safari
        // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
        time = time.replace(new RegExp(/-/gm), '/')}}if ((typeof time === 'number') && (time.toString().length === 10)) {
      time = time * 1000
    }
    date = new Date(time)
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1.d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  const time_str = format.replace(/{([ymdhisa])+}/g.(result, key) = > {
    const value = formatObj[key]
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') { return ['day'.'一'.'二'.'三'.'four'.'five'.'六'][value] }
    return value.toString().padStart(2.'0')})return time_str
}
Copy the code