持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
1.功能简介
在第二节中,我们已经把首页的基础框架建好了,首页分为主体以及右边的侧边栏个两个部分。在主体中我们需要显示论坛的帖子,其中帖子又可以根据一些条件进行分类,比如今日热帖、最新的帖子以及随机推荐的帖子等等。在侧边栏中需要显示一些用户信息、发布帖子入口等其他信息。本节主要是是处理首页的主体部分内容,以及侧边栏首个栏位(主要是侧边栏下面的没想好要添加什么内容)。
2.显示帖子列表
为了显示帖子列表,我们需要先在渲染主页模板文件的视图函数中获取帖子的相关数据,并传入到首页模板文件中。
a.最新的帖子
打开bbs/blueprint/frontend/index.py
模块,添加下面的代码。
from flask import Blueprint, render_template, request, current_app
from bbs.models import Post, VisitStatistic
from bbs.extensions import db
from sqlalchemy.sql.expression import func
from bbs.decorators import statistic_traffic
index_bp = Blueprint('index_bp', __name__)
@index_bp.route('/')
@index_bp.route('/index/')
def index():
page = request.args.get('page', 1, type=int)
pagination = Post.query.filter_by(status_id=1).order_by(Post.update_time.desc()).\
paginate(page, per_page=current_app.config['BBS_PER_PAGE'])
latest = pagination.items
return render_template('frontend/index.html', latest=latest, pagination=pagination)复制代码
在上面的代码中出现了一个新的东西paginate
,该方法会返回一个Pagination
类,这样就实现了分页操作。
当用户向首页路由发起请求后,首先会获取请求中的参数page
,如果不存在则默认为1,然后通过paginate
函数进行分页查询,获取到Pagination
实例,通过该实例我们可以获取到当前页码的帖子记录,然后将结果返回到模板文件中进行渲染。打开bbs/templates/frontend/index.html
文件,加入下面的代码。
{% block content %}
{{ moment.locale(auto_detect=True) }}
<main>
<div class="container mt-2">
{% include "_flash.html" %}
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('.index') }}">最新</a>
</li>
<li class="nav-item ">
<a class="nav-link" href="{{ url_for('.hot') }}">热帖</a>
</li>
<li class="nav-item ">
<a class="nav-link" href="{{ url_for('.rands') }}">随机</a>
</li>
</ul>
<hr class="bg-secondary">
<div class="row">
<div class="col-md-8 mt-1">
<div class="tab-content">
<div id="latestPost" class="tab-pane active">
{{ post_item(latest) }}
<div class="float-right mt-3">
{% if tag %}
{{ render_pagination(pagination) }}
{% endif %}
</div>
</div>
</div>
</div>
{% include "frontend/slider.html" %}
</div>
</div>
</main>
{% endblock %}复制代码
在上面代码中将帖子分为了三个tab分别是最新、热帖以及随机,三个tab分别定义了三个模板文件(另外两个后续会讲),在用户访问index路由的时候默认显示的最新的帖子。
在模板文件中,我们使用了post_item()
宏来渲染帖子的列表。因为我们显示帖子列表的页面有几个,所以可以将代码抽离出来进行宏定义,这样就减少了冗余的代码。打开bbs/templates/
文件夹,新建macro.html
宏定义文件,添加下面的代码到文件中。
{% macro post_item(posts) %}
<ul class="list-group">
{% for post in posts %}
<li class="list-group-item pl-1">
<div class="d-flex">
{% if post.is_anonymous == 1 %}
<img class="avatar-50 rounded mr-2" src="{{ post.user.avatar }}" alt="{{ post.user.nickname }}">
{% else %}
<img class="avatar-50 rounded mr-2" src="{{ url_for('static', filename='img/anonymous.jpeg') }}" alt="anonymous">
{% endif %}
<div class="flex-grow-1">
<a class="text-decoration-none" href="{{ url_for('post.read', post_id=post.id) }}">{{ post.title }}</a>
<div class="row mt-2">
<div class="col">
<p class="mb-0 text-muted f-12">
<a class="badge badge-secondary mr-2" href="{{ url_for('post.post_cate', cate_id=post.cats.id) }}">{{ post.cats.name }}</a>
<i class="fa fa-user-o mr-1"></i>
{% if post.is_anonymous == 1 %}
<a class="text-decoration-none" href="{{ url_for('profile.index', user_id=post.author_id) }}">{{post.user.nickname}}</a>
{% else %}
匿名
{% endif %}
<span><a class="text-decoration-none ml-2 text-muted flex-grow-1 text-left f-12-b" >
<span data-toggle="tooltip" data-placement="right" data-timestamp="{{ post.create_time }}" data-delay="500" data-original-title="" title="{{ post.create_time }}">{{ moment(post.create_time, local=True).fromNow(refresh=True) }}</span>
</a></span></p>
</div>
<div class="col text-right flex-row-reverse d-lg-flex d-none">
<p class="f-12 text-muted mb-0"><i class="fa fa-comment mr-1"></i>{{ post.comments|length }}</p>
<p class="f-12 text-muted mb-0"><i class="fa fa-eye mr-1"></i>{{post.read_times}} </p>
</div>
</div>
</div>
</div>
</li>
{% endfor %}
</ul>
{% endmacro %}复制代码
在该宏定义函数中,我们使用了bootstrap4的列表组组件来显示我们的帖子。通过for循环来遍历我们的帖子将每篇帖子的相关信息以列表的形式展示出来。同时如果帖子是用户匿名进行发布的,那么在显示帖子基本信息的时候都会用匿名信息进行填写。
我们通过上一节的帖子发布页面发布几个测试帖子,再次访问首页就可以看到如下所示的页面了。

b.最热的帖子
同样的在bbs/blueprint/frontend/index.py
模块中加入下面的代码
@index_bp.route('/hot-post/')
def hot():
page = request.args.get('page', 1, type=int)
pagination = Post.query.order_by(Post.read_times.desc()).paginate(page, per_page=current_app.config['BBS_PER_PAGE'])
hots = pagination.items
tag = pagination.total > current_app.config['BBS_PER_PAGE']
return render_template('frontend/index/hot-post.html', hots=hots, pagination=pagination, tag=tag)复制代码
上面的代码跟index视图函数
的代码十分类似,只是在数据库的查询条件上做了一些变化,通过帖子的阅读次数来进行排序,并将输出的内容进行分页处理。因此模板文件也与index.html
文件类似,在bbs/templates/frontend/index
目录中新建hot-post.html
模板文件,添加下面的代码
{% extends "frontend/base.html" %}
{% from "macro.html" import post_item, render_pagination with context%}
{% block title %}
主页
{% endblock %}
{% block content %}
{{ moment.locale(auto_detect=True) }}
<main>
<div class="container mt-2">
{% include "_flash.html" %}
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('.index') }}">最新</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('.hot') }}">热帖</a>
</li>
<li class="nav-item ">
<a class="nav-link" href="{{ url_for('.rands') }}">随机</a>
</li>
</ul>
<hr class="bg-secondary">
<div class="row">
<div class="col-md-8 mt-1">
<div class="tab-content">
<div id="latestPost" class="tab-pane active">
{{ post_item(hots) }}
<div class="float-right mt-3">
{% if tag %}
{{ render_pagination(pagination) }}
{% endif %}
</div>
</div>
</div>
</div>
{% include "frontend/slider.html" %}
</div>
</div>
</main>
{% endblock %}复制代码
c.随机推荐的帖子
处理完上面两个tab之后只剩下最后一个随机推荐的tab了,随机推荐的帖子数量我这里设置的是20篇帖子,我们可以使用flask-sqlachemy
orm框架中func
来轻松实现在数据库中随机获取对应条数的数据,在bbs/blueprint/index.py
模块中加入下面的视图函数代码
from sqlalchemy.sql.expression import func
@index_bp.route('/rand-post/')
def rands():
rand = Post.query.filter_by(status_id=1).order_by(func.random()).limit(20)
return render_template('frontend/index/rand-post.html', rands=rand)复制代码
打开bbs/templates/frontend/index
目录中新建rand-post.html
模板文件,模板文件中需要添加代码与上面两小节的代码类似,这里就不在累述了,如果读者不清楚可以去项目的github仓库克隆完整的代码查看。
3.首页侧边栏
在上面的小节中,处理完了首页主要内容中的帖子列表显示,到此侧边栏还没有对应的内容。关于侧边栏的设计,根据不同的功能来将每个功能块用一个card
来显示,在这一章节的教程中只完成用户个人资料card。
侧边栏slider是一个通用部分的内容,因此我们可以将其抽离出来,在需要使用的时候可以通过include
来导入,类似于我们的base.html
文件,打开bbs/templates
目录,新建slider.html
文件,代码清单如下
<div class="col-md-4 mt-1">
<div class="card border-secondary mb-2">
<div class="card-header"><i class="fa fa-user mr-2"></i>用户</div>
<div class="card-body">
{% if not current_user.is_authenticated %}
<div><small class="text-danger"><b>您尚未登录!</b></small>请先
<a href="/auth/login/"><span class="badge badge-info">登录</span></a> 或
<a href="/auth/register/"><span class="badge badge-success">注册</span></a>!
</div>
{% else %}
<div class="d-flex">
<img src="{{ current_user.avatar }}" class="rounded avatar-50">
<div>
<p class="mb-1 ml-1 text-success"><b>{{ current_user.nickname }}</b></p>
<p class="mb-1 ml-1 text-muted"><small>@{{ current_user.username }}</small></p>
</div>
<div class="d-flex flex-row-reverse">
<a href="/post/new/" class="btn btn-secondary h-75 ml-2"><i class="fa fa-plus"></i></a>
</div>
</div>
<hr class="bg-secondary">
<div class="d-flex p-1">
<div class="pr-2 dropdown">
<button class="btn btn-success dropdown-toggle" type="button" id="dropdownMenuButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
主题
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
{% for theme_name, display_name in config.BBS_THEMES.items() %}
<a class="dropdown-item"
href="{{ url_for('normal.change_theme', theme_name=theme_name, next=request.full_path) }}">
{{ theme_name }}</a>
{% endfor %}
</div>
</div>
<div class="dropdown">
<button class="btn btn-success dropdown-toggle" type="button" id="dropdownMenuButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
操作
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="{{url_for('profile.index', user_id=current_user.id)}}"><i class="fa fa-fw fa-user mr-2"></i>个人中心</a>
<a class="dropdown-item" href="{{url_for('user.index', user_id=current_user.id)}}"><i class="fa fa-fw fa-cog mr-2"></i>设置</a>
{% if current_user.role_id == 1 %}
<a class="dropdown-item" href="{{url_for('be_index.index')}}"><i class="fa fa-fw fa-magnet mr-2"></i>后台管理</a>
{% endif %}
<a class="dropdown-item" href="/auth/logout/"><i class="fa fa-fw fa-sign-out mr-2"></i>登出</a>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>复制代码
首先通过flask-login
的current_user
属性来判断用户是否已登录,如果没有登录则显示用户登录或者注册的视图。如果用户已经登录,则显示用户已经登录的视图。在已登录视图中,做了一个权限判断如果是普通用户则不显示后台管理的URL,如果是管理员则显示。在这个视图中用户可以做一些操作,如果切换页面主题、进入个人中心、设置个人资料、退出等等,这时候我们再访问首页,效果如下所示。
这里有很多后端endpoint还没有实现,如果读者出现报错,可以把href这个属性全部删除掉!
这一章节的内容就大致说完了,读者可以到项目github仓库下载完整的示例代码。目前代码的前端部分基本上已经完成了。