ThinkTemplate SSR 数据渲染指南
求元工坊CMS 基于 ThinkPHP6 + ThinkTemplate 模板引擎,采用服务端渲染(SSR)模式。本文档面向前端模板开发者,介绍在模板中如何调用和渲染数据。
一、全局变量标签
以下变量在 GlobalView 中间件中自动注入到所有前台模板,所有页面均可直接调用:
| 变量 | 含义 | 示例 |
|---|---|---|
{$web.site_name} | 站点名称 | <span class="cn">{$web.site_name}</span> |
{$web.site_description} | 站点简介 | {$web.site_description} |
{$web.contact_email} | 联系邮箱 | {$web.contact_email} |
{$web.contact_phone} | 联系电话 | {$web.contact_phone} |
{$web.site_copyright} | 版权信息 | {$web.site_copyright} |
{$web.site_beian} | 备案号 | {$web.site_beian} |
{$siteWww} | 站点根地址 | <link href="{$siteWww}/static/home/css/cms.css"> |
{$navList} | 栏目导航树 | {volist name="navList" id="nav"} |
二、循环标签 volist
volist 是 ThinkTemplate 的核心循环标签,用于遍历控制器传递的数组数据。
2.1 基本语法
{volist name="list" id="item" key="k"}
<li>
<a href="{$siteWww}/article/{$item.id}.html">{$item.title}</a>
<span>{$item.created_at_fmt}</span>
</li>
{/volist}
属性说明:
name="list"— 控制器传递的变量名(必须)id="item"— 循环体中单条数据的别名(必须)key="k"— 自增序号(从1开始,可选)offset="0"— 从第几条开始(可选)length="10"— 最多输出几条(可选)
2.2 文档列表调用示例
<div class="doc-list">
{volist name="list" id="item"}
<div class="doc-item">
<h3><a href="{$siteWww}/article/{$item.id}.html">{$item.title}</a></h3>
<p>{$item.summary}</p>
<div class="meta">
<span>{$item.author}</span>
<span>{$item.views} 次阅读</span>
<span>{$item.publishing_time}</span>
</div>
</div>
{/volist}
</div>
注解:遍历文档列表,输出标题、摘要、作者、浏览量和发布时间。
2.3 客户案例列表调用示例
<div class="case-grid">
{volist name="list" id="item"}
<div class="case-card">
<img src="{$item.cover}" alt="{$item.title}">
<h4>{$item.title}</h4>
<p>{$item.image_count} 张图</p>
</div>
{/volist}
</div>
注解:遍历图库/案例列表,输出封面图、标题和图片数量。
三、条件判断 if
使用 if 标签对数据进行条件渲染。
3.1 基本语法
{if condition="!empty($item['cover'])"}
<img src="{$item.cover}" alt="{$item.title}">
{else /}
<div class="no-img">暂无封面</div>
{/if}
3.2 多条件判断
{if condition="$item.status == 1"}
<span class="status-active">已发布</span>
{elseif condition="$item.status == 0" /}
<span class="status-inactive">草稿</span>
{else /}
<span>未知</span>
{/if}
3.3 空态处理
{if condition="!empty($list)"}
{volist name="list" id="item"}
... 渲染列表 ...
{/volist}
{else /}
<div class="empty">暂无数据</div>
{/if}
四、分页渲染
分页数据由控制器传入,在模板中使用分页变量输出链接。
4.1 分页调用示例
{if condition="$pagination['lastPage'] > 1"}
<div class="pagination">
{if condition="$pagination['currentPage'] > 1"}
<a href="{$siteWww}/case?page={$pagination['currentPage'] - 1}">上一页</a>
{/if}
<span>第 {$pagination.currentPage} / {$pagination.lastPage} 页</span>
{if condition="$pagination['currentPage'] < $pagination['lastPage']"}
<a href="{$siteWww}/case?page={$pagination['currentPage'] + 1}">下一页</a>
{/if}
</div>
{/if}
注解:仅在有多页时显示分页控件,支持上一页/下一页。
五、常用助手函数
| 函数 | 功能 | 示例 |
|---|---|---|
get_site_name() | 获取站点名称 | {$seo_title} ?: get_site_name() |
get_site_config() | 获取全部站点配置 | get_site_config()['seo_title'] |
site_url($path) | 将相对路径转为绝对URL | site_url('/upload/1.jpg') |
site_abs_content($html) | 将内容中的相对地址转为绝对地址 | site_abs_content($item.content) |
htmlspecialchars() | HTML实体编码输出 | {$item.title|htmlspecialchars} |
date('Y-m-d', $ts) | 时间戳格式化 | date('Y-m-d', $item.created_at) |
六、TDK 渲染
每个页面通过控制器传入 $seo_title、$seo_keywords、$seo_description,在模板 head 中输出:
<title>{$seo_title}</title>
<meta name="keywords" content="{$seo_keywords}">
<meta name="description" content="{$seo_description}">
注解:全站TDK统一由后台「网站设置」驱动,详情页可叠加内容自身SEO。
七、公共模板包含
ThinkTemplate 支持模板继承和包含,减少重复代码:
<!-- 包含公共头部 -->
{include file="common/header" /}
<!-- 包含公共底部 -->
{include file="common/footer" /}
<!-- 包含 Banner 区域 -->
{include file="common/bannet" /}
八、完整示例:单页控制器 + 模板
8.1 控制器(Sample.php)
<?php
namespace app\home\controller;
use app\BaseController;
use app\common\model\Gallery as GalleryModel;
use app\common\service\home\CateSeoService;
class Sample extends BaseController
{
public function index()
{
$page = (int)input('page', 1);
$list = GalleryModel::where('status', 1)
->order('sort', 'asc')
->order('id', 'desc')
->paginate(['list_rows' => 12, 'page' => $page]);
$items = $list->items();
foreach ($items as &$item) {
$images = json_decode($item['images'], true) ?: [];
$item['cover'] = !empty($images) ? site_url($images[0]) : '';
$item['image_count'] = count($images);
}
$seoTdk = CateSeoService::getByAlias('home/case');
return view('index', [
'seo_title' => $seoTdk['seo_title'] ?: ('客户案例 - ' . get_site_name()),
'seo_keywords' => $seoTdk['seo_keywords'],
'seo_description' => $seoTdk['seo_description'],
'list' => $items,
'pagination' => [
'total' => $list->total(),
'currentPage' => $list->currentPage(),
'lastPage' => $list->lastPage(),
'listRows' => $list->listRows(),
],
'total' => $list->total(),
]);
}
}
8.2 模板(index.html)
{include file="common/header" /}
<section class="hero">
<div class="container">
<h1>客户案例</h1>
<p>共 {$total} 套案例</p>
</div>
</section>
<section class="section">
<div class="container">
{if condition="!empty($list)"}
<div class="grid">
{volist name="list" id="item"}
<div class="card">
{if condition="!empty($item['cover'])"}
<img src="{$item.cover}" alt="{$item.title}">
{/if}
<h3>{$item.title}</h3>
<p>{$item.image_count} 张图</p>
</div>
{/volist}
</div>
{if condition="$pagination['lastPage'] > 1"}
<div class="pagination">
<span>第 {$pagination.currentPage} / {$pagination.lastPage} 页</span>
</div>
{/if}
{else /}
<div class="empty">暂无案例</div>
{/if}
</div>
</section>
{include file="common/footer" /}