Vue2 elementUI年份区间选择组件

环境:

"dependencies": {
    "axios": "^0.19.0",

    "element-ui": "^2.9.2",

    "vue": "^2.6.10",
    "vue-clipboard2": "^0.3.1",

    "vue-json-pretty": "^1.7.1",
    "vue-router": "^3.0.3",

    "vuex": "^3.1.0",

  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.5.5",
    "@vue/cli-plugin-eslint": "^3.5.1",
    "@vue/cli-service": "^3.5.3",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0-0",
    "file-saver": "^2.0.2",
    "node-sass": "^4.14.1",
    "sass-loader": "^7.1.0",
    "script-loader": "^0.7.2",
    "vue-cli-plugin-element": "^1.0.1",
    "vue-template-compiler": "^2.6.10"
  },

问题:elementUI中有日期范围组件,月份范围选择的,就是没有年份范围选择的,需要加一个类似风格的。

先看月份的范围选择效果:

在看实际实现效果(因为我们主题是自定义风格,所以颜色有些差异,可以调整): 

<template>
    <div class="yearPicker" ref="yearPicker" :style="{ width: width + 'px' }">
        <div class="_inner labelText" :style="{ width: labelWidth + 'px' }">{{ labelText }}</div>
        <input class="_inner" ref="inputLeft" v-model="startShowYear" @focus="onFocus" type="text" @click="clickInput"
            name="yearInput" @input="checkStartInput($event)" placeholder="选择年份" />
        <span>{{ sp }}</span>
        <input class="_inner" ref="inputRight" v-model="endShowYear" @focus="onFocus" type="text" @click="clickInput"
            name="yearInput" @input="checkEndInput($event)" placeholder="选择年份" />
        <div class="_inner floatPanel" v-if="showPanel">
            <div class="_inner leftPanel">
                <div class="_inner panelHead">
                    <i class="_inner el-icon-d-arrow-left" @click="onClickLeft"></i>
                    {{ leftYearList[0] + "-" + leftYearList[9] }}
                </div>
                <div class="_inner panelContent">
                    <div v-for="item in leftYearList" :class="{
                        disabled: checkValidYear(item)!= 0,
                        oneSelected: item === startYear && oneSelected,
                        startSelected: item === startYear,
                        endSelected: item === endYear,
                        _inner: true,
                        betweenSelected: item > startYear && item < endYear
                    }" :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>
                    {{ rightYearList[0] + "-" + rightYearList[9] }}
                </div>
                <div class="_inner panelContent">
                    <div :class="{
                        disabled: checkValidYear(item) != 0,
                        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>
</template>
  
<script>
const SELECT_STATE = {
    unselect: 0,
    selecting: 1,
    selected: 2,
};
export default {
    name: "YearPicker",
    computed: {
        oneSelected() {
            return (
                this.curState === SELECT_STATE.selecting &&
                (this.startYear === this.endYear || this.endYear == null)
            );
        },
        startDate() {
            return this.startYear;
        },
        leftYearList() {
            return this.yearList.slice(0, 10);
        },
        rightYearList() {
            return this.yearList.slice(10, 20);
        }

    },
    props: {
        width: {
            default: 200,
        },
        labelWidth: {
            default: 80,
        },
        labelText: {
            default: "时间标签",
        },
        sp: {
            default: "至",
        },
        initYear: {
            default: null,
        },
    },
    data() {
        return {
            itemBg: {},
            startShowYear: null,
            endShowYear: null,
            yearList: [],
            showPanel: false,
            startYear: null,
            endYear: null,
            curYear: 0,
            curSelectedYear: 0,
            curState: SELECT_STATE.unselect,
        };
    },
    methods: {
        checkValidYear(iYear) {
            if (this.initYear) {
                if (iYear > this.initYear.endYear) {
                    return 1
                } else if (iYear < this.initYear.startYear) {
                    return -1
                }
            }
            return 0
        },
        checkStartInput(event) {
            if (isNaN(this.startShowYear)) {
                this.startShowYear = this.startYear;
            } else {
                this.startYear = this.startShowYear * 1;
            }
        },

        checkEndInput() {
            if (isNaN(this.endShowYear)) {
                this.endShowYear = this.endYear;
            } else {
                this.endYear = this.endShowYear * 1;
            }
        },
        changeYear() {
            if (this.startYear > this.endYear) {
                let tmp = this.endYear;
                this.endYear = this.startYear;
                this.startYear = tmp;

            }
            if (this.initYear) {
                this.startYear = Math.max(this.startYear, this.initYear.startYear)
                this.endYear = Math.min(this.endYear, this.initYear.endYear)
            }
            this.startShowYear = this.startYear;
            this.endShowYear = this.endYear;


            if (this.startYear && this.endYear) {
                this.$emit("updateTimeRange", {
                    startYear: this.startYear,
                    endYear: this.endYear + ""
                });
            } else {
                console.warn("WARN:年份不合法", this.startYear, this.endYear);
            }
        },
        onHoverItem(iYear) {
            if (this.checkValidYear(iYear) != 0) {
                return;
            }
            if (this.curState === SELECT_STATE.selecting) {
                let tmpStart = this.curSelectedYear;
                this.endYear = Math.max(tmpStart, iYear);
                this.startYear = Math.min(tmpStart, iYear);
            }
        },
        onClickItem(iYear) {
            if (this.checkValidYear(iYear) != 0) {
                return;
            }

            if (
                this.curState === SELECT_STATE.unselect ||
                this.curState === SELECT_STATE.selected
            ) {
                this.startYear = iYear;
                this.curSelectedYear = iYear;
                this.endYear = null;
                this.curState = SELECT_STATE.selecting;
            } else if (this.curState === SELECT_STATE.selecting) {
                this.endShowYear = this.endYear;
                this.startShowYear = this.startYear;
                this.curState = SELECT_STATE.selected;
                this.$emit("updateTimeRange", {
                    startYear: this.startYear,
                    endYear: this.endYear,
                });

                setTimeout(() => {
                    //为动画留的时间,可优化
                    this.showPanel = false;
                }, 300);
            }
        },
        onFocus() {
            this.$nextTick(() => {
                this.showPanel = true;
            });
        },
        clickInput(e) {
            e.stopPropagation();
            return false;
        },

        updateYearList() {
            let iStart = Math.floor(this.curYear / 10) * 10 - 10;
            iStart = iStart < 0 ? 0 : iStart;
            this.yearList = [];
            for (let index = 0; index < 20; index++) {
                this.yearList.push(iStart + index);
            }
        },

        checkValidYear(iYear) {
            if (this.initYear) {
                if (iYear > this.initYear.endYear) {
                    return 1
                } else if (iYear < this.initYear.startYear) {
                    return -1
                }
            }
            return 0
        },
        closePanel(e) {
            if (!this.showPanel) {
                return;
            }
            if (typeof e.target.className !== "string" || e.target.className === "") {
                this.$nextTick(() => {
                    this.changeYear();
                    this.showPanel = false;
                });
                return;
            }
            if (
                e.target.className.indexOf("_inner") === -1 ||
                (e.target.name === "yearInput" &&
                    e.target !== this.$refs.inputLeft &&
                    e.target !== this.$refs.inputRight)
            ) {
                this.$nextTick(() => {
                    this.changeYear();
                    this.showPanel = false;
                });
            }

            e.stopPropagation();
            return false;
        },
        onClickLeft() {
            this.curYear = this.curYear * 1 - 10;
            this.updateYearList();
        },
        onClickRight() {
            this.curYear = this.curYear * 1 + 10;
            this.updateYearList();
        },

        //------------------对外接口------------------------
        // //直接传时间戳
        // setYear(startYearStamp, endYearStamp) {
        //     if (!isNaN(startYearStamp) && !isNaN(endYearStamp)) {
        //         let startYear = moment(startYearStamp).format("yyyy");
        //         let endYear = moment(endYearStamp).format("yyyy");
        //         this.startYear = startYear * 1;
        //         this.endYear = endYear * 1;
        //         this.endShowYear = endYear * 1;
        //         this.startShowYear = startYear * 1;
        //     }
        // },
    },

    created() {
        this.curYear = new Date().getFullYear();
        this.updateYearList();
    },
    beforeUnmount() {
        document.removeEventListener("click", this.closePanel.bind(this));
    },

    mounted() {
        document.addEventListener("click", this.closePanel.bind(this));
    },
};
</script>
<style lang="scss" scoped>
.yearPicker {
    font-size: 14px;
    display: flex;
    position: relative;
    transition: all 0.3s;
    input {
        text-align: center;
    }

    input:first-child {
        text-align: right;
    }

    background-color: #fff;

    .labelText {
        text-align: center;
    }

    span {
        padding: 0 8px;
        height: 32px;
        line-height: 32px;
    }

    border: 1px solid #eff1f3;
    height: 34px;
    line-height: 34px;
    border-radius: 4px;
    padding: 0 28px 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: -10px;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);

        .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;
            top:15px;
        }

        .rightPanel .panelHead i {
            right: 20px;
            top:15px;
        }
    }

    .floatPanel::before {
        content: "";
        height: 100%;
        position: absolute;
        left: 50%;
        width: 1px;
        border-left: 1px solid #e4e4e4;
    }
}

input {
    width: 60px;
    border: none;
    height: 32px;
    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>
  
  ​
  

调用方式:

   <YearPicker
              labelText="统计期"
              :width="300"
              :label-width="80"
              :initYear="dateValue2" 
              @updateTimeRange="updateStatisticYear"
            />

dateValue2调整为对象: dateValue2:{startYear:2000, endYear: new Date().getFullYear()},

updateTimeRange是选中之后的回调

 updateStatisticYear(startYear, endYear){
      console.log("看看回调:", startYear, endYear)
    },

2023/12/19更新:1,修复输入逻辑紊乱的bug。

                             2,处理scss版本不支持box-shadow rgb的bug 。

                             3,处理回调和传参都使用年份

                             4,优化点击关闭的效果

                             5,新增可选年份区间(区间外禁止点击和选择,同时校正输入)

                             6,删除了主动调用接口(看有没有需要,有需要后续处理一下)

                             7,删除了第三方库的依赖

 新增vue3 Element Plus支持:跳转电梯:http://t.csdnimg.cn/7rYpH

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Vue 2和Element UI中实现选择三个及年份的功能,可以使用Element UI的DatePicker组件结合自定义的校验规则来实现。具体步骤如下: 1. 确保你已经引入了Vue和Element UI库,并正确注册Element UI组件。 2. 在你的Vue组件中,使用DatePicker组件,并设置type属性为"daterange"。 ```vue <template> <div> <el-date-picker v-model="selectedYears" type="daterange" format="yyyy" value-format="yyyy" :picker-options="pickerOptions" :disabledDate="disabledDate" ></el-date-picker> </div> </template> <script> export default { data() { return { selectedYears: [], // 存储选择年份 pickerOptions: { disabledDate(time) { // 禁用除了当前年份和前一年之外的所有日期 const currentYear = new Date().getFullYear(); const prevYear = currentYear - 1; const year = time.getFullYear(); return year > currentYear || year < prevYear; } } }; }, methods: { disabledDate(time) { const selectedCount = this.selectedYears.length; return selectedCount >= 3; // 限制选择三个及以上年份 } } }; </script> ``` 在上述代码中,我们使用了`v-model`指令来绑定选择年份数组`selectedYears`。`format`和`value-format`属性分别用于设置显示和绑定的年份格式为"yyyy"。 在`pickerOptions`对象中的`disabledDate`函数中,我们禁用了除了当前年份和前一年之外的所有日期。 在Vue组件的`methods`选项中,我们定义了一个名为`disabledDate`的方法,该方法用于自定义校验规则。在该方法中,我们获取已选择年份数量,然后通过判断数量是否大于等于3来决定是否禁用其他日期。 通过以上步骤,你就可以在Vue 2和Element UI中实现选择三个及以上年份的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值