单标签与全局变量(全表)
全部 16 个单标签的属性和用途,以及模板里可以直接用的全局变量。
单标签**不压作用域**:它取一个值直接输出,写在页面的任何位置都行 —— 循环里、循环外、属性值里、甚至同一行属性中间(见「套标签」那一篇)。
全部单标签
| 标签 | 属性 | 作用 |
|---|---|---|
{naicha:site name="…"} |
name(站点设置里的键,默认 title) |
取一条站点设置。常用键:title slogan keywords description icp copyright contact_email domain |
{naicha:brand} | — | 品牌字标,输出两段(.brand-a / .brand-b)。在最后一个「小写→大写」的驼峰边界断开(NaichaCMS → Naicha+CMS);中文站名没有这种边界就整段当第一段,不会拆坏 |
{naicha:icp} | — | 备案号,输出成指向工信部的链接。没填时输出空串 —— 配合 .footer-bottom span:empty{display:none} 不会在页脚留空隙。海外站不需要备案,所以它必须是"空就不显示" |
{naicha:param name="…"} |
name、default |
取参数:先看控制器传的 param,再看 URL 上的同名查询参数,都没有就用 default。{naicha:param name="cid"} 是最常用的一个(当前栏目 id) |
{naicha:url } |
type = list|detail|page|tag|admin,配 cid/id/alias/name/path;或直接给 path |
生成站内地址。会自动带上当前语言的前缀 —— 所以英文站里的 /list/x 会变成 /en/list/x,不要自己拼前缀 |
{naicha:page} |
total、num、page、param |
分页条。不传 total 就复用最近一次 {naicha:list} 的统计;只有一页时输出空串。param 是页码参数名(默认 page,用户列表用的是 pg) |
{naicha:include file="…"} | file |
套用另一个模板文件(相对当前主题)。文件名只取 basename,写路径穿越无效 |
{naicha:search} | — | 当前搜索关键词(等同于 {keyword}),回填到搜索框的 value 里 |
{naicha:token} | — | CSRF 令牌,放进表单的隐藏域。页面里有它就不会被整页缓存(缓存下来发给别人会导致提交失败) |
{naicha:now} | format(默认 Y-m-d) |
当前时间(服务器时区) |
{naicha:asset file="…"} |
file |
静态资源地址,自动带 ?v= 版本号(取自文件修改时间)。改了文件 URL 就变,浏览器必然重新拉取 —— 比手工维护版本号可靠 |
{naicha:block name="…"} |
name |
页面片段(后台「站点素材 → 页面片段」里维护的那段文字)。取不到时输出空串,删掉片段不会把站点弄崩 |
{naicha:filters} |
cid、show(如 sort,year,tag) |
筛选条。它自己读当前地址判断"选了哪些",并生成保留其它参数的链接 |
{naicha:comments} |
num、form(0 = 只列不显示表单) |
评论区。站点关了评论、或拿不到内容 ID 时输出空串(不报错、不留空壳) |
{naicha:commentcount} | 同 comments |
只输出评论条数,适合放在标题旁边 |
{naicha:captcha} | — | 验证码控件(一行输入框 + 算式)。站点没开验证码时输出空串,模板不用自己判断 |
全局变量
这些不是标签,是控制器交给模板的值。取法就是 {名字},
可以带修饰符(见「标签语法总览」那篇)。
| 变量 | 含义 |
|---|---|
{seo.title} {seo.keywords} {seo.desc} | 当前页的 SEO 三件套(页头里那三个 meta) |
{canonical} | 规范地址。多语言站按语言区分 —— 所有语言都指同一个 canonical 等于白翻 |
{currentPath} | 当前路径。导航高亮就是拿它和导航项的 url 比 |
{title} | 页面主标题(部分页面提供,如商品列表的「全部商品」) |
{keyword} | 搜索关键词 |
{lang.html} {lang.dir} {lang.name} {lang.code} {lang.prefix} {lang.on} | 当前语言。写在 <html lang="…" dir="…"> 上 |
{lang.hreflang html="1"} | 输出各语言的 hreflang 标注。必须带 html="1",否则标签会被转义成文本显示在页面上 |
{lang.list} | 语言列表,配 {naicha:loop from="lang.list" as="L"} 用(语言切换器) |
{listTotal} {listPages} {listItems} | 最近一次 {naicha:list} 的总数 / 页数 / 本次条数。写「共 N 条」「没有结果」靠它们 |
{shopTotal} {shopItems} | 最近一次 {naicha:shop} 的同名统计(命名刻意与 list 一致) |
{crumbs} | 面包屑数组,配 {naicha:loop from="crumbs" as="c"},行内可用 {c.name} {c.url} |
{category.…} | 当前栏目(栏目页与详情页提供),如 {category.alias} {category.name} |
{hasShop} {shopItem.…} {shopUnlock.…} {loginUrl} | 详情页的商品与付费解锁。hasShop 是 '1'/'0' 开关(不要让模板去判断数组——引擎对数组会先 count) |
{viewSwitch.on} {viewSwitch.url} {viewSwitch.label} | 手机版 / 电脑版切换入口 |
{adminBase} | 后台地址前缀(后台模板里拼链接用) |
每个单标签的用法演示
下面每一段都是可以直接粘进模板的最小片段。
方括号里写的是"这段放在哪":[页头] 页头模板、[列表] 列表模板、
[详情] 详情模板、[任意] 哪都能放。
{naicha:site name="…"}
<!-- [页头] 站点名 + 描述,回落到站点设置 -->
<title>{naicha:site name="title"}</title>
<meta name="description" content="{naicha:site name="description"}">
<!-- [页脚] 联系方式(这三个键在「站点设置 → 站点信息」里填)-->
<a href="mailto:{naicha:site name="contact_email"}">{naicha:site name="contact_email"}</a>
<span>{naicha:site name="contact_wechat"}</span>
<!-- [任意] 统计代码:站点设置里存的是一整段 HTML,必须带 html="1" -->
{naicha:site name="stat_code" html="1"}
别用它输出正文。它只读「站点设置」里那一张表,取不到就是空串 ——
想让"某个键没填时整块不显示",外面套一层 {naicha:if var="…" notempty="1"}。
{naicha:brand}
<!-- [页头] 品牌字标:输出两段,后一段可以换个颜色 -->
<a class="logo" href="/">{naicha:brand}</a>
<!-- 配套 CSS:NaichaCMS 会被拆成 Naicha + CMS -->
<style>
.logo .brand-a{color:#1e293b}
.logo .brand-b{color:#0078d4} /* CMS 用品牌色 */
</style>
拆分规则是在最后一个「小写→大写」的驼峰边界断开; 中文站名(如「奶茶科技」)没有这种边界,就整段当第一段、第二段为空 —— 不会把站名拆坏。
{naicha:icp}
<!-- [页脚] 备案号。没填时输出空串,配合下面这条 CSS 不会留空隙 -->
<div class="footer-bottom">
{naicha:copyright}
{naicha:icp}
</div>
<style>.footer-bottom span:empty{display:none}</style>
它自己会包成指向 beian.miit.gov.cn 的链接。
海外站不需要备案,所以它必须是"空就不显示",而不是在模板里判断有没有填。
{naicha:param name="…"}
<!-- [列表] 当前页码:交给列表标签与分页条 -->
{naicha:list cid="{naicha:param name='cid'}" num="10" page="{naicha:param name='page'}"}
…
{/naicha:list}
<!-- [搜索页] 回填关键词;不存在时用默认值 -->
<input type="search" name="kw" value="{naicha:param name="kw"}" placeholder="搜点什么">
<input type="hidden" name="from" value="{naicha:param name="from" default="nav"}">
取值顺序:控制器传的 param → URL 查询参数 → default。
⚠️ 属性里的引号:外层用双引号时里面那层要写单引号({naicha:param name='cid'}),
写成一样的一对会把标签截断。
{naicha:url …}
<!-- [任意] 栏目地址(别名优先,别名空则用 id)-->
<a href="{naicha:url type="list" cid="3"}">解决方案</a>
<!-- [任意] 内容详情地址 -->
<a href="{naicha:url type="detail" id="12" alias="hello-world"}">读全文</a>
<!-- [任意] 单页 / 标签聚合 / 后台 -->
<a href="{naicha:url type="page" alias="about"}">关于我们</a>
<a href="{naicha:url type="tag" name="模板"}">#模板</a>
<a href="{naicha:url type="admin" path="contents"}">内容管理</a>
<!-- [任意] 只想拼一个站内相对地址 -->
<a href="{naicha:url path="/list/news"}">新闻</a>
⚠️ 它会自动带上当前语言的前缀:英文站里 /list/news 会变成 /en/list/news。
所以不要自己拼 /en/,两处都加就会变成 /en/en/…。
{naicha:page …}
<!-- [列表] 分页条。紧跟在列表标签后面即可,它会复用那次列表的总数 -->
{naicha:list cid="{naicha:param name='cid'}" num="10" page="{naicha:param name='page'}"}
<a href="{list.url}">{list.title}</a>
{/naicha:list}
{naicha:page}
<!-- [后台列表] 明确给数字,页码参数换成 pg(用户列表用的是 pg)-->
{naicha:page total="{total}" num="20" page="{page}" param="pg"}
<!-- [列表] 只有一页时它输出空串,所以外面不用判断 -->
<div class="pager-wrap">{naicha:page}</div>
不传 total 就复用最近一次 {naicha:list} /
{naicha:shop} 的统计。链接会保留当前地址上的其它查询参数(筛选、搜索词),
所以翻页不会把筛选条件丢掉。
{naicha:include file="…"}
<!-- [任意] 套用同主题下的另一个文件 -->
{naicha:include file="header.html"}
…页面内容…
{naicha:include file="footer.html"}
<!-- [详情] 把"骨架"拆成片段,九套版式共用同一份 -->
{naicha:include file="detail_head.html"} <!-- 标题区 -->
{naicha:include file="detail_action.html"} <!-- 购买/下载/门槛 -->
{naicha:include file="detail_foot.html"} <!-- 上下篇 + 相关推荐 -->
文件名只取 basename,所以写 ../../config/config.php 也只会去主题目录里找
config.php —— 路径穿越无效。子主题里没有这个文件时会自动回落到父主题。
{naicha:search} / {keyword}
<!-- [搜索页] 搜索框 + 关键词高亮(两个标签等价,search 更好读)-->
<form action="/search" method="get">
<input type="search" name="kw" value="{naicha:search}">
<button>搜索</button>
</form>
<!-- [搜索页] 结果列表:标题里的关键词会被包成 <mark class="nc-hl"> -->
{naicha:list keyword="{naicha:search}" num="10"}
<a href="{list.url}">{list.title highlight="{naicha:search}"}</a>
{/naicha:list}
<!-- [搜索页] 空结果时给一句人话 -->
{naicha:if var="listTotal" eq="0"}没有找到与「{naicha:search}」相关的内容。{/naicha:if}
highlight 是先转义、再插标签的(顺序反了就是 XSS 入口),
所以它能安全地用在用户可控的关键词上。
{naicha:token}
<!-- [任意表单] 每个 POST 表单里都要有,否则会被 CSRF 拦下 -->
<form method="post">
<input type="hidden" name="_token" value="{naicha:token}">
<textarea name="content"></textarea>
<button type="submit">提交</button>
</form>
⚠️ 页面里出现它,整页缓存就会自动跳过这一页(缓存下来发给别人会让提交失败)。 所以别把它放进"人人相同、且希望被缓存"的页面里 —— 那类页面本来也不该有表单。
{naicha:now …}
<!-- [页脚] 年份:用 U 拿时间戳,配合别的标签算版权年份 -->
<span>© {naicha:now format="Y"} {naicha:site name="title"}</span>
<!-- [任意] 完整时间 / 只到天 -->
<time>{naicha:now format="Y-m-d H:i"}</time>
<time>{naicha:now}</time> <!-- 默认 Y-m-d -->
时间格式就是 PHP 的 date() 那一套。
时区取站点设置;没设就是服务器时区(NaichaMail 那类应用会自己设 Asia/Shanghai,前台不必重复设)。
{naicha:asset file="…"}
<!-- [页头] 样式与脚本,自动带 ?v= 版本号 -->
<link rel="stylesheet" href="{naicha:asset file="static/css/site.css"}">
<script src="{naicha:asset file="static/js/site.js"}" defer></script>
<!-- [详情] 只有这一页需要的播放器脚本 -->
<script src="{naicha:asset file="static/js/player.js"}"></script>
<!-- [任意] 图片同理(图标、logo 这类静态图)-->
<img src="{naicha:asset file="static/img/arrow.svg"}" alt="">
版本号取自文件修改时间(filemtime 的后 7 位)——
所以改了文件 URL 就会变,不用手工维护版本号。
⚠️ 代价要知道:重新解压/覆盖部署会把 mtime 刷成新值,于是所有访客的缓存集体失效、重下一遍。
这不是 bug,但如果你在意那一次流量,就在部署时保留原 mtime(tar -m / unzip -X)。
(后台自己的样式表用的是内容哈希 md5_file,两处机制不同,别互相套用。)
{naicha:block name="…"}
<!-- [首页] 一段可以在后台改的文案(后台「内容与外观 → 页面片段」)-->
<section class="hero">
<h1>{naicha:block name="home_hero_title"}</h1>
<p>{naicha:block name="home_hero_sub"}</p>
</section>
<!-- [任意] 片段里存 HTML 时带 html="1" -->
<div class="notice">{naicha:block name="site_notice" html="1"}</div>
<!-- [任意] 取不到片段就整块不显示(删掉片段不会把站点弄崩)-->
{naicha:if var="site_notice" notempty="1"}
<div class="notice">{naicha:block name="site_notice" html="1"}</div>
{/naicha:if}
片段的意义是改文案不动 HTML 结构: 站长想改首页标题,不该需要去编辑模板文件。
{naicha:detailbody} 与 {naicha:toc}
<!-- [详情] 正文 + 侧栏章节目录(两件配套用)-->
<div class="dt-wrap">
<article class="article">
{naicha:detailbody} <!-- 正文 HTML(已给 h2/h3 加好锚点)-->
</article>
<aside class="dt-side">
{naicha:toc title="本页目录"}
</aside>
</div>
detailbody 与 {detail.content html="1"} 输出的是同一份正文,
差别是它会给正文里的 h2/h3 自动补上 id 锚点(已经手写过 id 的尊重原值,不覆盖)。
toc 就是根据那些锚点生成目录,所以正文必须用 detailbody,
否则目录里点进去会跳不动。toc 的 title 属性是目录标题,留空则用「本页目录」。
正文里一个 h2/h3 都没有时,toc 输出空串。
{naicha:filters}
<!-- [列表] 筛选条:站点设置里开启筛选功能后用它 -->
{naicha:filters show="sort,year,tag"}
<!-- 它自己读当前地址判断"选了哪些",并生成保留其它参数的链接。
想让它真的生效,列表标签要开 filters: -->
{naicha:list cid="{naicha:param name='cid'}" num="10" filters="1"}
…
{/naicha:list}
⚠️ filters="1" 只加在"响应地址栏筛选"的那个主列表上。
侧栏的「最新 5 篇」这类局部列表不要开 —— 否则用户点一个筛选,侧栏内容也跟着变,看着莫名其妙。
show 里没写的维度不显示(如 show="year" 就只有年份)。
{naicha:comments …}
<!-- [详情] 评论区(列表 + 表单)-->
<section class="comments">
<h2>评论 <span>{naicha:commentcount}</span></h2>
{naicha:comments}
</section>
<!-- [详情] 只要列表、不要表单(比如表单放在页面别处)-->
{naicha:comments num="20" form="0"}
站点关了评论、或当前拿不到内容 ID 时,它输出空串(不报错、也不留一个空壳)。 所以模板里不用写"如果开了评论就…"。
{naicha:commentcount}
<!-- [详情] 标题旁边那一个数字 -->
<h1>{detail.title}</h1>
<div class="meta">浏览 {detail.hits} · 评论 {naicha:commentcount}</div>
<!-- [列表] 想在列表里显示每篇的评论数:只能走 list 的字段,
这个标签是"当前内容"的评论数,循环里不是你要的数 -->
属性和 comments 一样(num / form),但它只输出数字。
{naicha:captcha}
<!-- [表单] 验证码控件:一行输入框 + 一道算式 -->
<form method="post">
<input type="hidden" name="_token" value="{naicha:token}">
<input type="text" name="name" placeholder="称呼">
{naicha:captcha}
<button type="submit">提交</button>
</form>
站点没开验证码时输出空串,所以模板不用判断开关 —— 开了就有、没开就没有。后台不做这类判断:模板里有没有这个标签是一回事, 服务端要不要校验是另一回事(服务端只认设置开关)。
