| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 | 
							- <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 || item.isDot"
 
- 						: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%);
 
- 					width: 100%;
 
- 					text-align: center;
 
- 				}
 
- 			}
 
- 			&__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>
 
 
  |