封装日期选择器筛选组件(月度+季度+年度+单选+多选+连选)

4,040 阅读9分钟

一、前言

需求:根据日期去筛选数据,分月度季度年度+单选多选连选。 Element-UI组件中的el-date-picker只能实现月份单选、月份连选和年份单选,其他都需要自己再写组件整合。所以我封装了一个这个组件供之后使用。实现效果如下图所示:

企业微信截图_16536392753732.png

二、MergeDatePicker内组件清单

单选多选连续选择
月度el-date-pickerMultipleMonthQuarterYearel-date-picker
季度SingleQuarterMultipleMonthQuarterYear两个SingleQuarter组合
年度el-date-pickerMultipleMonthQuarterYearYearsRange:两个单选年度组合

文件列表如下图所示:

企业微信截图_16535356661307.png

三、代码实现

1、Demos父组件——引用日期选择器MergeDatePicker

<template>
    <div>
        <el-tabs v-model="activeName" type="border-card" style="height: calc(100vh - 62px);">
            <el-tab-pane label="按需导出Excel" name="first">按需导出Excel</el-tab-pane>
            <el-tab-pane label="封装日期选择器" name="second">
                <!-- 引用日期选择器 -->
                <MergeDatePicker :language="language" :isFilter="false" @postFilterCondition="getFilterCondition"></MergeDatePicker>
            </el-tab-pane>
            <el-tab-pane label="下拉框嵌入表格" name="third">下拉框嵌入表格</el-tab-pane>
        </el-tabs>
    </div>
</template>
<script>
import MergeDatePicker from './MergeDatePicker'
export default {
    name:"Demos",
    components:{
        MergeDatePicker,
    },
    data() {
        return {
            activeName: 'first',
        }
    },
    computed:{
        language(){
            //zh是中文,en是英文,实际做项目时是从$store缓存的vuex中获取
            return 'zh' // return this.$store.state.language
        }
    },
    methods:{
        getFilterCondition(filterDate,category,choicetype){
            console.log(filterDate,category,choicetype);
            //得到筛选条件进行筛选...
        },
    }
}
</script>

2、MergeDatePicker日期选择器——整合组件

<template>
  <div>
    <el-tabs :style="'text-align:left;width:'+width" type="card" tab-position="left" v-model="category">
      <el-tab-pane class="tabpanebox" v-for="item in periodModel" :key="item.name" :label="getPropertyByLg(item,'name')" :name="item.name_en">
        <el-radio-group v-model="choicetype" style="width:300px">
          <el-radio v-for="item in choiceModel" :key="item.name" :label="item.model">{{getPropertyByLg(item,'name')}}</el-radio>
        </el-radio-group>
        <!-- 月度 -->
        <div v-show="choicetype === 'month' && category === 'monthly'">
          <el-date-picker v-model="filterDate.monthly.Single" type="month" popper-class="selectItemStyle" :placeholder="language==='zh'?'请选择月份':'Please select the month'" value-format="yyyy-MM" :picker-options="pickerOptions"></el-date-picker>
        </div>
        <div v-show="choicetype === 'months' && category === 'monthly'">
          <MultipleMonthQuarterYear :isHasfilter="isFilter" :canSelectList="myallList" :language="language" :category="'monthly'" @getMultiple="getMultipleMonth"></MultipleMonthQuarterYear>
        </div>
        <div v-show="choicetype === 'monthrange' && category === 'monthly'">
          <el-date-picker :default-value="String(years-1)" v-model="filterDate.monthly.Serial" type="monthrange" :picker-options="pickerOptions" align="center" value-format="yyyy-MM" range-separator="~" :start-placeholder="language==='zh'?'开始月份':'start month'" :end-placeholder="language==='zh'?'结束季度':'end month'"></el-date-picker>
        </div>
        <!-- 季度 -->
        <div v-show="choicetype === 'month' && category === 'quarterly'">
          <SingleQuarter :isHasfilter="isFilter" :canSelectList="myallList" :language="language" :status="'single'" @getsinglequarter="getsinglequarter"></SingleQuarter>
        </div>
        <div v-show="choicetype === 'months' && category === 'quarterly'">
          <MultipleMonthQuarterYear :isHasfilter="isFilter" :canSelectList="myallList" :language="language" :category="'quarterly'" @getMultiple="getMultiplequarter"></MultipleMonthQuarterYear>
        </div>
        <div v-show="choicetype === 'monthrange' && category === 'quarterly'">
          <div class="flexbox" style="width:50%">
            <SingleQuarter :isHasfilter="isFilter" :canSelectList="myallList" :language="language" :inputwidth="'160px'" :status="'start'" @getsinglequarter="getsinglequarter"></SingleQuarter>
            <span style="margin-left: 5px; margin-right: 5px;">~</span>
            <SingleQuarter :isHasfilter="isFilter" :canSelectList="myallList" :language="language" :inputwidth="'160px'" :status="'end'" @getsinglequarter="getsinglequarter"></SingleQuarter>
          </div>
        </div>
        <!-- 年度 -->
        <div v-show="choicetype === 'month' && category === 'annual'">
          <el-date-picker v-model="filterDate.annual.Single" type="year" popper-class="selectItemStyle" :picker-options="pickerOptions" value-format="yyyy" :placeholder="language==='zh'?'请选择年份':'Please select the year'"></el-date-picker>
        </div>
        <div v-show="choicetype === 'months' && category === 'annual'">
          <MultipleMonthQuarterYear :isHasfilter="isFilter" :canSelectList="myallList" :language="language" :category="'annual'" @getMultiple="getMultipleYear"></MultipleMonthQuarterYear>
        </div>
        <div v-show="choicetype === 'monthrange' && category === 'annual'">
          <YearsRange :isHasfilter="isFilter" :canSelectList="myallList" :language="language" @getYearRange="getYearRange"></YearsRange>
        </div>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script>
import MultipleMonthQuarterYear from './MultipleMonthQuarterYear'
import SingleQuarter from './SingleQuarter'
import YearsRange from './YearsRange'
export default {
  name: "MergeDatePicker",
  components:{
    MultipleMonthQuarterYear,
    SingleQuarter,
    YearsRange,
  },
  props:{
    isFilter:{ //是否过滤可选择的日期
      type: Boolean,
      default:true,
    },
    myallList:{ //isFilter为true时才需要传,有值时只能选择myallList包含的日期数据
      type: Array,
      default:() => [],
    },
    width:{
      type:String,
      default:'100%',
    },
    language:{
      type:String,
      default:'zh',
    }
  },
  data() {
    return {
      periodModel:[{id:1,name:'月度',name_en:'monthly'},{id:2,name:'季度',name_en:'quarterly'},{id:3,name:'年度',name_en:'annual'}],
      choiceModel:[{id:1,name:'单选',name_en:'Single',model:'month'},{id:2,name:'多选',name_en:'Multiple',model:'months'},{id:3,name:'连续选择',name_en:'Serial',model:'monthrange'}],
      filterDate:{ //选择的筛选条件数据
        monthly:{ Single:'', Multiple:[], Serial:[]},
        quarterly:{ Single:'', Multiple:[], Serial:[]},
        annual:{ Single:'', Multiple:[], Serial:[]},
      },
      choicetype:'month',//当前的选择方式:单选、多选、连选
      category:'monthly',//当前的选择周期:月度、季度、年度
      pickerOptions: {
        disabledDate: this.optionsDate
      },
      years:new Date().getFullYear(),//获取现在所处的年份
      startmonth:["01-03", "04-06", "07-09", "10-12"],//转季度Q1、Q2、Q3、Q4需要用
    };
  },
  watch:{
    choicetype:{
      handler(val){
        this.postDate()
      },
    },
    category:{
      handler(val){
        this.postDate()
      },
    },
    watchfiltetData:{
      handler(newVal,oldVal){
        if(newVal.monthly.Single !== oldVal.monthly.Single || JSON.stringify(newVal.monthly.Serial) !== JSON.stringify(oldVal.monthly.Serial) || newVal.annual.Single !== oldVal.annual.Single){
          this.postDate()
        }
      },
      deep:true
    },
  },
  computed:{
    watchfiltetData(){
      return JSON.parse(JSON.stringify(this.filterDate))
    },
    getPropertyByLg(){
      return function retVal(data, property){
        return this.language === 'zh' ? data[property] : data[property + '_en']
      }
    },
  },
  methods: {
    //el-date-picker日期选择器过滤日期
    optionsDate(time){
      if(this.isFilter){
        //单选年度时过滤可选年份
        if(this.choicetype === 'month' && this.category === 'annual'){
          let date = time.getFullYear()
          let timeList = this.myallList.map((item) =>{
            return item.start_time.slice(0,4)
          })
          if(timeList.includes(JSON.stringify(date))){
            return false
          }else{
            return true
          }
        }else{//单选月度时过滤可选月份
          let months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]
          let date = time.getFullYear()+'-'+months[time.getMonth()]
          let timeList = this.myallList.map((item) =>{
            return item.start_time.slice(0,4)+'-'+item.start_time.slice(5,7)
          })
          if(timeList.includes(date)){
            return false
          }else{
            return true
          }
        }
      }else{//最新日期以上可选
        return time.getTime() >= Date.now() - 8.64e7
      }
    },
    // 多选月份
    getMultipleMonth(resultList){
      this.$set(this.filterDate.monthly,'Multiple',resultList)
      this.postDate()
    },
    //多选季度
    getMultiplequarter(resultList){
      this.$set(this.filterDate.quarterly,'Multiple',[])
      //将2022-Q1转换成2022-01即第一季度起始月
      resultList.filter(showValue => {
        let start = showValue.slice(0,4)
        let startquarter = showValue.slice(6,7)
        this.startmonth.filter((item,index) => {
          if(index+1 == startquarter){
            var multiplestart = start + '-' + item.slice(0,2)
            this.filterDate.quarterly.Multiple.push(multiplestart)
          }
        })
      })
      this.postDate()
    },
    // 多选年份
    getMultipleYear(resultList){
      this.$set(this.filterDate.annual,'Multiple',resultList)
      this.postDate()
    },
    // 单选季度+连续选择季度
    getsinglequarter(showValue,status){
      let start = showValue.slice(0,4)
      let startquarter = showValue.slice(6,7)
      this.startmonth.filter((item,index) => {
        let value = start + '-' + item.slice(0,2)
        if(index+1 == startquarter){
          if(status == 'start'){
            this.filterDate.quarterly.Serial.splice(0,1,value)
            if(this.filterDate.quarterly.Serial.length === 2) this.verifySelectQuarterRange()
          }else if(status == 'end'){
            this.filterDate.quarterly.Serial.splice(1,1,value)
            if(this.filterDate.quarterly.Serial.length === 2) this.verifySelectQuarterRange()
          }else if(status == 'single'){
            this.filterDate.quarterly.Single = value
            this.postDate()
          }
        }
      })
    },
    // 验证连续选择的季度并提交
    verifySelectQuarterRange() {
      let start = this.filterDate.quarterly.Serial[0].slice(0,4)
      let startquarter = this.filterDate.quarterly.Serial[0].slice(5,7)
      let end = this.filterDate.quarterly.Serial[1].slice(0,4)
      let endquarter = this.filterDate.quarterly.Serial[1].slice(5,7)
      // console.log(start,end,startquarter,endquarter);
      if (Number(start) < 1991 || Number(end) > this.years) {
        this.$message({
          type: 'error',
          message: this.language==='zh'?'超过选择范围,请重新选择!':'Out of the selection range, please select again!'
        })
      } else if (Number(start) > Number(end) || (Number(start) == Number(end) && Number(startquarter) > Number(endquarter))) {
        this.$message.error(this.language==='zh'?'结束季度须大于等于开始季度,请重新选择!':'The end quarter must be greater than or equal to the start quarter, please select again!')
      } else {
        this.postDate()
      }
    },
    // 连续选择年
    getYearRange(start, end){
      this.filterDate.annual.Serial.splice(0,1,JSON.stringify(start))
      this.filterDate.annual.Serial.splice(1,1,JSON.stringify(end))
      this.postDate()
    },
    // 提交筛选数据
    postDate(){
      var item = this.periodModel.find((p)=>{return p.name_en === this.category})
      var item1 = this.choiceModel.find((p)=>{return p.model === this.choicetype})
      this.$emit('postFilterCondition',this.filterDate,item,item1)
    },
  },
};
</script>
<style scoped>
::v-deep .el-tabs__item{
    height: 26px;
    line-height: 26px;
}
::v-deep .el-tabs__item.is-active {
    color: #5f9aff;
    background-color: #ebf1ff;
}
::v-deep .el-tabs--left .el-tabs__header.is-left{
    margin-right: 0px;
}
::v-deep .el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active{
    border-right-color: #5f9aff;
}
.tabpanebox{
  background-color: #ebf1ff70;
  padding: 0 18px;
  height: 80px;
  line-height: 30px;
  border-radius: 0 8px 8px 0;
}
.flexbox{
  display: flex;
  justify-content: left;
  align-items: center;
}
</style>
<style>
.selectItemStyle .el-month-table td .cell:hover, 
.selectItemStyle .el-month-table td.current:not(.disabled) .cell,
.selectItemStyle .el-year-table td .cell:hover, 
.selectItemStyle .el-year-table td.current:not(.disabled) .cell {
  background-color: #409EFF;
  color: white;
  border-radius: 18px;
}
.selectItemStyle .el-year-table td .cell{
  border-radius: 18px;
}
</style>

3 、MultipleMonthQuarterYear组件——多选月度季度年度子组件

<template>
   <div>
       <el-popover placement="bottom" width="320" trigger="click" v-model="showBox">
       <!-- el-input输入框:readonly和clearable属性不能同时使用 -->
        <el-input slot="reference" @focus="focusselect" class="inputStyle" v-model="inputValue" type="text" readonly :placeholder="language==='zh'?'请选择月份':'Please select the month'" v-if="category === 'monthly'">
            <i slot="prefix" class="el-input__icon el-icon-date"></i>
            <!-- 清空图标:有内容的时候渲染出来,鼠标hover到input框的时候再显示出来(即:输入框有内容并且鼠标悬浮时显示该图标) -->
            <i slot="suffix" class="el-input__icon el-icon-circle-close clearIconStyle" v-if="showClear" @click="resetMonth"></i>
        </el-input>
        <el-input slot="reference" @focus="focusselect" class="inputStyle" v-model="inputValue" type="text" readonly :placeholder="language==='zh'?'请选择季度':'Please select the quarter'" v-else-if="category === 'quarterly'">
            <i slot="prefix" class="el-input__icon el-icon-date"></i>
            <i slot="suffix" class="el-input__icon el-icon-circle-close clearIconStyle" v-if="showClear" @click="resetMonth"></i>
        </el-input>
        <el-input slot="reference" @focus="focusselect" class="inputStyle" v-model="inputValue" type="text" readonly :placeholder="language==='zh'?'请选择年份':'Please select the year'" v-else-if="category === 'annual'">
            <i slot="prefix" class="el-input__icon el-icon-date"></i>
            <i slot="suffix" class="el-input__icon el-icon-circle-close clearIconStyle" v-if="showClear" @click="resetMonth"></i>
        </el-input>
        <!-- 年份月份选择弹框 -->
        <div>
           <div class="contentArea">
               <!-- header年份 -->
                <div v-if="category !== 'annual'" style="display:flex;padding:0 0 10px 0;;border-bottom: 1px solid #e5e5e5;text-align:center">
                    <div v-if="curIndex == DateList.length - 1" style="width: 15%;"><i class="el-icon-d-arrow-left"  style="opacity:0.3;"></i></div>
                    <div v-else @click="reduceYear" style="width: 15%;"><i class="el-icon-d-arrow-left"></i></div>
                    <div style="width: 70%;">{{OneY}}</div>
                    <div v-if="curIndex == 0" style="width: 15%;"><i class="el-icon-d-arrow-right" style="opacity:0.3;"></i></div>
                    <div v-else @click="addYear" style="width: 15%;"><i class="el-icon-d-arrow-right"></i></div>
                </div>
                <div v-else style="display:flex;padding: 15px 0;border-bottom: 1px solid #e5e5e5;text-align:center">
                    <div v-if="isReduce" style="width: 15%;"><i class="el-icon-d-arrow-left" style="opacity:0.3;"></i></div>
                    <div v-else @click="reduceYear" style="width: 15%;"><i class="el-icon-d-arrow-left"></i></div>
                    <div style="width: 70%;">{{yearStart}}--{{yearEnd}}</div>
                    <div v-if="isAdd" style="width: 15%;"><i class="el-icon-d-arrow-right" style="opacity:0.3;"></i></div>
                    <div v-else @click="addYear" style="width: 15%;"><i class="el-icon-d-arrow-right"></i></div>
                </div>
                <!-- 月份 -->
                <div class="conterList" v-if="category === 'monthly'">
                    <el-checkbox-group v-model="optTime[curIndex].queryTime" @change="onChange" v-if="language === 'zh'">
                        <el-checkbox :disabled="isDisabled[index]" class="onSelect onMonthlySelect" v-for="(item,index) in DateList[curIndex].queryTime" :key="index" :label="`${DateList[curIndex].TimeYear}-${(item<=9)?`0${item}`:item}`">
                        {{monthMap[item]}}
                        </el-checkbox>
                    </el-checkbox-group>
                    <el-checkbox-group v-model="optTime[curIndex].queryTime" @change="onChange" v-else-if="language === 'en'">
                        <el-checkbox :disabled="isDisabled[index]" class="onSelect onMonthlySelect" v-for="(item,index) in DateList[curIndex].queryTime" :key="index" :label="`${DateList[curIndex].TimeYear}-${(item<=9)?`0${item}`:item}`">
                        {{monthMapEn[item]}}
                        </el-checkbox>
                    </el-checkbox-group>
                </div>
                <!-- 季度 -->
                <div class="conterList" v-else-if="category === 'quarterly'">
                    <el-checkbox-group ref="checkList" v-model="optTime[curIndex].queryTime" @change="onChange" v-if="language === 'zh'">
                        <el-checkbox :disabled="isDisabled[index]" class="onSelect onQuarterlySelect" v-for="(item,index) in DateList[curIndex].queryTime" :key="index" :label="`${DateList[curIndex].TimeYear}-Q${item}`">
                        {{quarterMap[item]}}
                        </el-checkbox>
                    </el-checkbox-group>
                    <el-checkbox-group v-model="optTime[curIndex].queryTime" @change="onChange" v-else-if="language === 'en'">
                        <el-checkbox :disabled="isDisabled[index]" class="onSelect onQuarterlySelect" v-for="(item,index) in DateList[curIndex].queryTime" :key="index" :label="`${DateList[curIndex].TimeYear}-Q${item}`">
                        {{quarterMapEn[item]}}
                        </el-checkbox>
                    </el-checkbox-group>
                </div>
                <!-- 年度 -->
                <div class="conterList" v-else-if="category === 'annual'">
                    <el-checkbox-group v-model="optTime" @change="onChange">
                        <el-checkbox :disabled="isDisabled[index]" class="onSelect onAnnualSelect" v-for="(item,index) in DateList" :key="index" :label="`${item}`">
                        {{item}}
                        </el-checkbox>
                    </el-checkbox-group>
                </div>
            </div>
            <!-- 按钮 -->
            <div class="buttonBox">
                <el-button size="mini" type="primary" plain @click.stop="handleSubmit">{{language==='zh'?'确定':'OK'}}</el-button>
                <el-button size="mini" plain @click.stop="resetMonth">{{language==='zh'?'重置':'Reset'}}</el-button>
            </div>
       </div>
       </el-popover>
   </div>
</template>
<script>
export default {
    name:"MultipleMonthQuarterYear",
    props:{
        category:{ type: String,default:'monthly'},
        canSelectList: { type: Array,default:()=>[]},//可选择的日期列表
        isHasfilter:{ type: Boolean,default:false},//为true需要传canSelectList
        language:{ type: String,default:'zh'},
    },
    data(){
        return{
            DateList: [], // 年份月份数组
            optTime: [], // 当前选中的结果数组
            OneY: '', // 当前年份
            curIndex: 0, // 当前年份下标值
            yearStart: 2002, // 当前开始年份
            yearEnd: 2021, // 当前结束年份
            optTimes: [], // 点击月份时的所有选中结果
            resultTimes: [], // 点击“确定”按钮后的选择结果
            showBox: false, // 是否显示月份选择弹框
            inputValue: '', // 输入框的绑定值
            showClear: false, // 是否显示输入框右边的“清空”小图标
            monthMap: {'1': '一月', '2': '二月', '3': '三月', '4': '四月', '5': '五月', '6': '六月', '7': '七月', '8': '八月', '9': '九月', '10': '十月', '11': '十一月', '12': '十二月'},
            monthMapEn: {'1': 'Jan', '2': 'Feb', '3': 'Mar', '4': 'Apr', '5': 'May', '6': 'Jun', '7': 'Jul', '8': 'Aug', '9': 'Sep', '10': 'Oct', '11': 'Nov', '12': 'Dec'},
            quarterMap: {'1': '第1季度', '2': '第2季度', '3': '第3季度', '4': '第4季度'},
            quarterMapEn: {'1': 'Q1th', '2': 'Q2nd', '3': 'Q3rd', '4': 'Q4th'},
            selectList:[],//可选择的日期数据
            months:["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"],
            quarters:["01", "04", "07", "10"],
            isDisabled:Array.from({length:12}).map(()=>{return false}),
        }
    },
    created(){
        this.init();
    },
    watch: {
      canSelectList:{
        handler(newVal){
            this.selectList = JSON.parse(JSON.stringify(newVal))
            this.selectList.map((item) =>{
                return item.start_time.slice(0,4)+'-'+item.start_time.slice(5,7)
            })
        },
        deep: true,
      },
    },
    computed:{
        isAdd(){
            let Dates = new Date();
            return this.yearEnd === Dates.getFullYear()
        },
        isReduce(){
            let Dates = new Date();
            return this.yearEnd === Dates.getFullYear()-11
        }
    },
    methods:{
         //聚焦input时设定可选择项
        focusselect(){
            this.$nextTick(()=>{
                switch (this.category) {
                    case 'monthly':
                        var lis = document.getElementsByClassName("onMonthlySelect");
                        break;
                    case 'quarterly':
                        var lis = document.getElementsByClassName("onQuarterlySelect");
                        break;
                    case 'annual':
                        var lis = document.getElementsByClassName("onAnnualSelect");
                        break;
                }
                const lists = Array.from(lis)
                this.isDisabled = Array.from({length:lists.length}).map(()=>{return true})
                if(this.isHasfilter){
                    for(var j = 0; j < this.selectList.length; j++){
                        for(var i = 0; i < lists.length; i++){
                            if(this.category === 'monthly'){
                                var text = this.changeMonthFomat(lists[i].innerText)
                                if(Number(this.selectList[j].start_time.slice(0,4)) == this.OneY && text === this.selectList[j].start_time.slice(5,7)){
                                    this.isDisabled.splice(i,1,false)
                                }
                            }else if(this.category === 'quarterly'){
                                var text = this.changeQuarterFomat(lists[i].innerText)
                                if(Number(this.selectList[j].start_time.slice(0,4)) == this.OneY && text === this.selectList[j].start_time.slice(5,7)){
                                    this.isDisabled.splice(i,1,false)
                                }
                            }else if(this.category === 'annual'){
                                var text = lists[i].innerText
                                if(Number(this.selectList[j].start_time.slice(0,4)) == text){
                                    this.isDisabled.splice(i,1,false)
                                }
                            }
                        }
                    }
                }else{
                    this.isDisabled = Array.from({length:lists.length}).map(()=>{return false})
                }
            })
        },
        //将'一月/Jan'格式化为'01'格式
        changeMonthFomat(text){
            let ret = Object.values(this.language=== 'zh'?this.monthMap:this.monthMapEn)
            var index = 0
            for(var i = 0; i < ret.length; i++){
                if(text.trim() == ret[i]){
                    index = i
                }
            }
            return this.months[index]
        },
        //将'第1季度'格式化为'01'格式、'第2季度'格式化为'04'格式
        changeQuarterFomat(text){
            let ret = Object.values(this.language=== 'zh'?this.quarterMap:this.quarterMapEn)
            var index = 0
            for(var i = 0; i < ret.length; i++){
                if(text.trim() == ret[i]){
                    index = i
                }
            }
            return this.quarters[index]
        },
        // 初始化数据,获取前20年,然后循环 每一年里面都有12个月的 得到数组 opTime 和 DateList
        init(){
            if(this.category !== 'annual'){
                let _opt = [];
                let _optTime = []
                let arr = []
                if(this.category === 'quarterly'){
                    arr = new Array(4);
                }else if(this.category === 'monthly'){
                    arr = new Array(12);
                }
                let optDate = this.getDateList();
                optDate.map((item,index)=>{
                    // 月份选择时el-checkbox-group绑定的值
                    _optTime[index] = {
                        TimeYear: item,
                        queryTime: []
                    }
                    // 给每一年份设置12个月份,el-checkbox初始化显示时使用
                    _opt[index] = {
                        TimeYear: item, 
                        queryTime: []
                    }
                    for(let i = 1; i<= arr.length; i++){
                        _opt[index].queryTime.push(i)
                    }
                })
                this.optTime = _optTime
                this.DateList = _opt;
            }else{
                let Dates = new Date();
                this.yearEnd = Dates.getFullYear();
                this.yearStart = this.yearEnd-11;
                let optDate = [];
                for( let i = this.yearEnd - 11; i <= this.yearEnd; i++ ){
                    optDate.push(i)
                }
                this.DateList = optDate
            }
        },
        // 获取近20年年份列表,倒序排列,最新一年在最前面
        getDateList(){
            let Dates = new Date();
            let year = Dates.getFullYear();
            this.OneY = year;
            let  optDate = [];
            for( let i = year - 20; i <= year; i++ ){
                optDate.push(i)
            }
            return optDate.reverse()
        },
        // 确定
        handleSubmit(){
            // 更新输入框的值
            this.inputValue = this.optTimes.join(',')
            // 根据输入框是否有值来判断清空图标是否渲染
            this.showClear = this.inputValue == '' ? false : true
            // 将点击“确定”按钮的选择结果保存起来(该值将在哪里使用:在点击弹框以外区域关闭弹框时使用,mounted中)
            this.resultTimes = this.optTimes
            // 关闭弹框
            this.showBox = false
            this.$emit('getMultiple',this.resultTimes,this.category)
        },
        // 重置
        resetMonth() {
            // 将年份重置到当前年份
            let Dates = new Date();
            let year = Dates.getFullYear();
            if(this.category === 'annual'){
                this.yearStart = year-11;
                this.yearEnd = year;
                // 将已选择的月份清空
                this.optTime =[]
            }else{
                this.OneY = year;
                // 将已选择的月份清空
                for( let i in this.optTime){
                    this.optTime[i].queryTime =[]
                }
            }
            // 将已选择的月份清空
            this.optTimes = [];
            // 将输入框清空
            this.inputValue = ''
            // 根据输入框是否有值来判断清空图标是否渲染,此处必然不渲染
            this.showClear = false
            // 将点击“确定”按钮的选择结果清空
            this.resultTimes = []
            // 关闭月份选择弹框
            this.showBox = false
            this.$emit('getMultiple',[])
            if(this.category !== 'annual') this.curIndex = 0
        },
        // 左上角年份减少
        reduceYear() {
            if(this.category !== 'annual'){
                // 如果已经是最后一年了,则年份不能再减少了
                if(this.curIndex == this.DateList.length - 1) return;
                // 当前下标值+1,根据下标值获取年份值
                this.curIndex = this.curIndex + 1
                this.OneY = this.DateList[this.curIndex].TimeYear
                //自定义禁用的checkBox
                this.focusselect()
            }else{
                let Dates = new Date();
                if(this.yearEnd === Dates.getFullYear()-11) return;
                this.yearStart -= 11
                this.yearEnd -= 11
                let  optDate = [];
                for( let i = this.yearEnd - 11; i <= this.yearEnd; i++ ){
                    optDate.push(i)
                }
                this.DateList = optDate
            }
        },
        // 左上角年份增加
        addYear() {
            if(this.category !== 'annual'){
                // 如果已经是当前年份了,则年份不能再增加了
                if(this.curIndex == 0) return;
                // 当前下标值-1,根据下标值获取年份值
                this.curIndex = this.curIndex - 1
                this.OneY = this.DateList[this.curIndex].TimeYear
                this.focusselect()
            }else{
                let Dates = new Date();
                if(this.yearEnd === Dates.getFullYear()) return;
                this.yearStart += 11
                this.yearEnd += 11
                let  optDate = [];
                for( let i = this.yearEnd - 11; i <= this.yearEnd; i++ ){
                    optDate.push(i)
                }
                this.DateList = optDate
            }
        },
        // 选择日期
        onChange(){
            const _this = this;
            // 遍历optTime中已选择的日期,将已选结果塞到optTimes数组中
            let _opt = _this.optTime;
            let arr = [];
            if(this.category !== 'annual'){
              for(let item in _opt ){
                if(_opt[item].queryTime.length > 0){
                    _opt[item].queryTime = _opt[item].queryTime.map( p => {
                        if(this.category === 'quarterly'){
                            p = p.slice(0,4)+'-Q'+p.slice(6,7)
                        }
                        arr.push(p)
                        return p
                    })
                }
              }
            }else{
                if(_opt.length > 0){
                  _opt = _opt.map( p => {
                    p = p.slice(0,4)
                    arr.push(p)
                    return p
                  })
              }
            }
            this.optTimes = arr
            // 更新输入框的值
            this.inputValue = this.optTimes.join(',')
            // 根据输入框是否有值来判断清空图标是否渲染
            this.showClear = this.inputValue == '' ? false : true
        }
    }
}
</script>
<style scoped>
.inputStyle {
    width: 300px;
}
.clearIconStyle {
    display: none;
}
.inputStyle:hover .clearIconStyle{
    display: block;
}
.buttonBox {
    border-top: 1px solid #e5e5e5;
    padding: 7px;
    text-align: center;
}
.contentArea {
    width: 320px;
}
.conterList .onSelect{
    width: 60px!important;
    height: 36px;
    line-height: 36px;
    margin: 10px;
    text-align: center;
    border-radius: 20px;
}
.conterList .onSelect:hover{
    color: #FFF;
    background-color: #409EFF;
}
::v-deep .el-checkbox__input.is-checked+.el-checkbox__label{
    color: #FFF;
    background-color: #409EFF;
    width: 60px!important;
    height: 36px;
    line-height: 36px;
    text-align: center;
    border-radius: 20px;
    padding: 0;
}
::v-deep .el-checkbox__label{
    padding: 0;
}
::v-deep .el-checkbox__inner{
    display: none;
}
</style>

4、SingleQuarter组件——单选季度与连选季度子组件

<template>
  <div>
    <el-popover placement="bottom" width="320" trigger="click" v-model="showSeason">
      <el-input @focus="focusselect" slot="reference" size="medium" class="inputStyle"
        :placeholder="status==='single'?(language==='zh'?'请选择季度':'Please select the quarter'):(status==='start'?(language==='zh'?'开始季度':'start quarter'):(language==='zh'?'结束季度':'end quarter'))"
        v-model="showValue"
        :style="'width:'+(inputwidth)+';padding:5px'">
        <i slot="prefix" class="el-input__icon el-icon-date"></i>
        <i slot="suffix" class="el-input__icon el-icon-circle-close clearIconStyle" v-if="showClear" @click="resetQuarter"></i>
      </el-input>
      <div style="width:320px;margin-top:10px;">
        <div style="display:flex;padding:0 0 10px 0;border-bottom: 1px solid #e5e5e5;text-align:center" class="clearfix">
          <div v-if="isReduce" style="width: 15%;"><i class="el-icon-d-arrow-left" style="opacity:0.3;"></i></div>
          <div v-else aria-label="前一年" style="width: 15%;" @click="prev"><i class="el-icon-d-arrow-left"></i></div>
          <span style="width: 70%;">{{ year }}</span>
          <div v-if="isAdd" style="width: 15%;"><i class="el-icon-d-arrow-right" style="opacity:0.3;"></i></div>
          <div v-else aria-label="后一年" style="width: 15%;" @click="next"><i class="el-icon-d-arrow-right"></i></div>
        </div>
        <div class="textitem">
          <el-radio-group v-model="optTime[curIndex].queryTime" @change="selectSeason()">
            <el-radio :disabled="isDisabled[index]" v-for="(item,index) in DateList[curIndex].queryTime" :key="index" :label="`${DateList[curIndex].TimeYear}-Q${item}`" class="oneitem QuarterlySelect">
              {{getPropertyByLg(findDataById(quarterName, 'id', item),'name')}}
            </el-radio>
          </el-radio-group>
        </div>
      </div>
    </el-popover>
  </div>
</template>
<script>
export default {
  name: "SingleQuarter",
  props: {
    status:{
      default:'start',
      type:String,
    },
    inputwidth:{
      default: "220px",
      type: String,
    },
    canSelectList: { //可选择的日期列表
      type: Array,
      default:()=>[],
    },
    isHasfilter:{ //默认为false,为true需要传canSelectList
      type: Boolean,
      default:false,
    },
    language:{
      type:String,
      default:'zh',
    }
  },
  data() {
    return {
      showSeason: false,
      year: new Date().getFullYear(),
      showValue: "",
      showClear: false, // 是否显示输入框右边的“清空”小图标
      optTime:[],
      DateList:{},
      curIndex:0,
      quarterName:[{id:1,name:'第1季度',name_en:'Q1th',data:'01'},{id:2,name:'第2季度',name_en:'Q2nd',data:'04'},{id:3,name:'第3季度',name_en:'Q3rd',data:'07'},{id:4,name:'第4季度',name_en:'Q4th',data:'10'},],
      isDisabled:Array.from({length:4}).map(()=>{return false}),
      selectList:[],//接收的可选择canSelectList数据
    };
  },
  created() {
    this.init()
  },
  watch: {
    canSelectList:{
      handler(newVal){
        this.selectList = JSON.parse(JSON.stringify(newVal))
        this.selectList.map((item) =>{
          return item.start_time.slice(0,4)+'-'+item.start_time.slice(5,7)
        })
      },
      deep: true,
    },
  },
  computed:{
    findDataById(){
      return function(dataList, searchIndex, value){
        var data = dataList.find((data) => {
          return data[searchIndex] === value;
        });
        return data ? data : {};
      }
    },
    getPropertyByLg(){
      return function retVal(data, property){
        return this.language === 'zh' ? data[property] : data[property + '_en']
      }
    },
    isAdd(){
      let Dates = new Date();
      return this.year === Dates.getFullYear()
    },
    isReduce(){
      let Dates = new Date();
      return this.year === Dates.getFullYear()-20
    }
  },
  methods: {
    //初始化
    init(){
      let _opt = []
      let _optTime = []
      let optDate = this.getDateList();
      optDate.map((item,index)=>{
        _optTime[index] = {
          TimeYear: item,
          queryTime: '',
        }
        _opt[index] = {
          TimeYear: item,
          queryTime: [1,2,3,4],
        }
      })
      this.optTime = _optTime
      this.DateList = _opt
    },
    //聚焦input事件设定可选择项
    focusselect(){
      this.$nextTick(()=>{
        var lis = document.getElementsByClassName("QuarterlySelect");
        var lists = Array.from(lis)
        this.isDisabled = Array.from({length:lists.length}).map(()=>{return true})
        if(this.isHasfilter){
          for(var j = 0; j < this.selectList.length; j++){
            for(var i = 0; i < lists.length; i++){
              var text = this.changeQuarterFomat(lists[i].innerText)
              if(Number(this.selectList[j].start_time.slice(0,4)) === Number(this.year) && text === this.selectList[j].start_time.slice(5,7)){
                this.isDisabled.splice(i,1,false)
              }
            }
          }
        }else{
          this.isDisabled = Array.from({length:lists.length}).map(()=>{return false})
        }
      })
    },
    //将“第1季度”转为“01”格式、“第2季度”转为“04”格式...
    changeQuarterFomat(text){
      var ret = this.quarterName.find((p)=>{
        return text.trim() == this.getPropertyByLg(p,'name')
      })
      return ret.data
    },
    // 获取近20年年份列表,倒序排列,最新一年在最前面
    getDateList(){
      let Dates = new Date();
      let year = Dates.getFullYear();
      this.OneY = year;
      let  optDate = [];
      for( let i = year - 20; i <= year; i++ ){
        optDate.push(i)
      }
      return optDate.reverse()
    },
    prev() {
      if(this.curIndex == this.DateList.length - 1) return;
      // 当前下标值-1,根据下标值获取年份值
      this.curIndex = this.curIndex + 1
      this.year = this.DateList[this.curIndex].TimeYear
      this.focusselect()
    },
    next() {
      if(this.curIndex == 0) return;
      // 当前下标值-1,根据下标值获取年份值
      this.curIndex = this.curIndex - 1
      this.year = this.DateList[this.curIndex].TimeYear
      this.focusselect()
    },
    selectSeason() {
      this.showValue = this.optTime[this.curIndex].queryTime
      // 根据输入框是否有值来判断清空图标是否渲染
      this.showSeason = false
      this.optTime.map((p)=>{
        if(p.queryTime !== '' && p.TimeYear !== this.year){
          this.$set(p,'queryTime','')
        }
      })
      this.showClear = this.showValue == '' ? false : true
      this.$emit('getsinglequarter',this.showValue,this.status)
    },
    // 重置
    resetQuarter() {
      // 将年份重置到当前年份
      this.year = new Date().getFullYear();
      // 将已选择的月份清空
      for( let i in this.optTime){
        this.optTime[i].queryTime = ''
      }
      // 将输入框清空
      this.showValue = ''
      // 根据输入框是否有值来判断清空图标是否渲染,此处必然不渲染
      this.showClear = false
      this.curIndex = 0
      this.$emit('getMultiple',[])
    },
  },
};
</script>
<style scoped>
::v-deep .el-radio__input{
  display: none;
}
::v-deep .el-radio__label{
  display: inline-block;
  padding: 0;
}
::v-deep .el-radio__input.is-checked+.el-radio__label{
  color: #FFF;
  background-color: #409EFF;
  width: 60px!important;
  height: 36px;
  line-height: 36px;
  text-align: center;
  border-radius: 20px;
  padding: 0;
}
.textitem{
  text-align: center;
}
.oneitem{
  color: #606266;
  width: 60px!important;
  height: 36px;
  line-height: 36px;
  margin: 10px;
  text-align: center;
  border-radius: 20px;
}
.oneitem:hover{
  color: #FFF;
  background-color: #409EFF;
}
.clearIconStyle {
  display: none;
}
.inputStyle:hover .clearIconStyle{
  display: block;
}
</style>

5、YearsRange组件——连选年度子组件

<template>
  <div>
    <div class="year-picker-box">
      <el-date-picker :clearable="false" :picker-options="pickerOptions"
        v-model="annual.start"
        type="year"
        popper-class="selectItemStyle"
        size="medium"
        style="width: 120px"
        :placeholder="language==='zh'?'开始年份':'start year'">
      </el-date-picker>
      <span style="margin-left: 5px; margin-right: 5px">~</span>
      <el-date-picker :clearable="false" :picker-options="pickerOptions"
        v-model="annual.end"
        type="year"
        popper-class="selectItemStyle"
        size="medium"
        style="width: 120px; margin-right: 2px"
        :placeholder="language==='zh'?'结束年份':'end year'">
      </el-date-picker>
    </div>
  </div>
</template><script>
export default {
  name: 'YearsRange',
  props:{
    canSelectList:{ type: Array,default:()=>[]},//可选择的日期列表
    isHasfilter:{ type: Boolean,default:false},//默认为false,为true需要传canSelectList
    language:{ type: String,default:'zh'},
  },
  data() {
    return {
      years: [1991,new Date().getFullYear()],
      annual:{
        start: null,
        end: null,
      },
      pickerOptions: {
        disabledDate: this.optionsDate
      },
    }
  },
  watch:{
    annual:{
      deep:true,
      handler(val){
        if(val.start && val.end){
          let start = val.start.getFullYear()
          let end = val.end.getFullYear()
          if (start < this.years[0] || end > this.years[this.years.length - 1]) {
            this.$message({ type: 'error', message: this.language==='zh'?'超过年份范围选择,请重新选择。':'The year range is exceeded. Please select a new one.' })
          } else if (val.start > val.end) {
            this.$message.error(this.language==='zh'?'结束年份须大于起始年份,请重新选择。':'The end year must be greater than the start year. Please select a new one.')
          } else {
            this.$emit('getYearRange', start, end)
          }
        }
      }
    }
  },
  methods:{
    optionsDate(time){
      if(this.isHasfilter){
        let date = time.getFullYear()
        let timeList = this.canSelectList.map((item) =>{
          return item.start_time.slice(0,4)
        })
        if(timeList.includes(JSON.stringify(date))){
          return false
        }else{
          return true
        }
      }else{
        return time.getTime() >= Date.now() - 8.64e7
      }
    },
  }
}
</script>
<style scoped>
.year-picker-box {
  padding: 5px;
  width: 340px;
}
</style>

四、总结

这个通用组件结合了elementUI日期选择器组件和多个自定义子组件,通过Demos父组件getFilterCondition函数获取筛选条件对表格或者列表数据进行筛选。可以通过设置isFilter变量,修改日期选择器是否设定只可选择筛选列表包含的日期。其中elementUI日期选择器组件的英文化需要安装vue-i18n,并在入口文件引入使用。