标签与函数
前台模板只用 12 个标签 + 16 个全局函数就能拼出任何页面。这一页是完整参数手册,建议直接搜关键词。
一、标签写法规则
格式:{tw:标签名 属性="值"} … {/tw:标签名},不闭合的标签(如 {tw:seo /})直接自闭合。
属性值有三种写法,决定它怎么被解析:
| 写法 | 示例 | 含义 |
|---|---|---|
| 普通字符串 | limit="12" | 原样当字符串/数字用 |
以 $ 开头 | parent="$sort.id" | 当 PHP 变量用,取当前作用域的值 |
以 : 开头 | :limit="$page['limit']" | 当 PHP 表达式原样执行 |
外层用双引号时,里面写单引号:order="'id desc'"。不要套双引号,模板解析会断。
二、nav — 栏目导航
闭合标签
{tw:nav parent="0" limit="8" alias="nav" key="k"}
<a class="nav-link {if $nav.active}is-active{/if}" href="{$nav.url}"{$nav.target|raw}>{$nav.name}</a>
{/tw:nav}
| 属性 | 默认 | 说明 |
|---|---|---|
parent | 0 | 父栏目 ID。0 = 一级栏目;5 = ID 为 5 的子栏目;$sort.id = 当前栏目的子栏目 |
limit | 0(不限) | 最多取几条 |
alias | nav | 循环变量名,如填 item 就用 $item.name |
key | key | 索引变量名 |
循环里可用的字段
| 字段 | 说明 |
|---|---|
$nav.id | 栏目 ID |
$nav.name / $nav.subname | 名称 / 副标题 |
$nav.url | 已生成好的链接(外链栏目自动用外链地址) |
$nav.target | 外链栏目的 target="_blank" rel="noopener",输出时加 |raw |
$nav.active | 是否当前栏目(1/0),做导航高亮 |
$nav.son | 子栏目数量,大于 0 才渲染下拉箭头 |
$nav.ico / $nav.pic | 栏目缩略图 / 栏目大图 |
$nav.type | list 列表 / page 单页 / link 外链 |
二级下拉菜单(默认主题的写法)
{tw:nav parent="0" limit="9"}
<div class="nav-item">
<a class="nav-link {if $nav.active}is-active{/if}" href="{$nav.url}"{$nav.target|raw}>{$nav.name}</a>
{if $nav.son}
<div class="nav-sub">
{tw:nav parent="$nav.id" alias="sub" limit="12"}
<a href="{$sub.url}">{$sub.name}</a>
{/tw:nav}
</div>
{/if}
</div>
{/tw:nav}
三、list — 内容列表
闭合标签 · 自带分页
{tw:list sort="12" limit="9" page="$tw_page" order="publish_time desc"}
<article>
<a href="{$list.url}"><img src="{$list.ico}" alt="{$list.title}"></a>
<h3><a href="{$list.url}">{$list.title}</a></h3>
<p>{$list.summary|substr=0,80}</p>
<time>{$list.date}</time>
</article>
{/tw:list}
{tw:pagination /}
| 属性 | 默认 | 说明 |
|---|---|---|
sort | 当前栏目 | "*" 全站;"12" 栏目 ID(自动包含子栏目);"products" 按 slug(只取该栏目);不写 = 当前栏目 |
limit | 12 | 每页条数 |
page | 自动 | 页码。列表页要显式传 page="$tw_page" 才会分页 |
order | publish_time desc, id desc | 只允许 publish_time、id、views、sort、price,其他字段会被忽略(防注入) |
attr | 空 | 按推荐属性过滤:recommend / hot / new |
tags | 空 | 按标签过滤,如 tags="cnc" |
keyword | 空 | 关键词搜索(标题/摘要/标签) |
exclude | 空 | 排除的 ID,如 exclude="$content.id" 或 exclude="3,5,8" |
alias / key | list / key | 循环变量名 / 索引名 |
循环里可用的字段
id、title、subtitle、summary、url、ico(已自动转 WebP)、date(如 Oct 30, 2026)、datetime(如 2026-10-30,给 <time datetime=""> 用)、views、price、unit、model_no、moq、attr、tags、sort_name、sort_url、sort_slug
栏目定义过自定义字段后,值存在内容的 meta 列(JSON),标签已经自动解析好,三种取法:
{$list.字段键} 取单项(如 {$list.badge})、{$list.meta} 取原文、{$list.cf} 取解析后的数组。
遍历全部字段必须用 {foreach name="list.cf" item="v" key="k"}——{volist} 的 key 给的是序号 1/2/3,不是字段键。详见 栏目自定义字段。
标签会自动生成两个变量
| 变量 | 内容 |
|---|---|
$page | 分页信息:current、last、limit、total、has_prev、has_next;不分页时为 null |
$total | 符合条件的总条数 |
$page 是保留变量名
模板里别再自己起名叫 $page,会被覆盖。自己的分页参数用 $tw_page(列表页控制器已赋好这个值)。
四、attr — 推荐位内容
闭合标签
{tw:attr name="recommend" limit="8" sort="*"}
{include file="comm/product-card" /}
{/tw:attr}
| 属性 | 默认 | 说明 |
|---|---|---|
name | recommend | 对应内容表 attr 字段:recommend 推荐 / hot 热门 / new 最新。可组合 "recommend,hot" |
limit | 6 | 条数 |
sort | * | 限定栏目,写法同 {tw:list} |
alias | list | 循环变量名(和 list 一致,方便共用卡片片段) |
五、sort — 栏目信息
自闭合
{tw:sort /}
<h1>{$sort.name}</h1>
<p>{$sort.description}</p>
{* 取指定栏目:alias 换个名避免覆盖当前 $sort *}
{tw:sort id="6" alias="about" /}
<h2>{$about.name}</h2>
| 属性 | 默认 | 说明 |
|---|---|---|
id | 0 | 栏目 ID,不写取当前栏目 |
alias | sort | 赋值变量名 |
六、content — 内容详情
自闭合
<h1>{$content.title}</h1>
<div class="body">{$content.content|html_entity_decode|raw}</div>
{* 取指定 ID 的内容 *}
{tw:content id="18" alias="news" /}
<h3>{$news.title}</h3>
| 属性 | 默认 | 说明 |
|---|---|---|
id | 0 | 内容 ID,不写取当前内容 |
alias | content | 赋值变量名 |
auto | 1 | 为 1 时自动补全 url / images / tag_list |
七、position — 面包屑
{tw:position /} {* 默认分隔符 " / " *}
{tw:position separator=" > " /} {* 自定义分隔符 *}
输出的是完整 HTML:<nav class="tw-crumb">首页 / 产品中心 / 某某产品</nav>,最后一项自动变成 aria-current="page" 且不带链接。
八、pagination — 分页条
{tw:list sort="*" limit="12" page="$tw_page"}
...
{/tw:list}
{tw:pagination /} {* 默认左右各 2 个页码 *}
{tw:pagination range="3" /} {* 左右各 3 个 *}
输出 <nav class="tw-pager"> 结构,含 rel="prev" / rel="next"(对 SEO 有用);只有一页时不输出任何内容。
九、lang — 语言切换
<div class="lang-list">
{tw:lang alias="item"}
<a href="{$item.url}" class="{if $item.active}is-active{/if}">{$item.name}</a>
{/tw:lang}
</div>
循环字段:code(如 zh-cn)、name(语言本名)、region、url(当前页面的对应语言地址,不是首页)、active。
靠内容表的 trans_key 字段配对:两个语言的同一条内容填同一个 trans_key,切换时就跳对应版本;没配对则回落到该语言首页。
十、currency — 货币切换
闭合标签
<div class="cur-list">
{tw:currency alias="cur"}
<a href="javascript:;" data-cur="{$cur.code}" class="{if $cur.active}is-active{/if}">
{$cur.symbol} {$cur.code}
</a>
{/tw:currency}
</div>
| 属性 | 默认 | 说明 |
|---|---|---|
alias / key | cur / key | 循环变量名 / 索引名 |
循环里可用的字段
| 字段 | 说明 |
|---|---|
$cur.code | 货币代码,如 USD / EUR |
$cur.name / $cur.symbol | 名称 / 符号($、€) |
$cur.active | 是否当前生效货币(1/0) |
当前货币存在 cookie tw_cur 里(Currency::current() 读它,找不到就用基准货币)。
所以模板上的货币按钮只需写 cookie 再刷新,主题的 main.js 已经封装好:
document.cookie = 'tw_cur=' + code + ';path=/;max-age=31536000' 然后 location.reload()。
金额输出用 {:tw_price($list.price)},会自动按当前货币汇率换算并带上符号。
十一、seo — SEO 头部
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{tw:seo /}
<link rel="stylesheet" href="/template/{$theme}/css/style.css">
</head>
一行输出全部:<title>、description、keywords、canonical、hreflang(含 x-default)、og:、twitter:、JSON-LD 结构化数据、robots。具体逻辑见 SEO 运维。
十二、slide — 幻灯片
闭合标签
{tw:slide group="home" limit="5"}
<div class="slide{if $slide.first} is-first{/if}">
{if $slide.has_video}
<video src="{$slide.video}" muted loop playsinline></video>
{else /}
<img src="{$slide.image}" alt="{$slide.title}"{if $slide.mobile_image} data-mobile="{$slide.mobile_image}"{/if}>
{/if}
<h2>{$slide.title}</h2>
<p>{$slide.subtitle}</p>
{if $slide.url}<a href="{$slide.url}"{$slide.target_attr|raw}>{$slide.btn_text|default='了解更多'}</a>{/if}
</div>
{/tw:slide}
| 属性 | 默认 | 说明 |
|---|---|---|
group | 当前栏目绑定的分组 | 分组 code(如 home)或 id;不写就取栏目表单里的「幻灯片分组」,栏目没绑则输出空 |
limit | 0(不限) | 最多几张 |
order | sort asc, id asc | 只允许字母/数字/下划线/逗号/点/空格,其他字符会被过滤 |
lang | 当前语言 | 指定取哪种语言的图 |
alias / key | slide / key | 循环变量名 / 索引名 |
循环里可用的字段
| 字段 | 说明 |
|---|---|
$slide.id / $slide.title / $slide.subtitle | ID / 主标题 / 副标题 |
$slide.summary | 描述文字 |
$slide.image / $slide.image_url | 电脑图(两者同值,image_url 是旧主题兼容名) |
$slide.image_mobile / $slide.mobile_image | 手机图(同上) |
$slide.video / $slide.has_video / $slide.has_image | 视频地址 / 有没有视频 / 有没有图(1 或 0) |
$slide.url / $slide.btn_text | 跳转链接 / 按钮文字 |
$slide.target_attr | 已拼好的 target="_blank" rel="noopener",输出时加 |raw |
$slide.index / $slide.first / $slide.last | 第几张(从 1 起)/ 是否第一张 / 是否最后一张 |
① 多语言回退:当前语言下这个分组没有启用的图,整组回退默认语言——只有一个语言的站不用为每种语言重复上传同一张图。
② 定时上下线:条目上的 start_time / end_time 非 0 时按当前时间过滤,过期自动消失,不用手动下架。
十三、slidegroup — 幻灯片分组信息
可自闭合,也可闭合
{* 取分组信息,判断有没有图再渲染容器 *}
{tw:slidegroup code="home" alias="g" /}
{if $g.count}
<section class="hero" data-count="{$g.count}" data-w="{$g.width}">
{tw:slide group="home"}
... 单张 ...
{/tw:slide}
</section>
{/if}
{* 也支持闭合写法,一次输出一段文案 *}
{tw:slidegroup code="home" alias="g"}{$g.name} 共 {$g.count} 张{/tw:slidegroup}
| 属性 | 默认 | 说明 |
|---|---|---|
code | 空 | 分组 code,如 home;不写则取当前栏目绑定的分组 |
alias | g | 赋值变量名 |
可用字段:$g.id、$g.name、$g.code、$g.width、$g.height、$g.remark、$g.count(该分组当前语言下的图片数量)。分组不存在时返回各字段为空、count=0 的默认数组,模板不会报未定义变量。
十四、全局函数
| 函数 | 说明 |
|---|---|
tw_setting('phone') | 取任意站点设置项,第二参数可给默认值 |
tw_url('sort', ['sort'=>12]) | 生成链接。name 可取 home / sort / detail / search / message |
tw_asset('css/style.css') | 主题资源地址,等价于 /template/{当前主题}/css/style.css |
tw_seo() | 输出 SEO head(同 {tw:seo /}) |
tw_lang() | 当前语言代码 |
__tw('products') | 取语言包文案,模板里写 {:__tw('products')};支持占位符 __tw('hello', ['name'=>'Tom']) |
tw_e($str) | HTML 转义输出,防 XSS |
tw_slug('CNC 加工') | 生成 URL 别名(拼音/英文小写连字符) |
tw_date($ts) | 时间戳格式化,默认 M d, Y,第二参数可自定义格式 |
tw_cut($html, 160) | 按纯文本截取(先去标签),适合生成摘要与 meta description |
tw_price(4800) | 金额格式化:按当前货币汇率换算并带符号(¥4,800.00 / $672.00),第二参数是单位 |
tw_currency() | 当前货币代码,常用于货币按钮上显示 USD ▾ |
tw_dict_json(['k1','k2']) | 输出 window.TW_DICT 脚本,把指定词条注入前端 JS(只注入列出的键,别整包输出) |
tw_brand_url() / tw_brand_name() / tw_brand_link() | 品牌官网地址 / 名称 / 版权徽标链接(页脚用,自动读 config/tw.php 的 brand 段) |
模板里怎么调函数
{:__tw('contact_us')}
{:tw_setting('email')}
{:date('Y', $content.publish_time)}
{:count($content['images'])}
{:number_format($content.price, 2)}
规则:{:表达式},冒号后写 PHP 表达式,前面不加 echo。
十五、常用配方
相关产品(同栏目,排除自己,取 4 条)
{tw:list sort="$sort.id" limit="4" exclude="$content.id"}
{include file="comm/product-card" /}
{/tw:list}
最新新闻(限定 news 栏目)
{tw:list sort="news" limit="6" order="publish_time desc"}
<li><a href="{$list.url}">{$list.title}</a><span>{$list.date}</span></li>
{/tw:list}
按标签取内容
{tw:list sort="*" tags="anodizing" limit="6"}
<a href="{$list.url}">{$list.title}</a>
{/tw:list}
按浏览量排序(热门)
{tw:list sort="*" order="views desc" limit="5"}
<a href="{$list.url}">{$list.title}({$list.views})</a>
{/tw:list}
首页调「关于我们」单页正文
{tw:sort id="6" alias="about" /}
<h2>{$about.name}</h2>
<p>{$about.description}</p>
十六、排查清单
| 现象 | 原因 |
|---|---|
标签原样输出在页面上(能看到 {tw:list} 文字) | 标签库没注册成功,检查 config/view.php 的 taglib_pre_load 是否为 app\common\taglib\Tw |
| 循环体空白 | 该栏目下没有已发布内容;或语言不匹配(英文栏目只能取到 en 的内容) |
| 列表带不出子栏目内容 | 用的是 slug(sort="products")只取本栏目,改写成栏目 ID 才会自动包含子栏目 |
| 分页不显示 | {tw:list} 没写 page="$tw_page",或结果不足一页 |
| 图片是破图 | 内容是老数据、ico 存的是绝对路径;检查上传目录是否可访问 |
{if} 里报语法错 | 条件里用了过滤器(如 |count),改成函数写法 count($arr) |