ts+uniapp小程序时间日期选择框(分开选择)

效果图:

弹框:

组件:

复制代码
<!-- components/DateTimePicker.vue -->
<template>
    <view class="datetime-picker">
        <!-- 选择框 -->
        <view class="picker-field" :class="{ active: showPicker }" @click="openPicker">
            <view class="field-value">
                <text class="value-text" :class="{ placeholder: !modelValue }">
                    {{ displayValue || placeholder }}
                </text>
            </view>
        </view>

        <!-- 选择器弹窗 -->
        <view v-if="showPicker" class="picker-modal">
            <view class="picker-mask" @click="closePicker" />
            <view class="picker-content">
                <view class="picker-header">
                    <text class="header-btn cancel" @click="closePicker">取消</text>
                    <text class="header-title">{{ label }}</text>
                    <text class="header-btn confirm" @click="confirmSelection">确定</text>
                </view>

                <view class="picker-body">
                    <view class="picker-section">
                        <text class="section-label">日期</text>
                        <picker mode="date" :value="tempDate" :start="minDate" :end="maxDate" @change="onDateChange">
                            <view class="picker-display">
                                <text class="picker-text">{{ tempDate || '请选择日期' }}</text>
                            </view>
                        </picker>
                    </view>

                    <view class="picker-section">
                        <text class="section-label">时间</text>
                        <picker mode="time" :value="tempTime" @change="onTimeChange">
                            <view class="picker-display">
                                <text class="picker-text">{{ tempTime || '请选择时间' }}</text>
                            </view>
                        </picker>
                    </view>

                    <view class="preview-section">
                        <text class="preview-label">预览</text>
                        <text class="preview-value">{{ previewValue }}</text>
                    </view>
                </view>
            </view>
        </view>
    </view>
</template>

<script setup lang="ts">
import { ref, computed, watch, withDefaults, defineProps, defineEmits } from 'vue';

interface Props {
    modelValue?: string;
    label?: string;
    placeholder?: string;
    minDate?: string;
    maxDate?: string;
}

const props = withDefaults(defineProps<Props>(), {
    label: '选择时间',
    placeholder: '请选择日期时间',
    minDate: '2020-01-01',
    maxDate: '2030-12-31',
});

const emit = defineEmits<{
    'update:modelValue': [value: string];
}>();

// 响应式数据
const showPicker = ref(false);
const tempDate = ref('');
const tempTime = ref('');

// 计算属性
const displayValue = computed(() => {
    return props.modelValue;
});

const previewValue = computed(() => {
    if (tempDate.value && tempTime.value) {
        return `${tempDate.value} ${tempTime.value}:00`;
    }
    return '请选择日期和时间';
});

const closePicker = () => {
    showPicker.value = false;
};

const confirmSelection = () => {
    if (tempDate.value && tempTime.value) {
        const value = `${tempDate.value} ${tempTime.value}:00`;
        emit('update:modelValue', value);
    }
    closePicker();
};

const onDateChange = (e: any) => {
    tempDate.value = e.detail.value;
};

const onTimeChange = (e: any) => {
    tempTime.value = e.detail.value;
};

// 工具函数
const getCurrentDate = (): string => {
    const now = new Date();
    const year = now.getFullYear();
    const month = (now.getMonth() + 1).toString().padStart(2, '0');
    const day = now.getDate().toString().padStart(2, '0');
    return `${year}-${month}-${day}`;
};

const getCurrentTime = (): string => {
    const now = new Date();
    const hours = now.getHours().toString().padStart(2, '0');
    const minutes = now.getMinutes().toString().padStart(2, '0');
    return `${hours}:${minutes}`;
};
// 方法
const openPicker = () => {
    if (props.modelValue) {
        // 回显已选值
        const [datePart, timePart] = props.modelValue.split(' ');
        tempDate.value = datePart;
        tempTime.value = timePart ? timePart.slice(0, 5) : getCurrentTime();
    } else {
        // 使用当前时间
        tempDate.value = getCurrentDate();
        tempTime.value = getCurrentTime();
    }
    showPicker.value = true;
};
</script>

<style scoped>
.datetime-picker {
    width: 100%;
}

.picker-field {
    background: #ffffff;
    border: 2rpx solid #e5e5e5;
    border-radius: 16rpx;
    padding: 24rpx 30rpx;
    transition: all 0.3s ease;
}

.picker-field.active {
    border-color: #007aff;
}

.field-label {
    font-size: 28rpx;
    color: #666;
    font-weight: 500;
}

.field-value {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 16rpx;
}

.value-text {
    font-size: 32rpx;
    color: #333;
    font-weight: 500;
}

.value-text.placeholder {
    color: #999;
}

.field-icon {
    color: #999;
    font-size: 24rpx;
}

/* 弹窗样式同上面示例 */
.picker-modal {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 9999;
}

.picker-mask {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
}

.picker-content {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    background: #ffffff;
    border-top-left-radius: 24rpx;
    border-top-right-radius: 24rpx;
    max-height: 70vh;
    overflow: hidden;
}

.picker-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 30rpx;
    border-bottom: 1rpx solid #f0f0f0;
}

.header-title {
    font-size: 36rpx;
    font-weight: 600;
    color: #333;
}

.header-btn {
    font-size: 32rpx;
    padding: 16rpx 24rpx;
}

.cancel {
    color: #666;
}

.confirm {
    color: #007aff;
    font-weight: 500;
}

.picker-body {
    padding: 30rpx;
}

.picker-section {
    margin-bottom: 40rpx;
}

.section-label {
    display: block;
    font-size: 28rpx;
    color: #666;
    margin-bottom: 20rpx;
    font-weight: 500;
}

.picker-display {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 24rpx 30rpx;
    background: #f8f9fa;
    border-radius: 12rpx;
    border: 2rpx solid #e9ecef;
}

.picker-text {
    font-size: 32rpx;
    color: #333;
    font-weight: 500;
}

.picker-arrow {
    color: #999;
    font-size: 24rpx;
    transform: rotate(90deg);
}

.preview-section {
    background: #e8f4ff;
    border-radius: 12rpx;
    padding: 30rpx;
    margin-top: 20rpx;
}

.preview-label {
    display: block;
    font-size: 26rpx;
    color: #007aff;
    margin-bottom: 16rpx;
    font-weight: 500;
}

.preview-value {
    font-size: 32rpx;
    color: #333;
    font-weight: 600;
    text-align: center;
    display: block;
}
</style>

页面:

复制代码
<template>               
   <date-picker
                            v-model="departTime"
                            class="time-input"
                            label="开始时间"
                            placeholder="请选择开始时间" />
</template>


<script setup lang="ts">
  import { ref } from 'vue';                      
  const departTime = ref('');
</script>
相关推荐
古茗前端团队5 小时前
急招!前端|测试|后端|产品(名额多,速来)
前端·后端·架构
Lsx_6 小时前
不只是 Prompt:用 Superpowers Skill 给 AI 编程装上工程化工作流
前端·ai编程·claude
用户938515635076 小时前
从 Prompt 到 Harness:AI 工程化的三年跃迁与实战解码
javascript·人工智能
小碗细面6 小时前
前端 Prompt 工程实战:如何搭建场景化 Prompt 库
前端·ai编程
阿瑞IT6 小时前
2026年 AI Agent 生产化落地全景:四大高频故障根因分析与工程解法
前端
木木剑光6 小时前
我开源了一个 React 组件库,沉淀了多个高频组件和实用 Hooks
前端·javascript·react.js
kyriewen6 小时前
DeepSeek API 高峰时段涨价 2 倍,便宜大碗的时代要结束了?
前端·ai编程·deepseek
Moment7 小时前
牛逼,NextJs 从 16.3 开始全面拥抱 Agent Native 🥰🥰🥰
前端·后端·面试
沸点小助手7 小时前
6月沸点活动获奖名单公示|本周互动话题上新🎊
前端·后端
Csvn7 小时前
React 19 `use()` 来了:以后数据加载可以不用 useEffect?
前端·react.js