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>
相关推荐
*小雪2 小时前
uniapp写H5授权登录及分享,返回到目标页面
开发语言·javascript·uni-app
你不是我我3 小时前
【Java 开发日记】SQL 语句左连接右连接内连接如何使用,区别是什么?
java·javascript·数据库
一壶浊酒..3 小时前
请求签名(Request Signature)
javascript
P***25393 小时前
前端构建工具缓存清理,npm cache与yarn cache
前端·缓存·npm
好奇的菜鸟3 小时前
解决 npm 依赖版本冲突:从 “unable to resolve dependency tree“ 到依赖管理高手
前端·npm·node.js
lcc1873 小时前
Vue 内置指令
前端·vue.js
lijun_xiao20094 小时前
前端React18入门到实战
前端
o***Z4484 小时前
前端响应式设计资源,框架+模板
前端
IT_陈寒4 小时前
Vue 3.4 正式发布:5个不可错过的性能优化与Composition API新特性
前端·人工智能·后端