搜索全站
报错排查 14 分钟阅读

WordPress 菜单管理完全指南:创建、多级菜单与不显示的排查

菜单做好了前台不显示、二级菜单出不来、手机端没反应——这些问题都有明确成因。本文讲清菜单的注册与调用机制、四级菜单的制作、菜单位置与主题位置不匹配的处理,以及菜单在缓存和 CDN 下的坑。

U
UFO_PF 2018-01-16 · 阅读 14 分钟
Wo 报错排查
阅读提示
本文约 5,366 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:菜单建好了,前台却什么都没有

一个典型的排查工单:客户在 外观 → 菜单 里建好了菜单,勾了”主导航”位置,保存了,前台刷新——导航栏还是空的,或者显示的是”示例页面”列表。

这类问题 90% 出在主题没有注册菜单位置,或者前台模板没调用 wp_nav_menu()。菜单功能是 WordPress 核心提供的,但”菜单显示在哪”完全由主题决定。

原理分析:菜单的三层结构

WordPress 菜单的数据模型分三层:

  1. 菜单(Menu):wp_terms 里 taxonomy = nav_menu 的一个条目,比如”主导航”。
  2. 菜单项(Menu Item):wp_posts 里 post_type = nav_menu_item 的记录,每一项带 post_parent(决定层级)和一堆 meta(链接类型、目标 URL、CSS class 等)。
  3. 菜单位置(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

常见踩坑与失败案例

  1. 案例一:菜单建了前台空白。原因是主题没注册 primary 位置,后台”显示位置”里根本没有选项,勾不上。要么改主题注册,要么用自定义菜单小工具。
  2. 案例二:前台显示”示例页面”列表。这是 fallback_cb 默认值 wp_page_menu 在起作用,说明菜单没指派成功。设 'fallback_cb' => false。
  3. 二级菜单出不来:主题 CSS 里 .sub-menu 被设成 display:none 但 hover 规则没写,或者被 overflow:hidden 裁掉。检查 .main-nav 的 overflow 和 z-index。
  4. 菜单改了前台不变:整页缓存。清缓存插件,或排除登录用户(wp_rocket 的 “never cache logged-in users”)。
  5. 菜单项太多后台保存失败:PHP 的 max_input_vars 默认 1000,菜单项多(尤其多级)时 POST 变量超限被截断,保存后菜单项丢失。改 php.ini:

ini
max_input_vars = 5000

这是”菜单保存后少了一半”的最常见原因,很多人不知道。

  1. 菜单里的锚点链接在多语言站失效:Polylang/WPML 需要单独处理锚点,否则只跳到当前语言的首页。
  2. wp_nav_menu 输出的 <div> 破坏了布局:用 'container' => false 去掉外层容器,只输出 <ul>。

总结

菜单的三层是 菜单 / 菜单项 / 菜单位置,前台显示的是”被指派到位置的菜单”,所以”菜单建好了不显示”先查两件事:主题有没有 register_nav_menus(),后台有没有指派位置。前台最好加 has_nav_menu() 判断并把 fallback_cb 设成 false,避免输出页面列表。两个高频隐藏坑:max_input_vars 导致大菜单保存被截断、整页缓存导致当前项高亮失效。多级菜单和移动端汉堡菜单都需要主题 CSS/JS 配合,WordPress 只负责输出 <ul><li> 结构。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。