本文约 5,366 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:菜单建好了,前台却什么都没有
一个典型的排查工单:客户在 外观 → 菜单 里建好了菜单,勾了”主导航”位置,保存了,前台刷新——导航栏还是空的,或者显示的是”示例页面”列表。
这类问题 90% 出在主题没有注册菜单位置,或者前台模板没调用 wp_nav_menu()。菜单功能是 WordPress 核心提供的,但”菜单显示在哪”完全由主题决定。
原理分析:菜单的三层结构
WordPress 菜单的数据模型分三层:
- 菜单(Menu):
wp_terms里taxonomy = nav_menu的一个条目,比如”主导航”。 - 菜单项(Menu Item):
wp_posts里post_type = nav_menu_item的记录,每一项带post_parent(决定层级)和一堆 meta(链接类型、目标 URL、CSS class 等)。 - 菜单位置(Theme Location):主题用
register_nav_menus()声明的位置,比如primary、footer。菜单和位置是”指派”关系。
关键:前台显示的是”被指派到某个位置的菜单”,不是”存在的菜单”。所以菜单建好了但没指派到位置,前台就不显示。
调用链:
主题 register_nav_menus() → 后台指派菜单 → 模板 wp_nav_menu(['theme_location'=>'primary'])
→ 输出 <ul class="menu"><li>...</li></ul>
保姆级实操
一、注册菜单位置
add_action( 'after_setup_theme', function() {
register_nav_menus( array(
'primary' => '主导航',
'footer' => '页脚导航',
'mobile' => '移动端导航',
'social' => '社交链接',
) );
});
注册后去 外观 → 菜单 → 编辑菜单 → 最下方”菜单设置”里勾选显示位置。
二、模板里调用
wp_nav_menu( array(
'theme_location' => 'primary',
'container' => 'nav',
'container_class'=> 'main-nav',
'menu_class' => 'nav-list',
'depth' => 3, // 支持三级
'fallback_cb' => false, // 没菜单时什么都不输出
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
) );
fallback_cb 默认是 wp_page_menu,这就是”前台显示示例页面列表”的元凶——没有指派菜单时,WordPress 默认回退到输出页面列表。设成 false 或者用 wp_nav_menu 前先判断:
if ( has_nav_menu( 'primary' ) ) {
wp_nav_menu( array( 'theme_location' => 'primary' ) );
}
三、制作多级下拉菜单
后台 外观 → 菜单 → 把菜单项往右拖一格就是子菜单。WordPress 原生支持无限层级,但主题 CSS 通常只做了两级。
三级以上需要 CSS 支持:
/* 二级、三级下拉 */
.main-nav ul li { position: relative; }
.main-nav ul ul {
position: absolute;
top: 100%;
left: 0;
display: none;
min-width: 180px;
background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,.1);
z-index: 999;
}
.main-nav ul li:hover > ul { display: block; }
/* 三级:往右展开而不是往下 */
.main-nav ul ul ul {
top: 0;
left: 100%;
}
四、移动端菜单(汉堡菜单)
WordPress 不会自动做响应式菜单,需要主题 JS。最小实现:
// 输出汉堡按钮
<button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false">
<span class="screen-reader-text">菜单</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
document.addEventListener('DOMContentLoaded', function () {
var toggle = document.querySelector('.menu-toggle');
var menu = document.querySelector('.main-nav');
if (!toggle || !menu) return;
toggle.addEventListener('click', function () {
var expanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', String(!expanded));
menu.classList.toggle('toggled');
});
});
@media (max-width: 768px) {
.main-nav ul { display: none; }
.main-nav.toggled ul { display: block; }
.main-nav.toggled ul ul { position: static; }
}
注意 aria 属性——这不仅是可访问性要求,Google 的移动友好性评估也看这个。
五、菜单项不够用时(自定义链接、mega menu)
- 自定义链接:菜单编辑页左侧”自定义链接”,可以填任意 URL(含外链、锚点
#xxx)。 - CSS class:菜单项右上”显示选项”里勾”CSS 类”,可以给单项加 class 做高亮。
- Mega Menu:装插件 “Max Mega Menu” 或 “WP Mega Menu”,支持多列、图片、短代码。
- 菜单里放短代码:装 “Shortcode in Menus” 插件,否则菜单项的短代码不解析。
六、当前页高亮(current-menu-item)
WordPress 会自动给当前页对应的菜单项加 current-menu-item / current-menu-ancestor class。如果没加:
- 分类页:菜单项是分类,当前是某篇文章,WordPress 会加
current-menu-ancestor(因为文章属于该分类); - CPT 归档页:默认不会高亮。要手动:
add_filter( 'nav_menu_css_class', function( $classes, $item ) {
if ( is_singular( 'case' ) || is_post_type_archive( 'case' ) ) {
if ( '客户案例' === $item->title ) {
$classes[] = 'current-menu-item';
}
}
return $classes;
}, 10, 2 );
七、菜单在缓存/CDN 下的问题
菜单是动态生成的,但如果整页缓存了,菜单看起来”不跟着当前页变化”(因为所有页共用一份缓存)。两种解决:
- 用 ESI(Varnish / Litespeed Cache 支持)把菜单做成独立片段;
- 或者用 JS 在客户端补 class:
(function(){
var here = window.location.pathname;
document.querySelectorAll('.main-nav a').forEach(function(a){
if (a.getAttribute('href') && a.getAttribute('href').indexOf(here) > -1) {
a.parentNode.classList.add('current-menu-item');
}
});
})();
八、命令行管理菜单
wp menu list
wp menu item list primary --format=table
wp menu item add-post primary 123 --title='某文章'
wp menu item add-term primary category 42
wp menu location assign primary 5
常见踩坑与失败案例
- 案例一:菜单建了前台空白。原因是主题没注册
primary位置,后台”显示位置”里根本没有选项,勾不上。要么改主题注册,要么用自定义菜单小工具。 - 案例二:前台显示”示例页面”列表。这是
fallback_cb默认值wp_page_menu在起作用,说明菜单没指派成功。设'fallback_cb' => false。 - 二级菜单出不来:主题 CSS 里
.sub-menu被设成display:none但 hover 规则没写,或者被overflow:hidden裁掉。检查.main-nav的overflow和z-index。 - 菜单改了前台不变:整页缓存。清缓存插件,或排除登录用户(
wp_rocket的 “never cache logged-in users”)。 - 菜单项太多后台保存失败:PHP 的
max_input_vars默认 1000,菜单项多(尤其多级)时 POST 变量超限被截断,保存后菜单项丢失。改 php.ini:
ini
max_input_vars = 5000
这是”菜单保存后少了一半”的最常见原因,很多人不知道。
- 菜单里的锚点链接在多语言站失效:Polylang/WPML 需要单独处理锚点,否则只跳到当前语言的首页。
wp_nav_menu输出的<div>破坏了布局:用'container' => false去掉外层容器,只输出<ul>。
总结
菜单的三层是 菜单 / 菜单项 / 菜单位置,前台显示的是”被指派到位置的菜单”,所以”菜单建好了不显示”先查两件事:主题有没有 register_nav_menus(),后台有没有指派位置。前台最好加 has_nav_menu() 判断并把 fallback_cb 设成 false,避免输出页面列表。两个高频隐藏坑:max_input_vars 导致大菜单保存被截断、整页缓存导致当前项高亮失效。多级菜单和移动端汉堡菜单都需要主题 CSS/JS 配合,WordPress 只负责输出 <ul><li> 结构。
