3.1 模板与 CSS 的组织规范

> 让改动只落在该落的文件里,内核升级时不被人打回来。

模板不是自由格式,它先要变成 PHP

Discuz! 的模板文件(.htm,X5 的 default 套系里更多用 .php 后缀)不是浏览器直接读的 HTML。它先被 source/class/class_template.php 解析一遍,模板标签被翻译成 PHP,编译结果缓存进 data/cache/,后续访问直接跑缓存文件;缓存失效才重新编译。

所以模板是一套受限的伪代码:外面包着 HTML,里面嵌着会变成 PHP 的逻辑。官方对这种混合体单独立了一套书写规范,目的只有一个——让任何模板文件都能被人一眼看懂,并且能被工具稳定地解析。

标记写法的硬规矩

规矩 正确写法 常见错法
标记一律小写 <div class="wp"> <DIV CLASS="wp">
参数值用双引号 <img src="{IMGDIR}/a.png" /> <img src='{IMGDIR}/a.png'>
非成对标记以斜杠收尾 <br /> <img ... /> <br> <img ...>
属性之间单个空格分隔 <a href="#" title="x"> <a href="#" title="x">
布尔属性不写值 <input type="checkbox" checked /> <input type="checkbox" checked="checked" />
行内样式尽量移到 CSS <div class="t"> <div style="color:red">

这几个示例里最容易被违反的是"非成对标记以斜杠收尾"。Discuz! 的模板解析器对标记的处理有自己的实现习惯,XHTML 风格的写法在整个代码库里是一致的,你跟着写就不会在某个边角翻车。

逻辑体必须被注释包起来

真实模板文件里,所有逻辑体都写成 HTML 注释的形态——起止各一层:

<!--{if $uid}>
    <div class="userbar">欢迎回来,{$username}</div>
<!--{else}-->
    <div class="userbar"<a href="member.php?mod=logging&action=login"></a></div>
<!--{/if}-->

去掉注释壳之后,就是我们常说的 {if}、{/if} 标签。写成注释有两个实际好处:一是模板里的 HTML 在编辑器里仍然可读、可折叠;二是万一某处解析失败,输出到页面上的是一段注释而不是一堆乱码。

两个例外值得记住:

  1. {if} 条件嵌在 HTML 属性里时可以不写注释壳,例如 <div class="{if $uid}on{/if}">;
  2. {subtemplate xxx/yyy} 和 {template xxx/yyy} 这类调用标签后面不带斜杠,也没有结束标签。

缩进用 TAB,且只表示逻辑层级

规范要求缩进使用制表符(TAB),且缩进只用来表达逻辑嵌套深度——一层 {if} 或 {loop} 就多缩进一级,进入循环体、离开循环体再退回来。它不是用来对齐某个具体像素位置的。

<div class="threadlist">
    <!--{loop $threadlist $tid $thread}-->
        <div class="thread">
            <a href="forum.php?mod=viewthread&tid={$tid}">{$thread[subject]}</a>
        </div>
    <!--{/loop}-->
</div>

同层级的兄弟节点不靠空行或额外缩进来分组,缩进一变就意味着逻辑层级变了。这条规则看着死板,实际好处是:模板被解析器编译成 PHP 之后,缩进与生成的 PHP 结构一一对应,出问题时看缓存文件就能定位。

顺带一提,官方明确不建议用 DreamWeaver、Fireworks 这类可视化工具生成或编辑 Discuz! 模板。它们会自作主张把标签改写、补属性、加  ,一次保存就能把你的规范打乱。要编辑就用纯文本编辑器。

CSS 的两层扩展机制

Discuz! 不要求你重写全部样式。default 套系里已经有一份编译好的 common.css(公共部分)和 module.css(各模块部分),你的套系只需要写扩展文件去做覆盖式追加:

扩展文件 覆盖的对象 典型内容
template/{标识}/common/extend_common.css default 的 common.css 全站字体、颜色、按钮、导航、分页
template/{标识}/common/extend_module.css default 的 module.css 论坛列表、帖子正文、门户区块
/* extend_common.css —— 只写你要改的,其余全部继承 */
.wp { width: 1200px; margin: 0 auto; }
.pg { border: 1px solid #E5E5E5; }
.pg a { color: #2B8FD4; text-decoration: none; }
.pg a:hover { text-decoration: underline; }

文件被追加在默认样式之后加载,同选择器、同权重时你的规则胜出。好处是内核改版时你不需要跟着一层层比对。

class 命名:给内核留出安全距离

内核 CSS 里的类名大多很短:wp、pg、tl、f……你如果也给自定义样式起同名类,改一次就把整站布局掀了。做法是加一个自己的前缀,全站统一:

/* 前缀 dt,语义化命名,和内核类零冲突 */
.dt-card { border-radius: 4px; }
.dt-card-hd { padding: 12px 16px; font-weight: bold; }
.dt-list-item:hover { background: #F7F7F7; }

命名带上组件和元素两段(card + hd),半年后你自己回来改也知道动的是哪一块。改之前先在 template/default/common/ 里搜一下同名词,确认没撞车。

一张表说清"这文件该放哪"

你要做的事 落点 生效方式
改首页、帖子页 HTML template/{标识}/forum/ 下对应文件 后台 → 界面 → 更新缓存
改公共头尾 template/{标识}/common/header、footer 同上
改全站基础样式 template/{标识}/common/extend_common.css 覆盖 common.css
改单个模块样式 template/{标识}/common/extend_module.css 覆盖 module.css
换主色调(想可切换) template/{标识}/style/t1/style.css 后台风格页选扩展配色
换界面基础图片目录 后台风格页的界面基础图片目录 写入 $_G['style']['imgdir']
改手机版页面 template/{标识}/touch/ 与 PC 版互相独立
加门户 DIY 区域 template/{标识}/portal/diyxml/ 后台 DIY 导入

落地流程

改模板的顺序固定成这样,几乎不会返工:

  1. 开开发者模式(config/config_global.php 末尾加 $_config['plugindeveloper'] = 1;),后台才会出现设计模板的入口;
  2. 复制 template/default 里你要改的那几个文件到自己的 template/{标识}/ 下,只改复制出来的那几份,不动 default;
  3. 页面结构改模板文件,样式改 extend_common.css / extend_module.css,一个像素的问题不要往模板里塞内联 style;
  4. 后台 → 界面 → 更新缓存,再刷新页面。

这四步里最容易被跳过的是第 4 步。模板有编译缓存,改完不更新缓存你会以为代码没生效,然后开始瞎改——其实改的是缓存。