目录
- 背景
- 思路
- 实现
- 尾巴
背景
在使用uniapp进行开发时,tabbar是我们使用的很频繁的一个组件,但是在特定的平台会有一些使用上的限制,无法通过一套代码来做通用平台的适配。比如说中间按钮凸起,动态隐藏某个tab(不同角色展示不同功能),使用字体图标,数字角标等,这些功能不是所有平台都支持。今天我们就用一套代码来实现这些功能全端支持。
思路
实现思路就是通过通过自定义view来实现我们这个tabbar功能,然后页面通过组件来展示。通过点击不同的tab来显示不同的组件来达到模拟原生tabbar切换效果。那有些人要问了,你咋知道我项目中有多少个tab,这些tab叫什么名字了?那这里就可以利用uniapp提供的组件easycom模式来解决这些问题,只要我们设置好组件的限制个数和提前占位名称,这些问题就迎刃而解。
实现
1、我们现在components(没有就新建一个components目录)目录下新建一个文件夹(我这里叫ctab),然后分别新建一个vue组件和一个js文件。组件可以让我们在其他地方引用,js文件主要是做配置。
2、新建tab组件,我们组件最多限制5个tab组件,然后需要通过easycom占位来实现,所以你需要几个tab组件就在components目录下建几个组件命名为ctabx。如下所示,我这里要展示三个tab:
特别注意这里的tab组件命名一定要符合easycom规范,不然可能会引起组件引用错误。
这里示例一个ctab1写法:
<template> | |
<view style="width: 750rpx;height: 300rpx;background-color: red;"> | |
首页 | |
</view> | |
</template> | |
<script> | |
export default { | |
name: "ctab1", | |
data() { | |
return {}; | |
}, | |
mounted() {}, | |
methods: {} | |
} | |
</script> | |
<style> | |
</style> |
3、tabbar组件ctab.vue实现,这里就直接上代码了,直接copy就能使用,关键地方已加上注释
<template> | |
<view> | |
<!--中间按钮凸起模式--> | |
<block v-if="midBtn && midBtn.show"> | |
<!--凸起模式最多展示四个--> | |
<block v-if="tabs.length < 4"> | |
<ctab1 v-show="sindex == 0"></ctab1> | |
<ctab2 v-show="sindex == 2"></ctab2> | |
</block> | |
<block v-else="tabs.length >= 4"> | |
<ctab1 v-show="sindex == 0"></ctab1> | |
<ctab2 v-show="sindex == 1"></ctab2> | |
<ctab3 v-show="sindex == 3"></ctab3> | |
<ctab4 v-show="sindex == 4"></ctab4> | |
</block> | |
<view class="tabbar"> | |
<!--中间按钮凸起模式tab为3个或者5个--> | |
<view class="tab-item" | |
v-for="(item,index) in (tabs.length < 4 ? 3 : 5)" | |
:key="item" | |
@click="handleTabClick(index)"> | |
<!--中间按钮凸起显示图片和文字--> | |
<block v-if="index == floor"> | |
<view :style="'bottom: calc('+(midBtn.offset ? midBtn.offset : '50rpx')+' + env(safe-area-inset-bottom));width: '+(midBtn.width ? midBtn.width : '150rpx')+';height: '+(midBtn.width ? midBtn.width : '150rpx')+';background:'+(midBtn.background ? midBtn.background : '#ffffff')" | |
class="mid-btn"> | |
<image :src="midBtn.icon" | |
:style="'width: '+(midBtn.iconwidth ? midBtn.iconwidth : midBtn.width)+';height: '+(midBtn.iconheight ? midBtn.iconheight : midBtn.width)+';'" | |
@click="handleMidBtn"/> | |
</view> | |
<text class="mid-text" | |
:style="'margin-top: '+(midBtn.textoffset ? midBtn.textoffset : '50rpx;')" | |
v-show="midBtn.showtext">{{midBtn.text}}</text> | |
</block> | |
<!--普通tab这里需要注意index选择--> | |
<block v-else> | |
<view class="c-tab-item"> | |
<text :class="'tab-iconfont iconfont '+(tabs[index < floor ? index : index-1].iconfont)" | |
:style="'color:'+(sindex == index ? scolor : color)" | |
v-if="tabs[index < floor ? index : index-1].iconfont"/> | |
<image :src="sindex == index ? tabs[index < floor ? index : index-1].iconSelect : tabs[index < floor ? index : index-1].icon" | |
class="tab-icon" | |
v-else/> | |
<text class="tab-text" | |
:style="'color:'+(sindex == index ? scolor : color)">{{tabs[index < floor ? index : index-1].text}}</text> | |
<view class="corner" | |
v-show="tabs[index < floor ? index : index-1].mark > 0">{{tabs[index < floor ? index : index-1].mark > 99 ? '99+' : tabs[index < floor ? index : index-1].mark}}</view> | |
</view> | |
</block> | |
</view> | |
</view> | |
</block> | |
<!--普通模式--> | |
<block v-else> | |
<block v-if="tabs.length == 1"> | |
<ctab1 v-show="sindex == 0 && tabs[0].show"></ctab1> | |
</block> | |
<block v-else-if="tabs.length == 2"> | |
<ctab1 v-show="sindex == 0 && tabs[0].show"></ctab1> | |
<ctab2 v-show="sindex == 1 && tabs[1].show"></ctab2> | |
</block> | |
<block v-else-if="tabs.length == 3"> | |
<ctab1 v-show="sindex == 0 && tabs[0].show"></ctab1> | |
<ctab2 v-show="sindex == 1 && tabs[1].show"></ctab2> | |
<ctab3 v-show="sindex == 2 && tabs[2].show"></ctab3> | |
</block> | |
<block v-else-if="tabs.length == 4"> | |
<ctab1 v-show="sindex == 0 && tabs[0].show"></ctab1> | |
<ctab2 v-show="sindex == 1 && tabs[1].show"></ctab2> | |
<ctab3 v-show="sindex == 2 && tabs[2].show"></ctab3> | |
<ctab4 v-show="sindex == 3 && tabs[3].show"></ctab4> | |
</block> | |
<block v-else-if="tabs.length >= 5"> | |
<ctab1 v-show="sindex == 0 && tabs[0].show"></ctab1> | |
<ctab2 v-show="sindex == 1 && tabs[1].show"></ctab2> | |
<ctab3 v-show="sindex == 2 && tabs[2].show"></ctab3> | |
<ctab4 v-show="sindex == 3 && tabs[3].show"></ctab4> | |
<ctab5 v-show="sindex == 4 && tabs[4].show"></ctab5> | |
</block> | |
<view class="tabbar"> | |
<view class="tab-item" | |
v-for="(item,index) in tabs" | |
:key="item.text" | |
v-show="item.show" | |
@click="handleTabClick(index)"> | |
<view class="c-tab-item"> | |
<text :class="'tab-iconfont iconfont '+(item.iconfont)" | |
:style="'color:'+(sindex == index ? scolor : color)" | |
v-if="item.iconfont"/> | |
<image :src="sindex == index ? item.iconSelect : item.icon" | |
class="tab-icon" | |
v-else/> | |
<text class="tab-text" | |
:style="'color:'+(sindex == index ? scolor : color)">{{item.text}}</text> | |
<view class="corner" | |
v-show="item.mark > 0">{{item.mark > 99 ? '99+' : item.mark}}</view> | |
</view> | |
</view> | |
</view> | |
</block> | |
</view> | |
</template> | |
<script> | |
//读取配置 | |
import ctabbar from './ctab-config.js' | |
export default { | |
name: "ctab", | |
data() { | |
return { | |
tabs: [], | |
color: '', | |
scolor: '', | |
midBtn: {}, | |
sindex: 0, | |
floor: -1,//midButton开启时使用 | |
} | |
}, | |
mounted() { | |
let tabbar = ctabbar.tabbar | |
this.color = tabbar.color | |
this.scolor = tabbar.selectcolor | |
if(tabbar.midButton && tabbar.midButton.show && tabbar.tabs.length < 2){ | |
throw new Error('midButton模式开启,配置tab选项不能少于2个') | |
} | |
if(tabbar.midButton && tabbar.midButton.show){ | |
let mlength = tabbar.tabs.length < 4 ? 3 : 5 | |
this.floor = Math.floor(mlength/2) | |
} | |
//普通模式,设置选中的tab项 | |
let tablen = tabbar.tabs.length | |
if(!tabbar.midButton.show){ | |
if(!tabbar.tabs[0].show){ | |
this.sindex ++ | |
if(tablen >= 2 && !tabbar.tabs[1].show){ | |
this.sindex ++ | |
if(tablen >= 3 && !tabbar.tabs[2].show){ | |
this.sindex ++ | |
if(tablen >= 4 && !tabbar.tabs[3].show){ | |
this.sindex ++ | |
if(tablen >= 5 && !tabbar.tabs[4].show){ | |
throw new Error('tab不能全部隐藏') | |
} | |
} | |
} | |
} | |
} | |
} | |
if(tabbar.tabs.length <= 5){ | |
this.tabs = tabbar.tabs | |
}else { | |
this.tabs = tabbar.tabs.slice(0,5) | |
} | |
this.midBtn = tabbar.midButton | |
}, | |
methods: { | |
setTheme(color){ | |
this.scolor = color | |
this.midBtn.background = color | |
}, | |
//设置tab隐藏和显示,midButton模式失效 | |
setTabVisible(index,visible){ | |
if(this.tabs[index]){ | |
this.tabs[index].show = visible | |
} | |
}, | |
//设置角标 | |
setCorner(index,num){ | |
if(this.tabs[index]){ | |
this.tabs[index].mark = num | |
} | |
}, | |
handleTabClick(tab){ | |
if(this.midBtn && this.midBtn.show){ | |
if(tab == this.floor){ | |
return | |
} | |
} | |
this.sindex = tab | |
let rindex = tab | |
if(this.midBtn && this.midBtn.show){ | |
if(tab > this.floor){ | |
rindex -- | |
} | |
} | |
this.$emit('tabClick',rindex) | |
}, | |
handleMidBtn(){ | |
this.$emit('midClick') | |
} | |
} | |
} | |
</script> | |
<style> | |
/*这里引入字体图标,如果使用字体图标的话*/ | |
@import '@/common/font/iconfont.css'; | |
.tabbar { | |
position: fixed; | |
z-index: 99; | |
width: 100%; | |
height: 100rpx; | |
background-color: #ffffff; | |
bottom: 0; | |
left: 0; | |
box-shadow: 0rpx 4rpx 8rpx 0rpx rgba(0,0,0,0.5); | |
border-radius: 0px 0px 0px 0px; | |
opacity: 1; | |
display: flex; | |
flex-direction: row; | |
align-items: center; | |
justify-content: center; | |
padding-bottom: constant(safe-area-inset-bottom); | |
padding-bottom: env(safe-area-inset-bottom); | |
box-sizing: content-box; | |
} | |
.tab-item { | |
flex: 1; | |
height: 100rpx; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
} | |
.c-tab-item { | |
height: 120rpx; | |
display: flex; | |
flex-direction: column; | |
width: 120rpx; | |
align-items: center; | |
justify-content: center; | |
position: relative; | |
} | |
.tab-icon { | |
width: 45rpx; | |
height: 45rpx; | |
} | |
.tab-iconfont { | |
font-size: 45rpx; | |
font-weight: bold; | |
} | |
.tab-text { | |
font-size: 26rpx; | |
color: #333333; | |
margin-top: 5rpx; | |
} | |
.mid-btn { | |
position: absolute; | |
display: flex; | |
flex-direction: row; | |
justify-content: center; | |
align-items: center; | |
background-color: red; | |
border-radius: 50%; | |
} | |
.mid-text { | |
font-size: 25rpx; | |
color: #999999; | |
} | |
.corner { | |
text-align: center; | |
width: 45rpx; | |
height: 45rpx; | |
position: absolute; | |
background-color: red; | |
border-radius: 50%; | |
color: white; | |
font-size: 20rpx; | |
font-weight: bold; | |
top: 5rpx; | |
right: 0; | |
display: flex; | |
flex-direction: row; | |
justify-content: center; | |
align-items: center; | |
} | |
</style> |
4、配置文件如下:
var tabbar = { | |
/*开启midButton模式时取前两个或者前四个显示,其他忽略*/ | |
midButton: { | |
show: true,//是否是中间凸起模式 | |
width: '153rpx',//不填默认150rpx 中间按钮大小 | |
iconwidth: '67rpx',//不填默认150rpx 中间图标大小 | |
iconheight: '60rpx', | |
offset: '40rpx',//不填默认50rpx | |
background: '#F7D456',//中间按钮背景颜色 | |
text: '拍一拍', | |
textoffset: '50rpx',//不填默认50rpx | |
showtext: false, | |
icon: '../../static/tabbar/camera.png' | |
}, | |
color: '#333333',//未选中颜色 | |
selectcolor: '#F7D456',//选中颜色 | |
/*tabs最多5个,超过5个超过的部分会被忽略,show属性用来控制tab显示隐藏,midButton开启时失效,iconfont优先,没有就使用icon*/ | |
tabs: [{ | |
icon: '../../static/tabbar/main_tab_home_normal.png', | |
iconSelect: '../../static/tabbar/main_tab_home_select.png', | |
text: '首页', | |
iconfont: '', | |
show: true, | |
mark: 0//角标数量,小于等于0不显示 | |
}, { | |
icon: '../../static/tabbar/main_tab_task_normal.png', | |
iconSelect: '../../static/tabbar/main_tab_task_select.png', | |
text: '任务', | |
iconfont: '', | |
show: true, | |
mark: 100 | |
}, { | |
icon: '../../static/tabbar/main_tab_my_normal.png', | |
iconSelect: '../../static/tabbar/main_tab_my_select.png', | |
text: '我的', | |
iconfont: 'icon-wode',//注意配置字体图标会优先使用字体图标,这里是示例 | |
show: true, | |
mark: 9 | |
}] | |
} | |
module.exports = { | |
tabbar | |
} |
5、使用示例:
<template> | |
<ctab @midClick='midClick' | |
@tabClick='tabClick' | |
ref="ctab"/> | |
</template> | |
<script> | |
export default { | |
data() { | |
return { | |
} | |
}, | |
onLoad() { | |
}, | |
methods: { | |
//凸起按钮点击事件 | |
midClick(){ | |
console.log('midClick') | |
}, | |
//tab切换点击事件 | |
tabClick(tab){ | |
console.log('tabClick',tab) | |
} | |
} | |
} | |
</script> | |
<style> | |
page { | |
width: 100%; | |
height: 100%; | |
} | |
</style> |
6、到这里我们自定义tabbar就完成了,通过修改配置文件中的midButton中的show属性来开启是否中间按钮凸起,接下来我们看下效果。
midButton开启:
普通模式:
再普通模式下,我们可以通过配置或者动态修改tabs中tab obj中的show属性来动态形式和隐藏某个tab,我们这里配置第一个tab为隐藏:
... | |
tabs: [{ | |
icon: '../../static/tabbar/main_tab_home_normal.png', | |
iconSelect: '../../static/tabbar/main_tab_home_select.png', | |
text: '首页', | |
iconfont: '', | |
show: false,//隐藏第一个tab | |
mark: 0//角标数量,小于等于0不显示 | |
}, { | |
icon: '../../static/tabbar/main_tab_task_normal.png', | |
iconSelect: '../../static/tabbar/main_tab_task_select.png', | |
text: '任务', | |
iconfont: '', | |
show: true, | |
mark: 100 | |
}, { | |
icon: '../../static/tabbar/main_tab_my_normal.png', | |
iconSelect: '../../static/tabbar/main_tab_my_select.png', | |
text: '我的', | |
iconfont: '', | |
show: true, | |
mark: 9 | |
}] | |
... |
效果图如下:
7、到这里我们的自定义tabbar就完成了,剩下的就是在tab组件中实现我们各个页面的逻辑。我们通过配置文件可以轻松的使用一个套代码实现tabbar中间按钮凸起、数字角标、动态隐藏、自定义mask覆盖tabbar(需要自己控制好层级),字体图标等功能,并且全端适用。