van-picker实现日期时间选择器

前提

vue3项目  "vant": "^4.9.18"

vant4 官网 Vant 4 - A lightweight, customizable Vue UI library for mobile web apps.

时间选择组件 timePicker.vue

代码中只展示到分钟(我的业务需求),需要秒的话放开代码中second项目代码即可,需要各种属性自定义等,可自己加props传入设置,我没弄

<template>
    <van-picker v-model="pickerVal" :columns="columns" @change="onChange" option-height="44px"
    :show-toolbar="false"/>
</template>

<script setup lang="ts">
import { getTimeRange } from '@/utils/index';


let pickerVal = ref(parseTimeToArray(getTimeRange(0).start));//getTimeRange(0).start获取的就是一个我的默认显示的时间 ,你可以跟着你的业务来 ${year}-${month}-${date} 00:00
const props = defineProps({
    modelValue: {
        type: String,
        default: '',
    },
});

const emit = defineEmits(['update:modelValue']);

// 时间列数据源
const columns = ref<any[]>([]);

init();

watch(() => props.modelValue, async (newVal, oldVal) => {
    if (newVal) {
        pickerVal.value = parseTimeToArray(newVal);

    }
},{immediate:true});
// 初始化日期
function init() {
    const dateStr = props.modelValue || getTimeRange(0).start;
    // const date = new Date(dateStr);
    const date = new Date(dateStr.replace(/-/g, '/'));
    const Y = date.getFullYear();
    const M = date.getMonth(); // 0-based
    const D = date.getDate();
    const h = date.getHours();
    const m = date.getMinutes();
    // const s = date.getSeconds();

    // 初始化列数据
    columns.value = [
        getYearColumns(Y),
        getMonthColumns(M),
        getDayColumns(Y, M + 1, D),
        getHourColumns(h),
        getMinuteColumns(m),
        // getSecondColumns(s),
    ];
}

// 获取年份列
function getYearColumns(currentYear: number) {
    const years = [];
    for (let i = currentYear - 10; i <= currentYear ; i++) {
        years.push({ text: `${i}年`, value: i });
    }
    return years;
}

// 获取月份列
function getMonthColumns(month: number) {
    const months = Array.from({ length: 12 }, (_, i) => ({
        text: i + 1 < 10 ? `0${i + 1}月` : `${i + 1}月`,
        value: i + 1
    }));
    return months;
}

// 获取某年某月的天数
function getDaysInMonth(year: number, month: number): number {
    return new Date(year, month, 0).getDate();
}

// 获取天数列
function getDayColumns(year: number, month: number, day: number) {
    const days = Array.from({ length: getDaysInMonth(year, month) }, (_, i) => ({
        text: i + 1 < 10 ? `0${i + 1}日` : `${i + 1}日`,
        value: i+1
    }));
    return days;
}

// 获取小时列
function getHourColumns(hour: number) {
    const hours = Array.from({ length: 24 }, (_, i) => ({
        text: i < 10 ? `0${i}时` : `${i}时`,
        value: i
    }));
    return hours;
}

// 获取分钟列
function getMinuteColumns(minute: number) {
    const minutes = Array.from({ length: 60 }, (_, i) => ({
        text: i < 10 ? `0${i}分` : `${i}分`,
        value: i,
    }));
    return minutes;
}

// 获取秒列
function getSecondColumns(second: number) {
    const seconds = Array.from({ length: 60 }, (_, i) => ({
        text: i < 10 ? `0${i}秒` : `${i}秒`,
        value: i
    }));
    return seconds;
}

// 列改变监听

// (picker: any, value: any, index: number)
function onChange({ selectedValues, selectedOptions, selectedIndexes, columnIndex }) {
    if (columnIndex === 0 || columnIndex === 1) {
        // 年或月改变 → 更新天数
        const selectedYear = selectedValues[0];
        const selectedMonth = selectedValues[1];
        const selectedDay = selectedValues[2];

        const totalDays = getDaysInMonth(selectedYear, selectedMonth);
        const newDays = Array.from({ length: totalDays }, (_, i) => ({
            text: i + 1 < 10 ? `0${i + 1}日` : `${i + 1}日`,
            value: i + 1,
        }));

        columns.value[2] = newDays; // 更新第3列(天)
        if (selectedDay > totalDays) {
            let newVal = [...pickerVal.value];
            newVal[2] = totalDays - 1;
            pickerVal.value = newVal;
        }
    }
    // 提交最终值
    submitTime();
}

// 提交时间
function submitTime() {
    emit('update:modelValue', arrayToTimeString(pickerVal.value));
}

function parseTimeToArray(timeStr: string): number[] {
    const date = new Date(timeStr);
    const year = date.getFullYear(); // 2025
    const month = date.getMonth() + 1; // 5 (getMonth() 返回 0-based)
    const day = date.getDate(); // 5
    const hours = date.getHours(); // 23
    const minutes = date.getMinutes(); // 59
    const seconds = date.getSeconds(); // 59

    return [year, month, day, hours, minutes];
}
function arrayToTimeString(arr: number[]): string {
    const [year, month, day, hours, minutes] = arr;
    // 补零处理
    const pad = (num: number) => num.toString().padStart(2, '0');
    return `${year}-${pad(month)}-${pad(day)} ${pad(hours)}:${pad(minutes)}`;
    //:${pad(seconds)}
}
</script>

如果需要跟van-popup配合使用

这里更多是根据个人业务写的组件,仅供参考

<template>
    <van-popup v-model:show="show" position="bottom"
    :style="{ height: '60%' }" round>
        <div class="flexAlCt time-top">
            <span class="cancel-btn" @click="closePop">取消</span>
            <span class="time-title">日期筛选</span>
            <span class="sure-btn" @click="confirmPop">确定</span>
        </div>
        <div class="flexAlCt quick-col">
            <span v-for="v in list" :key="v.num" class="quick-row"
            :class="v.num==timeNum?'active':''" @click="quickChooseTime(v)">
                {{ v.name }}
            </span>
        </div>
        <p class="sub-title">自定义日期</p>
        <div class="flexAlCt choose-time">
            <label>开始日期</label>
            <span :class="defineTime.start?'choosed-time':'no-time'"
            @click="showTimePicker('start')">{{ defineTime.start||'请选择' }}</span>
        </div>
        <timePicker v-model:model-value="defineTime.start" v-if="timeShow.start"/>
        <div class="flexAlCt choose-time">
            <label>结束日期</label>
            <span :class="defineTime.end?'choosed-time':'no-time'"
            @click="showTimePicker('end')">{{ defineTime.end||'请选择' }}</span>
        </div>
        <timePicker v-model:model-value="defineTime.end" v-if="timeShow.end"/>
    </van-popup>
</template>
<script setup lang="ts">
import timePicker from "./timePicker.vue";
import { getTimeRange } from '@/utils/index';
import { showToast } from 'vant';
const emit = defineEmits(['close', 'confirm']);
const props = defineProps({
    popupShow: {
        type: Boolean,
        default: true,
    },
    time: {
        type: Object,
        default: () => {
            return {
                start: '',
                end: ''
            }
        },
    },
});

let show = ref(false);//弹出框显示
let list = [//快捷时间列表
    { name: '近一周', num: 7, time: getTimeRange(7) },
    { name: '近一月', num: 30, time: getTimeRange(30) },
    { name: '近三月', num: 90, time: getTimeRange(90) }
];
let timeNum = ref(7);//选中的快捷时间

let timeShow = reactive({//自定义时间选择器显示
    start: false,
    end: false,
});
let defineTime = reactive({//自定义时间
    start: '',
    end: '',
})

//监听popop显示
watch(()=>props.popupShow,() => {
    show.value = props.popupShow;
    if (props.time.start) {
        compareQuickTime(props.time);
        defineTime.start = props.time.start;
        defineTime.end = props.time.end;
    }
}, { immediate: true })

//监听自定义时间变化
watch(() => defineTime, (nv) => {
    compareQuickTime(nv);
}, { deep: true });

//比对快捷时间
function compareQuickTime(compare) {
    let obj = list.find(val => {
        return val.time.start === compare.start &&
            val.time.end === compare.end
    });
    if (obj) {
        timeNum.value = obj.num;
    } else {
        timeNum.value = -1;
    }
}
//快捷选择时间
function quickChooseTime(v) {
    if (timeNum.value == v.num) {
        timeNum.value = -1;
    } else {
        timeNum.value = v.num;
        defineTime.start = v.time.start;
        defineTime.end = v.time.end;
    }
}
//切换时间选择器显示
function showTimePicker(type) {
    if (!timeShow[type]) {
        let stype = type == 'start' ? 'end' : 'start';
        timeShow[stype] = false;
    }
    timeShow[type] = !timeShow[type];
}
//关闭弹出框
function closePop() {
    show.value = false;
    emit('close');
}
//确定
function confirmPop() {
    const startTime = new Date(defineTime.start.replace(/-/g, '/')).getTime();
    const endTime = new Date(defineTime.end.replace(/-/g, '/')).getTime();
    if (startTime > endTime) {
        showToast("开始时间不能大于结束时间");
        return;
    }
    emit('confirm', toRaw(defineTime));
    closePop();
}
</script>
<style lang="less" scoped>
.time-top{
    justify-content: space-between;
    padding:9px 16px;
    font-size:17px;
    .cancel-btn,.sure-btn{
        font-size:15px;
        line-height: 1;
        padding:8px 15px;
    }
    .sure-btn{
        background: var(--primary-yellow);
        border-radius: 8px;
    }
    .time-title{
        font-size:17px;
        line-height: 1;
        font-weight: 500;
    }
}
.quick-col{
    padding:12px;
    justify-content: space-between;
    .quick-row{
        width: 30%;
        height: 36px;
        border:1px solid #F5F5F5;
        background: #F5F5F5;
        border-radius: 4px;
        text-align: center;
        line-height: 34px;
        &.active{
            border-color: var(--primary-yellow);
            background: #FFFAED;
            position:relative;
            &::before{
                content:"✔";
                position:absolute;
                width:24px;
                height: 16px;
                line-height:16px;
                border-bottom-left-radius: 10px;
                background: var(--primary-yellow);
                top:0;
                right:0;
            }
        }
    }
}
.sub-title{
    color: #999;
    font-size:16px;
    padding-left:16px;
    line-height: 44px;
}
.choose-time{
    justify-content: space-between;
    line-height: 44px;
    font-size:16px;
    padding-left:16px;
    .choosed-time{
        color:var(--primary-yellow);
        padding:0 16px;
    }
    .no-time{
        padding:0 16px;
        color:#B3B3B3;
    }
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值