TabBar.vue 1.87 KB
<template>
  <view class="tab-bar">
    <view class="tab-bar-inner">
      <view
        v-for="item in tabs"
        :key="item.path"
        class="tab-item"
        @click="switchTab(item.path)"
      >
        <AppIcon :name="item.icon" size="md" :color="isActive(item.path) ? 'primary' : 'gray'" />
        <text class="tab-label" :class="{ active: isActive(item.path) }">{{ item.label }}</text>
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import AppIcon from './AppIcon.vue'

const { t } = useI18n()

const tabs = computed(() => [
  { path: '/pages/index/index', icon: 'home', label: t('nav.dashboard') },
  { path: '/pages/labels/labels', icon: 'tag', label: t('nav.labels') },
  { path: '/pages/more/more', icon: 'settings', label: t('nav.more') },
])

const isActive = (path: string) => {
  const pages = getCurrentPages()
  const cur = pages[pages.length - 1] as any
  const route = (cur && cur.route) ? cur.route : ''
  const fullPath = path.startsWith('/') ? path.slice(1) : path
  return route === fullPath
}

const switchTab = (path: string) => {
  if (isActive(path)) return
  uni.redirectTo({ url: path })
}
</script>

<style scoped>
.tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #ffffff;
  border-top: 1rpx solid #e5e7eb;
  padding-bottom: env(safe-area-inset-bottom);
  z-index: 999;
  box-shadow: 0 -2rpx 12rpx rgba(15,23,42,0.06);
}

.tab-bar-inner {
  max-width: 960rpx;
  margin: 0 auto;
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 160rpx;
}

.tab-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8rpx;
}

.tab-label {
  font-size: 24rpx;
  color: #9ca3af;
}

.tab-label.active {
  color: var(--theme-primary, #1F3A8A);
  font-weight: 600;
}
</style>