子比主题开发文档
使用指南Codestar Framework主题扩展在线部署AI 功能推荐插件赞助打赏

菜单徽章与高级子菜单

拆解子比主题导航菜单项图标、徽章、高级子菜单、图文卡片、多栏目链接和前台 Walker 渲染扩展边界。

模块边界

子比主题的菜单增强不是把 HTML 写进菜单标题,而是通过 Codestar Framework 给 WordPress 菜单项增加配置,再由自定义 zib_walker_nav_menu 在前台渲染。开发时要分清三件事:

层级负责内容入口
菜单项配置图标、徽章、高级子菜单开关、图文卡片、多栏目链接inc/options/metabox-options.php
配置读取从菜单项 post meta 读取 zib_menu_optionszib_menu_pz()
前台渲染修改标题 HTML、替换默认子菜单、输出高级子菜单结构zib_walker_nav_menu

如果只是给菜单项加一个小标识,使用 badgebadge_class。如果要做大块图文入口或多列链接,使用高级子菜单。不要在 WordPress 菜单标题里塞复杂 HTML,也不要把菜单项指向对象 ID 当成菜单项配置 ID。

核心文件

文件作用
inc/options/metabox-options.php注册 zib_menu_options,给菜单项增加图标、徽章和高级子菜单字段
inc/functions/zib-header.php定义 zib_walker_nav_menuzib_menu_pz(),输出 PC 菜单和移动菜单
inc/functions/zib-theme.php菜单缓存和主题加载相关能力
inc/options/options-module.php后台菜单和官网教程提示入口

Codestar 字段结构见 导航菜单项配置。本页更关注这些字段如何影响前台导航和二次开发时怎么接入。

配置保存结构

菜单项配置固定使用:

$prefix = 'zib_menu_options';
CSF::createNavMenuOptions($prefix, array(
    'data_type' => 'serialize',
));

它会保存到当前菜单项的 post_meta

post_id  = 菜单项 ID
meta_key = zib_menu_options

读取函数:

function zib_menu_pz($id, $key = '', $default = '')
{
    static $options = array();
    if (!isset($options[$id])) {
        $options[$id] = get_post_meta($id, 'zib_menu_options', true);
    }

    return zib_get_array_value($options[$id], $key, $default);
}

第一个参数必须是菜单项 ID:

$icon = zib_menu_pz($item->ID, 'icon');

不要传 $item->object_idobject_id 是菜单项指向的页面、文章或分类 ID,不是菜单项自身 ID。

基础字段

主题注册的基础字段:

字段类型作用
iconicon菜单标题前图标
badgetext菜单标题后徽章文字
badge_classpalette徽章背景色,依赖 badge 非空
submenu_sswitcher是否启用高级子菜单
submenu_typeradio高级子菜单类型
graphic_card_optsfieldset图文卡片配置
multi_column_links_optsfieldset多栏目链接配置

zib_walker_nav_menu::start_el() 会在输出菜单项前处理图标和徽章:

$title       = '<span class="menu-item-title">' . $item->title . '</span>';
$title_icon  = zib_menu_pz($item->ID, 'icon');
$title_badge = zib_menu_pz($item->ID, 'badge');

if ($title_icon) {
    $title = zib_get_cfs_icon($title_icon, 'mr3 menu-item-icon') . $title;
}

if ($title_badge) {
    $title .= ' <badge class="menu-item-badge ' . zib_menu_pz($item->ID, 'badge_class') . '">' . $title_badge . '</badge>';
}

$item->title = $title;

开发新增菜单展示字段时,尽量沿用这个路径:字段存进 zib_menu_options,前台 Walker 或过滤器读取字段,输出时使用主题已有 class。

高级子菜单规则

高级子菜单只在一级菜单生效:

if (zib_menu_pz($item->ID, 'submenu_s') && $depth == 0) {
    $submenu = self::submenu($item->ID);
}

如果高级子菜单有内容,主题会:

$item->classes[]     = 'menu-item-has-children';
self::$submenu_ids[] = $item->ID;

随后 display_element() 会跳过这个菜单项原本的子项目:

if (!empty($element->menu_item_parent) && in_array($element->menu_item_parent, self::$submenu_ids)) {
    return '';
}

这意味着高级子菜单是替换,不是追加。开启高级子菜单后,当前一级菜单下面原本拖拽出来的普通子菜单不会再显示,最终只显示 graphic_card_optsmulti_column_links_opts 里的配置。

子菜单类型分发

高级子菜单类型由 submenu_type 决定:

public function submenu($item_ID)
{
    $type          = zib_menu_pz($item_ID, 'submenu_type');
    $function_name = 'submenu_' . $type;
    if (method_exists($this, $function_name)) {
        return $this->$function_name($item_ID);
    }
    return '';
}

当前源码支持:

submenu_type渲染方法适合场景
graphic_cardsubmenu_graphic_card()带图片的大入口、产品、专题、活动、资源卡
multi_column_linkssubmenu_multi_column_links()多列文字链接、分类导航、文档目录

新增类型时要同时注册字段、补渲染方法、考虑移动端和缓存,不要只给 submenu_type 增加一个选项。

图文卡片菜单

图文卡片读取:

$opts        = zib_menu_pz($item_ID, 'graphic_card_opts', array());
$items       = $opts['items'] ?? array();
$arrangement = $opts['arrangement'] ?? '';

没有 items 时不输出。每个卡片都会交给 submenu_graphic_card_item(),没有图片会直接丢弃:

if (empty($args['img'])) {
    return '';
}

图文卡片支持:

字段作用
items.*.title卡片标题
items.*.img卡片图片,必填
items.*.link卡片链接
items.*.badge图片角标
items.*.badge_class角标颜色
arrangement空为自动换行,swiper 为单行左右滚动
img_scale图片比例,输出为 --img-scale
size卡片尺寸 class
align自动换行时的对齐方式

最终结构大致是:

<div class="sub-menu senior-submenu submenu-graphic-card size-md" style="--img-scale:1;">
  <div class="container">
    <div class="graphic-card-items-box flex hh">
      <div style="--ani-delay:0s;">
        <div class="menu-graphic-card-item">
          <a href="...">
            <div class="img-box hover-zoom-img">
              <img class="lazyload fit-cover">
              <div class="img-badg-box"><span class="badg badg-sm jb-red">NEW</span></div>
            </div>
            <div class="title mt10 text-ellipsis">标题</div>
          </a>
        </div>
      </div>
    </div>
  </div>
</div>

卡片数量多时优先使用 swiper,但移动菜单最好单独配置,不要把 PC 的大图高级菜单原样搬到移动端。

多栏目链接菜单

多栏目链接读取:

$opts    = zib_menu_pz($item_ID, 'multi_column_links_opts', array());
$columns = $opts['columns'] ?? array();

没有栏目时不输出。每个栏目先输出栏目标题,再输出栏目内链接:

$lists .= '<div class="links-column flex-auto">';
$lists .= self::submenu_multi_column_links_item($column, 'column-title');
$lists .= '<div class="links-items">';
foreach ($column['items'] as $item) {
    $lists .= self::submenu_multi_column_links_item($item, '');
}
$lists .= '</div></div>';

单个链接项要求标题存在。如果没有手动标题,会尝试使用链接字段里的 text

if (!$title && $link_title) {
    $title = $link_title;
}

if (!$title) {
    return '';
}

字段能力:

字段作用
columns.*.title栏目标题
columns.*.iconFont Awesome 或主题图标
columns.*.img_icon图片图标,优先级高于 icon
columns.*.link栏目标题链接
columns.*.items栏目内链接列表
columns.*.items.*.title链接标题
columns.*.items.*.link链接地址和打开方式

最终结构:

<div class="sub-menu senior-submenu submenu-multi-column-links">
  <div class="container">
    <div class="links-columns-box flex jsa hh">
      <div class="links-column flex-auto" style="--ani-delay:0.08s;">
        <div class="column-title link-item"><a href="...">栏目标题</a></div>
        <div class="links-items">
          <div class="link-item"><a href="...">链接标题<i class="fa fa-angle-double-right icon-hover-show"></i></a></div>
        </div>
      </div>
    </div>
  </div>
</div>

它适合做文档、功能、资源、分类导航,不适合放大图、视频或复杂业务状态。

扩展菜单项字段

新增字段时使用同一个 prefix,避免 zib_menu_pz() 读不到:

function zib_docs_register_menu_extra_field()
{
    $prefix = 'zib_menu_options';

    CSF::createSection($prefix, array(
        'fields' => array(
            array(
                'id'      => 'docs_desc',
                'type'    => 'text',
                'title'   => __('菜单说明', 'zib_language'),
                'default' => '',
            ),
        ),
    ));
}
add_action('after_setup_theme', 'zib_docs_register_menu_extra_field', 30);

读取时仍然用菜单项 ID:

function zib_docs_get_menu_desc($item)
{
    $desc = zib_menu_pz($item->ID, 'docs_desc', '');

    if (!$desc) {
        return '';
    }

    return '<small class="menu-item-desc muted-color">' . esc_html($desc) . '</small>';
}

如果要把这个字段挂进前台菜单标题,优先过滤 Walker 输出或继承 Walker;不要直接修改菜单标题保存值。

自定义高级子菜单类型

新增类型需要三个步骤:

  1. submenu_type 增加选项。
  2. 注册对应配置字段,例如 docs_panel_opts
  3. 扩展 Walker,提供 submenu_docs_panel()

示例:

class Zib_Docs_Walker_Nav_Menu extends zib_walker_nav_menu
{
    public function submenu_docs_panel($item_ID)
    {
        $opts  = zib_menu_pz($item_ID, 'docs_panel_opts', array());
        $title = !empty($opts['title']) ? $opts['title'] : '';

        if (!$title) {
            return '';
        }

        return '<div class="sub-menu senior-submenu docs-panel-menu"><div class="container"><div class="theme-box padding-15">' . esc_html($title) . '</div></div></div>';
    }
}

调用菜单时指定 Walker:

wp_nav_menu(array(
    'theme_location' => 'topmenu',
    'container'      => false,
    'walker'         => new Zib_Docs_Walker_Nav_Menu(),
));

如果只是新增一两种展示字段,继承 Walker 可能成本偏高。优先评估能否复用图文卡片或多栏目链接。

菜单缓存与更新

主题在菜单更新后会清理菜单缓存:

add_action('wp_update_nav_menu_item', 'zib_cache_delete_nav_menu');

所以开发菜单相关能力时要注意:

场景建议
改字段配置保存菜单后刷新前台
改 Walker 输出清理页面缓存和对象缓存
改菜单项图片检查懒加载、CDN 和图片尺寸
改高级子菜单类型同步 PC 菜单和移动菜单配置

如果使用页面缓存,菜单 HTML 可能被缓存为旧结构。菜单字段保存成功不代表前台马上可见。

移动端边界

高级子菜单源码提示“PC 端和移动端最好分开创建菜单”。原因是:

风险说明
图文卡片过多移动菜单会变高,抽屉滚动体验差
图片太大首次打开菜单时加载压力大
多栏目太宽多列结构容易横向溢出
普通子菜单被替换移动端可能需要更简单的层级菜单
hover 依赖PC 菜单交互不一定适合触摸

如果移动端只需要普通层级菜单,可以单独配置 mobilemenu,不要复用 PC 的高级子菜单。

常见风险

风险说明
把 HTML 塞进菜单标题会破坏后台可编辑性,也容易被 Walker 再包一层
object_id 读取配置读不到菜单项 post meta
子菜单项开启高级子菜单主题只在一级菜单 depth == 0 生效
同时维护普通子项和高级子菜单高级子菜单会替换普通子项,运营容易误判
图文卡片缺图片submenu_graphic_card_item() 会直接返回空
多栏目缺标题submenu_multi_column_links_item() 会直接返回空
字段 ID 冲突覆盖 iconbadgesubmenu_s 等主题字段会影响导航
忘记清缓存菜单保存、对象缓存、页面缓存、CDN 都可能影响前台显示

调试入口

现象优先检查
图标不显示icon 字段、zib_get_cfs_icon()、菜单项 ID 是否正确
徽章不显示badge 是否为空、badge_class 是否有效
高级子菜单不显示是否一级菜单、submenu_ssubmenu_type、字段组是否有内容
普通子菜单消失是否开启了高级子菜单,这是主题预期行为
图文卡片数量变少是否有卡片缺少 img
多栏目链接缺项是否缺少标题或链接文本
移动端错位是否 PC/移动共用同一个高级菜单,图片和列数是否过多

参考源码

本页根据 inc/options/metabox-options.phpinc/functions/zib-header.phpinc/options/options-module.php,以及子比主题官网公开的菜单、徽章和高级子菜单教程蒸馏整理。

On this page