uni-popup手寫菜鳥上門取件時(shí)間選擇器
引言
近期做的項(xiàng)目有個(gè)需求是做一個(gè)類似菜鳥的取件時(shí)間選擇器,去找了很久沒找到合適的,沒辦法只能自己收擼,經(jīng)過好幾個(gè)小版本修改之后也算是定型了,這里總結(jié)一篇文檔備忘,把源碼貼出來后續(xù)方便后續(xù)copy
技術(shù)
uniapp + vue2 + uni-popup
兼容
因?yàn)槟壳拔业捻?xiàng)目只用到這三端,其他的都還沒測(cè),所以兼容不保證
- 支付寶小程序開發(fā)者工具popup彈出來會(huì)直接滾到最頂部,顯示異常,但真機(jī)上面沒問題,可以不用管
環(huán)境 | 兼容 |
---|---|
支付寶小程序 | ? |
微信小程序 | ? |
H5 | ? |
菜鳥上門時(shí)間選擇器
需求分析:
1、彈窗從底部彈出
- 點(diǎn)擊蒙層不可關(guān)閉
- 彈窗header左側(cè)title , 右側(cè)關(guān)閉按鈕
2、左側(cè)日期選擇器
- 顯示近3天日期
- 顯示(今天、明天、周一、周二等)
3、右側(cè)時(shí)間選擇器
- 可選時(shí)間可配置
- 過期時(shí)間顯示 “已過期”
- 選中效果
- 當(dāng)前已無可選時(shí)間,應(yīng)該刪除今天日期,只可以選未來日期
代碼實(shí)現(xiàn):
1.popup彈窗
先做一下基礎(chǔ)布局,簡(jiǎn)單的分成上左右三大塊,并做一些基礎(chǔ)的配置
<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">請(qǐng)選擇取件時(shí)間</view> <view class="pop-close" @click="handleClose('datePop')" /> </view> <!-- 日期 --> <view class="date_con"> <scroll-view scroll-y="true" class="date_box"> </scroll-view> <!-- 時(shí)間 --> <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.日期+時(shí)間選擇器
按照需求我重新設(shè)計(jì)了一下功能及交互
日期選擇器
- 日期可配置,支持顯示最近n天日期
- 顯示今天、明天、后臺(tái)及工作日
- 默認(rèn)選中當(dāng)日(今天)
時(shí)間選擇器
基礎(chǔ)功能
- 刪除過期時(shí)間
- 今日所有可選日期都過期之后刪除日期選框(今天)選項(xiàng)
- 選中時(shí)間后面打鉤,并關(guān)閉彈窗
可選功能
- 顯示已過期時(shí)間 (邏輯幾個(gè)版本之前已經(jīng)刪除了,現(xiàn)在只剩類名,需要的同學(xué)可以大概看下代碼把它加上或者評(píng)論區(qū)留個(gè)言我把給你找找代碼 , 功能樣式就類似菜鳥)
- 直接刪除已過期時(shí)間
先看效果
????核心邏輯:
1、生成左側(cè)日期列表
// 生成時(shí)間選擇器 最近n天的時(shí)間 /** *@n {Number} : 生成的天數(shù) * */ 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 }); } //設(shè)置一下默認(rèn)選中日期 this.selectedDate = list[0]; return list; }, // 時(shí)間處理函數(shù) 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] }; },
最終數(shù)據(jù)格式如圖:
2、判斷時(shí)間有沒有過期
因?yàn)榭紤]到取件沒有那么快,至少要提前半小時(shí)下單,所以就有了下面的邏輯(我這里是90分鐘)
- 傳入 09:00-10:00 格式時(shí)間區(qū)間
- 截取過期時(shí)間, 和當(dāng)前時(shí)間做對(duì)比
- 判斷已過期 、即將過期 、未過期
/** * @return {Number} 1:已過期 , 2:即將過期 , 3:未過期 * @time {String} 09:00-10:00 */ checkRemainingMinute(time) { if (!time) return; //過期時(shí)間 const outTime = time.toString().split('-')[1]; // 這里兼容一下iphone,iphone不支持yyyy-mm-dd hh:mm 格式時(shí)間 ,分隔符換為 / 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; } /** * @description yyyy-mm-dd hh:mm * @author wangxinu * @export * @param {*} cent * @returns */ formatMinute: (date, separator = '-') => { 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) => { n = n.toString(); return n[1] ? n : '0' + n; }; return `${formatNumber(year)}${separator}${formatNumber(month)}${separator}${formatNumber( day, )} ${formatNumber(hour)}:${formatNumber(minute)}`; },
3、通過計(jì)算屬性獲取有效時(shí)間(即右側(cè)列表展示即將過期的和未過期的時(shí)間)
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: { // 有效取件時(shí)間 effectAppointmentTime() { //取件時(shí)間列表 const appointment = this.appointment; // 未來日期返回全部 if (this.selectedDate.date_en != this.currentDate) { return appointment; } // 當(dāng)日只返回有效時(shí)間 let list = appointment.filter((item) => this.checkRemainingMinute(item) != 1); // 當(dāng)天取件時(shí)間長(zhǎng)度>0 添加立即上門 if (list.length > 0) { list.unshift('立即上門'); } return list; } },
4、通過計(jì)算屬性獲取有效日期
computed: { // 有效日期 effectRecentDate() { //查看有效時(shí)間列表 const effectAppointmentTime = this.effectAppointmentTime; // 當(dāng)日取件時(shí)間全部失效 if (effectAppointmentTime.length == 0) { //刪除(今日) this.recentDateList.splice(0, 1); //修改默認(rèn)選中日期 this.selectedDate = this.recentDateList[0]; return this.recentDateList; } else { return this.recentDateList; } }, },
5、日期或時(shí)間選中函數(shù)
// 時(shí)間選擇器修改函數(shù) timeChange(date, type) { const dateList = this.recentDateList; if (type === 'date') { // 選擇日期 this.selectedDate = date; this.selectedTime = ''; } else { // 選擇時(shí)間 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">請(qǐng)選擇取件時(shí)間</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> <!-- 時(shí)間 --> <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; // 當(dāng)日取件時(shí)間全部失效 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; } }, // 有效取件時(shí)間 effectAppointmentTime() { const appointment = this.appointment; // 未來日期返回全部 if (this.selectedDate.date_en != this.currentDate) { return appointment; } let list = appointment.filter((item) => this.checkRemainingMinute(item) != 1); // 當(dāng)日只返回有效時(shí)間 if (list.length > 0) { list.unshift('立即上門'); } return list; } }, methods: { handleClose() { this.$emit('update:visible', false); }, // 生成時(shí)間選擇器 最近n天的時(shí)間 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; }, // 時(shí)間處理函數(shù) 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] }; }, // 時(shí)間選擇器修改函數(shù) timeChange(date, type) { const dateList = this.recentDateList; if (type === 'date') { // 選擇日期 this.selectedDate = date; this.selectedTime = ''; } else { // 選擇時(shí)間 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:
- 時(shí)間區(qū)域打開顯示對(duì)應(yīng)選中時(shí)間位置
- 右側(cè)時(shí)間列表改后臺(tái)返回
以上就是uni-popup手?jǐn)]了一個(gè)菜鳥上門取件時(shí)間選擇器的詳細(xì)內(nèi)容,更多關(guān)于uni popup取件時(shí)間選擇器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue解決使用webpack打包后keep-alive不生效的方法
今天小編就為大家分享一篇vue解決使用webpack打包后keep-alive不生效的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09Vue2.0利用vue-resource上傳文件到七牛的實(shí)例代碼
本篇文章主要介紹了Vue2.0利用vue-resource上傳文件到七牛的實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07基于vue2.0實(shí)現(xiàn)的級(jí)聯(lián)選擇器
這篇文章主要介紹了基于vue2.0實(shí)現(xiàn)的級(jí)聯(lián)選擇器,基于Vue的級(jí)聯(lián)選擇器,可以單項(xiàng),二級(jí), 三級(jí)級(jí)聯(lián),多級(jí)級(jí)聯(lián),有興趣可以了解一下2017-06-06element ui里dialog關(guān)閉后清除驗(yàn)證條件方法
下面小編就為大家分享一篇element ui里dialog關(guān)閉后清除驗(yàn)證條件方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-02-02手把手教你搭建一個(gè)vue項(xiàng)目的完整步驟
身為入行未深的小白前端,不斷的學(xué)習(xí)是我們不可丟失的習(xí)慣,前端流行的框架也是層出不窮,vue在眾多框架中脫穎而出,下面這篇文章主要給大家介紹了關(guān)于搭建一個(gè)vue項(xiàng)目的完整步驟,需要的朋友可以參考下2022-07-07vue awesome swiper異步加載數(shù)據(jù)出現(xiàn)的bug問題
這篇文章主要介紹了vue awesome swiper異步加載數(shù)據(jù)出現(xiàn)的bug問題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07解決vue項(xiàng)目 build之后資源文件找不到的問題
這篇文章主要介紹了解決vue項(xiàng)目 build之后資源文件找不到的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09