注:本控件需要依赖element-ui的控件和样式来使用
参考资源:vue + element-ui 季度选择器组件 el-quarter-picker-优快云博客,并在此基础上实现多选季度的功能
1.导入element-ui
import elementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
2.新建一个vue文件 ElQuarterPicker.vue,实现代码如下
<template>
<div class="el-quarter-picker" style="width: 100%;">
<el-popover
v-model="visible"
:disabled="!canPopover"
:tabindex="null"
placement="bottom-start"
transition="el-zoom-in-top"
trigger="click">
<div class="el-date-picker">
<div class="el-picker-panel__body">
<div class="el-date-picker__header el-date-picker__header--bordered" style="margin:0px; line-height:30px">
<button
type="button"
@click="clickLast"
aria-label="前一年"
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left"></button>
<span role="button" class="el-date-picker__header-label" @click="clickYear">{{ title }}</span>
<button
type="button"
@click="clickNext"
aria-label="后一年"
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right"></button>
</div>
<div class="el-picker-panel__content" style="margin:0px; width:100%">
<table class="el-month-table" style="">
<tbody>
<tr v-for="line in lineCount" :key="line">
<td v-for="index in (line * 4 <= viewList.length ? 4 : viewList.length - (line - 1) * 4)" :key="index" :class="{ today: viewList[(line - 1) * 4 + index - 1].current, current: viewList[(line - 1) * 4 + index - 1].active }">
<div :class="{'div_disabled': viewList[(line - 1) * 4 + index - 1].disabled}"><a class="cell" @click="clickItem(viewList[(line - 1) * 4 + index - 1])">{{ viewList[(line - 1) * 4 + index - 1].label }}</a></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<el-input
slot="reference"
@change="changeText"
@mouseenter.native="mouseEnter"
@mouseleave.native="mouseLeave"
:placeholder="placeholder"
v-model="text"
:size="size"
:readonly="!canEdit"
:disabled="disabled">
<i slot="prefix" class="el-input__icon el-icon-date"></i>
<i slot="suffix" class="el-input__icon el-icon-circle-close" v-show="showClear" style="cursor:pointer" @click.stop="clear"></i>
</el-input>
</el-popover>
</div>
</template>
<script>
export default {
name: 'ElQuarterPicker',
props: {
placeholder: {
type: String,
default: ''
},
size: {
type: String,
default: ''
},
readonly: {
type: Boolean,
default: false
},
clearable: {
type: Boolean,
default: true
},
editable: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
format: {
type: String,
default: 'yyyy年第Q季度'
},
valueFormat: {
type: String,
default: 'yyyy-qq'
},
value: {
type: Array,
default: []
},
multiple: { // 是否可多选
type: Boolean,
default: false
},
isHighLightCurYearQ: { //是否需要高亮显示当前的年和季度
type: Boolean,
default: false
}
},
model: {
prop: 'value',
event: 'change'
},
watch: {
value (val) {
// console.log('change-------', val)
this.changeValue(val)
},
readonly (val) {
this.canEdit = !val && this.editable
this.canPopover = !this.disabled && !val
},
editable (val) {
this.canEdit = !this.readonly && val
},
disabled (val) {
this.canPopover = !val && !this.readonly
}
},
data () {
return {
isHighLightCurYearQ: false, // 是否需要高亮显示当前的年和当前的季度
isDisabled: false,
visible: false,
showClear: false, // 控制清空按钮展示
canEdit: true, // 是否可编辑
canPopover: true, // 选择器弹出是否可用
text: '', // 文本框值
texts: [], // 所有选择的年度和季度,例2020-1,2020-2
viewType: 1, // 视图类型,1季度,2年度
viewYear: 0, // 当前年份
viewList: [], // 数据列表
lineCount: 0, // 数据行数
title: '', // 选择器标题
data: [0, 0], // 当前选择年度-季度
curYear: 0, //当前年
curQuarter: 0 //当前季度
}
},
mounted () {
// console.log('mounted--------', this.value)
this.changeValue(this.value)
// 设置文本框是否可编辑
this.canEdit = !this.readonly && this.editable
this.canPopover = !this.disabled && !this.readonly
},
destroyed () {
document.onkeydown = null
},
methods: {
//季度文本变更
changeText () {
const textSplits = this.text.split(',')
var nTexts = []
var allVerifySuccess = true
textSplits.map(item => {
if (! this.multiple && nTexts.length > 0) {
return
}
if (this.checkFormat(this.format, item)) {
this.formatFrom(item, this.format)
if (this.data[0] < 1
|| this.data[1] < 1
|| this.data[0] > this.curYear
|| (this.data[0] == this.curYear && this.data[1] > this.curQuarter)
) {
allVerifySuccess = false
} else {
const ct = this.formatTo([this.data[0], this.data[1]], this.valueFormat)
if (!nTexts.includes(ct)) {
this.viewYear = this.data[0]
nTexts.push(ct)
}
}
} else {
allVerifySuccess = false
}
})
if (allVerifySuccess) {
this.texts = nTexts.slice() // 记录新值
// this.$emit('change', this.formatTo(this.data, this.valueFormat))
this.$emit('change', this.texts)
} else { // 输入了无效的格式,还原回原来的值
this.text = ''
this.texts.map(item => {
this.formatFrom(item, this.valueFormat)
this.text += this.formatTo(this.data, this.format) + ','
})
if (this.text.indexOf(',') !== -1) { // 移除最后一个,
this.text = this.text.slice(0, -1)
}
this.viewYear = this.data[0]
}
},
// 鼠标进入
mouseEnter () {
if (!this.disabled && !this.readonly && this.clearable && this.text !== '') {
this.showClear = true
}
},
// 鼠标离开
mouseLeave () {
if (!this.disabled && this.clearable) {
this.showClear = false
}
},
// 清除季度
clear () {
this.showClear = false
this.visible = false
this.texts = []
this.$emit('change', this.texts)
},
// 季度值变更
changeValue (val) {
console.log(`changeValue>>>val:${JSON.stringify(val)}`)
this.viewType = 1
if (val && val.length > 0) {
// 反向格式化
this.text = ''
val.map(item => {
this.formatFrom(item, this.valueFormat)
this.text += this.formatTo(this.data, this.format) + ','
})
if (this.text.indexOf(',') !== -1) { // 移除最后一个,
this.text = this.text.slice(0, -1)
}
this.viewYear = this.data[0]
} else {
this.text = ''
this.texts = []
this.data = [0, 0]
this.viewYear = new Date().getFullYear()
}
this.initView()
},
// 初始化视图数据
initView () {
const list = []
const curDate = new Date()
this.curYear = curDate.getFullYear()
this.curQuarter = parseInt(curDate.getMonth() / 3) + 1
if (this.viewType === 1) {
let index = 0
for (const i of '一二三四') {
index++
const item = { label: '第' + i + '季度', year: this.viewYear, quarter: index, current: false, active: false, disabled: false }
if (this.viewYear === this.curYear && index === this.curQuarter && this.isHighLightCurYearQ) {
item.current = true
} else {
this.texts.map(it => {
this.formatFrom(it, this.valueFormat)
if (this.viewYear === +this.data[0] && index === +this.data[1]) {
item.active = true
}
})
}
if (this.viewYear > this.curYear) {
item.disabled = true
} else if (this.viewYear == this.curYear && index > this.curQuarter) {
item.disabled = true
}
list.push(item)
}
this.title = this.viewYear + ' 年'
} else {
const start = parseInt(this.viewYear / 10) * 10
this.viewYear = start
for (let i = 0; i < 10; i++) {
const year = start + i
const item = { label: year + '', year: year, current: false, active: false }
if (year === this.curYear && this.isHighLightCurYearQ) {
item.current = true
} else {
this.texts.map(it => {
this.formatFrom(it, this.valueFormat)
if (year === +this.data[0]) {
item.active = true
}
})
}
if (year > this.curYear) {
item.disabled = true
}
list.push(item)
}
this.title = start + ' 年 - ' + (start + 9) + ' 年'
}
this.viewList = list
this.lineCount = parseInt(list.length / 4)
if (list.length % 4 > 0) {
this.lineCount++
}
},
// 校验季度格式是否正确
checkFormat (pattern, val) {
// 格式转成正则表达式
let text = ''
for (const char of pattern) {
const dict = '\\^$.+?*[]{}!'
if (dict.indexOf(char) === -1) {
text += char
} else {
text += '\\' + char
}
}
text = text.replace('yyyy', '[1-9]\\d{3}')
text = text.replace('qq', '0[1-4]')
text = text.replace('q', '[1-4]')
text = text.replace('Q', '[一二三四]')
text = '^' + text + '$'
const patt = new RegExp(text)
return patt.test(val)
},
// 格式化季度到指定格式
formatTo (data, pattern) {
let text = pattern.replace('yyyy', '' + data[0])
text = text.replace('qq', '0' + data[1])
text = text.replace('q', '' + data[1])
text = text.replace('Q', '一二三四'.substr(data[1] - 1, 1))
return text
},
// 以指定格式解析季度
formatFrom (str, pattern) {
const year = this.findText(str, pattern, 'yyyy')
const quarter = this.findText(str, pattern, ['qq', 'q', 'Q'])
this.data = [year, quarter]
},
// 查找文本数值
findText (str, pattern, find) {
if (find instanceof Array) {
for (const f of find) {
const val = this.findText(str, pattern, f)
if (val !== -1) {
return val
}
}
return -1
}
const index = pattern.indexOf(find)
if (index === -1) {
return index
}
const val = str.substr(index, find.length)
if (find === 'Q') {
return '一二三四'.indexOf(val) + 1
} else {
return parseInt(val)
}
},
// 年份点击
clickYear () {
if (this.viewType !== 1) {
return
}
// 切换年度选择器
this.viewType = 2
this.initView()
},
// 季度选择
clickItem (item) {
// console.log('select--------', JSON.stringify(item))
if (this.viewType === 1) {
// 选择季度
this.visible = false
// this.$emit('change', this.formatTo([item.year, item.quarter], this.valueFormat))
const dataTemp = this.formatTo([item.year, item.quarter], this.valueFormat)
if (! this.multiple) {
this.texts = []
}
// 判断是否已经包含
if (!this.texts.includes(dataTemp)) {
this.viewYear = item.year
this.texts.push(dataTemp)
}
this.$emit('change', this.texts)
} else {
// 选择年度
this.viewType = 1
this.viewYear = item.year
this.initView()
}
},
// 上一年
clickLast () {
if (this.viewYear > 1000) {
if (this.viewType === 1) {
this.viewYear--
this.initView()
} else {
this.viewYear = this.viewYear - 10
this.initView()
}
}
},
// 下一年
clickNext () {
if (this.viewYear < 9999) {
if (this.viewType === 1) {
this.viewYear++
this.initView()
} else {
this.viewYear = this.viewYear + 10
this.initView()
}
}
}
}
}
</script>
<style>
.el-quarter-picker {
width: 220px;
display: inline-block;
}
.div_disabled {
opacity: 0.6;
pointer-events: none; /* 阻止鼠标事件 */
cursor: not-allowed; /* 改变鼠标指针样式 */
}
</style>
3.在需要使用季度选择器的页面中导入后使用,使用方式如下:
<el-quarter-picker v-model="quarters" placeholder="请选择季度" multiple isHighLightCurYearQ/>
其中,multiple参数表示是否需要多选季度,isHighLightCurYearQ参数表示是否需要高亮显示当前的年份和季度,返回值为选择的季度值的数组