在线商城项目13-商品列表分页功能实现

简介

设想一下,如果商品条目数量很多,假设有100条,如果我们一次性拉下来,是很影响性能的。所以我们需要为商品列表添加分页功能。本篇主要实现以下目的:

  1. 后端分页功能逻辑实现
  2. 前端分页功能逻辑实现

1. 后端分页功能逻辑实现

这里我们有如下约定;

  1. 后端需要接收参数page和pageSize。
  2. page表示请求第几页数据。
  3. pageSize表示每页返回的数目。
  4. 按pageSize分页并返回指定页数的数据。

修改routes/goods.js文件如下:

/* GET goods */
router.get('/', function (req, res, next) {
    // 只有接口请求带参数sort=priceDown才会按价格降序
    let sort = req.query['sort'] === 'priceDown'?-1:1;
    let startPrice = req.query['startPrice'];
    let endPrice = req.query['endPrice'];
    let page = parseInt(req.query['page']);
    let pageSize = parseInt(req.query['pageSize']);

    // 价格筛选处理逻辑
    startPrice = startPrice? parseInt(startPrice):0;
    endPrice = endPrice?parseInt(endPrice):undefined;
    console.log(startPrice, endPrice);
    let params = {};
    if (endPrice) {
        params = {salePrice: {$gt: startPrice, $lte: endPrice}};
    } else {
        params = {salePrice: {$gt: startPrice}};
    }

    // 分页处理逻辑
    if (!(page > 0 && pageSize > 0)) {
        res.json({
            code: '801',
            msg: '请求参数有误'
        });
        return;
    }
    let skip = (page - 1)*pageSize;

    // 查询起始价(不包含)到结尾价(包含)区间的商品,并分页返回
    let query = Good.find(params).skip(skip).limit(pageSize);
    query.sort({salePrice: sort});
    query.exec((err, doc) => {
        if (err) {
            res.json({
                code: '900',
                msg: err.message || '服务器错误'
            })
        } else {
            res.json({
                code: '000',
                msg: '',
                result: {
                    count: doc.length,
                    list: doc
                }
            })
        }
    });
});

2. 前端分页功能逻辑实现

这里我们不再详述与后端分离开发的逻辑,直接使用已经开发好的后端接口来看效果。 step1 在前端写死分页参数请求数据并展示 此处由于后端返回的数据结构有了变化,为了看到效果,我们首先要更改一下前端数据处理逻辑。

export default {
  data () {
    return {
      prdList: [], // 产品列表
      // 价格过滤列表
      priceFilterList: [
        {
          startPrice: 0,
          endPrice: 100
        },
        {
          startPrice: 100,
          endPrice: 500
        },
        {
          startPrice: 500,
          endPrice: 2000
        },
        {
          startPrice: 2000
        }
      ],
      priceChecked: 'all', // 选中的价格过滤列表项
      filterPrice: null, // 选中的价格过滤列表对象
      isShowFilterBy: false, // 是否展示过滤列表弹窗
      isShowOverLay: false, // 是否展示遮罩层
      page: 1,
      pageSize: 8,
      sortChecked: 'default',
      isPriceUp: true
    }
  },
  computed: {
    isPriceArrowUp () {
      return !this.isPriceUp
    }
  },
  components: {
    PageHeader,
    PageBread,
    PageFooter
  },
  created () {
    this.getPrdList()
  },
  methods: {
    // 请求接口获取产品列表数据
    getPrdList () {
      queryPrdObj = Object.assign({}, this.filterPrice, {page: this.page, pageSize: this.pageSize, sort: this.sortChecked})
      axios.get('/api/goods', {params: queryPrdObj}).then((res) => {
        console.log('res', res)
        let data = (res && res.data) || {}
        if (data.code === '000') {
          let result = data.result || {}
          this.prdList = result.list || []
          console.log('prdList', this.prdList)
        } else {
          alert(`err:${data.msg || '系统错误'}`)
        }
      })
    },
    // 选取价格过滤列表项
    checkPriceFilter (index) {
      this.priceChecked = index
      this.filterPrice = index === 'all' ? null : this.priceFilterList[index]
      this.getPrdList()
      this.closeFilterBy()
    },
    // 展示过滤列表弹窗
    showFilterBy () {
      this.isShowFilterBy = true
      this.isShowOverLay = true
    },
    // 关闭过滤列表弹窗
    closeFilterBy () {
      this.isShowFilterBy = false
      this.isShowOverLay = false
    },
    checkSort (val) {
      this.sortChecked = val
      if (val === 'priceUp' || val === 'priceDown') {
        this.isPriceUp = !this.isPriceUp
      } else {
        this.isPriceUp = true
      }
      this.getPrdList()
    }
  }
}

step2 使用vue-infinite-loading进行上拉加载 关于vue-infinite-loading使用方法见官网。 安装;

npm install vue-infinite-loading --save

six-tao-1301.gif

修改GoodsList.vue文件如下:

<template>
  <div>
    <PageHeader></PageHeader>
    <PageBread>
      <span>Goods</span>
    </PageBread>
    <div class="accessory-result-page accessory-page">
      <div class="container">
        <div class="filter-nav">
          <span class="sortby">Sort by:</span>
          <a href="javascript:void(0)" class="default" :class="{'cur': sortChecked === 'default'}" @click="checkSort('default')">Default</a>
          <a href="javascript:void(0)" class="price" :class="{'cur': sortChecked === 'priceUp' ||  sortChecked === 'priceDown'}" @click="checkSort(isPriceUp?'priceDown':'priceUp')">Price
            <span v-if="isPriceArrowUp">↑</span>
            <span v-else>↓</span>
            <!--<svg class="icon icon-arrow-short">-->
              <!--<symbol id="icon-arrow-short" viewBox="0 0 25 32">-->
                <!--<title>arrow-short</title>-->
                <!--<path class="path1" d="M24.487 18.922l-1.948-1.948-8.904 8.904v-25.878h-2.783v25.878l-8.904-8.904-1.948 1.948 12.243 12.243z"></path>-->
              <!--</symbol>-->
              <!--<use xlink:href="#icon-arrow-short"></use>-->
            <!--</svg>-->
          </a>
          <a href="javascript:void(0)" class="filterby stopPop" @click="showFilterBy">Filter by</a>
        </div>
        <div class="accessory-result">
          <!-- filter -->
          <div class="filter stopPop" id="filter" :class="{'filterby-show': isShowFilterBy}">
            <dl class="filter-price">
              <dt>Price:</dt>
              <dd><a href="javascript:void(0)" :class="{'cur': priceChecked === 'all'}" @click="checkPriceFilter('all')">All</a></dd>
              <dd v-for="(item, index) in priceFilterList" :key="index">
                <a v-if="item.endPrice" href="javascript:void(0)" :class="{'cur': priceChecked === index}" @click="checkPriceFilter(index)">{{item.startPrice}} - {{item.endPrice}}</a>
                <a v-else href="javascript:void(0)" :class="{'cur': priceChecked === index}" @click="checkPriceFilter(index)">> {{item.startPrice}}</a>
              </dd>
            </dl>
          </div>

          <!-- search result accessories list -->
          <div class="accessory-list-wrap">
            <div class="accessory-list col-4">
              <ul>
                <li v-for="item in prdList" :key="item.productId">
                  <div class="pic">
                    <a href="#"><img v-lazy="`../../../static/${item.productImage}`" alt=""></a>
                  </div>
                  <div class="main">
                    <div class="name">{{item.productName}}</div>
                    <div class="price">{{item.salePrice}}</div>
                    <div class="btn-area">
                      <a href="javascript:;" class="btn btn--m">加入购物车</a>
                    </div>
                  </div>
                </li>
              </ul>
            </div>
            <infinite-loading @infinite="infiniteHandler">
              <span slot="no-more">
                No more ...
              </span>
            </infinite-loading>
          </div>
        </div>
      </div>
    </div>
    <div class="md-overlay" v-show="isShowOverLay" @click="closeFilterBy"></div>
    <PageFooter></PageFooter>
  </div>
</template>

<script>
import PageHeader from '../../components/PageHeader'
import PageBread from '../../components/PageBread'
import PageFooter from '../../components/PageFooter'
import axios from 'axios'
import InfiniteLoading from 'vue-infinite-loading'

let queryPrdObj = {}

export default {
  data () {
    return {
      prdList: [], // 产品列表
      // 价格过滤列表
      priceFilterList: [
        {
          startPrice: 0,
          endPrice: 100
        },
        {
          startPrice: 100,
          endPrice: 500
        },
        {
          startPrice: 500,
          endPrice: 2000
        },
        {
          startPrice: 2000
        }
      ],
      priceChecked: 'all', // 选中的价格过滤列表项
      filterPrice: null, // 选中的价格过滤列表对象
      isShowFilterBy: false, // 是否展示过滤列表弹窗
      isShowOverLay: false, // 是否展示遮罩层
      page: 1,
      pageSize: 8,
      sortChecked: 'default',
      isPriceUp: true
    }
  },
  computed: {
    isPriceArrowUp () {
      return !this.isPriceUp
    }
  },
  components: {
    PageHeader,
    PageBread,
    PageFooter,
    InfiniteLoading
  },
  created () {
    // this.getPrdList()
  },
  methods: {
    // 请求接口获取产品列表数据
    getPrdList ($state) {
      queryPrdObj = Object.assign({}, this.filterPrice, {page: this.page, pageSize: this.pageSize, sort: this.sortChecked})
      axios.get('/api/goods', {params: queryPrdObj}).then((res) => {
        console.log('res', res)
        this.page++
        let data = (res && res.data) || {}
        if (data.code === '000') {
          let result = data.result || {}
          this.prdList = this.prdList.concat(result.list || [])
          if (result.count === this.pageSize) {
            $state.loaded()
          } else {
            $state.complete()
          }
        } else {
          alert(`err:${data.msg || '系统错误'}`)
          $state.complete()
        }
      })
    },
    // 选取价格过滤列表项
    checkPriceFilter (index) {
      this.priceChecked = index
      this.filterPrice = index === 'all' ? null : this.priceFilterList[index]
      this.getPrdList()
      this.closeFilterBy()
    },
    // 展示过滤列表弹窗
    showFilterBy () {
      this.isShowFilterBy = true
      this.isShowOverLay = true
    },
    // 关闭过滤列表弹窗
    closeFilterBy () {
      this.isShowFilterBy = false
      this.isShowOverLay = false
    },
    checkSort (val) {
      this.sortChecked = val
      if (val === 'priceUp' || val === 'priceDown') {
        this.isPriceUp = !this.isPriceUp
      } else {
        this.isPriceUp = true
      }
      this.getPrdList()
    },
    infiniteHandler ($state) {
      this.getPrdList($state)
    }
  }
}
</script>

总结

到此,商品列表页的查询展示逻辑基本上完成了。 我们提交代码:

  1. six-tao
git status
git diff
git commit -am 'add vue-infinite-loading'
git push
  1. six-tao-server
git status
git diff
git commit -am 'paging logic done'
git push

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微服务生态

Akka简单的性能测试

这种方案是采用MQ作为中间的媒介,在服务端采用线程池异步处理任务,处理完成之后将结果发送到MQ中,客户端采用侦听的方式得到结果继续进行处理。

1441
来自专栏有趣的Python和你

Python爬虫之微打赏爬虫网站分析代码

1312
来自专栏魏艾斯博客www.vpsss.net

代码实现 WordPress 文章中英文数字间自动添加空格

2433
来自专栏DeveWork

WordPress删除头部wp_head()多余代码

如果你有查看过你的WordPress博客的“查看源代码”的话,你会发现头部的html代码非常多,而且是密密麻麻,有些像meta name="generator"...

8727
来自专栏云瓣

通过一个demo了解Redux

TodoList小demo 效果展示 项目地址 (单向)数据流 数据流是我们的行为与响应的抽象;使用数据流能帮我们明确了行为对应的响应,这和react的状态可预...

33710
来自专栏一“技”之长

iOS8新特性扩展(Extension)应用之三——照片编辑插件

        通过前几篇博客的介绍,我们了解到扩展给app提供的更加强大的交互能力,这种强大的交互能力另一方面体现在照片编辑插件的应用。

842
来自专栏技术博文

关于微信二次分享,标题变链接的解决方法(二)----代码部分

声明: 本篇博文只是个人工作中的分享总结,仅代表个人观点,虽然解决了不少网友的问题,但同时也引来了一些网友的不满,所以特此声明,当您遇到本博文解决不了的问题,可...

3626
来自专栏小詹同学

发爬虫和反反爬虫(下篇)

首先我们都知道要爬取网站图片有简单的几个步骤:获取网页源码、审阅元素寻找所需信息、下载并保存到指定位置。

1702
来自专栏Python绿色通道

用Xpath,bs4,正则三种方式爬51job

最近和星球球友一起做了一个爬取51job上的Python招聘职位的项目实战,数据如下:

2583
来自专栏日常学python

利用python爬取网易云音乐,并把数据存入mysql

这是我的第四篇原创文章 在简单学习了python爬虫后,又想继续折腾,进而找到了这个网易云音乐,因为本人平时就是用它听的歌,也喜欢看歌里的评论,所以就爬网易云...

1.4K12

扫码关注云+社区

领取腾讯云代金券