v1.11.0 新增
TabBar/TabBarItem自定义底部导航栏组件,支持徽标(badge / dot)、切换拦截器(beforeChange)、插槽自定义;组件样式全面迁移到 SCSS,支持 SCSS 变量!default编译时覆盖 +
CSS 自定义属性运行时覆盖双层级主题定制;组件目录按 easycom 规范重构
前言
@meng-xi/uni-router 在 v1.10.0 已经提供了完整的路由管理能力,但自定义底部导航栏仍需开发者从零实现。uni-app 原生 tabBar 配置灵活度有限,无法支持徽标、切换拦截、插槽自定义等高级需求。v1.11.0 通过新增 TabBar /
TabBarItem 组件填补这一空白,同时对组件体系做了 easycom 规范化与 SCSS 主题定制的全面升级。
一、问题分析
1. 原生 tabBar 的局限
uni-app 的 tabBar 通过 pages.json 静态配置,存在以下限制:
{"tabBar": {"list": [{ "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/home.png", "selectedIconPath": "static/home-active.png" }]}
}
- 无法动态控制:图标、文字、显隐均无法运行时切换
- 不支持徽标:无法显示未读数、小红点
- 不支持拦截:无法在切换前执行权限校验或确认弹窗
- 无法自定义渲染:不支持插槽、自定义图标组件
- 样式不可定制:高度、颜色、安全区适配等无法精细控制
2. 组件目录不符合 easycom 规范
v1.10.0 的组件采用扁平文件结构:
components/
├── RouterLink.vue
├── TabBar.vue
└── TabBarItem.vue
uni-app easycom 要求 components/<name>/<name>.vue 嵌套结构才能自动注册。npm 包用户需要手动配置 pages.json 的 easycom.custom 规则,而 uni_modules 用户则完全无法自动注册。
3. 样式无法定制
组件内部使用硬编码 CSS 值,无法通过变量或自定义属性覆盖主题色、高度、字号等。
二、新增能力
1. TabBar / TabBarItem 组件
基本用法
<template><TabBar selected-color="#007aff" @change="onChange"><TabBarItem to="/pages/index/index" icon-path="/static/home.png" selected-icon-path="/static/home-active.png" text="首页" /><TabBarItem to="/pages/msg/index" icon-path="/static/msg.png" selected-icon-path="/static/msg-active.png" text="消息" :badge="5" /><TabBarItem to="/pages/user/index" icon-path="/static/user.png" selected-icon-path="/static/user-active.png" text="我的" dot /></TabBar>
</template><script setup>
import { useRouter } from '@meng-xi/uni-router'const onChange = (item, index) => {console.log(`切换到: ${item.text}, 索引: ${index}`)
}
</script>
徽标系统
TabBarItem 内置三种徽标模式,优先级:dot > badge:
<!-- 小红点:最高优先级 -->
<TabBarItem to="/pages/msg/index" text="消息" dot /><!-- 数字徽标:超过 badgeMax 显示 {max}+ -->
<TabBarItem to="/pages/msg/index" text="消息" :badge="99" :badge-max="99" /><!-- 文字徽标 -->
<TabBarItem to="/pages/msg/index" text="消息" badge="New" /><!-- 自定义徽标颜色 -->
<TabBarItem to="/pages/msg/index" text="消息" dot badge-color="#ff6900" />
切换拦截器
beforeChange 可在切换前执行权限校验或确认弹窗,返回 false 或 reject 阻止切换:
<TabBar :before-change="beforeChange"><TabBarItem to="/pages/index/index" text="首页" /><TabBarItem to="/pages/vip/index" text="会员" />
</TabBar><script setup>
const beforeChange = async (item, index) => {if (item.to.includes('/vip') && !isVip()) {uni.showToast({ title: '请先开通会员', icon: 'none' })return false}return true
}
</script>
插槽自定义
<TabBar selected-color="#007aff"><!-- 自定义图标 --><TabBarItem to="/pages/index/index" text="首页"><template #icon="{ active }"><CustomIcon :name="active ? 'home-fill' : 'home'" /></template></TabBarItem><!-- 自定义文字 --><TabBarItem to="/pages/user/index" icon-path="/static/user.png" selected-icon-path="/static/user-active.png"><text style="font-weight: bold">我的</text></TabBarItem>
</TabBar>
容器能力
TabBar 提供完整的容器控制能力:
| Prop | 说明 | 默认值 |
|---|---|---|
fixed |
是否固定在底部 | true |
border |
是否显示顶部边框 | true |
placeholder |
fixed 时是否生成等高占位 | false |
safeAreaInsetBottom |
是否开启底部安全区适配 | true |
zIndex |
元素 z-index | 999 |
2. SCSS 主题定制
组件样式全面迁移到 SCSS,支持双层级覆盖:
运行时覆盖(推荐)
在父元素或 :root 上设置 CSS 自定义属性,无需构建配置:
:root {--mx-tabbar-height: 60px;--mx-tabbar-badge-color: #ff6900;--mx-tabbar-item-icon-size: 28px;
}
编译时覆盖
通过 vite 的 css.preprocessorOptions.scss.additionalData 前置定义 SCSS 变量:
// vite.config.ts
export default defineConfig({css: {preprocessorOptions: {scss: {additionalData: `$mx-tabbar-height: 60px; $mx-tabbar-badge-color: #ff6900;`}}}
})
完整变量列表
TabBar:
| CSS 自定义属性 | SCSS 变量 | 默认值 |
|---|---|---|
--mx-tabbar-height |
$mx-tabbar-height |
50px |
--mx-tabbar-background |
$mx-tabbar-background |
#ffffff |
--mx-tabbar-border-color |
$mx-tabbar-border-color |
#e5e5e5 |
TabBarItem:
| CSS 自定义属性 | SCSS 变量 | 默认值 |
|---|---|---|
--mx-tabbar-item-icon-size |
$mx-tabbar-item-icon-size |
24px |
--mx-tabbar-item-font-size |
$mx-tabbar-item-font-size |
10px |
--mx-tabbar-item-gap |
$mx-tabbar-item-gap |
2px |
--mx-tabbar-badge-color |
$mx-tabbar-badge-color |
#ee0a24 |
--mx-tabbar-badge-dot-size |
$mx-tabbar-badge-dot-size |
8px |
--mx-tabbar-badge-font-size |
$mx-tabbar-badge-font-size |
10px |
--mx-tabbar-badge-min-width |
$mx-tabbar-badge-min-width |
16px |
--mx-tabbar-badge-line-height |
$mx-tabbar-badge-line-height |
16px |
--mx-tabbar-badge-padding |
$mx-tabbar-badge-padding |
0 3px |
3. TabBarItemProps 类型导出
TabBarItemProps 从主入口导出,用于 change 事件回调的类型标注:
import type { TabBarItemProps } from '@meng-xi/uni-router'const onChange = (item: TabBarItemProps, index: number) => {console.log(item.text, item.to)
}
三、架构设计
组件目录 easycom 规范化
components/
├── router-link/
│ ├── router-link.vue # 组件主体
│ └── type.ts # RouterLinkProps / RouterLinkEmits
├── tab-bar/
│ ├── tab-bar.vue # 父组件(provide 上下文)
│ ├── context.ts # TabBarContext / TabBarItemProps / TABBAR_KEY
│ └── type.ts # TabBarProps / TabBarEmits
└── tab-bar-item/└── tab-bar-item.vue # 子组件(inject 上下文)
父子通信机制
TabBar (provide)
├── selectedColor / color (ComputedRef)
├── activePath / activeName (从 useRoute 派生)
├── beforeChange (ComputedRef)
├── register(uid) / unregister(uid) — 子项注册
├── indexOf(uid) — 响应式索引查询
├── notifyChange(item, index) — 触发 change 事件
└── notifyError(error) — 触发 error 事件TabBarItem (inject)
├── ctx.activePath / ctx.activeName → 计算 isActive
├── ctx.register(uid) → onMounted 注册
├── ctx.unregister(uid) → onUnmounted 注销
├── ctx.indexOf(uid) → 计算 index
└── onClick → beforeChange 拦截 → router.push/replace → notifyChange/notifyError
活跃状态判定
子组件通过 inject(TABBAR_KEY) 获取父组件上下文,匹配当前路由:
const isActive = computed(() => {if (typeof props.to === 'string') return ctx.activePath.value === props.toif (props.to.name) return ctx.activeName.value === props.to.nameif (props.to.path) return ctx.activePath.value === props.to.pathreturn false
})
新增导出
// src/index.ts 新增
export type { TabBarItemProps } from '@/components/tab-bar/context'
四、完整使用示例
场景:带徽标和拦截的底部导航
<template><TabBar selected-color="#007aff" :bg-color="'#ffffff'" :border="true" :placeholder="true" :safe-area-inset-bottom="true" :before-change="beforeTabChange" @change="onTabChange" @error="onTabError"><TabBarItem to="/pages/index/index" icon-path="/static/tab/home.png" selected-icon-path="/static/tab/home-active.png" text="首页" /><TabBarItem to="/pages/msg/index" icon-path="/static/tab/msg.png" selected-icon-path="/static/tab/msg-active.png" text="消息" :badge="unreadCount" /><TabBarItem to="/pages/vip/index" icon-path="/static/tab/vip.png" selected-icon-path="/static/tab/vip-active.png" text="会员" /><TabBarItem to="/pages/user/index" icon-path="/static/tab/user.png" selected-icon-path="/static/tab/user-active.png" text="我的" dot /></TabBar>
</template><script setup>
import type { TabBarItemProps, NavigationFailure } from '@meng-xi/uni-router'const unreadCount = ref(3)const beforeTabChange = async (item: TabBarItemProps, index: number) => {if (item.to.toString().includes('/vip') && !isVip()) {uni.showModal({title: '提示',content: '该功能需要开通会员,是否前往开通?',success: (res) => {if (res.confirm) router.push('/pages/vip/index')}})return false}return true
}const onTabChange = (item: TabBarItemProps, index: number) => {console.log(`切换到: ${item.text} (索引: ${index})`)
}const onTabError = (error: NavigationFailure) => {console.error('TabBar 导航失败:', error.message)
}
</script>
场景:内嵌展示(非固定)
<!-- fixed=false 时 TabBar 作为普通内联元素展示,不固定在底部 -->
<TabBar :fixed="false" :border="true" selected-color="#007aff"><TabBarItem to="/pages/index/index" text="首页" /><TabBarItem to="/pages/about/index" text="关于" :badge="5" />
</TabBar>
场景:SCSS 主题定制
/* 全局样式 - 运行时覆盖 */
:root {--mx-tabbar-height: 56px;--mx-tabbar-background: #1a1a1a;--mx-tabbar-border-color: #333;--mx-tabbar-badge-color: #ff6900;--mx-tabbar-item-icon-size: 26px;--mx-tabbar-item-font-size: 11px;
}
// vite.config.ts - 编译时覆盖
export default defineConfig({css: {preprocessorOptions: {scss: {additionalData: `$mx-tabbar-height: 56px;$mx-tabbar-background: #1a1a1a;$mx-tabbar-badge-color: #ff6900;`}}}
})
升级指南
v1.11.0 新增功能向后兼容,默认行为不变。组件目录结构变更为 easycom 规范化,npm 用户需更新导入路径。
迁移说明
npm 用户需更新组件导入路径:
| 旧路径 | 新路径 |
|---|---|
@meng-xi/uni-router/components/RouterLink.vue |
@meng-xi/uni-router/components/router-link/router-link.vue |
@meng-xi/uni-router/components/TabBar.vue |
@meng-xi/uni-router/components/tab-bar/tab-bar.vue |
@meng-xi/uni-router/components/TabBarItem.vue |
@meng-xi/uni-router/components/tab-bar-item/tab-bar-item.vue |
uni_modules 用户无需修改,easycom 自动注册 <RouterLink> / <TabBar> / <TabBarItem>。
easycom 自动注册配置
npm 用户可在 pages.json 中配置 easycom 自定义规则实现自动注册:
{"easycom": {"custom": {"^router-link$": "@meng-xi/uni-router/components/router-link/router-link.vue","^tab-bar$": "@meng-xi/uni-router/components/tab-bar/tab-bar.vue","^tab-bar-item$": "@meng-xi/uni-router/components/tab-bar-item/tab-bar-item.vue"}}
}
新增导出
TabBarItemProps— TabBarchange事件回调参数类型
新增组件
TabBar— 自定义底部导航栏容器TabBarItem— 导航栏子项(需作为 TabBar 子组件使用)
兼容性
package.json的exports通配符"./components/*": "./components/*"支持嵌套路径,新导入路径可正常解析TabBar/TabBarItem为全新组件,不影响现有代码- SCSS 变量使用
!default,未覆盖时使用默认值
