| <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%); | 
|                     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> |