引言
需求如文章标题,查阅资料发现
element-ui
并没有做支持这样类型的时间选择器的打算,所以只能手动造轮子。
组件经过一次改版,之前无法进行默认值的设置,后来改造之后,采用双向数据绑定驱动
组件 => 页面;页面 => 组件
的方式。
具备功能:默认时间、时间类型(可选)、是否区间(可选)。
正文
- 演示效果
账期 - 季度或者半年类型的时间选择组件 - 区间.gif
账期 - 季度或者半年类型的时间选择组件 - 非区间.gif
- 源码
- 组件
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
是源码中这样定义的,这个可以根据业务需求自行更改。
网友评论