- 获取所有顶级栏目:使用
{dede:channelartlist}标签,这个标签专门用于获取顶级栏目。 - 循环获取每个顶级栏目下的子栏目:在
{dede:channelartlist}的循环体内,使用{dede:type}标签来获取当前顶级栏目([field:id])下的所有子栏目。
下面我将为您提供两种最常用和最稳定的方法:使用 channelartlist 和 type,以及使用 SQL 查询,强烈推荐第一种方法,因为它更符合 DedeCMS 的模板设计理念,也更易于维护。

(图片来源网络,侵删)
使用 {dede:channelartlist} 和 {dede:type} 标签(推荐)
这是最标准、最灵活的方法。channelartlist 负责循环顶级栏目,type 负责在循环内部获取子栏目。
第1步:准备模板文件
在你的模板文件夹中(通常是 /templets/default/),创建一个新的模板文件,nav_sub.htm。
第2步:编写模板代码
将以下代码复制到 nav_sub.htm 文件中,代码中包含了详细的注释,方便你理解每个部分的作用。
{dede:channelartlist typeid='0' currentstyle='active'}
<li class="nav-item">
<!--
顶级栏目链接
[field:typelink/] 是顶级栏目的链接地址
[field:typename/] 是顶级栏目的名称
currentstyle 属性用于设置当前栏目所在项的样式,这里我们给一个 'active' 类
-->
<a href="[field:typelink/]" class="nav-link [field:global name=typeid runphp='yes']@me=(@me=='[field:id]')?'active':'';[/field:global]">
[field:typename/]
</a>
<!--
子栏目列表容器
使用 {dede:sql} 或 {dede:channel} 来获取子栏目
这里我们使用 {dede:channel},因为它更简洁,专门用于获取子栏目
-->
<ul class="subnav-list">
{dede:channel type='son' noself='yes'}
<!--
子栏目链接
[field:typelink/] 是子栏目的链接
[field:typename/] 是子栏目的名称
-->
<li>
<a href="[field:typelink/]">[field:typename/]</a>
</li>
{/dede:channel}
</ul>
</li>
{/dede:channelartlist}
第3步:参数说明
-
{dede:channelartlist typeid='0' currentstyle='active'}
(图片来源网络,侵删)typeid='0':表示获取所有顶级栏目,如果你想指定只获取某些顶级栏目,可以写成typeid='1,3,5',1, 3, 5 是顶级栏目的 ID。currentstyle='active':这是一个非常有用的属性,当用户访问某个顶级栏目或其下的任意页面时,这个顶级栏目对应的<li>会自动应用active样式,你可以通过 CSS 来定义active类的样式,例如改变背景色或文字颜色。
-
{dede:channel type='son' noself='yes'}type='son':这是关键参数,表示获取当前顶级栏目(由外层channelartlist提供)的所有直接子栏目。noself='yes':表示不获取栏目本身,因为我们已经在上面显示了顶级栏目的链接,这里就不需要重复显示了,如果你想去掉这个属性,顶级栏目本身也会出现在子列表中。
第4步:在页面中调用
在你需要显示这个嵌套导航栏的页面模板文件(head.htm)中,使用 include 标签来引入 nav_sub.htm。
<div class="main-nav">
<ul class="nav-list">
{dede:include filename='nav_sub.htm'/}
</ul>
</div>
第5步:添加 CSS 样式
为了让子栏目列表在鼠标悬停时才显示,你需要添加一些 CSS 样式,在你的 CSS 文件中添加以下代码:
/* 主导航列表样式 */
.nav-list {
list-style: none;
margin: 0;
padding: 0;
display: flex; /* 使用 flex 布局让导航项横向排列 */
}
.nav-item {
position: relative; /* 关键:为子菜单定位提供参考 */
}
.nav-link {
display: block;
padding: 10px 15px;
text-decoration: none;
color: #333;
}
/* 当前栏目激活状态 */
.nav-link.active {
color: #ff6600; /* 示例:激活状态为橙色 */
font-weight: bold;
}
/* 默认隐藏子菜单 */
.subnav-list {
list-style: none;
margin: 0;
padding: 0;
position: absolute; /* 绝对定位,相对于父级 .nav-item */
top: 100%; /* 显示在父级链接的下方 */
left: 0;
background-color: #fff;
border: 1px solid #ddd;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
display: none; /* 默认隐藏 */
z-index: 1000;
}
/* 鼠标悬停在父级上时,显示子菜单 */
.nav-item:hover .subnav-list {
display: block; /* 鼠标悬停时显示 */
}
/* 子菜单链接样式 */
.subnav-list li a {
display: block;
padding: 8px 15px;
text-decoration: none;
color: #555;
white-space: nowrap; /* 防止文字换行 */
}
.subnav-list li a:hover {
background-color: #f0f0f0;
}
使用 SQL 查询(适用于复杂场景)
如果你需要对栏目数据进行更复杂的筛选和处理,可以使用 {dede:sql} 标签直接执行 SQL 查询,这种方法更灵活,但可读性稍差。
模板代码示例
{dede:sql sql="SELECT id,typename,typedir FROM `dede_arctype` WHERE reid=0 AND ishidden<>1 ORDER BY sortrank ASC"}
<li class="nav-item">
<a href="[field:typedir function='str_replace("{cmspath}", "", "@me")'/]" class="nav-link">
[field:typename/]
</a>
<ul class="subnav-list">
{dede:sql sql="SELECT id,typename,typedir FROM `dede_arctype` WHERE reid=[field:id/] AND ishidden<>1 ORDER BY sortrank ASC"}
<li><a href="[field:typedir function='str_replace("{cmspath}", "", "@me")'/]">[field:typename/]</a></li>
{/dede:sql}
</ul>
</li>
{/dede:sql}
代码说明
- 外层循环:
{dede:sql sql="..."}查询reid=0的所有栏目,reid为 0 的就是顶级栏目。 - 顶级栏目链接:
[field:typedir/]返回的是包含{cmspath}的完整路径,我们使用function='str_replace("{cmspath}", "", "@me")'来移除它,得到正确的相对链接。 - 内层循环:在
channelartlist循环中,我们使用[field:id/]获取当前顶级栏目的 ID,并将其作为参数传递给内层的 SQL 查询,WHERE reid=[field:id/]就实现了获取子栏目的目的。 - 子栏目链接:同样处理
typedir字段以获得正确链接。
总结与建议
| 特性 | 方法一 (channelartlist + type) |
方法二 (sql 查询) |
|---|---|---|
| 易用性 | 高,标签化,符合 DedeCMS 模板哲学 | 低,需要写 SQL 语句,容易出错 |
| 可维护性 | 高,代码清晰,结构分明 | 低,SQL 与 HTML 混合,不易阅读和修改 |
| 灵活性 | 中等,能满足大部分需求 | 高,可以执行任何复杂的 SQL 逻辑 |
| 性能 | 良好 | 良好,但要注意 SQL 查询效率 |
对于绝大多数情况,请优先选择方法一,它更安全、更易于维护,并且是 DedeCMS 社区普遍推荐的实践方式。
只有在方法一无法满足你的特殊需求时(需要根据其他表的数据来筛选栏目),才考虑使用方法二。
