模板渲染 #
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>© 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