chili 默默学编程

分页功能 - 网站更新记录


1、什么是分页功能

这就是分页功能:


2、不分页有什么问题

一个问题是,会造成访问速度的降低:把较多内容放在一个页面显示时,访问这个页面,会一次性请求所有的数据,可能产生直观上能感受到的网络卡顿;

另一个问题是,会降低阅读体验。

3、这个问题只有分页才能解决吗

有其他办法解决,以豆瓣、知乎的界面为例。豆瓣知乎的数据量巨大,肯定不可能一次性全部给出所有数据,所以在其首页(或某个话题之下),都是一部分一部分的给出数据。

手机版的豆瓣,点击“首页”后会更新 6  条推荐文章。

网页版的豆瓣,也是“点击 - 添加”的模式:


手机版的知乎,滚动条往下滑到一定的时候,会自动在底部添加更多的内容。

网页版的知乎,也是根据滚动条位置自动添加内容,“滑动 - 添加”模式。

4、既然有其他解决方案,你为什么要用分页,你是不是不会其他办法

额。。是不会。

其实分页我也不会,临时查一查、想一想就会了。

其实 “点击 - 添加”、“滑动 - 添加” 两个模式,粗一想原理很简单的,监听 “click” 或 “scoll“ 事件,然后触发函数,函数里的动作行为通常是 AJAX 去请求新的数据,把新的数据插入到页面之中即可。

“分页” 是比较常见的解决方案,对于数据量很小的场景而言,“分页”可以让用户看见数据的总量(共多少页),心里有个数。

5、分页怎么实现

要讲清楚分页怎么实现,先说一下最开始不分页的情况。

5.1 不分页时,数据的传递过程

一个网站的后端服务器,概括起来说,其实只做了一件事:

管理 route

route 通常被称为 “路由”,就是 “网址路径“ 的意思。

管理 route,意思就是,用户在客户端发起一个访问路径的请求(简答的说就是打开浏览器,输入了一个网址),服务器收到请求的路径之后,返回一个 HTML 文件 ,客户端收到 HTML 文件,浏览器能把其中的内容展示出来。这就是访问一个网站的过程。

先不去说访问路由的请求还有 GET POST 的区别,返回的也不一定是一个 HTML 文件,先就这么理解。


服务器上管理 route 的代码大概是这样的(python flask 框架):

@main.route("/")
def index():
return render_template("index.html")

这是访问主页(主页后面是没有路径的,只有一个 “ / ”,路径前面的主机名是省略的,也就是“ shenkeyang.cc” 不用写),返回的就是主页的 HTML 文件(index.html)。


再举一个例子,访问 games 页面:

@main.route("/games")
def games():
return render_template("games/index.html")

这是访问 “shenkeyang.cc/games” 的时候,返回一个 对应的 HTML 文件(存在 games 文件夹之下的 index.html)。


但是上述代码有一个问题是,返回的 HTML 文件是一个写死的文件,这肯定是不行的,比如说主页上有很多 “文章” 的数据,怎么加载到返回的 HTML 文件里呢。

flask 可以在渲染模板(render_template)时增加参数(在渲染 HTML 文件时使用的是 jinja 的功能)。

比如说,我要把所有的文章(articles)都写进首页,那么 route 的代码就是这样的:

@main.route("/")
def index():
articles = Article.all()
return render_template("index.html", articles=articles)

“articles = Articles.all()”,是从数据库中拿出所有的 article 数据,articles 是所有 article 的数组;然后在渲染 index.html 文件的时候,把 articles 作为参数,传进去。


HTMl 文件使用 articles 参数的代码是:

{% for a in articles %}
<div>
{{ a.title }}
{{ a.author }}
{{ a.content }}
{{ a.created_time }}
</div>
{% endfor %}

其中 {% for ……  %} 是 jinja 的遍历语法

a.title,a.author,a.content,a.created_time 都是某一个 article 的字段信息(标题、作者、内容、时间)。


于是,一个加载所有文章的 index.html 文件就渲染出来了。当用户访问主页( “/” )时,就会得到一个从上到下得到所有文章信息的页面。


5.2 对于分页功能的需求拆解

现在来做分页功能。

按照 M(存储) 、V(展示)、 C(控制)分离的结构,分页展示的功能,主要属于 V、C两部分。

分页功能的需求,拆解开来有以下 2 点:

- 第 n 页只显示属于第 n 页的那些文章
- 在第 n 页时,生成对应的 导航标签



第一个需求意思很简单:

假设一页显示 9 篇文章,在第 1 页,显示序号为 1 - 9 的文章;第 2 页显示序号为 10 - 18 的文章。

实现这个需求,有两个步骤

一、知道当前是在第几页,这个数据通过路由获得:

http://shenkeyang.cc/?page=2

解析路由,将 page 的值得到即可。

flask 处理请求特别简单,其代码如下(了解一下):

qs = request.args
page_current = int(qs.get('page', '1'))

“ page_current ”就是当前是第 几 页。

二,根据 page_current 计算显示的序号范围,也很简单

start = (page_current - 1)* 9 + 1

end =  page_current * 9

注意,最后一页 end 有些许不同,需要有例外的条件判断:

if end > len(articles):
end = len(articles)


第二个需求比较复杂。

在第 n 页,生成对应的导航标签,是什么意思呢

导航标签就是这样的



看起来很自然,但是不同的情况,导航标签的样式是不一样的。

比如页面比较少的时候,就把所有页码全部写出来:


页面比较多的时候,只显示当前页码、前后附近页码、首尾页码,其他页码用 “…” 显示:



(我搜索的时候,发现有人已经做好了样式,可以复制使用,但是理解别人的思路比自己重新做一个更费时间,于是自己做了一个)


我把逻辑梳理了一下,用准确的语言,可以描述如下:

1、当页面总数量 <= 5 时,那就把所有的页码都写进一个列表,展示出来,例如:
如果只有 2 个页面,那就是 [1, 2]
如果只有 4 个页面,那就是 [1, 2, 3, 4]
如果只有 5 个页面,那就是 [1, 2, 3, 4, 5]

2、当总页面 n > 5时,则根据当前页码 m 的大小,显示页码列表:
第 1 页、第 2 页时,m = 1或2,那就是[1, 2, 3, '...', n]
第 3 页时,m = 3,那就是[1, 2, 3, 4, '...', n]
中间页时,m 不大不小,那就是[1, '...', m-1, m, m+1, '...', n]
倒数第 3 页时,m = n-2,那就是[1, '...', n-3, n-2, n-1, n]
倒数第 2 页、倒数第 1 页时,m = n-1 或 n,那就是[1, '...', n-2, n-1, n]

相当于,根据当前页(page_current),和总页数(page_total),计算导航列表(page_nav)的内容。

对应的代码是:

page_nav = make_navi_list(page_current, page_total)

其中 make_navi_list 就是上述计算逻辑的函数,这段代码很丑,也贴上来吧:

def make_navi_list(current, total):
nav = []
if total <= 5:
i = 1
while i <= total:
nav.append(i)
i += 1
else:
if current <= 2:
nav = [1, 2, 3, '...', total]
elif current == 3:
nav = [1, 2, 3, 4, '...', total]
elif current > 3 and current < total - 2:
nav = [1, '...', current - 1, current, current + 1, '...', total]
elif current == total - 2:
nav = [1, '...', current - 1, current, current + 1, total]
elif current >= total -1:
nav = [1, '...', total - 2, total - 1, total]
return nav


这样第二个需求也做好了。


最后就是把最好的导航,作为参数,传送到 HTML 文件即可:

@main.route("/")
def index():
qs = request.args
page_current = int(qs.get('page', '1'))
index_length = 6
articles = Article.all()
page_total = int((len(articles) + index_length - 1) / index_length)
page_nav = make_navi_list(page_current, page_total)
end_page = index_length * page_current
if end_page > len(articles):
end_page = len(articles)
articles = articles[index_length * (page_current - 1):end_page]
return render_template("index.html",articles=articles, nav=page_nav, page_current=page_current)


HTML 文件使用 nav:

<div class="pagenavi">
<span class="page-numbers">第 {{ page_current }} 页 , 共 {{ nav[-1] }} 页</span>
{% for n in nav %}
{% if n == page_current %}
<span class='current'>{{ n }}</span>
{% else %}
{% if n == '...' %}
<span class="page-numbers">...</span>
{% else %}
<a href="{{ url_for('.index', page=n) }}" >{{ n }}</a>
{% endif %}
{% endif %}
{% endfor %}
</div>


最后效果如下:






reply ( 0 )