模板渲染 #

Rocket支持多种模板引擎,其中Tera是最常用的选择。本节将介绍如何使用Tera模板引擎渲染动态HTML页面。

配置模板引擎 #

添加依赖 #

toml
[dependencies]
rocket = { version = "0.5", features = ["json"] }
rocket_dyn_templates = { version = "0.1", features = ["tera"] }

启用模板 #

rust
#[macro_use] extern crate rocket;

use rocket_dyn_templates::Template;

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(Template::fairing())
        .mount("/", routes![index])
}

模板目录结构 #

text
my_app/
├── Cargo.toml
├── templates/
│   ├── base.html
│   ├── index.html
│   ├── user/
│   │   ├── list.html
│   │   └── detail.html
│   └── partials/
│       ├── header.html
│       └── footer.html
└── src/
    └── main.rs

基本模板渲染 #

渲染模板 #

rust
use rocket_dyn_templates::Template;

#[get("/")]
fn index() -> Template {
    Template::render("index", &Context::default())
}

传递数据 #

rust
use rocket_dyn_templates::Template;
use rocket::serde::Serialize;

#[derive(Serialize)]
struct User {
    name: String,
    email: String,
}

#[get("/user")]
fn user_page() -> Template {
    let user = User {
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    };
    Template::render("user/detail", &user)
}

Tera模板语法 #

变量输出 #

html
<h1>Hello, {{ name }}!</h1>
<p>Email: {{ user.email }}</p>

条件语句 #

html
{% if user.is_admin %}
    <span class="badge">Admin</span>
{% elif user.is_moderator %}
    <span class="badge">Moderator</span>
{% else %}
    <span class="badge">User</span>
{% endif %}

循环语句 #

html
<ul>
{% for user in users %}
    <li>
        {{ loop.index }}. {{ user.name }}
        {% if loop.first %}(First){% endif %}
        {% if loop.last %}(Last){% endif %}
    </li>
{% endfor %}
</ul>

循环变量 #

变量 说明
loop.index 当前索引(从1开始)
loop.index0 当前索引(从0开始)
loop.first 是否为第一个元素
loop.last 是否为最后一个元素
loop.length 列表长度

模板继承 #

基础模板 (base.html) #

html
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Default Title{% endblock %}</title>
    <link rel="stylesheet" href="/static/css/style.css">
    {% block head %}{% endblock %}
</head>
<body>
    <header>
        {% include "partials/header.html" %}
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        {% include "partials/footer.html" %}
    </footer>
    
    <script src="/static/js/main.js"></script>
    {% block scripts %}{% endblock %}
</body>
</html>

继承模板 (index.html) #

html
{% extends "base.html" %}

{% block title %}Home Page{% endblock %}

{% block content %}
<h1>Welcome to My Site</h1>
<p>This is the home page.</p>
{% endblock %}

多级继承 #

html
{% extends "layouts/dashboard.html" %}

{% block sidebar %}
<nav>
    <ul>
        <li><a href="/dashboard">Dashboard</a></li>
        <li><a href="/settings">Settings</a></li>
    </ul>
</nav>
{% endblock %}

{% block content %}
<h2>Dashboard Overview</h2>
{% endblock %}

包含模板 #

部分模板 (partials/header.html) #

html
<nav class="navbar">
    <a href="/" class="logo">MyApp</a>
    <ul class="nav-links">
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
    </ul>
    {% if user %}
        <span>Hello, {{ user.name }}</span>
        <a href="/logout">Logout</a>
    {% else %}
        <a href="/login">Login</a>
    {% endif %}
</nav>

使用include #

html
<body>
    {% include "partials/header.html" %}
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    {% include "partials/footer.html" %}
</body>

传递变量到include #

html
{% include "partials/card.html" with title="My Card" content="Card content" %}

过滤器 #

内置过滤器 #

html
<p>Upper: {{ name | upper }}</p>
<p>Lower: {{ name | lower }}</p>
<p>Capitalized: {{ name | capitalize }}</p>
<p>Length: {{ items | length }}</p>
<p>Default: {{ missing | default(value="N/A") }}</p>
<p>Join: {{ tags | join(sep=", ") }}</p>
<p>First: {{ items | first }}</p>
<p>Last: {{ items | last }}</p>
<p>Reverse: {{ items | reverse }}</p>
<p>Sort: {{ items | sort }}</p>
<p>Date: {{ created_at | date(format="%Y-%m-%d") }}</p>
<p>Truncate: {{ content | truncate(length=100) }}</p>
<p>Safe: {{ html_content | safe }}</p>

自定义过滤器 #

rust
use rocket_dyn_templates::Template;
use tera::{Value, Error};

fn pluralize(value: &Value, args: &HashMap<String, Value>) -> Result<Value, Error> {
    let count = value.as_u64().unwrap_or(0);
    let singular = args.get("singular").unwrap().as_str().unwrap();
    let plural = args.get("plural").unwrap().as_str().unwrap();
    
    let word = if count == 1 { singular } else { plural };
    Ok(Value::String(format!("{} {}", count, word)))
}

#[launch]
fn rocket() -> _ {
    let mut tera = Tera::new("templates/**/*").unwrap();
    tera.register_filter("pluralize", pluralize);
    
    rocket::build()
        .attach(Template::custom(|engines| {
            engines.tera.register_filter("pluralize", pluralize);
        }))
        .mount("/", routes![index])
}

宏定义 #

定义宏 #

html
{% macro input(name, label, type="text", value="") %}
<div class="form-group">
    <label for="{{ name }}">{{ label }}</label>
    <input type="{{ type }}" name="{{ name }}" id="{{ name }}" value="{{ value }}">
</div>
{% endmacro %}

{% macro card(title, content) %}
<div class="card">
    <div class="card-header">{{ title }}</div>
    <div class="card-body">{{ content }}</div>
</div>
{% endmacro %}

使用宏 #

html
{% import "macros/form.html" as form %}
{% import "macros/ui.html" as ui %}

<form>
    {{ form::input(name="username", label="Username") }}
    {{ form::input(name="email", label="Email", type="email") }}
    {{ form::input(name="password", label="Password", type="password") }}
</form>

{{ ui::card(title="Welcome", content="Hello, World!") }}

完整示例 #

rust
#[macro_use] extern crate rocket;

use rocket_dyn_templates::Template;
use rocket::serde::Serialize;
use rocket::fs::FileServer;

#[derive(Serialize)]
struct IndexContext {
    title: String,
    users: Vec<User>,
}

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    email: String,
    is_admin: bool,
}

#[get("/")]
fn index() -> Template {
    let context = IndexContext {
        title: "User List".to_string(),
        users: vec![
            User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string(), is_admin: true },
            User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string(), is_admin: false },
            User { id: 3, name: "Charlie".to_string(), email: "charlie@example.com".to_string(), is_admin: false },
        ],
    };
    Template::render("index", &context)
}

#[get("/user/<id>")]
fn user_detail(id: u32) -> Template {
    let context = User {
        id,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
        is_admin: true,
    };
    Template::render("user/detail", &context)
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(Template::fairing())
        .mount("/", routes![index, user_detail])
        .mount("/static", FileServer::from("static/"))
}

模板文件 #

templates/base.html:

html
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}MyApp{% endblock %}</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <header>
        <nav>
            <a href="/">Home</a>
            <a href="/users">Users</a>
        </nav>
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        <p>&copy; 2024 MyApp</p>
    </footer>
</body>
</html>

templates/index.html:

html
{% extends "base.html" %}

{% block title %}{{ title }} - MyApp{% endblock %}

{% block content %}
<h1>{{ title }}</h1>

<table>
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Role</th>
        </tr>
    </thead>
    <tbody>
        {% for user in users %}
        <tr>
            <td>{{ user.id }}</td>
            <td><a href="/user/{{ user.id }}">{{ user.name }}</a></td>
            <td>{{ user.email }}</td>
            <td>
                {% if user.is_admin %}
                    <span class="badge admin">Admin</span>
                {% else %}
                    <span class="badge user">User</span>
                {% endif %}
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>

<p>Total users: {{ users | length }}</p>
{% endblock %}

下一步 #

掌握了模板渲染后,让我们继续学习 静态文件,了解如何提供静态资源服务。

最后更新:2026-03-28