史上最简单的Vue datePicker组件

2023-07-17 中文

技术

Nuxt.js Vue.js 组件

Vue子组件和父组件如何双向传值?父组件如何二次传值给子组件?参考我写的这个datePicker component即可。

Vue DatePicker

设计思路

假设我们想做一个简约(简陋)的日期选择组件,用三个 <select></select> 元素来完成一个日期选择功能,UI参考下图。

需要做到的功能有:

  1. 年份选择要可以限制范围
  2. 日子选择需要和年份和月份联动,自动变成28天、29天、30天或31天
  3. 从父组件点开不同来源的数据详情,每次都渲染一个新日期到子组件上去
  4. 手动更改日期后,可以回传给父组件,以便更新父组件的数据源

子组件的代码

<template>
    <div>
        <select v-model="yearVal" @change="getDateVal">
            <option v-for="(year, i) in yearOption" :key="i">{{ year }}</option>
        </select><select v-model="monthVal" @change="getDateVal">
            <option v-for="(month, i) in monthOption" :key="i">{{ month }}</option>
        </select><select v-model="dayVal" @change="getDateVal">
            <option v-for="(day, i) in dayOption" :key="i">{{ day }}</option>
        </select></div>
</template>
var monthOption = ["01", "02", "03", "04", "05", "06", "07", "08", "09", 10, 11, 12];
var _28dayOption = ["01", "02", "03", "04", "05", "06", "07", "08", "09",10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28];
var _29dayOption = _28dayOption.concat([29]);
var _30dayOption = _29dayOption.concat([30]);
var _31dayOption = _30dayOption.concat([31]);
export default {
    name: "DatePickerUnit",
    //按理说props应该设置类型什么的,但是我为了书写方便,
    //选项里又有数字又有字符串,如果限定了类型,可能不太好统一。
    props: ["startYear", "endYear", "year", "month", "day"],
    data() {
        return {
            monthOption,
            yearVal: this.year,
            monthVal: this.month,
            dayVal: this.day
        };
    },
    computed: {
        yearOption() {
            //初始年份为过去10年
            //如果有传参,则输出从开始年份到结束年份
            var date = new Date();
            var yearNow = date.getFullYear();
            var startYear = this.startYear ? this.startYear : yearNow - 10;
            var endYear = this.endYear ? this.endYear : yearNow;
            var result = [];
            for (let i = 0; i < endYear - startYear + 1; i++) {
                result.push(startYear + i);
            }
            return result;
        },
        dayOption() {
            var year = this.yearVal;
            var month = this.monthVal;
            var day = this.dayVal;
            var month31day = ["01", "03", "05", "07", "08", 10, 12];
            var month30day = ["04", "06", "09", 11];
            //1,3,5,7,8,10,12月
            for (let i = 0; i < month31day.length; i++) {
                if (month == month31day[i]) {
                    return _31dayOption;
                }
            }
            //4,6,9,11月
            for (let i = 0; i < month30day.length; i++) {
                if (month == month30day[i]) {
                    if (day == 31) {
                        //之前选了31日,本月没有,所以清空选择
                        this.dayVal = "";
                    }
                    return _30dayOption;
                }
            }
            //2月
            if ((0 == year % 4 && 0 != year % 100) || 0 == year % 400) {
                //闰年2月
                if (day > 29) {
                    //之前选的日子大于29,本月只有29天,所以清空选择
                    this.dayVal = "";
                }
                return _29dayOption;
            } else {
                //普通2月
                if (day > 28) {
                    //之前选的日子大于28,本月只有28天,所以清空选择
                    this.dayVal = "";
                }
                return _28dayOption;
            }
        }
    },
    methods: {
        getDateVal() {
            //定义事件名称和要传的值,后面可以在父组件进行调用
            this.$emit("dateChanged", {
                year: this.yearVal,
                month: this.monthVal,
                day: this.dayVal
            });
        }
    },
    watch: {
        //父组件再次给props传值时,如果没有watch,就不会接收第二次
        year(newYear) {
            this.yearVal = newYear;
        },
        month(newMonth) {
            this.monthVal = newMonth;
        },
        day(newDay) {
            this.dayVal = newDay;
        }
    }
};

父组件内的应用

<DatePickerUnit @dateChanged="changeBillDate"
:startYear="startYear" :endYear="endYear"
:year="billDateYear" :month="billDateMonth" :day="billDateDay"
></DatePickerUnit> 
import DatePickerUnit from "~/components/Date-Picker-Unit";
export default {
    components: ["DatePickerUnit"],
    data() {
        return {
            startYear: 2013,
            endYear: 2023,
            billDateYear: 2023,
            billDateMonth: "07",
            billDateDay: 17
        }
    },
    methods: {
        changeBillDate(dateObj) {
         this.billDateYear = dateObj.year;
         this.billDateMonth = dateObj.month;
         this.billDateDay = dateObj.day;
        }
    }
}

父组件向子组件传值

首次渲染及props和data的利用

我们在父组件里定义了子组件需要的5个 props 对应的 data,此时会渲染第一次。

startYearendYear 渲染完一次就够了,不涉及到父子组件双向传值,所以不用理它们。

year month day 这三个值,我们希望能在渲染到 <select></select> 之后,在用户手动选择选项的时候触发三个值的更改。

但是子组件的 props 是不能被赋予新值的,所以我们需要在子组件的 data 里新建3个值,yearVal monthVal dayVal,在 data 里获取到 props 的值之后,再渲染到 html 里。

后续我们可以在子组件里触发 data 的更改,就可以二次渲染了。

父组件二次传值及渲染

如果父组件的 data 的值有变化,子组件是不会感应到的,props 第一次获取到的值不会随之改变,所以我们需要在子组件里设置 watch 来监听父组件的数据变化。监听到之后,就可以让子组件显示新的一组数据了。

子组件向父组件传值

子组件内设置传值的事件名称及要传的数据

当点开下拉菜单选择选项的时候,触发传值事件

<template>
    <div>
        <select @change="getDateVal">
            ...
        </select>
    </div>
</template>
methods: {
    getDateVal() {
        //定义事件名称和要传的值,后面可以在父组件进行调用
        this.$emit("dateChanged", {
            year: this.yearVal,
            month: this.monthVal,
            day: this.dayVal
        });
    }
}

父组件调用该事件获取新值

当子组件发生 dateChanged 事件的时候,父组件调用 changeBillDate 方法获取子组件的值

<DatePickerUnit @dateChanged="changeBillDate"
></DatePickerUnit> 
methods: {
    changeBillDate(dateObj) {
        this.billDateYear = dateObj.year;
        this.billDateMonth = dateObj.month;
        this.billDateDay = dateObj.day;
    }
}

然后就可以更新父组件的数据了。

总结

虽然是个简陋的功能,但是也是能用的呢。

全文完。

Load Comments