域名 AXUM.RS 将于2025年10月到期。我们无意再对其进行续费,我们希望你能够接续这个域名,让更多 AXUM 开发者继续受益。
  • 方案1️⃣AXUM.RS 域名 = 3000
  • 方案2️⃣方案1️⃣ + 本站所有专题原始 Markdown 文档 = 5000
  • 方案3️⃣方案2️⃣ + 本站原始数据库 = 5500
如果你有意接续这份 AXUM 情怀,请与我们取得联系。
说明:
  1. 如果有人购买 AXUM.RS 域名(方案1️⃣),或者该域名到期,本站将启用新的免费域名继续提供服务。
  2. 如果有人购买了 AXUM.RS 域名,且同时购买了内容和/或数据库(方案2️⃣/方案3️⃣),本站将关闭。届时我们或许会以另一种方式与你再相遇。

模板

我们的博客分为“前台”和“后台”两部分。前台用于展示博客内容,后台用于管理博客。本章我们将编写前台和后台的基础模板以及对应的路由。

目录结构

前台模板位于 templates/frontend,后台模板位于templates/backend

我们的前台模板基于 Bootstrap的Blog 修改而来

我们的前台模板基于 Bootstrap的Blog 修改而来

布局

首先,我们对前台页面的布局来做一个全局总览。

注意,以下示意图链接到 github,如果你无法查看,请更换网络环境

前台页面布局

母模板

  • 对应【页面标题】的 {% block category_name %}分类名称{%endblock%}
  • 对应【页面内容】的{% block content%}页面内容{%endblock%}

虽然其它区域是固定的,但有些区域的内容还是需要从数据库中获取数据来填充。现阶段而言,我们只定义了【页面标题】对应的块,其它区域都保持硬编码的文字。

首页模板

有了母模板,我们就可以通过extends来衍生出业务需要的特定模板了,比如现阶段我们定义的首页的模板:

<!-- templates/frontend/index.html -->
{%extends "./base.html"%}
{%block category_name%}最新博文{%endblock%}

视图类

下面,我们为前台首页定义一下视图类:

// src/view/frontend/index.rs
use askama::Template;

#[derive(Template)]
#[template(path="frontend/index.html")]
pub struct Index {}

handler

有了视图类,就可以将模板进行渲染:

// src/handler/frontend/index.rs

pub async fn index()->Result<HtmlView> {
    let handler_name = "frontend/index/index";
    let tmpl = Index{};
    render(tmpl).map_err(log_error(handler_name))
}

在这个 handler 中,出现了几个新元素:

  • HtmlView:自定义的类型,用于返回HTML视图
  • render():用于通过视图类来渲染模板
  • log_error():使用日志记录AppError

它们的代码实现如下:

HtmlView

// src/handler/mod.rs

type HtmlView = axum::response::Html<String>;

render()

// src/handler/mod.rs

fn render<T>(tmpl: T) ->Result<HtmlView> where T:Template {
    let html = tmpl.render().map_err(AppError::from)?;
    Ok(Html(html))
}

泛型T必须满足askama::Template约束。由于我们定义视图类的时候使用了 #[derive(Template)],所以我们的视图类满足这一约束。

该函数用于渲染模板,模板文件的路径通过视图类的#[template(path="frontend/index.html")]指定。默认模板引擎会在项目根目录的templates目录下,查找path指定的模板文件。

log_error()

// src/handler/mod.rs

fn log_error(handler_name:&str) -> Box<dyn Fn(AppError)->AppError> {
     let handler_name = handler_name.to_string();
     Box::new(move |err| {
         tracing::error!("操作失败:{:?},  {}", err, handler_name);
         err
     })
 }

该函数用于记录产生的AppError日志。

路由

我们还需要定义路由。

// src/handler/frontend/mod.rs

pub fn router()->Router {
     Router::new().route("/", get(index::index))
}

后台

我们的后台模板基于 Bootstrap的Dashboard 修改而来

我们的后台模板基于 Bootstrap的Dashboard 修改而来

布局

我们来看一下后台页面的布局:

后台页面布局

  • 【页眉】显示应用名称以【退出登录】链接
  • 【侧边菜单】显示可用的菜单
  • 【页面标题】显示当前页面的标题
  • 【工具栏】显示当前页面可用的工具栏
  • 【提示信息】该区域和【页面内容】平行,示意图上没有画出。用于显示提示信息。
  • 【页面内容】显示当前页面的内容
  • 【分页】如果需要分页,则这里显示分页按钮

母模板

通过以上分析,我们需要定义以下块:

  • 【页面标题】:{% block title%}标题{%endblock%}
  • 【工具栏】:{% block toolbar %}{%endblock%}
  • 【提示信息】 {% block msg%} {%endblock%}
  • 【页面内容】{%block content%}内容{%endblock%}

当然,现阶段而言,我们并不需要定义它们,随着项目的推进,会对其进行补全。

首页模板

根据母模板,我们来定义后台首页模板:

<!-- templates/backend/index.html -->

 {% extends "./base.html" %}

视图类

// src/view/backend/index.rs

use askama::Template;

#[derive(Template)]
#[template(path="backend/index.html")]
pub struct Index {}

handler

// src/handler/backend/index.rs

pub async fn index()->Result<HtmlView>{
let handler_name="backend/index/index";
    let tmpl = Index{};
    render(tmpl).map_err(log_error(handler_name))
}

路由

// src/handler/backend/mod.rs

pub fn router() -> Router {
    Router::new().route("/", get(index))
}

启动axum服务

// src/main.rs

use axum::Router;
use axum_rs_blog::handler::{backend, frontend};

#[tokio::main]
async fn main() {
    if std::env::var_os("RUST_LOG").is_none() {
        std::env::set_var("RUST_LOG", "axum_rs_blog=debug");
    }
    tracing_subscriber::fmt::init();

    tracing::info!("服务已启动");

    let frentend_routers = frontend::router();
    let backend_routers = backend::router();
    let app = Router::new()
        .nest("/", frentend_routers)
        .nest("/admin", backend_routers);

    axum::Server::bind(&"127.0.0.1:9527".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

本章代码位于02/模板

要查看完整内容,请先登录