在电商类、社交类微信小程序中,tabbar的消息提示是引导用户查看重要信息的核心入口,未读消息的红点、具体未读数量都需要根据业务数据实时变化。原生tabbar虽然提供了showTabBarRedDot等API,但只能控制固定位置的红点,无法支持自定义样式、位置偏移,也无法实现数字徽标的灵活展示,因此大部分需要动态徽标的场景都会选择自定义tabbar方案。

自定义tabbar的基础搭建与徽标数据结构设计
自定义tabbar首先需要关闭原生tabbar,在项目根目录的app.json中配置"tabBar": {"custom": true},同时删除原有的list配置项,避免原生tabbar和自定义tabbar同时渲染。之后需要在项目根目录创建custom-tab-bar目录,目录下包含index.js、index.json、index.wxml、index.wxss四个文件,这是微信小程序自定义tabbar的约定目录结构,框架会自动识别并渲染该组件。
徽标的数据需要设计成可扩展的结构,每个tab项对应一个徽标配置,包含类型、显隐状态、数值三个核心字段。可以在custom-tab-bar的data中初始化tab列表数据,每个tab对象除了常规的pagePath、iconPath、selectedIconPath、text字段外,额外添加badge字段,结构如下:badge: { show: false, type: 'dot', count: 0 },其中type为dot时表示红点,为number时表示数字徽标,count仅在数字徽标类型下生效。
初始化数据的示例代码如下,这里模拟了四个常见的tab项,分别对应首页、消息、订单、我的四个页面,其中消息tab默认设置了红点显示,方便后续测试动态更新逻辑:
// custom-tab-bar/index.js
Component({
data: {
selected: 0,
list: [
{
pagePath: '/pages/index/index',
iconPath: '/images/tab_home.png',
selectedIconPath: '/images/tab_home_active.png',
text: '首页',
badge: { show: false, type: 'dot', count: 0 }
},
{
pagePath: '/pages/message/message',
iconPath: '/images/tab_message.png',
selectedIconPath: '/images/tab_message_active.png',
text: '消息',
badge: { show: true, type: 'dot', count: 0 }
},
{
pagePath: '/pages/order/order',
iconPath: '/images/tab_order.png',
selectedIconPath: '/images/tab_order_active.png',
text: '订单',
badge: { show: false, type: 'number', count: 0 }
},
{
pagePath: '/pages/mine/mine',
iconPath: '/images/tab_mine.png',
selectedIconPath: '/images/tab_mine_active.png',
text: '我的',
badge: { show: false, type: 'dot', count: 0 }
}
]
},
methods: {
// 切换tab的方法后续补充
}
})
消息红点与数字徽标的动态更新实现逻辑
动态更新的核心是将徽标状态和全局数据绑定,通常我们会使用小程序全局的globalData或者状态管理库(如mobx-miniprogram)来存储未读消息数量等核心数据,避免每个页面单独维护状态导致数据不一致。当业务页面获取到新的未读消息数据时,需要主动更新自定义tabbar的徽标状态,这里可以通过获取自定义tabbar实例的方式实现跨组件通信。
在小程序的页面生命周期中,可以通过this.getTabBar()方法获取自定义tabbar的组件实例,该方法仅在自定义tabbar开启时可用,返回的就是custom-tab-bar组件的实例对象,之后可以直接调用组件实例的setData方法更新徽标数据。比如消息页面收到新消息时,需要更新消息tab的数字徽标,首先更新全局的未读消息数量,再同步到tabbar的list数据中对应的徽标字段。
以下是更新数字徽标的示例代码,假设全局app.globalData中存储了未读消息总数unreadMsgCount,当该数值变化时,触发tabbar徽标更新:
// pages/message/message.js
const app = getApp()
Page({
onShow() {
// 模拟获取未读消息数量
const newUnreadCount = 5
app.globalData.unreadMsgCount = newUnreadCount
// 获取自定义tabbar实例
const tabBar = this.getTabBar()
if (tabBar) {
// 找到消息tab对应的索引,这里是1
const newList = tabBar.data.list.map((item, index) => {
if (index === 1) {
return {
...item,
badge: {
show: newUnreadCount > 0,
type: newUnreadCount > 0 ? 'number' : 'dot',
count: newUnreadCount
}
}
}
return item
})
tabBar.setData({
list: newList
})
}
}
})
如果是更新红点状态,逻辑会更简单,只需要控制对应tab的badge.show字段即可,比如订单页面有新待处理订单时,显示红点:
// pages/order/order.js
Page({
onLoad() {
// 模拟有新待处理订单
const hasNewOrder = true
const tabBar = this.getTabBar()
if (tabBar) {
const newList = tabBar.data.list.map((item, index) => {
if (index === 2) {
return {
...item,
badge: {
...item.badge,
show: hasNewOrder,
type: 'dot'
}
}
}
return item
})
tabBar.setData({
list: newList
})
}
}
})
徽标样式实现与点击状态重置处理
徽标的样式需要在custom-tab-bar的wxml和wxss中实现,红点通常是圆形红色小块,数字徽标是带圆角的红色背景加白色文字,位置要固定在tab图标右上角。在wxml中,每个tab项的结构里需要添加徽标的渲染逻辑,根据item.badge.show判断是否显示,再根据item.badge.type区分红点和数字两种样式。
样式实现时要注意定位问题,tab项的容器需要设置position: relative,徽标容器设置position: absolute,通过top和right调整位置,避免遮挡tab图标和文字。红点的尺寸一般设置为16rpx*16rpx,数字徽标的高度设置为32rpx,最小宽度32rpx,内边距左右8rpx,保证数字显示完整。同时要注意数字过长时的处理,比如未读数量超过99时显示99+,避免徽标尺寸过大影响布局。
以下是custom-tab-bar的wxml和wxss样式示例代码:
<!-- custom-tab-bar/index.wxml -->
<view class="tab-bar">
<view class="tab-bar-item" wx:for="{{list}}" wx:key="pagePath" data-index="{{index}}" data-path="{{item.pagePath}}" bindtap="switchTab">
<image class="tab-icon" src="{{selected === index ? item.selectedIconPath : item.iconPath}}"></image>
<text class="tab-text">{{item.text}}</text>
<!-- 徽标渲染 -->
<view class="tab-badge" wx:if="{{item.badge.show}}">
<view class="badge-dot" wx:if="{{item.badge.type === 'dot'}}"></view>
<view class="badge-number" wx:if="{{item.badge.type === 'number'}}">
{{item.badge.count > 99 ? '99+' : item.badge.count}}
</view>
</view>
</view>
</view>
/* custom-tab-bar/index.wxss */
.tab-bar {
display: flex;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
background: #fff;
border-top: 1rpx solid #eee;
padding-bottom: env(safe-area-inset-bottom);
}
.tab-bar-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.tab-icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 4rpx;
}
.tab-text {
font-size: 24rpx;
color: #666;
}
.tab-bar-item.active .tab-text {
color: #07c160;
}
/* 徽标样式 */
.tab-badge {
position: absolute;
top: 8rpx;
right: 50%;
transform: translateX(60rpx);
}
.badge-dot {
width: 16rpx;
height: 16rpx;
background: #f56c6c;
border-radius: 50%;
}
.badge-number {
height: 32rpx;
min-width: 32rpx;
padding: 0 8rpx;
background: #f56c6c;
border-radius: 16rpx;
color: #fff;
font-size: 20rpx;
line-height: 32rpx;
text-align: center;
}
点击tab后重置徽标状态也是常见需求,比如用户点击消息tab进入消息列表后,消息tab的未读徽标应该消失。这个逻辑可以在自定义tabbar的switchTab方法中实现,当切换到对应tab页面时,将该tab的徽标show设为false,同时清空对应的全局未读数据。需要注意的是,切换tab时使用的是wx.switchTabAPI,该API不支持传递参数,因此状态更新还是要依赖全局数据或者组件实例的setData。
以下是switchTab方法的实现代码,同时包含了点击后重置对应tab徽标的逻辑:
// custom-tab-bar/index.js methods中添加
switchTab(e) {
const { index, path } = e.currentTarget.dataset
const app = getApp()
// 切换tab
wx.switchTab({
url: path,
success: () => {
// 切换后更新选中状态
this.setData({
selected: index
})
// 重置当前点击tab的徽标状态
const newList = this.data.list.map((item, i) => {
if (i === index) {
// 清空对应全局数据,比如消息tab对应未读消息数
if (index === 1) {
app.globalData.unreadMsgCount = 0
}
return {
...item,
badge: {
show: false,
type: item.badge.type,
count: 0
}
}
}
return item
})
this.setData({
list: newList
})
}
})
}
实际开发中还需要注意几个细节问题,比如小程序冷启动时,需要读取本地缓存或者接口请求获取初始的未读状态,初始化自定义tabbar的徽标数据;如果小程序有多个入口页面,每个页面显示时都需要同步一次tabbar的选中状态和徽标状态,避免选中和实际页面不匹配;当未读数量从0变为大于0时,要同时更新show字段为true,避免只更新count导致徽标不显示的问题。这些细节处理到位后,自定义tabbar的徽标动态更新就能稳定支持各种业务场景了。