前端專案框架搭建隨筆---DatePicker元件編寫
距離上次更新13天了。。。
時間過的可真快
好的,讓我們切入正題:
今天我們來做一個 DatePicker(時間選擇器)
如圖:
由於時間緊迫,只實現了 傳入開始時間和結束時間的功能
使用外掛:better-scroll, ydui的時間處理工具類
我們先上HTML和CSS程式碼
HTML+CSS
<template> <div class="zb-picker--datetime-wrapper"> <div class="zb-picker--datetime__indicator"> <div class="indicator__top"></div> <div class="indicator__main"></div> // 中間的指示器 <div class="indicator__bottom"></div> </div> <div class="zb-picker--datetime__header"> <span>取消</span> <span>{{getSelectTime}}</span> <span>確定</span> </div> <div class="zb-picker--datetime__content"> <div class="picker--datetime-content__item datetime--years"> <ul class="picker--datetime-content__item__box wheel-scroll"> <li class="wheel-item" v-for="i in dateItem.yearsItem">{{i.name}}</li> //年份列表 </ul> </div> <div class="picker--datetime-content__item datetime--months"> <ul class="picker--datetime-content__item__box wheel-scroll"> <li class="wheel-item" v-for="i in dateItem.monthsItem">{{i.name}}</li> //月份列表 </ul> </div> <div class="picker--datetime-content__item datetime--days"> <ul class="picker--datetime-content__item__box wheel-scroll"> <li class="wheel-item" v-for="i in dateItem.daysItem">{{i.name}}</li> //天列表 </ul> </div> <div class="picker--datetime-content__item datetime--hours"> <ul class="picker--datetime-content__item__box wheel-scroll"> <li class="wheel-item" v-for="i in dateItem.hoursItem">{{i.name}}</li> //時列表 </ul> </div> <div class="picker--datetime-content__item datetime--mins"> <ul class="picker--datetime-content__item__box wheel-scroll"> <li class="wheel-item" v-for="i in dateItem.minsItem">{{i.name}}</li> //分列表 </ul> </div> </div> </div> </template>複製程式碼
<style scoped> * { color: black; } .zb-picker--datetime-wrapper { transition: transform .25s, -webkit-transform .25s; touch-action: none; will-change: transform; z-index: 1600; position: fixed; bottom: 0; background-color: white; width: 100%; } .zb-picker--datetime__header { display: flex; justify-content: space-between; position: relative; } .zb-picker--datetime__header > span { align-items: center; padding: 1rem 2.6rem; font-size: 1.5rem; cursor: pointer; user-select: none; } .zb-picker--datetime__header > span:last-child { color: #1890FF; } .zb-picker--datetime__content { height: 22rem; display: flex; flex-direction: row; } .picker--datetime-content__item { flex: 1; text-align: center; overflow: hidden; } .picker--datetime-content__item > .picker--datetime-content__item__box { display: flex; flex-direction: column; margin-top: 9rem; } .picker--datetime-content__item__box > li { height: 3rem; line-height: 3rem; font-size: 1.3rem; user-select: none; } .zb-picker--datetime__indicator { position: absolute; width: 100%; height: 100%; display: flex; flex-direction: column; padding-top: 4rem; } .zb-picker--datetime__indicator .indicator__main { position: relative; /*border: 1px solid #ECECEC;*/ border-right-width: 0; border-left-width: 0; flex: 1; } .indicator__top { z-index: 1600; pointer-events: none; flex: 4; background: linear-gradient(0deg, hsla(0, 0%, 100%, .4), hsla(0, 0%, 100%, .8)); } .indicator__bottom { z-index: 1600; pointer-events: none; flex: 6; background: linear-gradient(0deg, hsla(0, 0%, 100%, .4), hsla(0, 0%, 100%, .8)); } </style> 複製程式碼
data/props裡的變數:
props: { startTime: { // 開始時間 required: false }, endTime: { // 結束時間 required: false } }, data() { return { scrollParam: { //scroll通用引數 scrollY: true, bounce: true, wheel: { selectedIndex: 0, rotate: 25, wheelWrapperClass: 'wheel-scroll', wheelItemClass: 'wheel-item' } }, dateItem: { yearsItem: [], //年份列表 monthsItem: [], //月份列表 daysItem: [], //天數列表 hoursItem: [], //時列表 minsItem: [] //分列表 }, selectItem: { year: { //當前選擇的年份 value: 0, index: 0 }, month: { //當前選擇的月份 value: 0, index: 0 }, day: { //當前選擇的天 value: 0, index: 0 }, hour: { //當前選擇的時 value: 0, index: 0 }, min: { //當前選中的分 value: 0, index: 0 }, } } },複製程式碼
還有兩個import
import BScroll from 'better-scroll' import dateUtils from '@/utils/tools/DateUtils'複製程式碼複製程式碼
實現思路
首先我們先列出開發中的預知問題:
- 天數多月份向天數少月份轉換問題
- 開始時間和結束時間日期圈定
- 更改年份後同步更改子時間單位
看起來很容易解決,但串聯起來不容易
思考了半天,想了這麼一種解決思路:
仔細閱讀的小夥伴可能就瞭解了,我這種解決思路是層層遞減的。也就是選中我所選擇的單位時,只重新整理他的子單位列表,不重新整理父單位列表。
上程式碼:
對此我們二次封裝了一堆獲取日期列表的方法,供大家參考:
getYearsItems(startDate, endDate) { //獲取年列表 return dateUtils.getYearItems({ format: '{value}年', startDate: startDate ? startDate : '', endDate: endDate ? endDate : '' }) }, getMonthItems(currentYear, startDate, endDate) { //獲取月列表 return dateUtils.getMonthItems({ format: '{value}月', startDate: startDate ? startDate : '', endDate: endDate ? endDate : '', currentYear: currentYear ? currentYear : '' }) }, getDaysItems(currentYear, currentMonth, startDate, endDate) { //獲取天列表 return dateUtils.getDayItems({ format: '{value}日', startDate: startDate ? startDate : '', endDate: endDate ? endDate : '', currentYear: currentYear ? currentYear : '', currentMonth: currentMonth ? currentMonth : '', }) }, getHoursItems(currentYear, currentMonth, currentDay, startHour, endHour, startDate, endDate) { return dateUtils.getHourItems({ //獲取時列表 format: '{value}時', startDate: startDate ? startDate : '', endDate: endDate ? endDate : '', currentYear: currentYear ? currentYear : '', currentMonth: currentMonth ? currentMonth : '', currentDay: currentDay ? currentDay : '', startHour: startHour ? startHour : 0, endHour: endHour ? endHour : 23 }) }, getMinsItems(currentYear, currentMonth, currentDay, currentHour, startDate, endDate) { return dateUtils.getMinuteItems({ //獲取分列表 format: '{value}分', startDate: startDate ? startDate : '', endDate: endDate ? endDate : '', currentYear: currentYear ? currentYear : '', currentMonth: currentMonth ? currentMonth : '', currentDay: currentDay ? currentDay : '', currentHour: currentHour ? currentHour : '', }) },複製程式碼複製程式碼
獲取時列表那裡還需要傳入開始時和結束時。空的話預設0時 - 23時
初始化:
mounted() { if (this.startTime && this.endTime) { //如果含有開始和結束時間 this.dateItem.yearsItem = this.getYearsItems(this.startTime, this.endTime); //按照開始結束時間獲取年份列表 this.dateItem.monthsItem = this.getMonthItems(new Date(this.startTime).getFullYear(), this.startTime, this.endTime); //同上 this.dateItem.daysItem = this.getDaysItems(new Date(this.startTime).getFullYear(), new Date(this.startTime).getMonth() + 1, this.startTime, this.endTime);//同上 this.dateItem.hoursItem = this.getHoursItems(new Date(this.startTime).getFullYear(), new Date(this.startTime).getMonth() + 1, new Date(this.startTime).getDate(), new Date(this.startTime).getHours(), 23, this.startTime, this.endTime);//同上 this.dateItem.minsItem = this.getMinsItems(new Date(this.startTime).getFullYear(), new Date(this.startTime).getMonth() + 1, new Date(this.startTime).getDate(), new Date(this.startTime).getHours(), this.startTime, this.endTime)//同上 } else { //如果不存在開始結束時間 this.dateItem.yearsItem = this.getYearsItems(); //預設為空 獲取當前日期前後100年的時間 this.dateItem.monthsItem = this.getMonthItems(); //預設為空 獲取當前日期前後100年的時間 this.dateItem.daysItem = this.getDaysItems(); //預設為空 獲取當前日期前後100年的時間 this.dateItem.hoursItem = this.getHoursItems();//預設為空 獲取當前日期前後100年的時間 this.dateItem.minsItem = this.getMinsItems(); //預設為空 獲取當前日期前後100年的時間 } this.$nextTick(() => { //data更新後 //初始化當前選擇項 this.selectItem.year.value = this.dateItem.yearsItem[0].value; //獲取時間列表第一個的value給預設值 this.selectItem.year.index = 0;//初始化日期選擇列表的index為0 this.selectItem.month.value = this.dateItem.monthsItem[0].value; this.selectItem.month.index = 0; this.selectItem.day.value = this.dateItem.daysItem[0].value; this.selectItem.day.index = 0; this.selectItem.hour.value = this.dateItem.hoursItem[0].value; this.selectItem.hour.index = 0; this.selectItem.min.value = this.dateItem.minsItem[0].value; this.selectItem.min.index = 0; this.initScroll();//初始化scroll }); },複製程式碼
註釋已經寫的很清楚了~~~~
接下來我們以年列表滾動監聽事件為註釋例子,方便大家理解(initScroll方法):
let self = this; //新建變數重新指向this //年列表滾動 let YearsScroll = new BScroll(document.getElementsByClassName("datetime--years")[0], this.scrollParam); //初始化scroll YearsScroll.on('scrollEnd', () => { //scrollEnd監聽事件 let selectYear = self.dateItem.yearsItem[YearsScroll.getSelectedIndex()].value; //獲取當前選中的年份 let selectMonth = self.dateItem.monthsItem[MonthsScroll.getSelectedIndex()].value; //獲取當前選中的月份 let selectDay = self.selectItem.day.value;//獲取當前選中的天 self.selectItem.year.value = selectYear; //更新選中的年份 self.selectItem.year.index = YearsScroll.getSelectedIndex();//更新選中的年份 if (self.startTime && self.endTime) {//如果有開始結束日期 if (new Date(self.startTime).getFullYear() === selectYear) { //如果開始年份===當前選擇年份 self.$nextTick(() => { // 重新重新整理月份列表 傳入開始日期的年份,開始的時間和結束時間 self.dateItem.monthsItem = self.getMonthItems(new Date(self.startTime).getFullYear(), self.startTime, self.endTime); // 更新當前選中月份 self.fixMonthDateBug(); }); self.$nextTick(() => { // 重新重新整理天份列表 傳入開始日期的年份,開始時間的月份,開始的時間和結束時間 self.dateItem.daysItem = self.getDaysItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, self.startTime, self.endTime); // 更新當前選中天 self.fixDaysDateBug(); }); self.$nextTick(() => { // 重新重新整理時列表 傳入開始日期的年份,開始時間的月份,開始時間的天份,開始時刻-23時。 self.dateItem.hoursItem = self.getHoursItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, new Date(self.startTime).getDate(), new Date(self.startTime).getHours(), 23); // 更新當前選中時 self.fixHourDateBug(); }); self.$nextTick(() => { // 重新重新整理分列表 傳入開始日期的年份,開始時間的月份,開始時間的天份,開始時刻,開始的時間和結束時間 self.dateItem.minsItem = self.getMinsItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, new Date(self.startTime).getDate(), new Date(self.startTime).getHours(), self.startTime, self.endTime); // 更新當前選中分 self.fixMinDateBug(); }); return false; } else if (new Date(self.endTime).getFullYear() === selectYear) { //如果是結束時間 與開始時間同理 // 您選中了最後一個年份 self.dateItem.monthsItem = self.getMonthItems( new Date(self.endTime).getFullYear(), self.startTime, self.endTime); self.$nextTick(() => { self.fixMonthDateBug(); }); self.$nextTick(() => { self.dateItem.daysItem = self.getDaysItems( new Date(self.endTime).getFullYear(), self.startTime, self.endTime); self.fixDaysDateBug(); }); self.$nextTick(() => { self.dateItem.hoursItem = self.getHoursItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, new Date(self.endTime).getDate(), 0, new Date(self.endTime).getHours()); //因為是結束時間,所以時刻要從0-結束時間 self.fixHourDateBug(); }); self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, new Date(self.endTime).getDate(), new Date(self.endTime).getHours(), self.startTime, self.endTime); self.fixMinDateBug(); }); return false; } } //如果沒有傳入開始和結束時間 self.dateItem.monthsItem = self.getMonthItems(selectYear, null, null); //根據當前選中的年份,更新月份列表 self.$nextTick(() => { // 更新當前選中月份 self.fixMonthDateBug(); }); self.$nextTick(() => { //根據當前選中的年份和月份,更新天份列表 self.dateItem.daysItem = self.getDaysItems(selectYear, this.selectItem.month.value, null, null); self.fixDaysDateBug(); }); self.$nextTick(() => { // 簡單粗暴 0-23時 self.dateItem.hoursItem = self.getHoursItems(null, null, null, 0, 23); self.fixHourDateBug(); }); self.$nextTick(() => { //根據當前選中的年份,月份,天,更新分份列表 self.dateItem.minsItem = self.getMinsItems(selectYear, selectMonth, selectDay, null, null); self.fixMinDateBug(); }); return false; });複製程式碼
眼尖的小夥伴看出來了,對於重新選中選項的操作,單獨分了一個名為fix****DateBug的方法,那我們就繼續理解下列表更新,選中切換的思路
以自動切換月份當前選中為例:
fixMonthDateBug() { if (!this.dateItem.monthsItem[this.selectItem.month.index]) {// 如果當前選擇的月份index沒在當前的月份列表裡 /* * 這裡要解釋一下:這種情況說明當前選中的月份value和index都不在當前的月份列表內。 * 例如:當前選擇的是7.31,選中index就是32。如果切換到2月份,月份有28天,index最長29。 * 就找不到32這個index。所以直接賦值2月份最後一天。 * 這是月份的邏輯。其他同理還有“天”,“時”,“分”的同理邏輯。 * 因為年是最大的單位,所以年不設定此邏輯 * */ this.selectItem.month.index = this.dateItem.monthsItem.length - 1; this.selectItem.month.value = this.dateItem.monthsItem[this.dateItem.monthsItem.length - 1].value } else { //如果在的話,只更新value值。 this.selectItem.month.value = this.dateItem.monthsItem[this.selectItem.month.index].value } },複製程式碼複製程式碼
這個操作是為了解決: 上次選擇的index超過本次列表的長度,導致沒有對應值問題。
解決思路:也就是當我上次選擇7.31日時,本次切換到2月份。然而2月份沒有31日,那我們就重新指向選擇為2月最後一天。
那麼我們放出剩下的“月”,"天",“時”,“分”監聽事件(前方大量程式碼來襲):
//月列表滾動 let MonthsScroll = new BScroll(document.getElementsByClassName("datetime--months")[0], this.scrollParam); MonthsScroll.on('scrollEnd', () => { let selectYear = self.dateItem.yearsItem[YearsScroll.getSelectedIndex()].value; //選中的月份Value let selectMonth = self.dateItem.monthsItem[MonthsScroll.getSelectedIndex()].value; let selectDay = self.selectItem.day.value; self.selectItem.month.value = selectMonth; self.selectItem.month.index = MonthsScroll.getSelectedIndex(); if (self.startTime && self.endTime) { if (MonthsScroll.getSelectedIndex() === 0 && new Date(self.startTime).getFullYear() === parseInt(selectYear)) { self.$nextTick(() => { self.dateItem.daysItem = self.getDaysItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, self.startTime, self.endTime); self.fixDaysDateBug(); }); self.$nextTick(() => { self.dateItem.hoursItem = self.getHoursItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, new Date(self.startTime).getDate(), new Date(self.startTime).getHours(), 23); self.fixHourDateBug(); }); self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, new Date(self.startTime).getDate(), new Date(self.startTime).getHours(), self.startTime, self.endTime); self.fixMinDateBug(); }); return false; } else if (new Date(self.endTime).getMonth() + 1 === parseInt(selectMonth) && new Date(self.endTime).getFullYear() === parseInt(selectYear)) { self.$nextTick(() => { self.dateItem.daysItem = self.getDaysItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, self.startTime, self.endTime); self.fixDaysDateBug(); }); self.$nextTick(() => { self.dateItem.hoursItem = self.getHoursItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, new Date(self.endTime).getDate(), 0, new Date(self.endTime).getHours()); self.fixHourDateBug(); }); self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, new Date(self.endTime).getDate(), new Date(self.endTime).getHours(), self.startTime, self.endTime); self.fixMinDateBug(); }); return false; } } self.$nextTick(() => { self.dateItem.daysItem = self.getDaysItems(selectYear, selectMonth, null, null); self.fixDaysDateBug(); }); self.$nextTick(() => { self.dateItem.hoursItem = self.getHoursItems(null, null, null, 0, 24); self.fixHourDateBug(); }); self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(selectYear, selectMonth, selectDay, null, null); self.fixMinDateBug(); }); return false; }); //日列表滾動 let DaysScroll = new BScroll(document.getElementsByClassName("datetime--days")[0], this.scrollParam); DaysScroll.on('scrollEnd', () => { self.selectItem.day.value = self.dateItem.daysItem[DaysScroll.getSelectedIndex()].value; self.selectItem.day.index = DaysScroll.getSelectedIndex(); let selectYear = self.selectItem.year.value; let selectMonth = self.selectItem.month.value; let selectDay = self.selectItem.day.value; if (self.startTime && self.endTime) { if (new Date(self.startTime).getFullYear() === parseInt(selectYear) && new Date(self.startTime).getMonth() + 1 === parseInt(selectMonth) && new Date(self.startTime).getDate() === parseInt(selectDay)) { self.$nextTick(() => { self.dateItem.hoursItem = self.getHoursItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, new Date(self.startTime).getDate(), new Date(self.startTime).getHours(), 23); self.fixHourDateBug(); }); self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, new Date(self.startTime).getDate(), new Date(self.startTime).getHours(), self.startTime, self.endTime); self.fixMinDateBug(); }); return false; } else if (new Date(self.endTime).getFullYear() === parseInt(selectYear) && new Date(self.endTime).getMonth() + 1 === parseInt(selectMonth) && new Date(self.endTime).getDate() === parseInt(selectDay)) { self.$nextTick(() => { self.dateItem.hoursItem = self.getHoursItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, new Date(self.endTime).getDate(), 0, new Date(self.endTime).getHours()); self.fixHourDateBug(); }); self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, new Date(self.endTime).getDate(), new Date(self.endTime).getHours(), self.startTime, self.endTime); self.fixMinDateBug(); }); return false; } } self.dateItem.hoursItem = self.getHoursItems(null, null, null, 0, 24); self.$nextTick(() => { self.fixHourDateBug(); }); self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(selectYear, selectMonth, selectDay, null, null); self.fixMinDateBug(); }); return false; }) //時列表滾動 let HoursScroll = new BScroll(document.getElementsByClassName("datetime--hours")[0], this.scrollParam); HoursScroll.on('scrollEnd', () => { self.selectItem.hour.value = self.dateItem.hoursItem[HoursScroll.getSelectedIndex()].value; self.selectItem.hour.index = HoursScroll.getSelectedIndex(); let selectYear = self.selectItem.year.value; let selectMonth = self.selectItem.month.value; let selectDay = self.selectItem.day.value; let selectHour = self.selectItem.hour.value; if (self.startTime && self.endTime) { if (new Date(self.startTime).getFullYear() === parseInt(selectYear) && new Date(self.startTime).getMonth() + 1 === parseInt(selectMonth) && new Date(self.startTime).getDate() === parseInt(selectDay) && new Date(self.startTime).getHours() === parseInt(selectHour)) { self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(new Date(self.startTime).getFullYear(), new Date(self.startTime).getMonth() + 1, new Date(self.startTime).getDate(), new Date(self.startTime).getHours(), self.startTime, self.endTime); self.fixMinDateBug(); }); return false; } else if (new Date(self.endTime).getFullYear() === parseInt(selectYear) && new Date(self.endTime).getMonth() + 1 === parseInt(selectMonth) && new Date(self.endTime).getDate() === parseInt(selectDay) && new Date(self.endTime).getHours() === parseInt(selectHour)) { self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(new Date(self.endTime).getFullYear(), new Date(self.endTime).getMonth() + 1, new Date(self.endTime).getDate(), new Date(self.endTime).getHours(), self.startTime, self.endTime); self.fixMinDateBug(); }); return false; } } self.$nextTick(() => { self.dateItem.minsItem = self.getMinsItems(selectYear, selectMonth, selectDay, null, null); self.fixMinDateBug(); }); return false; }) //分列表滾動 let MinsScroll = new BScroll(document.getElementsByClassName("datetime--mins")[0], this.scrollParam); MinsScroll.on('scrollEnd', () => { self.selectItem.min.value = self.dateItem.minsItem[MinsScroll.getSelectedIndex()].value; self.selectItem.min.index = MinsScroll.getSelectedIndex(); })複製程式碼
注意:每一個監聽事件,都是重新重新整理子時間單位的列表並重新更改選擇項。所以程式碼量從 ”年“ 到 ”分“ 是逐級遞減的
剩餘的日期選擇項更改邏輯:
fixDaysDateBug() { if (!this.dateItem.daysItem[this.selectItem.day.index]) { this.selectItem.day.index = this.dateItem.daysItem.length - 1; this.selectItem.day.value = this.dateItem.daysItem[this.dateItem.daysItem.length - 1].value } else { this.selectItem.day.value = this.dateItem.daysItem[this.selectItem.day.index].value } }, fixHourDateBug() { if (!this.dateItem.hoursItem[this.selectItem.hour.index]) { this.selectItem.hour.index = this.dateItem.hoursItem.length - 1; this.selectItem.hour.value = this.dateItem.hoursItem[this.dateItem.hoursItem.length - 1].value } else { this.selectItem.hour.value = this.dateItem.hoursItem[this.selectItem.hour.index].value } }, fixMinDateBug() { if (!this.dateItem.minsItem[this.selectItem.min.index]) { this.selectItem.min.index = this.dateItem.minsItem.length - 1; this.selectItem.min.value = this.dateItem.minsItem[this.dateItem.minsItem.length - 1].value } else { this.selectItem.min.value = this.dateItem.minsItem[this.selectItem.min.index].value } },複製程式碼
好了,到這裡一個功能基礎功能的時間選擇器(DatePicker)就做完了。元件程式碼在碼雲上有託管。連結: 點選這裡
emmm.....求各位路過點個小心心唄~~