You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
9.0 KiB
330 lines
9.0 KiB
<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>
|
|
|