uniapp自定义一个选择年月日时分的组件。
<template><view><u-popup :show="timePopShow" mode="bottom" @close="close" @open="open" :closeOnClickOverlay="true"><view class="popup-container"><!-- 自定义时间内容 --><view class="date-range-container" v-if="currentTab === 'tab2'"><view class="date-input" :class="{ selected: currentSelect === 'start' }"@click="selectDate('start')"><text>{{ startDateText }}</text></view><text class="date-separator">至</text><view class="date-input" :class="{ selected: currentSelect === 'end' }" @click="selectDate('end')"><text>{{ endDateText }}</text></view></view><view class="content-range" v-if="currentTab === 'tab2'"><picker-view :value="pickerValue" indicator-style="height: 100rpx;" class="picker-view"@change="pickerChange"><picker-view-column><view v-for="(year, index) in years" :key="index" class="item">{{ year }}</view></picker-view-column><picker-view-column><view v-for="(month, index) in months" :key="index" class="item">{{ month }}</view></picker-view-column><picker-view-column><view v-for="(day, index) in days" :key="index" class="item">{{ day }}</view></picker-view-column><picker-view-column><view v-for="(hours, index) in hours" :key="index" class="item">{{ hours }}</view></picker-view-column><picker-view-column><view v-for="(mins, index) in mins" :key="index" class="item">{{ mins }}</view></picker-view-column></picker-view></view><view @click="confirm2" class="confirm-btn2">取消</view><view @click="confirm" class="confirm-btn">确认</view></view><!-- 确认按钮 --><!-- <view class="header-buttons"><button @click="confirm" class="button button--confirm">确认</button></view> --></u-popup></view>
</template><script setup>
import moment from 'moment'
import {ref,defineEmits,watch
} from 'vue';// 注册组件暴露方法
const emits = defineEmits(['emitsClose', 'emitsGetMonthDays', 'emitsCompareDates'])// 组件传参
const props = defineProps({// 弹窗开启timePopShow: {type: Boolean,default: false},remorkStartTime: {type: String,default: ''},remorkEndTime: {type: String,default: ''}
})const currentTab = ref('tab2') //选项卡控制变量
const tabList = ref([{name: '月份选择',value: 'tab1'
},
{name: '自定义时间',value: 'tab2'
}
]) //选项卡内容const dateHave = (item) => {const date = moment(item, 'YYYY-MM-DD HH:mm');return [date.year() - 2020, // 年份减去2020date.month(), // 月份从0开始date.date() - 1, // 日期从0开始date.hours() - 1, // 小时从0开始date.minutes() // 分钟从0开始];
}
const startNowDate = new Date().getTime()const currentDate = new Date(); //当前时间
const currentYear = currentDate.getFullYear(); //当前年
const currentMonth = currentDate.getMonth(); //当前月
const currentDatehours =currentDate.getHours() - 1;
const currentDatemins = currentDate.getMinutes() + 1;const years = Array.from({length: 180
}, (v, i) => 2020 + i) // 生成2020到2200年
const months = Array.from({length: 12
}, (v, i) => i + 1) // 生成1到12月
const days = months == (1 || 3 || 5 || 7 || 8 || 10 || 12) ? Array.from({length: 31
}, (v, i) => i + 1) : Array.from({length: 30
}, (v, i) => i + 1) // 生成1到31日
const hours = Array.from({length: 18
}, (v, i) => i + 1) // 生成1到31日
const mins = Array.from({length: 60
}, (v, i) => i) // 生成1到31日const dateValue = ref([currentYear - 2020, currentMonth]) // 默认选中当前年份和月份const currentSelect = ref('start') // 用于标记当前选择的是开始日期还是结束日期
// 初始化 startDateText
let startDate = moment(props.remorkStartTime, 'YYYY-MM-DD HH:mm');
if (!startDate.isValid() || currentDatehours.value >= 18) {startDate = moment().add(1, 'days').startOf('day').hour(8).minute(0); // 默认设置为下一天的9:00}
const startDateText = ref(startDate.format('YYYY-MM-DD HH:mm')) // 开始时间// 初始化 endDateText
let endDate = moment(props.remorkEndTime, 'YYYY-MM-DD HH:mm');
if (!endDate.isValid() || currentDatehours.value >= 18) {endDate = moment(startDate).add(1, 'hours'); // 默认设置为开始时间后1小时
}
const endDateText = ref(endDate.format('YYYY-MM-DD HH:mm')) // 截至时间
const dateArrayStart = props.remorkStartTime ? dateHave(props.remorkStartTime) : dateHave(startDateText.value);
const dateArrayEnd = props.remorkEndTime ? dateHave(props.remorkEndTime) : dateHave(endDateText.value);
const pickerValue = ref(dateArrayStart) // 当前选择器的值
const startDateValue = ref(dateArrayStart) // 默认选中当前日期
const endDateValue = ref(dateArrayEnd) // 默认选中截止日期// 选项卡切换点击
const clickTab = (item) => {currentTab.value = item.value;
}// 组件开启
const open = () => {currentTab.value = 'tab1';
}// 组件关闭
const close = () => {selectDate(currentSelect.value)emits('emitsClose', false)
}// 时间选择监听
const pickerChange = (e) => {const value = e.detail.value;if (currentTab.value === 'tab1') {// 当 currentTab 为 'tab1' 时,处理月份选择dateValue.value = value; // 更新 dateValue} else if (currentTab.value === 'tab2') {// 当 currentTab 为 'tab2' 时,处理自定义时间if (currentSelect.value === 'start') {startDateValue.value = value;pickerValue.value = valuestartDateText.value =`${years[value[0]]}-${months[value[1]].toString().padStart(2, '0')}-${days[value[2]].toString().padStart(2, '0')} ${hours[value[3]].toString().padStart(2, '0')}:${mins[value[4]].toString().padStart(2, '0')}`;} else if (currentSelect.value === 'end') {endDateValue.value = value;pickerValue.value = valueendDateText.value =`${years[value[0]]}-${months[value[1]].toString().padStart(2, '0')}-${days[value[2]].toString().padStart(2, '0')} ${hours[value[3]].toString().padStart(2, '0')}:${mins[value[4]].toString().padStart(2, '0')}`;}}
}// 开始/截止时间点击选择
const selectDate = (type) => {currentSelect.value = type;// 恢复上一次选中时间if (type === 'start') {pickerValue.value = props.remorkStartTime ? dateHave(props.remorkStartTime) : [...startDateValue.value];} else if (type === 'end') {pickerValue.value = props.remorkEndTime ? dateHave(props.remorkEndTime) : [...endDateValue.value];}
}// 确认按钮
const confirm = () => {if (currentTab.value === 'tab1') {const year = years[dateValue.value[0]];const month = months[dateValue.value[1]];emits('emitsGetMonthDays', getMonthDays(year, month), year + '-' + month)} else if (currentTab.value === 'tab2') {const startYear = years[startDateValue.value[0]];const startMonth = months[startDateValue.value[1]];const startDay = days[startDateValue.value[2]];const startHours = hours[startDateValue.value[3]];const startMins = mins[startDateValue.value[4]];const endYear = years[endDateValue.value[0]];const endMonth = months[endDateValue.value[1]];const endDay = days[endDateValue.value[2]];const endHours = hours[endDateValue.value[3]];const endMins = mins[endDateValue.value[4]];if (compareDates(startYear, startMonth, startDay, startHours, startMins, endYear, endMonth, endDay, endHours, endMins)) {emits('emitsCompareDates', compareDates(startYear, startMonth, startDay, startHours, startMins, endYear, endMonth, endDay, endHours, endMins))}}
}
//取消按钮
const confirm2 = () => {close()
}// 月份数据处理(返回每个月开始/截止时间)
const getMonthDays = (year, month) => {const firstDayOfMonth = moment([year, month - 1, 1]);const lastDayOfMonth = firstDayOfMonth.clone().endOf('month');return {startTime: firstDayOfMonth.format('YYYY-MM-DD'),endTime: lastDayOfMonth.format('YYYY-MM-DD'),}
}// 处理区间时间数据
const compareDates = (startYear, startMonth, startDay, startHours, startMins, endYear, endMonth, endDay, endHours, endMins) => {// 比较两个日期let startTime = `${startYear}-${startMonth}-${startDay} ${startHours.toString().padStart(2, '0')}:${startMins.toString().padStart(2, '0')}`;let endTime = `${endYear}-${endMonth}-${endDay} ${endHours.toString().padStart(2, '0')}:${endMins.toString().padStart(2, '0')}`;const startTime1 = JSON.parse(JSON.stringify(startTime))const endTime1 = JSON.parse(JSON.stringify(endTime))// 如果开始时间和结束时间相同,则提示用户if (startYear === endYear && startMonth === endMonth && startDay === endDay && startHours === endHours && startMins === endMins) {uni.showToast({title: '开始时间不能与截止时间一致',icon: 'none',})return}if (new Date(startTime1.replace(/-/g, '/')).getTime() < new Date().getTime()) {uni.showToast({title: '预约时间不能小于当前时间',icon: 'none',})return}if (new Date(endTime1.replace(/-/g, '/')).getTime() < new Date().getTime()) {uni.showToast({title: '预约时间不能小于当前时间',icon: 'none',})return}if (new Date(endTime1.replace(/-/g, '/')).getTime() < new Date(startTime1.replace(/-/g, '/')).getTime()) {uni.showToast({title: '开始时间不能大于截止时间',icon: 'none',})return}// 比较日期并交换如果必要return {startTime,endTime};
}// 时间比对参数调换
const swapIfGreater = (startTime, endTime) => {const startTimestamp = moment(startTime).valueOf();const endTimestamp = moment(endTime).valueOf();if (startTimestamp > endTimestamp) {return {startTime: endTime,endTime: startTime};}return {startTime,endTime};
}
</script><style lang="less" scoped>
.popup-container {position: relative;width: 100vw;height: 760rpx;padding: 40rpx 40rpx;box-sizing: border-box;background-color: #fff;.confirm-btn {width: 200rpx;height: 50rpx;background-color: #0f43a8;border-radius: 20rpx;color: #fff;display: flex;justify-content: center;align-items: center;font-size: 30rpx;position: absolute;bottom: 50rpx;left: 60%;}.confirm-btn2 {width: 200rpx;height: 50rpx;border: 1rpx solid #0f43a8;border-radius: 20rpx;color: #0f43a8;display: flex;justify-content: center;align-items: center;font-size: 30rpx;position: absolute;bottom: 50rpx;left: 10%;}.tabs {width: 100%;margin-bottom: 40rpx;}.content {margin-top: 40rpx;display: flex;justify-content: center;align-items: center;height: 560rpx;}.date-range-container {display: flex;justify-content: center;align-items: center;margin-bottom: 40rpx;margin-top: 40rpx;.date-separator {margin: 0 20rpx;}.date-input {border-bottom: 4rpx solid #ccc;padding: 10rpx 20rpx;margin: 0 20rpx;cursor: pointer;text-align: center;width: 50%;/* 固定宽度确保开始时间和结束时间一致 */height: 60rpx;/* 设置高度以确保一致 */line-height: 60rpx;/* 设置行高以居中内容 */}.date-input.selected {border-bottom: 4rpx solid #007bff;color: #007bff;}.content-range {margin-top: 40rpx;display: flex;justify-content: center;align-items: center;height: 400rpx;position: relative;.picker-view-column-label {width: 100%;position: absolute;top: 10rpx;left: 50%;z-index: 2000;}}}
}.picker-view {width: 100%;height: 400rpx;margin-top: 20rpx;position: relative;
}.header-buttons {display: flex;justify-content: space-between;padding: 0 40rpx;box-sizing: border-box;margin-bottom: 20rpx;.button--confirm {text-align: center;background-color: #007bff;color: #fff;width: 100%;font-size: 32rpx;font-weight: 400;line-height: 88rpx;/* 150% */}
}.item {line-height: 100rpx;text-align: center;
}
</style>
该组件在数据初始化以及选择做了限制,如果到当天晚上18点的时候,直接自动转到下一天。