基于 `uv-tabbar` 封装的底部导航栏组件,支持**滚动智能显隐**——页面顶部常显、向下滚动隐藏释放全屏、停下或向上滚动立即显示。

目录结构

components/bottom-tabbar/

├── bottom-tabbar.vue     # 底部导航栏组件(纯渲染,无硬编码配置)

├── tabbar.config.js      # 【配置入口】tab 列表、颜色、图标等(增删 tab 改这里)

└── tabbar.js             # 滚动追踪 composable(单例状态)

> `tabbar.js` 是独立于框架的纯状态管理,不依赖组件,可被 `plugins/index.js` 等外部模块导入。

bottom-tabbar.vue
 

<template>
  <!-- 外层容器:用于实现滚动显隐的动画效果 -->
  <view
    class="bottom-tabbar"
    :style="{
      transform: tabbarVisible ? 'translateY(0)' : 'translateY(100%)',
      transition: 'transform 0.32s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
    }"
  >
    <!-- uv-ui 底部导航栏组件,所有配置由 tabbar.config.js 驱动 -->
    <uv-tabbar
      :value="currentTab"
      :fixed="false"
      :placeholder="false"
      :safe-area-inset-bottom="true"
      :border="true"
      :active-color="config.activeColor"
      :inactive-color="config.inactiveColor"
      :icon-size="config.iconSize"
      @change="onTabChange"
    >
      <!-- 遍历配置渲染每个 tab 项 -->
      <uv-tabbar-item
        v-for="item in config.tabs"
        :key="item.name"
        :name="item.name"
        :text="item.showText !== false ? item.text : ''"
      >
        <!-- 选中图标插槽(自定义 image 替换 uv-ui 内置图标) -->
        <template #active-icon>
          <image
            :src="item.activeIcon"
            :style="{ width: config.iconSize + 'px', height: config.iconSize + 'px' }"
          />
        </template>
        <!-- 未选中图标插槽 -->
        <template #inactive-icon>
          <image
            :src="item.inactiveIcon"
            :style="{ width: config.iconSize + 'px', height: config.iconSize + 'px' }"
          />
        </template>
      </uv-tabbar-item>
    </uv-tabbar>
  </view>
</template>

<script setup>
import { ref, onMounted } from "vue"
import { useTabbar } from "./tabbar"
import { tabbarConfig } from "./tabbar.config"

// 直接使用配置对象(静态导入,不会被响应式代理包裹)
const config = tabbarConfig

// tab 名称 → 页面路径 的映射,用于 tabChange 时跳转
const tabPaths = Object.fromEntries(
  config.tabs.map((t) => [t.name, t.path])
)
// 页面路径 → tab 名称 的映射,用于 onMounted 时匹配当前 tab
const pathToTab = Object.fromEntries(
  config.tabs.map((t) => [t.path, t.name])
)

// 滚动显隐状态(由 tabbar.js 中的单例管理)
const { visible: tabbarVisible } = useTabbar()

// 当前选中的 tab 名称,默认取配置中第一个 tab
const currentTab = ref(config.tabs[0]?.name || "home")

// 组件挂载时根据当前页面路由匹配高亮的 tab
onMounted(() => {
  const pages = getCurrentPages()
  if (pages.length > 0) {
    // 取页面栈最后一页的路由(如 "pages/index"),拼成完整路径
    const route = `/${pages[pages.length - 1].route}`
    currentTab.value = pathToTab[route] || config.tabs[0]?.name || "home"
  }
})

// tab 切换回调:跳转到对应页面
function onTabChange(name) {
  const path = tabPaths[name]
  // 路径不存在或与当前 tab 相同时跳过
  if (!path || currentTab.value === name) return
  currentTab.value = name
  // 使用 reLaunch 关闭所有页面栈,与原生 tabBar 的 switchTab 行为等价
  uni.reLaunch({ url: path })
}
</script>

<style lang="scss" scoped>
.bottom-tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 99;
  will-change: transform;
}
</style>

tabbar.config.js
 

// 底部导航栏配置
// 集中管理所有 tab 项、颜色、图标,增删 tab 只改此处即可
export const tabbarConfig = {
  // 选中状态的文字/图标颜色
  activeColor: '#2f855a',
  // 未选中状态的颜色
  inactiveColor: '#909399',
  // 图标尺寸(单位 px)
  iconSize: 22,
  // tab 列表,按顺序从左到右渲染
  tabs: [
    {
      name: 'home',           // tab 唯一标识,用于匹配选中状态
      text: '首页',            // 显示的文本
      showText: true,          // 是否显示文本,false 则只显示图标
      inactiveIcon: '/static/images/tabbar/home.png',     // 未选中图标(绝对路径)
      activeIcon: '/static/images/tabbar/home_.png',      // 选中图标
      path: '/pages/index',    // 点击后跳转的页面路径
    },
    {
      name: 'todo',
      text: '任务清单',
      showText: true,
      inactiveIcon: '/static/images/tabbar/todo.png',
      activeIcon: '/static/images/tabbar/todo_.png',
      path: '/pages/work/todoList/index',
    },
    {
      name: 'record',
      text: '农事记录',
      showText: true,
      inactiveIcon: '/static/images/tabbar/record.png',
      activeIcon: '/static/images/tabbar/record_.png',
      path: '/pages/work/farmRecord/index',
    },
    {
      name: 'compliance',
      text: '合规状态',
      showText: true,
      inactiveIcon: '/static/images/tabbar/compliance.png',
      activeIcon: '/static/images/tabbar/compliance_.png',
      path: '/pages/work/compliance/index',
    },
    {
      name: 'mine',
      text: '我的',
      showText: true,
      inactiveIcon: '/static/images/tabbar/mine.png',
      activeIcon: '/static/images/tabbar/mine_.png',
      path: '/pages/mine/index',
    },
  ],
}

tabbar.js

import { ref } from 'vue'

/** 上次滚动位置,用于判定滚动方向 */
let lastScrollTop = 0

/** 是否可见(向下滚动隐藏,向上/停止/顶部时显示) */
const visible = ref(true)

/** 滚动停止后的空闲定时器(停顿后自动显示) */
let idleTimer = null

/**
 * 更新滚动状态,由全局 onPageScroll 调用
 * @param {number} scrollTop 当前滚动距离
 */
function updateScroll(scrollTop) {
  const st = Number(scrollTop) || 0
  if (st <= 0) {
    visible.value = true
  } else if (st > lastScrollTop) {
    visible.value = false
    clearTimeout(idleTimer)
  } else if (st < lastScrollTop) {
    visible.value = true
    clearTimeout(idleTimer)
  } else {
    clearTimeout(idleTimer)
    idleTimer = setTimeout(() => {
      visible.value = true
    }, 150)
  }
  lastScrollTop = st
}

/** 重置为可见(页面切入前台时调用) */
function resetVisible() {
  lastScrollTop = 0
  visible.value = true
  clearTimeout(idleTimer)
}

export function useTabbar() {
  return {
    visible,
    updateScroll,
    resetVisible,
  }
}

快速接入(3 步)

第一步:复制组件

将 `components/bottom-tabbar/` 整个目录复制到目标项目的 `components/` 下。

确保目标项目已安装 `uv-tabbar` 及其依赖(`uv-icon`、`uv-badge`、`uv-safe-bottom`)。如果未安装,在 `uni_modules` 中添加:

- `uv-tabbar`

- `uv-icon`

- `uv-badge`

- `uv-safe-bottom`

uniapp直接插件市场安装:uv-ui 破釜沉舟DCloud 插件市场

第二步:注册滚动监听

在 `plugins/index.js`(或 `App.vue` 的 global mixin)中添加 tabbar 的滚动追踪:

import { useTabbar } from '@/components/bottom-tabbar/tabbar'

const tabbar = useTabbar()

// 在全局 mixin 中:

onPageScroll(e) {

  const st = e?.scrollTop || e?.detail?.scrollTop || 0

  tabbar.updateScroll(st)

}

onShow() {

  tabbar.resetVisible()

}

如果项目已有类似 `plugins/index.js` 的入口文件,参考示例:

// plugins/index.js

import { useTabbar } from '@/components/bottom-tabbar/tabbar'



export function install(app) {

  const tabbar = useTabbar()



  app.mixin({

    onShow() {

      tabbar.resetVisible()

    },

    onPageScroll(e) {

      const st = e?.scrollTop || e?.detail?.scrollTop || 0

      tabbar.updateScroll(st)

    }

  })

}

 第三步:在页面中添加组件

在需要显示底部导航栏的页面模板末尾添加:

<template>

  <!-- 页面内容 -->

  <bottom-tabbar />

</template>

> 建议写在 `</template>` 闭合之前,作为模板最后一个子节点。

自定义 tab

打开 `tabbar.config.js`,修改 `tabs` 数组即可,无需改动组件模板或脚本。

// tabbar.config.js

export const tabbarConfig = {

  activeColor: '#2f855a',        // 选中颜色

  inactiveColor: '#909399',      // 未选中颜色

  iconSize: 22,                  // 图标大小

  tabs: [

    {

      name: 'home',                // tab 唯一标识

      text: '首页',                // 显示文本

      showText: true,              // 是否显示文本(false 仅显示图标)

      inactiveIcon: '/static/images/tabbar/home.png',   // 未选中图标

      activeIcon: '/static/images/tabbar/home_.png',   // 选中图标

      path: '/pages/index',        // 页面路径

    },

    // 追加或删除 tab 对象即可

  ],

}
字段 类型 说明
name string tab 唯一标识
text string 显示文本
showText boolean 是否显示文本,默认 true。设为 false 时仅显示图标
inactiveIcon string 未选中图标,支持 uv-ui 内置图标名或绝对路径图片
activeIcon string 选中图标,支持 uv-ui 内置图标名或绝对路径图片
path string uni-app 页面路径

> 组件自动根据 `tabs` 数组生成路径映射,增删 tab **只需改这一个文件**,无需修改 `bottom-tabbar.vue`。

滚动显隐行为

场景 tabbar 状态
页面顶部(scrollTop = 0) 显示
向下滚动 立即隐藏,内容全屏展示
向上滚动 立即显示
滚动停止约 150ms 自动显示

行为由 `tabbar.js` 中的 `updateScroll()` 控制,无需额外配置。

属性和方法

 `useTabbar()` 返回值

名称 类型 说明
visible Ref<boolean> 响应式显隐状态,组件内部读取
updateScroll(scrollTop) Function 由全局 onPageScroll 调用,传入当前滚动距离
resetVisible() Function 页面 onShow 时重置为可见

 `<bottom-tabbar />` 组件

纯展示组件,所有配置由 `tabbar.config.js` 驱动,组件自身无 prop。修改 tab 列表、颜色等请编辑 `tabbar.config.js`。

迁移指南:从原生 tabBar 切换到自定义 bottom-tabbar

如果你的项目原本使用 uni-app 原生的 `pages.json` 中的 `tabBar` 配置,切换到本组件需要修改以下文件。

涉及文件一览

# 文件路径 操作 说明
1 pages.json 删除 tabBar 配置块 移除原生 tabBar 定义
2 plugins/index.js 修改 + 新增导入 注册 useTabbar 滚动追踪
3 plugins/tab.js 修改 switchTab 内部改用 uni.reLaunch
4 permission.js 删除 拦截器列表移除 switchTab
5 pages/index.vue 新增 <bottom-tabbar /> 添加自定义导航栏
6 pages/xx/xxx/index.vue 新增 <bottom-tabbar /> 同上
7 pages/xx/xx1/index.vue 新增 <bottom-tabbar /> 同上
8 pages/xx/xx2/index.vue 新增 <bottom-tabbar /> 同上
9 pages/mine/index.vue 新增 <bottom-tabbar /> 同上
10 页面内 uni.switchTab() 调用 改为 uni.reLaunch() 原生 API 不再支持

 详细操作步骤

1. `pages.json` — 删除原生 tabBar

删除 `pages.json` 中的整个 `tabBar` 配置段。以下是需要删除的内容示例:



{

  "pages": [ /* ... 页面列表 ... */ ],

  // 删除以下整个 tabBar 块 ↓↓↓

  "tabBar": {

    "color": "#000000",

    "selectedColor": "#000000",

    "borderStyle": "white",

    "backgroundColor": "#ffffff",

    "list": [

      {

        "pagePath": "pages/index",

        "iconPath": "static/images/tabbar/home.png",

        "selectedIconPath": "static/images/tabbar/home_.png",

        "text": "首页"

      },

      // ... 其余 4 个 tab

    ]

  },

  // ↑↑↑ 删除到此处

  "globalStyle": { /* ... */ }

}

2. `plugins/index.js` — 注册全局滚动追踪

diff

  import { useBackTop } from '@/components/gap-back-top/backTop'

+ import { useTabbar } from '@/components/bottom-tabbar/tabbar'

  export function install(app) {

    const backTop = useBackTop()

+   const tabbar = useTabbar()

    app.mixin({

      onShow() {

        backTop.resetScrollTop()

+       tabbar.resetVisible()

      },

      onPageScroll(e) {

        const st = e?.scrollTop || e?.detail?.scrollTop || 0

        backTop.setScrollTop(st)

+       tabbar.updateScroll(st)

      }

    })

  }

3. `plugins/tab.js` — 修改 switchTab 实现

原生 tabBar 使用 `uni.switchTab()` 跳转,移除后统一使用 `uni.reLaunch()`:

diff

  switchTab(url) {

-   return uni.switchTab({ url })

+   return uni.reLaunch({ url })

  }

 4. `permission.js` — 移除 switchTab 拦截器

diff

- let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]

+ let list = ["navigateTo", "redirectTo", "reLaunch"]

 5. 页面中添加 `<bottom-tabbar />`

在需要设置的 tab 面的模板末尾(`<gap-back-top />` 后)添加:

<template>

  <!-- ... 页面内容 ... -->

  <gap-back-top />

  <bottom-tabbar />

</template>

涉及路径:

- `pages/index.vue`

xxx/xx/index.vue

 6. 替换页面中的 `uni.switchTab()` 调用

如果页面代码中有直接调用 `uni.switchTab()` 的地方,全部改为 `uni.reLaunch()`。例如:

diff

- uni.switchTab({ url: "/pages/work/todoList/index" })

+ uni.reLaunch({ url: "/pages/work/todoList/index" })

 注意事项

- **不要在非 tabbar 页面中使用** `bottom-tabbar`,它会导致底部出现多余的导航栏。

- **保证 tab 页面的 `onPageScroll` 能正常触发**。如果页面使用 `scroll-view scroll-y`,需要在 `scroll-view` 的 `@scroll` 事件中手动调用 `tabbar.updateScroll()`。

- 切换 tab 使用 `uni.reLaunch()` 关闭所有页面栈,与原生 tabBar 的 `switchTab` 行为等价。

- 组件依赖 `uv-tabbar` 系列组件,确保 `uni_modules` 中已安装。

更多推荐