diff --git a/assets/js/admin-nav-menu-senior.js b/assets/js/admin-nav-menu-senior.js index 76e98501..4fce94d8 100644 --- a/assets/js/admin-nav-menu-senior.js +++ b/assets/js/admin-nav-menu-senior.js @@ -18,9 +18,10 @@ function graphicTemplate() { return { - arrangement: '', + arrangement: 'swiper', size: 'md', img_scale: 100, + align: '', items: [{ img: '', title: '', badge: '', badge_class: 'jb-red', link: { url: '', text: '', target: '' } }], }; } @@ -80,7 +81,12 @@ }); html += ''; html += field('size', '尺寸 sm/md/lg/xs', data.size || 'md'); - html += field('img_scale', '图片比例%', data.img_scale || 100, 'number'); + html += field('img_scale', '图片比例%(20-300)', data.img_scale || 100, 'number'); + html += ' '; $wrap.html(html); } @@ -114,6 +120,7 @@ var data = graphicTemplate(); data.arrangement = $wrap.find('[data-name="arrangement"]').val() || ''; data.size = $wrap.find('[data-name="size"]').val() || 'md'; + data.align = $wrap.find('[data-name="align"]').val() || ''; data.img_scale = parseInt($wrap.find('[data-name="img_scale"]').val(), 10) || 100; data.items = []; $wrap.find('.jio-sme-card').each(function () { diff --git a/inc/helpers/menu-decoration.php b/inc/helpers/menu-decoration.php index 04040411..0a2591b8 100644 --- a/inc/helpers/menu-decoration.php +++ b/inc/helpers/menu-decoration.php @@ -28,21 +28,26 @@ function jio_menu_pz(int $id, string $key = '', $default = '') { } /** - * @param string $icon_class Icon-picker value (FA5 class, jiosvg- or customsvg-). - * @param bool $enabled Global switch. + * @param string $icon_class Icon-picker value (FA5 class, jiosvg- or customsvg-). + * @param bool $enabled Global switch. + * @param string $extra_classes Classes appended to the glyph. Top-level menu + * titles use the default; mega-menu link lists use + * `mr3 fa-fw` so every row's glyph is the same width. */ -function jio_render_menu_icon(string $icon_class, bool $enabled = true): string { +function jio_render_menu_icon(string $icon_class, bool $enabled = true, string $extra_classes = 'mr3 menu-item-icon'): string { if (!$enabled || $icon_class === '') { return ''; } + $extra = trim($extra_classes); + // Sprite/inline values must reach jio_icon_markup untouched — the legacy // FA class padding below would corrupt them (jiosvg-hot → fa fas fa-jiosvg-hot). if (str_starts_with($icon_class, 'jiosvg-')) { - return jio_icon_markup($icon_class, 'icon mr3 menu-item-icon'); + return jio_icon_markup($icon_class, $extra === '' ? 'icon' : 'icon ' . $extra); } if (str_starts_with($icon_class, 'customsvg-')) { - return jio_icon_markup($icon_class, 'mr3 menu-item-icon'); + return jio_icon_markup($icon_class, $extra); } // legacy uses the Font Awesome 5 `fa` prefix. @@ -51,7 +56,46 @@ function jio_render_menu_icon(string $icon_class, bool $enabled = true): string } elseif (strpos($icon_class, 'fa-') === 0 && strpos($icon_class, ' ') === false) { $icon_class = 'fa ' . $icon_class; } - return jio_icon_markup($icon_class, 'mr3 menu-item-icon'); + return jio_icon_markup($icon_class, $extra); +} + +/** + * Whether a menu item passes its display policy (show_policy). + * + * @param int $menu_item_id Menu item ID. + * @return bool + */ +function jio_menu_item_match_show_policy(int $menu_item_id): bool { + $policy = (string) jio_menu_pz($menu_item_id, 'show_policy', ''); + if ($policy === '') { + return true; + } + + $user_id = get_current_user_id(); + if ($user_id <= 0) { + return false; + } + + switch ($policy) { + case 'signin': + return true; + case 'vip': + return function_exists('jio_is_vip') && jio_is_vip($user_id); + case 'vip_2': + return function_exists('jio_is_vip2') && jio_is_vip2($user_id); + case 'auth': + if (!function_exists('jio_get_user_identity_signals')) { + return false; + } + foreach (jio_get_user_identity_signals($user_id) as $signal) { + if ((string) ($signal['type'] ?? '') === 'verify') { + return true; + } + } + return false; + } + + return true; } /** diff --git a/src/Admin/NavMenuOptions.php b/src/Admin/NavMenuOptions.php index cec57616..491b2670 100644 --- a/src/Admin/NavMenuOptions.php +++ b/src/Admin/NavMenuOptions.php @@ -46,8 +46,9 @@ class NavMenuOptions { $icon = (string) ($opts['icon'] ?? ''); $badge = (string) ($opts['badge'] ?? ''); $badge_class = (string) ($opts['badge_class'] ?? ''); + $show_policy = (string) ($opts['show_policy'] ?? ''); $submenu_s = !empty($opts['submenu_s']); - $submenu_type = (string) ($opts['submenu_type'] ?? ''); + $submenu_type = (string) ($opts['submenu_type'] ?? 'graphic_card'); $graphic_json = !empty($opts['graphic_card_opts']) ? wp_json_encode($opts['graphic_card_opts'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : self::sample_graphic_json(); @@ -83,6 +84,19 @@ class NavMenuOptions { value="">

+

+ +

@@ -134,6 +147,7 @@ class NavMenuOptions { 'icon' => jio_sanitize_icon_value((string) ($raw['icon'] ?? '')), 'badge' => sanitize_text_field((string) ($raw['badge'] ?? '')), 'badge_class' => sanitize_html_class((string) ($raw['badge_class'] ?? '')), + 'show_policy' => self::sanitize_show_policy((string) ($raw['show_policy'] ?? '')), 'submenu_s' => !empty($raw['submenu_s']), 'submenu_type' => sanitize_key((string) ($raw['submenu_type'] ?? '')), ]; @@ -150,6 +164,15 @@ class NavMenuOptions { update_post_meta($menu_item_db_id, 'jio_menu_options', $clean); } + /** + * @param string $policy + */ + private static function sanitize_show_policy(string $policy): string { + $allowed = ['', 'signin', 'vip', 'vip_2', 'auth']; + $policy = sanitize_key($policy); + return in_array($policy, $allowed, true) ? $policy : ''; + } + /** * @return array|null */ @@ -177,7 +200,7 @@ class NavMenuOptions { 'img' => esc_url_raw((string) ($item['img'] ?? '')), 'title' => sanitize_text_field((string) ($item['title'] ?? '')), 'badge' => sanitize_text_field((string) ($item['badge'] ?? '')), - 'badge_class' => sanitize_html_class((string) ($item['badge_class'] ?? '')), + 'badge_class' => sanitize_html_class((string) ($item['badge_class'] ?? 'jb-red')), 'link' => [ 'url' => esc_url_raw((string) ($item['link']['url'] ?? '')), 'text' => sanitize_text_field((string) ($item['link']['text'] ?? '')), @@ -186,10 +209,10 @@ class NavMenuOptions { ]; } return [ - 'arrangement' => sanitize_key((string) ($opts['arrangement'] ?? '')), + 'arrangement' => sanitize_key((string) ($opts['arrangement'] ?? 'swiper')), 'align' => sanitize_key((string) ($opts['align'] ?? '')), - 'size' => sanitize_key((string) ($opts['size'] ?? '')), - 'img_scale' => max(0, min(200, (int) ($opts['img_scale'] ?? 100))), + 'size' => sanitize_key((string) ($opts['size'] ?? 'md')), + 'img_scale' => max(20, min(300, (int) ($opts['img_scale'] ?? 100))), 'items' => $items, ]; } @@ -240,7 +263,7 @@ class NavMenuOptions { private static function sample_graphic_json(): string { $sample = [ - 'arrangement' => '', + 'arrangement' => 'swiper', 'size' => 'md', 'img_scale' => 100, 'items' => [ diff --git a/src/Core/Setup.php b/src/Core/Setup.php index 5163b676..ff170c2d 100644 --- a/src/Core/Setup.php +++ b/src/Core/Setup.php @@ -78,11 +78,10 @@ class Setup { public function register_nav_menus(): void { register_nav_menus([ - 'primary' => __('主导航', 'jio'), - 'mobile' => __('移动端菜单', 'jio'), - 'footer' => __('页脚菜单', 'jio'), - 'social' => __('社交链接', 'jio'), - 'header_actions' => __('顶部右侧入口', 'jio'), + 'primary' => __('主导航', 'jio'), + 'mobile' => __('移动端菜单', 'jio'), + 'footer' => __('页脚菜单', 'jio'), + 'social' => __('社交链接', 'jio'), ]); } diff --git a/src/Frontend/NavWalker.php b/src/Frontend/NavWalker.php index 184164e7..9925446c 100644 --- a/src/Frontend/NavWalker.php +++ b/src/Frontend/NavWalker.php @@ -15,9 +15,13 @@ class NavWalker extends \Walker_Nav_Menu { public static $submenu_ids = []; /** - * Skip WP children when parent uses senior submenu. + * Skip hidden items (display policy) and WP children when the parent uses + * a senior submenu. */ public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) { + if (function_exists('jio_menu_item_match_show_policy') && !jio_menu_item_match_show_policy((int) $element->ID)) { + return; + } if (!empty($element->menu_item_parent) && in_array((int) $element->menu_item_parent, self::$submenu_ids, true)) { return; } @@ -29,7 +33,7 @@ class NavWalker extends \Walker_Nav_Menu { $icon = function_exists('jio_menu_pz') ? (string) jio_menu_pz((int) $item->ID, 'icon', '') : ''; $badge = function_exists('jio_menu_pz') ? (string) jio_menu_pz((int) $item->ID, 'badge', '') : ''; - $badge_class = function_exists('jio_menu_pz') ? (string) jio_menu_pz((int) $item->ID, 'badge_class', '') : ''; + $badge_class = function_exists('jio_menu_pz') ? (string) jio_menu_pz((int) $item->ID, 'badge_class', 'jb-red') : ''; if ($icon && function_exists('jio_render_menu_icon')) { $title = jio_render_menu_icon($icon) . $title; @@ -58,7 +62,9 @@ class NavWalker extends \Walker_Nav_Menu { * @param int $item_id */ public function submenu(int $item_id): string { - $type = function_exists('jio_menu_pz') ? sanitize_key((string) jio_menu_pz($item_id, 'submenu_type', '')) : ''; + $type = function_exists('jio_menu_pz') + ? sanitize_key((string) jio_menu_pz($item_id, 'submenu_type', 'graphic_card')) + : ''; $method = 'submenu_' . $type; if ($type !== '' && method_exists($this, $method)) { return (string) $this->$method($item_id); @@ -76,7 +82,7 @@ class NavWalker extends \Walker_Nav_Menu { if (!$items) { return ''; } - $arrangement = (string) ($opts['arrangement'] ?? ''); + $arrangement = (string) ($opts['arrangement'] ?? 'swiper'); $lists = ''; $i = 0; foreach ($items as $item) { @@ -123,10 +129,10 @@ class NavWalker extends \Walker_Nav_Menu { $title = $link_title; } $badge = !empty($args['badge']) - ? '
' + ? '
' . esc_html((string) $args['badge']) . '
' : ''; - $img_html = '
' . $badge . '
'; + $img_html = '
' . $badge . '
'; $title_html = $title !== '' ? '
' . esc_html($title) . '
' : ''; return ''; @@ -181,7 +187,8 @@ class NavWalker extends \Walker_Nav_Menu { } $icon = ''; if (!empty($args['icon']) && function_exists('jio_render_menu_icon')) { - $icon = jio_render_menu_icon((string) $args['icon'], true); + // Mega-menu rows reserve a fixed glyph column so titles line up. + $icon = jio_render_menu_icon((string) $args['icon'], true, 'mr3 fa-fw'); } if (!empty($args['img_icon'])) { $icon = ''; diff --git a/tests/widget-system/icon-picker-contract.test.cjs b/tests/widget-system/icon-picker-contract.test.cjs index 40a82e69..625d4e43 100644 --- a/tests/widget-system/icon-picker-contract.test.cjs +++ b/tests/widget-system/icon-picker-contract.test.cjs @@ -133,7 +133,11 @@ test('custom settings pages and standalone save paths speak the three icon forma test('nav menu icons render every format without legacy fa-prefix mangling', () => { const helper = read('inc/helpers/menu-decoration.php'); assert.match(helper, /str_starts_with\(\$icon_class, 'jiosvg-'\)/); - assert.match(helper, /jio_icon_markup\(\$icon_class, 'icon mr3 menu-item-icon'\)/); + // Top-level titles keep the default glyph classes; mega-menu lists pass + // `mr3 fa-fw` so every row's icon occupies the same column. + assert.match(helper, /\$extra_classes = 'mr3 menu-item-icon'/); + assert.match(helper, /jio_icon_markup\(\$icon_class, \$extra\)/); + assert.match(read('src/Frontend/NavWalker.php'), /jio_render_menu_icon\(\(string\) \$args\['icon'\], true, 'mr3 fa-fw'\)/); assert.doesNotMatch(helper, /'