From 028326b7062f864900de8c314ecde4b0c24901ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Tue, 31 Mar 2026 20:56:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8B=B9=E6=9E=9C=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../static/src/js/status_bar_buttons.js | 144 ++++++++++-------- .../static/src/scss/status_bar_buttons.scss | 88 +++++++++++ 2 files changed, 170 insertions(+), 62 deletions(-) diff --git a/k_ry_mobile_form/static/src/js/status_bar_buttons.js b/k_ry_mobile_form/static/src/js/status_bar_buttons.js index faf93cf45..5652016c0 100644 --- a/k_ry_mobile_form/static/src/js/status_bar_buttons.js +++ b/k_ry_mobile_form/static/src/js/status_bar_buttons.js @@ -21,6 +21,51 @@ const isMobileDevice = () => { return hasTouch && (isIOS || isSmallScreen); }; +// soong_workflow 头部按钮 class 与 Odoo defaultRank 叠用;WebKit 上可能出现「能点但看不见」。 +// 对底部固定栏内按钮强制前景/背景与 -webkit-text-fill(不依赖 getComputedStyle 误判)。 +const WORKFLOW_BUTTON_STYLES = [ + ['btn-confirm', '#2563eb', '#ffffff'], + ['btn-finish', '#1d4ed8', '#ffffff'], + ['btn-transfer', '#166534', '#ffffff'], + ['btn-distribute', '#16a34a', '#ffffff'], + ['btn-recall', '#f59e0b', '#ffffff'], + ['btn-return', '#ef4444', '#ffffff'], + ['btn-archive', '#6b7280', '#ffffff'], +]; + +function forceMobileBarVisible(container) { + container.style.setProperty('display', 'block', 'important'); + container.style.setProperty('visibility', 'visible', 'important'); + container.style.setProperty('opacity', '1', 'important'); + container.style.setProperty('position', 'fixed', 'important'); + container.style.setProperty('bottom', '0', 'important'); + container.style.setProperty('left', '0', 'important'); + container.style.setProperty('right', '0', 'important'); + container.style.setProperty('z-index', '99999', 'important'); + // 强制设置容器可见背景与最小高度,避免“能点但看不见” + container.style.setProperty('background-color', '#ffffff', 'important'); + container.style.setProperty('background', '#ffffff', 'important'); + container.style.setProperty('border-top', '1px solid #d9d9d9', 'important'); + container.style.setProperty('box-shadow', '0 -2px 8px rgba(0, 0, 0, 0.12)', 'important'); + container.style.setProperty('min-height', '56px', 'important'); + container.style.setProperty('pointer-events', 'auto', 'important'); +} + +function applyWorkflowButtonPaint(button) { + for (const [cls, bg, fg] of WORKFLOW_BUTTON_STYLES) { + if (button.classList.contains(cls)) { + button.style.setProperty('background-color', bg, 'important'); + button.style.setProperty('border-color', bg, 'important'); + button.style.setProperty('color', fg, 'important'); + button.style.setProperty('-webkit-text-fill-color', fg, 'important'); + button.style.setProperty('opacity', '1', 'important'); + button.style.setProperty('visibility', 'visible', 'important'); + return true; + } + } + return false; +} + // 覆盖 StatusBarButtons 组件,添加移动端检测逻辑 patch(StatusBarButtons, { template: "k_ry_mobile_form.StatusBarButtons", @@ -57,38 +102,26 @@ patch(StatusBarButtons, { useEffect(() => { const checkAndFixMobileButtons = () => { try { - // 如果是移动设备但 env.isSmall 为 false,强制显示移动端按钮 - if (isMobileDevice()) { - const mobileContainers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed'); - if (mobileContainers.length === 0) return; - - mobileContainers.forEach(container => { - try { - // 使用内联样式强制显示,优先级最高 - container.style.setProperty('display', 'block', 'important'); - container.style.setProperty('visibility', 'visible', 'important'); - container.style.setProperty('opacity', '1', 'important'); - container.style.setProperty('position', 'fixed', 'important'); - container.style.setProperty('bottom', '0', 'important'); - container.style.setProperty('left', '0', 'important'); - container.style.setProperty('right', '0', 'important'); - container.style.setProperty('z-index', '99999', 'important'); - // 强制设置背景色,防止被主题覆盖 - container.style.setProperty('background-color', '#ffffff', 'important'); - container.style.setProperty('background', '#ffffff', 'important'); - - // 同时修复容器内的按钮 - const buttons = container.querySelectorAll('button, a.btn, .btn, a[role="button"], .o_statusbar_buttons > * > button, .o_statusbar_buttons > * > a'); - buttons.forEach(button => { - try { - // 强制设置按钮可见性 - button.style.setProperty('visibility', 'visible', 'important'); - button.style.setProperty('opacity', '1', 'important'); - button.style.setProperty('display', 'inline-block', 'important'); - button.style.setProperty('font-size', '14px', 'important'); - button.style.setProperty('font-weight', '500', 'important'); - // 防止文字被设置为透明 - button.style.setProperty('text-indent', '0', 'important'); + const mobileContainers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed'); + if (mobileContainers.length === 0) return; + + mobileContainers.forEach(container => { + try { + forceMobileBarVisible(container); + + // 同时修复容器内的按钮 + const buttons = container.querySelectorAll('button, a.btn, .btn, a[role="button"], .o_statusbar_buttons > * > button, .o_statusbar_buttons > * > a'); + buttons.forEach(button => { + try { + applyWorkflowButtonPaint(button); + // 强制设置按钮可见性 + button.style.setProperty('visibility', 'visible', 'important'); + button.style.setProperty('opacity', '1', 'important'); + button.style.setProperty('display', 'inline-block', 'important'); + button.style.setProperty('font-size', '14px', 'important'); + button.style.setProperty('font-weight', '500', 'important'); + // 防止文字被设置为透明 + button.style.setProperty('text-indent', '0', 'important'); // 检查按钮颜色 const computedStyle = getComputedStyle(button); @@ -132,24 +165,23 @@ patch(StatusBarButtons, { button.style.setProperty('color', '#ffffff', 'important'); } } - } catch (e) { - console.warn('Error fixing button in setup:', e); - } - }); - } catch (e) { - console.warn('Error fixing container in setup:', e); - } - }); - - // 隐藏桌面端按钮 - try { - const desktopButtons = document.querySelectorAll('.o_form_statusbar > .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons)'); - desktopButtons.forEach(btn => { - btn.style.setProperty('display', 'none', 'important'); + } catch (e) { + console.warn('Error fixing button in setup:', e); + } }); } catch (e) { - console.warn('Error hiding desktop buttons in setup:', e); + console.warn('Error fixing container in setup:', e); } + }); + + // 隐藏桌面端按钮 + try { + const desktopButtons = document.querySelectorAll('.o_form_statusbar > .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons)'); + desktopButtons.forEach(btn => { + btn.style.setProperty('display', 'none', 'important'); + }); + } catch (e) { + console.warn('Error hiding desktop buttons in setup:', e); } } catch (e) { console.warn('Error in checkAndFixMobileButtons:', e); @@ -200,26 +232,14 @@ if (typeof window !== 'undefined' && typeof document !== 'undefined') { containers.forEach(container => { try { - // 如果是移动设备,确保容器可见 - if (isMobileDevice()) { - // 使用 setProperty 的 important 标志强制设置样式 - container.style.setProperty('display', 'block', 'important'); - container.style.setProperty('visibility', 'visible', 'important'); - container.style.setProperty('opacity', '1', 'important'); - container.style.setProperty('position', 'fixed', 'important'); - container.style.setProperty('bottom', '0', 'important'); - container.style.setProperty('left', '0', 'important'); - container.style.setProperty('right', '0', 'important'); - container.style.setProperty('z-index', '99999', 'important'); - // 强制设置背景色,防止被主题覆盖 - container.style.setProperty('background-color', '#ffffff', 'important'); - container.style.setProperty('background', '#ffffff', 'important'); - } + // 无条件强制底栏可见,避免主题在特定机型上把栏绘制成透明 + forceMobileBarVisible(container); // 修复按钮的交互性和可见性 const buttons = container.querySelectorAll('button, a.btn, .btn, a[role="button"], .o_statusbar_buttons > * > button, .o_statusbar_buttons > * > a'); buttons.forEach(button => { try { + applyWorkflowButtonPaint(button); // 使用 setProperty 强制设置样式 button.style.setProperty('pointer-events', 'auto', 'important'); button.style.setProperty('cursor', 'pointer', 'important'); diff --git a/k_ry_mobile_form/static/src/scss/status_bar_buttons.scss b/k_ry_mobile_form/static/src/scss/status_bar_buttons.scss index 6faae2ff6..d69a16221 100644 --- a/k_ry_mobile_form/static/src/scss/status_bar_buttons.scss +++ b/k_ry_mobile_form/static/src/scss/status_bar_buttons.scss @@ -29,6 +29,94 @@ min-height: 44px; } } + + // soong_workflow 流程按钮与 Odoo 注入的 defaultRank(如 btn-secondary)叠在同一 + // button 上时,WebKit/iOS 上可能出现「可点击但看不见」(颜色/填充未按预期绘制)。 + // 在此容器内用更高优先级强制绘制,与 soong_workflow/static/src/css/button_css.scss 对齐。 + button.btn-confirm { + background-color: #2563eb !important; + color: #fff !important; + border-color: #2563eb !important; + -webkit-text-fill-color: #fff !important; + } + button.btn-finish { + background-color: #1d4ed8 !important; + color: #fff !important; + border-color: #1d4ed8 !important; + -webkit-text-fill-color: #fff !important; + } + button.btn-transfer { + background-color: #166534 !important; + color: #fff !important; + border-color: #166534 !important; + -webkit-text-fill-color: #fff !important; + } + button.btn-distribute { + background-color: #16a34a !important; + color: #fff !important; + border-color: #16a34a !important; + -webkit-text-fill-color: #fff !important; + } + button.btn-recall { + background-color: #f59e0b !important; + color: #fff !important; + border-color: #f59e0b !important; + -webkit-text-fill-color: #fff !important; + } + button.btn-return { + background-color: #ef4444 !important; + color: #fff !important; + border-color: #ef4444 !important; + -webkit-text-fill-color: #fff !important; + } + button.btn-archive { + background-color: #6b7280 !important; + color: #fff !important; + border-color: #6b7280 !important; + -webkit-text-fill-color: #fff !important; + } + + // ViewButton 文案在内部 span 上;防止 WebKit 子节点仍使用透明或系统色 + button span { + color: inherit !important; + -webkit-text-fill-color: inherit !important; + opacity: 1 !important; + } + } +} + +// 最硬兜底:仅移动端启用,避免主题将底部栏渲染为透明/不可见 +@include media-breakpoint-down(md) { + .o_form_view .o_statusbar_buttons_mobile_fixed { + display: block !important; + visibility: visible !important; + opacity: 1 !important; + position: fixed !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + z-index: 99999 !important; + min-height: 56px !important; + background: #fff !important; + background-color: #fff !important; + border-top: 1px solid #d9d9d9 !important; + box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.12) !important; + pointer-events: auto !important; + + .o_statusbar_buttons, + .o_statusbar_buttons > * { + visibility: visible !important; + opacity: 1 !important; + } + + .o_statusbar_buttons button, + .o_statusbar_buttons .btn, + .o_statusbar_buttons a.btn { + visibility: visible !important; + opacity: 1 !important; + text-indent: 0 !important; + -webkit-text-fill-color: currentColor !important; + } } }