import { useMemo } from 'react'; import Taro from '@tarojs/taro'; export type NavBarMetrics = { /** 状态栏高度(刘海/灵动岛上方) */ statusBarHeight: number; /** 导航栏总高度 = 状态栏 + 内容区 */ navBarHeight: number; /** 标题栏内容区高度(与胶囊对齐) */ navContentHeight: number; /** 右侧留白,避免与微信胶囊按钮重叠 */ navBarPaddingRight: number; /** 左侧留白,与右侧对称以实现标题视觉居中 */ navBarPaddingLeft: number; }; const H5_FALLBACK: NavBarMetrics = { statusBarHeight: 0, navBarHeight: 56, navContentHeight: 56, /** 避开微信内置浏览器右上角 ··· / 设置入口(约一颗胶囊宽) */ navBarPaddingRight: 96, navBarPaddingLeft: 16, }; const WEAPP_FALLBACK: NavBarMetrics = { statusBarHeight: 20, navBarHeight: 64, navContentHeight: 44, navBarPaddingRight: 96, navBarPaddingLeft: 96, }; /** 计算小程序自定义导航栏尺寸(对齐微信胶囊按钮) */ export function getNavBarMetrics(): NavBarMetrics { if (process.env.TARO_ENV === 'h5') { // H5:在微信浏览器内额外避让右上角菜单;非微信保持较小右侧留白 const inWechat = typeof navigator !== 'undefined' && /MicroMessenger/i.test(navigator.userAgent || ''); return { ...H5_FALLBACK, navBarPaddingRight: inWechat ? 96 : 16, }; } try { const win = Taro.getWindowInfo?.() ?? Taro.getSystemInfoSync(); const menu = Taro.getMenuButtonBoundingClientRect(); const statusBarHeight = win.statusBarHeight ?? WEAPP_FALLBACK.statusBarHeight; const navContentHeight = menu.height > 0 ? (menu.top - statusBarHeight) * 2 + menu.height : WEAPP_FALLBACK.navContentHeight; const navBarHeight = statusBarHeight + navContentHeight; const navBarPaddingRight = menu.width > 0 ? Math.max(win.windowWidth - menu.left + 8, 16) : WEAPP_FALLBACK.navBarPaddingRight; return { statusBarHeight, navBarHeight, navContentHeight, navBarPaddingRight, navBarPaddingLeft: navBarPaddingRight, }; } catch { return WEAPP_FALLBACK; } } export function useNavBarMetrics(): NavBarMetrics { return useMemo(() => getNavBarMetrics(), []); } /** 仅注入 CSS 变量,供 PageShell / sticky 子元素使用(不加 height/padding) */ export function pageShellCssVars(metrics: NavBarMetrics): Record { return { '--nav-bar-height': `${metrics.navBarHeight}px`, '--nav-content-height': `${metrics.navContentHeight}px`, '--nav-status-bar-height': `${metrics.statusBarHeight}px`, '--nav-padding-right': `${metrics.navBarPaddingRight}px`, '--nav-padding-left': `${metrics.navBarPaddingLeft}px`, }; } /** 顶栏自身样式:statusBar padding + 总高 + CSS 变量 */ export function navBarStyle(metrics: NavBarMetrics): Record { return { paddingTop: `${metrics.statusBarHeight}px`, height: `${metrics.navBarHeight}px`, ...pageShellCssVars(metrics), }; } /** Tab 顶栏内容行:左右留白(标题单独全屏居中) */ export function tabNavContentStyle(metrics: NavBarMetrics): Record { return { height: `${metrics.navContentHeight}px`, paddingLeft: '20px', paddingRight: `${metrics.navBarPaddingRight}px`, boxSizing: 'border-box', }; } /** 子页/内页顶栏:标题全屏居中,内容区单独留白 */ export function subPageNavBarStyle(metrics: NavBarMetrics): Record { const pagePad = 20; return { paddingTop: `${metrics.statusBarHeight}px`, height: `${metrics.navBarHeight}px`, '--nav-bar-height': `${metrics.navBarHeight}px`, '--nav-content-height': `${metrics.navContentHeight}px`, '--nav-status-bar-height': `${metrics.statusBarHeight}px`, '--nav-padding-left': `${pagePad}px`, '--nav-padding-right': `${metrics.navBarPaddingRight}px`, }; } /** 子页顶栏内容行:左侧页边距 + 右侧避让胶囊 */ export function subPageNavContentStyle(metrics: NavBarMetrics): Record { return { height: `${metrics.navContentHeight}px`, paddingLeft: '20px', paddingRight: `${metrics.navBarPaddingRight}px`, boxSizing: 'border-box', }; }