模板开发教程

掌握 Tera 模板引擎,从基础语法到高级定制,打造完全可控的子站前端表现。

前提条件

本教程假设你已了解 HTML、CSS 基础,并已有正在运行的 rsSites 子站。模板在子站后台的「模板管理」中编辑,内置 Monaco 编辑器提供语法高亮与实时预览。

Tera 模板基础

rsSites 使用 Tera 作为模板引擎 —— 一款受 Jinja2 / Django 启发的 Rust 原生模板语言。语法简洁,学习曲线平缓。

输出变量

使用双花括号输出变量值,自动进行 HTML 转义以防止 XSS:

<h1>{{ site.name }}</h1>
<p>{{ site.description }}</p>

安全输出

如果变量内容包含安全的 HTML(例如富文本正文),使用 safe 过滤器跳过转义:

<div class="article-body">{{ content.body | safe }}</div>

条件判断

{% if site.logo_url %}
  <img src="{{ site.logo_url }}" alt="{{ site.name }}">
{% else %}
  <span class="text-logo">{{ site.name }}</span>
{% endif %}

循环遍历

{% for item in content_list %}
  <article>
    <h2><a href="{{ item.url }}">{{ item.title }}</a></h2>
    <time>{{ item.publish_time | date(format="%Y-%m-%d") }}</time>
  </article>
{% endfor %}

常用过滤器

模板继承

Tera 支持类似 Django 的 extends + block 继承机制,实现 DRY 原则。先定义一个基础布局模板 base.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>{% block title %}{{ site.name }}{% endblock %}</title>
  <link rel="stylesheet" href="/assets/theme.css">
  {% block head_extra %}{% endblock %}
</head>
<body>
  {% include "partials/header.html" %}
  <main>{% block content %}{% endblock %}</main>
  {% include "partials/footer.html" %}
  {% block scripts %}{% endblock %}
</body>
</html>

子模板继承基础布局,只需定义需要覆盖的块:

{% extends "base.html" %}

{% block title %}{{ category.name }} - {{ site.name }}{% endblock %}

{% block content %}
  <section class="category-page">
    <h1>{{ category.name }}</h1>
    {% for item in content_list %}
      {% include "partials/card.html" %}
    {% endfor %}
  </section>
{% endblock %}

继承要点:extends 必须是模板第一行;block 可嵌套;父模板的 block 内容作为默认值,子模板会完全覆盖。

模板组合

除了继承,Tera 还提供 include 和 macro 两种组合手段,适合不同场景。

include —— 引入片段

适合引入纯展示型片段(页头、页脚、卡片等):

{% include "partials/header.html" %}
{% include "partials/card.html" %}

被 include 的模板可以访问父级上下文的所有变量,但也意味着耦合度较高。适合无参数的静态片段。

macro —— 带参数的宏

适合封装可复用的 UI 组件,并支持参数传入:

{% macro card(title, url, summary, image_url="") %}
  <div class="card">
    {% if image_url %}
      <img src="{{ image_url }}" alt="{{ title }}" class="card-img">
    {% endif %}
    <h3><a href="{{ url }}">{{ title }}</a></h3>
    <p>{{ summary | truncate(length=150) }}</p>
  </div>
{% endmacro %}

宏可以定义在独立文件中,然后在模板中导入使用:

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

{% for item in content_list %}
  {{ ui::card(title=item.title, url=item.url, summary=item.summary, image_url=item.cover) }}
{% endfor %}

渲染上下文

模板渲染时,引擎会注入一个结构化的上下文对象。理解上下文结构是高效开发模板的关键。

site —— 站点信息

nav —— 导航菜单

content_list / content —— 内容数据

category —— 栏目信息

分页信息

Slot 插槽系统

Slot 是 rsSites 独有的动态内容匹配机制。模板中声明 slot 名称,运行时按内容模型自动匹配并渲染相应的子模板。这让你用一套布局适配多种内容类型。

声明 Slot

在模板中预留 slot 占位:

<div class="page-body">
  {{ slot("hero") }}
  {{ slot("sidebar") }}
  {{ slot("main") }}
</div>

Slot 匹配规则

系统根据当前页面的内容模型,查找对应的 slot 模板文件。匹配优先级如下:

  1. 内容模型别名 + slot 名:article-hero.html、article-sidebar.html
  2. 栏目 slug + slot 名:news-hero.html、news-main.html
  3. 全局默认 slot:default-hero.html

如果均未匹配,该 slot 输出为空,不会报错。

典型用法

新闻类内容需要大图横幅,文档类需要目录侧栏。只需在模板目录中放置对应的 slot 文件,同一个页面布局自动按内容类型切换展示。

主题变量

rsSites 为每个子站提供一套可控范围的主题变量,模板中使用 site.theme 访问。管理员在后台通过色板/滑块配置,无需接触代码就能调整配色与尺寸。变量范围固定,保证模板的可预期性。

颜色变量

尺寸变量

模板中通过 CSS 变量或内联样式引用这些值:

<style>
:root {
  --primary: {{ site.theme.primary_color }};
  --text: {{ site.theme.text_color }};
  --radius: {{ site.theme.border_radius }}px;
}
.btn { background: var(--primary); border-radius: var(--radius); }
</style>

Monaco IDE 使用技巧

模板编辑器内嵌 Monaco Editor(VS Code 同款内核),提供专业的编码体验。

语法高亮

编辑器自动识别 Tera 语法,HTML、CSS、JavaScript 与 Tera 标签使用不同颜色区分,错误标签会以红色波浪线标记。

自动补全

输入 {{ site. 或 {% 时,编辑器会弹出上下文变量、过滤器、标签的候选列表。自定义内容模型的字段也会出现在补全提示中。

实时预览

编辑器右侧提供实时预览面板,渲染真实数据。预览数据来源:

预览渲染结果与正式环境完全一致,所见即所得。

快捷键

Git 同步工作流

模板支持 Git 版本管理,适合团队协作和专业开发流程。

工作流概览

  1. 本地开发 —— 克隆模板 Git 仓库到本地,使用你熟悉的编辑器编写模板
  2. 推送到草稿 —— git push 到远端 draft 分支,系统自动拉取并更新子站的草稿模板
  3. 预览验证 —— 在后台预览草稿模板效果,确认无误
  4. 发布上线 —— 在模板管理页面点击「发布」,草稿模板替换正式模板,访客即时看到新版本

Git 仓库结构

templates/
├── base.html              # 基础布局
├── index.html             # 首页
├── list.html              # 列表页
├── detail.html            # 详情页
├── partials/
│   ├── header.html
│   ├── footer.html
│   └── card.html
├── slots/
│   ├── article-hero.html
│   ├── default-hero.html
│   └── ...
├── macros/
│   └── ui.html
└── assets/
    ├── theme.css
    └── main.js

分支策略

回滚

每次发布都会在模板管理页面生成一个版本快照。如果新版出问题,可以在后台一键回滚到任意历史版本,不需要操作 Git。

最佳实践

响应式布局

兜底模板

性能建议

调试技巧

安全注意

下一步

掌握以上内容后,你可以:

核心机制解析

下面从代码实现角度,梳理模板系统背后几个关键机制的工作原理。

模板三态与在线编辑

模板不是「一个文件」,而是三态:草稿(编辑中,不影响线上)、正式(当前生效)、归档(历史版本,可恢复)。发布时系统把当前正式版归档、再切换草稿为正式,因此回滚就是切回某个归档版本。编辑在 Monaco 驱动的在线编辑器里完成,支持目录树浏览、读写、上传、重命名、移动、复制,以及把整个草稿区用 ZIP 整体替换——后者特别适合 SPA 打包产物的批量部署。

「分享预览链接」生成一个 6 位短码,带 1 / 7 / 30 天有效期,经 Redis 校验后无需登录即可查看草稿效果;平台模板库支持把模板推送到子站草稿区或从子站收编,是跨站复用模板的通道(当前协作通过文件复制完成)。

渲染与 Slot

前台渲染时,每个子站加载自己独立的 Tera 实例,模板通过 Slot 声明占位、运行时按「内容模型别名 → 栏目 slug → 全局默认」优先级匹配具体 slot 文件,使一套布局适配多种内容类型。系统会校验正式区是否补齐 home / search / list / read 等期望槽位,缺失则提示,保证上线前结构完整。渲染上下文统一携带 site / nav / content_list / content / category / pagination / theme 等变量,供模板直接消费。