- 后端
- 物联网
- 语音
- 人工智能
- AI 应用
- AI Agent
- RAG
【免费下载链接】xiaozhi-esp32-server
本项目为xiaozhi-esp32提供后端服务,帮助您快速搭建ESP32设备控制服务器。Backend service for xiaozhi-esp32, helps you quickly build an ESP32 device control server.
本文围绕小智 ESP32 服务端移动端管理应用(manager-mobile)中底部导航栏(tabbar)的四种实现策略展开,系统讲解「无 tabbar」「原生 tabbar」「有缓存自定义 tabbar」「无缓存自定义 tabbar」四种方案的能力边界、优缺点与源码级实现细节。读者读完可以掌握该模板中 tabbar 策略的切换方式、tabbarList.ts的核心配置字段、图标渲染机制以及页面缓存与路由切换的底层原理,从而为自己的 uni-app 项目选型并落地合适的底部导航方案。
一、背景:tabbar 在 Manager-Mobile 中的位置
在小智 ESP32 服务端项目中,manager-mobile 是基于 uni-app(Vue 3 + TypeScript)构建的移动端管理应用,主要包含首页、配网、系统设置等页面。其底部导航栏的实现集中收敛在一个独立目录中:
main/manager-mobile/src/layouts/fg-tabbar/ ├── fg-tabbar.vue # 自定义 tabbar 渲染组件 ├── tabbar.md # 四种策略说明文档(本文核心依据) ├── tabbar.ts # tabbar 选中状态(reactive + storageSync) └── tabbarList.ts # 策略开关、tabbar 列表与 pages.json tabBar 配置其中 tabbar.md 明确了 tabbar 分为4 种情况(策略),分别用数字 0、1、2、3 标识;而 tabbarList.ts 中的TABBAR_MAP常量正是这四种策略的程序化映射。二者一一对应,是理解整个 tabbar 体系的钥匙。
二、四种 tabbar 策略总览
根据 tabbar.md,四种策略的核心差异集中在三个维度:是否显示底部导航栏、页面是否有缓存、切换底层 API(switchTab还是navigateTo)。
| 策略值 | 名称 | 切换 API | 页面缓存 | 导航栏形态 |
|---|---|---|---|---|
| 0 | 无 tabbar | 无(单页面入口) | 无 | 底部不显示 tabbar |
| 1 | 原生 tabbar | switchTab | 有 | 系统原生 tabbar |
| 2 | 有缓存自定义 tabbar | switchTab | 有 | 第三方 UI 库 tabbar 组件,隐藏原生 tabbar |
| 3 | 无缓存自定义 tabbar | navigateTo | 无 | 第三方 UI 库 tabbar 组件 |
在源码 tabbarList.ts 中,四种策略被定义为:
export const TABBAR_MAP = { NO_TABBAR: 0, // 无 tabbar NATIVE_TABBAR: 1, // 完全原生 tabbar CUSTOM_TABBAR_WITH_CACHE: 2, // 有缓存自定义 tabbar CUSTOM_TABBAR_WITHOUT_CACHE: 3, // 无缓存自定义 tabbar } // TODO:通过这里切换使用tabbar的策略 export const selectedTabbarStrategy = TABBAR_MAP.NATIVE_TABBAR当前仓库默认启用的是策略 1(原生 tabbar),开发者只需修改selectedTabbarStrategy这一处即可在四种策略间切换。
三、策略 0:无 tabbar(临时活动页场景)
适用场景:只有一个页面入口,底部不显示任何 tabbar,常用于临时活动页、落地页、引导页等单页场景。
- 优势:页面形态最纯粹,不受底部导航约束,适合全屏沉浸式展示;
- 注意点:此策略下
tabbarList不生效(源码注释明确标注「NO_TABBAR(0) 时,tabbarList 不生效」)。
从实现上看,当selectedTabbarStrategy = 0时,tabbarList.ts 中cacheTabbarEnable为false,因此最终导出的tabBar为undefined,pages.json中不会生成tabBar配置,也就不会渲染任何底部导航:
// 0和1 需要显示底部的tabbar的各种配置,以利用缓存 export const tabBar = cacheTabbarEnable ? _tabbar : undefined四、策略 1:原生 tabbar(默认策略)
适用场景:页面结构稳定、追求首屏渲染速度与性能的常规业务 App。
原生 tabbar 是框架(uni-app / 微信小程序等)内置的底部导航,通过switchTab进行页面切换:
- 优势:原生自带,最先渲染,加载最快;页面有缓存,切换不丢失状态;
- 劣势:图标只能使用 2 组图片(未选中
iconPath与选中selectedIconPath)来切换状态;修改颜色只能重新替换图片(或借助 iconfont 变通),扩展定制能力有限。
对应源码配置位于 tabbarList.ts:
const _tabbar: TabBar = { // 只有微信小程序支持 custom。App 和 H5 不生效 custom: selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE, color: '#e6e6e6', selectedColor: '#667dea', backgroundColor: '#fff', borderStyle: 'black', height: '50px', fontSize: '10px', iconWidth: '24px', spacing: '3px', list: tabbarList as unknown as TabBar['list'], }原生策略下每个 tab 项需要提供两张图片。仓库中已内置三组 tab 图标(static/tabbar):robot/robot_activate(首页)、network/network_activate(配网)、system/system_activate(系统),分别对应iconPath与selectedIconPath字段。
说明:策略 1 与策略 2 均通过
switchTab切换,因此cacheTabbarEnable在两者下都为true,从而保证pages.json生成原生tabBar配置以复用框架缓存能力。
五、策略 2:有缓存自定义 tabbar
适用场景:既需要switchTab带来的页面缓存,又希望拥有高度可定制的图标、配色与动效。
该策略使用第三方 UI 库(本项目为 wot-design-uni 的wd-tabbar)渲染自定义 tabbar,并隐藏原生 tabbar的显示:
- 优势:可随意配置自己想要的 SVG icon,切换字体颜色方便;页面有缓存;可以实现各种自定义动效;
- 劣势:首次点击 tabbar 会闪烁(自定义组件渲染与页面切换存在时序差)。
自定义 tabbar 的渲染在 fg-tabbar.vue 中完成,切换逻辑如下:
function selectTabBar({ value: index }: { value: number }) { const url = tabbarList[index].path tabbarStore.setCurIdx(index) if (cacheTabbarEnable) { uni.switchTab({ url }) // 策略 2:有缓存 } else { uni.navigateTo({ url }) // 策略 3:无缓存 } }同时,页面onLoad时会主动隐藏原生 tabbar,避免出现「两个 tabbar 叠加」的问题:
onLoad(() => { // 解决原生 tabBar 未隐藏导致有2个 tabBar 的问题 const hideRedundantTabbarEnable = selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE hideRedundantTabbarEnable && uni.hideTabBar({ fail(err) { console.log('hideTabBar fail: ', err) }, success(res) { console.log('hideTabBar success: ', res) }, }) })此外需注意custom: true的custom字段只有微信小程序支持,App 端与 H5 端不生效(见 tabbarList.ts 注释)。
六、策略 3:无缓存自定义 tabbar
适用场景:同样需要自定义图标与配色,但可以接受页面状态不保留的轻量场景。
- 优势:可随意配置 SVG icon,切换字体颜色方便,可实现自定义动效;
- 劣势:首次点击 tabbar 会闪烁,且页面无缓存——因为底层使用的是
navigateTo(每次切换都会重新创建页面实例),而非switchTab。
从代码逻辑看,策略 3 与策略 2 在渲染层完全一致(都走wd-tabbar),唯一区别是cacheTabbarEnable为false,从而切换走uni.navigateTo分支。该策略适合对状态保留要求不高的页面组合,避免因缓存带来额外内存开销。
七、核心配置详解:tabbarList.ts
tabbarList.ts 是整个 tabbar 体系的「总控文件」,承担三类职责:策略开关、列表数据、pages.json 的 tabBar 注入。
7.1 列表数据结构与图标类型
type FgTabBarItem = TabBar['list'][0] & { icon: string iconType: 'uiLib' | 'unocss' | 'iconfont' }当前内置的三个 tab 项(tabbarList.ts):
| 页面 | 文本 | 原生图标(iconPath / selectedIconPath) | 自定义图标(icon / iconType) |
|---|---|---|---|
| pages/index/index | 首页 | robot.png / robot_activate.png | home / uiLib |
| pages/device-config/index | 配网 | network.png / network_activate.png | i-carbon-network-3 / uiLib |
| pages/settings/index | 系统 | system.png / system_activate.png | i-carbon-settings / uiLib |
源码注释明确了三条配置约定(tabbarList.ts):
- 策略 1(原生)时:需要填
iconPath和selectedIconPath两张图片; - 策略 2/3(自定义)时:需要填
icon和iconType; - 策略 0(无 tabbar)时:
tabbarList不生效。
7.2 四种图标渲染方式
在 fg-tabbar.vue 模板中,wd-tabbar-item根据iconType分四种情况渲染图标:
uiLib:直接使用 UI 框架(wot-design-uni)自带的 icon 组件,如:icon="item.icon";unocss:使用 UnoCSS 原子类(h-40rpx w-40rpx)配合图标类名,并通过is-active/is-inactive切换选中态样式;iconfont:使用 iconfont 字体图标,同样以 class 方式渲染;local:使用本地图片资源,通过<image :src="item.icon" />渲染。
值得注意的是,模板中还支持了文档未单独列出的
local(本地图片)渲染分支,说明自定义 tabbar 的图标方案实际上比文档列举的 svg/iconfont 更灵活,还可以直接放本地图片。
7.3 缓存开关与 tabBar 注入
// NATIVE_TABBAR(1) 和 CUSTOM_TABBAR_WITH_CACHE(2) 时,需要tabbar缓存 export const cacheTabbarEnable = selectedTabbarStrategy === TABBAR_MAP.NATIVE_TABBAR || selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHEcacheTabbarEnable是一个派生开关:只有策略 1、2 为true。它同时决定了三件事——switchTab分支、原生 tabbar 是否显示、以及pages.json中是否注入tabBar配置。
八、页面状态管理:tabbar.ts
自定义 tabbar 的选中索引由一个轻量状态对象维护(tabbar.ts):
export const tabbarStore = reactive({ curIdx: uni.getStorageSync('app-tabbar-index') || 0, setCurIdx(idx: number) { this.curIdx = idx uni.setStorageSync('app-tabbar-index', idx) }, })这里有两个设计要点:
- 刻意不用 Pinia:注释明确说明「使用 reactive 简单状态,而不是 pinia 全局状态」,因为 tabbar 索引是极简的 UI 状态,引入全局 store 反而增加复杂度;
- storageSync 持久化:每次设置
curIdx都会同步写入app-tabbar-index,这样刷新浏览器(H5 端)或重启小程序时,能恢复到正确的 tabbar 页面,避免「刷新后回到默认页」的体验断裂。
九、与 pages.config.ts 的集成
uniapp 项目中页面与 tabBar 的声明通过 pages.config.ts 完成,该文件在构建时生成pages.json:
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages' import { tabBar } from './src/layouts/fg-tabbar/tabbarList' export default defineUniPages({ // ... // tabbar 的配置统一在 "./src/layouts/fg-tabbar/tabbarList.ts" 文件中 tabBar: tabBar as any, })由此形成了一条清晰的配置链路:
tabbarList.ts(策略 + 列表 + tabBar 对象) └── pages.config.ts(注入 tabBar → 生成 pages.json) └── 页面路由声明与原生 tabbar 渲染源码在 tabbarList.ts 中给出了一条重要提醒:本文件的任何代码更改之后,都需要重新运行(重新构建),否则pages.json不会更新导致错误。这是切换策略时最常见的坑,务必牢记。
十、布局集成:default.vue 与 tabbar.vue
项目中存在两套布局(src/layouts):
- default.vue:仅提供
wd-config-provider、wd-toast、wd-message-box等全局能力,不渲染 tabbar; - tabbar.vue:在 default 基础上额外引入
<FgTabbar />组件(fg-tabbar.vue),用于自定义 tabbar 策略的页面布局。
在 fg-tabbar.vue 中,只有当策略为 2 或 3(自定义 tabbar)时才渲染wd-tabbar组件:
const customTabbarEnable = selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE || selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITHOUT_CACHEwd-tabbar开启safe-area-inset-bottom(安全区适配)、bordered、placeholder、fixed等属性,兼顾 iPhone 底部安全区与页面占位,避免内容被固定导航遮挡。
十一、路由拦截器对 tabbar 切换的兼容
项目在 router/interceptor.ts 中注册了全局路由拦截器(基于黑名单的登录拦截),并通过uni.addInterceptor同时拦截了navigateTo、reLaunch、redirectTo、switchTab四种跳转方式:
uni.addInterceptor('navigateTo', navigateToInterceptor) uni.addInterceptor('reLaunch', navigateToInterceptor) uni.addInterceptor('redirectTo', navigateToInterceptor) uni.addInterceptor('switchTab', navigateToInterceptor)这意味着无论采用switchTab(策略 1/2)还是navigateTo(策略 3)进行 tab 切换,都会被登录拦截逻辑统一覆盖,切换 tabbar 策略不会造成路由鉴权漏洞;且拦截器对 url 做了规范化处理(相对路径转绝对路径),与 fg-tabbar.vue 中「path 从 pages.config.ts 得到,并补全/前缀」的约定一致。
十二、实操指南:如何切换与验证
结合源码,切换 tabbar 策略只需两步:
- 修改策略开关:在 tabbarList.ts 中修改
selectedTabbarStrategy为TABBAR_MAP中的 0/1/2/3 之一; - 重启/重新构建项目:由于
pages.json由构建期生成,修改后必须重新运行,否则配置不生效。
同时按策略补齐字段:
- 策略 1:确认每个 tab 项都填了
iconPath与selectedIconPath(图片位于 static/tabbar); - 策略 2/3:确认填了
icon与iconType,并根据uiLib/unocss/iconfont/local四种类型准备对应的图标资源; - 策略 2:微信小程序端生效
custom: true,App/H5 端不生效,需注意平台差异。
十三、选型建议与总结
结合文档的优劣势说明与源码实现,四种策略的选型建议如下:
- 策略 0(无 tabbar):仅用于单页入口、临时活动页、引导页等场景;
- 策略 1(原生 tabbar):默认推荐。追求首屏性能与页面缓存,对图标样式要求不高时优先选择,代价是定制能力弱(仅两组图片);
- 策略 2(有缓存自定义):需要「缓存 + 高度定制」时的最佳折中,代价是首次点击闪烁、且
custom配置在微信小程序外不生效; - 策略 3(无缓存自定义):当页面无需保留状态、且希望自定义能力最大化时使用,代价是每次切换都会重建页面。
总结来看,小智 ESP32 服务端的 manager-mobile 通过 tabbarList.ts 这一个「策略总控」文件,将四种 tabbar 方案的切换成本压缩到了极致:一处常量、一次重启、按策略补齐图标字段即可完成切换。这种「配置驱动、组件收敛」的设计,配合switchTab/navigateTo的 API 差异与storageSync的状态持久化,为同类 uni-app 项目的底部导航实现提供了一个结构清晰、易于维护的参考范本。至于动效等「花里胡哨」的效果,正如 tabbar.md 末尾的提示所说:需要开发者自行实现,模板本身并不提供。
- 后端
- 物联网
- 语音
- 人工智能
- AI 应用
- AI Agent
- RAG
【免费下载链接】xiaozhi-esp32-server
本项目为xiaozhi-esp32提供后端服务,帮助您快速搭建ESP32设备控制服务器。Backend service for xiaozhi-esp32, helps you quickly build an ESP32 device control server.
相关推荐
awesome-opensource-documents开发者宝典:20个必备编程语言文档和最佳实践
awesome opensource documents开发者宝典:20个必备编程语言文档和最佳实践 awesome opensource documents是
uPlot缓存策略:本地存储与服务端缓存结合方案
uPlot缓存策略:本地存储与服务端缓存结合方案 你是否在使用uPlot时遇到过大量时间序列数据加载缓慢的问题?是否想过如何在保证图表渲染速度的同时,减少不必要
前端图表库数据可视化roadmap.sh缓存策略:服务端与客户端缓存
roadmap.sh缓存策略:服务端与客户端缓存 概述 roadmap.sh作为开发者学习平台,采用了多层缓存策略来优化性能并提升用户体验。本文将深入分析其服务
文档教程知识库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考