/** * 安全区域和导航栏尺寸组合式函数 * 用于获取微信小程序的状态栏、胶囊按钮、导航栏高度 */ import { ref, computed, onMounted } from 'vue' // 胶囊按钮信息 interface MenuButtonInfo { width: number height: number top: number right: number bottom: number left: number } // 全局单例状态(避免重复计算) const systemBarHeight = ref(0) const systemInfo = ref(null) const menuButtonInfo = ref(null) const isInitialized = ref(false) /** * 将状态栏高度注入全局 CSS 变量 --status-bar-height * APP/H5 端页面依赖此变量计算导航栏 padding */ function injectStatusBarCSS(height: number) { const px = `${height}px` systemBarHeight.value = height // #ifdef H5 if (typeof document !== 'undefined') { document.documentElement.style.setProperty('--status-bar-height', px) } // #endif // #ifdef APP-PLUS if (typeof document !== 'undefined') { document.documentElement.style.setProperty('--status-bar-height', px) } // #endif // #ifdef MP-WEIXIN // 微信小程序框架会注入,此处同步写入以便自定义导航栏一致 if (typeof document !== 'undefined') { document.documentElement.style.setProperty('--status-bar-height', px) } // #endif } /** * 初始化安全区域数据 */ function initSafeArea() { if (isInitialized.value) return try { systemInfo.value = uni.getSystemInfoSync() const barHeight = systemInfo.value.statusBarHeight || 0 // 非 MP 端若获取失败,使用 20px 兜底(iPhone 通常 44-47px) const effectiveHeight = barHeight > 0 ? barHeight : 20 injectStatusBarCSS(effectiveHeight) // #ifdef MP-WEIXIN // 获取微信小程序胶囊按钮位置信息 try { const rect = uni.getMenuButtonBoundingClientRect() menuButtonInfo.value = { width: rect.width, height: rect.height, top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left } console.log('📱 [SafeArea] 胶囊按钮信息:', menuButtonInfo.value) } catch (e) { console.warn('⚠️ [SafeArea] 获取胶囊按钮信息失败:', e) } // #endif isInitialized.value = true } catch (e) { console.error('❌ [SafeArea] 初始化失败:', e) } } export function useSafeArea() { // 状态栏高度 const statusBarHeight = computed(() => { return systemInfo.value?.statusBarHeight || systemBarHeight.value || 20 }) /** 状态栏高度 CSS 值,供页面 :style 绑定 */ const statusBarHeightPx = computed(() => `${statusBarHeight.value}px`) // 导航栏内容高度(不含状态栏) const navBarContentHeight = computed(() => { // #ifdef MP-WEIXIN if (menuButtonInfo.value) { // 导航栏高度 = (胶囊按钮距顶部距离 - 状态栏高度) * 2 + 胶囊按钮高度 const paddingTop = menuButtonInfo.value.top - statusBarHeight.value return paddingTop * 2 + menuButtonInfo.value.height } return 48 // 微信小程序默认值 // #endif // #ifndef MP-WEIXIN return 44 // H5/App 默认值 // #endif }) // 导航栏总高度(状态栏 + 导航栏内容) const navBarTotalHeight = computed(() => { return statusBarHeight.value + navBarContentHeight.value }) // 胶囊按钮右侧边距(用于计算导航栏右侧安全区域) const menuButtonRight = computed(() => { // #ifdef MP-WEIXIN if (menuButtonInfo.value && systemInfo.value) { // 屏幕宽度 - 胶囊按钮右边距 return systemInfo.value.windowWidth - menuButtonInfo.value.right } return 10 // 默认 10px // #endif // #ifndef MP-WEIXIN return 0 // #endif }) // 安全区域右边距(避开胶囊按钮的宽度) const safeAreaRight = computed(() => { // #ifdef MP-WEIXIN if (menuButtonInfo.value && systemInfo.value) { // 胶囊按钮宽度 + 右边距 + 额外间距 return menuButtonInfo.value.width + menuButtonRight.value + 10 } return 100 // 默认 100px // #endif // #ifndef MP-WEIXIN return 0 // #endif }) // 初始化(在组件挂载时调用) onMounted(() => { initSafeArea() }) // 也可以手动初始化 function init() { initSafeArea() } return { systemInfo, menuButtonInfo, statusBarHeight, statusBarHeightPx, navBarContentHeight, navBarTotalHeight, menuButtonRight, safeAreaRight, init } } // 导出初始化函数,可在 App.vue 中调用 export { initSafeArea } export default useSafeArea