Files
training/miniprogram/src/components/TopInset.jsx
T
Pine b1b86bb80d 小程序:大厅顶部标题区内容再上移 30rpx
- PageHeader 新增 shift 参数(默认 65rpx,其他页面不变)
- 大厅传 95rpx:标题文本整体上移,底部占位高度不变
2026-09-08 23:48:58 +08:00

87 lines
3.5 KiB
React

import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
let cached = null;
/** 顶部安全区(沉浸式自定义导航用):状态栏 + 胶囊高度。 */
export function getTopInset() {
if (!cached) {
try {
const win = Taro.getWindowInfo ? Taro.getWindowInfo() : Taro.getSystemInfoSync();
const statusBar = win.statusBarHeight || 44;
const windowWidth = win.windowWidth || 375;
let navH = statusBar + 44;
try {
const menu = Taro.getMenuButtonBoundingClientRect ? Taro.getMenuButtonBoundingClientRect() : null;
if (menu && menu.height) navH = statusBar + (menu.top - statusBar) * 2 + menu.height;
} catch { /* 无胶囊环境用默认 */ }
cached = { statusBar, navH, windowWidth };
} catch {
cached = { statusBar: 44, navH: 88, windowWidth: 375 };
}
}
return cached;
}
/** 统一固定标题区(fixed 不随滚动):像素字 kicker + 彩虹渐变标题 + 像素字注脚。
* back:传入函数=自定义返回;true=自动返回(无上一页则回首页 Tab)。
* shift:内容整体上移量(rpx),默认 65;底部占位高度不受影响。
* 占位高度由 createSelectorQuery 实测 navbar 真实高度,绝无空白/覆盖/重复。 */
export function PageHeader({ no, title, en, sub, back, shift = 65 }) {
const { navH, windowWidth } = getTopInset();
// 标题区整体上移 shift rpx:从状态栏让位空间中扣除(不是容器偏移,占位实测自动跟随,无空白)
const SHIFT_PX = Math.round((shift * (windowWidth || 375)) / 750);
const topPad = Math.max(navH - SHIFT_PX, 24); // 下限:至少给状态栏留 24px
// 默认占位高度 = 状态栏+导航区+标题内容,避免异步测量前内容被遮挡
const defaultH = navH + 80;
const [h, setH] = useState(defaultH);
useEffect(() => {
Taro.nextTick(() => {
Taro.createSelectorQuery()
.select('.page-navbar')
.boundingClientRect((r) => { if (r && r.height) setH(r.height); })
.exec();
});
}, [no, title, en, sub, back]);
const goBack = () => {
if (typeof back === 'function') { back(); return; }
const pages = Taro.getCurrentPages ? Taro.getCurrentPages() : [];
if (pages.length > 1) Taro.navigateBack({ delta: 1 });
else Taro.switchTab({ url: '/pages/index/index' });
};
return (
<>
<View className="page-navbar" style={{ paddingTop: `${topPad}px` }}>
<View className="page-navbar-col">
{no || back ? (
<View className="page-navbar-norow">
{back ? (
<View className="page-navbar-back-hit" hoverClass="pressed" onClick={goBack}>
<Text className="page-navbar-back-ic ic ic-arrow-left" aria-hidden />
</View>
) : null}
{no ? <Text className="page-navbar-no">{no}</Text> : null}
</View>
) : null}
<View className="page-navbar-titlerow">
<Text className="page-navbar-title">{title}</Text>
{sub ? <Text className="page-navbar-sub">{sub}</Text> : null}
</View>
{en ? <Text className="page-navbar-en">{en}</Text> : null}
</View>
</View>
<View style={{ height: `${h}px` }} />
</>
);
}
/** 沉浸式顶部占位:配合页面 navigationStyle=custom 使用。 */
export default function TopInset() {
const { navH } = getTopInset();
return <View style={{ height: `${navH}px` }} />;
}