98 lines
3.6 KiB
TypeScript
98 lines
3.6 KiB
TypeScript
import React, { useMemo } from 'react'
|
||
import Taro from '@tarojs/taro'
|
||
import { View, Text } from '@tarojs/components'
|
||
import styles from './index.module.scss'
|
||
|
||
/**
|
||
* 运行时导航栏尺寸(px,需内联样式动态应用)。
|
||
*/
|
||
export interface NavBarMetrics {
|
||
/** 状态栏高度(px) */
|
||
statusBarHeight: number
|
||
/** 导航内容区高度(px),即胶囊按钮所在行的高度 */
|
||
navContentHeight: number
|
||
/** 状态栏 + 内容区总高度(px),页面顶部留白直接使用该值 */
|
||
totalHeight: number
|
||
}
|
||
|
||
/**
|
||
* 获取自定义导航栏运行时尺寸。
|
||
*
|
||
* <p>计算规则:内容区高度 = (胶囊顶部 - 状态栏高度) × 2 + 胶囊高度,
|
||
* 保证标题与右上角胶囊按钮垂直居中对齐;非微信端(H5 预览等)
|
||
* 胶囊 API 不可用时降级为 statusBar 20px + content 44px 的通用值。</p>
|
||
*/
|
||
export function useNavBarMetrics(): NavBarMetrics {
|
||
return useMemo(() => {
|
||
let statusBarHeight = 20
|
||
let navContentHeight = 44
|
||
try {
|
||
const sysInfo = Taro.getSystemInfoSync()
|
||
statusBarHeight = sysInfo.statusBarHeight || 20
|
||
if (typeof Taro.getMenuButtonBoundingClientRect === 'function') {
|
||
const rect = Taro.getMenuButtonBoundingClientRect()
|
||
if (rect && rect.top > 0 && rect.height > 0) {
|
||
navContentHeight = (rect.top - statusBarHeight) * 2 + rect.height
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.warn('[CustomNavBar] 获取系统信息失败,使用默认导航高度:', err)
|
||
}
|
||
return { statusBarHeight, navContentHeight, totalHeight: statusBarHeight + navContentHeight }
|
||
}, [])
|
||
}
|
||
|
||
interface CustomNavBarProps {
|
||
/** 导航栏标题 */
|
||
title: string
|
||
/** 页面滚动后显示底色与阴影(透明 → 主题色),由页面根据滚动位置传入 */
|
||
solid?: boolean
|
||
/** 是否显示返回按钮(非 tabBar 子页传入 true);默认 false */
|
||
showBack?: boolean
|
||
}
|
||
|
||
/**
|
||
* 自定义导航栏(沉浸式)。
|
||
*
|
||
* <p><b>为什么需要自定义:</b>原生导航栏与页面渐变背景之间存在硬分界线,
|
||
* 无法实现"渐变从状态栏一气呵成"的沉浸式视觉效果;自定义导航栏透明悬浮于
|
||
* 页面渐变之上,滚动后切换为主题色底 + 阴影,保证标题可读性。</p>
|
||
*
|
||
* <p><b>使用约定:</b>页面需设置 {@code navigationStyle: 'custom'},
|
||
* 并通过 {@link useNavBarMetrics} 将页面顶部内容让出导航高度,
|
||
* 防止标题遮挡内容。子页面(非 tabBar 页)可开启 {@code showBack} 显示返回按钮。</p>
|
||
*
|
||
* @author haisen
|
||
*/
|
||
const CustomNavBar: React.FC<CustomNavBarProps> = ({ title, solid = false, showBack = false }) => {
|
||
const { statusBarHeight, navContentHeight } = useNavBarMetrics()
|
||
|
||
const handleBack = () => {
|
||
// 优先返回上一页,无历史时兜底跳首页
|
||
const pages = Taro.getCurrentPages()
|
||
if (pages.length > 1) {
|
||
Taro.navigateBack()
|
||
} else {
|
||
Taro.switchTab({ url: '/pages/home/index' })
|
||
}
|
||
}
|
||
|
||
return (
|
||
<View className={`${styles.navBar} ${solid ? styles.navBarSolid : ''}`}>
|
||
{/* 状态栏占位:高度因机型而异,运行时动态获取 */}
|
||
<View style={{ height: `${statusBarHeight}px` }} />
|
||
{/* 导航内容区:标题与胶囊按钮垂直居中对齐 */}
|
||
<View className={styles.navContent} style={{ height: `${navContentHeight}px` }}>
|
||
{showBack && (
|
||
<View className={styles.backBtn} onClick={handleBack}>
|
||
<Text className={styles.backArrow}>‹</Text>
|
||
</View>
|
||
)}
|
||
<Text className={styles.navTitle}>{title}</Text>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default CustomNavBar
|