下面我将为您提供最常用、最清晰的两种实现方法:传统嵌套法 和 递归模板法,递归法在处理无限层级时更为优雅和强大。

准备工作:栏目结构
请确保您的后台栏目结构是清晰的,一个典型的三级栏目结构如下:
- 一级栏目 (ID: 1): 公司简介
- 二级栏目 (ID: 2): 发展历程
- 三级栏目 (ID: 3): 2025年
- 三级栏目 (ID: 4): 2025年
- 二级栏目 (ID: 5): 企业文化
- 三级栏目 (ID: 6): 使命愿景
- 三级栏目 (ID: 7): 核心价值观
- 二级栏目 (ID: 2): 发展历程
- 一级栏目 (ID: 8): 产品中心
- 二级栏目 (ID: 9): 软件产品
- 三级栏目 (ID: 10): ERP系统
- 三级栏目 (ID: 11): CRM系统
- 二级栏目 (ID: 12): 硬件产品
- 三级栏目 (ID: 13): 服务器
- 三级栏目 (ID: 14): 存储设备
- 二级栏目 (ID: 9): 软件产品
我们将基于这个结构来编写代码。
传统嵌套法(适用于固定层级)
这种方法最直观,适合明确知道只需要三级栏目结构的情况,代码直接写在模板文件里。
适用场景:栏目层级固定,结构不会轻易变化。
代码示例:
<ul class="top-nav">
<!-- 循环一级栏目 -->
{dede:channel type='top' row='8'}
<li>
<a href="[field:typelink/]">[field:typename/]</a>
<!-- 循环当前一级栏目下的二级栏目 -->
{dede:channel type='son' typeid='[field:id]' row='10'}
<ul class="sub-nav">
<li>
<a href="[field:typelink/]">[field:typename/]</a>
<!-- 循环当前二级栏目下的三级栏目 -->
{dede:channel type='son' typeid='[field:id]' row='10'}
<ul class="third-nav">
<li><a href="[field:typelink/]">[field:typename/]</a></li>
</ul>
{/dede:channel}
</li>
</ul>
{/dede:channel}
</li>
{/dede:channel}
</ul>
代码解析:
-
{dede:channel type='top' row='8'}:type='top': 获取所有顶级栏目(一级栏目)。row='8': 限制显示8个一级栏目。- 循环输出所有一级栏目。
-
{dede:channel type='son' typeid='[field:id]' row='10'}:type='son': 获取指定typeid的子栏目。typeid='[field:id]': 这是关键!这里的[field:id]是外层循环(一级栏目)的栏目ID,这句代码的意思是“获取当前这个一级栏目下的所有二级栏目”。- 嵌套在一级栏目循环内部,循环输出所有二级栏目。
-
最内层
{dede:channel}:- 同样使用
type='son'和typeid='[field:id]'。 - 这里的
[field:id]是中间层循环(二级栏目)的栏目ID,这句代码的意思是“获取当前这个二级栏目下的所有三级栏目”。 - 嵌套在二级栏目循环内部,循环输出所有三级栏目。
- 同样使用
递归模板法(推荐,适用于无限层级)
递归方法更灵活,可以轻松应对四、五级甚至更多层级的栏目,并且代码结构更清晰,易于维护,它通过一个单独的模板文件来循环子栏目,然后自身调用自己。
适用场景:栏目层级不确定或可能动态增加,希望代码更具扩展性。
实现步骤:
第1步:创建子栏目循环模板文件
在您的模板目录(通常是 /templets/default/)下,新建一个文件,subnav.htm。
(subnav.htm)**:
<ul class="sub-level">
<!-- 循环当前层级的所有子栏目 -->
{dede:channel type='son' typeid='[field:global name=typeid/]' row='20'}
<li>
<a href="[field:typelink/]">[field:typename/]</a>
<!-- 递归调用:如果当前栏目还有子栏目,则再次调用本模板 -->
{dede:if condition="(@me['soncount'] > 0)"}
{dede:include filename="subnav.htm" typeid="[field:id]"/}
{/dede:if}
</li>
{/dede:channel}
</ul>
代码解析:
-
{dede:channel type='son' typeid='[field:global name=typeid/]' row='20'}:typeid='[field:global name=typeid/]': 这里我们使用全局变量typeid来传递父级栏目的ID,这个变量会在调用模板时被指定。- 这句代码会获取传递进来的
typeid对应的所有子栏目。
-
{dede:if condition="(@me['soncount'] > 0)"}:- 这是一个条件判断。
@me['soncount']是当前栏目的子栏目数量。 soncount大于 0,说明当前栏目还有下级栏目,就执行递归调用。
- 这是一个条件判断。
-
{dede:include filename="subnav.htm" typeid="[field:id]"/}:- 这是递归的核心,它包含
subnav.htm文件本身。 typeid="[field:id]": 这是关键!它将当前循环到的栏目的ID作为新的typeid传递给下一次的subnav.htm调用,这样,下一次循环就会查找这个新ID的子栏目,形成递归。
- 这是递归的核心,它包含
第2步:在主模板中调用递归
在你的主栏目模板文件(head.htm 或 nav.htm)中,你可以这样调用它来生成任意层级的导航。
主模板调用示例:
<ul class="main-nav">
<!-- 循环一级栏目 -->
{dede:channel type='top' row='8'}
<li>
<a href="[field:typelink/]">[field:typename/]</a>
<!-- 调用递归模板,传入一级栏目的ID -->
{dede:include filename="subnav.htm" typeid="[field:id]"/}
</li>
{/dede:channel}
</ul>
流程说明:
- 主模板循环一级栏目。
- 当循环到某个一级栏目(公司简介”,ID=1)时,执行
{dede:include filename="subnav.htm" typeid="1"}。 subnav.htm被调用,typeid为 1,它查找 ID=1 的所有子栏目(即“发展历程”和“企业文化”)。- 循环到“发展历程”(ID=2)时,因为它的
soncount> 0,所以它会再次调用subnav.htm,但这次传入的typeid是 2。 - 新的
subnav.htm调用查找 ID=2 的所有子栏目(即“2025年”和“2025年”)。 - 因为“2025年”和“2025年”没有子栏目(
soncount=0),所以不再递归调用。 - 这个过程会一直持续,直到所有层级的栏目都被遍历完毕。
总结与对比
| 特性 | 传统嵌套法 | 递归模板法 |
|---|---|---|
| 代码结构 | 所有层级写在一个文件里,代码冗长。 | 结构清晰,主模板和子模板分离。 |
| 扩展性 | 差,增加第四级需要再嵌套一层,代码会变得很乱。 | 极佳,可以轻松处理无限层级,无需修改代码。 |
| 维护性 | 差,修改样式或逻辑需要在一大段嵌套代码中寻找。 | 好,修改子栏目的样式或逻辑只需编辑 subnav.htm。 |
| 适用场景 | 栏目层级固定且较少(如最多三级)。 | 栏目结构复杂、层级不确定,或需要长期维护的项目。 |
强烈推荐使用方法二(递归模板法),它代表了 DedeCMS 模板开发中更专业、更灵活的实践方式,能让你未来的维护工作事半功倍。
