文章分页功能

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

pagination

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

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

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

MongoDB 实现分页原理

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

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

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

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

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

实现一个基本的分页器

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

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

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

// routes/posts.js
module.exports = {
    async index (ctx, next) {
        const pageSize = 15
        const currentPage = parseInt(ctx.query.page) || 1
        const allPostsCount = await PostModel.count()
        const pageCount = Math.ceil(allPostsCount / pageSize)
        const posts = await PostModel.find(query).skip((currentPage - 1) * pageSize).limit(pageSize)
        await ctx.render('index', {
            title: 'JS之禅',
            posts,
            currentPage,
            pageCount
        }

    }
}

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

高级一点儿的分页器

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

// routes/posts.js#index
const pageStart = currentPage - 2 > 0 ? currentPage - 2 : 1
const pageEnd = pageStart + 4 >= pageCount ? pageCount : pageStart + 4
const baseUrl = ctx.path + '?page='
await ctx.render('index', {
    title: 'JS之禅',
    posts,
    currentPage,
    pageCount,
    pageStart,
    pageEnd,
    baseUrl
}

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

{%  if 1 < pageCount and pageCount >= currentPage  %}
<nav class="pagination is-small margin-top" role="navigation" aria-label="pagination">
    {# 上一页 #}
    {% if currentPage == 1 %}
    <a class="pagination-previous">Previous</a>
    {% else %}
    <a href="{{baseUrl + (currentPage - 1)}}" class="pagination-previous">Previous</a>
    {% endif %}
    {# 下一页 #}
    {% if currentPage == pageCount %}
    <a class="pagination-previous">Next page</a>
    {% else %}
    <a href="{{baseUrl + (currentPage + 1)}}" class="pagination-next">Next page</a>
    {% endif %}

    <ul class="pagination-list">
        {# 第一页 #}
        {% if currentPage == 1 %}
        <li><a href="{{baseUrl + 1}}" class="pagination-link is-current" aria-label="Goto page 1">1</a></li>
        {% else %}
        <li><a href="{{baseUrl + 1}}" class="pagination-link" aria-label="Goto page 1">1</a></li>
        {% endif %}

        {% if pageStart > 1 %}
        <li><span class="pagination-ellipsis">&hellip;</span></li>
        {% endif %}
        {# 页码 #}

        {# 渲染当前页和当前页的上下一页 #}
        {% for i in range(pageStart + 1, pageEnd) %}
            {% if i == currentPage  %}
            <li><a class="pagination-link is-current" aria-label="Goto page {{i}}">{{i}}</a></li>
            {% else %}
            <li><a href="{{baseUrl + i}}" class="pagination-link" aria-label="Goto page {{i+1}}">{{i}}</a></li>
            {% endif %}
        {% endfor %}

        {% if pageEnd < pageCount %}
        <li><span class="pagination-ellipsis">&hellip;</span></li>
        {% endif %}
        {# 最后一页 #}
        {% if currentPage == pageCount%}
        <li><a href="{{baseUrl + pageCount}}" class="pagination-link is-current" aria-label="Goto page {{pageCount}}">{{pageCount}}</a></li>
        {% else %}
        <li><a href="{{baseUrl + pageCount}}" class="pagination-link" aria-label="Goto page {{pageCount}}">{{pageCount}}</a></li>
        {% endif %}
    </ul>
</nav>
{% else %}
<p class="has-text-centered margin-top is-size-7 has-text-grey">没有更多了... </p>
{% endif %}

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

修改routes/posts.js的index.js

async index (ctx, next) {
    console.log(ctx.session.user)
    const pageSize = 5
    const currentPage = parseInt(ctx.query.page) || 1
    // 分类名
    const cname = ctx.query.c
    let cid
    if (cname) {
        // 查询分类id
        const cateogry = await CategoryModel.findOne({ name: cname })
        cid = cateogry._id
    }
    // 根据是否有分类来控制查询语句
    const query = cid ? { category: cid } : {}
    const allPostsCount = await PostModel.find(query).count()
    const pageCount = Math.ceil(allPostsCount / pageSize)
    const pageStart = currentPage - 2 > 0 ? currentPage - 2 : 1
    const pageEnd = pageStart + 4 >= pageCount ? pageCount : pageStart + 4
    const posts = await PostModel.find(query).skip((currentPage - 1) * pageSize).limit(pageSize)
    // 根据是否有分类来控制分页链接
    const baseUrl = cname ? `${ctx.path}?c=${cname}&page=` : `${ctx.path}?page=`
    await ctx.render('index', {
        title: 'JS之禅',
        posts,
        pageSize,
        currentPage,
        allPostsCount,
        pageCount,
        pageStart,
        pageEnd,
        baseUrl
    })
},