移动端纵向日历开发总结

695 阅读6分钟

总的来说就是要开发一个又麻烦又没用的纵向日历组件(摊手)

本人是用vue开发的,在下面的代码里你可能会看到很多this,那只是我把数据存data里了,因为有的代码不能贴,所以。。如果你全复制粘贴的话可能会报很多错,还是主要看看思路吧,希望对看到文章的人有所帮助。

组件就长这个样子:

交互效果是仿照飞猪的,总体如下:

  • 每个月份的title在滑动的时候要有交替的吸顶的效果;
  • 滚动时月份title要有阴影,比如现在6月的title下有阴影,滚动到7月的时候7月的title就要有阴影;
  • 底下的黑色蒙层是fade in的效果,日历部分是从下往上的动画
  • 选中的日期是紫底白字,可选日期是整体黑字,不可选的是灰字

在实现的过程中遇到了很多问题,总结如下:

  1. 后台给我返回的起止时间是十位时间戳,需要利用这两个时间戳遍历出我们能遍历成日历的某种格式的数组,还要处理跨年问题;
  2. 每个日期item需要分成两种状态,可选和不可选,还要加上唯一标识,方便给选中的那项加类名;
  3. 月份title吸顶和阴影效果:现在图上是6月title吸附在顶部且有阴影,滚动到7月的话,就是7月title吸附在顶部并加上阴影;

首先解决第一个问题。那么我们需要的数据格式是什么样子:

let date = [
  0: {
    year: 2020,
    mon: 6,
    day: 23, // 默认选中日期
    list: [ //这个月的每一天 
      0: {
        num: '', // 日期
        status: '', // 状态,用来区分是否可选
        text: '', // 文案
        tabIndex: '', //唯一标识,用date的索引和list的索引生成,比如这项是0-0
      },
      ...后面很多项
    ],
  },
  ...后面很多项
];

与横向的日历不同,纵向的日历是一下把所有需要的月份都遍历出来,所以我最终根据业务确定的数据结构是这样,可能还会有更好的。

我们需要这样几个方法:

  • 获取这个月的第一天是星期几,方便我们知道前面应该空几个格
  • 获取当月一共多少天,方便我们遍历list数组
  • 计算一共跨过几个月份,方便我们遍历date和处理跨年
// 获取当月共多少天
getCurMonDays(year, month) {
  return new Date(year, month, 0).getDate();
},
// 获取当月第一天星期几
getFirstDayOfWeek(year, month) {
  return new Date(Date.UTC(year, month - 1, 1)).getDay();
},
// 计算跨过几个月份
getThroughMon() {
  if (this.startYear === this.endYear) { // 说明没有跨年
    this.throughMon = this.endMon - this.startMon;
  } else {
    this.throughMon = (12 - this.startMon) + this.endMon;
  }
},

然后需要从起止时间戳中获取年月日:

// 我就不仔细写了,公司有专门处理时间戳的js文件
this.startYear = '...'; // 开始日期年份
this.endYear = '...'; // 结束时间年份
this.startMon = '...'; // 开始月
this.endMon = '...'; // 结束月
this.$nextTick(() => { this.getThroughMon(); }); //计算跨了几个月份
this.startDay = '...'; // 开始日
this.endDay = '...'; // 结束日
this.defaultDate = [...]; // 默认选择日期(数组)
this.$nextTick(() => { this.initDate(); });// 初始化date数组

初始化date数组:

let mon = this.startMon;
let year = this.startYear;
for (let i = 0; i <= this.throughMon; i += 1) { // 跨过了几个月就循环几次
  // 数据格式
  const obj = {
    year: 2020,
    mon: 5,
    day: 25,
    list: [],
  };
  // 处理跨年
  if (mon + i === 13) {
    year = this.endYear;
    mon = this.startMon - 12;
  }
  // 给年月日分别赋值
  obj.year = year;
  obj.mon = mon + i;
  if (obj.mon === this.startMon) {
    obj.day = this.startDay;
  } else if (obj.mon === this.endMon) {
    obj.day = this.endDay;
  } else {
    // 如果不是开始月也不是结束月,则day值为当月总天数
    obj.day = this.getCurMonDays(obj.year, obj.mon);
  }
  this.date.push(obj);
}

再向date的list数组中填入数据

this.date.forEach((item, index) => {
const weekday = this.getFirstDayOfWeek(item.year, item.mon);
const allday = this.getCurMonDays(item.year, item.mon);
// 向当月一号之前push空对象占位
for (let i = 0; i < weekday; i += 1) {
  item.list.push({ num: '', text: '', status: '' });
}
// 接下来给每个日期item的文案状态等字段赋值并push进去
for (let j = 0; j < allday; j += 1) {
  const obj = { num: j + 1 }; // 日期
  obj.text = '可预约'; // 文案
  obj.status = 'black'; // 状态:可选black/不可选gray
  obj.tabIndex = `${index}-${j + weekday}`; // 唯一标识
  // 下面代码是让起止日期中间的日子是可选的
  // 比如起止日期是2020年6月23日到2020年8月23日
  // 那么6月23日之前的日子和8月23日之后的日子就得置灰
  if (index === 0) {
    obj.text = j + 1 < item.day ? '不可预约' : '可预约';
    obj.status = j + 1 < item.day ? 'gray' : 'black';
  } else if (index === this.date.length - 1) {
    obj.text = j + 1 > item.day ? '不可预约' : '可预约';
    obj.status = j + 1 > item.day ? 'gray' : 'black';
  }
  item.list.push(obj);
}
});

别忘了在v-for时绑定动态类名:class="item.status",这样我们就得到了一个(初级的能看不能点的)纵向日历

再解决第二个问题,给选中项加类名:

其实这个功能我们已经完成一大半了,在前面遍历生成list的时候我们已经给每一项都添加了唯一标识。现在要做的就是给出默认选中项和点击选中的功能。

默认选中项:我司后台返回的是十位时间戳,我把它处理成数组的形式,可以先把时间戳转成"2020-06-23"这样的时间戳,然后在分割成数组。以下贴的是得到数组之后的代码

const arr = ['2020', '06', '23']; // 格式是这样的
let dateIndex = 0;
if ((+arr[0]) === this.startYear) { // 处理跨年
  dateIndex = (+arr[1]) - this.startMon;
} else {
  dateIndex = ((+arr[1]) - this.startMon) + 12;
}
const listIndex = (this.getFirstDayOfWeek((+arr[0]), (+arr[1]))) + ((+arr[2]) - 1);
this.actItem = Object.assign({}, this.date[dateIndex].list[listIndex]);

在data中存入actItem后,别忘了把动态类名改了,当然了status还是要留着的: :class="[item.status, {'act': actItem.tabIndex === item.tabIndex}]"

初始化选中项其实就是根据日期算出了这个日期属于哪个list的哪一项,其中有两个索引,一个是dateIndex,一个是listIndex,有了这两个索引就可以做很多事情。比如:

  1. 点击时传入这两个索引,根据这两个值找到点击项,在赋值给actItem,就做出了点击选中功能;
  2. 如果在数据中间有某一天是不能选中的,也可以根据日期算出这两个索引,然后把这一项的status赋值为gray即可

最后就是title吸顶和滚动阴影

我写到这都烦了,估计看到这也该烦了,也不知道你看没看懂,先说声谢谢

这个title吸顶吧,css就可以解决:position: sticky,还要配合top属性,具体的百度一下就会用了,我以前确实不知道这个,涨姿势了。

再说滚动阴影,飞猪的web端(似乎)用了jQuery,为了遵循vue不操作DOM的中心思想(其实是我没看懂人家的代码),我用了一个投机取巧的方法:写一个p标签,把它定位到月份title的位置,给它加上阴影,用z-index控制其层级在title下面,在滚动事件里控制这个标签的显示隐藏,scrollTop大于0就显示,否则不显示,这样视觉就很像哪个title在顶部,哪个就有阴影。(不会做gif图的我只能这么描述了)

 

 

各公司业务不同,希望有帮助,如果有空我会再完善这篇文章。