总的来说就是要开发一个又麻烦又没用的纵向日历组件(摊手)
本人是用vue开发的,在下面的代码里你可能会看到很多this,那只是我把数据存data里了,因为有的代码不能贴,所以。。如果你全复制粘贴的话可能会报很多错,还是主要看看思路吧,希望对看到文章的人有所帮助。
组件就长这个样子:

- 每个月份的title在滑动的时候要有交替的吸顶的效果;
- 滚动时月份title要有阴影,比如现在6月的title下有阴影,滚动到7月的时候7月的title就要有阴影;
- 底下的黑色蒙层是fade in的效果,日历部分是从下往上的动画
- 选中的日期是紫底白字,可选日期是整体黑字,不可选的是灰字
在实现的过程中遇到了很多问题,总结如下:
- 后台给我返回的起止时间是十位时间戳,需要利用这两个时间戳遍历出我们能遍历成日历的某种格式的数组,还要处理跨年问题;
- 每个日期item需要分成两种状态,可选和不可选,还要加上唯一标识,方便给选中的那项加类名;
- 月份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,有了这两个索引就可以做很多事情。比如:
- 点击时传入这两个索引,根据这两个值找到点击项,在赋值给actItem,就做出了点击选中功能;
- 如果在数据中间有某一天是不能选中的,也可以根据日期算出这两个索引,然后把这一项的status赋值为gray即可
最后就是title吸顶和滚动阴影
我写到这都烦了,估计看到这也该烦了,也不知道你看没看懂,先说声谢谢
这个title吸顶吧,css就可以解决:position: sticky,还要配合top属性,具体的百度一下就会用了,我以前确实不知道这个,涨姿势了。
再说滚动阴影,飞猪的web端(似乎)用了jQuery,为了遵循vue不操作DOM的中心思想(其实是我没看懂人家的代码),我用了一个投机取巧的方法:写一个p标签,把它定位到月份title的位置,给它加上阴影,用z-index控制其层级在title下面,在滚动事件里控制这个标签的显示隐藏,scrollTop大于0就显示,否则不显示,这样视觉就很像哪个title在顶部,哪个就有阴影。(不会做gif图的我只能这么描述了)
各公司业务不同,希望有帮助,如果有空我会再完善这篇文章。