首页内容展示
头部内容渲染
修改后端登录代码
python
from flask_jwt_extended import current_user
@index_bp.route("/")
@jwt_required(optional=True)
def index():
return render_template("forum/index.html", current_user=current_user)
右上角个人信息渲染
html
<div class="header">
<div class="header-demo">
<!-- 省略部分 HTML -->
{% if not current_user.id %}
<!--登录注册栏-->
<div class="login">
<a href="javascript:;" class="login_btn">登录</a> /
<a href="javascript:;" class="register_btn">注册</a>
</div>
{% else %}
<!-- 用户登录后显示下面 -->
<div class="login">
<img src="/static/images/person01.png" class="login-pic" alt="">
<a href="#">{{ current_user.nickname }}</a>
<a href="/logout">退出</a>
</div>
{% endif %}
</div>
</div>
添加后端退出视图
python
@index_bp.get('/logout')
def logout_view():
response = make_response(redirect('/'))
unset_access_cookies(response)
return response
文章列表渲染
- 文章列表数据只是当前页面的一部分
- 点击分类时需要去获取当前分类下的文章数据
- 并在展示的时候需要更新文章列表界面,不需要整体页面刷新
- 所以文章数据也使用 ajax 的方式去请求后台接口进行获取
SSR 渲染-后端视图
后端获取并渲染数据
python
from flask import Blueprint, render_template, current_app, request
from models import CategoryModel, ArticleModel
from com import constants
index_bp = Blueprint('index', __name__)
@index_bp.route('/')
def index():
"""首页 新闻分类数据渲染"""
# 1. 获取所有分类数据
category_list = CategoryModel.query.all()
# 2. 点击排行榜的新闻数据
click_article_list = ArticleModel.query.order_by(
ArticleModel.clicks.desc()).limit(
constants.CLICK_RANK_MAX_NEWS).all()
# 3. 渲染文章数据(需要分页)
# 3.1 获取分页参数
cid = request.args.get('cid', type=int, default=1) # 类别 id 1-7
page = request.args.get('page', type=int, default=1)
limit = request.args.get('limit', type=int, default=10)
# 3.2 构建查询条件
filters = []
if cid == 1:
pass
else:
filters.append(ArticleModel.category_id == cid)
# 3.3 分页查询数据
paginate = ArticleModel.query.order_by(
ArticleModel.create_time.desc()).filter(*filters).paginate(page, limit, False)
return render_template('index.html',
category_list=category_list,
click_article_list=click_article_list,
paginate=paginate)
python
@index_bp.route("/")
@jwt_required(optional=True)
def index():
"""首页 新闻分类数据渲染"""
# 1. 获取文章数据
cid = request.args.get('cid', type=int, default=1) # 类别 id 1-7
page = request.args.get('page', type=int, default=1)
limit = request.args.get('limit', type=int, default=10)
q = db.select(ArticleORM)
q = q.order_by(ArticleORM.create_at.desc())
paginate = db.paginate(q, page=page, per_page=limit)
return render_template(
"forum/index.html",
current_user=current_user,
paginate=paginate
)
SSR 渲染 - jinja2
html
<!--文章列表-->
<div class="articles">
{% for article in paginate.items %}
<div class="article">
<a href="/article/{{ article.id }}" class="article_image">
<img src="{{ article.index_image_url }}" alt="">
</a>
<div class="article_content">
<a href="/article/{{ article.id }}" class="article_title">{{ article.title }}</a>
<a href="/article/{{ article.id }}" class="article_detail">{{ article.digest }}</a>
<div class="author_info">
<div class="author">
<img src="{{ article.user.avatar or '/static/images/person.png' }}" alt="author">
<a href="/user/{{ article.user.id }}">{{ article.user.nickname }}</a>
</div>
<div class="time">{{ article.create_at }}</div>
</div>
</div>
</div>
{% endfor %}
<div id="article-page" style="text-align: center"></div>
</div>
首页内容切换
前端-分页切换
实现切换分类刷新逻辑
前端记录分页数据
html<!-- filename: /static/js/main.js --> <script> let page = {{ paginate.page }}; let limit = {{ paginate.per_page }}; let count = {{ paginate.total }}; </script>
前段分页展示内容
javascriptlayui.use(function () { let laypage = layui.laypage; laypage.render({ elem: "article-page", curr: page, count: count, first: "首页", last: "尾页", limit: limit, prev: "<em>←</em>", next: "<em>→</em>", jump: function (obj, first) { if (!first) { if (obj.curr !== page) { /*模板渲染*/ console.log(obj); let params = new URLSearchParams(location.search); params.set("page", obj.curr); location.search = params.toString(); } } }, }); });
前端-首页分类切换
在 main.js
文件中
javascript
// filename: /static/js/main.js
let params = new URLSearchParams(location.search);
// 首页分类切换
$(".menu li").click(function () {
let click_cid = $(this).attr("data-cid");
$(".menu li").each(function () {
$(this).removeClass("active");
});
// 设置当前标签别选中并且高亮
$(this).addClass("active");
if (params.get("cid") !== click_cid) {
// 记录当前分类id
params.set("cid", click_cid);
// 重置分页参数
params.set("page", "1");
location.search = params.toString();
}
});
// 高亮当前选择的内容 SSR 渲染之后的内容
if (params.get("cid")) {
$(".menu li").removeClass("active");
$(`li[data-cid="${params.get("cid")}"]`).addClass("active");
}
后端-处理分页查询
- 根据前端传递的分页、分类查询数据
- 在查询的时候,如果用户分类id 传 0,则不添加分类查询条件
python
@index_bp.route("/")
@jwt_required(optional=True)
def index():
"""首页 新闻分类数据渲染"""
# 1. 获取文章数据
cid = request.args.get('cid', type=int, default=0) # 类别 id 1-5
page = request.args.get('page', type=int, default=1)
limit = request.args.get('limit', type=int, default=10)
q = db.select(ArticleORM)
if cid != 0:
q = q.where(ArticleORM.category_id == cid)
q = q.order_by(ArticleORM.create_at.desc())
paginate = db.paginate(q, page=page, per_page=limit, error_out=False)
return render_template(
"forum/index.html",
current_user=current_user,
paginate=paginate
)
拓展-CSR 渲染内容
在前面的操作中,所有的数据查询都是采用服务器渲染的方式(SSR),这种方式有利于搜索做搜索引擎优化以及首屏快速展现内容,但是对服务器的资源消耗也是相对较多。
如果想要进行优化,可以采用服务器渲染(SSR) + 客户端渲染(CSR) 的方式进行混合渲染。将一部分不是很重要的内容通过接口返回数据到前端进行渲染。
后端-文章接口部分
- 在
application/views/index.py
中定义视图函数
python
# filename: forum/apis/index.py
@index_api.route('/article_list')
def article_list():
# 1. 获取文章数据
cid = request.args.get('cid', type=int, default=0) # 类别 id 1-5
page = request.args.get('page', type=int, default=1)
limit = request.args.get('limit', type=int, default=10)
q = db.select(ArticleORM)
if cid != 0:
q = q.where(ArticleORM.category_id == cid)
q = q.order_by(ArticleORM.create_at.desc())
# 分页查询数据
paginate = db.paginate(q, page=page, per_page=limit, error_out=False)
return {
'code': 0,
'msg': '请求数据成功',
'results': [
{
'id': article.id,
'index_image_url': article.index_image_url,
'title': article.title,
'digest': article.digest,
'source': article.source,
'create_at': article.create_at,
'user_id': '1', # 有些文章没有作者
} for article in paginate.items # 获取查询出来的数据
],
'total': paginate.total, # 获取到总数
'page': paginate.page, # 当前是第几页
}
CSR-前端部分
javascript
layui.use(function () {
let $ = layui.$;
let laypage = layui.laypage;
let params = new URLSearchParams(location.search);
let current_cid = params.get("cid") || 0; // 当前分类 id
let current_page = params.get("page") || 1; // 当前页
let current_count = params.get("count") || 100; // 当前页
// 高亮当前选择的内容 SSR 渲染之后的内容
$(".menu li").removeClass("active");
$(`li[data-cid="${current_cid}"]`).addClass("active");
// 首页分类切换
$(".menu li").click(function () {
let click_cid = $(this).attr("data-cid");
$(".menu li").removeClass("active");
// 设置当前标签别选中并且高亮
$(this).addClass("active");
if (click_cid !== current_cid) {
// 点击的分类
current_cid = click_cid;
// 设置 url 查询参数
params.set("page", "1");
params.set("cid", current_cid);
// // SSR 渲染 前端点击跳转
// location.search = params.toString()
// CSR 客户端渲染
csr_update_article();
}
});
// 请求并加载新的数据
function csr_update_article() {
// TODO 更新文章数据
let data = {
cid: params.get("cid"),
page: params.get("page"),
};
$.get("/static/api/article.json", data, function (data) {
console.log(data);
if (data.status === "ok") {
total_page = data.total;
$(".article").remove();
for (let i = 0; i < data.data.length; i++) {
let article = data.data[i];
let content = `
<div class="article">
<a href="article.html" class="article_image">
<img src="${article.img}" alt="">
</a>
<div class="article_content">
<a href="article.html" class="article_title">${article.title}</a>
<a href="article.html" class="article_detail"> ${article.detail}</a>
<div class="author_info">
<div class="author">
<img src="${article.author_url}" alt="author">
<a href="/templates/user.html">${article.author_name}</a>
</div>
<div class="time">${article.time}</div>
</div>
</div>
</div>
`;
$(".articles").prepend(content);
}
}
});
}
// 自定义首页、尾页、上一页、下一页文本
// 分页展示数据
laypage.render({
elem: "article-pagination",
count: current_count,
curr: current_page,
first: "首页",
last: "尾页",
limit: 10,
prev: "<em>←</em>",
next: "<em>→</em>",
jump: function (obj, first) {
if (!first) {
if (obj.curr !== params.get("page")) {
// SSR 渲染
params.set("page", obj.curr);
// location.search = params.toString();
// CSR 动态请求
csr_update_article();
}
}
},
});
});