后台管理完成后,我们开始进入前台功能的开发。本章我们将完成博客首页的开发。
母模板
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+Display:700,900&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>,
}
除了list
和page
是本身模板需要的数据之外,其它的都是母模板(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
:可选的分页页码。