首页 前端知识 Vue3 Element Plus自定义年份区间选择组件

Vue3 Element Plus自定义年份区间选择组件

2024-04-29 11:04:03 前端知识 前端哥 810 24 我要收藏

环境:

"dependencies": {
    "@rollup/plugin-alias": "^3.1.9",
    "@types/node": "^17.0.43",
    "element-plus": "^2.2.15",
    "vue": "^3.2.25",
    "vue-router": "^4.0.16"
  },
  "devDependencies": {
    "typescript": "^4.5.4",
    "vite": "^2.9.9",
    "vite-plugin-vue-setup-extend": "^0.4.0",
    "vue-tsc": "^0.34.7"
  }

效果:(按照element操作方式来写的,颜色自定义的)

组件代码:(composition API的方式)

<template>
    <div class="yearPicker" :ref="yearPicker">
        <div class="_inner" :style="{ width: props.labelWidth + 'px' }">{{ props.labelText }}</div>

        <input class="_inner" :ref="inputLeft" v-model="data.startShowYear" @focus="onFocus" @click="clickInput" type="text"
            name="yearInput" @input="checkStartInput()" placeholder="选择年份" />
        <span>{{ props.sp }}</span>
        <input class="_inner" :ref="inputRight" v-model="data.endShowYear" @focus="onFocus" @click="clickInput" type="text"
            name="yearInput" @input="checkEndInput()" placeholder="选择年份" />
        <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 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>
.yearPicker {
    font-size: 14px;
    display: flex;
    position: relative;
    transition: all 0.3s;

    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>
   
  ​

外部调用:

<template>
    <div>
        <yearPicker
               style="width:250px"
              ref="statisticPicker"
              :labelWidth = "50"
              labelText="统计期"
              :initYear="dateValue"
              @updateTimeRange="updateStatisticYear"
            />
    </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import yearPicker from "./component/yearPick.vue"

//可选择区间,initYear传参,不传则所有年份有效,小于0判断一下?
const dateValue= ref<any>({startYear:2000, endYear: new Date().getFullYear()});
//选完/输入完成的回调
const updateStatisticYear:any = (startYear:number, endYear:number)=>{
    console.log("选中年份", startYear, endYear)
}
</script>

<style lang="scss" scoped>

</style>

这个是vue2升级上来的,修复了输入的逻辑问题,添加了有效年份区间(区间外的禁止选择,禁止输入),优化了点击关闭页面的逻辑:

vue2版本电梯:http://t.csdnimg.cn/ngmgL

转载请注明出处或者链接地址:https://www.qianduange.cn//article/5921.html
标签
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!