如果一个列表有365天的日期或者更多,全部渲染到列表里会耗费浏览器性能,所以我们通过虚拟列表来实现它。
template层,主要是实现dom结构,逻辑主要是来判断是单程日历还是往返日历,往返日历要显示两个日期,
css样式我会放在最底部,是采用less写的。
<template>
<div class="calendar-lowprice">
<div class="indicate icon-arrow-left" :class="{disabled: columnIndex == 0}" @click.stop="arrowLeft"></div>
<!-- 日历 -->
<div class="calendar-wrap" ref="calendarWrap">
<!-- 单程 -->
<ul class="calendar-ul" ref="calendarUl" :style="documentStyle" v-if="!mode">
<li v-for="(item, i) in showCalendarList" :key="i" :class="{active: (item.fullDate == startDate)}" @click="changeDate(item)">
<div class="date">{{ item.month }}月{{ item.day }}日</div>
<div v-if="item.fullDate == startDate && !minPriceLoading"><span class="money">{{ ('¥' + minPrice) }}</span><span >起</span></div>
<div v-else class="week">星期{{ item.week }}</div>
</li>
</ul>
<!-- 往返 -->
<ul class="calendar-ul back-calendar" ref="calendarUl" :style="documentStyle" v-else>
<li v-for="(item, i) in showCalendarList" :key="i" :class="{active: (item.fullDate == startDate)}" @click="changeDate(item)">
<div class="date">{{ item.month }}月{{ item.day }}日 周{{ item.week }} <span class="c666">去</span></div>
<div class="date"><span v-if="item.back_month">{{ item.back_month }}月{{ item.back_day }}日 周{{ item.back_week }}</span><span v-else>未选择</span> <span class="c666">返</span></div>
<div v-if="item.fullDate == startDate && !minPriceLoading"><span class="money">{{ ('¥' + minPrice) }}</span><span >起</span></div>
<div v-else-if="item.fullDate == startDate" class="c666">询价中...</div>
</li>
</ul>
</div>
<div class="indicate icon-arrow-right" :class="{disabled: columnIndex == getCountRow}" @click="arrowRight"></div>
</div>
</template>
script层,引入了一个方法 getFutureDates 的工具函数,这个工具函数可以在我的上一篇文章中找到。data数据里有个 rowNum 字段它是当前想让列表可见区域显示多少天,比如显示一周就要设为 7、主要就是这个数据需要手动设置。
props数据:
必须传的就是出发时间startDate,后面程序会自动计算当前选中的日期在所有未来日期列表中的第几组,然后通过slice来剪切所要显示的列表数据。对应的left样式也会跟着移动。
<script>
import {
getFutureDates, // 时间组件
} from "../assets/js/date"
export default {
name: 'calendar-lowprice', // 最低价日历
data() {
return {
calendarList: [],
wrapWidth: 0,
rowNum: 8, // 一排展示8个元素
columnIndex: 0, // 当前日期所在哪一列
minPriceLoading: false,
}
},
props: {
// 出发时间
startDate: {
type: String,
default: ""
},
// 返程时间
backDate: {
type: String,
default: ""
},
// 最低价
minPrice: {
type: [String, Number],
default: ""
},
// 往返模式
mode: {
type: Boolean,
default: false
},
// 回调函数
dateCallBack: {
type: Function,
default: () => {}
}
},
computed: {
// 元素的style
documentStyle() {
let left = 0;
if(this.columnIndex > 0) {
left = `${((this.columnIndex - 1) * this.wrapWidth)}px`
}
return {
"margin-left": left,
transform: `translateX(-${this.columnIndex * this.wrapWidth}px)`
}
},
// 获取当前可视数据
showCalendarList() {
let arr = []; // 要显示的数据
let startArr = []; // 开头的数据
let endArr = []; // 结尾的数据
arr = this.calendarList.slice(this.columnIndex * this.rowNum, (this.columnIndex * this.rowNum) + this.rowNum);
if(this.columnIndex > 0) {
startArr = this.calendarList.slice((this.columnIndex - 1) * this.rowNum, ((this.columnIndex - 1) * this.rowNum) + this.rowNum);
}
endArr = this.calendarList.slice((this.columnIndex + 1) * this.rowNum, ((this.columnIndex + 1) * this.rowNum) + this.rowNum);
return [...startArr, ...arr, ...endArr]
},
// 获取一共多少列
getCountRow() {
return Math.floor(this.calendarList.length / this.rowNum)
}
},
watch: {
startDate(newVal, oldVal) {
this.minPriceLoading = true;
this.getIndex(); // 搜索时间一有变化就重新定位虚拟列表位置
},
minPrice(newVal, oldVal) {
this.minPriceLoading = false;
},
backDate(newVal, oldVal) {
if(new Date(newVal).getDay() > -1) {
this.init();
}
},
},
mounted() {
this.wrapWidth = this.$refs.calendarWrap.offsetWidth; // 外层盒子的宽
},
created() {
this.init()
},
methods: {
// 寻找当前下标
getDataIndex(date) {
let index = 0;
for(let i = 0; i < this.calendarList.length; i++) {
if(date == this.calendarList[i].fullDate) {
index = i;
break;
}
}
return index
},
// 选择日期
changeDate(item) {
if(this.startDate == item.fullDate) {
return
}
this.dateCallBack(item)
},
// 上一页
arrowLeft() {
this.columnIndex -= 1;
},
// 下一页
arrowRight() {
this.columnIndex += 1;
},
// 获取当前日期所在下标的所在列
getColumnIndex() {
this.columnIndex = Math.floor(this.getIndex() / this.rowNum);
return this.columnIndex
},
// 获取当前日期所在下标
getIndex() {
let index = 0;
for(let i = 0; i < this.calendarList.length; i++) {
let item = this.calendarList[i];
if(item.fullDate == this.startDate) {
console.log(item)
index = i;
break;
}
}
this.columnIndex = Math.floor(index / this.rowNum)
return index;
},
init() {
if(this.mode && !this.backDate) {
return console.error('请传入返程日期')
}
this.calendarList = getFutureDates(365, {
key: {
lowprice: "查看"
},
back_date: this.mode ? this.backDate : false, // 返回返程
start_date: this.startDate, // 返回返程也要传出发日期
})
this.getIndex();
}
}
};
</script>
到这里就差不多结束了,下面都是css样式,使用下列样式需要通过less编译
<style lang="less" scoped>
@defale-height: 72px;
.calendar-lowprice {
display: flex;
align-items: center;
text-align: center;
font-size: 16px;
background: #fff;
cursor: pointer;
user-select: none;
border-radius: 4px;
.indicate {
width: 65px;
line-height: @defale-height;
font-size: 16px;
&.disabled {
color: #ccc;
pointer-events: none;
}
}
.calendar-wrap {
flex: 1;
overflow: hidden;
.calendar-ul {
text-align: left;
white-space: nowrap;
// overflow-x: scroll;
transition: transform .3s ease-in-out;
li {
text-align: center;
display: inline-block;
width: 123px;
height: @defale-height;
padding-top: 14px;
box-sizing: border-box;
&.active {
background: linear-gradient(180deg, #D6EDFF 0%, #FFFFFF 100%);
}
.date {
margin-bottom: 6px;
}
.money {
color: red;
}
}
}
// 往返
.back-calendar {
li {
padding-top: 20px;
vertical-align: top;
font-size: 13px;
&.active {
padding-top: 10px;
}
}
}
}
}
</style>