Files
dukang/apps/mini-user/src/lib/nav-bar.ts
T
jacy 2ebdb08a2f fix(mini-user): 门店详情加载、标题居中与走马灯展示
修复仅用 useDidShow 导致一直加载中;顶栏标题左右等宽居中;走马灯改用 View 双份文本动画。发版默认改生产。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 14:28:59 +08:00

131 lines
4.4 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 { 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<string, string> {
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<string, string | number> {
return {
paddingTop: `${metrics.statusBarHeight}px`,
height: `${metrics.navBarHeight}px`,
...pageShellCssVars(metrics),
};
}
/** Tab 顶栏内容行:左右留白(标题单独全屏居中) */
export function tabNavContentStyle(metrics: NavBarMetrics): Record<string, string | number> {
return {
height: `${metrics.navContentHeight}px`,
paddingLeft: '20px',
paddingRight: `${metrics.navBarPaddingRight}px`,
boxSizing: 'border-box',
};
}
/** 子页/内页顶栏:标题全屏居中,内容区单独留白 */
export function subPageNavBarStyle(metrics: NavBarMetrics): Record<string, string | number> {
const pagePad = 20;
// 标题两侧取「右侧避让胶囊」宽度,保证相对屏幕视觉居中
const titlePad = Math.max(metrics.navBarPaddingRight, 72);
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': `${titlePad}px`,
};
}
/** 子页顶栏内容行:左侧页边距 + 右侧避让胶囊 */
export function subPageNavContentStyle(metrics: NavBarMetrics): Record<string, string | number> {
return {
height: `${metrics.navContentHeight}px`,
paddingLeft: '20px',
paddingRight: `${metrics.navBarPaddingRight}px`,
boxSizing: 'border-box',
};
}