在开发过程中,经常会遇到需要用户选择时间段的需求。本文将介绍如何使用 Vue.js 构建一个简单的时间选择器组件,该组件允许用户选择服务的开始时间和结束时间,并确保选择的时间段满足一定的业务逻辑要求。

Vue/uniapp 实现自定义时间段选择器组件_数据


组件功能概述

  1. 选择服务时间:用户可以从上午和下午两个时间段中选择服务的开始时间和结束时间。
  2. 验证时间间隔:确保用户选择的服务时间至少为2小时。
  3. 显示服务时长:展示用户选择的服务总时长。

组件实现步骤

1. 组件结构设计

组件的基本结构包括一个弹出框,其中包含选择服务时间的界面以及确定按钮。

<template>
	<u-popup :show="show" mode="bottom" @close="close" @open="open" :closeable="true">
		<view class="">
			<view class="title">选择服务时间 </view>
			
			<!-- 显示服务时间 -->
			<view class="workTime">
				<!-- 开始时间 -->
				<view :class="timeType=='am'?'t1 tbottom':'t1'" @click="choseType('am')">{{startTime}}</view>
				<!-- 服务时长 -->
				<view class="t2">{{workTime}}小时</view>
				<!-- 结束时间 -->
				<view :class="timeType=='pm'?'t1 tbottom':'t1'" @click="choseType('pm')">{{endTime}}</view>
			</view>
			
			<!-- 上午/下午选择 -->
			<view class="workTime">
				<view :class="showType=='am'?'t1 typecolor' :'t1'" @click="showType='am';"
				style="border: none;">00:00-11:59</view>
				<view class="t2"> </view>
				<view :class="showType=='pm'?'t1 typecolor' :'t1'" @click="showType='pm'"
				style="border: none;">12:00-23:59</view>
			</view>
			
			<!-- 时间选择列表 -->
			<view class="timebox">
				<view class="time" v-if="showType=='am'">
					<view v-for="(item,index) in amlist" :key="index" :class="startTime==item ? 'activetime' : ''"
					@click="changetime(item,'am')">
						{{item}}
					</view>
				</view>
				
				<view class="time" v-if="showType=='pm'">
					<view v-for="(it,i) in pmlist" :key="i" :class="endTime==it ? 'activetime' : ''"
					@click="changetime(it,'pm')">
						{{it}}
					</view>
				</view>
			</view>
			
			<!-- 确认按钮 -->
			<view class="woodsbtn btn" @click="handleTime">
				确定
			</view>
		</view>
	</u-popup>
</template>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.

2. 数据和方法定义

接下来定义组件的数据和相关方法。

export default {
	name:"choseTime",
	data() {
		return {
			show: false,
			startTime: "",
			endTime: "",
			amlist: ["7:00", "7:30", "8:00", "8:30", "9:00", "9:30"],
			pmlist: ["12:00", "13:30", "14:00", "19:30"],
			workTime: "0",
			timeType: "am",
			showType: "am"
		};
	},
	methods: {
		handleTime() {
			if (this.workTime == 0) {
				uni.showToast({
					title: '请选择服务时间',
					icon: 'none'
				});
				return;
			}
			this.$emit('handleTime', {
				startTime: this.startTime,
				endTime: this.endTime,
				workTime: this.workTime
			});
			this.show = false;
		},
		open() {
			// console.log('open');
		},
		close() {
			this.show = false;
		},
		choseType(type) {
			this.timeType = type;
		},
		changetime(item, type) {
			if (type == 'am') {
				this.startTime = item;
				this.endTime = '';
				this.workTime = "0";
				this.timeType = 'pm';
			} else if (type == 'pm') {
				if (!this.startTime) {
					return uni.showToast({
						title: "请先选择开始时间",
						icon: "none"
					});
				}
				if (isEndTimeValid(this.startTime, item)) {
					this.endTime = item;
				} else {
					return uni.showToast({
						title: "服务时间至少2小时",
						icon: "none"
					});
				}
				if (this.startTime && this.endTime) {
					this.workTime = calculateTimeDifference(this.startTime, this.endTime);
				}
			}
		},
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.

3. 样式定义

定义组件的样式以使其更美观。

<style scoped lang="scss">
.title{
	text-align: center;
	line-height: 50rpx;
	font-weight: bold;
	padding: 30rpx;
	
}
.workTime{
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 20rpx;
	.t1{
		border-bottom: 1px solid #EEEEEE;
		width: 220rpx;
		margin: 0 30rpx;
		height:60rpx;
		text-align: center;
	}
	.t2{
		height: 60rpx;
		width: 160rpx;
		text-align: center;
		font-size: 28rpx;
		color: #999999;
	}
	.typecolor{
		font-weight: bold;
		font-size: 32rpx;
		color: #277EEF;
	}
	.tbottom{
		border-bottom: 1px solid #277EEF;
	}
}
.timebox{
	padding: 10rpx 0;
	.timetitile{
		font-size: 36rpx;
		font-weight: bold;
		padding: 0 30rpx;
	}
	.time{
		display: flex;
		align-items: center;
		flex-wrap: wrap;
		margin-top: 20rpx;
		margin-bottom: 60rpx;
		>view{
			// width: 120rpx;
			padding:20rpx 35rpx;
			margin: 15rpx;
			// height: 60rpx;
			 
			background-color: #F2F2F2;
			border-radius: 8rpx
		}
		.activetime{
			border: 1px solid #277EEF;
			color: #277EEF;
		}
	}
}
.chosetime{
	position: fixed;
	left: 5%;
	bottom: 80rpx;
	width: 90%;
	background: linear-gradient(#FF7134,#FF3A3E);
	border-radius:20rpx;
	padding: 20rpx;
	display: flex;
	align-items: center;
	justify-content: space-between;
	.tleft{
		color: #fff;
		text{
			font-size: 36rpx;
			color: #fff;
		}
	}
	.tright{
		width: 200rpx;
		height:80rpx;
		background: #ffc368;
		border-radius: 12rpx;
		text-align: center;
		line-height: 80rpx;
		color: #594423;
	}
}
.btn{
	width: 90%;
	margin-bottom: 40rpx;
}

</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.

4. 实现验证逻辑

项目要求时间段至少为2小时,为了确保用户选择的服务时间至少为2小时,我们需要定义一个辅助函数 isEndTimeValid 来进行验证。

isEndTimeValid 函数实现
function isEndTimeValid(startTimeStr, endTimeStr) {
  const [startHours, startMinutes] = startTimeStr.split(':').map(Number);
  const [endHours, endMinutes] = endTimeStr.split(':').map(Number);

  const twoHoursLaterHours = startHours + 2;

  const isNextDay = twoHoursLaterHours >= 24;
  const twoHoursLaterHoursAdjusted = isNextDay ? twoHoursLaterHours - 24 : twoHoursLaterHours;

  if (endHours > twoHoursLaterHoursAdjusted) {
    return true; // 结束时间在2小时后的一天
  } else if (endHours === twoHoursLaterHoursAdjusted) {
    return endMinutes >= startMinutes; // 同一天,检查分钟
  } else {
    return false; // 结束时间小于2小时后的开始时间
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

5. 使用组件

在父组件中引入并使用此时间选择器组件。

<template>
	<view>
		<chose-time ref="timePicker" @handleTime="onHandleTime"></chose-time>
	</view>
</template>

<script>
import ChoseTime from './ChoseTime.vue';

export default {
	components: {
		ChoseTime
	},
	methods: {
		onHandleTime(data) {
			console.log("Selected Time:", data);
			// 可以在这里处理选中的时间数据
		}
	}
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.