elementUI 不支持年份范围的选择器,依照下面的文章进行修改和完善 el-year-picker;
element日期选择范围、选择年份范围_elemet 两个日期 选择的年份范围必须在三年之内-****博客
el-year-picker 组件:
依赖包:moment
属性:
- sp:默认 ‘至’
- value:默认 [ ]
- size:默认 large,支持 small、mini
- clearable:默认 true
- startPlaceholder:默认 开始年份
-
endPlaceholder:默认 结束年份
事件:
- input:参数 value[ ]
效果图
代码
index.vue
<template>
<el-popover
ref="popover"
placement="bottom"
v-model="showPanel"
popper-class="custom_year_range"
trigger="manual"
v-clickoutside="() => { showPanel = false }"
>
<div class="_inner floatPanel">
<div class="_inner leftPanel">
<div class="_inner panelHead">
<i class="_inner el-icon-d-arrow-left" @click="onClickLeft"></i>
<span>{{ leftYearList[0] + '年 ' + '- ' + leftYearList[9] + '年' }}</span>
</div>
<div class="_inner panelContent">
<div
:class="{
oneSelected: item === startYear && oneSelected,
startSelected: item === startYear,
endSelected: item === endYear,
betweenSelected: item > startYear && item < endYear,
}"
v-for="item in leftYearList"
:key="item"
>
<a
:class="{
cell: true,
_inner: true,
selected: item === startYear || item === endYear,
}"
@click="onClickItem(item)"
@mouseover="onHoverItem(item)"
>{{ item }}</a>
</div>
</div>
</div>
<div class="_inner rightPanel">
<div class="_inner panelHead">
<i class="_inner el-icon-d-arrow-right" @click="onClickRight"></i>
<span>{{ rightYearList[0] + '年 ' + '- ' + rightYearList[9] + '年' }}</span>
</div>
<div class="_inner panelContent">
<div
:class="{
startSelected: item === startYear,
endSelected: item === endYear,
betweenSelected: item > startYear && item < endYear,
}"
v-for="item in rightYearList"
:key="item"
>
<a
:class="{
cell: true,
_inner: true,
selected: item === endYear || item === startYear,
}"
@click="onClickItem(item)"
@mouseover="onHoverItem(item)"
>{{ item }}</a>
</div>
</div>
</div>
</div>
<div slot="reference">
<div
ref="yearPicker"
style="width: 100%"
class="el-date-editor el-range-editor el-input__inner el-date-editor--daterange yearPicker"
:class="['el-range-editor--' + size, showPanel ? 'is-active' : '', startShowYear ? 'is-val' : '']"
@mouseover="handleHover(true)"
@mouseleave="handleHover(false)"
>
<i class="el-input__icon el-range__icon el-icon-date"></i>
<input
class="_inner range_input"
ref="inputLeft"
type="text"
name="yearInput"
:placeholder="startPlaceholder"
v-model="startShowYear"
@focus="onFocus"
@keyup="handleInput('start')"
/>
<span class="el-range-separator">{{ sp }}</span>
<input
class="_inner range_input"
ref="inputRight"
type="text"
name="yearInput"
:placeholder="endPlaceholder"
v-model="endShowYear"
@focus="onFocus"
@keyup="handleInput('end')"
/>
<i
class="el-input__icon el-range__close-icon"
:class="[startShowYear && isHover && clearable ? 'el-icon-circle-close' : '']"
@click="onClear"
></i>
</div>
</div>
</el-popover>
</template>
<script>
import moment from 'moment'
import { clickoutside, SELECT_STATE } from './utils.js'
export default {
name: 'ElYearPicker',
directives: { clickoutside },
computed: {
oneSelected () {
return this.curState === SELECT_STATE.selecting && (this.startYear === this.endYear || this.endYear == null)
},
leftYearList () {
return this.yearList.slice(0, 10)
},
rightYearList () {
return this.yearList.slice(10, 20)
}
},
props: {
sp: {
default: '至'
},
value: {
type: Array,
default: [],
},
size: {
type: String,
default: 'large'
},
clearable: {
type: Boolean,
default: true
},
startPlaceholder: {
type: String,
default: '开始年份'
},
endPlaceholder: {
type: String,
default: '结束年份'
}
},
data () {
return {
itemBg: {},
startShowYear: null,
endShowYear: null,
yearList: [],
showPanel: false,
startYear: null,
endYear: null,
curYear: 0,
curSelectedYear: 0,
curState: SELECT_STATE.unselect,
isHover: false
}
},
methods: {
handleInput (type) {
switch (type) {
case 'start':
if (isNaN(this.startShowYear)) {
this.startShowYear = this.startYear
return
}
this.startYear = this.startShowYear * 1
break
case 'end':
if (isNaN(this.endShowYear)) {
this.endShowYear = this.endYear
return
}
this.endYear = this.endShowYear * 1
break
}
[this.startYear, this.endYear] = [this.endYear, this.startYear]
this.startShowYear = this.startYear
this.endShowYear = this.endYear
},
onHoverItem (iYear) {
if (this.curState === SELECT_STATE.selecting) {
const tmpStart = this.curSelectedYear
this.endYear = Math.max(tmpStart, iYear)
this.startYear = Math.min(tmpStart, iYear)
}
},
async onClickItem (selectYear) {
if (
this.curState === SELECT_STATE.unselect ||
this.curState === SELECT_STATE.selected
) {
this.startYear = selectYear
this.curSelectedYear = selectYear
this.endYear = null
this.curState = SELECT_STATE.selecting
} else if (this.curState === SELECT_STATE.selecting) {
this.endShowYear = this.endYear || this.startYear
this.startShowYear = this.startYear
this.curState = SELECT_STATE.selected
await this.$nextTick()
this.showPanel = false
this.$parent.$parent.$parent.$parent.$parent.clearValidate()
}
},
async onFocus () {
await this.$nextTick()
this.showPanel = true
},
handleHover (flag) {
this.isHover = flag
},
updateYearList () {
const startYear = ~~(this.curYear / 10) * 10
console.log(startYear, this.curYear, 'this.curYearthis.curYearthis.curYear')
this.yearList = []
for (let index = 0; index < 20; index++) {
this.yearList.push(startYear + index)
}
},
onClickLeft () {
this.curYear = this.curYear * 1 - 10
this.updateYearList()
},
onClickRight () {
this.curYear = this.curYear * 1 + 10
this.updateYearList()
},
onClear () {
if (this.startShowYear && this.isHover && this.clearable) {
this.startYear = null
this.endYear = null
this.curSelectedYear = 0
this.curState = SELECT_STATE.unselect
this.showPanel = false;
this.curYear = moment().format('yyyy')
this.updateYearList()
this.startShowYear = ''
this.endShowYear = ''
this.$emit('input', [])
}
}
},
watch: {
value: {
handler (val) {
if (val.length == 0) {
this.startShowYear = ''
this.endShowYear = ''
} else {
const [first, end] = val || []
this.startShowYear = val[0]
this.endShowYear = val[1]
}
},
immediate: true,
deep: true
},
startShowYear: {
handler (val) {
this.$emit('input', [val, this.endShowYear || ''])
},
immediate: true,
deep: true
},
endShowYear: {
handler (val) {
this.$emit('input', [this.startShowYear || '', val])
},
immediate: true,
deep: true
}
},
created () {
console.log('value', this.value)
const [startYear, endYear] = this.value || []
if (startYear) {
this.startYear = Number(startYear)
this.endYear = Number(endYear)
this.curState = SELECT_STATE.selected
this.curYear = startYear
} else {
this.curYear = moment().format('yyyy')
}
this.updateYearList()
},
mounted () {
window.Vue = this
}
}
</script>
<style lang="scss">
.custom_year_range {
border-radius: 10px;
.floatPanel {
> div {
width: 50%;
}
padding: 0 16px;
// position: absolute;
display: flex;
background-color: #fff;
z-index: 2000;
border-radius: 4px;
width: 650px;
height: 250px;
top: 40px;
left: -50px;
.panelContent {
display: flex;
flex-wrap: wrap;
width: 100%;
height: calc(100% - 70px);
.oneSelected {
border-top-right-radius: 24px;
border-bottom-right-radius: 24px;
}
.startSelected {
background-color: #f2f6fc;
border-top-left-radius: 24px;
border-bottom-left-radius: 24px;
}
.endSelected {
background-color: #f2f6fc;
border-top-right-radius: 24px;
border-bottom-right-radius: 24px;
}
.betweenSelected {
background-color: #f2f6fc;
}
> div {
width: 75px;
height: 48px;
line-height: 48px;
margin: 3px 0;
// border-radius: 24px;
text-align: center;
a {
display: inline-block;
width: 60px;
height: 36px;
cursor: pointer;
line-height: 36px;
border-radius: 18px;
&:hover {
color: #409eff;
}
}
.selected {
background-color: #409eff;
color: #fff;
&:hover {
color: #fff !important;
}
}
}
}
.panelHead {
position: relative;
height: 46px;
line-height: 46px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
span {
font-size: 16px;
font-weight: 500;
padding: 0 5px;
line-height: 22px;
text-align: center;
cursor: pointer;
color: #606266;
&:hover {
color: #409eff;
}
}
i {
position: absolute;
cursor: pointer;
&:hover {
color: #3e77fc;
}
}
}
.rightPanel {
padding-left: 8px;
}
.leftPanel .panelHead i {
left: 20px;
}
.rightPanel .panelHead i {
right: 20px;
}
}
.floatPanel::before {
content: '';
height: 100%;
top: 0;
position: absolute;
left: 50%;
width: 1px;
border-left: 1px solid #e4e4e4;
}
.cell._inner {
color: #606266;
text-decoration: none;
}
}
</style>
<style lang="scss" scoped>
.range_input {
appearance: none;
border: none;
outline: 0;
padding: 0;
width: 130px;
color: #606266;
line-height: 1;
height: 100%;
margin: 0;
text-align: center;
display: inline-block;
}
.yearPicker {
.el-icon-circle-close {
color: #c0c4cc;
&:hover {
color: #909399;
}
}
&.is-val {
}
&.is-active {
border-color: #0052d9 !important;
}
}
input {
width: 60px;
border: none;
height: 32px;
line-height: 32px;
box-sizing: border-box;
background-color: transparent;
}
input:focus {
outline: none;
background-color: transparent;
}
.dateIcon {
position: absolute;
right: 16px;
top: 9px;
color: #adb2bc;
}
</style>
utils.js
export const clickoutside = {
bind(el, binding, vnode) {
function documentHandler(e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false
}
// 判断指令中是否绑定了函数
if (binding && binding.expression) {
// 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
if (binding.value && binding.value(e)) {
binding.value(e)
}
}
}
// 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
el.__vueClickOutside__ = documentHandler
document.addEventListener('click', documentHandler)
},
unbind(el, binding) {
// 解除事件监听
document.removeEventListener('click', el.__vueClickOutside__)
delete el.__vueClickOutside__
}
}
export const SELECT_STATE = {
unselect: 0,
selecting: 1,
selected: 2
}