Files
nl-im-uniapp/src/composables/useSafeArea.ts
2026-07-08 08:18:02 +08:00

176 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 安全区域和导航栏尺寸组合式函数
* 用于获取微信小程序的状态栏、胶囊按钮、导航栏高度
*/
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<UniApp.GetSystemInfoResult | null>(null)
const menuButtonInfo = ref<MenuButtonInfo | null>(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