第三十章 详情页模板
详情页渲染一篇文章;按骨架讲面包屑、章节树、正文自动处理与上一章/下一章,并给出三个容易踩的坑。
这一章解决什么
详情页是「一篇文章」的页面,地址形如 /show/?id=314。手册里的每一章都是详情页,全部由 theme/576so/model/show/Ju7CMS1.html 渲染。改这个文件,全书 46 篇一起变;只想改某一章的版面,应该改那一章的正文,而不是改模板。
| 页面 | 地址形态 | 谁决定内容 |
|---|---|---|
| 手册章节 | /show/?id=314 | content 表里 id=314 的那一行 |
| 普通文章 | /show/?id=123 | 同上,栏目决定用哪个模板 |
| 单页栏目 | /list/?classid=16 | 栏目的 content,走列表链路,不是本章 |
栏目绑定哪一个详情页模板,存在 category.cate_show 列(后台:设置 → 栏目管理 → 编辑 → 内容模板)。手册七个栏目都指向 model/show/Ju7CMS1.html。
一次请求里发生什么
- 按
id从content表取一行,只认islock=1;查不到或有草稿标记就直接 404。 - 把这一行的每一列都变成同名模板变量:
title、content、createdate、lastupdate、keywords、descriptions、pic、tags,以及字段管理里加的自定义列(手册用的Ju7_Other)。 - 按
classid取栏目行,补出classname、catedesc、cateurl、sonid、followid、modeid等变量。 - 处理正文:多页分隔符、正文图补
alt/loading/宽高、内链首处替换、图片防盗链等,都在这一步完成。 - 整份模板编译执行,输出 HTML。
正文里用编辑器插入的分页符会被拆成多页,第 2 页起地址会增加 &page=2。分页逻辑只认正文里的分隔标记,不占模板变量。
模板骨架
Ju7CMS1 详情页共 65 行,结构与列表页同源,差异在正文区和翻页区。
文档头与页头
<title>{seotitle}</title><meta name="keywords" content="{seokey}" /><meta name="description" content="{seodesc}" />{andvar:include("../../seo/head.html")}<body class="layout-center docs-chapter">{andvar:include("../../head[langid].html")}详情页的 title 不带品牌后缀,因为文章的 SEO 标题通常已经写全;列表页才统一补「-聚企互联」。docs-chapter 这个类名只做一件事:手机版把左侧章节树收起来,免得多占一屏。
面包屑
<a href="{webroot}">{c_name}首页</a><a href="{andvar.getcateurl(langid,classid,ismobile)}">{catename}</a><span>{title}</span>andvar.getcateurl 是公共方法,按当前运行模式拼出栏目地址:动态模式给 /list/?classid=21,静态模式给实际的 .html 路径。不要在模板里手写 /list/?classid=,否则切换运行模式后链接会全部失效。
两栏与章节树
<div class="docs-layout"> <aside class="docs-side" aria-label="手册章节目录"> {andvar:rs top="0" field="autoid,cateid,followid,catename,catelang,htmldir,isnav,ismark,ismenu,ordnum" table="category" where="followid=10 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 {andvar.is_current(id,$rsm[id],"active")}" href="{$rsm[link]}">{$rsm[title]}</a> {/andvar:rsm} {/andvar:rs} </aside>章节页的左侧目录固定显示整本书(followid=10),当前章用 andvar.is_current 加 active 类高亮:三个参数依次是「当前 id、列表里的 id、命中时输出的类名」。这个方法的第三个参数可省,省了默认输出 active。
正文区
<article class="docs-main"> <h1 class="docs-main__title">{title}</h1> <p class="docs-main__meta">发布于 {andvar.getdate(createdate,"-",1)} · 更新于 {andvar.getdate(lastupdate,"-",1)}</p> <div class="docs-body field-name-body"> {Ju7_Other} {if seodesc<>""}<p class="docs-body__lede">{seodesc}</p>{/if} {content} </div>| 写法 | 意思 |
|---|---|
{Ju7_Other} | 自定义字段「Ju7_Other」的原样输出。手册用它放章节开头的一句话导语,正文仍在编辑器里 |
{content} | 文章正文,HTML 原样输出,不做二次转义 |
{seodesc} | SEO 描述。这里当导语用,所以为空时整段不输出 |
{andvar.getdate(…)} | 日期格式化,第三个参数 1 表示只取年月日 |
{content} 里如果写了模板花括号,会被当作模板表达式执行。手册正文里要显示 {andvar:rs …} 这类示例时,必须写成实体 {、},否则示例会被编译掉,页面上直接少一段。
上一章 / 下一章
{andvar:rs field="id,classid,isurl,url,languages,title" top="1" table="content" where="classid in(select cateid from [category] where followid='[followid]') and islock=1 and ordnum<[ordnum]" order="ordnum desc,id desc"} <a href="{$rs[link]}">上一章:{$rs[title]}</a>{/andvar:rs}{andvar:rs field="id,classid,isurl,url,languages,title" top="1" table="content" where="classid in(select cateid from [category] where followid='[followid]') and islock=1 and ordnum>[ordnum]" order="ordnum asc,id asc"} <a href="{$rs[link]}">下一章:{$rs[title]}</a>{/andvar:rs}翻页按 ordnum(章节序号)而不是 id:手册的 46 篇文章分布在六个分组里,按 id 翻只能在本组内前后走,跨组就断。子查询 select cateid from [category] where followid='[followid]' 取「当前栏目的兄弟栏目集合」,[followid] 在章节页等于 10,于是六个分组全部纳入范围。
如果某个栏目没有兄弟栏目,子查询只返回它自己,翻页在本栏目内工作,不会报错。想改成「只在同组内翻」,把子查询换成 classid=[classid],或直接比较 classid。
正文会自动做哪些处理
| 处理 | 触发条件 | 结果 |
|---|---|---|
| 补图片 alt | <img> 没有 alt | 用当前页 seotitle 填,多图时追加序号 |
| 懒加载 | 正文里的 <img> | 补 loading="lazy" 与 decoding="async" |
| 站内链接 | 后台「文章内链」有匹配词 | 正文里第一次出现该词时替换成链接,只替换一处 |
| 多页正文 | 编辑器里插入分页符 | 拆成 &page=N 的多页,自动生成上一页/下一页 |
| 手机图 | 字段带 _m 后缀且有值 | 手机端优先用 _m 列的值覆盖同名字段 |
这些处理都在 lib/show.asp 里,模板只负责取变量。所以即使模板换成另一套,正文的 alt、懒加载与内链行为仍然生效。
三个容易踩的坑
{if}里比较什么。seodesc<>""是字符串比较;islock=1是数字比较。把布尔值ismobile直接写进 SQL 会得到True,Access 不认识。- 正文里的花括号。见上面的警告。代码示例一律用实体,或者放进
<pre><code>里也照样会被编译——实体转义是唯一可靠的办法。 - 缓存。改模板后要等编译缓存失效;改文章则只影响整页缓存,到缓存管理清一次即可。若是「编辑页存了但前台没变」,先分清楚是模板缓存、整页缓存还是浏览器缓存,见第三十三章。
接下来怎么读
- 页头页脚是怎么被引用的:第三十一章,公共片段。
- 模板语法还没看熟:第二十八章。
- 想换一套主题:第三十五章,主题切换与手机版。
微信扫码聊需求