51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

ElementUI 季度选择器 QuarterPicker

背景

很久没写博客了,最近接到产品的新需求,需要按季度汇总数据,可是这么重要的组件,Element 官方居然没有提供。网上逛了一圈,发现功能都比较单一,无法满足需求。于是决定整一个并记录下来,供大家参考,欢迎留言共同学习交流。

实现方式

秉承自己动手丰衣足食的原则,通过查看并借鉴 Element 官方 DatePicker 日期选择器 源码,使用 Popover 弹出框 封装了一个 季度选择器 组件 QuarterPicker,以期用法和功能尽量接近官方类似组件的用法,先来看下最终效果图,如下:

季度选择器.png

季度选择器 QuarterPicker 组件源码

在项目 components 路径下创建 quarter-picker 文件夹,并在其下创建 index.vue 文件,内容如下:

<!--
 * @Descripttion: 季度选择器
 * @version: 1.0
 * @Author: https://www.lervor.com/
 * @Date: 2021-12-06
 * @LastEditTime: 2021-12-09
-->
<template>
  <el-popover
    trigger="focus"
    v-model="pickerVisible"
    popper-class="lervor-quarter-popover"
    :disabled="disabled">
    <el-input
      ref="reference"
      slot="reference"
      class="el-date-editor"
      readonly
      :disabled="disabled"
      :size="size"
      :placeholder="placeholder"
      :value="displayValue"
      :validate-event="false"
      :style="{ width }"
      @mouseenter.native="handleMouseEnter"
      @mouseleave.native="showClose = false">
      <i slot="prefix"
        class="el-input__icon"
        :class="triggerClass">
      </i>
      <i slot="suffix"
        class="el-input__icon"
        :class="[showClose ? '' + clearIcon : '']"
        @click="handleClickIcon"
        @mousedown="handleMousedownIcon">
      </i>
    </el-input>
    <div class="lervor-quarter-picker">
      <div class="el-date-picker__header el-date-picker__header--bordered">
        <button
          type="button"
          aria-label="前一年"
          class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left"
          @click="prevYear">
        </button>
        <span
          role="button"
          class="el-date-picker__header-label">{{ yearLabel }}</span>
        <button
          type="button"
          aria-label="后一年"
          class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right"
          @click="nextYear">
        </button>
      </div>
      <div class="el-picker-panel__content" style="width: 200px; margin: 10px 15px;">
        <table class="lervor-quarter-table" @click="handleTableClick">
          <tbody>
            <tr>
              <td class="available" :class="getCellStyle(0)">
                <a class="cell">第一季度</a>
              </td>
              <td class="available" :class="getCellStyle(1)">
                <a class="cell">第二季度</a>
              </td>
            </tr>
            <tr>
              <td class="available" :class="getCellStyle(2)">
                <a class="cell">第三季度</a>
              </td>
              <td class="available" :class="getCellStyle(3)">
                <a class="cell">第四季度</a>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </el-popover>
</template>

\<script\>
import { formatDate, prevYear, nextYear, range, nextDate, isDateObject, parseDate } from 'element-ui/src/utils/date-util'
import { hasClass } from 'element-ui/src/utils/dom'


// 获取指定年份和季度的所有日期
const datesInYearAndQuarter = (year, quarter) =\> {
const numOfDays = getDayCountOfQuarter(year, quarter)
const firstDay = new Date(year, quarter \* 3, 1)
return range(numOfDays).map(n =\> nextDate(firstDay, n))
}


// 获取指定年份和季度总天数
const getDayCountOfQuarter = (year, quarter) =\> {
switch(quarter) {
case 0: // 第一季度包含二月,需要对是否闰年进行判断处理
if (year % 4 === 0 \&\& year % 100 !== 0 \|\| year % 400 === 0) {
return 91
} else {
return 90
}
case 1:
return 91
default:
return 92
}
}


export default {
name: 'QuarterPicker',
props: {
size: String,
format: String, // 显示在输入框中的格式,引入季度:q(阿拉伯数字)、Q(中文数字)
valueFormat: String,
placeholder: String,
prefixIcon: String,
clearIcon: {
type: String,
default: 'el-icon-circle-close'
},
disabled: Boolean,
clearable: {
type: Boolean,
default: true
},
width: { // 组件宽度
type: String,
default: ''
},
disabledDate: {}, // 不可用的日期
value: null
},
data() {
return {
showClose: false,
pickerVisible: false,
date: new Date(),
quarterText: \[ '一', '二', '三', '四' \]
}
},
computed: {
triggerClass() {
return this.prefixIcon \|\| 'el-icon-date'
},
displayValue() {
if (!this.value) return null
// 季度,从0开始
const quarter = parseInt(this.parsedValue.getMonth() / 3)
let fDate = formatDate(this.parsedValue, this.format)
fDate = fDate.replace(/q/, quarter + 1).replace(/Q/, this.quarterText\[quarter\])
return fDate
},
year() {
return this.date.getFullYear()
},
yearLabel() {
return this.year + ' 年'
},
parsedValue() {
if (!this.value) {
return this.value
}
if (isDateObject(this.value)) {
return this.value
}
// 非时间格式且设置了valueFormat,进行时间转换
if (this.valueFormat) {
return parseDate(this.value, this.valueFormat)
}
// 非时间格式且未设置valueFormat,再尝试转换时间
return new Date(this.value)
}
},
watch: {
value(value) {
this.date = value ? this.parsedValue : new Date()
}
},
methods: {
handleMouseEnter() {
if (this.disabled) return
if (this.value \&\& this.clearable) {
this.showClose = true
}
},
handleClickIcon(event) {
if (this.disabled) return
if (this.showClose) {
this.$emit('input', null)
this.$emit('change', null)
this.showClose = false
this.pickerVisible = false
this.$refs.reference.blur()
}
},
handleMousedownIcon(event) {
// 阻止鼠标按下清空按钮,防止清空数据时季度选择面板闪现
event.preventDefault()
},
handleTableClick(event) {
let target = event.target
if (target.tagName === 'A') {
target = target.parentNode
}
if (target.tagName !== 'TD') return
if (hasClass(target, 'disabled')) return
const column = target.cellIndex
const row = target.parentNode.rowIndex
// 季度,从0开始
const quarter = row \* 2 + column
// 季度开始月份,从0开始
const month = quarter \* 3
let newDate = new Date(this.year, month, 1)
if (this.valueFormat) {
newDate = formatDate(newDate, this.valueFormat)
}
this.pickerVisible = false
this.$emit('input', newDate)
this.$emit('change', newDate)
},
prevYear() {
this.date = prevYear(this.date)
},
nextYear() {
this.date = nextYear(this.date)
},
getCellStyle(quarter) {
const style = {}
const today = new Date()
const date = this.parsedValue ? this.parsedValue : today
style.disabled = typeof this.disabledDate === 'function'
? datesInYearAndQuarter(this.year, quarter).every(this.disabledDate) : false
// 当前选中的季度样式
style.current = date.getFullYear() === this.year \&\& parseInt(date.getMonth() / 3) === quarter
// 今日所在季度样式
style.quarter = today.getFullYear() === this.year \&\& parseInt(today.getMonth() / 3) === quarter
return style
}
}
}
\</script\>


\<style\>
.lervor-quarter-picker {
line-height: 30px;
}
.lervor-quarter-popover {
padding: 0;
}
.lervor-quarter-table {
font-size: 12px;
margin: -1px;
border-collapse: collapse;
width: 100%;
}
.lervor-quarter-table td {
text-align: center;
padding: 10px 3px;
cursor: pointer;
}
.lervor-quarter-table td .cell {
height: 32px;
display: block;
line-height: 32px;
color: #606266;
margin: 0 auto;
}
.lervor-quarter-table td .cell:hover {
color: #1890ff;
}


.lervor-quarter-table td.current:not(.disabled) .cell {
color: #409eff;
}


.lervor-quarter-table td.quarter .cell {
color: #409eff;
font-weight: 700;
}

`.lervor-quarter-table td.disabled .cell {
background-color: #F5F7FA;
cursor: not-allowed;
color: #C0C4CC;
}
</style>`

季度选择器 QuarterPicker 组件文档

Attributes

| 参数 | 说明 | 类型 | 可选值 | 默认值 | |:----------------|:---------------------------------|:---------|:-------------------|:---------------------| | value / v-model | 绑定值 | date | --- | --- | | disabled | 禁用 | boolean | --- | false | | clearable | 是否显示清除按钮 | boolean | --- | true | | size | 输入框尺寸 | string | large, small, mini | --- | | width | 组件宽度 | string | --- | --- | | placeholder | 占位内容 | string | --- | --- | | format | 显示在输入框中的格式,引入季度:q(阿拉伯数字)、Q(中文数字) | string | --- | --- | | value-format | 可选,绑定值的格式。不指定则绑定值为 Date 对象 | string | --- | --- | | prefix-icon | 自定义头部图标的类名 | string | --- | el-icon-date | | clear-icon | 自定义清空图标的类名 | string | --- | el-icon-circle-close | | disabled-date | 不可用的日期 | function | --- | --- |

Events

| 事件名称 | 说明 | 回调参数 | |:-------|:---------|:----------------------------------| | change | 用户选择值时触发 | 组件绑定值。格式与绑定值一致,可受 value-format 控制 |


注意:本组件获取到的值为所选季度的第一天日期,如 2021年第二季度,其值为 2021-04-01 00:00:00,类似官方 年/月选择器


季度选择器 QuarterPicker 组件的使用

简单使用如下:

<template>
  <div>
    <quarter-picker
      width="150px"
      format="yyyy年q季度"
      value-format="yyyyMM"
      placeholder="选择季度"
      v-model="quarterDate"
      :disabled-date="disabledQuarter"
      @change="handleChangeQuarter" />
  </div>
</template>
`<script>
import QuarterPicker from '@/components/quarter-picker'
export default {
components: {
QuarterPicker
},
data() {
return {
quarterDate: '202104',
// 禁用日期/季度示例,只允许选择本年度本季度(不含)之前的季度
disabledQuarter: time => {
const now = new Date()
return time.getFullYear() > now.getFullYear() || time.getFullYear() === now.getFullYear() && parseInt(time.getMonth() / 3) >= parseInt(now.getMonth() / 3)
}
}
},
methods: {
handleChangeQuarter(date) {
console.info(date)
}
}
}
</script>`

本例子的最终效果图如下:

季度选择器2.png


友情提示:也可将组件进行全局注册,方便后续直接使用。


赞(0)
未经允许不得转载:工具盒子 » ElementUI 季度选择器 QuarterPicker