tabbar封装

32 阅读2分钟

路劲path 等于 当前的tabber item的 path 就高亮

相比与传统的索引好处

多个地方使用 不用加active全局状态

Tabbar

<template>
  <nav class="tabbar-nav">
    <button
      v-for="item in tabs"
      :key="item.path"
      class="tabbar-item"
      :class="{ active: isActive(item) }"
      type="button"
      @click="handleClick(item.path)"
    >
      <span class="tabbar-icon">
        <!-- 简单文字图标,可按需替换成真正 svg / iconfont -->
        {{ item.icon }}
      </span>
      <span class="tabbar-label">{{ item.label }}</span>
    </button>
  </nav>
</template>

<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'

/**
 * Tab 项配置接口
 */
export interface TabItem {
  /** 路由路径 */
  path: string
  /** 显示标签 */
  label: string
  /** 图标(可以是 emoji、文字或后续替换成 SVG) */
  icon: string
}

/**
 * 组件 Props
 */
interface Props {
  /** Tab 配置数组 */
  tabs: TabItem[]
  /** 自定义激活判断函数(可选,默认使用路径匹配) */
  isActiveFn?: (item: TabItem, currentPath: string) => boolean
}

const props = defineProps<Props>()

const route = useRoute()
const router = useRouter()

/**
 * 判断某个 tab 是否激活
 * 如果当前路由匹配 tab 的路径,则激活(高亮显示)
 */
const isActive = (item: TabItem): boolean => {
  // 如果提供了自定义判断函数,使用自定义函数
  if (props.isActiveFn) {
    return props.isActiveFn(item, route.path)
  }

  // 默认判断逻辑:精确匹配主路径,或者以该路径为前缀(方便做二级详情页也高亮)
  return route.path === item.path || route.path.startsWith(item.path + '/')
}

/**
 * 处理 Tab 点击
 */
const handleClick = (path: string) => {
  // 如果点击的是当前路径,不重复跳转
  if (path === route.path) return
  
  router.push(path)
}
</script>

<style scoped>
.tabbar-nav {
  position: sticky;
  bottom: 0;
  z-index: 90;
  display: flex;
  background-color: #ffffff;
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
}

.tabbar-item {
  flex: 1;
  padding: 6px 0 4px;
  border: none;
  background-color: transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #666;
  font-size: 12px;
  gap: 2px;
  transition: all 0.2s;
}

.tabbar-item.active {
  color: #1989fa;
}

.tabbar-item:active {
  transform: scale(0.96);
}

.tabbar-icon {
  font-size: 18px;
  line-height: 1;
}

.tabbar-label {
  line-height: 1.2;
}

@media (max-width: 768px) {
  .tabbar-icon {
    font-size: 16px;
  }

  .tabbar-label {
    font-size: 11px;
  }
}
</style>


tabbarConfig.ts

/**
 * Tab 项配置接口
 */
export interface TabItem {
  /** 路由路径 */
  path: string
  /** 显示标签 */
  label: string
  /** 图标(可以是 emoji、文字或后续替换成 SVG) */
  icon: string
}
import type { TabItem } from '~/components/Tabbar.vue'

/**
 * Tabbar 配置
 * 集中管理所有 Tab 页面的配置
 */
export const tabbarTabs: TabItem[] = [
  { path: '/home', label: '首页', icon: '🏠' },
  { path: '/list', label: '列表', icon: '📋' },
  { path: '/search', label: '搜索', icon: '🔍' },
  { path: '/details', label: '我的', icon: '👤' }
]


    <!-- 底部 Tabbar -->
    <Tabbar :tabs="tabs" />
    
    import Tabbar from '~/components/Tabbar.vue'
import { tabbarTabs } from '~/utils/tabbarConfig'


// 使用统一的 Tab 配置
const tabs = tabbarTabs