分页功能

随着内容的增加,我们的文章会越来越多,全部一次显示出来会增加数据查询耗时,同时不利用用户浏览。我们就需要分页功能。

pagination

在MongoDB 终端中运行如下代码新增 101个文章

  1. for(var i = 0; i < 101; i++){
  2. db.posts.insert({
  3. title: 'test ' + i,
  4. content: 'test' + i,
  5. category: ObjectId("5b15f4f45aaaa85ea7bccf65"),
  6. author : ObjectId("5b07648464ce83289036ea71")
  7. })
  8. }

现在访问主页,将返回包含101个文章的列表。

MongoDB 实现分页原理

MongoDB实现分页主要有两种方式

  1. 通过skip与limit()方法 实现分页
  2. 获取前一页的最后一条记录,查询之后的指定条记录

本案例将通过第一种方式实现,修改下routes/posts.js 的index方法

  1. const pageSize = 15
  2. const currentPage = parseInt(ctx.query.page) || 1
  3. const posts = await PostModel.find({}).skip((currentPage - 1) * pageSize).limit(pageSize)

我们通过 /posts?page=2 的方式来传页码。第二页就应该跳过前15 条记录再返回16到第30条内容。可以在浏览器中更改页码试试

实现一个基本的分页器

前面我们了解到分页的原来,现在来渲染分页器。新建一个分页器的组件components/pagination.html

  1. // components/pagination.html
  2. {% if 1 < pageCount and pageCount >= currentPage %}
  3. <nav class="pagination is-small" role="navigation" aria-label="pagination">
  4. <a href="/posts?page={{currentPage - 1}}" class="pagination-previous">Previous</a>
  5. <a href="/posts?page={{currentPage + 1}}" class="pagination-next">Next page</a>
  6. <ul class="pagination-list">
  7. {% for i in range(1, pageCount + 1) %}
  8. {% if i == currentPage %}
  9. <li><a href="/posts?page={{i}}" class="pagination-link is-current" aria-label="Goto page 1">{{i}}</a></li>
  10. {% else %}
  11. <li><a href="/posts?page={{i}}" class="pagination-link" aria-label="Goto page 1">{{i}}</a></li>
  12. {% endif %}
  13. {% endfor %}
  14. </ul>
  15. </nav>
  16. {% else %}
  17. <p class="has-text-centered margin-top is-size-7 has-text-grey">没有更多了... </p>
  18. {% endif %}

在这分页器中,我们需要总页数,当前页。然后循环显示出每一页的页码

  1. // routes/posts.js
  2. module.exports = {
  3. async index (ctx, next) {
  4. const pageSize = 15
  5. const currentPage = parseInt(ctx.query.page) || 1
  6. const allPostsCount = await PostModel.count()
  7. const pageCount = Math.ceil(allPostsCount / pageSize)
  8. const posts = await PostModel.find(query).skip((currentPage - 1) * pageSize).limit(pageSize)
  9. await ctx.render('index', {
  10. title: 'JS之禅',
  11. posts,
  12. currentPage,
  13. pageCount
  14. }
  15. }
  16. }

通过count()方法获取到文章的总数,然后算出页数,再通过skip().limt() 来获取当页数据。现在一个基本的分页器就已经实现了。但是有个问题,如果页数特别多没页面上就会显示出很多也页码按钮不出来。

高级一点儿的分页器

现在来实现一个高级一点儿的分页器(即文首的图片中的那样的分页器)。根据当前页码显示出前后两页,其他显示为三个点。这个分页器的关键在于设置需要显示的起始页和结束页,即循环页码时不再从1开始到pageCount结束,而是从pageStart(起始页)到pageEnd(结束页)结束。我们根据当前页来计算起始和结束

  1. // routes/posts.js#index
  2. const pageStart = currentPage - 2 > 0 ? currentPage - 2 : 1
  3. const pageEnd = pageStart + 4 >= pageCount ? pageCount : pageStart + 4
  4. const baseUrl = ctx.path + '?page='
  5. await ctx.render('index', {
  6. title: 'JS之禅',
  7. posts,
  8. currentPage,
  9. pageCount,
  10. pageStart,
  11. pageEnd,
  12. baseUrl
  13. }

修改components/pagination.html 来渲染当前页及当前页的上下页

  1. {% if 1 < pageCount and pageCount >= currentPage %}
  2. <nav class="pagination is-small margin-top" role="navigation" aria-label="pagination">
  3. {# 上一页 #}
  4. {% if currentPage == 1 %}
  5. <a class="pagination-previous">Previous</a>
  6. {% else %}
  7. <a href="{{baseUrl + (currentPage - 1)}}" class="pagination-previous">Previous</a>
  8. {% endif %}
  9. {# 下一页 #}
  10. {% if currentPage == pageCount %}
  11. <a class="pagination-previous">Next page</a>
  12. {% else %}
  13. <a href="{{baseUrl + (currentPage + 1)}}" class="pagination-next">Next page</a>
  14. {% endif %}
  15. <ul class="pagination-list">
  16. {# 第一页 #}
  17. {% if currentPage == 1 %}
  18. <li><a href="{{baseUrl + 1}}" class="pagination-link is-current" aria-label="Goto page 1">1</a></li>
  19. {% else %}
  20. <li><a href="{{baseUrl + 1}}" class="pagination-link" aria-label="Goto page 1">1</a></li>
  21. {% endif %}
  22. {% if pageStart > 1 %}
  23. <li><span class="pagination-ellipsis">&hellip;</span></li>
  24. {% endif %}
  25. {# 页码 #}
  26. {# 渲染当前页和当前页的上下一页 #}
  27. {% for i in range(pageStart + 1, pageEnd) %}
  28. {% if i == currentPage %}
  29. <li><a class="pagination-link is-current" aria-label="Goto page {{i}}">{{i}}</a></li>
  30. {% else %}
  31. <li><a href="{{baseUrl + i}}" class="pagination-link" aria-label="Goto page {{i+1}}">{{i}}</a></li>
  32. {% endif %}
  33. {% endfor %}
  34. {% if pageEnd < pageCount %}
  35. <li><span class="pagination-ellipsis">&hellip;</span></li>
  36. {% endif %}
  37. {# 最后一页 #}
  38. {% if currentPage == pageCount%}
  39. <li><a href="{{baseUrl + pageCount}}" class="pagination-link is-current" aria-label="Goto page {{pageCount}}">{{pageCount}}</a></li>
  40. {% else %}
  41. <li><a href="{{baseUrl + pageCount}}" class="pagination-link" aria-label="Goto page {{pageCount}}">{{pageCount}}</a></li>
  42. {% endif %}
  43. </ul>
  44. </nav>
  45. {% else %}
  46. <p class="has-text-centered margin-top is-size-7 has-text-grey">没有更多了... </p>
  47. {% endif %}

因为我们还有分类功能,我们还应该让这个分页器在显示分页分类文章的时候也适用,http://localhost:3000/posts?c=nodejs&page=2

修改routes/posts.js的index.js

  1. async index (ctx, next) {
  2. console.log(ctx.session.user)
  3. const pageSize = 5
  4. const currentPage = parseInt(ctx.query.page) || 1
  5. // 分类名
  6. const cname = ctx.query.c
  7. let cid
  8. if (cname) {
  9. // 查询分类id
  10. const cateogry = await CategoryModel.findOne({ name: cname })
  11. cid = cateogry._id
  12. }
  13. // 根据是否有分类来控制查询语句
  14. const query = cid ? { category: cid } : {}
  15. const allPostsCount = await PostModel.find(query).count()
  16. const pageCount = Math.ceil(allPostsCount / pageSize)
  17. const pageStart = currentPage - 2 > 0 ? currentPage - 2 : 1
  18. const pageEnd = pageStart + 4 >= pageCount ? pageCount : pageStart + 4
  19. const posts = await PostModel.find(query).skip((currentPage - 1) * pageSize).limit(pageSize)
  20. // 根据是否有分类来控制分页链接
  21. const baseUrl = cname ? `${ctx.path}?c=${cname}&page=` : `${ctx.path}?page=`
  22. await ctx.render('index', {
  23. title: 'JS之禅',
  24. posts,
  25. pageSize,
  26. currentPage,
  27. allPostsCount,
  28. pageCount,
  29. pageStart,
  30. pageEnd,
  31. baseUrl
  32. })
  33. },