第二十九章 列表页模板

Ju7CMS 用户手册 · 发布于 2026-10-27 · 更新于 2026-10-27

列表页负责一屏列出多篇内容;按模板骨架逐段讲解分页写法与常见改法,手册的七个页面就是这份模板的实例。

这一章解决什么

列表页是「一屏列出多篇内容」的页面:栏目列表、搜索结果、手册的分组页都算。本手册自己就是一套列表页模板的实例——手册首页(栏目 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;留空则用系统默认规则。

一次请求里发生什么

  1. 浏览器请求 /list/?classid=21,lib/list.asp 接到请求。
  2. 按 classid 从 category 表读出栏目行:栏目名、父级、子级串、SEO 三件套、模板路径等。
  3. 校验:栏目不存在、栏目被锁定、模板文件不存在,任一不成立就转 404,不再往下走。
  4. 设置页面级变量(见下表),然后整份模板编译成一个 VBScript 单元并执行。
  5. 模板里的 {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}

这是两层循环,读法是:

  1. 外层从 category 表取「父栏目 = 当前栏目」的全部子栏目,按 ordnum,autoid 排序,就是六个分组。
  2. var="cateid:cateid" 把当前行的 cateid 赋给同名变量 cateid。冒号左边是变量名,右边是字段名;写成 var="gid:cateid" 就得到 {gid}。
  3. 内层用 [cateid] 引用外层变量,取该分组下所有已发布内容。方括号是编译期文本替换,不是 SQL,所以 where 里直接写 [cateid] 即可。
  4. 内层标签叫 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>
属性取值示例说明
top20、0最多取多少条;0 表示不限
pagesize10每页条数,写了它才会分页
page1 或 [page]当前页;列表页通常由地址栏的 page 决定
orderordnum,idordnum 是后台排序,升序
iscachetrue块级缓存,适合页脚、导航这类不常变的数据

第七段:页脚

{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(本手册做法)
换一套版式复制模板改名,再到栏目编辑页把「列表模板」指向新文件

三个容易踩的坑

  1. 整份模板是一个编译单元。花括号配错、{if} 没闭合、把内联 JS 的 { var x } 写进模板,都可能让整页变成 0 字节或 500。改完先看页面是否完整渲染,再检查 errlog。
  2. 改模板要防缓存。模板编译产物在 cache/theme/,系统会按模板与分片的修改时间自动失效;但整页缓存 cache/html/ 与块缓存 cache/cache/ 不吃这一套。改完样式或数据不生效,到「设置 → 缓存管理」清理。
  3. 别把 where 写成逻辑陷阱。and 两侧都要写完整条件;islock=1 表示已发布,漏了它会把草稿也列出来(草稿与发布的关系见第十五章)。

接下来怎么读

  1. 要改文章页的版面:下一章第三十章,详情页模板。
  2. 想知道页头页脚为什么能一处改全站:第三十一章,公共片段。
  3. 不熟悉花括号语法:回第二十九章之前先看第二十八章的语法速查。