vant实现自定义日期时间选择器(年月日时分秒)

vant实现自定义日期时间选择器(年月日时分秒)

1 背景

使用vant作为UI组件时,需要一个日期时间选择器,vant中有Dateicker和TimePicker,可以将两个组件组合封装使用,但是处理传入参数和传出参数稍显麻烦,所以使用Picker封装一个简单的日期时间选择器。

2 要求

  • 可以传入初始值
  • 可以选择年,年月,年月日,年月日时,年月日时分,年月日时分秒
  • 可以限定选择的最大日期和最小日期

3 实现

封装:

javascript 复制代码
// DateTimePicker.vue
<template>
    <van-picker
        ref="picker"
        title="请选择时间"
        :columns="columns"
        @change="handleChange"
        @cancel="handleCancel"
        @confirm="handleConfirm"
        v-model="values"
    />
</template>
<script setup lang="ts">
const props = defineProps({
    // 显示的列数量共6列(年月日时分秒)
    columnCount: {
        type: Number,
        default: 6,
    },
    // 可以选择的最小日期
    minDate: {
        type: Date,
        default: new Date(new Date().getFullYear() - 10, 1, 1, 0, 0, 0),
    },
    // 可以选择的最大日期
    maxDate: {
        type: Date,
        default: new Date(new Date().getFullYear() + 10, 11, 31, 23, 59, 59),
    },
});

const values = defineModel({
    get(val) {
        let param = val;
        if (Array.isArray(param)) {
            if (param.length > 0) {
                return param.map(i => i.toString().padStart(2, '0'));
            }
            param = new Date();
        } else if (!param) {
            param = new Date();
        }
        const date = new Date(param);
        const Y = date.getFullYear().toString();
        const M = (date.getMonth() + 1).toString().padStart(2, '0'); //实际月份
        const D = date.getDate().toString().padStart(2, '0');
        const h = date.getHours().toString().padStart(2, '0');
        const m = date.getMinutes().toString().padStart(2, '0');
        const s = date.getSeconds().toString().padStart(2, '0');

        return [Y, M, D, h, m, s];
    },
});

const columns = ref([]); //所有时间列

const maxDateArray = computed(() => getDateArray(props.maxDate));

onBeforeMount(() => {
    getColumns();
});

const getColumns = () => {
    const options = [
        {
            radix: props.maxDate.getFullYear() - props.minDate.getFullYear() + 1,
            base: props.minDate.getFullYear(),
        },
        {
            radix: 12,
            base: 1,
        },
        {
            radix: getCountDays(values.value[0], values.value[1]),
            base: 1,
        },
        {
            radix: 24,
            base: 0,
        },
        {
            radix: 60,
            base: 0,
        },
        {
            radix: 60,
            base: 0,
        },
    ].slice(0, props.columnCount);
    options.forEach((item, index) => {
        columns.value.push(fillData(index, item.radix, item.base));
    });
};

const fillData = (index, radix, baseNum = 0) => {
    return Object.keys(new Array(radix).fill()).map(item => ({
        text: (Number(item) + baseNum).toString().padStart(2, '0'),
        value: (Number(item) + baseNum).toString().padStart(2, '0'),
        disabled: Number(item) + baseNum > maxDateArray.value[index],
    }));
};

const getCountDays = (year, month) => {
    //获取某年某月最后1天
    return new Date(year, month, 0).getDate();
};

const getDateArray = param => {
    const date = new Date(param);
    const Y = date.getFullYear();
    const M = date.getMonth() + 1; //实际月份
    const D = date.getDate();
    const h = date.getHours();
    const m = date.getMinutes();
    const s = date.getSeconds();

    return [Y, M, D, h, m, s];
};

const handleChange = ({ selectedValues, columnIndex }) => {
    const dateArray = [...selectedValues].map((i, idx) => {
        if (idx == 1) {
            return Number(i) - 1;
        }
        return Number(i);
    });
    const maxDate = maxDateArray.value.map((i, idx) => {
        if (idx == 1) {
            return Number(i) - 1;
        }
        return i;
    });

    if (columnIndex <= 1 && columns.value.length > 2) {
        const lastDay = getCountDays(Number(selectedValues[0]), Number(selectedValues[1]));
        columns.value[2] = fillData(2, lastDay, 1);

        if (lastDay < Number(selectedValues[2])) {
            //切换年列或者月列时,因为每月的天可能不同,所以天列需要重置
            values.value = values.value
                .map((item, idx) => {
                    if (idx === 2) {
                        return lastDay.toString();
                    }
                    return item;
                })
                .slice(0, props.columnCount);
        }
    }

    if (
        new Date(...dateArray.slice(0, columnIndex + 1)).getTime() <
        new Date(...maxDate.slice(0, columnIndex + 1)).getTime()
    ) {
        for (let i = columnIndex + 1; i < columns.value.length; i++) {
            columns.value[i].forEach(k => {
                k.disabled = false;
            });
        }
    } else {
        for (let i = columnIndex + 1; i < columns.value.length; i++) {
            columns.value[i].forEach(k => {
                if (Number(k.value) > maxDateArray.value[i]) {
                    k.disabled = true;
                }
            });
        }
        if (new Date(...dateArray).getTime() > new Date(...maxDate).getTime()) {
            values.value = maxDateArray.value.slice(0, props.columnCount);
        }
    }
};

const handleCancel = () => {
    emit('cancel');
};

const handleConfirm = ({ selectedValues }) => {
    emit(
        'confirm',
        (selectedValues.slice(0, 3).join('-') + ' ' + selectedValues.slice(3).join(':')).trim()
    );
};

const emit = defineEmits(['confirm', 'cancel']);
</script>

调用:

javascript 复制代码
// index.vue
<template>
<van-button type="primary" @click="handleClick">选择日期</van-button>
<van-popup v-model:show="showPicker" round position="bottom">
<DateTimePicker
    v-model="values"
    :maxDate="maxDate"
    @cancel="handleCancel"
    @confirm="handleConfirm"
/>
</van-popup>
</template>
<script setup lang="ts">
const maxDate = new Date();
const showPicker = ref(false);
const values = ref(['2025','12','23','12','01','12']);//设置初始日期时间
const handleConfirm = () => { 
     //点击确定的事件
     showPicker.value = false;
}

const handleCancel = () => {
    //点击取消的事件,比如关闭弹框
    showPicker.value = false;
}

const handleClick = () => {
     showPicker.value = true;
}
 </script> 
相关推荐
m0_719084118 小时前
React笔记张天禹
前端·笔记·react.js
Ziky学习记录9 小时前
从零到实战:React Router 学习与总结
前端·学习·react.js
wuhen_n9 小时前
JavaScript链表与双向链表实现:理解数组与链表的差异
前端·javascript
wuhen_n9 小时前
JavaScript数据结构深度解析:栈、队列与树的实现与应用
前端·javascript
狗哥哥9 小时前
微前端路由设计方案 & 子应用管理保活
前端·架构
TT哇10 小时前
【实习 】银行经理端两个核心功能的开发与修复(银行经理绑定逻辑修复和线下领取扫码功能开发)
java·vue.js
前端大卫10 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘10 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare10 小时前
浅浅看一下设计模式
前端
Lee川10 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试