feat(miniprogram): 沉浸式自定义导航+统一固定标题区

- 五个 tab 页 navigationStyle=custom,内容直通状态栏(去白色页眉)
- TopInset/PageHeader 组件:状态栏+胶囊安全区实测;标题区(像素字kicker+彩虹渐变标题+像素字注脚)四页同构、fixed 不随滚动
- 占位高度 createSelectorQuery 实测 navbar 真实高度,无空白/覆盖/重复
- 标题区上移 65rpx(从状态栏让位空间扣除,非容器偏移)
This commit is contained in:
Pine
2026-08-29 00:07:50 +08:00
parent 9e96559a44
commit 8362334f1b
7 changed files with 90 additions and 5 deletions
+19
View File
@@ -394,3 +394,22 @@ page { -ms-overflow-style: none; scrollbar-width: none; }
.ic-shield { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M12 3l8 3v5c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z'/%3E%3Cpath d='M9 12l2 2 4-4'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M12 3l8 3v5c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z'/%3E%3Cpath d='M9 12l2 2 4-4'/%3E%3C/svg%3E"); }
.ic-landmark { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M3 10l9-6 9 6'/%3E%3Cpath d='M5 10v9M9.5 10v9M14.5 10v9M19 10v9M3 20h18'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Cpath d='M3 10l9-6 9 6'/%3E%3Cpath d='M5 10v9M9.5 10v9M14.5 10v9M19 10v9M3 20h18'/%3E%3C/svg%3E"); }
.ic-users { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Ccircle cx='9' cy='8' r='3.5'/%3E%3Cpath d='M2.5 20a6.5 6.5 0 0 1 13 0'/%3E%3Cpath d='M15.5 5a3.5 3.5 0 0 1 0 6.6'/%3E%3Cpath d='M17.5 14.4a6.5 6.5 0 0 1 4 5.6'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.5'%3E%3Ccircle cx='9' cy='8' r='3.5'/%3E%3Cpath d='M2.5 20a6.5 6.5 0 0 1 13 0'/%3E%3Cpath d='M15.5 5a3.5 3.5 0 0 1 0 6.6'/%3E%3Cpath d='M17.5 14.4a6.5 6.5 0 0 1 4 5.6'/%3E%3C/svg%3E"); }
/* ============ 统一固定标题区(像素字 kicker + 彩虹渐变标题 + 像素字注脚) ============ */
.page-navbar {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
padding: 0 28rpx; /* 标题区上下零冗余:高度 = 安全区 + 三行文字,占位实测自然贴合 */
background: rgba(247, 249, 255, 0.88);
backdrop-filter: blur(20px) saturate(160%);
-webkit-backdrop-filter: blur(20px) saturate(160%);
}
.page-navbar-col { display: flex; flex-direction: column; }
.page-navbar-no { font-family: 'BubbledotICG', monospace; color: #a3abc9; font-size: 26rpx; letter-spacing: 2rpx; line-height: 1.15; }
.page-navbar-titlerow { display: flex; align-items: baseline; gap: 16rpx; line-height: 1.15; }
.page-navbar-title {
font-size: 40rpx; font-weight: 800; letter-spacing: -0.5rpx; line-height: 1.15;
background: linear-gradient(120deg, #8fc3ff 0%, #a8a0ff 45%, #e0a0ff 80%, #7fe0ff 100%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.page-navbar-sub { font-size: 24rpx; color: #5a6aa3; }
.page-navbar-en { font-family: 'BubbledotICG', monospace; color: #5b5b5b; font-size: 24rpx; letter-spacing: 2rpx; line-height: 1.15; padding-bottom: 12rpx; }
+66
View File
@@ -0,0 +1,66 @@
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 + 彩虹渐变标题 + 像素字注脚。
* 占位高度由 createSelectorQuery 实测 navbar 真实高度,绝无空白/覆盖/重复。 */
export function PageHeader({ no, title, en, sub }) {
const { navH, windowWidth } = getTopInset();
// 标题区整体上移 65rpx:从状态栏让位空间中扣除(不是容器偏移,占位实测自动跟随,无空白)
const SHIFT_PX = Math.round((65 * (windowWidth || 375)) / 750);
const topPad = Math.max(navH - SHIFT_PX, 24); // 下限:至少给状态栏留 24px
const [h, setH] = useState(0);
useEffect(() => {
Taro.nextTick(() => {
Taro.createSelectorQuery()
.select('.page-navbar')
.boundingClientRect((r) => { if (r && r.height) setH(r.height); })
.exec();
});
}, [no, title, en, sub]);
return (
<>
<View className="page-navbar" style={{ paddingTop: `${topPad}px` }}>
<View className="page-navbar-col">
{no ? <Text className="page-navbar-no">{no}</Text> : 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` }} />;
}
@@ -1 +1 @@
export default { navigationBarTitleText: '活动' };
export default { navigationBarTitleText: '活动', navigationStyle: 'custom' };
@@ -1 +1 @@
export default { navigationBarTitleText: '资讯中心' };
export default { navigationBarTitleText: '资讯中心', navigationStyle: 'custom' };
+1 -1
View File
@@ -1 +1 @@
export default { navigationBarTitleText: 'OPC · 为进化而设计' };
export default { navigationBarTitleText: 'OPC · 为进化而设计', navigationStyle: 'custom' };
+1 -1
View File
@@ -1 +1 @@
export default { navigationBarTitleText: '我的' };
export default { navigationBarTitleText: '我的', navigationStyle: 'custom' };
+1 -1
View File
@@ -1 +1 @@
export default { navigationBarTitleText: 'OPC 创业测评' };
export default { navigationBarTitleText: 'OPC 创业测评', navigationStyle: 'custom' };