v1.11.0 发布:全新 TabBar 组件体系与 SCSS 主题定制全面升级
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.to
if (props.to.name) return ctx.activeName.value === props.to.name
if (props.to.path) return ctx.activePath.value === props.to.path
return 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,未覆盖时使用默认值

浙公网安备 33010602011771号