遊戲陪玩系統開發,日期時間選擇介面的實現

雲豹科技程式設計師發表於2021-10-20

前言

在進行遊戲陪玩系統中,日期選擇介面是必備的開發專案之一,雖然看起來很簡單,但是真正實現起來卻有些難度,接下來我們就一起來學習一下吧。
在這裡插入圖片描述

通過這個介面來看,我們正常的日期時間選擇的元件庫肯定不能直接滿足,但是有些部分可以滿足要求;像右邊的時間選擇就可以,但是左邊的這個日期和星期幾怎麼做呢?
介面上沒有年份?這裡做一個伏筆,後面再看。
下面我們一步步來實現遊戲陪玩系統的這個需求,我使用的是 Vue + Vant,Vant官網。

開始

基礎元件選擇

  • 首先我們需要檢視相應的 UI 元件庫找到可以基本滿足需要的元件,我主要找了兩個元件一個是 DatetimePicker時間選擇,另一個是Picker 選擇器。
  • 這兩個按理說應該都可以在遊戲陪玩系統中實現日期時間選擇介面,我選擇的是 Picker 選擇器元件,DatetimePicker 時間選擇 元件怎麼實現可以自己試試。

資料構造

  • 從圖片上我們可以知道這個需要三列,第一列顯示日期和星期,第二列顯示小時,最後一列顯示分鐘。

1、html

<van-picker
    title="標題"
    show-toolbar
    :columns="dateColumns"
    @confirm="onConfirm"
    @cancel="onCancel"
    @change="onChange"/>

2、vue

  • 我們在進遊戲陪玩系統頁面後初始化一個 dateColumns,包括預設的第一列以及後面兩列時間。後面的 onChange 和
    getNewDateArray 方法就是我們後面邏輯實現的地方。
export default {
	...
    data() {
    	dateColumns: [],
        defaultYear: new Date().getFullYear(), // 儲存年份,預設當前年
        dateArray: [], // 儲存第一列的values
    },
    mounted() {
        this.dateColumns = [
            this.getNewDateArray(undefined, true),// 第一列
            {
                values: Array.from({ length: 24 }, (v, k) => {
                    if (k < 10) {
                        k = `0${k}`
                    }
                    return k;
                }),
                defaultIndex: 1,
            }, { // 第二列
                values: Array.from({ length: 60 }, (v, k) => {
                    if (k < 10) {
                        k = `0${k}`
                    }
                    return k;
                }),
                defaultIndex: 1,
            }]; // 第三列
    },
    method:{
        onConfirm(picker, values) {...},
        onChange(picker, values) {
            console.log('piker', picker, values)
            // ...
        },
        onCancel() { ... },
        getNewDateArray(date, flag, picker, type) {
        	...
        }

邏輯分析

  • 首先我們看第一列的資料結構,是XX月XX日XX,第一個XX是月份,第二個XX是多少日,第三個XX是星期幾,我們需要用到的就是月份(onChange 中getNewDateArray 的呼叫為什麼不加第幾天而預設使用 1,這個後面‘疑問’目錄中會詳細說明),每次滾動到頭或者到尾的時候我們需要通過這個來判斷是第一個月還是最後一個月,從而對月份和年份重新設定。

1、當滾動到 dateColumns 第一列 values 陣列的第一個的時候,需要載入上一個月的資料;
2、當滾動到 dateColumns 第一列 values 陣列的最後一個的時候,需要載入後一個月的資料;
3、當滾動到 dateColumns 第一列 values 陣列的第一個的時候,需要判斷 month(月份)是否為第一個月,如果是第一個月我們需要將月份重置為第 12 個月,並且將預設的年份 -1;
4、當滾動到 dateColumns 第一列 values 陣列的最後一個的時候,需要判斷 month(月份)是否為最後一個月,如果是最後一個月我們需要將月份重置為第 1 個月,並且將預設的年份 +1。

  • 相關程式碼如下
onChange(picker, values) {
    console.log('piker', picker, values)
    const dateArr = values[0].text.split(' ')[0].slice(0, -1).split('月');
    const month = dateArr[0]; // 當前的月份
    if(values[0] === this.dateArray[0]) { // 判斷`dateColumns` 第一列 `values` 陣列的第一個
        let newMonth;
        if(month === '1') { // 判斷 `month`(月份)是否為`第一個月`
            newMonth = 12;
            this.defaultYear -= 1;
        } else {
            newMonth = Number(month) - 1;
        }
        this.getNewDateArray(`${this.defaultYear}-${newMonth}-1`, false, picker, true)
    } else if (values[0] === this.dateArray[this.dateArray.length - 1]) { // 判斷`dateColumns` 第一列 `values` 陣列的最後一個
        let newMonth;
        if(month === '12') { // 判斷 `month`(月份)是否為`最後一個月`
            newMonth = 1;
            this.defaultYear += 1;
        } else {
            newMonth = Number(month) + 1
        }
        this.getNewDateArray(`${this.defaultYear}-${newMonth}-1`, false, picker, false)
    }
},

初始化

  • 第一次進入遊戲陪玩系統時間頁面我們初始化當前月份的資料。

1、我們需要拿到當前月份的總天數;
2、生成遊戲陪玩系統 UI 上對應的資料格式,每次需要通過當前的日期拿到星期幾;
3、定義一個週一到週日的陣列方便對應取值,注意星期日是返回 0 ;
4、通過 flag 判斷為 true 返回物件。

程式碼如下:

 const  weekArray = ['週日', '週一', '週二', '週三', '週四', '週五', '週六'];
 /**
 *	 date: 需要增加的資料
 *  flag:是否是第一次進入
 *  picker:Picker的例項
 *  type:資料push的方向
 */
 getNewDateArray(date, flag, picker, type) {
     date = date ? new Date(date) : new Date();
     let month = date.getMonth() + 1; // 儲存當前月份
     const monthDays = new Date(date.getFullYear(), month, 0).getDate(); // 獲取當前月份的總天數
     
       let arr = [];
       let index = 0;
       for (let i = 1; i <= monthDays; i ++) {
           let str = `${month}月${i}日 ${weekArray[new Date(`${date.getFullYear()}-${month}-${i}`).getDay()]}`
           arr.push(str);
       }
       ... // code
       
       if(flag) {
           return {
               values: arr,
               defaultIndex: date.getDate() - 1 // 設定預設選中
           }
       }
       ... // code
 }

後續上下滾動載入

  • 什麼的遊戲陪玩系統程式碼只是第一次進入的時候初始化的資料,上下滑動到開始或結尾並不會新增內容,所以對什麼的程式碼進行補充和修改。

1、通過 type 判斷是向下滾動到一個還是向上滾動到最後一個;
2、向下滾動到一個將預設值設為新增的上一個月的總天數,並將新月份新增到 dateArray 陣列之前;
3、向上滾動到最後一個將預設值設為為新增新月份的陣列長度 -1,因為 column 的索引是 0 開始的,這裡需要注意一下 ;並將新月份新增到 dateArray 陣列之後;
4、通過 flag 判斷為 false,呼叫Picker例項修改已存在的values及預設選中的索引。

程式碼如下:

 ... // code
+  if(type) {
+     this.dateArray = arr.concat(this.dateArray); // 新增到dateArray陣列之前
+     index = monthDays;
+ } else {
+     index = this.dateArray.length - 1; // 設定新陣列前設定預設選中索引
+     this.dateArray = this.dateArray.concat(arr); // 新增到dateArray陣列之後
+ }
 if(flag) {
     return {
+         values: this.dateArray,
-          values: arr,
         defaultIndex: date.getDate() - 1
     }
+  } else {
+     picker.setColumnValues(0, this.dateArray)
+     picker.setColumnIndex(0, index) // 設定預設選中
 }

大功告成?

  • 剛剛我們看到只是選擇的時候的 遊戲陪玩系統UI 介面,那選擇完成後需要怎麼顯示呢?我們再看一下選完後的 UI 介面?
    在這裡插入圖片描述
  • 哈哈哈哈哈,wtf,年份呢?居然不顯示年份?為了安全起見,我們還是把年份加上,當然後端也會用到這個。
  • 查閱文件,我發現陣列的值可以是一個物件,顯示的是 text 欄位,那我們把生成的的資料 str 那個結構改一下。
    在這裡插入圖片描述

程式碼如下:

onChange() {
 -  if(values[0] === this.dateArray[0]) {
 -  if(values[0].text === this.dateArray[0].text) {
      ...
 -  } else if (values[0] === this.dateArray[this.dateArray.length - 1]) {
 -  } else if (values[0].text === this.dateArray[this.dateArray.length - 1].text) {
      ...
  }
},
getNewDateArray(date, flag, picker, type) {
 -   let str = `${month}月${i}日 ${weekArray[new Date(`${date.getFullYear()}-${month}-${i}`).getDay()]}`
 -   let str = {
 -     year: date.getFullYear(),,
 -     text: `${month}月${i}日 ${weekArray[new Date(`${newYear}-${month}-${i}`).getDay()]}`
 -   };
}
  • 資料結構是加上了,那生不生效呢?我們看一下介面;
    在這裡插入圖片描述
  • 遊戲陪玩系統介面倒是沒什麼問題,那是否能拿到資料呢?我們在 onChange 中列印一下當前選擇的 values;
    在這裡插入圖片描述
  • ok,完美!年的問題就解決了,是不是覺得 so easy。

再試試?

  • 試試遊戲陪玩系統當前日期是當前月的第一天或者最後一天。 是不是發現什麼了?如果當前是第一天,你下拉是不會載入前一個月的資料的,因為沒有觸發
    onChange 事件,你可以上拉到新的日期,再下拉到第一個,這樣就會重新整理了,當然如果你們產品能接受那也是可以的;最後一天是相同的道理。

解決方案

  • 我們可以通過判斷初始時是否是今天是當月的第一天或者最後一天,來多載入前一個月或者後一個月;

1、增加一個識別符號變數 getMoreMonth ,false:不需要獲取更多, 1:獲取前一個月,2:獲取後一個月;
2、增加一個長度標識變數 moreLen 初始化為當前日期的,用來儲存對預設選中需要增加的長度;
3、獲取當前年份和月份,判斷獲取之前還是之後的月份,對年份和月份進行重置;
4、生成新的一個月的陣列,判斷是之前還是之後的月份,生成之前的月份將所有資料放到新陣列中;之後的月份直接向之前的 arr 陣列中 push 就可以了;
5、迴圈結束後,設定索引需要增加的長度,如果是獲取之前的一個月(getMoreMonth === 1),將 moreLen 加上之前一個月的總天數;並將新的陣列和之前的陣列進行拼接;

  • 新增程式碼如下:
getNewDateArray(date, flag, picker, type) {
+    let getMoreMonth = false; // 不需要獲取更多 1、獲取前一個月;2、獲取後一個月
+   if(flag) { // 初次渲染
+       const newDate = new Date();
+       const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate()
+       if(new Date().getDate() === 1) { // 當月第一天
+           getMoreMonth = 1;
+       } else if (new Date().getDate() === monthDays) { // 當月最後一天
+           getMoreMonth = 2;
+       }
+   }
    date = date ? new Date(date) : new Date();
    ...
    for (let i = 1; i <= monthDays; i ++) {
        let str = {
            year: date.getFullYear(),
            text: `${month}月${i}日 ${weekArray[new Date(`${date.getFullYear()}-${month}-${i}`).getDay()]}`
        };
        arr.push(str);
    }
+   let moreLen = date.getDate() - 1;
+   if(getMoreMonth) {
+       let newYear = date.getFullYear();
+       if (getMoreMonth === 1) { // 當月第一天
+           if (month === 1) {
+               month = 12;
+               newYear -= 1;
+           } else {
+               month -= 1;
+           }
+       } else {
+           if (month === 12) {//  當月最後一天
+               month = 1;
+               newYear += 1;
+           } else {
+               month += 1;
+           }
+      }
+       const moreMonthDays = new Date(newYear, month, 0).getDate();
+       let beforeArray = [];
+       for (let i = 1; i <= moreMonthDays; i ++) {
+           let str = {
+               year: newYear,
+               text: `${month}月${i}日 ${weekArray[new Date(`${newYear}-${month}-${i}`).getDay()]}`
+           };
+           if (getMoreMonth === 2) { // 獲取後一個月直接push
+               arr.push(str);
+           } else {
+               beforeArray.push(str); // 獲取前一個月存入新的陣列
+           }
+       }
+       moreLen = getMoreMonth === 1 ? (moreLen + beforeArray.length) : moreLen; // 獲取前一個月索引增加前一個月總天數
+       arr = beforeArray.concat(arr);
+   }
    if(flag) {
        return {
            values: this.dateArray,
-           defaultIndex: date.getDate() - 1
+           defaultIndex: moreLen
        }
    } else {
    ...

測試一下

  • 修改 mounted 中的首次呼叫入參為當前月份的第一日,並修改 getNewDateArray 中第一個判斷是否首次進入的邏輯。
  • 程式碼修改如下:
 mounted() {
      this.dateColumns = [
 -       this.getNewDateArray(undefined, true),// 第一列
 -       this.getNewDateArray('2020-11-1', true),// 第一列
         ...
  },
  
  getNewDateArray(date, flag, picker, type) {
      let getMoreMonth = false; // 不需要獲取更多 1、獲取前一個月;2、獲取後一個月
      if(flag) {
 -         const newDate = new Date();
 -         const newDate = new Date(date);
          const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate()
 -         if(new Date().getDate() === 1) {
 -         if(new Date(date).getDate() === 1) {
 -             console.log('first day')
              getMoreMonth = 1;
          } else if (new Date().getDate() === monthDays) {
              getMoreMonth = 2;
          }
      }
      ...
  }
  • 看一下游戲陪玩系統測試的效果,獲取前面的一個月的沒有問題,相應的我們再看看獲取下一個月。
    在這裡插入圖片描述
  • 獲取下一個月,修改程式碼如下:
mounted() {
      this.dateColumns = [
 -       this.getNewDateArray('2020-11-1', true),// 第一列
 -       this.getNewDateArray('2020-11-30', true),// 第一列
         ...
  },
  
  getNewDateArray(date, flag, picker, type) {
      let getMoreMonth = false; // 不需要獲取更多 1、獲取前一個月;2、獲取後一個月
      if(flag) {
 -         const newDate = new Date(date);
 -         const newDate = new Date();
          const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate()
 -         if(new Date().getDate() === 1) {
 -         if(new Date(date).getDate() === 1) {
 -             console.log('first day')
              getMoreMonth = 1;
 -         } else if (new Date().getDate() === monthDays) {
 -         } else if (new Date(date).getDate() === monthDays) {
 -         	  console.log('last day')
              getMoreMonth = 2;
          }
      }
      ...
  }
  • 好的,也沒有問題,針對目前的問題就基本上解決了。
    在這裡插入圖片描述
    疑問

1、為什麼 this.getNewDateArray( {this.defaultYear}- t h i s . d e f a u l t Y e a r {newMonth}-1, false, picker, true)這裡不用選擇的資料中的日作為第一個引數日期的最後日子傳進去呢?這裡有個坑,之前我也以為需要將這個日子用上,所以我傳了這個過去;但是後面在使用時,我發現滾動到1月份底的時候,載入的是 3 月份,並沒有載入 2 月份。

在這裡插入圖片描述

  • 將如果是當月第一天或最後一天預載入兩個月的程式碼全部註釋(便於測試),並修改程式碼測試如下就會得到上述結果:
mounted() {
  this.dateColumns = [
 -     this.getNewDateArray('2020-11-30', true),// 第一列
 -     this.getNewDateArray(undefined, true),// 第一列
   	  ...
    ]
 },
onChange(picker, values) {
    ...
    const month = dateArr[0]; // 當前的月份
    const day = dateArr[1]; // 當月第幾天
	if(...) {
    	...
        this.getNewDateArray(`${this.defaultYear}-${newMonth}-${day}`, false, picker, true)
    } else if (...) {
    	...
        this.getNewDateArray(`${this.defaultYear}-${newMonth}-${day}`, false, picker, false)
    }
}
  • 原因是因為這裡獲取到 2020 年的 1 月份的最後一天是 31 號,但是 2 月只有 29 天,所以 getMonth() 拿到的就是
    2,再 +1 就變成了 3。

可優化點

1、 遊戲陪玩系統每次會到第一個或最後一個再去載入,是否可以優化為滾動去載入?
2、遊戲陪玩系統程式碼由於比較寫得比較急,有些點是可以優化的。

完整程式碼及使用

元件程式碼

// customSelectDate.vue<template>
    <van-popup class="time-custom-picker" v-model="showDatepicker" position="bottom" :style="{ height: '300px' }">
        <van-picker
            title="選擇時間"
            show-toolbar
            :visible-item-count="5"
            :columns="dateColumns"
            @confirm="onConfirm"
            @cancel="onCancel"
            @change="onChange"
            />
    </van-popup></template><script>import { weekArray } from '@/assets/js/constant';const timeArray = [
    {
        values: Array.from({ length: 24 }, (v, k) => {
            if (k < 10) {
                k = `0${k}`
            }
            return k;
        }),
        defaultIndex: 24,
    }, {
        values: [':']
    }, { // 第二列
        values: Array.from({ length: 60 }, (v, k) => {
            if (k < 10) {
                k = `0${k}`
            }
            return k;
        }),
        defaultIndex: 60,
    } // 第三列]export default {
    name: 'publishSelectDate',
    props: {},
    components: {},
    data() {
        return {
            showDatepicker: false,
            dateColumns: [],
            defaultYear: new Date().getFullYear(),
            dateArray: [],
        }
    },
    created() {},
    computed: {},
    mounted() {
        this.dateColumns = [
            this.getNewDateArray(undefined, true),// 第一列
            ...timeArray        ]; 
    },
    methods: {
        onConfirm(values, indexs) {
            this.showDatepicker = !this.showDatepicker            this.$emit('select-date', values)
        },
        // 改變選擇時間
        onChange(picker, values) {
            const dateArr = values[0].text.split(' ')[0].slice(0, -1).split('月');
            const month = dateArr[0]; // 當前的月份
            // 已到陣列第一個,獲取上一個月
            if(values[0].text === this.dateArray[0].text) {
                let newMonth;
                if(month === '1') {
                    newMonth = 12;
                    this.defaultYear -= 1;
                } else {
                    newMonth = Number(month) - 1;
                }
                this.getNewDateArray(`${this.defaultYear}/${newMonth}/1`, false, picker, true)
            } 
            // 已到陣列最後一個,獲取下一個月
            else if (values[0].text === this.dateArray[this.dateArray.length - 1].text) {
                let newMonth;
                if(month === '12') {
                    newMonth = 1;
                    this.defaultYear += 1;
                } else {
                    newMonth = Number(month) + 1
                }
                this.getNewDateArray(`${this.defaultYear}/${newMonth}/1`, false, picker, false)
            }
            
        },
        onCancel() {
            this.showDatepicker = !this.showDatepicker        },
        getNewDateArray(date, flag, picker, type) {
            let getMoreMonth = false; // 不需要獲取更多 1、獲取前一個月;2、獲取後一個月
            if(flag) { // 是否是第一次
                const newDate = new Date();
                const monthDays = new Date(newDate.getFullYear(), newDate.getMonth() + 1, 0).getDate()
                if(new Date().getDate() === 1) { // 是當月第一天就獲取前一個月
                    getMoreMonth = 1;
                } else if (new Date().getDate() === monthDays) { // 是當月最後一天就獲取後一個月
                    getMoreMonth = 2;
                }
            }
            date = date ? new Date(date) : new Date();
            let month = date.getMonth() + 1;
            const monthDays = new Date(date.getFullYear(), month, 0).getDate();
            let arr = [];
            let index = 0;
            // 生成當月的日期陣列
            for (let i = 1; i <= monthDays; i ++) {
                let str = {
                    year: date.getFullYear(),
                    text: `${month}月${i}日 ${weekArray[new Date(`${date.getFullYear()}/${month}/${i}`).getDay()]}`
                };
                arr.push(str);
            }
            let moreLen = 0;
            // 是否獲取更多月份
            if(getMoreMonth) {
                let newYear = date.getFullYear();
                if (getMoreMonth === 1) { // 獲取前一個月
                    if (month === 1) {
                        month = 12;
                        newYear -= 1;
                    } else {
                        month -= 1;
                    }
                } else { // 獲取後一個月
                    if (month === 12) {
                        month = 1;
                        newYear += 1;
                    } else {
                        month += 1;
                    }
                }
                const moreMonthDays = new Date(newYear, month, 0).getDate();
                let beforeArray = []; // 上一個月
                // 生成前一個月或後一個月日期陣列
                for (let i = 1; i <= moreMonthDays; i ++) {
                    let str = {
                        year: newYear,
                        text: `${month}月${i}日 ${weekArray[new Date(`${newYear}/${month}/${i}`).getDay()]}`
                    };
                    if (getMoreMonth === 2) { // 後一個月的直接往當前月的日期陣列裡面push
                        arr.push(str);
                    } else { // 上一個月放入上一個月陣列
                        beforeArray.push(str);
                    }
                }
                // 如果是獲取的上一個月更新預設下表長度增加上一個月天數
                moreLen = getMoreMonth === 1 ? beforeArray.length : 0;
                arr = beforeArray.concat(arr);
            }
            // 是否是獲取後一個月
            if(type) {
                this.dateArray = arr.concat(this.dateArray);
                index = monthDays;
            } else {
                index = this.dateArray.length - 1;
                this.dateArray = this.dateArray.concat(arr);
            }
            // 是否是第一次進入選擇日期
            if(flag) {
                return {
                    values: this.dateArray,
                    defaultIndex: getMoreMonth === 1 ? moreLen + date.getDate() - 1 : date.getDate() - 1
                }
            } else {
                picker.setColumnValues(0, this.dateArray)
                picker.setColumnIndex(0, index)
            }
        },
    }}</script><style lang="scss" scope>
    .time-custom-picker {
        font-family: PingFangSC, PingFangSC-Regular;
        color: #000000;
        .van-picker {
            .van-picker__toolbar {
                .van-picker__confirm {
                    color: #1795ff;
                }
            }
            .van-picker__columns {
                .van-picker-column {
                    flex: none;
                }
                .van-picker-column:first-child {
                    width: 200px;
                }
                .van-picker-column:nth-child(2),
                .van-picker-column:nth-child(4) {
                    width: 60px;
                }
            }
        }
    }</style>

使用

<custom-select-date ref="selectDate" @select-date="onConfirm"></custom-select-date>onConfirm(values) {
    this.checkedYear = values[0].year;
    this.form.submitDeadline = `${values[0].text} ${values[1]}:${values[3]}`;
},

總結

  • 通過遊戲陪玩系統出的原型和 UI,如果現有的UI庫不能完全滿足需求,我們可以找一個相似度比較高的進行修改。
  • 在遊戲陪玩系統功能做完之後,需要針對一些臨界點做一些測試。

本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理
原文連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2838409/,如需轉載,請註明出處,否則將追究法律責任。

相關文章