模板开发教程
掌握 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 %}
常用过滤器
| date(format="%Y-%m-%d")—— 格式化日期时间| truncate(length=120)—— 截断字符串,超出加省略号| lower/| upper—— 大小写转换| safe—— 标记为安全 HTML,不转义| default(value="默认值")—— 变量为空时使用默认值| json_encode—— 将对象序列化为 JSON 字符串
模板继承
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 —— 站点信息
site.name—— 站点名称site.alias—— 站点别名(URL 路径片段)site.description—— 站点简介site.logo_url—— Logo 图片地址site.favicon_url—— Favicon 地址site.copyright—— 版权信息site.theme—— 当前主题变量集合(见下文)
nav —— 导航菜单
nav.primary—— 主导航菜单项数组,每项含label、url、active、childrennav.footer—— 底部导航,结构同上
content_list / content —— 内容数据
content_list—— 列表页:当前栏目的内容项数组content—— 详情页:单条内容完整数据- 每条内容包含:
title、body、summary、cover_url、publish_time、author、tags、以及对应内容模型的全部自定义字段
category —— 栏目信息
category.name—— 栏目名称category.slug—— 栏目 URL 片段category.description—— 栏目描述category.children—— 子栏目列表
分页信息
pagination.current—— 当前页码pagination.total—— 总页数pagination.has_prev/pagination.has_next—— 是否有上一页/下一页pagination.prev_url/pagination.next_url—— 翻页链接
Slot 插槽系统
Slot 是 rsSites 独有的动态内容匹配机制。模板中声明 slot 名称,运行时按内容模型自动匹配并渲染相应的子模板。这让你用一套布局适配多种内容类型。
声明 Slot
在模板中预留 slot 占位:
<div class="page-body">
{{ slot("hero") }}
{{ slot("sidebar") }}
{{ slot("main") }}
</div>
Slot 匹配规则
系统根据当前页面的内容模型,查找对应的 slot 模板文件。匹配优先级如下:
- 内容模型别名 + slot 名:
article-hero.html、article-sidebar.html - 栏目 slug + slot 名:
news-hero.html、news-main.html - 全局默认 slot:
default-hero.html
如果均未匹配,该 slot 输出为空,不会报错。
典型用法
新闻类内容需要大图横幅,文档类需要目录侧栏。只需在模板目录中放置对应的 slot 文件,同一个页面布局自动按内容类型切换展示。
主题变量
rsSites 为每个子站提供一套可控范围的主题变量,模板中使用 site.theme 访问。管理员在后台通过色板/滑块配置,无需接触代码就能调整配色与尺寸。变量范围固定,保证模板的可预期性。
颜色变量
site.theme.primary_color—— 主色调(如按钮、链接、强调色)site.theme.secondary_color—— 辅色调site.theme.bg_color—— 页面背景色site.theme.text_color—— 正文颜色site.theme.heading_color—— 标题颜色site.theme.link_color—— 链接颜色site.theme.border_color—— 边框颜色
尺寸变量
site.theme.base_font_size—— 基准字号(13–20px),影响全局rem基准site.theme.content_width—— 内容区最大宽度(800–1400px)site.theme.border_radius—— 全局圆角(0–24px)site.theme.card_gap—— 卡片间距(8–48px)
模板中通过 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. 或 {% 时,编辑器会弹出上下文变量、过滤器、标签的候选列表。自定义内容模型的字段也会出现在补全提示中。
实时预览
编辑器右侧提供实时预览面板,渲染真实数据。预览数据来源:
- 默认使用当前栏目的最新 3 条内容作为示例数据
- 可通过预览面板顶栏切换不同栏目或内容模型查看效果
- 支持响应式尺寸切换(桌面 / 平板 / 手机)
预览渲染结果与正式环境完全一致,所见即所得。
快捷键
Ctrl+S—— 保存为草稿Ctrl+Shift+P—— 命令面板(查找替换、格式化等)Ctrl+/—— 注释/取消注释
Git 同步工作流
模板支持 Git 版本管理,适合团队协作和专业开发流程。
工作流概览
- 本地开发 —— 克隆模板 Git 仓库到本地,使用你熟悉的编辑器编写模板
- 推送到草稿 ——
git push到远端draft分支,系统自动拉取并更新子站的草稿模板 - 预览验证 —— 在后台预览草稿模板效果,确认无误
- 发布上线 —— 在模板管理页面点击「发布」,草稿模板替换正式模板,访客即时看到新版本
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
分支策略
draft分支 —— 草稿模板,推送后自动同步到后台预览main分支 —— 受保护分支,仅管理员发布时由系统自动合并- 建议日常开发在 feature 分支上进行,完成后合并到
draft
回滚
每次发布都会在模板管理页面生成一个版本快照。如果新版出问题,可以在后台一键回滚到任意历史版本,不需要操作 Git。
最佳实践
响应式布局
- 使用 CSS Grid / Flexbox 构建弹性布局,避免固定宽度
- 利用
site.theme.content_width控制内容区最大宽度 - 移动端优先:从小屏开始编写样式,用
@media (min-width: ...)逐步增强 - 图片使用
max-width: 100%防止溢出,配合srcset做响应式图片
兜底模板
- 始终提供
default-hero.html等全局默认 slot,确保任何内容模型都有可用 slot - 为
list.html和detail.html编写通用版本,避免每种内容类型都需要单独模板 - 使用
| default(value="...")过滤器为所有变量提供兜底值
性能建议
- 避免在循环内执行复杂过滤器或多次
include,Tera 会在每次迭代中重新解析 - 将不变的片段提取到循环外部
- 静态资源(CSS/JS/图片)放在模板仓库的
assets/目录,通过 Nginx 直接 serve - 合理使用分页,避免单页加载过多内容
调试技巧
{{ __tera_context }}—— 输出当前模板可用的全部上下文变量(调试用,调试完记得删除)- Monaco 编辑器右下角显示渲染耗时(毫秒级),如果超过 100ms 建议检查模板复杂度
- 模板语法错误会在编辑器左侧显示红色标记,鼠标悬停查看详细错误信息
- 利用预览面板的内容模型切换功能,逐一检查不同内容类型下的模板表现
安全注意
- 始终对用户生成内容使用
safe过滤器前确保来源可信 - 避免在模板中硬编码敏感信息(API key、密码等),使用环境变量或站点配置
- 模板中引入的外部脚本需经过管理员审核
下一步
掌握以上内容后,你可以:
- 阅读子站管理教程,了解如何将模板绑定到具体栏目
- 参考系统内置的默认模板源码(后台「模板管理」→「系统模板」),学习更多实战模式
- 探索 Tera 官方文档,了解自定义过滤器和高级功能
核心机制解析
下面从代码实现角度,梳理模板系统背后几个关键机制的工作原理。
模板三态与在线编辑
模板不是「一个文件」,而是三态:草稿(编辑中,不影响线上)、正式(当前生效)、归档(历史版本,可恢复)。发布时系统把当前正式版归档、再切换草稿为正式,因此回滚就是切回某个归档版本。编辑在 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 等变量,供模板直接消费。