如何在游戏陪玩系统中实现一个日期时间选择界面?

前言

在进行游戏陪玩系统中,日期选择界面是必备的开发项目之一,虽然看起来很简单,但是真正实现起来却有些难度,接下来我们就一起来学习一下吧。
在这里插入图片描述

通过这个界面来看,我们正常的日期时间选择的组件库肯定不能直接满足,但是有些部分可以满足要求;像右边的时间选择就可以,但是左边的这个日期和星期几怎么做呢?
界面上没有年份?这里做一个伏笔,后面再看。
下面我们一步步来实现游戏陪玩系统的这个需求,我使用的是 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( t h i s . d e f a u l t Y e a r − {this.defaultYear}- this.defaultYear{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库不能完全满足需求,我们可以找一个相似度比较高的进行修改。
  • 在游戏陪玩系统功能做完之后,需要针对一些临界点做一些测试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值