uni-popup手擼瞭一個菜鳥上門取件時間選擇器

引言

 近期做的項目有個需求是做一個類似菜鳥的取件時間選擇器,去找瞭很久沒找到合適的,沒辦法隻能自己收擼,經過好幾個小版本修改之後也算是定型瞭,這裡總結一篇文檔備忘,把源碼貼出來後續方便後續copy

技術

uniapp + vue2 + uni-popup

兼容

因為目前我的項目隻用到這三端,其他的都還沒測,所以兼容不保證

  • 支付寶小程序開發者工具popup彈出來會直接滾到最頂部,顯示異常,但真機上面沒問題,可以不用管
環境 兼容
支付寶小程序
微信小程序
H5

菜鳥上門時間選擇器

需求分析:

1、彈窗從底部彈出

  • 點擊蒙層不可關閉
  • 彈窗header左側title , 右側關閉按鈕

2、左側日期選擇器

  • 顯示近3天日期
  • 顯示(今天、明天、周一、周二等)

3、右側時間選擇器

  • 可選時間可配置
  • 過期時間顯示 “已過期”
  • 選中效果
  • 當前已無可選時間,應該刪除今天日期,隻可以選未來日期

代碼實現:

1.popup彈窗

先做一下基礎佈局,簡單的分成上左右三大塊,並做一些基礎的配置

<template>
	<uni-popup
		mask-background-color="rgba(0, 0, 0, .8)"
		ref="datePickerPop"
		type="bottom"
		background-color="#fff"
		:is-mask-click="false"
	>
		<view class="date_pop">
			<view class="popup_header">
				<view class="pop_title">請選擇取件時間</view>
				<view class="pop-close" @click="handleClose('datePop')" />
			</view>
			<!-- 日期 -->
			<view class="date_con">
				<scroll-view scroll-y="true" class="date_box">
				</scroll-view>
				<!-- 時間 -->
				<scroll-view scroll-y="true" class="time_box">
				</scroll-view>
			</view>
		</view>
	</uni-popup>
</template>
<script>
	export default {
		name: 'TimePicker',
		props: {
			visible: {
				required: true,
				default: false
			}
		},
		watch: {
			visible(newVal) {
				if (newVal) {
					if (!this.selectedDate.date_zh) {
						this.selectedDate = this.effectRecentDate[0];
					}
					this.$refs.datePickerPop.open();
				} else {
					this.$refs.datePickerPop.close();
				}
			}
		},
		methods: {
			handleClose() {
				this.$emit('update:visible', false);
			},
		}
	};
</script>
<style scoped lang="scss">
	.date_pop {
		padding: 0;
		height: 750rpx;
		.popup_header {
			display: flex;
			align-items: center;
			justify-content: space-between;
			box-sizing: border-box;
			padding: 60rpx 40rpx;
			.pop_title {
				font-weight: bold;
				font-size: 32rpx;
				width: 90%;
			}
			.pop-close {
				width: 60rpx;
				height: 60rpx;
				background: url('~@/static/images/close.png');
				background-size: 22rpx;
				background-position: center;
				background-repeat: no-repeat;
			}
		}
		.date_con {
			font-size: 28rpx;
			position: relative;
			height: 600rpx;
		}
		.date_box {
			position: absolute;
			top: 0;
			left: 0;
			width: 40%;
			height: 100%;
			background: #f7f7f9;
			overflow-y: scroll;
			.date_item {
				padding: 0 40rpx;
				line-height: 100rpx;
			}
		}
		.time_box {
			position: absolute;
			top: 0;
			right: 0;
			width: 60%;
			height: 100%;
		}
		.date_active {
			background: #fff;
		}
	}
</style>

2.日期+時間選擇器

按照需求我重新設計瞭一下功能及交互

日期選擇器

  • 日期可配置,支持顯示最近n天日期
  • 顯示今天、明天、後臺及工作日
  • 默認選中當日(今天)

時間選擇器

基礎功能

  • 刪除過期時間
  • 今日所有可選日期都過期之後刪除日期選框(今天)選項
  • 選中時間後面打鉤,並關閉彈窗

可選功能

  • 顯示已過期時間 (邏輯幾個版本之前已經刪除瞭,現在隻剩類名,需要的同學可以大概看下代碼把它加上或者評論區留個言我把給你找找代碼 , 功能樣式就類似菜鳥)
  • 直接刪除已過期時間

先看效果

🎉🎃核心邏輯:

1、生成左側日期列表

// 生成時間選擇器 最近n天的時間
/**
*@n {Number} : 生成的天數
*
*/
setRecentData(n) {
	const oneDaySeconds = 60 * 1000 * 60 * 24;
	const today = +new Date();
	let list = [];
	for (let i = 0; i &lt; n; i++) {
		let formatTime = this.formatTime_zh(today + oneDaySeconds * i);
		list.push({
			...formatTime,
			week: i == 0 ? '今天' : i == 1 ? '明天' : formatTime.week
		});
	}
        //設置一下默認選中日期
	this.selectedDate = list[0];
	return list;
},
// 時間處理函數
formatTime_zh(date){
	date = new Date(date);
	const year = date.getFullYear();
	const month = date.getMonth() + 1;
	const day = date.getDate();
	const weekDay = date.getDay();
	const formatNumber = (n) =&gt; {
		n = n.toString();
		return n[1] ? n : '0' + n;
	};
        const numToTxt = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
	return {
		date_zh: `${formatNumber(month)}月${formatNumber(day)}日`,
		date_en: `${year}/${formatNumber(month)}/${formatNumber(day)}`,
		week: numToTxt[weekDay]
	};
},

最終數據格式如圖:

2、判斷時間有沒有過期

因為考慮到取件沒有那麼快,至少要提前半小時下單,所以就有瞭下面的邏輯(我這裡是90分鐘)

  • 傳入 09:00-10:00 格式時間區間
  • 截取過期時間, 和當前時間做對比
  • 判斷已過期 、即將過期 、未過期
/**
* @return {Number} 1:已過期 , 2:即將過期 , 3:未過期
* @time   {String} 09:00-10:00
*/
checkRemainingMinute(time) {
	if (!time) return;
        //過期時間
	const outTime = time.toString().split('-')[1];
        // 這裡兼容一下iphone,iphone不支持yyyy-mm-dd hh:mm 格式時間 ,分隔符換為 /
	const fullYearDate = formatMinute(new Date(), '/');
	const now = new Date(fullYearDate);
	const dateTime = this.currentDate + ' ' + outTime;
	const check = new Date(dateTime);
	const difference = check - now;
	const minutes = difference / (1000 * 60);
	// minutes &lt;= 0  : 已過期    --&gt; 1
	// minutes &lt;= 90 : 即將過期  --&gt; 2
	// minutes &gt;  0  : 未過期     --&gt; 3
	return minutes &lt;= 0 ? 1 : minutes &lt;= 90 ? 2 : 3;
}
  /**
   * @description yyyy-mm-dd hh:mm
   * @author wangxinu
   * @export
   * @param {*} cent
   * @returns
   */
  formatMinute: (date, separator = '-') =&gt; {
    date = new Date(date);
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    const hour = date.getHours();
    const minute = date.getMinutes();
    const second = date.getSeconds();
    const formatNumber = (n) =&gt; {
      n = n.toString();
      return n[1] ? n : '0' + n;
    };
    return `${formatNumber(year)}${separator}${formatNumber(month)}${separator}${formatNumber(
      day,
    )} ${formatNumber(hour)}:${formatNumber(minute)}`;
  },

3、通過計算屬性獲取有效時間(即右側列表展示即將過期的和未過期的時間)

data(){
    return {
    	appointment: [
		'08:00-09:00',
		'09:00-10:00',
		'10:00-11:00',
		'11:00-12:00',
		'12:00-13:00',
		'13:00-14:00',
		'14:00-15:00',
		'15:00-16:00',
		'16:00-17:00',
		'17:00-18:00',
		'18:00-19:00',
		'19:00-20:00'
	]
    }
},
computed: {
	// 有效取件時間
   effectAppointmentTime() {
        //取件時間列表
	const appointment = this.appointment;
	// 未來日期返回全部
	if (this.selectedDate.date_en != this.currentDate) {
		return appointment;
	}
        // 當日隻返回有效時間
	let list = appointment.filter((item) =&gt; this.checkRemainingMinute(item) != 1);
	// 當天取件時間長度&gt;0 添加立即上門
	if (list.length &gt; 0) {
		list.unshift('立即上門');
	}
	return list;
   }
},

4、通過計算屬性獲取有效日期

computed: {
	// 有效日期
   effectRecentDate() {
        //查看有效時間列表
	const effectAppointmentTime = this.effectAppointmentTime;
	// 當日取件時間全部失效
            if (effectAppointmentTime.length == 0) {
                //刪除(今日)
		this.recentDateList.splice(0, 1);
                //修改默認選中日期
		this.selectedDate = this.recentDateList[0];
		return this.recentDateList;
            } else {
		return this.recentDateList;
            }
	},
},

5、日期或時間選中函數

	// 時間選擇器修改函數
	timeChange(date, type) {
		const dateList = this.recentDateList;
		if (type === 'date') {
			// 選擇日期
			this.selectedDate = date;
			this.selectedTime = '';
		} else {
			// 選擇時間
			this.selectedTime = date;
			if (this.selectedDate.date_zh == '') {
				this.selectedDate = dateList[0];
			}
                        this.handleClose();
                       	this.$emit('selectTime', this.selectedDate, this.selectedTime);
		}
	},

源碼及使用

使用:

<template>
	<div class="page">
		<button @click="timePicker_visible = true" type="primary">打開彈窗</button>
		<TimePicker :visible.sync="timePicker_visible" @selectTime="selectTime"/>
	</div>
</template>
<script>
	import TimePicker from './components/TimePicker';
	export default {
		name: 'test',
		components: { TimePicker },
		mixins: [],
		props: {},
		data() {
			return {
				timePicker_visible: false
			};
		},
                methods:{
                    selectTime(date,time){
                        console.log('date',date)
                        console.log('time',time)
                   }
                }
	};
</script>

源碼:

<template>
	<uni-popup
		mask-background-color="rgba(0, 0, 0, .8)"
		ref="datePickerPop"
		type="bottom"
		background-color="#fff"
		:is-mask-click="false"
	>
		<view class="date_pop">
			<view class="popup_header">
				<view class="pop_title">請選擇取件時間</view>
				<view class="pop-close" @click="handleClose('datePop')" />
			</view>
			<!-- 日期 -->
			<view class="date_con">
				<scroll-view scroll-y="true" class="date_box">
					<view
						v-for="date in effectRecentDate"
						:key="date.date_zh"
						:class="[`date_item`, selectedDate.date_zh == date.date_zh ? `date_active` : ``]"
						@click="timeChange(date, 'date')"
					>
						{{ date.date_zh }}({{ date.week }})
					</view>
				</scroll-view>
				<!-- 時間 -->
				<scroll-view scroll-y="true" class="time_box">
					<view
						v-for="(time, index) in effectAppointmentTime"
						:key="index"
						:class="{
							bottom: true,
							time_item: true,
							time_active: selectedTime === time
						}"
						@click="timeChange(effectAppointmentTime[index], `time`)"
					>
						{{ time }}
					</view>
				</scroll-view>
			</view>
		</view>
	</uni-popup>
</template>
<script>
	import { formatDate, toFixed, formatMinute } from '@/public/utils/utils';
	export default {
		name: 'TimePicker',
		props: {
			visible: {
				required: true,
				default: false
			}
		},
		watch: {
			visible(newVal) {
				if (newVal) {
					if (!this.selectedDate.date_zh) {
						this.selectedDate = this.effectRecentDate[0];
					}
					this.$refs.datePickerPop.open();
				} else {
					this.$refs.datePickerPop.close();
				}
			}
		},
		data() {
			// 生成取件日期
			const recentDayNum = 5;
			this.toFixed = toFixed;
			return {
				currentDate: formatDate(new Date(), '/'),
				selectedTime: '',
				selectedDate: {},
				recentDateList: this.setRecentData(recentDayNum),
				appointment: [
					'08:00-09:00',
					'09:00-10:00',
					'10:00-11:00',
					'11:00-12:00',
					'12:00-13:00',
					'13:00-14:00',
					'14:00-15:00',
					'15:00-16:00',
					'16:00-17:00',
					'17:00-18:00',
					'18:00-19:00',
					'19:00-20:00'
				]
			};
		},
		computed: {
			// 有效日期
			effectRecentDate() {
				const effectAppointmentTime = this.effectAppointmentTime;
				// 當日取件時間全部失效
				if (effectAppointmentTime.length == 0) {
					this.recentDateList.splice(0, 1);
					this.selectedDate = this.recentDateList[0];
					console.log('this.selectedDate: ', this.selectedDate);
					return this.recentDateList;
				} else {
					return this.recentDateList;
				}
			},
			// 有效取件時間
			effectAppointmentTime() {
				const appointment = this.appointment;
				// 未來日期返回全部
				if (this.selectedDate.date_en != this.currentDate) {
					return appointment;
				}
				let list = appointment.filter((item) => this.checkRemainingMinute(item) != 1);
				// 當日隻返回有效時間
				if (list.length > 0) {
					list.unshift('立即上門');
				}
				return list;
			}
		},
		methods: {
			handleClose() {
				this.$emit('update:visible', false);
			},
			// 生成時間選擇器 最近n天的時間
			setRecentData(n) {
				const oneDayTime = 60 * 1000 * 60 * 24;
				const today = +new Date();
				let list = [];
				for (let i = 0; i < n; i++) {
					let formatTime = this.formatTime_zh(today + oneDayTime * i);
					list.push({
						...formatTime,
						week: i == 0 ? '今天' : i == 1 ? '明天' : formatTime.week
					});
				}
				this.selectedDate = list[0];
				return list;
			},
			// 時間處理函數
			formatTime_zh: (date) => {
				date = new Date(date);
				const year = date.getFullYear();
				const month = date.getMonth() + 1;
				const day = date.getDate();
				const weekDay = date.getDay();
				const formatNumber = (n) => {
					n = n.toString();
					return n[1] ? n : '0' + n;
				};
				const numToTxt = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
				return {
					date_zh: `${formatNumber(month)}月${formatNumber(day)}日`,
					date_en: `${year}/${formatNumber(month)}/${formatNumber(day)}`,
					week: numToTxt[weekDay]
				};
			},
			// 時間選擇器修改函數
			timeChange(date, type) {
				const dateList = this.recentDateList;
				if (type === 'date') {
					// 選擇日期
					this.selectedDate = date;
					this.selectedTime = '';
				} else {
					// 選擇時間
					this.selectedTime = date;
					if (this.selectedDate.date_zh == '') {
						this.selectedDate = dateList[0];
					}
					this.handleClose();
					this.$emit('selectTime', this.selectedDate, this.selectedTime);
				}
			},
			/**
			 * @return {Number} 1:已過期 , 2:即將過期 , 3:未過期
			 */
			checkRemainingMinute(time) {
				console.log('time: ', time);
				if (!time) return;
				const outTime = time.toString().split('-')[1];
				const fullYearDate = formatMinute(new Date(), '/');
				const now = new Date(fullYearDate);
				const dateTime = this.currentDate + ' ' + outTime;
				const check = new Date(dateTime);
				const difference = check - now;
				const minutes = difference / (1000 * 60);
				// minutes <= 0  : 已過期    --> 1
				// minutes <= 90 : 即將過期  --> 2
				// minutes >  0  : 未過期     --> 3
				return minutes <= 0 ? 1 : minutes <= 90 ? 2 : 3;
			}
		}
	};
</script>
<style scoped lang="scss">
	.date_pop {
		padding: 0;
		height: 750rpx;
		.popup_header {
			display: flex;
			align-items: center;
			justify-content: space-between;
			box-sizing: border-box;
			padding: 60rpx 40rpx;
			.pop_title {
				font-weight: bold;
				font-size: 32rpx;
				width: 90%;
			}
			.pop-close {
				width: 60rpx;
				height: 60rpx;
				background: url('~@/static/images/close.png');
				background-size: 22rpx;
				background-position: center;
				background-repeat: no-repeat;
			}
		}
		.date_con {
			font-size: 28rpx;
			position: relative;
			height: 600rpx;
		}
		.date_box {
			position: absolute;
			top: 0;
			left: 0;
			width: 40%;
			height: 100%;
			background: #f7f7f9;
			overflow-y: scroll;
			.date_item {
				padding: 0 40rpx;
				line-height: 100rpx;
			}
                        .date_active {
                            background: #fff;
                        }
		}
		.time_box {
			position: absolute;
			top: 0;
			right: 0;
			width: 60%;
			height: 100%;
			.disabled {
				color: #ccc;
				&::after {
					content: '已過期';
					margin-left: 130rpx;
				}
			}
			.outTime {
				color: #ccc;
				&::after {
					content: '即將過期';
					margin-left: 100rpx;
				}
			}
			.time_item {
				padding: 0 40rpx;
				line-height: 100rpx;
			}
		}
		.time_active {
			color: #ff5b29;
			position: relative;
			&::after {
				position: absolute;
				content: '✔';
				right: 15%;
				margin: auto;
			}
		}
	}
</style>

TODO:

  • 時間區域打開顯示對應選中時間位置
  • 右側時間列表改後臺返回

以上就是uni-popup手擼瞭一個菜鳥上門取件時間選擇器的詳細內容,更多關於uni popup取件時間選擇器的資料請關註WalkonNet其它相關文章!

推薦閱讀: