/** * 移动端交互优化工具 */ // 检测移动设备 export const isMobileDevice = () => { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) } // 检测触摸设备 export const isTouchDevice = () => { return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 } // 检测设备方向 export const getOrientation = () => { return window.innerWidth > window.innerHeight ? 'landscape' : 'portrait' } // 监听方向变化 export const onOrientationChange = (callback) => { let orientation = getOrientation() const handleResize = () => { const newOrientation = getOrientation() if (newOrientation !== orientation) { orientation = newOrientation callback(newOrientation) } } window.addEventListener('resize', handleResize) return () => { window.removeEventListener('resize', handleResize) } } // 防止iOS缩放 export const preventIOSZoom = () => { let lastTouchEnd = 0 const preventZoom = (e) => { const now = Date.now() if (now - lastTouchEnd <= 300) { e.preventDefault() } lastTouchEnd = now } document.addEventListener('touchend', preventZoom, { passive: false }) return () => { document.removeEventListener('touchend', preventZoom) } } // 添加触摸反馈 export const addTouchFeedback = (element, options = {}) => { const { className = 'touch-feedback', duration = 150, scale = 0.98 } = options if (!element) return const style = document.createElement('style') style.textContent = ` .${className} { transform: scale(${scale}); transition: transform ${duration}ms ease; } ` document.head.appendChild(style) const addFeedback = () => { element.classList.add(className) setTimeout(() => { element.classList.remove(className) }, duration) } element.addEventListener('touchstart', addFeedback, { passive: true }) return () => { element.removeEventListener('touchstart', addFeedback) if (style.parentNode) { style.parentNode.removeChild(style) } } } // 滚动到顶部 export const scrollToTop = (smooth = true) => { window.scrollTo({ top: 0, behavior: smooth ? 'smooth' : 'auto' }) } // 滚动到元素 export const scrollToElement = (element, options = {}) => { const { behavior = 'smooth', block = 'start', inline = 'nearest', offset = 0 } = options if (!element) return const elementTop = element.offsetTop + offset window.scrollTo({ top: elementTop, behavior }) } // 获取安全区域 export const getSafeArea = () => { const style = getComputedStyle(document.documentElement) return { top: parseInt(style.getPropertyValue('env(safe-area-inset-top)')) || 0, right: parseInt(style.getPropertyValue('env(safe-area-inset-right)')) || 0, bottom: parseInt(style.getPropertyValue('env(safe-area-inset-bottom)')) || 0, left: parseInt(style.getPropertyValue('env(safe-area-inset-left)')) || 0 } } // 处理键盘弹起 export const handleVirtualKeyboard = () => { let initialViewportHeight = window.innerHeight const handleResize = () => { const currentHeight = window.innerHeight const heightDifference = initialViewportHeight - currentHeight // 键盘弹起时的处理(高度差超过150px认为是键盘弹起) if (heightDifference > 150) { document.body.classList.add('keyboard-open') document.body.style.height = `${currentHeight}px` } else { document.body.classList.remove('keyboard-open') document.body.style.height = '' } } window.addEventListener('resize', handleResize) return () => { window.removeEventListener('resize', handleResize) document.body.classList.remove('keyboard-open') document.body.style.height = '' } } // 防抖函数 export const debounce = (func, wait) => { let timeout return function executedFunction(...args) { const later = () => { clearTimeout(timeout) func(...args) } clearTimeout(timeout) timeout = setTimeout(later, wait) } } // 节流函数 export const throttle = (func, limit) => { let inThrottle return function(...args) { if (!inThrottle) { func.apply(this, args) inThrottle = true setTimeout(() => inThrottle = false, limit) } } } // 复制到剪贴板 export const copyToClipboard = async (text) => { try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text) return true } else { // 降级方案 const textArea = document.createElement('textarea') textArea.value = text textArea.style.position = 'fixed' textArea.style.left = '-999999px' textArea.style.top = '-999999px' document.body.appendChild(textArea) textArea.focus() textArea.select() try { document.execCommand('copy') return true } catch (err) { console.error('复制失败:', err) return false } finally { textArea.remove() } } } catch (err) { console.error('复制失败:', err) return false } } // 震动反馈 export const vibrate = (pattern = 50) => { if ('vibrate' in navigator) { navigator.vibrate(pattern) } } // 全屏显示 export const requestFullscreen = (element = document.documentElement) => { if (element.requestFullscreen) { return element.requestFullscreen() } else if (element.webkitRequestFullscreen) { return element.webkitRequestFullscreen() } else if (element.msRequestFullscreen) { return element.msRequestFullscreen() } } // 退出全屏 export const exitFullscreen = () => { if (document.exitFullscreen) { return document.exitFullscreen() } else if (document.webkitExitFullscreen) { return document.webkitExitFullscreen() } else if (document.msExitFullscreen) { return document.msExitFullscreen() } } // 检测网络状态 export const getNetworkStatus = () => { if ('connection' in navigator) { const connection = navigator.connection return { online: navigator.onLine, effectiveType: connection.effectiveType, downlink: connection.downlink, rtt: connection.rtt, saveData: connection.saveData } } return { online: navigator.onLine } } // 监听网络变化 export const onNetworkChange = (callback) => { const handleOnline = () => callback({ online: true }) const handleOffline = () => callback({ online: false }) window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) return () => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) } } // PWA安装提示 export const handlePWAInstall = () => { let deferredPrompt const beforeInstallPrompt = (e) => { e.preventDefault() deferredPrompt = e } const showInstallPrompt = async () => { if (deferredPrompt) { deferredPrompt.prompt() const { outcome } = await deferredPrompt.userChoice deferredPrompt = null return outcome === 'accepted' } return false } window.addEventListener('beforeinstallprompt', beforeInstallPrompt) return { showInstallPrompt, cleanup: () => { window.removeEventListener('beforeinstallprompt', beforeInstallPrompt) } } } export default { isMobileDevice, isTouchDevice, getOrientation, onOrientationChange, preventIOSZoom, addTouchFeedback, scrollToTop, scrollToElement, getSafeArea, handleVirtualKeyboard, debounce, throttle, copyToClipboard, vibrate, requestFullscreen, exitFullscreen, getNetworkStatus, onNetworkChange, handlePWAInstall }