elementUI 年份范围选择器实现

elementUI 不支持年份范围的选择器,依照下面的文章进行修改和完善 el-year-picker

element日期选择范围、选择年份范围_elemet 两个日期 选择的年份范围必须在三年之内-CSDN博客


el-year-picker 组件:

依赖包:moment
属性:
  • sp:默认 '至'
  • value:默认 [ ]
  • size:默认 large,支持 small、mini
  • clearable:默认 true
  • startPlaceholder:默认 开始年份
  • endPlaceholder:默认 结束年份
事件:
  • input:参数 value[ ]

效果图

代码

index.vue
html 复制代码
<template>
  <el-popover
    ref="popover"
    placement="bottom"
    v-model="showPanel"
    popper-class="custom_year_range"
    trigger="manual"
    v-clickoutside="() => { showPanel = false }"
  >
    <div class="_inner floatPanel">
      <div class="_inner leftPanel">
        <div class="_inner panelHead">
          <i class="_inner el-icon-d-arrow-left" @click="onClickLeft"></i>
          <span>{{ leftYearList[0] + '年 ' + '- ' + leftYearList[9] + '年' }}</span>
        </div>
        <div class="_inner panelContent">
          <div
            :class="{
            oneSelected: item === startYear && oneSelected,
            startSelected: item === startYear,
            endSelected: item === endYear,
            betweenSelected: item > startYear && item < endYear,
          }"
            v-for="item in leftYearList"
            :key="item"
          >
            <a
              :class="{
              cell: true,
              _inner: true,
              selected: item === startYear || item === endYear,
            }"
              @click="onClickItem(item)"
              @mouseover="onHoverItem(item)"
            >{{ item }}</a>
          </div>
        </div>
      </div>
      <div class="_inner rightPanel">
        <div class="_inner panelHead">
          <i class="_inner el-icon-d-arrow-right" @click="onClickRight"></i>
          <span>{{ rightYearList[0] + '年 ' + '- ' + rightYearList[9] + '年' }}</span>
        </div>
        <div class="_inner panelContent">
          <div
            :class="{
            startSelected: item === startYear,
            endSelected: item === endYear,
            betweenSelected: item > startYear && item < endYear,
          }"
            v-for="item in rightYearList"
            :key="item"
          >
            <a
              :class="{
              cell: true,
              _inner: true,
              selected: item === endYear || item === startYear,
            }"
              @click="onClickItem(item)"
              @mouseover="onHoverItem(item)"
            >{{ item }}</a>
          </div>
        </div>
      </div>
    </div>
    <div slot="reference">
      <div
        ref="yearPicker"
        style="width: 100%"
        class="el-date-editor el-range-editor el-input__inner el-date-editor--daterange yearPicker"
        :class="['el-range-editor--' + size, showPanel ? 'is-active' : '', startShowYear ? 'is-val' : '']"
        @mouseover="handleHover(true)"
        @mouseleave="handleHover(false)"
      >
        <i class="el-input__icon el-range__icon el-icon-date"></i>
        <input
          class="_inner range_input"
          ref="inputLeft"
          type="text"
          name="yearInput"
          :placeholder="startPlaceholder"
          v-model="startShowYear"
          @focus="onFocus"
          @keyup="handleInput('start')"
        />
        <span class="el-range-separator">{{ sp }}</span>
        <input
          class="_inner range_input"
          ref="inputRight"
          type="text"
          name="yearInput"
          :placeholder="endPlaceholder"
          v-model="endShowYear"
          @focus="onFocus"
          @keyup="handleInput('end')"
        />
        <i
          class="el-input__icon el-range__close-icon"
          :class="[startShowYear && isHover && clearable ? 'el-icon-circle-close' : '']"
          @click="onClear"
        ></i>
      </div>
    </div>
  </el-popover>
</template>

<script>
import moment from 'moment'
import { clickoutside, SELECT_STATE } from './utils.js'
export default {
  name: 'ElYearPicker',
  directives: { clickoutside },
  computed: {
    oneSelected () {
      return this.curState === SELECT_STATE.selecting && (this.startYear === this.endYear || this.endYear == null)
    },
    leftYearList () {
      return this.yearList.slice(0, 10)
    },
    rightYearList () {
      return this.yearList.slice(10, 20)
    }
  },
  props: {
    sp: {
      default: '至'
    },
    value: {
      type: Array,
      default: [],
    },
    size: {
      type: String,
      default: 'large'
    },
    clearable: {
      type: Boolean,
      default: true
    },
    startPlaceholder: {
      type: String,
      default: '开始年份'
    },
    endPlaceholder: {
      type: String,
      default: '结束年份'
    }
  },
  data () {
    return {
      itemBg: {},
      startShowYear: null,
      endShowYear: null,
      yearList: [],
      showPanel: false,
      startYear: null,
      endYear: null,
      curYear: 0,
      curSelectedYear: 0,
      curState: SELECT_STATE.unselect,
      isHover: false
    }
  },
  methods: {
    handleInput (type) {
      switch (type) {
        case 'start':
          if (isNaN(this.startShowYear)) {
            this.startShowYear = this.startYear
            return
          }
          this.startYear = this.startShowYear * 1
          break
        case 'end':
          if (isNaN(this.endShowYear)) {
            this.endShowYear = this.endYear
            return
          }
          this.endYear = this.endShowYear * 1
          break
      }
      [this.startYear, this.endYear] = [this.endYear, this.startYear]
      this.startShowYear = this.startYear
      this.endShowYear = this.endYear
    },

    onHoverItem (iYear) {
      if (this.curState === SELECT_STATE.selecting) {
        const tmpStart = this.curSelectedYear
        this.endYear = Math.max(tmpStart, iYear)
        this.startYear = Math.min(tmpStart, iYear)
      }
    },

    async onClickItem (selectYear) {
      if (
        this.curState === SELECT_STATE.unselect ||
        this.curState === SELECT_STATE.selected
      ) {
        this.startYear = selectYear
        this.curSelectedYear = selectYear
        this.endYear = null
        this.curState = SELECT_STATE.selecting
      } else if (this.curState === SELECT_STATE.selecting) {
        this.endShowYear = this.endYear || this.startYear
        this.startShowYear = this.startYear
        this.curState = SELECT_STATE.selected
        await this.$nextTick()
        this.showPanel = false
        this.$parent.$parent.$parent.$parent.$parent.clearValidate()
      }
    },

    async onFocus () {
      await this.$nextTick()
      this.showPanel = true
    },

    handleHover (flag) {
      this.isHover = flag
    },

    updateYearList () {
      const startYear = ~~(this.curYear / 10) * 10
      console.log(startYear, this.curYear, 'this.curYearthis.curYearthis.curYear')
      this.yearList = []
      for (let index = 0; index < 20; index++) {
        this.yearList.push(startYear + index)
      }
    },

    onClickLeft () {
      this.curYear = this.curYear * 1 - 10
      this.updateYearList()
    },

    onClickRight () {
      this.curYear = this.curYear * 1 + 10
      this.updateYearList()
    },

    onClear () {
      if (this.startShowYear && this.isHover && this.clearable) {
        this.startYear = null
        this.endYear = null
        this.curSelectedYear = 0
        this.curState = SELECT_STATE.unselect

        this.showPanel = false;
        this.curYear = moment().format('yyyy')
        this.updateYearList()
        this.startShowYear = ''
        this.endShowYear = ''
        this.$emit('input', [])
      }
    }
  },
  watch: {
    value: {
      handler (val) {
        if (val.length == 0) {
          this.startShowYear = ''
          this.endShowYear = ''
        } else {
          const [first, end] = val || []
          this.startShowYear = val[0]
          this.endShowYear = val[1]
        }

      },
      immediate: true,
      deep: true
    },

    startShowYear: {
      handler (val) {
        this.$emit('input', [val, this.endShowYear || ''])
      },
      immediate: true,
      deep: true
    },

    endShowYear: {
      handler (val) {
        this.$emit('input', [this.startShowYear || '', val])
      },
      immediate: true,
      deep: true
    }
  },
  created () {
    console.log('value', this.value)
    const [startYear, endYear] = this.value || []
    if (startYear) {
      this.startYear = Number(startYear)
      this.endYear = Number(endYear)
      this.curState = SELECT_STATE.selected
      this.curYear = startYear
    } else {
      this.curYear = moment().format('yyyy')
    }
    this.updateYearList()
  },

  mounted () {
    window.Vue = this
  }
}
</script>
<style lang="scss">
.custom_year_range {
  border-radius: 10px;
  .floatPanel {
    > div {
      width: 50%;
    }

    padding: 0 16px;
    // position: absolute;
    display: flex;
    background-color: #fff;
    z-index: 2000;
    border-radius: 4px;
    width: 650px;
    height: 250px;
    top: 40px;
    left: -50px;

    .panelContent {
      display: flex;
      flex-wrap: wrap;
      width: 100%;
      height: calc(100% - 70px);

      .oneSelected {
        border-top-right-radius: 24px;
        border-bottom-right-radius: 24px;
      }

      .startSelected {
        background-color: #f2f6fc;
        border-top-left-radius: 24px;
        border-bottom-left-radius: 24px;
      }

      .endSelected {
        background-color: #f2f6fc;
        border-top-right-radius: 24px;
        border-bottom-right-radius: 24px;
      }

      .betweenSelected {
        background-color: #f2f6fc;
      }

      > div {
        width: 75px;
        height: 48px;
        line-height: 48px;
        margin: 3px 0;
        // border-radius: 24px;
        text-align: center;

        a {
          display: inline-block;
          width: 60px;
          height: 36px;
          cursor: pointer;
          line-height: 36px;
          border-radius: 18px;

          &:hover {
            color: #409eff;
          }
        }

        .selected {
          background-color: #409eff;
          color: #fff;

          &:hover {
            color: #fff !important;
          }
        }
      }
    }

    .panelHead {
      position: relative;
      height: 46px;
      line-height: 46px;
      text-align: center;
      display: flex;
      align-items: center;
      justify-content: center;

      span {
        font-size: 16px;
        font-weight: 500;
        padding: 0 5px;
        line-height: 22px;
        text-align: center;
        cursor: pointer;
        color: #606266;

        &:hover {
          color: #409eff;
        }
      }

      i {
        position: absolute;
        cursor: pointer;

        &:hover {
          color: #3e77fc;
        }
      }
    }

    .rightPanel {
      padding-left: 8px;
    }

    .leftPanel .panelHead i {
      left: 20px;
    }

    .rightPanel .panelHead i {
      right: 20px;
    }
  }

  .floatPanel::before {
    content: '';
    height: 100%;
    top: 0;
    position: absolute;
    left: 50%;
    width: 1px;
    border-left: 1px solid #e4e4e4;
  }

  .cell._inner {
    color: #606266;
    text-decoration: none;
  }
}
</style>
<style lang="scss" scoped>
.range_input {
  appearance: none;
  border: none;
  outline: 0;
  padding: 0;
  width: 130px;
  color: #606266;
  line-height: 1;
  height: 100%;
  margin: 0;
  text-align: center;
  display: inline-block;
}

.yearPicker {
  .el-icon-circle-close {
    color: #c0c4cc;
    &:hover {
      color: #909399;
    }
  }
  &.is-val {
  }
  &.is-active {
    border-color: #0052d9 !important;
  }
}

input {
  width: 60px;
  border: none;
  height: 32px;
  line-height: 32px;
  box-sizing: border-box;
  background-color: transparent;
}

input:focus {
  outline: none;
  background-color: transparent;
}
.dateIcon {
  position: absolute;
  right: 16px;
  top: 9px;
  color: #adb2bc;
}
</style>
utils.js
javascript 复制代码
export const clickoutside = {
  bind(el, binding, vnode) {
    function documentHandler(e) {
      // 这里判断点击的元素是否是本身,是本身,则返回
      if (el.contains(e.target)) {
        return false
      }
      // 判断指令中是否绑定了函数
      if (binding && binding.expression) {
        // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
        if (binding.value && binding.value(e)) {
          binding.value(e)
        }
      }
    }
    // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
    el.__vueClickOutside__ = documentHandler
    document.addEventListener('click', documentHandler)
  },
  unbind(el, binding) {
    // 解除事件监听
    document.removeEventListener('click', el.__vueClickOutside__)
    delete el.__vueClickOutside__
  }
}

export const SELECT_STATE = {
  unselect: 0,
  selecting: 1,
  selected: 2
}
相关推荐
黄智勇16 分钟前
xlsx-handlebars 一个用于处理 XLSX 文件 Handlebars 模板的 Rust 库,支持多平台使
前端
brzhang1 小时前
为什么 OpenAI 不让 LLM 生成 UI?深度解析 OpenAI Apps SDK 背后的新一代交互范式
前端·后端·架构
brzhang2 小时前
OpenAI Apps SDK ,一个好的 App,不是让用户知道它该怎么用,而是让用户自然地知道自己在做什么。
前端·后端·架构
程序员王天2 小时前
【开发AGIC】Vue3+NestJS+DeepSeek AI作业批改系统(已开源)
vue.js·ai编程·nestjs
井柏然3 小时前
前端工程化—实战npm包深入理解 external 及实例唯一性
前端·javascript·前端工程化
昔冰_G3 小时前
Vue内置组件KeepAlive——缓存组件实例
vue.js·缓存·vue3·vue2·keep-alive·vue组件缓存·vue内置组件
IT_陈寒3 小时前
Redis 高性能缓存设计:7个核心优化策略让你的QPS提升300%
前端·人工智能·后端
aklry3 小时前
elpis之动态组件机制
javascript·vue.js·架构
井柏然4 小时前
从 npm 包实战深入理解 external 及实例唯一性
前端·javascript·前端工程化
羊锦磊4 小时前
[ vue 前端框架 ] 基本用法和vue.cli脚手架搭建
前端·vue.js·前端框架