首先我们需要确定如何存储获取到的商品数据。在后端定义的接口中,所有商品数据被分为new/pop/sell三类,它们互不影响。假设页面默认停留在new类商品,第一次请求(即页面初始化显示)时应该获取每一类商品的第1页。并且随着用户滚动页面,可以依次获取当前new类商品的第2、3……页数据。当用户通过点击TabControl切换到pop时,页面仍然停留在pop类商品的第一页。根据以上需求,我们采用下面的方案:定义一个大的goods对象,包含new/pop/sell三个子对象,每个子对象包含page和list两个属性。(方便逐页加载)
goods: { pop: { page: 0, list: [] }, new: { page: 0, list: [] }, sell: { page: 0, list: [] }, }
然后是我们之前封装过的网络请求函数,它接受两个参数:type和page:
getHomeGoods(type, page){ return request({ url: '/home/data', params: { type, page } }) }
page表示:该加载第几页的数据,应该是当前type的page+1;type则是new/pop/sell三者之一。请求成功后,在then回调中将传来的数据加入到对应type的list中,然后将该type的page+1。将这些内容再次封装为函数getHomeGoodsUSE:
getHomeGoodsUSE(type) { const page = this.goods[type].page + 1; getHomeGoods(type, page).then( res => { this.goods[type].list.push(...res.data.list); this.goods[type].page += 1; // 设置可以多次“上拉加载更多” this.$refs.scroll.finishPullUp(); }); }
在页面初始化时,应该获取每一类商品的第1页。所以在created生命周期钩子函数中调用getHomeGoodsUSE:
created() { this.getHomeGoodsUSE("pop"); this.getHomeGoodsUSE("new"); this.getHomeGoodsUSE("sell"); }
拿到的数据被保存在goods对象中,要想显示在页面中,需要知道当前被选中的是哪一类商品。之前我们在TabControl组件的实现中给Home添加了一个currentType属性,可以用它来构造一个计算属性:
computed: { showGoods() { return this.goods[this.currentType].list; }, }
将这个商品列表发送给之前定义的子组件GoodsList,GoodsList再把每个商品的信息发送给孙子组件GoodsListItem,GoodsListItem读取某个商品的image、price、cfav、title。这样就完成了第一页数据的显示。
/* Home组件中 */ <goods-list :goods="showGoods"></goods-list>
/* GoodsList组件中 */ <goods-list-item v-for="(item, index) in goods" :key="index" :goods-item="item"/>
/* GoodsListItem组件中 */ <template> <div class="goods-item" @click="itemClick"> <img :src="showImg" alt="" @load="imageLoad"> <div class="goods-info"> <p>{{goodsItem.title}}</p> <span class="price">{{goodsItem.price}}</span> <span class="collect">{{goodsItem.cfav}}</span> </div> </div> </template>
但是要继续加载下一页的数据,需要用户滑动到底后上拉。这个功能在BetterScroll中有专门的解决方案,但是会出现很多bug。在这里我们也简单介绍一下一直在使用的BetterScroll框架。
我们在Scroll组件中导入BetterScroll框架,然后在mounted()函数中创建其实例。BScroll( )第一个参数为一个html元素,该元素可以看做滚动区域的容器,里面只能有一个包裹层元素,包裹层内部才是需要滚动的内容,可以有任意多个元素。第二个参数为一个可选的配置对象,有下面几个选项:
1、probeType:数值,表示是否开启滚动位置监听。0/1表示不监听,2表示滑动过程中实时监听位置,但惯性期间不监听,3表示惯性期间也监听。该选项不要写死,使用时指定,在没有必要的时候设为3会影响性能。
2、click:布尔值,默认为false,会阻止div之类的非按钮元素对原生事件的监听。
3、pullUpLoad:布尔值,表示是否开启上拉加载更多。
根据第一个参数规定的格式,我们需要设计两层div分别代表容器和包裹层,然后在里面放置一个插槽,当使用时用需要滚动的内容替换掉插槽即可。
<template> <div class="container" ref="container"> <div class="wrapper"> <slot></slot> </div> </div> </template>
this.scroll = new BScroll(this.$refs.container, { probeType: this.probeType, click: true, pullUpLoad: this.pullUpLoad, });
<scroll class="content" ref="scroll" :probe-type="3" :pull-up-load="true" @scroll="scrollEvent" @pullingUp="loadMore"></scroll>
特别注意第一个参数这里使用$refs来获取元素,一定不要用document.querySelector('.container'),因为可能会被其它组件中的同类名元素所干扰。
当传入的probeType属性为2或3时,就开启对滚动位置的实时监听,向Home组件中发送自定义的scroll事件和一个position参数,然后在Home组件的scrollEvent方法中利用position完成一些操作,比如之前的BackTop组件的显示与隐藏、TabControl组件的悬顶等。
if (this.probeType === 2 || this.probeType === 3) { this.scroll.on("scroll", (position) => { this.$emit("scroll", position); }); }
当传入的pullUpLoad属性为true时,就开启对滚动到底部后下拉动作的监听,向Home组件中发送自定义的pullingUp事件,然后在Home组件的loadMore方法中完成加载更多的功能,只需要调用一次getHomeGoodsUSE即可:
if (this.pullUpLoad) { this.scroll.on("pullingUp", () => { this.$emit("pullingUp"); }); }
loadMore() { this.getHomeGoodsUSE(this.currentType); }
由于scroll组件中的上拉加载更多只能被监听一次,所以每次加载完成后需要调用finishPullUp方法来表明加载结束,这样才能开启下一次加载,实现多次下拉加载更多。调用语句在上面第三个代码块的最后,finishPullUp为sroll自带的方法:
finishPullUp() { this.scroll && this.scroll.finishPullUp(); }
至此,实现了商品列表显示的全部功能。