草庐IT

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

abs625 2023-04-05 原文

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

尾巴

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

有关uniapp自定义tabbar(支持中间凸起,角标,动态隐藏tab,全端适用)的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  3. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  4. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  5. ruby CSV : How can I read a tab-delimited file? - 2

    CSV.open(name,"r").eachdo|row|putsrowend我得到以下错误:CSV::MalformedCSVErrorUnquotedfieldsdonotallow\ror\n文件名是一个.txt制表符分隔文件。我是专门做的。我有一个.csv文件,我转到excel,并将文件保存为.txt制表符分隔的文件。所以它是制表符分隔的。CSV.open不应该能够读取制表符分隔的文件吗? 最佳答案 尝试像这样指定字段分隔符:CSV.open("name","r",{:col_sep=>"\t"}).eachdo|row|

  6. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  7. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  8. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  9. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  10. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

随机推荐