blob: 448206c043f9daa7541af7995fedfda3f66e181c [file] [log] [blame]
guangchao.xu070005a2020-12-07 09:56:40 +08001<template>
2 <view v-if="show" class="u-tabbar" @touchmove.stop.prevent="() => {}">
3 <view class="u-tabbar__content safe-area-inset-bottom" :style="{
4 height: $u.addUnit(height),
5 backgroundColor: bgColor,
6 }" :class="{
7 'u-border-top': borderTop
8 }">
9 <view class="u-tabbar__content__item" v-for="(item, index) in list" :key="index" :class="{
10 'u-tabbar__content__circle': midButton &&item.midButton
11 }" @tap.stop="clickHandler(index)" :style="{
12 backgroundColor: bgColor
13 }">
14 <view :class="[
15 midButton && item.midButton ? 'u-tabbar__content__circle__button' : 'u-tabbar__content__item__button'
16 ]">
17 <u-icon
18 :size="midButton && item.midButton ? midButtonSize : iconSize"
19 :name="elIconPath(index)"
20 img-mode="scaleToFill"
21 :color="elColor(index)"
22 :custom-prefix="item.customIcon ? 'custom-icon' : 'uicon'"
23 ></u-icon>
24 <u-badge :count="item.count" :is-dot="item.isDot"
25 v-if="item.count"
26 :offset="[-2, getOffsetRight(item.count, item.isDot)]"
27 ></u-badge>
28 </view>
29 <view class="u-tabbar__content__item__text" :style="{
30 color: elColor(index)
31 }">
32 <text class="u-line-1">{{item.text}}</text>
33 </view>
34 </view>
35 <view v-if="midButton" class="u-tabbar__content__circle__border" :class="{
36 'u-border': borderTop,
37 }" :style="{
38 backgroundColor: bgColor,
39 left: midButtonLeft
40 }">
41 </view>
42 </view>
43 <!-- 这里加上一个48rpx的高度,是为了增高有凸起按钮时的防塌陷高度(也即按钮凸出来部分的高度) -->
44 <view class="u-fixed-placeholder safe-area-inset-bottom" :style="{
45 height: `calc(${$u.addUnit(height)} + ${midButton ? 48 : 0}rpx)`,
46 }"></view>
47 </view>
48</template>
49
50<script>
51 export default {
52 props: {
53 // 显示与否
54 show: {
55 type: Boolean,
56 default: true
57 },
58 // 通过v-model绑定current值
59 value: {
60 type: [String, Number],
61 default: 0
62 },
63 // 整个tabbar的背景颜色
64 bgColor: {
65 type: String,
66 default: '#ffffff'
67 },
68 // tabbar的高度,默认50px,单位任意,如果为数值,则为rpx单位
69 height: {
70 type: [String, Number],
71 default: '50px'
72 },
73 // 非凸起图标的大小,单位任意,数值默认rpx
74 iconSize: {
75 type: [String, Number],
76 default: 40
77 },
78 // 凸起的图标的大小,单位任意,数值默认rpx
79 midButtonSize: {
80 type: [String, Number],
81 default: 90
82 },
83 // 激活时的演示,包括字体图标,提示文字等的演示
84 activeColor: {
85 type: String,
86 default: '#303133'
87 },
88 // 未激活时的颜色
89 inactiveColor: {
90 type: String,
91 default: '#606266'
92 },
93 // 是否显示中部的凸起按钮
94 midButton: {
95 type: Boolean,
96 default: false
97 },
98 // 配置参数
99 list: {
100 type: Array,
101 default () {
102 return []
103 }
104 },
105 // 切换前的回调
106 beforeSwitch: {
107 type: Function,
108 default: null
109 },
110 // 是否显示顶部的横线
111 borderTop: {
112 type: Boolean,
113 default: true
114 },
115 // 是否隐藏原生tabbar
116 hideTabBar: {
117 type: Boolean,
118 default: true
119 },
120 },
121 data() {
122 return {
123 // 由于安卓太菜了,通过css居中凸起按钮的外层元素有误差,故通过js计算将其居中
124 midButtonLeft: '50%',
125 pageUrl: '', // 当前页面URL
126 }
127 },
128 created() {
129 // 是否隐藏原生tabbar
130 if(this.hideTabBar) uni.hideTabBar();
131 // 获取引入了u-tabbar页面的路由地址,该地址没有路径前面的"/"
132 let pages = getCurrentPages();
133 // 页面栈中的最后一个即为项为当前页面,route属性为页面路径
134 this.pageUrl = pages[pages.length - 1].route;
135 },
136 computed: {
137 elIconPath() {
138 return (index) => {
139 // 历遍u-tabbar的每一项item时,判断是否传入了pagePath参数,如果传入了
140 // 和data中的pageUrl参数对比,如果相等,即可判断当前的item对应当前的tabbar页面,设置高亮图标
141 // 采用这个方法,可以无需使用v-model绑定的value值
142 let pagePath = this.list[index].pagePath;
143 // 如果定义了pagePath属性,意味着使用系统自带tabbar方案,否则使用一个页面用几个组件模拟tabbar页面的方案
144 // 这两个方案对处理tabbar item的激活与否方式不一样
145 if(pagePath) {
146 if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) {
147 return this.list[index].selectedIconPath;
148 } else {
149 return this.list[index].iconPath;
150 }
151 } else {
152 // 普通方案中,索引等于v-model值时,即为激活项
153 return index == this.value ? this.list[index].selectedIconPath : this.list[index].iconPath
154 }
155 }
156 },
157 elColor() {
158 return (index) => {
159 // 判断方法同理于elIconPath
160 let pagePath = this.list[index].pagePath;
161 if(pagePath) {
162 if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) return this.activeColor;
163 else return this.inactiveColor;
164 } else {
165 return index == this.value ? this.activeColor : this.inactiveColor;
166 }
167 }
168 }
169 },
170 mounted() {
171 this.midButton && this.getMidButtonLeft();
172 },
173 methods: {
174 async clickHandler(index) {
175 if(this.beforeSwitch && typeof(this.beforeSwitch) === 'function') {
176 // 执行回调,同时传入索引当作参数
177 // 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this
178 // 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文
179 let beforeSwitch = this.beforeSwitch.bind(this.$u.$parent.call(this))(index);
180 // 判断是否返回了promise
181 if (!!beforeSwitch && typeof beforeSwitch.then === 'function') {
182 await beforeSwitch.then(res => {
183 // promise返回成功,
184 this.switchTab(index);
185 }).catch(err => {
186
187 })
188 } else if(beforeSwitch === true) {
189 // 如果返回true
190 this.switchTab(index);
191 }
192 } else {
193 this.switchTab(index);
194 }
195 },
196 // 切换tab
197 switchTab(index) {
198 // 发出事件和修改v-model绑定的值
199 this.$emit('change', index);
200 // 如果有配置pagePath属性,使用uni.switchTab进行跳转
201 if(this.list[index].pagePath) {
202 uni.switchTab({
203 url: this.list[index].pagePath
204 })
205 } else {
206 // 如果配置了papgePath属性,将不会双向绑定v-model传入的value值
207 // 因为这个模式下,不再需要v-model绑定的value值了,而是通过getCurrentPages()适配
208 this.$emit('input', index);
209 }
210 },
211 // 计算角标的right值
212 getOffsetRight(count, isDot) {
213 // 点类型,count大于9(两位数),分别设置不同的right值,避免位置太挤
214 if(isDot) {
215 return -20;
216 } else if(count > 9) {
217 return -40;
218 } else {
219 return -30;
220 }
221 },
222 // 获取凸起按钮外层元素的left值,让其水平居中
223 getMidButtonLeft() {
224 let windowWidth = this.$u.sys().windowWidth;
225 // 由于安卓中css计算left: 50%的结果不准确,故用js计算
226 this.midButtonLeft = (windowWidth / 2) + 'px';
227 }
228 }
229 }
230</script>
231
232<style scoped lang="scss">
233 @import "../../libs/css/style.components.scss";
234 .u-fixed-placeholder {
235 /* #ifndef APP-NVUE */
236 box-sizing: content-box;
237 /* #endif */
238 }
239
240 .u-tabbar {
241
242 &__content {
243 @include vue-flex;
244 align-items: center;
245 position: relative;
246 position: fixed;
247 bottom: 0;
248 left: 0;
249 width: 100%;
250 z-index: 998;
251 /* #ifndef APP-NVUE */
252 box-sizing: content-box;
253 /* #endif */
254
255 &__circle__border {
256 border-radius: 100%;
257 width: 110rpx;
258 height: 110rpx;
259 top: -48rpx;
260 position: absolute;
261 z-index: 4;
262 background-color: #ffffff;
263 // 由于安卓的无能,导致只有3tabbar item时,此css计算方式有误差
264 // 故使用js计算的形式来定位,此处不注释,是因为js计算有延后,避免出现位置闪动
265 left: 50%;
266 transform: translateX(-50%);
267
268 &:after {
269 border-radius: 100px;
270 }
271 }
272
273 &__item {
274 flex: 1;
275 justify-content: center;
276 height: 100%;
277 padding: 12rpx 0;
278 @include vue-flex;
279 flex-direction: column;
280 align-items: center;
281 position: relative;
282
283 &__button {
284 position: absolute;
285 top: 14rpx;
286 left: 50%;
287 transform: translateX(-50%);
288 }
289
290 &__text {
291 color: $u-content-color;
292 font-size: 26rpx;
293 line-height: 28rpx;
294 position: absolute;
295 bottom: 14rpx;
296 left: 50%;
297 transform: translateX(-50%);
298 }
299 }
300
301 &__circle {
302 position: relative;
303 @include vue-flex;
304 flex-direction: column;
305 justify-content: space-between;
306 z-index: 10;
307 /* #ifndef APP-NVUE */
308 height: calc(100% - 1px);
309 /* #endif */
310
311 &__button {
312 width: 90rpx;
313 height: 90rpx;
314 border-radius: 100%;
315 @include vue-flex;
316 justify-content: center;
317 align-items: center;
318 position: absolute;
319 background-color: #ffffff;
320 top: -40rpx;
321 left: 50%;
322 z-index: 6;
323 transform: translateX(-50%);
324 }
325 }
326 }
327 }
328</style>