美文网首页
vue 账期 - 季度或者半年类型的时间(区间)选择组件

vue 账期 - 季度或者半年类型的时间(区间)选择组件

作者: sunxiaochuan | 来源:发表于2021-05-25 15:30 被阅读0次

引言

需求如文章标题,查阅资料发现 element-ui 并没有做支持这样类型的时间选择器的打算,所以只能手动造轮子。

组件经过一次改版,之前无法进行默认值的设置,后来改造之后,采用双向数据绑定驱动 组件 => 页面;页面 => 组件 的方式。

具备功能:默认时间、时间类型(可选)、是否区间(可选)。

正文

  1. 演示效果
账期 - 季度或者半年类型的时间选择组件 - 区间.gif
账期 - 季度或者半年类型的时间选择组件 - 非区间.gif
  1. 源码
  • 组件 QuarterOrHalfyear.vue
<template lang='pug'>
//- 账期 - 季度或者半年类型的时间(区间)选择组件
.quarter-or-halfyear
  //- 遮罩,为了点击空白处关闭时间选择框
  .mask-block(v-show='showChooseBox', @click.stop='handlerCancel')
  //- input 展示框
  el-input(
    v-model='showName',
    @focus='showChooseBox = true',
    placeholder='请选择',
    size='small'
  )
    i.el-input__icon.el-icon-date(slot='prefix')
  //- 时间选择框
  .date-choose-box(
    v-show='showChooseBox',
    :class='{ "date-choose-box-shot": !isInterval }'
  )
    //- 左侧
    .box-left
      .box-year
        button.el-picker-panel__icon-btn.el-icon-d-arrow-left(
          type='button',
          @click='leftYear--'
        )
        .left-year {{ leftYear }}
        button.el-picker-panel__icon-btn.el-icon-d-arrow-right(
          type='button',
          @click='(leftYear < rightYear - 1 || !isInterval) && leftYear++'
        )
      .box-choose
        .choose-list
          .choose-item(
            v-for='(item, index) in typeDictionary[dateType]',
            :key='index',
            :class='{ "choose-item-active": periodDataCopy instanceof Array ? periodDataCopy.some((element) => element === `${leftYear + item.value}`) : periodDataCopy === leftYear + item.value }',
            @click='handlerItemClick(`${leftYear + item.value}`, `${leftYear}年${item.name}`)'
          ) {{ item.name }}
    //- 右侧
    .box-right(v-show='isInterval')
      .box-year
        button.el-picker-panel__icon-btn.el-icon-d-arrow-left(
          type='button',
          @click='rightYear > leftYear + 1 && rightYear--'
        )
        .left-year {{ rightYear }}
        button.el-picker-panel__icon-btn.el-icon-d-arrow-right(
          type='button',
          @click='rightYear++'
        )
      .box-choose
        .choose-list
          .choose-item(
            v-for='(item, index) in typeDictionary[dateType]',
            :key='index',
            :class='{ "choose-item-active": periodDataCopy instanceof Array ? periodDataCopy.some((element) => element === `${rightYear + item.value}`) : periodDataCopy === rightYear + item.value }',
            @click='handlerItemClick(`${rightYear + item.value}`, `${rightYear}年${item.name}`)'
          ) {{ item.name }}
</template>

<script>
import { differenceWith, isEqual, findIndex } from 'lodash-es'

export default {
  name: 'QuarterOrHalfyear',
  model: {
    prop: 'periodData',
    event: 'change'
  },
  props: {
    // 默认的时间类型,可设置为 quarter / halfyear
    dateType: {
      type: String,
      default: 'quarter'
    },
    // 是否是区间
    isInterval: {
      type: Boolean,
      default: true
    },
    // 绑定的数据字段
    periodData: {
      type: [Array, String],
      default: () => []
    }
  },
  data() {
    return {
      // 时间类型字典
      typeDictionary: {
        quarter: [
          {
            name: '第一季度',
            value: 'Q1'
          },
          {
            name: '第二季度',
            value: 'Q2'
          },
          {
            name: '第三季度',
            value: 'Q3'
          },
          {
            name: '第四季度',
            value: 'Q4'
          }
        ],
        halfyear: [
          {
            name: '上半年',
            value: 'H1'
          },
          {
            name: '下半年',
            value: 'H2'
          }
        ]
      },
      // 左右两侧的年份
      leftYear: '',
      rightYear: '',
      // 备份数据,为了可以还原数据
      periodDataCopy: [],
      // 储存两次点击时所选中的数据
      saveClickData: [],
      // 是否显示时间选择框
      showChooseBox: false,
      // 输入框中回显的值
      showName: ''
    }
  },
  watch: {
    // 监测该值,为了解决外部修改数据,组件内部无法同步的问题
    periodData: {
      handler(newVal, oldVal) {
        // 值不一样的话,才做处理,非区间的字符串比较,和区间的数组比较
        if (
          newVal &&
          (oldVal !== newVal || differenceWith(oldVal, newVal, isEqual).length)
        ) {
          const { isInterval } = this
          if (isInterval) {
            // 区间
            let result = ''
            newVal.forEach((element, index) => {
              result += this.transformQuarterOrHalfyear(element)
              index === 0 && (result += ' - ')
            })
            this.showName = result
          } else {
            // 非区间
            this.showName = this.transformQuarterOrHalfyear(newVal)
          }
          this.periodDataCopy = newVal
        }
      },
      immediate: true
    }
  },
  mounted() {
    const year = this.$moment().format('YYYY')
    this.leftYear = year
    this.rightYear = year * 1 + 1
    // 如果没有默认时间 或者 需要默认回显文本,需要设置一下,为当前年份 + 当前季度 / 上/下半年
    const { periodData, typeDictionary, dateType, isInterval } = this
    if (!periodData || (periodData && !periodData.length)) {
      // 依据时间类型判断当前是哪一季度,或者是上半年还是下半年
      // 定义一个在 typeDictionary 字典中的索引
      let accurate
      if (dateType === 'quarter') {
        accurate = this.$moment().quarter() - 1
      } else {
        const month = this.$moment().month()
        month > 6 ? (accurate = 1) : (accurate = 0)
      }
      accurate = typeDictionary[dateType][accurate]
      const dateValue = `${year + accurate.value}`
      const dateName = `${year}年${accurate.name}`
      // 区间与非区间
      if (isInterval) {
        this.$emit('change', [dateValue, dateValue])
      } else {
        this.$emit('change', dateValue)
      }
    }
  },
  methods: {
    /**
     * 时间选择框 中底部的选项的点击逻辑
     *  vaue 2021Q1
     *  name 2021年第一季度
     */
    handlerItemClick(value, name) {
      const { periodDataCopy, isInterval } = this
      // 区间与非区间
      if (isInterval) {
        // 先判断,如果点击之前左右两侧都存在值,需要清空选项值,这里清空的只是备份值
        if (periodDataCopy && periodDataCopy.length === 2) {
          this.periodDataCopy = []
          this.saveClickData = []
        }
        // 这里如果是第二个选项,且 value 小于第一个选项,需要把它放到前面,否则正常追加
        if (
          this.saveClickData.length === 1 &&
          value < this.saveClickData[0].value
        ) {
          this.periodDataCopy.unshift(value)
          this.saveClickData.unshift({
            value,
            name
          })
        } else {
          this.periodDataCopy.push(value)
          this.saveClickData.push({
            value,
            name
          })
        }
        // 如果选中了两项数据,就直接赋值,关闭弹窗
        if (this.periodDataCopy.length === 2) {
          this.$emit('change', periodDataCopy)
          this.showChooseBox = false
        }
      } else {
        this.$emit('change', value)
        this.showChooseBox = false
      }
    },
    /**
     * 点击空白区域回调
     */
    handlerCancel() {
      this.showChooseBox = false
      const { periodDataCopy, isInterval } = this
      // 区间与非区间
      if (isInterval) {
        // 如果没有两个选中值,需要重置选择
        if (periodDataCopy && periodDataCopy.length !== 2) {
          this.periodDataCopy = this.periodData
          this.saveClickData = []
        }
      } else {
        // 非区间的
      }
    },
    /**
     * 封装一个 value 转成 name 的函数,示例:2021Q1 => 2021年第一季度;2021H2 => 2021年xiabann
     */
    transformQuarterOrHalfyear(value) {
      // 非空且格式正确
      if (value && value.length === 6) {
        const { typeDictionary } = this
        const newTypeDictionary = [
          ...typeDictionary.quarter,
          ...typeDictionary.halfyear
        ]
        const year = `${value.substr(0, 4)}年`
        let accurate = value.substr(4)
        const index = findIndex(
          newTypeDictionary,
          (element) => element.value === accurate
        )
        accurate = newTypeDictionary[index].name
        return `${year + accurate}`
      } else {
        return ''
      }
    }
  }
}
</script>

<style lang="stylus" scoped>
.quarter-or-halfyear
  position relative
  .mask-block
    position fixed
    top 0
    bottom 0
    left 0
    right 0
    margin auto
    z-index 996
  .date-choose-box
    box-shadow 0 0 0 1px $main-border-gray inset
    position absolute
    z-index 999
    top 40px
    width 500px
    height 250px
    background-color #fff
    display flex
    &-shot
      width 250px
    &>div:first-child
      box-shadow 1px 0 0 0 $main-border-gray
    &>div
      flex 1
      width 250px
      padding 16px
      .box-year
        line-height 28px
        margin 0 0 10px 0
        display flex
        justify-content space-between
        align-items center
        font-size 16px
        .el-picker-panel__icon-btn
          margin 0
      .box-choose
        .choose-list
          .choose-item
            text-align center
            line-height 24px
            font-size 12px
            cursor pointer
            margin 0 0 10px 0
            border-radius 8px
            &:hover, &-active
              background-color $main-blue
              color #fff
</style>

上面代码引用的外部依赖有:lodash-es moment

因为我们的业务需求,需要拼接数据格式为 2021Q1 2021H1,所以 时间类型字典 typeDictionary 是源码中这样定义的,这个可以根据业务需求自行更改。

相关文章

网友评论

      本文标题:vue 账期 - 季度或者半年类型的时间(区间)选择组件

      本文链接:https://www.haomeiwen.com/subject/ynuqsltx.html