2025-12-15 11:06:42 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 安全区域和导航栏尺寸组合式函数
|
|
|
|
|
|
* 用于获取微信小程序的状态栏、胶囊按钮、导航栏高度
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
// 胶囊按钮信息
|
|
|
|
|
|
interface MenuButtonInfo {
|
|
|
|
|
|
width: number
|
|
|
|
|
|
height: number
|
|
|
|
|
|
top: number
|
|
|
|
|
|
right: number
|
|
|
|
|
|
bottom: number
|
|
|
|
|
|
left: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 全局单例状态(避免重复计算)
|
2026-07-08 08:18:02 +08:00
|
|
|
|
const systemBarHeight = ref(0)
|
2025-12-15 11:06:42 +08:00
|
|
|
|
const systemInfo = ref<UniApp.GetSystemInfoResult | null>(null)
|
|
|
|
|
|
const menuButtonInfo = ref<MenuButtonInfo | null>(null)
|
|
|
|
|
|
const isInitialized = ref(false)
|
|
|
|
|
|
|
2026-07-08 08:18:02 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 将状态栏高度注入全局 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 11:06:42 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 初始化安全区域数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
function initSafeArea() {
|
|
|
|
|
|
if (isInitialized.value) return
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
systemInfo.value = uni.getSystemInfoSync()
|
2026-07-08 08:18:02 +08:00
|
|
|
|
const barHeight = systemInfo.value.statusBarHeight || 0
|
|
|
|
|
|
// 非 MP 端若获取失败,使用 20px 兜底(iPhone 通常 44-47px)
|
|
|
|
|
|
const effectiveHeight = barHeight > 0 ? barHeight : 20
|
|
|
|
|
|
injectStatusBarCSS(effectiveHeight)
|
2025-12-15 11:06:42 +08:00
|
|
|
|
|
|
|
|
|
|
// #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(() => {
|
2026-07-08 08:18:02 +08:00
|
|
|
|
return systemInfo.value?.statusBarHeight || systemBarHeight.value || 20
|
2025-12-15 11:06:42 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-07-08 08:18:02 +08:00
|
|
|
|
/** 状态栏高度 CSS 值,供页面 :style 绑定 */
|
|
|
|
|
|
const statusBarHeightPx = computed(() => `${statusBarHeight.value}px`)
|
|
|
|
|
|
|
2025-12-15 11:06:42 +08:00
|
|
|
|
// 导航栏内容高度(不含状态栏)
|
|
|
|
|
|
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,
|
2026-07-08 08:18:02 +08:00
|
|
|
|
statusBarHeightPx,
|
2025-12-15 11:06:42 +08:00
|
|
|
|
navBarContentHeight,
|
|
|
|
|
|
navBarTotalHeight,
|
|
|
|
|
|
menuButtonRight,
|
|
|
|
|
|
safeAreaRight,
|
|
|
|
|
|
init
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出初始化函数,可在 App.vue 中调用
|
|
|
|
|
|
export { initSafeArea }
|
|
|
|
|
|
|
|
|
|
|
|
export default useSafeArea
|
|
|
|
|
|
|