使用axum构建博客系统 - 网站首页

发布时间 2023-10-21 20:31:14作者: CrossPython

后台管理完成后,我们开始进入前台功能的开发。本章我们将完成博客首页的开发。

母模板

templates/frontend/base.html

是时候对前台母模板进行数据填充和块的定义了:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content="" />
    <title>AXUM.RS博客</title>
    <!-- Bootstrap core CSS -->
    <link
        href="https://getbootstrap.com/docs/5.1/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />

    <meta name="theme-color" content="#7952b3" />
    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }
    </style>

    <!-- Custom styles for this template -->
    <link
      href="https://fonts.googleapis.com/css?family=Playfair&#43;Display:700,900&amp;display=swap"
      rel="stylesheet"
    />
    <!-- Custom styles for this template -->
    <link href="https://getbootstrap.com/docs/5.1/examples/blog/blog.css" rel="stylesheet" />
  </head>
  <body>
    <div class="container">
      <header class="blog-header py-3">
        <div class="row flex-nowrap justify-content-between align-items-center">
          <div class="col-4 pt-1"></div>
          <div class="col-4 text-center">
            <a class="blog-header-logo text-dark" href="/">AXUM.RS博客</a>
          </div>
          <div class="col-4 d-flex justify-content-end align-items-center">
              <a class="btn btn-sm btn-outline-secondary" href="https://axum.rs/subject/blog" target="_blank">教程地址</a>
          </div>
        </div>
      </header>

      <div class="nav-scroller py-1 mb-2">
        <nav class="nav d-flex justify-content-between">
            {% for cat in cats %}
          <a class="p-2 link-secondary" href="/category/{{cat.id}}">{{cat.name}}</a>
          {%endfor%}
        </nav>
      </div>
    </div>

    <main class="container">
      <div class="row g-5">
        <div class="col-md-8">
          <h3 class="pb-4 mb-4 fst-italic border-bottom">{% block category_name %}分类名称{%endblock%}</h3>
          {% block content %}
          <article class="blog-post">
            <h2 class="blog-post-title">Sample blog post</h2>
            <p class="blog-post-meta">
              January 1, 2021 by <a href="#">Mark</a>
            </p>

          </article>
          {% endblock %}
          {% block paginate %}{%endblock%}
        </div>

        <div class="col-md-4">
          <div class="position-sticky" style="top: 2rem">
            <div class="p-4 mb-3 bg-light rounded">
              <h4 class="fst-italic">关于我们</h4>
              <p class="mb-0">AXUM中文网(axum.rs)为你提供了使用axum进行企业级Web开发中所需要的大部分知识。从基础知识到企业级项目的开发,都有完整的系列教程。更难得的是,除了文字教程,我们还录制了配套的视频教程,方便你以多种形式进行学习。</p>
            </div>

            <div class="p-4">
              <h4 class="fst-italic">存档</h4>
              <ol class="list-unstyled mb-0">
                  {% for arch in archives %}
                <li><a href="/archive/{{arch.dateline}}">{{arch.dateline}}</a></li>
                {%endfor%}
              </ol>
            </div>

            <div class="p-4">
              <h4 class="fst-italic">链接</h4>
              <ol class="list-unstyled">
                <li><a href="https://axum.rs" target="_blank">axum中文网</a></li>
              </ol>
            </div>
          </div>
        </div>
      </div>
    </main>

    <footer class="blog-footer">
      <p>
          本教程由<a href="https://axum.rs" target="_blank">axum.rs</a>提供,模板基于<a href="https://getbootstrap.com/docs/5.1/examples/blog/" target="_blank">Bootstrap Blog</a>修改。
      </p>
    </footer>
  </body>
</html>

其中:

<nav class="nav d-flex justify-content-between">
  {% for cat in cats %}
  <a class="p-2 link-secondary" href="/category/{{cat.id}}">{{cat.name}}</a>
  {%endfor%}
</nav>

用于将分类列表填充为头部导航,而

<ol class="list-unstyled mb-0">
  {% for arch in archives %}
  <li><a href="/archive/{{arch.dateline}}">{{arch.dateline}}</a></li>
  {%endfor%}
</ol>

用于填充按月份为单位的存档。

其它块的定义之前章节已做过说明,此处略过。

首页模板

templates/frontend/index.html

{%extends "./base.html"%}
{%block category_name%}最新博文{%endblock%}
{% block content %}
{% for item in list.data %}
<article class="blog-post">
    <h2 class="blog-post-title">
        <a href="/topic/{{item.id}}">{{item.title}}</a>
    </h2>
    <p class="blog-post-meta">
        <a href="https://axum.rs" target="_blank">AXUM.RS</a> 发表于 {{item.dateline()}} [<a href="/category/{{item.category_id}}">{{item.category_name}}</a>]
    </p>

    {{ item.summary }}
</article>
{%endfor%}
{% endblock %}
{% block paginate %}
{%include "../pagination.html"%}
{%endblock%}

其中 {% for item in list.data %}...{%endfor%}用于填充文章列表。这个list是一个 Paginate分页对象。

视图类

// src/view/frontend/index.rs
#[derive(Template)]
#[template(path="frontend/index.html")]
pub struct Index {
    pub list: Paginate<Vec<TopicList>>,
    pub page : u32,
    pub cats: Vec<Category>,
    pub archives: Vec<TopicArchive>,
}

除了listpage是本身模板需要的数据之外,其它的都是母模板(base.html)所需要的

handler

// src/handler/frontend/index.rs
pub async fn index(
    Extension(state):Extension<Arc<AppState>>,
    Query(args):Query<Args>
)->Result<HtmlView> {
    let page = args.page();
    let handler_name = "frontend/index/index";
    let client = get_client(&state).await.map_err(log_error(handler_name))?;
    let list = topic::list(&client, page).await.map_err(log_error(handler_name))?;
    let cats = category::list(&client).await.map_err(log_error(handler_name))?;
    let archives = topic::archive_list(&client).await.map_err(log_error(handler_name))?;
    let tmpl = Index{
        list,
        page,
        cats,
        archives,
    };
    render(tmpl).map_err(log_error(handler_name))
}
  • Args:前台页面所需要的参数,请参见下文的“Args”部分。
  • topic::archive_list():根据已发表的文章获取按月存档的日期列表。详情请见下文的“数据库操作”部分。

数据库操作

// src/db/topic.rs
pub async fn archive_list(client: &Client) -> Result<Vec<TopicArchive>> {
    let sql = "SELECT
       to_char(DATE_TRUNC('month',dateline), 'YYYY年MM月')
         AS  dateline
FROM topics
GROUP BY to_char(DATE_TRUNC('month',dateline), 'YYYY年MM月')";
    super::query(client, sql, &[]).await
}
  • archive_list():调用Postgresql相关函数,根据已发表的文章获取按月存档的日期列表。单条记录的格式如2022年03月

Args

// src/handler/frontend/mod.rs

#[derive(Deserialize)]
pub struct Args {
    pub page : Option<u32>,
}
impl Args {
    pub fn page(&self) -> u32 {
        self.page.unwrap_or(0)
    }
}
  • page:可选的分页页码。