最通用的uni-app自定义导航栏组件核心是平台适配稳、高度计算准、插槽开放、props灵活;需用getSystemInfoSync和getMenuButtonBoundingClientRect动态算高,区分MP-WEIXIN与其他平台;left/center/right插槽透传内容,title仅作fallback;pages.json必须全局设navigationStyle: "custom";TabBar显隐应由业务层控制而非组件内调用。

最通用的 uni-app 自定义导航栏组件,核心不在“功能多”,而在「平台适配不崩、高度计算不偏、插槽留得够开、props 控得够松」。硬塞一堆开关反而让复用变难——你真需要“返回首页+自定义图标+动态背景色+胶囊对齐+H5 fallback”时,80% 的场景其实只用其中 3 个。
必须用 uni.getSystemInfoSync() + uni.getMenuButtonBoundingClientRect() 算高度
只靠 statusBarHeight 是错的。微信小程序里,导航栏实际高度 = 状态栏高度 + 胶囊按钮上下间距(即 menuButton.top - statusBarHeight),而 H5 和 App 平台根本没胶囊按钮。
-
uni.getSystemInfoSync()拿到statusBarHeight和平台标识(platform) -
uni.getMenuButtonBoundingClientRect()只在MP-WEIXIN下有效,其他平台会返回null或报错,必须加try/catch或平台判断 - 最终导航栏总高建议设为响应式变量:
navHeight = platform === 'MP-WEIXIN' ? (menuButton.bottom + menuButton.top) : statusBarHeight + 88(88rpx 是 H5/App 常用安全高度)
插槽设计比 props 更关键:left / center / right 必须全支持
硬编码“返回按钮”或“标题文字”是通用性的最大敌人。真实项目里,有的页面左上角要放二维码图标,有的中间要放搜索框,有的右上角要带 badge 数字——这些全靠插槽透传,而不是靠一堆 showBack、rightText、hasBadge props 堆砌。
- 默认插槽留给
center内容,titleprop 仅作 fallback(v-if="!$slots.center") -
left插槽必须能响应点击,组件内部不绑定任何事件逻辑,只提供@click透传出口 - 避免在插槽内写条件判断(比如
v-if="platform==='MP-WEIXIN'"),这部分应由父组件控制——组件只负责渲染
pages.json 里必须全局关掉原生导航栏
封装再好,如果页面没声明 "navigationStyle": "custom",自定义导航栏就会被原生导航栏盖住,或出现双导航栏重叠。这不是组件问题,是配置漏项。
- 不要只在个别页面加,应在
pages.json的"h5"、"mp-weixin"、"app-plus"三个平台节点下统一设置 - 尤其注意子包页面(如
subNVue或分包路径),容易遗漏 - 调试时可临时加个
border: 1px solid red到外层容器,确认是否真被撑开且无遮挡
别在组件里调 uni.hideTabBar() 或 uni.showTabBar()
这是高频翻车点。导航栏组件和 TabBar 是两个独立区域,但很多人误以为“用了自定义导航栏就得隐藏 TabBar”,结果导致底部 tab 在某些页面消失、切换页面后不恢复。
- TabBar 显隐应由业务逻辑控制(比如登录页需隐藏,首页需显示),不是导航栏组件的职责
- 如果真要联动,应该通过
$emit('tabbar-toggle', false)向上抛事件,由页面或 layout 层统一处理 - 更稳妥的做法是:TabBar 显隐状态交由 pinia/vuex 管理,导航栏组件只读取,不修改
真正难的从来不是怎么写完这个组件,而是下次接手的人能不能在 2 分钟内看懂哪里改标题、哪里换图标、哪里加点击逻辑——所以删掉所有“看起来很酷但没人用”的配置项,把插槽留宽、把高度算准、把平台分支收拢,比堆功能重要十倍。


















