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

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

关于产品提出的年区间的选择,一开始在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)
  }

上效果图

相关推荐
树上有只程序猿2 分钟前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼37 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下44 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞1 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行1 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758101 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周1 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei2 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯