Files
account/resources/mobile/components/tab-bar/tab-bar.vue
2026-01-18 17:42:46 +08:00

349 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="tab-bar-container" :style="{ paddingBottom: safeAreaBottom }">
<view
class="tab-bar-item"
v-for="(item, index) in tabbarList"
:key="index"
:class="{ 'active': currentPath === item.pagePath }"
@tap="switchTab(index, item.pagePath)"
>
<view class="icon-wrapper">
<uni-icons
:type="currentPath === item.pagePath ? item.activeIcon : item.icon"
:size="iconSize"
:color="currentPath === item.pagePath ? activeColor : inactiveColor"
:customStyle="{ transition: 'all 0.3s' }"
></uni-icons>
<view class="badge-wrapper">
<!-- 数字徽章 -->
<view class="badge badge-num" v-if="item.badge > 0">
{{ item.badge > 99 ? '99+' : item.badge }}
</view>
<!-- 红点徽章 -->
<view class="badge badge-dot" v-else-if="item.dot"></view>
</view>
</view>
<text class="tab-text" :style="{ color: currentPath === item.pagePath ? activeColor : inactiveColor }">
{{ item.text }}
</text>
<!-- 激活状态下划线 -->
<view class="active-line" v-if="currentPath === item.pagePath" :style="{ backgroundColor: activeColor }"></view>
</view>
</view>
</template>
<script>
import config from '@/config/index.js'
export default {
name: 'TabBar',
props: {
// 当前页面路径
currentPath: {
type: String,
default: ''
},
// 图标大小
iconSize: {
type: Number,
default: 24
},
// 激活状态颜色
activeColor: {
type: String,
default: '#667eea'
},
// 未激活状态颜色
inactiveColor: {
type: String,
default: '#999'
},
// 是否显示激活状态下划线
showActiveLine: {
type: Boolean,
default: true
}
},
data() {
return {
tabbarList: [],
safeAreaBottom: '0px'
}
},
created() {
// 从配置文件加载tabbar配置
this.tabbarList = config.tabbarList || []
// 获取安全区域底部高度
this.getSafeAreaBottom()
},
mounted() {
// 监听页面显示,重新获取安全区域
uni.onWindowResize(() => {
this.getSafeAreaBottom()
})
},
methods: {
/**
* 切换tab
* @param {Number} index - tab索引
* @param {String} path - 页面路径
*/
switchTab(index, path) {
// 如果点击的是当前页面,不执行跳转
if (this.currentPath === path) return
// 发送切换事件
this.$emit('change', path)
// 触发点击反馈
this.playClickFeedback()
// 使用reLaunch跳转到对应页面关闭所有页面打开新页面
this.navigateToPage(path)
},
/**
* 跳转到页面
* @param {String} path - 页面路径
*/
navigateToPage(path) {
// 使用reLaunch跳转因为这是自定义tabbar
uni.reLaunch({
url: path,
success: () => {
console.log('跳转成功:', path)
},
fail: (err) => {
console.error('跳转失败:', err)
this.handleNavigationError()
}
})
},
/**
* 处理跳转失败的情况
*/
handleNavigationError() {
uni.showToast({
title: '页面跳转失败',
icon: 'none',
duration: 2000
})
},
/**
* 点击反馈动画
*/
playClickFeedback() {
// 可以添加震动反馈
if (uni.vibrateShort) {
uni.vibrateShort({
success: () => {},
fail: () => {}
})
}
},
/**
* 获取安全区域底部高度
*/
getSafeAreaBottom() {
const systemInfo = uni.getSystemInfoSync()
this.safeAreaBottom = systemInfo.safeAreaInsets
? systemInfo.safeAreaInsets.bottom + 'px'
: '0px'
},
/**
* 设置数字徽章
* @param {Number} index - tab索引
* @param {Number} count - 徽章数量
*/
setBadge(index, count) {
if (this.tabbarList[index]) {
this.$set(this.tabbarList, index, {
...this.tabbarList[index],
badge: count,
dot: false
})
}
},
/**
* 设置红点徽章
* @param {Number} index - tab索引
* @param {Boolean} show - 是否显示红点
*/
setDot(index, show) {
if (this.tabbarList[index]) {
this.$set(this.tabbarList, index, {
...this.tabbarList[index],
dot: show,
badge: 0
})
}
},
/**
* 清除所有徽章
*/
clearAllBadges() {
this.tabbarList.forEach((item, index) => {
this.$set(this.tabbarList, index, {
...item,
badge: 0,
dot: false
})
})
},
/**
* 获取tabbar项
* @param {Number} index - tab索引
* @returns {Object} tabbar项
*/
getTabItem(index) {
return this.tabbarList[index] || null
}
}
}
</script>
<style lang="scss" scoped>
.tab-bar-container {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
display: flex;
align-items: center;
justify-content: space-around;
height: 100rpx;
background: #fff;
border-top: 1rpx solid #eee;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s;
}
.tab-bar-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
position: relative;
transition: all 0.3s;
cursor: pointer;
&:active {
opacity: 0.7;
transform: scale(0.98);
}
&.active {
.tab-text {
font-weight: 500;
transform: translateY(-2rpx);
}
.icon-wrapper {
transform: translateY(-2rpx);
}
}
}
.icon-wrapper {
position: relative;
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6rpx;
transition: all 0.3s;
}
.badge-wrapper {
position: absolute;
top: 0;
right: -8rpx;
z-index: 10;
}
.badge {
position: absolute;
top: -8rpx;
right: -8rpx;
min-width: 32rpx;
height: 32rpx;
padding: 0 6rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 20rpx;
font-weight: 500;
line-height: 1;
box-shadow: 0 2rpx 8rpx rgba(245, 108, 108, 0.4);
animation: badgeIn 0.3s ease-out;
&.badge-num {
background: linear-gradient(135deg, #f56c6c 0%, #f89898 100%);
border-radius: 16rpx;
border: 2rpx solid #fff;
}
&.badge-dot {
min-width: 16rpx;
height: 16rpx;
background: #f56c6c;
border-radius: 50%;
border: 2rpx solid #fff;
padding: 0;
}
}
@keyframes badgeIn {
0% {
transform: scale(0);
opacity: 0;
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.tab-text {
font-size: 22rpx;
color: #999;
transition: all 0.3s;
line-height: 1.2;
}
.active-line {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
border-radius: 2rpx;
animation: lineIn 0.3s ease-out;
}
@keyframes lineIn {
0% {
width: 0;
}
100% {
width: 40rpx;
}
}
</style>