Files
EchoChat/frontend/src/components/CustomTabBar.vue
2026-03-05 09:24:49 +08:00

171 lines
4.1 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.

<!--
自定义 TabBar 组件
设计系统design-system/echochat/MASTER.md
图标方案@dcloudio/uni-ui uni-icons跨平台兼容
色板Primary #2563EB / Text #1E293B / Muted #94A3B8
功能
- 底部导航栏4 Tab消息 / 联系人 / 会议 / 我的
- 选中态使用 filled 图标 + Primary 未选中态使用轮廓图标 + Muted
- 消息 Tab 支持未读消息 badge联系人 Tab 支持好友申请未读 badge
- 使用 switchTab 跳转
-->
<template>
<view class="tab-bar">
<view
v-for="(item, index) in tabs"
:key="item.path"
class="tab-item"
:class="{ 'tab-active': currentIndex === index }"
@tap="switchTo(index)"
>
<view class="tab-icon-wrap">
<uni-icons
:type="currentIndex === index ? item.iconActive : item.icon"
size="24"
:color="currentIndex === index ? '#2563EB' : '#94A3B8'"
/>
<text v-if="getBadge(index) > 0" class="tab-badge">{{ getBadge(index) > 99 ? '99+' : getBadge(index) }}</text>
</view>
<text class="tab-label">{{ item.label }}</text>
</view>
</view>
</template>
<script>
/**
* 自定义 TabBar 组件
*
* Props:
* - current: 当前选中的 Tab 索引
*
* 功能:
* - 跳转使用 uni.switchTab确保与 pages.json tabBar 配置的页面一致
* - 消息 Tabindex=0显示全局未读消息 badge
* - 联系人 Tabindex=1显示待处理好友申请 badge
* - 选中态使用 filled 图标变体以增强视觉区分
*/
import { useChatStore } from '@/store/chat'
import { useContactStore } from '@/store/contact'
export default {
name: 'CustomTabBar',
props: {
/** 当前选中 Tab 的索引0-3 */
current: {
type: Number,
default: 0
}
},
data() {
return {
/** Tab 配置列表icon 为未选中态iconActive 为选中态 */
tabs: [
{ path: '/pages/chat/index', label: '消息', icon: 'chatbubble', iconActive: 'chatbubble-filled' },
{ path: '/pages/contact/index', label: '联系人', icon: 'contact', iconActive: 'contact-filled' },
{ path: '/pages/meeting/index', label: '会议', icon: 'videocam', iconActive: 'videocam-filled' },
{ path: '/pages/profile/index', label: '我的', icon: 'person', iconActive: 'person-filled' }
]
}
},
computed: {
currentIndex() {
return this.current
}
},
methods: {
/**
* 切换 Tab
* @param {number} index - 目标 Tab 索引
*/
switchTo(index) {
if (index === this.currentIndex) return
uni.switchTab({ url: this.tabs[index].path })
},
/**
* 获取指定 Tab 的 badge 数
* @param {number} index - Tab 索引
* @returns {number} badge 数量0 表示不显示)
*/
getBadge(index) {
if (index === 0) {
const chatStore = useChatStore()
return chatStore.totalUnread
}
if (index === 1) {
const contactStore = useContactStore()
return contactStore.pendingCount
}
return 0
}
}
}
</script>
<style scoped>
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 110rpx;
background-color: #FFFFFF;
display: flex;
align-items: center;
justify-content: space-around;
border-top: 1rpx solid #E2E8F0;
padding-bottom: env(safe-area-inset-bottom, 0);
z-index: 999;
}
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
min-height: 88rpx;
transition: opacity 150ms ease;
}
.tab-item:active {
opacity: 0.7;
}
.tab-icon-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4rpx;
}
.tab-badge {
position: absolute;
top: -8rpx;
right: -20rpx;
min-width: 32rpx;
height: 32rpx;
padding: 0 8rpx;
font-size: 20rpx;
line-height: 32rpx;
color: #FFFFFF;
background-color: #EF4444;
border-radius: 16rpx;
text-align: center;
}
.tab-label {
font-size: 22rpx;
color: #94A3B8;
font-weight: 400;
}
.tab-active .tab-label {
color: #2563EB;
font-weight: 500;
}
</style>