uniapp自定义一个选择年月日时分的组件。

发布于:2025-05-01 ⋅ 阅读:(29) ⋅ 点赞:(0)
<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, // 年份减去2020
		date.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 = value
			startDateText.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 = value
			endDateText.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点的时候,直接自动转到下一天。


网站公告

今日签到

点亮在社区的每一天
去签到