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 6fa0ea98e..faf93cf45 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 @@ -2,8 +2,408 @@ import { StatusBarButtons } from "@web/views/form/status_bar_buttons/status_bar_buttons"; import { patch } from "@web/core/utils/patch"; +import { onMounted, onWillUnmount, useEffect, useRef } from "@odoo/owl"; -// 覆盖 StatusBarButtons 组件的模板 +// 检测是否为移动设备(包括iOS Safari) +const isMobileDevice = () => { + if (typeof window === 'undefined') return false; + + // 检测触摸设备 + const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + + // 检测用户代理(iOS设备) + const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; + + // 检测屏幕宽度 + const isSmallScreen = window.innerWidth <= 767; + + // 检测是否为移动设备(综合考虑) + return hasTouch && (isIOS || isSmallScreen); +}; + +// 覆盖 StatusBarButtons 组件,添加移动端检测逻辑 patch(StatusBarButtons, { template: "k_ry_mobile_form.StatusBarButtons", + setup() { + const mobileBarRef = useRef("mobileBarRoot"); + let restoreMobileBarPlacement = null; + + // 将移动端底部栏挂到表单根节点,与 .o_form_view_container 同级(卸载前移回,便于 OWL 正确销毁) + onMounted(() => { + const mobile = mobileBarRef.el; + if (!mobile) { + return; + } + const formRoot = mobile.closest(".o_form_view"); + if (!formRoot || mobile.parentNode === formRoot) { + return; + } + const origParent = mobile.parentNode; + const origNext = mobile.nextSibling; + formRoot.appendChild(mobile); + restoreMobileBarPlacement = () => { + if (origParent && mobile.parentNode === formRoot) { + origParent.insertBefore(mobile, origNext); + } + }; + }); + + onWillUnmount(() => { + restoreMobileBarPlacement?.(); + }); + + // 如果 env.isSmall 判断不准确,强制显示移动端按钮 + if (typeof window !== 'undefined') { + 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 computedStyle = getComputedStyle(button); + const bgColor = computedStyle.backgroundColor; + const textColor = computedStyle.color; + + // 如果背景色是透明或白色,且文字颜色也是白色或透明,强制设置颜色 + const isTransparent = bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)'; + const isWhiteBg = bgColor === 'rgb(255, 255, 255)' || bgColor === '#ffffff' || bgColor === '#fff'; + const isWhiteText = textColor === 'rgb(255, 255, 255)' || textColor === '#ffffff' || textColor === '#fff'; + const isTransparentText = textColor === 'transparent' || textColor === 'rgba(0, 0, 0, 0)'; + + // 如果按钮不可见,强制设置颜色 + if (isTransparent || (isWhiteBg && (isWhiteText || isTransparentText)) || textColor === 'rgba(0, 0, 0, 0)') { + // 根据按钮类型设置颜色 + const hasPrimary = button.classList.contains('btn-primary') || button.classList.contains('o_statusbar_status'); + const hasSecondary = button.classList.contains('btn-secondary'); + const hasSuccess = button.classList.contains('btn-success'); + const hasDanger = button.classList.contains('btn-danger'); + const hasWarning = button.classList.contains('btn-warning'); + const hasInfo = button.classList.contains('btn-info'); + + if (hasPrimary || hasSuccess) { + button.style.setProperty('background-color', '#007bff', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasSecondary) { + button.style.setProperty('background-color', '#6c757d', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasDanger) { + button.style.setProperty('background-color', '#dc3545', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasWarning) { + button.style.setProperty('background-color', '#ffc107', 'important'); + button.style.setProperty('color', '#212529', 'important'); + } else if (hasInfo) { + button.style.setProperty('background-color', '#17a2b8', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else { + // 默认蓝色按钮 + button.style.setProperty('background-color', '#007bff', 'important'); + 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 hiding desktop buttons in setup:', e); + } + } + } catch (e) { + console.warn('Error in checkAndFixMobileButtons:', e); + } + }; + + // 延迟执行,确保DOM已渲染,不阻塞页面加载 + setTimeout(checkAndFixMobileButtons, 200); + + // 监听窗口大小变化 + const handleResize = () => { + setTimeout(checkAndFixMobileButtons, 100); + }; + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }); + } + }, }); + +// iOS Safari修复:确保移动端按钮在iOS上可点击和可见 +// 使用MutationObserver监听DOM变化,确保新添加的按钮也能正常工作 +if (typeof window !== 'undefined' && typeof document !== 'undefined') { + let fixTimeout = null; + let observer = null; + let checkInterval = null; + + // 防抖函数,避免频繁执行 + const debounce = (func, wait) => { + return function executedFunction(...args) { + const later = () => { + clearTimeout(fixTimeout); + func(...args); + }; + clearTimeout(fixTimeout); + fixTimeout = setTimeout(later, wait); + }; + }; + + const fixIOSButtons = () => { + try { + // 确保移动端按钮容器可见 + const containers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed'); + if (containers.length === 0) return; + + 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'); + } + + // 修复按钮的交互性和可见性 + const buttons = container.querySelectorAll('button, a.btn, .btn, a[role="button"], .o_statusbar_buttons > * > button, .o_statusbar_buttons > * > a'); + buttons.forEach(button => { + try { + // 使用 setProperty 强制设置样式 + button.style.setProperty('pointer-events', 'auto', 'important'); + button.style.setProperty('cursor', 'pointer', 'important'); + button.style.setProperty('touch-action', 'manipulation', 'important'); + button.style.setProperty('visibility', 'visible', 'important'); + button.style.setProperty('opacity', '1', 'important'); + button.style.setProperty('display', 'inline-block', 'important'); + // 确保按钮有最小高度和字体大小 + button.style.setProperty('min-height', '44px', '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); + const bgColor = computedStyle.backgroundColor; + const textColor = computedStyle.color; + + // 如果背景色是透明或白色,且文字颜色也是白色或透明,强制设置颜色 + const isTransparent = bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)'; + const isWhiteBg = bgColor === 'rgb(255, 255, 255)' || bgColor === '#ffffff' || bgColor === '#fff'; + const isWhiteText = textColor === 'rgb(255, 255, 255)' || textColor === '#ffffff' || textColor === '#fff'; + const isTransparentText = textColor === 'transparent' || textColor === 'rgba(0, 0, 0, 0)'; + + // 如果按钮不可见,强制设置颜色 + if (isTransparent || (isWhiteBg && (isWhiteText || isTransparentText)) || textColor === 'rgba(0, 0, 0, 0)') { + // 根据按钮类型设置颜色 + const hasPrimary = button.classList.contains('btn-primary') || button.classList.contains('o_statusbar_status'); + const hasSecondary = button.classList.contains('btn-secondary'); + const hasSuccess = button.classList.contains('btn-success'); + const hasDanger = button.classList.contains('btn-danger'); + const hasWarning = button.classList.contains('btn-warning'); + const hasInfo = button.classList.contains('btn-info'); + + if (hasPrimary || hasSuccess) { + button.style.setProperty('background-color', '#007bff', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasSecondary) { + button.style.setProperty('background-color', '#6c757d', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasDanger) { + button.style.setProperty('background-color', '#dc3545', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasWarning) { + button.style.setProperty('background-color', '#ffc107', 'important'); + button.style.setProperty('color', '#212529', 'important'); + } else if (hasInfo) { + button.style.setProperty('background-color', '#17a2b8', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else { + // 默认蓝色按钮 + button.style.setProperty('background-color', '#007bff', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } + } + } catch (e) { + console.warn('Error fixing button:', e); + } + }); + } catch (e) { + console.warn('Error fixing container:', e); + } + }); + + // 在移动设备上,确保原始状态栏中的按钮被隐藏 + if (isMobileDevice()) { + 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:', e); + } + } + } catch (e) { + console.warn('Error in fixIOSButtons:', e); + } + }; + + // 防抖版本的修复函数 + const debouncedFixIOSButtons = debounce(fixIOSButtons, 300); + + // 初始化函数:确保DOM准备好后再设置observer + const initIOSButtonFix = () => { + try { + // 先执行一次修复 + fixIOSButtons(); + + // 确保document.body存在后再创建observer + if (document.body && !observer) { + // 监听DOM变化,确保动态添加的按钮也能正常工作 + observer = new MutationObserver((mutations) => { + // 只处理相关的变化 + const hasRelevantChange = mutations.some(mutation => { + if (mutation.type === 'childList') { + return Array.from(mutation.addedNodes).some(node => + node.nodeType === 1 && ( + node.classList?.contains('o_statusbar_buttons_mobile_fixed') || + node.querySelector?.('.o_statusbar_buttons_mobile_fixed') + ) + ); + } + if (mutation.type === 'attributes') { + return mutation.target.classList?.contains('o_statusbar_buttons_mobile_fixed') || + mutation.target.closest?.('.o_statusbar_buttons_mobile_fixed'); + } + return false; + }); + + if (hasRelevantChange) { + debouncedFixIOSButtons(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['style', 'class'] + }); + } + } catch (e) { + console.warn('Error initializing button fix:', e); + } + }; + + // 页面加载后执行 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initIOSButtonFix, { once: true }); + } else { + // 使用setTimeout确保DOM完全准备好,不阻塞页面加载 + setTimeout(initIOSButtonFix, 100); + } + + // 监听窗口大小变化和方向变化(iOS Safari 横竖屏切换) + window.addEventListener('resize', () => { + debouncedFixIOSButtons(); + }, { passive: true }); + + window.addEventListener('orientationchange', () => { + setTimeout(debouncedFixIOSButtons, 200); + }, { passive: true }); + + // 定期检查按钮是否可见(防止被其他样式覆盖) + if (isMobileDevice()) { + // 延迟启动定时器,避免影响页面加载 + setTimeout(() => { + checkInterval = setInterval(() => { + try { + const containers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed'); + if (containers.length > 0) { + containers.forEach(container => { + try { + const computedStyle = getComputedStyle(container); + // 如果按钮被隐藏,强制显示 + if (computedStyle.display === 'none' || + computedStyle.visibility === 'hidden' || + parseFloat(computedStyle.opacity) < 0.1) { + fixIOSButtons(); + } + } catch (e) { + console.warn('Error checking container:', e); + } + }); + } + } catch (e) { + console.warn('Error in interval check:', e); + } + }, 2000); // 改为每2秒检查一次,减少频率 + }, 2000); + + // 页面卸载时清除定时器和observer + window.addEventListener('beforeunload', () => { + if (checkInterval) { + clearInterval(checkInterval); + } + if (observer) { + observer.disconnect(); + } + if (fixTimeout) { + clearTimeout(fixTimeout); + } + }); + } +} diff --git a/k_ry_mobile_form/static/src/js/status_bar_buttons22.js b/k_ry_mobile_form/static/src/js/status_bar_buttons22.js new file mode 100644 index 000000000..6fa0ea98e --- /dev/null +++ b/k_ry_mobile_form/static/src/js/status_bar_buttons22.js @@ -0,0 +1,9 @@ +/** @odoo-module **/ + +import { StatusBarButtons } from "@web/views/form/status_bar_buttons/status_bar_buttons"; +import { patch } from "@web/core/utils/patch"; + +// 覆盖 StatusBarButtons 组件的模板 +patch(StatusBarButtons, { + template: "k_ry_mobile_form.StatusBarButtons", +}); 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 603c223c9..6faae2ff6 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 @@ -1,44 +1,25 @@ // Mobile: 底部悬浮栏样式 .o_statusbar_buttons_mobile_fixed { - position: fixed !important; // iOS 必须使用 !important - bottom: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 9999 !important; // 使用更高的 z-index - background-color: $o-view-background-color !important; + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 1050; // 确保在其他元素之上,但低于模态框 + background-color: $o-view-background-color; border-top: 1px solid $border-color; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); padding: map-get($spacers, 2) map-get($spacers, 3); // 为底部悬浮栏预留空间,避免内容被遮挡(支持 iOS safe area) padding-bottom: calc(#{map-get($spacers, 2)} + env(safe-area-inset-bottom, 0px)); - - // iOS 优化:强制 GPU 加速和可见性 - -webkit-transform: translateZ(0); - transform: translateZ(0); - -webkit-backface-visibility: visible; - backface-visibility: visible; - -webkit-perspective: 1000; - perspective: 1000; - -webkit-transform-style: preserve-3d; - transform-style: preserve-3d; - - // 确保容器有明确的高度和背景 - min-height: 60px; - background: $o-view-background-color; - + .o_statusbar_buttons { max-width: 100%; width: 100%; - display: flex !important; // iOS 需要强制 flex 显示 - flex-direction: row !important; - align-items: center !important; - justify-content: center !important; - + > * { flex: 1; min-width: 0; - display: block !important; // 确保子元素显示 - + > button { width: 100%; white-space: nowrap; @@ -46,22 +27,6 @@ text-overflow: ellipsis; // 确保按钮有足够的点击区域 min-height: 44px; - - // iOS 优化:确保按钮可见且可点击 - opacity: 1 !important; - visibility: visible !important; - display: inline-block !important; - -webkit-appearance: none; - appearance: none; - - // 确保按钮背景和边框可见 - background-color: inherit; - position: relative; - z-index: 1; - - // 强制文本显示 - color: inherit; - font-size: inherit; } } } @@ -82,8 +47,8 @@ display: none; } } - - // 为 form view 加底部 padding,避免内容被底部悬浮栏遮挡 + + // 为 form view 添加底部 padding,避免内容被底部悬浮栏遮挡 .o_form_view { padding-bottom: 80px; // 为底部悬浮栏预留空间 } @@ -91,14 +56,3 @@ position: relative !important; } } - -// iOS 专用调试样式 - 如果还是看不到,启用这个来诊断 -// .o_statusbar_buttons_mobile_fixed { -// background-color: rgba(255, 0, 0, 0.3) !important; -// border: 2px solid red !important; -// } -// .o_statusbar_buttons_mobile_fixed button { -// background-color: rgba(0, 255, 0, 0.3) !important; -// border: 1px solid green !important; -// color: black !important; -// } diff --git a/k_ry_mobile_form/static/src/scss/status_bar_buttons222.scss b/k_ry_mobile_form/static/src/scss/status_bar_buttons222.scss deleted file mode 100644 index 6faae2ff6..000000000 --- a/k_ry_mobile_form/static/src/scss/status_bar_buttons222.scss +++ /dev/null @@ -1,58 +0,0 @@ -// Mobile: 底部悬浮栏样式 -.o_statusbar_buttons_mobile_fixed { - position: fixed; - bottom: 0; - left: 0; - right: 0; - z-index: 1050; // 确保在其他元素之上,但低于模态框 - background-color: $o-view-background-color; - border-top: 1px solid $border-color; - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); - padding: map-get($spacers, 2) map-get($spacers, 3); - // 为底部悬浮栏预留空间,避免内容被遮挡(支持 iOS safe area) - padding-bottom: calc(#{map-get($spacers, 2)} + env(safe-area-inset-bottom, 0px)); - - .o_statusbar_buttons { - max-width: 100%; - width: 100%; - - > * { - flex: 1; - min-width: 0; - - > button { - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - // 确保按钮有足够的点击区域 - min-height: 44px; - } - } - } -} - -// 桌面端隐藏移动端悬浮栏 -@include media-breakpoint-up(md) { - .o_statusbar_buttons_mobile_fixed { - display: none !important; - } -} - -// 移动端隐藏原始状态栏中的按钮(避免重复显示) -@include media-breakpoint-down(md) { - .o_form_statusbar { - // 隐藏状态栏中的按钮容器,但保留字段等其他内容 - > .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons) { - display: none; - } - } - - // 为 form view 添加底部 padding,避免内容被底部悬浮栏遮挡 - .o_form_view { - padding-bottom: 80px; // 为底部悬浮栏预留空间 - } - .o_content .o_form_statusbar { - position: relative !important; - } -} diff --git a/k_ry_mobile_form/static/src/xml/status_bar_buttons.xml b/k_ry_mobile_form/static/src/xml/status_bar_buttons.xml index 875d06036..def93482c 100644 --- a/k_ry_mobile_form/static/src/xml/status_bar_buttons.xml +++ b/k_ry_mobile_form/static/src/xml/status_bar_buttons.xml @@ -4,24 +4,21 @@ - - -
-
- - - -
-
-
- - -
+ + +
+
- +
+ +
+ + + +
diff --git a/k_ry_mobile_form/status_bar_buttons.js b/k_ry_mobile_form/status_bar_buttons.js new file mode 100644 index 000000000..faf93cf45 --- /dev/null +++ b/k_ry_mobile_form/status_bar_buttons.js @@ -0,0 +1,409 @@ +/** @odoo-module **/ + +import { StatusBarButtons } from "@web/views/form/status_bar_buttons/status_bar_buttons"; +import { patch } from "@web/core/utils/patch"; +import { onMounted, onWillUnmount, useEffect, useRef } from "@odoo/owl"; + +// 检测是否为移动设备(包括iOS Safari) +const isMobileDevice = () => { + if (typeof window === 'undefined') return false; + + // 检测触摸设备 + const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + + // 检测用户代理(iOS设备) + const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; + + // 检测屏幕宽度 + const isSmallScreen = window.innerWidth <= 767; + + // 检测是否为移动设备(综合考虑) + return hasTouch && (isIOS || isSmallScreen); +}; + +// 覆盖 StatusBarButtons 组件,添加移动端检测逻辑 +patch(StatusBarButtons, { + template: "k_ry_mobile_form.StatusBarButtons", + setup() { + const mobileBarRef = useRef("mobileBarRoot"); + let restoreMobileBarPlacement = null; + + // 将移动端底部栏挂到表单根节点,与 .o_form_view_container 同级(卸载前移回,便于 OWL 正确销毁) + onMounted(() => { + const mobile = mobileBarRef.el; + if (!mobile) { + return; + } + const formRoot = mobile.closest(".o_form_view"); + if (!formRoot || mobile.parentNode === formRoot) { + return; + } + const origParent = mobile.parentNode; + const origNext = mobile.nextSibling; + formRoot.appendChild(mobile); + restoreMobileBarPlacement = () => { + if (origParent && mobile.parentNode === formRoot) { + origParent.insertBefore(mobile, origNext); + } + }; + }); + + onWillUnmount(() => { + restoreMobileBarPlacement?.(); + }); + + // 如果 env.isSmall 判断不准确,强制显示移动端按钮 + if (typeof window !== 'undefined') { + 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 computedStyle = getComputedStyle(button); + const bgColor = computedStyle.backgroundColor; + const textColor = computedStyle.color; + + // 如果背景色是透明或白色,且文字颜色也是白色或透明,强制设置颜色 + const isTransparent = bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)'; + const isWhiteBg = bgColor === 'rgb(255, 255, 255)' || bgColor === '#ffffff' || bgColor === '#fff'; + const isWhiteText = textColor === 'rgb(255, 255, 255)' || textColor === '#ffffff' || textColor === '#fff'; + const isTransparentText = textColor === 'transparent' || textColor === 'rgba(0, 0, 0, 0)'; + + // 如果按钮不可见,强制设置颜色 + if (isTransparent || (isWhiteBg && (isWhiteText || isTransparentText)) || textColor === 'rgba(0, 0, 0, 0)') { + // 根据按钮类型设置颜色 + const hasPrimary = button.classList.contains('btn-primary') || button.classList.contains('o_statusbar_status'); + const hasSecondary = button.classList.contains('btn-secondary'); + const hasSuccess = button.classList.contains('btn-success'); + const hasDanger = button.classList.contains('btn-danger'); + const hasWarning = button.classList.contains('btn-warning'); + const hasInfo = button.classList.contains('btn-info'); + + if (hasPrimary || hasSuccess) { + button.style.setProperty('background-color', '#007bff', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasSecondary) { + button.style.setProperty('background-color', '#6c757d', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasDanger) { + button.style.setProperty('background-color', '#dc3545', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasWarning) { + button.style.setProperty('background-color', '#ffc107', 'important'); + button.style.setProperty('color', '#212529', 'important'); + } else if (hasInfo) { + button.style.setProperty('background-color', '#17a2b8', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else { + // 默认蓝色按钮 + button.style.setProperty('background-color', '#007bff', 'important'); + 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 hiding desktop buttons in setup:', e); + } + } + } catch (e) { + console.warn('Error in checkAndFixMobileButtons:', e); + } + }; + + // 延迟执行,确保DOM已渲染,不阻塞页面加载 + setTimeout(checkAndFixMobileButtons, 200); + + // 监听窗口大小变化 + const handleResize = () => { + setTimeout(checkAndFixMobileButtons, 100); + }; + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }); + } + }, +}); + +// iOS Safari修复:确保移动端按钮在iOS上可点击和可见 +// 使用MutationObserver监听DOM变化,确保新添加的按钮也能正常工作 +if (typeof window !== 'undefined' && typeof document !== 'undefined') { + let fixTimeout = null; + let observer = null; + let checkInterval = null; + + // 防抖函数,避免频繁执行 + const debounce = (func, wait) => { + return function executedFunction(...args) { + const later = () => { + clearTimeout(fixTimeout); + func(...args); + }; + clearTimeout(fixTimeout); + fixTimeout = setTimeout(later, wait); + }; + }; + + const fixIOSButtons = () => { + try { + // 确保移动端按钮容器可见 + const containers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed'); + if (containers.length === 0) return; + + 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'); + } + + // 修复按钮的交互性和可见性 + const buttons = container.querySelectorAll('button, a.btn, .btn, a[role="button"], .o_statusbar_buttons > * > button, .o_statusbar_buttons > * > a'); + buttons.forEach(button => { + try { + // 使用 setProperty 强制设置样式 + button.style.setProperty('pointer-events', 'auto', 'important'); + button.style.setProperty('cursor', 'pointer', 'important'); + button.style.setProperty('touch-action', 'manipulation', 'important'); + button.style.setProperty('visibility', 'visible', 'important'); + button.style.setProperty('opacity', '1', 'important'); + button.style.setProperty('display', 'inline-block', 'important'); + // 确保按钮有最小高度和字体大小 + button.style.setProperty('min-height', '44px', '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); + const bgColor = computedStyle.backgroundColor; + const textColor = computedStyle.color; + + // 如果背景色是透明或白色,且文字颜色也是白色或透明,强制设置颜色 + const isTransparent = bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)'; + const isWhiteBg = bgColor === 'rgb(255, 255, 255)' || bgColor === '#ffffff' || bgColor === '#fff'; + const isWhiteText = textColor === 'rgb(255, 255, 255)' || textColor === '#ffffff' || textColor === '#fff'; + const isTransparentText = textColor === 'transparent' || textColor === 'rgba(0, 0, 0, 0)'; + + // 如果按钮不可见,强制设置颜色 + if (isTransparent || (isWhiteBg && (isWhiteText || isTransparentText)) || textColor === 'rgba(0, 0, 0, 0)') { + // 根据按钮类型设置颜色 + const hasPrimary = button.classList.contains('btn-primary') || button.classList.contains('o_statusbar_status'); + const hasSecondary = button.classList.contains('btn-secondary'); + const hasSuccess = button.classList.contains('btn-success'); + const hasDanger = button.classList.contains('btn-danger'); + const hasWarning = button.classList.contains('btn-warning'); + const hasInfo = button.classList.contains('btn-info'); + + if (hasPrimary || hasSuccess) { + button.style.setProperty('background-color', '#007bff', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasSecondary) { + button.style.setProperty('background-color', '#6c757d', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasDanger) { + button.style.setProperty('background-color', '#dc3545', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else if (hasWarning) { + button.style.setProperty('background-color', '#ffc107', 'important'); + button.style.setProperty('color', '#212529', 'important'); + } else if (hasInfo) { + button.style.setProperty('background-color', '#17a2b8', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } else { + // 默认蓝色按钮 + button.style.setProperty('background-color', '#007bff', 'important'); + button.style.setProperty('color', '#ffffff', 'important'); + } + } + } catch (e) { + console.warn('Error fixing button:', e); + } + }); + } catch (e) { + console.warn('Error fixing container:', e); + } + }); + + // 在移动设备上,确保原始状态栏中的按钮被隐藏 + if (isMobileDevice()) { + 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:', e); + } + } + } catch (e) { + console.warn('Error in fixIOSButtons:', e); + } + }; + + // 防抖版本的修复函数 + const debouncedFixIOSButtons = debounce(fixIOSButtons, 300); + + // 初始化函数:确保DOM准备好后再设置observer + const initIOSButtonFix = () => { + try { + // 先执行一次修复 + fixIOSButtons(); + + // 确保document.body存在后再创建observer + if (document.body && !observer) { + // 监听DOM变化,确保动态添加的按钮也能正常工作 + observer = new MutationObserver((mutations) => { + // 只处理相关的变化 + const hasRelevantChange = mutations.some(mutation => { + if (mutation.type === 'childList') { + return Array.from(mutation.addedNodes).some(node => + node.nodeType === 1 && ( + node.classList?.contains('o_statusbar_buttons_mobile_fixed') || + node.querySelector?.('.o_statusbar_buttons_mobile_fixed') + ) + ); + } + if (mutation.type === 'attributes') { + return mutation.target.classList?.contains('o_statusbar_buttons_mobile_fixed') || + mutation.target.closest?.('.o_statusbar_buttons_mobile_fixed'); + } + return false; + }); + + if (hasRelevantChange) { + debouncedFixIOSButtons(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['style', 'class'] + }); + } + } catch (e) { + console.warn('Error initializing button fix:', e); + } + }; + + // 页面加载后执行 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initIOSButtonFix, { once: true }); + } else { + // 使用setTimeout确保DOM完全准备好,不阻塞页面加载 + setTimeout(initIOSButtonFix, 100); + } + + // 监听窗口大小变化和方向变化(iOS Safari 横竖屏切换) + window.addEventListener('resize', () => { + debouncedFixIOSButtons(); + }, { passive: true }); + + window.addEventListener('orientationchange', () => { + setTimeout(debouncedFixIOSButtons, 200); + }, { passive: true }); + + // 定期检查按钮是否可见(防止被其他样式覆盖) + if (isMobileDevice()) { + // 延迟启动定时器,避免影响页面加载 + setTimeout(() => { + checkInterval = setInterval(() => { + try { + const containers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed'); + if (containers.length > 0) { + containers.forEach(container => { + try { + const computedStyle = getComputedStyle(container); + // 如果按钮被隐藏,强制显示 + if (computedStyle.display === 'none' || + computedStyle.visibility === 'hidden' || + parseFloat(computedStyle.opacity) < 0.1) { + fixIOSButtons(); + } + } catch (e) { + console.warn('Error checking container:', e); + } + }); + } + } catch (e) { + console.warn('Error in interval check:', e); + } + }, 2000); // 改为每2秒检查一次,减少频率 + }, 2000); + + // 页面卸载时清除定时器和observer + window.addEventListener('beforeunload', () => { + if (checkInterval) { + clearInterval(checkInterval); + } + if (observer) { + observer.disconnect(); + } + if (fixTimeout) { + clearTimeout(fixTimeout); + } + }); + } +} diff --git a/k_ry_mobile_form/status_bar_buttons.xml b/k_ry_mobile_form/status_bar_buttons.xml new file mode 100644 index 000000000..def93482c --- /dev/null +++ b/k_ry_mobile_form/status_bar_buttons.xml @@ -0,0 +1,24 @@ + + + + + + + + +
+
+ + + +
+
+ +
+ + + +
+
+ +