手撸【菜鸟上门取件】时间选择器

您所在的位置:网站首页 快递如何上门取件 手撸【菜鸟上门取件】时间选择器

手撸【菜鸟上门取件】时间选择器

2024-04-16 16:22| 来源: 网络整理| 查看: 265

背景: 近期做的项目有个需求是做一个类似菜鸟的取件时间选择器,去找了很久没找到合适的,没办法只能自己收撸,经过好几个小版本修改之后也算是定型了,这里总结一篇文档备忘,把源码贴出来后续方便后续copy

技术

uniapp + vue2 + uni-popup

兼容

因为目前我的项目只用到这三端,其他的都还没测,所以兼容不保证

支付宝小程序开发者工具popup弹出来会直接滚到最顶部,显示异常,但真机上面没问题,可以不用管 环境兼容支付宝小程序✔微信小程序✔H5✔ 菜鸟上门时间选择器

image-20220818162604842

需求分析:

1、弹窗从底部弹出

点击蒙层不可关闭 弹窗header左侧title , 右侧关闭按钮

2、左侧日期选择器

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

3、右侧时间选择器

可选时间可配置 过期时间显示 “已过期” 选中效果 当前已无可选时间,应该删除今天日期,只可以选未来日期 代码实现: 1.popup弹窗

先做一下基础布局,简单的分成上左右三大块,并做一些基础的配置

image.png

请选择取件时间 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); }, } }; .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; } } 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 < 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) => { 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] }; },

最终数据格式如图:

image.png

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 1 // minutes 2 // minutes > 0 : 未过期 --> 3 return minutes { 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) => this.checkRemainingMinute(item) != 1); // 当天取件时间长度>0 添加立即上门 if (list.length > 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); } }, 3.源码及使用 使用: 打开 import TimePicker from '@/components/TimePicker'; export default { components: { TimePicker }, data() { return { timePickerConf: { reservationEndTime: '20:00', //截止时间 reservationStartTime: '9:00', // 开始时间 reservationTimeDifference: 60 // 时间差(min)(demo: 可选时间18点,当前17点10分,离18点刚好不到60分钟,选择器要干掉这个时间点) }, timePicker_visible: false, selectedDate: {}, selectedTime: '' }; }, methods: { selectTime(date, time) { this.selectedDate = date; this.selectedTime = time; } } }; 源码: 请选择取件时间 {{ date.date_zh }}({{ date.week }}) {{ time }} 取消 提交 /** * @props * visible : 用于控制弹窗显隐 * showFooter : 是否显示底部按钮 * selectedDateProp : 默认选中日期 * selectedTimeProp : 默认选中时间 * timeConfig : 选择器基本配置 * autoClose : 选择时间后是否自动关闭 * @methods * selectTime: return function(){return {date , time}} * handleSubmit: 点击footer 提交的回调 * handleCancel: 点击footer 取消的回调 */ import dayjs from 'dayjs'; // import cloneDeep from 'lodash/cloneDeep'; export default { name: 'TimePicker', props: { visible: { required: true, default: false }, showFooter: { required: false, default: false }, autoClose: { required: false, default: true }, selectedDateProp: { required: true, type: Object, default: {} }, selectedTimeProp: { required: true, type: String, default: '' }, timeConfig: { required: true, type: Object, default: () => { return { reservationStartTime: '9:00', reservationEndTime: '20:00', reservationTimeDifference: 60 }; } } }, computed: { scrollIntoViewId() { return `time_${this.selectedTimeIdx}`; } }, watch: { timeConfig(newVal, oldVal) { if (newVal) { this.loadReservationTime(); } }, visible: { handler: function (newVal) { if (newVal) { this.setEffectAppointmentTime(); this.setEffectRecentDate(); this.$refs.datePickerPop.open(); } else { this.$refs.datePickerPop.close(); } } } }, data() { // 生成取件日期 const defaultTimeList = [ '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' ]; return { timePickerConfig: { dayCount: 3, /** 默认时间列表起始结束时间 */ timeListStartHour: 9, timeListEndHour: 20, delayMinutes: 60 }, currentDate: dayjs().format('YYYY/MM/DD'), selectedTime: '', selectedTimeIdx: 0, defaultTimeList, selectedDate: {}, recentDateList: [], effectRecentDate: [], effectAppointmentTime: [], appointment: [] }; }, created() { this.setRecentData(); this.loadReservationTime(); // 自动选中 setTimeout(() => { if (!this.selectedDate.date_zh) { this.selectedDate = this.effectRecentDate[0]; } this.setEffectAppointmentTime(); this.setEffectRecentDate(); this.selectedTime = this.effectAppointmentTime[0]; this.selectedDate = this.effectRecentDate[0]; this.$emit('update:selectedDateProp', this.effectRecentDate[0]); this.$emit('update:selectedTimeProp', this.effectAppointmentTime[0]); }, 1000); }, methods: { submit() { this.$emit('handleSubmit', this.selectedDate, this.selectedTime); }, loadReservationTime() { const conf = this.timeConfig; const newConfig = Object.assign(this.timePickerConfig, { timeListStartHour: Number(conf?.reservationStartTime.replace(':00', '')), timeListEndHour: Number(conf?.reservationEndTime.replace(':00', '')), delayMinutes: conf?.reservationTimeDifference }); this.timePickerConfig = newConfig; this.createTimeList(); }, createTimeList() { const { timeListStartHour, timeListEndHour } = this.timePickerConfig; const createList = (item) => { let hour = Number(item.slice(0, 2)); if (hour >= timeListStartHour && hour < timeListEndHour) { return item; } }; const setEffectList = (val) => val; const timeList = this.defaultTimeList.map(createList).filter(setEffectList); this.effectAppointmentTime = timeList; this.appointment = timeList; }, timeSelect(type, index, date) { if (type === 'time') { this.selectedTimeIdx = index; this.timeChange(this.effectAppointmentTime[index], `time`); } else { this.timeChange(date, 'date'); this.selectedTimeIdx = 0; } }, /** * @description: 时间选择器修改函数 * @param {date} : 选择的日期或时间 * @return {type} : 选择的类型, date or time */ timeChange(date, type) { const dateList = this.recentDateList; if (type === 'date') { // 选择日期 this.selectedDate = date; this.selectedTime = ''; this.setEffectAppointmentTime(); } else { // 选择时间 if (this.selectedDate.date_zh == '') { this.selectedDate = dateList[0]; } this.selectedTime = date; // 自动关闭弹窗 if (this.autoClose) { this.handleClose(); } } this.$emit('update:selectedDateProp', this.selectedDate); this.$emit('update:selectedTimeProp', this.selectedTime); this.$emit('selectTime', this.selectedDate, this.selectedTime); }, // 有效日期 setEffectRecentDate() { if (this.effectAppointmentTime.length > 0) { // this.effectRecentDate = cloneDeep(this.recentDateList); this.effectRecentDate = JSON.parse(JSON.stringify(this.recentDateList)); return; } let list = this.recentDateList; list.splice(0, 1); // 当日取件时间全部失效 this.effectRecentDate = list; this.selectedDate = this.recentDateList[0]; this.setEffectAppointmentTime(); }, // 有效取件时间 setEffectAppointmentTime() { const appointment = this.appointment; // 未来日期返回全部 if (this.selectedDate.date_en != this.currentDate) { this.effectAppointmentTime = appointment; return; } // 当日 let list = appointment.filter((item) => this.checkRemainingMinute(item) === 3); const { timeListStartHour, timeListEndHour } = this.timePickerConfig; // 当日只返回有效时间 if ( new Date().getHours() >= timeListStartHour && new Date().getHours() < timeListEndHour - 1 ) { list.unshift('立即上门'); } this.effectAppointmentTime = list; }, handleClose() { this.$emit('handleCancel', this.selectedDate, this.selectedTime); this.$emit('update:visible', false); }, // 生成时间选择器 最近n天的时间 setRecentData(n = this.timePickerConfig.dayCount) { 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 ? '明天' : i == 2 ? '后天' : formatTime.week }); } this.selectedDate = list[0]; this.recentDateList = 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] }; }, /** * @return {Number} 1:已过期 , 2:即将过期 , 3:未过期 */ checkRemainingMinute(time) { const delayMinutes = this.timePickerConfig?.delayMinutes ?? 60; if (!time) return; const outTime = time.toString().split('-')[0]; const now = dayjs().valueOf(); const dateTime = this.currentDate + ' ' + outTime; const check = dayjs(dateTime).valueOf(); const difference = check - now; const minutes = difference / (1000 * 60); // minutes 1 // minutes 2 // minutes > 0 : 未过期 --> 3 return minutes


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3