| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 | <template>	<view v-if="show" class="u-tabbar" @touchmove.stop.prevent="() => {}">		<view class="u-tabbar__content safe-area-inset-bottom" :style="{			height: $u.addUnit(height),			backgroundColor: bgColor,		}" :class="{			'u-border-top': borderTop		}">			<view class="u-tabbar__content__item" v-for="(item, index) in list" :key="index" :class="{				'u-tabbar__content__circle': midButton &&item.midButton			}" @tap.stop="clickHandler(index)" :style="{				backgroundColor: bgColor			}">				<view :class="[					midButton && item.midButton ? 'u-tabbar__content__circle__button' : 'u-tabbar__content__item__button'				]">					<u-icon						:size="midButton && item.midButton ? midButtonSize : iconSize"						:name="elIconPath(index)"						img-mode="scaleToFill"						:color="elColor(index)"						:custom-prefix="item.customIcon ? 'custom-icon' : 'uicon'"					></u-icon>					<u-badge :count="item.count" :is-dot="item.isDot"						v-if="item.count"						:offset="[-2, getOffsetRight(item.count, item.isDot)]"					></u-badge>				</view>				<view class="u-tabbar__content__item__text" :style="{					color: elColor(index)				}">					<text class="u-line-1">{{item.text}}</text>				</view>			</view>			<view v-if="midButton" class="u-tabbar__content__circle__border" :class="{				'u-border': borderTop,			}" :style="{				backgroundColor: bgColor,				left: midButtonLeft			}">			</view>		</view>		<!-- 这里加上一个48rpx的高度,是为了增高有凸起按钮时的防塌陷高度(也即按钮凸出来部分的高度) -->		<view class="u-fixed-placeholder safe-area-inset-bottom" :style="{				height: `calc(${$u.addUnit(height)} + ${midButton ? 48 : 0}rpx)`,			}"></view>	</view></template><script>	export default {		props: {			// 显示与否			show: {				type: Boolean,				default: true			},			// 通过v-model绑定current值			value: {				type: [String, Number],				default: 0			},			// 整个tabbar的背景颜色			bgColor: {				type: String,				default: '#ffffff'			},			// tabbar的高度,默认50px,单位任意,如果为数值,则为rpx单位			height: {				type: [String, Number],				default: '50px'			},			// 非凸起图标的大小,单位任意,数值默认rpx			iconSize: {				type: [String, Number],				default: 40			},			// 凸起的图标的大小,单位任意,数值默认rpx			midButtonSize: {				type: [String, Number],				default: 90			},			// 激活时的演示,包括字体图标,提示文字等的演示			activeColor: {				type: String,				default: '#303133'			},			// 未激活时的颜色			inactiveColor: {				type: String,				default: '#606266'			},			// 是否显示中部的凸起按钮			midButton: {				type: Boolean,				default: false			},			// 配置参数			list: {				type: Array,				default () {					return []				}			},			// 切换前的回调			beforeSwitch: {				type: Function,				default: null			},			// 是否显示顶部的横线			borderTop: {				type: Boolean,				default: true			},			// 是否隐藏原生tabbar			hideTabBar: {				type: Boolean,				default: true			},		},		data() {			return {				// 由于安卓太菜了,通过css居中凸起按钮的外层元素有误差,故通过js计算将其居中				midButtonLeft: '50%',				pageUrl: '', // 当前页面URL			}		},		created() {			// 是否隐藏原生tabbar			if(this.hideTabBar) uni.hideTabBar();			// 获取引入了u-tabbar页面的路由地址,该地址没有路径前面的"/"			let pages = getCurrentPages();			// 页面栈中的最后一个即为项为当前页面,route属性为页面路径			this.pageUrl = pages[pages.length - 1].route;		},		computed: {			elIconPath() {				return (index) => {					// 历遍u-tabbar的每一项item时,判断是否传入了pagePath参数,如果传入了					// 和data中的pageUrl参数对比,如果相等,即可判断当前的item对应当前的tabbar页面,设置高亮图标					// 采用这个方法,可以无需使用v-model绑定的value值					let pagePath = this.list[index].pagePath;					// 如果定义了pagePath属性,意味着使用系统自带tabbar方案,否则使用一个页面用几个组件模拟tabbar页面的方案					// 这两个方案对处理tabbar item的激活与否方式不一样					if(pagePath) {						if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) {							return this.list[index].selectedIconPath;						} else {							return this.list[index].iconPath;						}					} else {						// 普通方案中,索引等于v-model值时,即为激活项						return index == this.value ? this.list[index].selectedIconPath : this.list[index].iconPath					}				}			},			elColor() {				return (index) => {					// 判断方法同理于elIconPath					let pagePath = this.list[index].pagePath;					if(pagePath) {						if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) return this.activeColor;						else return this.inactiveColor;					} else {						return index == this.value ? this.activeColor : this.inactiveColor;					}				}			}		},		mounted() {			this.midButton && this.getMidButtonLeft();		},		methods: {			async clickHandler(index) {				if(this.beforeSwitch && typeof(this.beforeSwitch) === 'function') {					// 执行回调,同时传入索引当作参数					// 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this					// 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文					let beforeSwitch = this.beforeSwitch.bind(this.$u.$parent.call(this))(index);					// 判断是否返回了promise					if (!!beforeSwitch && typeof beforeSwitch.then === 'function') {						await beforeSwitch.then(res => {							// promise返回成功,							this.switchTab(index);						}).catch(err => {						})					} else if(beforeSwitch === true) {						// 如果返回true						this.switchTab(index);					}				} else {					this.switchTab(index);				}			},			// 切换tab			switchTab(index) {				// 发出事件和修改v-model绑定的值				this.$emit('change', index);				// 如果有配置pagePath属性,使用uni.switchTab进行跳转				if(this.list[index].pagePath) {					uni.switchTab({						url: this.list[index].pagePath					})				} else {					// 如果配置了papgePath属性,将不会双向绑定v-model传入的value值					// 因为这个模式下,不再需要v-model绑定的value值了,而是通过getCurrentPages()适配					this.$emit('input', index);				}			},			// 计算角标的right值			getOffsetRight(count, isDot) {				// 点类型,count大于9(两位数),分别设置不同的right值,避免位置太挤				if(isDot) {					return -20;				} else if(count > 9) {					return -40;				} else {					return -30;				}			},			// 获取凸起按钮外层元素的left值,让其水平居中			getMidButtonLeft() {				let windowWidth = this.$u.sys().windowWidth;				// 由于安卓中css计算left: 50%的结果不准确,故用js计算				this.midButtonLeft = (windowWidth / 2) + 'px';			}		}	}</script><style scoped lang="scss">	@import "../../libs/css/style.components.scss";	.u-fixed-placeholder {		/* #ifndef APP-NVUE */		box-sizing: content-box;		/* #endif */	}	.u-tabbar {		&__content {			@include vue-flex;			align-items: center;			position: relative;			position: fixed;			bottom: 0;			left: 0;			width: 100%;			z-index: 998;			/* #ifndef APP-NVUE */			box-sizing: content-box;			/* #endif */			&__circle__border {				border-radius: 100%;				width: 110rpx;				height: 110rpx;				top: -48rpx;				position: absolute;				z-index: 4;				background-color: #ffffff;				// 由于安卓的无能,导致只有3个tabbar item时,此css计算方式有误差				// 故使用js计算的形式来定位,此处不注释,是因为js计算有延后,避免出现位置闪动				left: 50%;				transform: translateX(-50%);				&:after {					border-radius: 100px;				}			}			&__item {				flex: 1;				justify-content: center;				height: 100%;				padding: 12rpx 0;				@include vue-flex;				flex-direction: column;				align-items: center;				position: relative;				&__button {					position: absolute;					top: 14rpx;					left: 50%;					transform: translateX(-50%);				}				&__text {					color: $u-content-color;					font-size: 26rpx;					line-height: 28rpx;					position: absolute;					bottom: 14rpx;					left: 50%;					transform: translateX(-50%);				}			}			&__circle {				position: relative;				@include vue-flex;				flex-direction: column;				justify-content: space-between;				z-index: 10;				/* #ifndef APP-NVUE */				height: calc(100% - 1px);				/* #endif */				&__button {					width: 90rpx;					height: 90rpx;					border-radius: 100%;					@include vue-flex;					justify-content: center;					align-items: center;					position: absolute;					background-color: #ffffff;					top: -40rpx;					left: 50%;					z-index: 6;					transform: translateX(-50%);				}			}		}	}</style>
 |