前端关于年区间的时间选择器封装

# 前端关于年区间的时间选择器封装

关于产品提出的年区间的选择,一开始在element Plus找了好半天也没有发现年区间的选择器,甚至为此我还自创了个类型 type='yearrange',然而并没有什么卵用,既然轮子没有那就自己造,废话不多说了,直接上代码

这是组件代码

js 复制代码
<template>
    <div class="yearPicker" :ref="yearPicker">
 
        <input class="_inner" :ref="inputLeft" style="width: 100%;padding-right: 30px;" v-model="data.startShowYear" @focus="onFocus" @click="clickInput" type="text"
            name="yearInput" @input="checkStartInput()" placeholder="选择年份" />
        <span>{{ props.sp }}</span>
        <input class="_inner" style="width: 100%;" :ref="inputRight" v-model="data.endShowYear" @focus="onFocus" @click="clickInput" type="text"
            name="yearInput" @input="checkEndInput()" placeholder="选择年份" />


           <div >

            <el-icon @click="clearForm" class="icon" v-if="data.startShowYear&&data.endShowYear"><CircleClose /></el-icon>
           </div> 
        <div class="_inner floatPanel" v-if="data.showPanel">
            <div class="_inner leftPanel">
                <div class="_inner panelHead">
                    <i class="_inner" @click="onClickLeft">&lt;&lt;</i>
                    {{ leftYearList[0] + "-" + leftYearList[9] }}
                </div>
                <div class="_inner panelContent">
                    <div :class="{
                        disabled: checkValidYear(item),
                        oneSelected: item === data.startYear && oneSelected,
                        startSelected: item === data.startYear,
                        endSelected: item === data.endYear,
                        _inner: true,
                        betweenSelected: item > data.startYear && item < data.endYear,
                    }" v-for="item in leftYearList" :key="item">
                        <a :class="{
                            cell: true,
                            _inner: true,
                            selected: item === data.startYear || item === data.endYear,
                        }" @click="onClickItem(item)" @mouseover="onHoverItem(item)">
                            {{ item }}
                        </a>
                    </div>
                </div>
            </div>
            <div class="_inner rightPanel">
                <div class="_inner panelHead">
                    <i class="_inner" @click="onClickRight">&gt;&gt;</i>
                    {{ rightYearList[0] + "-" + rightYearList[9] }}
                </div>
                <div class="_inner panelContent">
                    <div :class="{
                        disabled: checkValidYear(item),
                        startSelected: item === data.startYear,
                        endSelected: item === data.endYear,
                        betweenSelected: item > data.startYear && item < data.endYear,
                    }" v-for="item in rightYearList" :key="item">
                        <a :class="{
                            cell: true,
                            _inner: true,
                            selected: item === data.endYear || item === data.startYear,
                        }" @click="onClickItem(item)" @mouseover="onHoverItem(item)">
                            {{ item }}
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
   
<script lang="ts" setup>
import { VNodeRef, computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref } from "vue";
 
interface Emits {
    (e: "updateTimeRange", startYear: string, endYear: string): void;
}
interface Props { labelWidth: number, labelText: string, sp?: string, initYear?: {startYear:number, endYear:number} }
const SELECT_STATE = {
    unselect: 0,
    selecting: 1,
    selected: 2,
};
 
const data = reactive<any>({
    itemBg: {},
    startShowYear: null,
    endShowYear: null,
    yearList: [],
    showPanel: false,
    startYear: null,
    endYear: null,
    curYear: 0,
    curSelectedYear: 0,
    curState: SELECT_STATE.unselect,
})

const yearPicker = ref<VNodeRef>()
const inputLeft = ref<VNodeRef>()
const inputRight = ref<VNodeRef>()
 
const oneSelected = computed(() => {
    return (
        data.curState === SELECT_STATE.selecting &&
        (data.startYear === data.endYear || data.endYear == null)
    );
})
//清除表单数据
const clearForm=()=>{
    data.startYear = null;
    data.endYear =null;
    data.startShowYear = null;
    data.endShowYear = null;
    emits("updateTimeRange",
            data.startYear,
            data.endYear,
        );
}

const leftYearList = computed(() => {
    return data.yearList.slice(0, 10);
})
const rightYearList = computed(() => {
    return data.yearList.slice(10, 20);
})
 
const emits = defineEmits<Emits>()
 
const props = withDefaults(defineProps<Props>(), {
    labelWidth: 80,
    // labelText: "时间标签",
    sp: "至",
})
 
 
const clickInput = (e:any) => {
    e.stopPropagation();
    return false;
}
 
const checkValidYear = (iYear:number) => {
    if (props.initYear) {
        if (iYear > props.initYear.endYear) {
            return 1
        } else if (iYear < props.initYear.startYear) {
            return -1
        }
    }
    return 0
}
const checkStartInput = () => {
    if (isNaN(data.startShowYear)) {
        data.startShowYear = data.startYear;
    } else {
        data.startYear = data.startShowYear * 1;
    }
}
 
const checkEndInput = () => {
    if (isNaN(data.endShowYear)) {
        data.endShowYear = data.endYear;
    } else {
        data.endYear = data.endShowYear * 1;
    }
}
const changeYear = () => {
    if (data.startYear > data.endYear) {
        let tmp = data.endYear;
        data.endYear = data.startYear;
        data.startYear = tmp;
 
    }
    if (props.initYear) {
        data.startYear = Math.max(data.startYear, props.initYear.startYear)
        data.endYear = Math.min(data.endYear, props.initYear.endYear)
    }
    data.startShowYear = data.startYear;
    data.endShowYear = data.endYear;
 
    if (data.startYear && data.endYear) {
        emits("updateTimeRange",
            data.startYear,
            data.endYear,
        );
    } else {
        console.warn("WARN:年份不合法", data.startYear, data.endYear);
    }
}
const onHoverItem = (iYear:number) => {
    if (checkValidYear(iYear) != 0) {
        return;
    }
    if (data.curState === SELECT_STATE.selecting) {
        let tmpStart = data.curSelectedYear;
        data.endYear = Math.max(tmpStart, iYear);
        data.startYear = Math.min(tmpStart, iYear);
    }
}
const onClickItem = (iYear:number) => {
    if (checkValidYear(iYear) != 0) {
        return;
    }
    if (
        data.curState === SELECT_STATE.unselect ||
        data.curState === SELECT_STATE.selected
    ) {
        data.startYear = iYear;
        data.curSelectedYear = iYear;
        data.endYear = null;
        data.curState = SELECT_STATE.selecting;
    } else if (data.curState === SELECT_STATE.selecting) {
        data.endShowYear = data.endYear;
        data.startShowYear = data.startYear;
        data.curState = SELECT_STATE.selected;
        emits("updateTimeRange",
            data.startYear,
            data.endYear,
        );
 
        setTimeout(() => {
            //为动画留的时间,可优化
            data.showPanel = false;
        }, 300);
    }
}
const onFocus = () => {
    nextTick(() => {
        data.showPanel = true;
    });
}
 
const updateYearList = () => {
    let iStart = Math.floor(data.curYear / 10) * 10 - 10;
    iStart = iStart < 0 ? 0 : iStart;
    data.yearList = [];
    for (let index = 0; index < 20; index++) {
        data.yearList.push(iStart + index);
    }
}
const closePanel = (e:any) => {
    if (!data.showPanel) {
        return;
    }
    if (typeof e.target.className !== "string" || e.target.className === "") {
        nextTick(() => {
            changeYear()
            data.showPanel = false;
        });
        return;
    }
 
    if (
        e.target.className.indexOf("_inner") === -1 ||
        (e.target.name === "yearInput" &&
            e.target !== inputLeft.value &&
            e.target !== inputRight.value)
    ) {
        nextTick(() => {
            changeYear()
            data.showPanel = false;
        });
    }
 
    e.stopPropagation();
    return false;
}
const onClickLeft = () => {
    data.curYear = data.curYear * 1 - 10;
    updateYearList();
 
 
}
const onClickRight = () => {
    data.curYear = data.curYear * 1 + 10;
    updateYearList();
}
 
onBeforeMount(() => {
    data.curYear = new Date().getFullYear();
    updateYearList();
})
onBeforeUnmount(() => {
    document.removeEventListener("click", closePanel.bind(data));
})
 
onMounted(() => {
    document.addEventListener("click", closePanel.bind(data));
}) 
</script>
<style lang="scss" scoped>


.icon{
    color: #adb2bc;
}
.yearPicker {
    font-size: 14px;
    display: flex;
    position: relative;
    transition: all 0.3s;
    width: 100%;
    input:first-child {
        text-align: right;
    }
 
    background-color: #fff;
 
    span {
        padding: 0 8px;
        height: 32px;
        line-height: 32px;
    }
 
    border: 1px solid #eff1f3;
    height: 34px;
    line-height: 34px;
    border-radius: 4px;
    padding: 0 8px;
    box-sizing: border-box;
 
    .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;
        box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
 
        .panelContent {
            display: flex;
            flex-wrap: wrap;
            width: 100%;
            height: calc(100% - 70px);
 
            .disabled {
                color: #ccc;
            }
 
            .oneSelected {
                border-top-right-radius: 24px;
                border-bottom-right-radius: 24px;
            }
 
            .startSelected {
                background-color: #f6f6f7;
                border-top-left-radius: 24px;
                border-bottom-left-radius: 24px;
            }
 
            .endSelected {
                background-color: #f6f6f7;
                border-top-right-radius: 24px;
                border-bottom-right-radius: 24px;
            }
 
            .betweenSelected {
                background-color: #f6f6f7;
            }
 
            >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;
                }
 
                .selected {
                    background-color: #3e77fc;
                    color: #fff;
                }
            }
        }
 
        .panelHead {
            position: relative;
            height: 46px;
            line-height: 46px;
            text-align: center;
 
            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%;
        position: absolute;
        left: 50%;
        width: 1px;
        border-left: 1px solid #e4e4e4;
    }
}
 
input {
    width: 60px;
    border: none;
    height: 32px;
    text-align: center;
    line-height: 32px;
    box-sizing: border-box;
    background-color: transparent;
}
 
input:focus {
    outline: none;
    background-color: transparent;
}
 
.yearPicker:hover {
    border-color: #3e77fc;
}
 
.dateIcon {
    position: absolute;
    right: 16px;
    top: 9px;
    color: #adb2bc;
}
</style>
   
  ​

这是组件的使用方法

js 复制代码
   <yearPicker
               style="width:79%"
              ref="statisticPicker"
               @updateTimeRange="updateStatisticYear"
            />
            
            


    function updateStatisticYear(startYear,endYear){
    if(!startYear&&!endYear){
      seachdata.value.buildYear=null
    }else{
   seachdata.value.buildYear=`${startYear},${endYear}`
    }
    
    console.log("选中年份", startYear, endYear)
  }

上效果图

相关推荐
m0_748240255 分钟前
前端如何检测用户登录状态是否过期
前端
black^sugar6 分钟前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人1 小时前
前端知识补充—CSS
前端·css
GISer_Jing1 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245521 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v1 小时前
webpack最基础的配置
前端·webpack·node.js
pubuzhixing1 小时前
开源白板新方案:Plait 同时支持 Angular 和 React 啦!
前端·开源·github
2401_857600951 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600951 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL1 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js