首先我们需要确定如何存储获取到的商品数据。在后端定义的接口中,所有商品数据被分为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();
    }

至此,实现了商品列表显示的全部功能。

 

posted on 2021-06-27 15:47  springxxxx  阅读(188)  评论(0编辑  收藏  举报