1:iview的季度组件 是一个博客那里找到的一起学习
原文章changeYear有点小问题,我修复了下,可选择季度和左右切换年份
<template>
<div v-clickoutside="closePicker" style="width: 100px; display: inline-block">
<Input v-model="message" :icon="arrowType" readonly
@on-focus="show"
@on-click="clickIcon"
@mouseenter.native="handleInputMouseenter"
@mouseleave.native="handleInputMouseleave"></Input>
<div v-show="visible" class="pickerCard">
<div class="pickHeader">
<span class="pickerIconBtn leftIcon ivu-date-picker-prev-btn-arrow-double" @click="changeYear('last')">
<Icon type="ios-arrow-back"></Icon>
</span>
<span>
{{year}}年
</span>
<span class="pickerIconBtn rightIcon ivu-date-picker-next-btn-arrow-double" @click="changeYear('next')">
<Icon type="ios-arrow-forward"></Icon>
</span>
</div>
<div class="pickQuarterCard">
<span v-for="item in dataList" :key="item.value" :class="item.disable ?'disableSpan': 'defaultSpan'" @click="handleClick(item)">
<em :class="!item.disable && quarter===item.value ? 'selected': 'defaultEm'">{{item.label}}</em>
</span>
</div>
</div>
</div>
</template>
<script>
const clickoutside = {
// 初始化指令
bind (el, binding, vnode) {
function documentHandler (e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false;
}
// 判断指令中是否绑定了函数
if (binding.expression) {
// 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
binding.value(e);
}
}
// 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
el.__vueClickOutside__ = documentHandler;
document.addEventListener('click', documentHandler);
},
unbind (el, binding) {
// 解除事件监听
document.removeEventListener('click', el.__vueClickOutside__);
delete el.__vueClickOutside__;
}
};
export default {
name: 'quarter-picker',
props: {
options: { // 不可选日期
type: Object,
default: () => ({})
},
clearable: {
type: Boolean,
default: true
},
value: {
type: [String],
default: ''
}
},
data () {
return {
visible: false,
message: null,
year: null,
quarter: null,
currentYear: null,
currentQuarter: null,
dataList: [
{label: 'Q1', value: 1, disable: false},
{label: 'Q2', value: 2, disable: false},
{label: 'Q3', value: 3, disable: false},
{label: 'Q4', value: 4, disable: false}
],
showClose: false
};
},
computed: {
arrowType () {
return this.showClose ? 'ios-close-circle' : 'ios-calendar-outline';
}
},
directives: { clickoutside },
watch: {
value () {
if (this.value !== this.message) {
this.initData();
}
}
},
created () {
this.currentYear = new Date().getFullYear();
const currentMonth = new Date().getMonth() + 1;
if (currentMonth < 4) {
this.currentQuarter = 1;
} else if (currentMonth < 7) {
this.currentQuarter = 2;
} else if (currentMonth < 10) {
this.currentQuarter = 3;
} else {
this.currentQuarter = 4;
}
this.initData();
},
methods: {
initData () {
if (this.value) {
const arr = this.value.split('Q');
this.year = arr[0];
this.quarter = Number(arr[1]);
this.message = this.value;
} else {
this.year = this.currentYear;
this.quarter = this.currentQuarter;
this.message = '';
}
},
show () {
this.handleData();
this.visible = true;
},
handleInputMouseenter () {
this.showClose = this.message && this.clearable;
},
handleInputMouseleave () {
this.showClose = false;
},
clickIcon () {
if (this.showClose) {
this.quarter = null;
this.message = null;
this.$emit('on-change', this.message, this.year, null);
}
},
changeYear (type) {
if (type === 'last') {
this.year--;
} else {
this.year++;
}
let val = this.message.split('Q')
if(this.message){
if(this.year === Number(val[0])){
this.quarter = Number(val[1])
} else {
this.quarter = null
}
}
this.handleData();
},
handleData () {
if (this.options && this.options.disabledDate) {
this.dataList.forEach(item => {
item.disable = this.options.disabledDate(this.year, item.value);
});
}
},
handleClick (item) {
if (item.disable) return;
this.quarter = item.value;
this.message = this.year + 'Q' + item.value;
this.$emit('on-change', this.message, this.year, item.value);
this.closePicker();
},
closePicker () {
this.visible = false;
}
},
};
</script>
<style scoped lang="less">
.pickerCard{
width: 200px;
font-size: 12px;
z-index: 100;
background: #fff;
border-radius: 0.3rem;
position:absolute;
margin: 5px 0;
box-shadow: 0 1px 6px rgba(0,0,0,.2);
.pickHeader{
height: 32px;
line-height: 32px;
text-align: center;
border-bottom: 1px solid #e8eaec;
.leftIcon{
float: left;
}
.rightIcon{
float: right;
}
.pickerIconBtn{
display: inline-block;
width: 20px;
height: 24px;
line-height: 26px;
margin-top: 2px;
text-align: center;
cursor: pointer;
color: #c5c8ce;
transition: color .2s ease-in-out;
}
}
.pickQuarterCard{
margin: 10px 8px;
white-space: normal;
span {
//width: 60px;
height: 28px;
line-height: 28px;
margin: 10px 12px;
border-radius: 3px;
cursor: pointer;
display: inline-block;
em{
padding: 0 10px;
height: 28px;
line-height: 28px;
display: inline-block;
margin: 0;
font-style: normal;
border-radius: 3px;
text-align: center;
transition: all .2s ease-in-out;
}
}
.disableSpan{
cursor: not-allowed;
color: #c5c8ce;
background: #f7f7f7;
}
.defaultSpan:hover .defaultEm {
background: #e1f0fe;
}
.defaultSpan .selected{
background: #2d8cf0;
color: #fff;
}
}
}
</style>
2:elementUi 可选择季度和年份,左右切换年份
<template>
<div class="el-quarter-picker">
<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><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: String,
default: ''
}
},
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 {
visible: false,
showClear: false, // 控制清空按钮展示
canEdit: true, // 是否可编辑
canPopover: true, // 选择器弹出是否可用
text: '', // 文本框值
viewType: 1, // 视图类型,1季度,2年度
viewYear: 0, // 当前年份
viewList: [], // 数据列表
lineCount: 0, // 数据行数
title: '', // 选择器标题
data: [0, 0] // 当前选择年度-季度
}
},
mounted() {
// console.log('mounted--------', this.value)
this.changeValue(this.value)
// 设置文本框是否可编辑
this.canEdit = !this.readonly && this.editable
this.canPopover = !this.disabled && !this.readonly
// 监听按键(上下左右键可以切换季度)
document.onkeydown = (event) => {
if (this.visible) {
const data = [this.data[0], this.data[1]]
if (data[0] < 1 || data[1] < 1) {
// 以当前季度为标准
const curDate = new Date()
data[0] = curDate.getFullYear()
data[1] = parseInt(curDate.getMonth() / 3) + 1
}
if (event.code === 'ArrowLeft') {
// 上一个季度
if (data[1] === 1) {
data[0] = data[0] - 1
data[1] = 4
} else {
data[1] = data[1] - 1
}
} else if (event.code === 'ArrowRight') {
// 下一个季度
if (data[1] === 4) {
data[0] = data[0] + 1
data[1] = 1
} else {
data[1] = data[1] + 1
}
} else if (event.code === 'ArrowUp') {
// 上一年季度
data[0] = data[0] - 1
} else if (event.code === 'ArrowDown') {
// 下一年季度
data[0] = data[0] + 1
} else {
return
}
// 超过年限的不处理
if (data[0] < 1000 || data[0] > 9999) {
return
}
this.data = data
this.viewType = 1
this.viewYear = data[0]
this.$emit('change', this.formatTo(data, this.valueFormat))
}
}
},
destroyed() {
document.onkeydown = null
},
methods: {
// 季度文本变更
changeText() {
if (this.checkFormat(this.format, this.text)) {
// 设置值
this.formatFrom(this.text, this.format)
this.$emit('change', this.formatTo(this.data, this.valueFormat))
} else {
// 输入了无效的格式,还原回原来的值
if (this.data[0] < 1 || this.data[1] < 1) {
this.text = ''
} else {
this.text = this.formatTo(this.data, this.format)
}
}
this.visible = false
},
// 鼠标进入
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.$emit('change', '')
},
// 季度值变更
changeValue(val) {
this.viewType = 1
if (val) {
// 反向格式化
this.formatFrom(val, this.valueFormat)
this.text = this.formatTo(this.data, this.format)
this.viewYear = this.data[0]
} else {
this.text = ''
this.data = [0, 0]
this.viewYear = new Date().getFullYear()
}
this.initView()
},
// 初始化视图数据
initView() {
const list = []
const curDate = new Date()
const curYear = curDate.getFullYear()
const 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 }
if (this.viewYear === curYear && index === curQuarter) {
item.current = true
} else if (this.viewYear === this.data[0] && index === this.data[1]) {
item.active = 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 === curYear) {
item.current = true
} else if (year === this.data[0]) {
item.active = 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--------', item)
if (this.viewType === 1) {
// 选择季度
this.$emit('change', this.formatTo([item.year, item.quarter], this.valueFormat))
this.visible = false
} 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;
}
</style>
因为公司用的是iview框架,测试的时候发现没办法显示可选年份,后面自己结合这两篇改造了下,代码如下:因为公司需求要第一次进来就要反显当前年季度所以用了init直接输出
this.$emit('quarterTodayDate', this.message)这个方法获取年季度值
<template>
<div>
<Input v-model="message" :icon="arrowType" readonly
@on-focus="show"
@click="clickIcon"
@mouseenter.native="handleInputMouseenter"
@mouseleave.native="handleInputMouseleave"></Input>
<div v-show="visible" class="pickerCard">
<div class="pickHeader">
<span class="pickerIconBtn leftIcon ivu-date-picker-prev-btn-arrow-double" @click="changeYear('last')">
<Icon type="ios-arrow-back"></Icon>
</span>
<span @click="clickYear">
{{year}}年
</span>
<span class="pickerIconBtn rightIcon ivu-date-picker-next-btn-arrow-double" @click="changeYear('next')">
<Icon type="ios-arrow-forward"></Icon>
</span>
</div>
<div class="pickQuarterCard">
<Row v-for="i in Math.ceil(dataList.length / 4)" :key="i">
<Col
span="6"
v-for="(item, index) in dataList.slice(4 * (i - 1), 4 * (i - 1) + 4)"
:key="i + '-' + index"
:class="item.disable ?'disableSpan': 'defaultSpan'"
>
<span @click="handleClick(item)" :class="!item.disable && quarter===item.value ? 'selected': 'defaultEm'">{{ item.label }}</span>
</Col>
</Row>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'quarter-picker',
props: {
options: { // 不可选日期
type: Object,
default: () => ({})
},
clearable: {
type: Boolean,
default: false
},
value: {
type: [String],
default: ''
}
},
data () {
return {
visible: false,
message: null,
year: null,
quarter: null,
currentQuarter: null,
quarterList: [
{label: 'Q1', value: 1, disable: false},
{label: 'Q2', value: 2, disable: false},
{label: 'Q3', value: 3, disable: false},
{label: 'Q4', value: 4, disable: false}
],
showClose: false,
dataList: [],
viewType: 1, // 视图类型,1季度,2年度
viewYear: 0,
data: [0, 0] // 当前选择年度-季度
};
},
computed: {
arrowType () {
return this.showClose ? 'ios-close-circle' : 'ios-calendar-outline';
}
},
watch: {
value () {
if (this.value !== this.message) {
this.initData();
}
}
},
created () {
this.viewYear = new Date().getFullYear()
this.currentYear = new Date().getFullYear()
const currentMonth = new Date().getMonth() + 1;
if (currentMonth < 4) {
this.currentQuarter = 1;
} else if (currentMonth < 7) {
this.currentQuarter = 2;
} else if (currentMonth < 10) {
this.currentQuarter = 3;
} else {
this.currentQuarter = 4;
}
this.init()
},
mounted() {
this.viewType = 1
},
methods: {
init () {
this.year = this.currentYear;
this.quarter = this.currentQuarter;
this.message = this.viewYear + 'Q' + this.currentQuarter
this.$emit('quarterTodayDate', this.message)
this.dataList = this.quarterList
},
initData () {
const list = []
const arr = this.value.split('Q');
if (this.viewType === 1) {
if(Number(arr[0]) === this.year){
this.year = arr[0];
this.quarter = Number(arr[1]);
} else {
this.quarter = null
}
this.dataList = this.quarterList
} else {
const start = parseInt(this.viewYear / 10) * 10
for (let i = 0; i < 10; i++) {
const year = start + i
const item = { label: year + '', value: year }
// 当前选中年
if (year === Number(arr[0])) {
this.quarter = Number(arr[0])
}
list.push(item)
}
this.year = start + '年 - ' + (start + 9)
this.dataList = list
}
},
show () {
this.handleData();
this.visible = true;
},
handleInputMouseenter () {
this.showClose = this.message && this.clearable;
},
handleInputMouseleave () {
this.showClose = false;
},
clickIcon () {
if (this.showClose) {
this.quarter = null;
this.message = null;
this.$emit('on-change', this.message, this.year, null);
}
},
// 上,下一年
changeYear (type) {
if (this.viewType === 1) {
if (type === 'last') {
this.year--;
} else {
this.year++;
}
let val = this.message.split('Q')
if(this.message){
if(this.year === Number(val[0])){
this.quarter = Number(val[1])
} else {
this.quarter = null
}
}
this.handleData();
} else {
if (type === 'last') {
this.viewYear = this.viewYear - 10
} else {
this.viewYear = this.viewYear + 10
}
this.initData()
}
},
handleData () {
if (this.options && this.options.disabledDate) {
this.dataList.forEach(item => {
item.disable = this.options.disabledDate(this.year, item.value);
});
}
},
handleClick (item) {
if (this.viewType === 1) {
// 选择季度
if (item.disable) return
this.quarter = item.value
this.message = this.year + 'Q' + item.value
this.closePicker()
this.$emit('on-change', this.message, this.year, item.value)
this.$emit('quarterTodayDate', this.year + 'Q' +this.quarter,'handleClick')
} else {
// 选择年度
this.viewType = 1
this.year = item.value
this.initData()
}
},
closePicker () {
this.visible = false;
},
// 年份点击
clickYear() {
if (this.viewType !== 1) {
return
}
// 切换年度选择器
this.viewType = 2
this.initData()
},
},
};
</script>
效果如下