uniapp自定义tabbar(支持中间凸起,角标,动态隐藏tab,全端适用)

news2025/1/21 3:00:18

uniapp自定义tabbar(支持中间凸起,角标,全端适用)

  • 背景
  • 思路
  • 实现
  • 尾巴

背景

在使用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(需要自己控制好层级),字体图标等功能,并且全端适用。

尾巴

本次内容就这么多,欢迎给我点赞和关注,有什么疑问欢迎给我留言,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/408648.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

box-sizing:border-box的理解和作用

盒子模型 盒子模型是指&#xff1a;外边距&#xff08;margin&#xff09; border&#xff08;边框&#xff09; 内边距&#xff08;padding&#xff09; content&#xff08;内容&#xff09; 可以把每一个容器&#xff0c;比如div&#xff0c;都看做是一个盒子模型 比如你给…

Vue实战【Vue开发中的的前端代码风格规范】

目录&#x1f31f;前言&#x1f31f;命名规范1.1 项目文件命名1.1.1 项目名1.1.2 目录名1.1.3 图像文件名1.1.4 HTML 文件名1.1.5 CSS 文件名1.1.6 JavaScript 文件名1.2 Vue 组件命名1.2.1 单文件组件名1.2.2 单例组件名1.2.3 基础组件名1.2.4 业务组件1.2.5 紧密耦合的组件名…

前端开发中常见的浏览器兼容性问题及解决方案

文章目录前言一、浏览器四大内核二、主流兼容问题&#xff08;一&#xff09;浏览器引擎&#xff08;二&#xff09;兼容问题的原因&#xff08;三&#xff09; 为什么浏览器会存在兼容性问题?&#xff08;四&#xff09;处理兼容问题的思路1. 要不要做&#xff1f;2. 做到什么…

vue3 | HighCharts实战自定义封装之径向条形图

1.前言 目前正在做vue3的数据可视化项目&#xff0c;vue3的组合式api写法十分方便&#xff0c;可以有各种玩法&#xff0c;有兴趣的同学可以看我个人主页的其他文章。难点是在网上找了一圈的有关径向条形图的示例都没有好的解决方案&#xff0c;决心亲自下手&#xff0c;在其中…

Vue(四)Vue脚手架——手把手教你安装和使用

一、什么是Vue脚手架 之前我们使用Vue框架&#xff0c;都是通过脚本的方式引入Vue脚本&#xff0c;在html中运行&#xff0c;但是这只是入门级的操作&#xff0c;下面我们介绍Vue脚手架。 脚手架&#xff1a;脚手架是一类软件的总称&#xff0c;此类软甲用于生成标准化的项目包…

Vue3中简单使用Mock.js

mock.js简介 官方链接&#xff1a;Mock.js (mockjs.com) 前端开发人员用来模拟虚拟数据&#xff0c;拦截ajax请求&#xff0c;方便模拟后端接口 安装 npm install mockjs 使用 本文主要介绍在Vue项目中使用mock.js&#xff0c;包括axios发送请求与请求简单封装 创建mock文件夹…

js+css+html制作简易留言板

jscsshtml制作简易留言板1 案例说明2 编写HTML代码3 编写css代码4 编写JavaScript代码5 全部代码1 案例说明 利用JavaScript、css以及html制作简易留言板&#xff0c;也可以看作是简易评论区。 要求在页面文本框中输入一些文字之后&#xff0c;点击“发布”按钮&#xff0c;就…

前端element-ui组件库el-card卡片【hover效果与点击事件(点击无效用@click.native=““)解决】

&#x1f680;作者简介 主页&#xff1a;水香木鱼的博客 专栏&#xff1a;后台管理系统 能量&#xff1a;&#x1f50b;容量已消耗1%&#xff0c;自动充电中… 笺言&#xff1a;用博客记录每一次成长&#xff0c;书写五彩人生。 &#x1f4d2;技术聊斋 &#xff08;1&#…

直面JavaScript数据处理的5个常见疑难杂症

前言 随着前端技术的不断发展&#xff0c;前端工作需要展示的界面越来越复杂&#xff0c;因此数据处理的场景越来越多&#xff0c;例如&#xff1a;后台管理系统中常常需要展示一个树状结构&#xff0c;后台返回的前端的数据是平级结构&#xff0c;这时候需要我们把数据转成树结…

进阶版JavaScript学习【第二期】

距离上一期更新已经过了好久&#xff0c;非常抱歉。因为自己的一些原因&#xff0c;没有能够及时更新。 博主主页&#xff1a;GUIDM的主页 专栏内容&#xff1a;进阶版JavaScript学习 往期内容&#xff1a;第一期 给大家安利一个刷题神器&#xff1a;牛客网 JavaScript系列刷题…

Vue使用Element-UI实现分页效果

前言 分页在展示数据列表的场景肯定是非常多的。 一般的项目开发中&#xff0c;数据量特别大&#xff0c;一般都是后端接口直接处理分页返回&#xff0c;前端直接调用即可。 但是前端也是可以不需要借助后端&#xff0c;自己也是可以处理分页的。今天我这个后端开发就站在前…

H5画布 canvas(一)canvas简介、绘制圆形/矩形、案例饼状图绘制

目录 1. canvas 简介 2. canvas 标签介绍 3. canvas 上下文 Context 4. 案例&#xff1a;在 canvas 画布中绘制表格 5. canvas 的 beginPath 状态 6. 绘制矩形 rect 7. 绘制圆形 arc 8. 案例&#xff1a;根据一组数据绘制饼状图 1. canvas 简介 canvas 是HTML5 提供的一…

【蓝桥杯Web】大一小白参与蓝桥杯模拟赛二期web组体会

目录 前言 一、相关比赛介绍 1.ACM国际大学生程序设计竞赛 2.蓝桥杯 3.GPLT团队程序设计天梯赛 4.leetcode周赛和双周赛 5.PAT 二、蓝桥杯 1.应该参加蓝桥杯吗&#xff1f; 2.如何进行蓝桥杯的准备 三.蓝桥杯模拟赛二期web组真题 1.凭空消失的TA&#xff08;简单&a…

node-sass安装失败解决方法,终有一款适合我们

项目中常常遇到node-sass安装失败&#xff0c;动不动就是报各种错误。以前我一次也没有失败过&#xff0c;自从系统重装我的天呀&#xff0c;node-sass就没有成功过&#xff0c;我能做的node卸载&#xff0c;sass重装各种版&#xff0c;以及换了淘宝镜像和用了vpn都安装失败。我…

Vue--Router--解决多路由复用同一组件页面不刷新问题

原文网址&#xff1a;Vue--Router--解决多路由复用同一组件页面不刷新问题_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍如何解决Vue的多路由复用同一组件页面不刷新问题。 多路由复用同一组件的场景 多路由使用同一组件 比如&#xff1a;添加博客&#xff08;path为&…

H5如何实现唤起APP

前言 写过hybrid的同学&#xff0c;想必都会遇到这样的需求&#xff0c;如果用户安装了自己的APP&#xff0c;就打开APP或跳转到APP内某个页面&#xff0c;如果没安装则引导用户到对应页面或应用商店下载。这里就涉及到了H5与Native之间的交互&#xff0c;为什么H5能够唤起APP…

【简陋Web应用3】实现人脸比对

文章目录&#x1f349; 前情提要&#x1f337; 效果演示&#x1f95d; 实现过程1. utils.py2. compare.html3. forms.py4. insightface_api.py5. app.py&#x1f345; 记录1. Bugs1.1 cv2.imshow()报错1.2 insightface人脸检测标注框错乱(&#x1f4a2;)2. 杂记&#x1f33e; 小…

给el-table-column添加指定列的点击事件该怎么做

嗨害嗨&#xff0c;我又来了奥。大家在工作中用组件吗&#xff1f;elementUI应该都用过吧&#xff0c; element是一套UI组件库,是由国内饿了么团队开发的。它提供了丰富的PC组件,有效地降低了使用者的开发难度。 如果工作中遇到了表格&#xff0c;我们经常会用el-table组件来写…

微信小程序 slot插槽基本使用

文章目录前言一、基本插槽二、具名插槽三、样式:hoststyleIsolation可控化样式隔离externalClasses外部样式类使用页面/父组件的样式总结前言 小程序中, 如果插槽有必要拿到父组件的数据来展示, 直接父传子即可, 和Vue一样的思路 一、基本插槽 参考Vue的基本插槽, 两者的使用…

初入微前端---qiankun学习这一篇就够了,并在vite中使用qiankun

目录 原因 Why Not iframe&#xff1f; qiankun 使用qiankun部署项目 在子路由的页面去暴露根元素 Create-react-app 子应用 在vite中使用qiankun 方法一&#xff08;不太推荐&#xff09;&#xff1a; 方法二&#xff08;推荐&#xff09;&#xff1a; 安装插件 修…