第二十九章 列表页模板
列表页负责一屏列出多篇内容;按模板骨架逐段讲解分页写法与常见改法,手册的七个页面就是这份模板的实例。
这一章解决什么
列表页是「一屏列出多篇内容」的页面:栏目列表、搜索结果、手册的分组页都算。本手册自己就是一套列表页模板的实例——手册首页(栏目 10)和六个分组栏目(21–26)全部绑定同一个文件 theme/576so/model/list/Ju7CMS1.html,改这一个文件,七个页面同时变。
| 你看到的页面 | 由谁渲染 | 数据来自 |
|---|---|---|
/list/?classid=10 手册首页 | model/list/Ju7CMS1.html | 栏目 10 的字段 + 正文 content |
/list/?classid=21 一、入门与环境 | 同一个文件 | 栏目 21 的字段,左侧列出 21 下的六章 |
/list/?classid=34 某个服务栏目 | 栏目自己指定的模板(如 FW1) | 该栏目的字段与内容列表 |
栏目用哪个模板,存在 category 表的 listtemp 列里(后台:设置 → 栏目管理 → 编辑栏目 → 列表模板)。模板文件必须是主题目录下的相对路径,例如 model/list/Ju7CMS1.html;留空则用系统默认规则。
一次请求里发生什么
- 浏览器请求
/list/?classid=21,lib/list.asp接到请求。 - 按
classid从category表读出栏目行:栏目名、父级、子级串、SEO 三件套、模板路径等。 - 校验:栏目不存在、栏目被锁定、模板文件不存在,任一不成立就转 404,不再往下走。
- 设置页面级变量(见下表),然后整份模板编译成一个 VBScript 单元并执行。
- 模板里的
{andvar:rs}标签在编译期被改写成取数据的代码,执行期才真正查库、拼 HTML。
| 列表页可用变量 | 含义 | 注意 |
|---|---|---|
{classid} {classname} | 当前栏目 ID 与栏目名 | 数字 ID 参与 SQL 拼接,模板里不要加引号 |
{seotitle} {seokey} {seodesc} | 栏目的标题、关键词、描述 | 为空时系统按栏目名回退 |
{content} | 栏目正文(栏目编辑页的「内容」) | 不是文章正文;分组页的导语写在这里 |
{sonid} {followid} | 本栏目的子栏目串、父栏目串 | 本质是逗号串,别当数字用 |
{langid} {ismobile} | 当前语言、是否手机端 | ismobile 是布尔值,别直接拼进 SQL |
{webroot} {skins} | 站点根、当前主题目录 | 分站的 webroot 是 /LINHAI/ 这类子目录 |
列表页没有 {catedesc} 这个变量。想显示栏目描述,用 {seodesc};把 {catedesc} 写进模板不会报错,只会原样输出成文字或空串,很容易以为「描述没填」。
模板骨架逐段看
Ju7CMS1 一共 36 行,可以切成七段。下面按顺序给出真实写法(花括号为避免被模板引擎执行,已转成实体)。
第一段:文档头
<!DOCTYPE html><html lang="{seo_langcode}"><head><title>{seotitle}-{c_name}聚企互联</title><meta name="keywords" content="{seokey}" /><meta name="description" content="{seodesc}" /></head>{andvar:include("../../seo/head.html")}seo/head.html 负责 robots、viewport、canonical、Open Graph、结构化数据和 css/js 引用;标题与三条 meta 由本模板自己写,因为列表页和详情页的拼法不同。
第二段:页头与面包屑
{andvar:include("../../head[langid].html")}<main role="main">head1.html 是中文站页头,[langid] 会在编译期换成 1,于是载入 head1.html。多语言站会同时存在 head1.html、head2.html,模板不用为语言分支写判断。
第三段:两栏布局
<div class="docs-layout"> <aside class="docs-side" aria-label="手册章节目录"> ... </aside> <div class="docs-main"> ... </div></div>docs-layout 是两栏网格,docs-side 是左侧章节树,docs-main 是右侧正文。手机版由 ju7.css 把侧栏收成可折叠块,窄屏不需要另做模板。
第四段:左侧章节树(本章最重要)
{andvar:rs top="0" field="autoid,cateid,followid,catename,catelang,htmldir,isnav,ismark,ismenu,ordnum" table="category" where="followid=[classid] and catelang=[langid]" order="ordnum,autoid" var="cateid:cateid"} <p class="docs-side__book">{$rs[catename]}</p> {andvar:rsm field="id,classid,isurl,url,languages,title,descriptions,ordnum" table="content" where="classid=[cateid] and islock=1" order="ordnum,id"} <a class="docs-side__item" href="{$rsm[link]}" title="{$rsm[title]}">{$rsm[title]}</a> {/andvar:rsm}{/andvar:rs}这是两层循环,读法是:
- 外层从
category表取「父栏目 = 当前栏目」的全部子栏目,按ordnum,autoid排序,就是六个分组。 var="cateid:cateid"把当前行的cateid赋给同名变量cateid。冒号左边是变量名,右边是字段名;写成var="gid:cateid"就得到{gid}。- 内层用
[cateid]引用外层变量,取该分组下所有已发布内容。方括号是编译期文本替换,不是 SQL,所以where里直接写[cateid]即可。 - 内层标签叫
rsm,字段写成$rsm[...],与外层rs区分。嵌套标签必须改名,同名会互相覆盖。
手册首页(classid=10)和六个分组页用的是同一段代码:在首页上外层取到 10 的六个子栏目;在 21 号分组页上,外层同样写 followid=[classid] 就会变成「21 的子栏目」——而 21 下面没有子栏目,侧栏会空。本手册的做法是把 where 固定成 followid=10,让所有分组页都显示完整目录;普通栏目照原写法即可。
第五段:右侧标题与导语
<h1 class="docs-main__title">{classname}</h1><p class="docs-main__lede">……</p>{if seodesc<>""}<div class="docs-intro">{seodesc}</div>{/if}<div class="field-name-body">{content}</div>{if}/{/if} 是编译期条件,条件成立才保留中间那段 HTML。<> 表示「不等于」;字符串要用引号包住。这里的意思是:栏目描述非空才输出导语框,避免出现空盒子。
第六段:内容列表
手册首页的正文直接写在 {content} 里(也就是栏目编辑页填的 HTML),因为「六大部分」是一张手工维护的总览表,不适合用循环生成。普通栏目要列出文章时,在 docs-main 里再放一个 {andvar:rs},例如:
{andvar:rs top="20" field="id,classid,isurl,url,languages,title,descriptions,pic,createdate" table="content" where="classid=[classid] and islock=1" order="ordnum,id" pagesize="10" page="1"} <article class="card"> <a href="{$rs[link]}">{$rs[title]}</a> <p>{$rs[descriptions]}</p> </article>{/andvar:rs}<nav class="pager">{showpage}</nav>| 属性 | 取值示例 | 说明 |
|---|---|---|
top | 20、0 | 最多取多少条;0 表示不限 |
pagesize | 10 | 每页条数,写了它才会分页 |
page | 1 或 [page] | 当前页;列表页通常由地址栏的 page 决定 |
order | ordnum,id | ordnum 是后台排序,升序 |
iscache | true | 块级缓存,适合页脚、导航这类不常变的数据 |
第七段:页脚
{andvar:include("../../foot[langid].html")}</body></html>页脚里含咨询弹窗与底部导航,见第三十一章。
分页怎么写
{showpage} 会展开成上一页、页码、下一页的完整 HTML,它读的是同一次请求里最后一个带 pagesize 的 {andvar:rs}。{andvar:page.showpage} 是同一份的输出别名。
分页链接会带上当前地址的其他查询参数,所以 /list/?classid=34&page=2 不会把 classid 丢掉。翻到不存在的页码时,系统会把页码钳回最后一页,而不是给出空白页(详见第四十四章)。
常见改法
| 想做的事 | 怎么改 |
|---|---|
| 列表多显示一个字段 | 在 field="…" 里加列名,正文用 {$rs[列名]} 取值 |
| 只显示带图的文章 | where 追加 and pic<>'' |
| 按发布时间倒序 | order="createdate desc,id desc" |
| 侧栏只显示六个分组、不列章节 | 删掉内层 {andvar:rsm}…{/andvar:rsm} 整块 |
| 分组页也显示章节树 | 把侧栏 where 固定成 followid=10(本手册做法) |
| 换一套版式 | 复制模板改名,再到栏目编辑页把「列表模板」指向新文件 |
三个容易踩的坑
- 整份模板是一个编译单元。花括号配错、
{if}没闭合、把内联 JS 的{ var x }写进模板,都可能让整页变成 0 字节或 500。改完先看页面是否完整渲染,再检查errlog。 - 改模板要防缓存。模板编译产物在
cache/theme/,系统会按模板与分片的修改时间自动失效;但整页缓存cache/html/与块缓存cache/cache/不吃这一套。改完样式或数据不生效,到「设置 → 缓存管理」清理。 - 别把
where写成逻辑陷阱。and两侧都要写完整条件;islock=1表示已发布,漏了它会把草稿也列出来(草稿与发布的关系见第十五章)。
接下来怎么读
- 要改文章页的版面:下一章第三十章,详情页模板。
- 想知道页头页脚为什么能一处改全站:第三十一章,公共片段。
- 不熟悉花括号语法:回第二十九章之前先看第二十八章的语法速查。
微信扫码聊需求