Vue package month multi-select component

When writing this component. element el-date-picker has no type=”months” feature to select multiple months. Now if you want to select multiple months, you can use element directly. This article is just for the record.

1. Use el-date-picker type=”months” directly

<div class="container">
  <div class="block">
    <span class="demonstration">week</span>
    <el-date-picker
      v-model="value1"
      type="week"
      format="yyyy week WW"
      placeholder="select week">
    </el-date-picker>
  </div>
  <div class="block">
    <span class="demonstration">month</span>
    <el-date-picker
      v-model="value2"
      type="month"
      placeholder="select month">
    </el-date-picker>
  </div>
</div>
<div class="container">
  <div class="block">
    <span class="demonstration">year</span>
    <el-date-picker
      v-model="value3"
      type="year"
      placeholder="select year">
    </el-date-picker>
  </div>
  <div class="block">
    <span class="demonstration">Multiple dates</span>
    <el-date-picker
      type="dates"
      v-model="value4"
      placeholder="Select one or more dates">
    </el-date-picker>
  </div>
</div>
<div class="container">
  <div class="block">
    <span class="demonstration">multiple months</span>
    <el-date-picker
      type="months"
      v-model="value5"
      placeholder="Select one or more months">
    </el-date-picker>
  </div>
  <div class="block">
    <span class="demonstration">multiple years</span>
    <el-date-picker
      type="years"
      v-model="value6"
      placeholder="Select one or more years">
    </el-date-picker>
  </div>
</div>

<script>
  export default {
    data() {
      return {
        value1: '',
        value2: '',
        value3: '',
        value4: '',
        value5: '',
        value6: ''
      };
    }
  };
</script>

2. Package components yourself

1. Create a new component SelectMonths.vue

<template>
  <div id="boxArea" ref="selectMonths" class="selectMonthBoxSquare rel clearFixed">
    <el-popover
      v-model="visible"
      placement="bottom"
      width="250"
      trigger="click"
    >
      <el-input slot="reference" v-model="inputValue" class="inputStyle" type="text" size="small" :placeholder="placeholder" readonly @focus="showBox = true">
        <i slot="prefix" class="el-input__icon el-icon-date"></i>
        <i v-if="showClear" slot="suffix" class="el-input__icon el-icon-circle-close clearIconStyle" @click="resetMonth"></i>
      </el-input>

      <div class="selectContentBox">
        <div class="contentArea">

          <div class="flex flex-wrap flex-around" style="padding: 15px 0;border-bottom: 1px solid #e5e5e5;">
            <div v-if="curIndex == DateList.length - 1" class="cursor el-icon-d-arrow-left" style="width: 15%; color: gray;" />
            <div v-else class="cursor el-icon-d-arrow-left" style="width: 15%;" @click="reduceYear" />
            <div>{<!-- -->{ OneY }}Year</div>
            <div v-if="curIndex == 0" class="cursor t-r el-icon-d-arrow-right" style="width: 15%; color: gray;" />
            <div v-else class="cursor t-r el-icon-d-arrow-right" style="width: 15%;" @click="addYear" />
          </div>

          <div class="conterList">
            <el-checkbox-group v-model="optTime[curIndex].queryTime" class="flex flex-wrap" @change="onChange">
              <el-row class="monthRow">
                <el-col v-for="(item,index) in DateList[curIndex].queryTime" :key="index" :span="6" class="monthCol">
                  <el-checkbox :class="[{'today': item === currentM & amp; & amp; curIndex === (DateList.length - 1) / 2}, 'onSelect flex-x-center']" :label="`${DateList[curIndex].TimeYear}-${(item<=9)?`0${item}`:item}`">
                    {<!-- -->{ monthMap[item] }} month
                  </el-checkbox>
                </el-col>
              </el-row>
            </el-checkbox-group>
          </div>
        </div>

        <div class="buttonBox t-r">
          <el-button class="buttonStyle" size="mini" plain @click.stop="resetMonth">Reset</el-button>
          <el-button class="buttonStyle" size="mini" type="primary" plain @click.stop="handleSubmit">OK</el-button>
        </div>
      </div>
    </el-popover>
  </div>
</template>
<script>
  export default {
    name: 'SelectMonths',
    props: {
      placeholder: {
        type: String,
        default: 'Please select the query month'
      }
    },
    data() {
      return {
        DateList: [], // year and month array
        optTime: [], // Array of selected results for the month
        OneY: '', // current year
        currentM: '', // current month
        curIndex: 0, // current year subscript value
        optTimes: [], // all selected results when clicking the month
        resultTimes: [], // The selection result after clicking the "OK" button
        showBox: false, // Whether to display the month selection box
        visible: false,
        inputValue: '', // the binding value of the input box
        showClear: false, // Whether to display the "clear" small icon on the right side of the input box
        monthMap: { // The month is displayed in Chinese
          '1 a',
          '2': 'two',
          '3': 'three',
          '4': 'four',
          '5': 'five',
          '6': 'six',
          '7': 'seven',
          '8': 'eight',
          '9': 'nine',
          '10': 'ten',
          '11': 'Eleventh',
          '12': 'twelve'
        }
      }
    },
    created() {
      this.init()
    },
    mounted() {
      // Click anywhere outside the popup box to close the popup window in the area
      document. addEventListener('click', (e) => {
        // Get popup window object
        const boxArea = document. getElementById('boxArea')
        // Determine whether the click object is included in the popup window object
        if (boxArea & amp; & amp; !boxArea. contains(e. target)) {
          // Determine whether the currently selected month is consistent with the selection result when the "OK" button was clicked last time
          const equalArr = this.resultTimes.sort().toString() === this.optTimes.sort().toString()
          if (!equalArr) {
            // If inconsistent (because it is multiple selection, the query must be performed after clicking the "OK" button):
            // Restore the selection result to the result when the "OK" button was clicked last time
            this.optTimes = this.resultTimes
            // Restore the value of the input box to the value when the "OK" button was clicked last time
            this.inputValue = this.optTimes.join(',')
            // Determine whether to render the empty icon according to whether the input box has a value
            this.showClear = !!this.inputValue !== ''
            // Restore the selected month to the selected month when the "OK" button was clicked last time
            const _opt = this.resultTimes.map(v => { return v.substring(0, 4) })
            for (const item in this. optTime) {
              this.optTime[item].queryTime = []
              _opt. map((items, indexes) => {
                if (items === this. optTime[item]. TimeYear) {
                  this.optTime[item].queryTime.push(this.resultTimes[indexes])
                }
              })
            }
          }
          // close popup
          this. showBox = false
        }
      })
    },
    methods: {
      // Initialize the data, get the first 20 years, and then loop. There are 12 months in each year to get the array opTime and DateList
      init() {
        const _this = this
        const_opt = []
        const _optTime = []
        const arr = new Array(12)
        const optDate = this. getDateList()
        optDate. map((item, index) => {
          // The value bound to el-checkbox-group when selecting the month
          _optTime[index] = {
            TimeYear: item. value,
            queryTime: []
          }
          // Set 12 months for each year, used when el-checkbox initializes the display
          _opt[index] = {
            TimeYear: item. value,
            queryTime: []
          }
          for (let i = 1; i <= arr. length; i ++ ) {
            _opt[index].queryTime.push(i)
          }
        })
        _this.optTime = _optTime
        _this. DateList = _opt
        _this.curIndex = (_this.DateList.length - 1) / 2
        // console. log('_this. curIndex', _this. curIndex)
      },
      // Get the list of years in the past 20 years, sorted in reverse order, with the latest year at the top
      getDateList() {
        const Dates = new Date()
        const year = Dates. getFullYear()
        this.OneY = year
        this.currentM = Dates.getMonth() + 1
        const startyear = Dates.getFullYear() - 20
        const endyear = Dates.getFullYear() + 20
        const Years = []
        for (let i = startyear; i <= endyear; i ++ ) {
          const currentYear = { key: i, value: i }
          Years. push(currentYear)
        }
        return Years. reverse()
      },
      // Sure
      handleSubmit() {
        const _this = this
        // Update the value of the input box
        _this.inputValue = _this.optTimes.join(',')
        // Determine whether to render the empty icon according to whether the input box has a value
        _this.showClear = !!_this.inputValue !== ''
        // Save the selection result of clicking the "OK" button (where this value will be used: when clicking on an area outside the pop-up frame to close the pop-up frame, in mounted)
        _this.resultTimes = _this.optTimes
        // close popup
        _this. showBox = false
        _this.visible = false
        _this.$emit('submitBtn', _this.resultTimes)
      },
      // reset
      resetMonth() {
        const _this = this
        // reset the year to the current year
        const Dates = new Date()
        const year = Dates. getFullYear()
        _this.OneY = year
        _this.curIndex = (_this.DateList.length - 1) / 2
        // Clear the selected month
        _this.optTimes = []
        for (const i in _this. optTime) {
          _this.optTime[i].queryTime = []
        }
        // Clear the input box
        _this.inputValue = ''
        // Judging whether to render the empty icon according to whether the input box has a value, it must not be rendered here
        this. showClear = false
        // Clear the selection result of clicking the "OK" button
        _this.resultTimes = []
        // Close the month selection popup
        _this. showBox = false
        _this.visible = false
        _this.$emit('resetBtn', _this.resultTimes)
      },
      // Decrease the year in the upper left corner
      reduceYear() {
        const _this = this
        // If it is already the last year, the year cannot be decreased any more
        if (_this. curIndex === _this. DateList. length - 1) return
        // The current subscript value + 1, get the year value according to the subscript value
        _this.curIndex = _this.curIndex + 1
        _this.OneY = _this.DateList[_this.curIndex].TimeYear
      },
      // Increase the year in the upper left corner
      addYear() {
        const _this = this
        // If it is already the current year, the year cannot be increased
        if (_this. curIndex === 0) return
        // The current subscript value -1, get the year value according to the subscript value
        _this.curIndex = _this.curIndex - 1
        _this.OneY = _this.DateList[_this.curIndex].TimeYear
      },
      // select month
      onChange() {
        const _this = this
        // Traverse the selected months in optTime, and stuff the selected results into the optTimes array
        const _opt = _this. optTime
        const arr = []
        for (const item in _opt) {
          if (_opt[item].queryTime.length > 0) _opt[item].queryTime.filter(v => { arr.push(v) })
        }
        _this.optTimes = arr
        // Update the value of the input box
        _this.inputValue = _this.optTimes.join(',')
        // Determine whether to render the empty icon according to whether the input box has a value
        _this.showClear = !!_this.inputValue !== ''
      }
    }
  }
</script>
<style lang="scss" scoped>
  .flex {
    display: -webkit-box;
    display: -webkit-flex;
    display: flex;
    justify-content: center;
  }
  .flex-wrap {
    flex-wrap: wrap;
  }
  .flex-around {
    justify-content: space-around;
  }
  .flex-x-center {
    display: -webkit-box;
    display: -webkit-flex;
    display: flex;
    -webkit-box-pack: center;
    -webkit-justify-content: center;
    -ms-flex-pack: center;
    justify-content: center;
  }
  .selectMonthBoxSquare {
    // width: 250px;
    width: 100%;
    .selectContentBox {
      position: absolute;
      top: 35px;
      left: 0;
      z-index: 2021;
      background: #ffffff;
      border: 1px solid #e5e5e5;
      border-radius: 3px;
      .contentArea {
        width: 330px;
      }
    }
  }

    .inputStyle {
      width: 100%;
    }
    .clearIconStyle {
      display: none;
    }
    .inputStyle:hover .clearIconStyle{
      display: block;
    }
    .conterList{
      .monthCol{
        .today {
          color: #1890ff;
        }
      }
      .onSelect{
        // width: 25% !important;
        padding: 3px 0;
        margin: 20px 0 !important;
      }
    }
    ::v-deep.el-checkbox__input {
      display: none !important;
    }
    ::v-deep.el-checkbox__label {
      padding-left: 0px !important;
    }
    ::v-deep .el-checkbox__input.is-checked + .el-checkbox__label {
      color: #fff;
    }
    label.el-checkbox.onSelect.flex-x-center.is-checked {
      background: #409EFF;
      width: 50px;
      border-radius: 15px;
    }
    .lableStyle {
      font-size: 14px;
    }
    .el-button --mini {
      padding: 5px 15px;
      font-size: 12px;
      border-radius: 3px;
    }
    .buttonBox {
      border-top: 1px solid #e5e5e5;
      padding: 10px;
      display: flex;
      justify-content: flex-end;
    }
</style>

2. Use the component SelectMonths

<template>
  <div class="app-container">
    <div>
      <span class="label">Month:</span>
      <Select Months
        ref="selectMonths"
        v-model="monthsValue"
        placeholder="Please select a month"
        @submitBtn="submitBtn"
        @resetBtn="resetBtn" />
    </div>
  </div>
</template>

<script>
  import SelectMonths from '@/components/SelectMonths'
  export default {
    components: {
      Select Months
    },
    data() {
      return {
        monthsValue: null
      }
    },
    methods: {
      submitBtn(val) {
        const months = JSON. parse(JSON. stringify(val))
        const monthsArr = months. map(item => {
          return item.concat('-01')
        })
        this.monthsValue = monthsArr.join(',')
      },
      resetBtn() {
        this. monthsValue = null
      },
    }
  }
</script>