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;
+}