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>
相关推荐
2501_9209317012 小时前
React Native鸿蒙跨平台采用ScrollView的horizontal属性实现横向滚动实现特色游戏轮播和分类导航
javascript·react native·react.js·游戏·ecmascript·harmonyos
0思必得014 小时前
[Web自动化] Selenium处理动态网页
前端·爬虫·python·selenium·自动化
东东51614 小时前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino14 小时前
图片、文件的预览
前端·javascript
2501_9209317016 小时前
React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
javascript·react native·react.js·游戏·ecmascript·harmonyos
layman052816 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔16 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html
AI老李16 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
_OP_CHEN17 小时前
【前端开发之CSS】(一)初识 CSS:网页化妆术的终极指南,新手也能轻松拿捏页面美化!
前端·css·html·网页开发·样式表·界面美化
啊哈一半醒17 小时前
CSS 主流布局
前端·css·css布局·标准流 浮动 定位·flex grid 响应式布局