核心思路
无论使用哪种方法,核心逻辑都是一样的:

- 后端准备:在PHP模板中,我们需要为每个一级栏目都生成一个包含其所有二级栏目列表的HTML结构,但这个二级列表在初始状态下是隐藏的(通过CSS的
display: none;实现)。 - 前端交互:当用户点击一级栏目时,通过JavaScript来触发一个事件,这个事件会做两件事:
- 隐藏当前所有已展开的二级栏目列表(如果有的话)。
- 显示被点击的一级栏目对应的二级栏目列表。
纯CSS + HTML (最简洁,推荐首选)
这种方法利用了HTML的 target 伪类,不需要编写任何JavaScript代码,非常简洁和优雅。
优点
- 代码量少:无需JS,易于维护。
- 性能好:没有额外的JavaScript解析和执行开销。
- SEO友好:即使没有JS,链接也是有效的。
缺点
- URL会变化:点击后,URL的末尾会加上一个锚点(如
#nav1),虽然不影响功能,但可能会让一些用户感到困惑。 - 交互体验略有不同:无法通过点击外部区域来关闭菜单。
实现步骤
第一步:修改一级栏目循环模板
找到你网站首页或栏目页的一级栏目循环代码,通常在 head.htm 或 index.htm 文件中,默认代码可能长这样:
{dede:channel type='top' currentstyle="<li class='thisclass'><a href='~typelink~'>~typename~</a></li>"}
<li><a href='[field:typelink/]'>[field:typename/]</a></li>
{/dede:channel}
你需要将其修改为下面的结构,这里我们为每个一级栏目链接添加一个 id,并创建一个隐藏的二级栏目列表容器。

{dede:channel type='top' currentstyle="<li class='thisclass'><a href='~typelink~' id='~id~'>~typename~</a></li>"}
<li>
<!--
为一级栏目链接添加一个唯一的ID,'nav1', 'nav2'...
这个ID将作为后面二级列表容器的锚点。
-->
<a href="[field:typelink/]" id="nav[field:id/]">[field:typename/]</a>
<!--
创建一个二级栏目列表容器。
注意:class="sub-menu" 和 style="display: none;" 是关键。
它的id必须和上面一级链接的id对应,并在末尾加上'-content'。
-->
<ul class="sub-menu" id="nav[field:id/]-content" style="display: none;">
{dede:channel type='son' noself='yes'}
<li><a href="[field:typelink/]">[field:typename/]</a></li>
{dede:channel}
</ul>
</li>
{/dede:channel}
代码解释:
id="nav[field:id/]":为每个一级链接设置一个唯一的ID,如nav1,nav2。[field:id]是织梦的栏目ID字段。class="sub-menu":给二级列表一个类名,方便我们用CSS来控制它的样式。id="nav[field:id/]-content":为二级列表容器设置一个与一级链接ID对应的ID,如nav1-content,nav2-content,这是target选择器能工作的关键。style="display: none;":初始状态下,二级列表是隐藏的。{dede:channel type='son' noself='yes'}:这是一个嵌套的循环,用于获取当前一级栏目下的所有二级栏目。type='son'表示获取子栏目。
第二步:添加CSS样式
在你的CSS文件(通常是 /templets/你的模板/style/ 目录下的文件)中,添加以下样式:
/* 默认隐藏所有二级菜单 */
.sub-menu {
display: none;
}
/* 当一级链接被点击后(URL变为 #nav1-content),对应的二级菜单就会显示 */
/* 当URL是 http://你的网站/#nav1-content 时,id为nav1-content的ul就会显示 */
#nav1-content:target,
#nav2-content:target,
#nav3-content:target,
#nav4-content:target {
display: block;
}
/* (可选) 美化样式,让下拉菜单更美观 */
.sub-menu {
list-style: none;
padding-left: 0;
margin-top: 10px;
/* 可以添加背景色、边框等 */
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
}
.sub-menu li {
padding: 8px 15px;
}
.sub-menu li a {
color: #333;
text-decoration: none;
display: block;
}
.sub-menu li a:hover {
background-color: #eee;
}
注意:CSS选择器 #nav1-content:target, #nav2-content:target... 需要你根据网站实际的栏目数量来写全,如果你的栏目很多,可以考虑用JavaScript来动态添加样式,但这样就失去了纯CSS的意义,对于大多数网站(栏目数<20),手动写全是完全可行的。
至此,方法一就完成了,刷新你的网站,点击一级栏目,应该就能看到对应的二级栏目了。
JavaScript + CSS (更灵活,URL不变)
如果你不希望URL因为点击而改变,或者需要更复杂的交互(如点击外部关闭菜单),那么JavaScript是更好的选择。
优点
- URL保持不变:用户体验更好。
- 交互灵活:可以实现更复杂的逻辑,如点击外部关闭菜单、添加动画效果等。
缺点
- 需要编写和引入JS:比纯CSS方法多一步操作。
- 依赖JavaScript:如果用户禁用了JS,这个功能将失效(但一级栏目的链接仍然有效)。
实现步骤
第一步:修改一级栏目循环模板
这一步和方法一的第一步基本一样,但我们可以去掉 style="display: none;",因为我们将用JS来控制显示和隐藏。
{dede:channel type='top' currentstyle="<li class='thisclass'><a href='~typelink~' data-id='~id~'>~typename~</a></li>"}
<li>
<!--
这里我们使用 data-id 属性来存储栏目ID,这是HTML5的标准做法,比在JS中解析ID更可靠。
-->
<a href="[field:typelink/]" data-id="nav[field:id/]">[field:typename/]</a>
<ul class="sub-menu" id="nav[field:id/]-content">
{dede:channel type='son' noself='yes'}
<li><a href="[field:typelink/]">[field:typename/]</a></li>
{dede:channel}
</ul>
</li>
{/dede:channel}
代码变化:
- 我们将
id换成了data-id="nav[field:id/]",并将data-id赋给了一级链接,而不是二级列表容器,这是一种更清晰的JS交互模式。 - 二级列表容器的
id依然保留,但移除了内联的display: none;。
第二步:添加CSS样式
CSS只需要设置二级列表的默认隐藏状态即可。
/* 默认隐藏所有二级菜单 */
.sub-menu {
display: none;
/* (可选) 添加美化和过渡动画 */
list-style: none;
padding-left: 0;
margin-top: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
/* 添加平滑过渡效果 */
transition: all 0.3s ease-in-out;
}
.sub-menu li {
padding: 8px 15px;
}
.sub-menu li a {
color: #333;
text-decoration: none;
display: block;
}
.sub-menu li a:hover {
background-color: #eee;
}
第三步:创建并引入JavaScript文件
-
创建JS文件:在你的模板目录下(
/templets/你的模板/js/)创建一个新的文件,命名为dropdown_menu.js。 -
编写JS代码:将以下代码复制到
dropdown_menu.js文件中。
document.addEventListener('DOMContentLoaded', function() {
// 1. 获取所有的一级栏目链接
const topLevelLinks = document.querySelectorAll('a[data-id]');
// 2. 获取所有的二级栏目列表
const subMenus = document.querySelectorAll('.sub-menu');
// 为每个一级链接添加点击事件监听器
topLevelLinks.forEach(function(link) {
link.addEventListener('click', function(event) {
// 阻止链接的默认跳转行为,因为我们只想触发下拉菜单
// 注意:如果用户按住Ctrl/Cmd点击在新标签页打开,这个阻止可能不生效,这是正常行为。
event.preventDefault();
// 获取被点击链接的 data-id 属性值,'nav1'
const clickedLinkId = this.getAttribute('data-id');
// 构建对应二级列表的ID,'nav1-content'
const targetSubMenuId = clickedLinkId + '-content';
// 在所有二级列表中找到那个目标列表
const targetSubMenu = document.getElementById(targetSubMenuId);
// 先隐藏所有二级列表
subMenus.forEach(function(menu) {
if (menu.id !== targetSubMenuId) {
menu.style.display = 'none';
}
});
// 切换目标二级列表的显示状态
if (targetSubMenu) {
// 如果目标列表是隐藏的,就显示它;如果已经是显示的,就隐藏它(实现切换效果)
if (targetSubMenu.style.display === 'block') {
targetSubMenu.style.display = 'none';
} else {
targetSubMenu.style.display = 'block';
}
}
});
});
// (可选) 点击页面其他地方关闭菜单
document.addEventListener('click', function(event) {
// 如果点击的不是一级链接,也不是二级菜单
if (!event.target.closest('a[data-id]') && !event.target.closest('.sub-menu')) {
subMenus.forEach(function(menu) {
menu.style.display = 'none';
});
}
});
});
- 引入JS文件:在你的模板的公共文件(通常是
head.htm)的<head>或<body>标签结束前,加入以下代码来引入你刚创建的JS文件。
<script src="{dede:global.cfg_templets_skin/}/js/dropdown_menu.js"></script>
请确保 {dede:global.cfg_templets_skin/} 路径正确,它指向你的模板目录。
至此,方法二也完成了,刷新网站,点击一级栏目,就能看到二级栏目下拉效果,并且URL不会改变。
总结与选择建议
| 特性 | 方法一 (纯CSS) | 方法二 (JavaScript) |
|---|---|---|
| 实现复杂度 | 低 | 中 |
| 代码量 | 少 | 多 |
| URL变化 | 是 (会添加锚点) | 否 |
| JS依赖 | 无 | 是 |
| 交互灵活性 | 一般 | 高 |
| 推荐度 | ⭐⭐⭐⭐⭐ (首选) | ⭐⭐⭐⭐ |
给你的建议:
- 如果你的网站追求简洁、高效,且不介意URL有微小的变化,强烈推荐使用方法一(纯CSS),它是最“干净”的解决方案。
- 如果你对用户体验有更高要求,不希望URL有任何变化,或者计划在未来添加更复杂的交互效果,那么方法二(JavaScript)是不二之选,它更现代,也更灵活。
希望这个详细的教程能帮助你成功实现织梦CMS的二级栏目点击下拉功能!
