/** @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); } }); } }