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>
相关推荐
iDao技术魔方4 小时前
深入Vue 3响应式系统:为什么嵌套对象修改后界面不更新?
javascript·vue.js·ecmascript
历程里程碑4 小时前
普通数组-----除了自身以外数组的乘积
大数据·javascript·python·算法·elasticsearch·搜索引擎·flask
摸鱼的春哥4 小时前
春哥的Agent通关秘籍07:5分钟实现文件归类助手【实战】
前端·javascript·后端
念念不忘 必有回响4 小时前
viepress:vue组件展示和源码功能
前端·javascript·vue.js
Amumu121384 小时前
Vue3 Composition API(一)
开发语言·javascript·ecmascript
C澒4 小时前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅4 小时前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘4 小时前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
hzb666664 小时前
unictf2026
开发语言·javascript·安全·web安全·php
恋猫de小郭5 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter