176 lines
4.6 KiB
TypeScript
176 lines
4.6 KiB
TypeScript
/**
|
||
* 安全区域和导航栏尺寸组合式函数
|
||
* 用于获取微信小程序的状态栏、胶囊按钮、导航栏高度
|
||
*/
|
||
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
|
||
|