diff --git a/sun_badge_menu/__manifest__.py b/sun_badge_menu/__manifest__.py index 24a8e8cad..95ef56bec 100644 --- a/sun_badge_menu/__manifest__.py +++ b/sun_badge_menu/__manifest__.py @@ -8,7 +8,7 @@ "maintainer": "sun", "license" : "", "category": "Tools", - "depends": ["base", "web",], + "depends": ["base", "web"], "data": [ 'views/webclient_templates.xml', 'views/ir_ui_menu.xml' diff --git a/sun_badge_menu/static/src/scss/badge.scss b/sun_badge_menu/static/src/scss/badge.scss index 4554c2aaa..8aa8d20a7 100644 --- a/sun_badge_menu/static/src/scss/badge.scss +++ b/sun_badge_menu/static/src/scss/badge.scss @@ -1,39 +1,46 @@ -/* sun_badge_menu — 菜单角标样式 */ -/* 角标由 sh_backmate_theme_adv_enterprise menu.xml 渲染 */ +/* 角标由 sh_backmate_theme_adv_enterprise menu.xml 渲染,此处覆盖为菜单项最右侧显示 */ -/* 顶部应用菜单: 标签是 badge 的定位参照 */ -.sh_backmate_theme_appmenu_div .o_app2, -.sh_backmate_theme_appmenu_div .direct_menu, -.sh_backmate_theme_appmenu_div a[href] { - position: relative; +/* + * 主题 navbar.scss 中 .cssmenu > span > a > .menu-badge 的权重较高, + * 普通 body.o_web_client .menu-badge 会被它覆盖。 + * 下面用重复类名提升选择器优先级,确保子菜单和顶级菜单的角标都定位到最右侧。 + */ + +/* 给所有菜单链接容器加相对定位 */ +body.o_web_client .dropdown-item.dropdown-item, +body.o_web_client .sh_dropdown_toggle.sh_dropdown_toggle, +body.o_web_client .dropdown-header.dropdown-header, +body.o_web_client .o_app.o_app a, +body.o_web_client .o_app.o_app > a { + position: relative !important; } -/* 子菜单:.sh_submenu_icon_wrap 已有 position:relative(行内样式),无需额外设置 */ - -/* 角标基础样式 */ -.menu-badge { - position: absolute; - top: -6px; - right: -8px; - min-width: 18px; - height: 18px; - padding: 0 5px; - border-radius: 9px; - background: #dc3545; - color: #fff; - font-size: 11px; - font-weight: 600; - line-height: 18px; - text-align: center; - pointer-events: none; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - z-index: 10; - display: inline-flex; - align-items: center; - justify-content: center; -} - -/* 确保 badge 数字不换行 */ -.menu-badge { - white-space: nowrap; +/* 统一所有 .menu-badge 为菜单项最右侧、垂直居中 */ +body.o_web_client .menu-badge.menu-badge.menu-badge, +body.o_web_client .cssmenu > span > a > .menu-badge.menu-badge, +body.o_web_client .dropdown-item.dropdown-item > .menu-badge.menu-badge, +body.o_web_client .sh_dropdown_toggle.sh_dropdown_toggle > .menu-badge.menu-badge, +body.o_web_client .dropdown-header.dropdown-header .menu-badge.menu-badge, +body.o_web_client .sh_backmate_theme_appmenu_div .o_app.o_app > span > a > .menu-badge.menu-badge { + position: absolute !important; + top: 50% !important; + right: 12px !important; + left: auto !important; + bottom: auto !important; + transform: translateY(-50%) !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + min-width: 18px !important; + height: 18px !important; + padding: 0 6px !important; + margin: 0 !important; + background-color: #dc3545 !important; + color: #fff !important; + font-size: 11px !important; + font-weight: 700 !important; + border-radius: 9px !important; + line-height: 1 !important; + vertical-align: middle !important; + z-index: 1 !important; } diff --git a/sun_badge_menu/static/src/webclient/navbar/navbar.js b/sun_badge_menu/static/src/webclient/navbar/navbar.js index d28696844..634065ee0 100644 --- a/sun_badge_menu/static/src/webclient/navbar/navbar.js +++ b/sun_badge_menu/static/src/webclient/navbar/navbar.js @@ -5,6 +5,7 @@ import { patch } from "@web/core/utils/patch"; import { deepCopy } from "@web/core/utils/objects"; import { useService } from "@web/core/utils/hooks"; import { url } from "@web/core/utils/urls"; +import { onMounted, onWillUnmount } from "@odoo/owl"; import { clickRefreshButton } from "../actions/client_actions"; const WKF_NOTIFY_TYPE = 'wkf_notify' @@ -67,5 +68,95 @@ patch(NavBar.prototype, { } }, 300) }); + + /** + * 主题 navbar.scss 对子菜单的 .menu-badge 有更高权重定位, + * 导致 CSS 覆盖不生效。此处用 MutationObserver 在菜单 DOM 变化后 + * 强制把 .menu-badge 设置为绝对定位到父链接最右侧。 + */ + onMounted(() => { + this._fixMenuBadgePosition(); + this.menuBadgeObserver = new MutationObserver(() => { + this._fixMenuBadgePosition(); + }); + this.menuBadgeObserver.observe(document.body, { childList: true, subtree: true }); + }); + onWillUnmount(() => { + if (this.menuBadgeObserver) { + this.menuBadgeObserver.disconnect(); + } + }); + }, + + /** + * 修正所有菜单角标位置:推到菜单项最右侧。 + * 主题 XML 里角标放在图标容器(.sh_submenu_icon_wrap)内、文字前面, + * 这里先用 JS 把角标真正移动到文字后面,再给菜单链接容器加 flex, + * 最后用 margin-left:auto 把角标顶到最右。 + */ + _fixMenuBadgePosition() { + document.querySelectorAll('.menu-badge').forEach((badge) => { + // 已处理过的直接跳过,避免 MutationObserver 循环 + if (badge.dataset.badgeMoved === '1') { + return; + } + + // 找到最近的菜单链接容器( 或 .dropdown-item) + let container = badge.parentElement; + while (container && container.tagName !== 'A' && !container.classList.contains('dropdown-item') && !container.classList.contains('sh_dropdown_toggle')) { + container = container.parentElement; + if (!container || container.tagName === 'BODY') { + container = badge.parentElement; + break; + } + } + if (!container) { + return; + } + + // 把角标移动到文字 span 内部末尾,让它自然跟在文字后面 + let textSpan = null; + const iconWrap = badge.closest('.sh_submenu_icon_wrap'); + if (iconWrap) { + textSpan = iconWrap.nextElementSibling; + } else if (badge.previousElementSibling && badge.previousElementSibling.tagName === 'IMG') { + // 顶级 AppIcon:角标在 img 后面、文字前面 + textSpan = badge.nextElementSibling; + } else { + textSpan = badge.previousElementSibling; + } + if (textSpan && textSpan.tagName === 'SPAN') { + textSpan.appendChild(badge); + badge.dataset.badgeMoved = '1'; + } + + // 还原容器 flex/width 等样式,让菜单项按原本块级布局 + container.style.display = ''; + container.style.flexDirection = ''; + container.style.alignItems = ''; + container.style.position = 'relative'; + container.style.width = ''; + container.style.justifyContent = ''; + + if (textSpan && textSpan.tagName === 'SPAN') { + textSpan.style.setProperty('display', 'inline', 'important'); + textSpan.style.setProperty('flex', 'none', 'important'); + textSpan.style.setProperty('width', 'auto', 'important'); + } + + // 角标作为文字 span 内的行内 flex 容器,紧跟文字并居中显示数字 + badge.style.setProperty('display', 'inline-flex', 'important'); + badge.style.setProperty('align-items', 'center', 'important'); + badge.style.setProperty('justify-content', 'center', 'important'); + badge.style.setProperty('position', 'static', 'important'); + badge.style.setProperty('top', 'auto', 'important'); + badge.style.setProperty('right', 'auto', 'important'); + badge.style.setProperty('left', 'auto', 'important'); + badge.style.setProperty('bottom', 'auto', 'important'); + badge.style.setProperty('transform', 'none', 'important'); + badge.style.setProperty('margin', '0 0 0 6px', 'important'); + badge.style.setProperty('vertical-align', 'middle', 'important'); + badge.style.setProperty('z-index', '1', 'important'); + }); } }); diff --git a/sun_badge_menu/static/src/webclient/navbar/navbar.scss b/sun_badge_menu/static/src/webclient/navbar/navbar.scss new file mode 100644 index 000000000..f17b23536 --- /dev/null +++ b/sun_badge_menu/static/src/webclient/navbar/navbar.scss @@ -0,0 +1,42 @@ +/* =========================================== */ +/* sun_badge_menu 角标样式:移到文字后面,加占位符 */ +/* =========================================== */ + +// 一级菜单行布局 +.app-icon-row { + display: flex; + align-items: center; + justify-content: space-between; + .menu-text { + flex-grow: 1; + margin-left: 8px; + } +} + +// 角标容器(固定宽度,保持对齐) +.menu-badge-wrap { + min-width: 22px; + display: inline-flex; + justify-content: center; + align-items: center; +} + +// 角标数字(红色圆形) +.badge-count { + width: 18px; + height: 18px; + background-color: red; + color: white; + border-radius: 50%; + font-size: 11px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +// 占位符(无角标时预留空间,保持文字对齐) +.badge-placeholder { + display: inline-block; + width: 18px; + height: 18px; +}