Skip to content

首页内容展示

头部内容渲染

修改后端登录代码

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>

首页内容切换

前端-分页切换

实现切换分类刷新逻辑

  1. 前端记录分页数据

    html
    <!-- filename: /static/js/main.js -->
    <script>
        let page = {{ paginate.page }};
        let limit = {{ paginate.per_page }};
        let count = {{ paginate.total }};
    </script>
  2. 前段分页展示内容

    javascript
    layui.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();
        }
      }
    },
  });
});