uniapp两种方式实现自定义tabbar

1.自定义组件

html 复制代码
<template>
  <view class="tabbar">
    <view
      v-for="(item, index) in list"
      :key="index"
      class="tab-item"
      :class="{ active: selected === index }"
      @click="switchTab(index)"
    >
      <image
        :class="['icon', selected === index ? 'animate' : '']"
        :src="selected === index ? item.selectedIconPath : item.iconPath"
      />
      <text>{{ item.text }}</text>
    </view>
  </view>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue';
import type { Ref } from 'vue';
import { inject } from 'vue';

const selected = inject<Ref<number>>('selected', ref(0));

const list = ref([
  {
    pagePath: '/pages/home/index',
    text: '首页',
    iconPath: '/static/tabbar/home.png',
    selectedIconPath: '/static/tabbar/home-active.png',
  },
  {
    pagePath: '/pages/message/index',
    text: '消息',
    iconPath: '/static/tabbar/message.png',
    selectedIconPath: '/static/tabbar/message-active.png',
  },
  {
    pagePath: '/pages/profile/index',
    text: '个人',
    iconPath: '/static/tabbar/profile.png',
    selectedIconPath: '/static/tabbar/profile-active.png',
  },
]);

const switchTab = (index: number) => {
  const url = list.value[index].pagePath;
  selected.value = index;
  uni.switchTab({ url });
};
onMounted(() => {});
</script>

<style scoped lang="scss">
.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 100rpx;
  display: flex;
  justify-content: space-around;
  align-items: center;
  background: #ffffff;
  padding-bottom: calc(constant(safe-area-inset-bottom) / 2);
  padding-bottom: calc(env(safe-area-inset-bottom) / 2);
  box-sizing: content-box;
}
.tab-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 22rpx;
  color: #aab2b0;
  width: 144rpx;
  height: 98rpx;
  padding: 10rpx 0;
  box-sizing: border-box;
  .icon {
    width: 48rpx;
    height: 48rpx;
    margin-bottom: 5rpx;
  }
  &.active {
    color: #0f60ed;
    .icon {
      &.animate {
        animation: enlarge 0.2s ease-out 1;
        transition: all 0.2s ease-out;
      }
    }
  }
}

@keyframes enlarge {
  0% {
    transform: scale(0.5);
  }
  100% {
    transform: scale(1);
  }
}
</style>
javascript 复制代码
provide('selected', ref(0));

页面对应获取注入数据,然后更改selected

html 复制代码
<template>
  <view class="profile-container">
    <TopBg />
    <view class="top">
      <TopTitle title="个人"></TopTitle>
      <view class="profile">
        <image src="/static/profile/avatar.png" class="avatar"></image>
        <view class="user-info">
          <text class="user-name">{{ userInfo?.nickName || '' }}</text>
          <view class="user-dept">
            <text class="company">{{ userInfo?.dept.deptName || '' }}</text>
          </view>
        </view>
      </view>
    </view>

    <view class="content">
      <view>
        <text>用户账号</text>
        <text>{{ userInfo?.userName || '' }}</text>
      </view>
      <view>
        <text>您的电话</text>
        <text>{{ userInfo?.phonenumber || '' }}</text>
      </view>
      <view>
        <text>您的邮箱</text>
        <text>{{ userInfo?.email || '' }}</text>
      </view>
    </view>
  </view>
 <CustomTabBar />
</template>

<script setup lang="ts">
import { inject, ref } from 'vue';
import type { Ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import TopBg from '@/components/TopBg/index.vue';
import TopTitle from '@/components/TopTitle/index.vue';
import { getUserInfo } from './api';

const selected = inject<Ref<number>>('selected', ref(0));
onShow(() => {
  selected.value = 2;
});

const userInfo = ref();
const _getUserInfo = async () => {
  const res: any = await getUserInfo();
  userInfo.value = res.data;
  userInfo.value.postGroup = res.postGroup;
  userInfo.value.roleGroup = res.roleGroup;
};
_getUserInfo();
</script>

<style scoped lang="scss">
.profile-container {
  width: 100vw;
  position: relative;
  height: calc(100vh - 100rpx - constant(safe-area-inset-bottom) / 2);
  height: calc(100vh - 100rpx - env(safe-area-inset-bottom) / 2);

  .top {
    height: 418rpx;
    width: 100%;
    position: relative;
    padding: 30rpx;
    display: flex;
    flex-direction: column;

    .profile {
      height: 224rpx;
      width: 100vw;
      display: flex;
      align-items: center;
      .avatar {
        width: 132rpx;
        height: 132rpx;
        margin-right: 24rpx;
      }
      .user-info {
        display: flex;
        flex-direction: column;
        .user-name {
          font-weight: bold;
          font-size: 42rpx;
          color: #0f0f0f;
          line-height: 42rpx;
        }

        .user-dept {
          font-weight: 400;
          font-size: 26rpx;
          color: #4c4d4d;
          line-height: 26rpx;
          margin-top: 32rpx;

          .gap {
            width: 1rpx;
            height: 26rpx;
            // background: rgba(76, 77, 77, 0.6);
            margin: 0 24rpx;
          }
        }
      }
    }
  }

  .content {
    width: calc(100% - 60rpx - 60rpx);
    height: 282rpx;
    position: relative;
    text-align: right;
    background: #ffffff;
    margin: 0 30rpx;
    box-sizing: content-box;
    border-radius: 12rpx;
    font-weight: 400;
    font-size: 34rpx;
    color: #0f0f0f;
    line-height: 34rpx;
    padding: 30rpx;
    view {
      width: 100%;
      height: 33%;
      display: flex;
      align-items: center;
      border-bottom: 1rpx solid #dbdbdb;
      &:last-child {
        border-bottom: none;
      }
      text:first-child {
        width: 30%;
        text-align: left;
      }
      text:last-child {
        width: 70%;
        text-align: right;
      }
    }
  }
}
</style>

2.使用uniapp官方方式

pages.json同级目录下创建custom-tab-bar组件;必须同名

html 复制代码
<!-- 自定义TabBar的WXML结构 -->
<view class="tab-bar">
  <view
    wx:for="{{list}}"
    wx:key="index"
    class="tab-bar-item"
    data-index="{{index}}"
    bindtap="switchTab"
  >
    <!-- Tab图标 -->
    <image
      class="{{selected === index ? 'animate tab-bar-icon' : 'tab-bar-icon'}}"
      src="{{selected === index ? item.selectedIconPath : item.iconPath}}"
      mode="aspectFit"
    ></image>
    <!-- Tab文字 -->
    <text
       class="{{selected === index ? 'active tab-bar-text' : 'tab-bar-text'}}"
    >
      {{item.text}}
    </text>
  </view>
</view>
javascript 复制代码
Component({
  data: {
    // 初始选中状态
    selected: 0,
    // TabBar列表配置,使用相对路径与pages.json保持一致
    list: [
      {
        pagePath: '/pages/home/index',
        text: '首页',
        iconPath: '/static/tabbar/home.png',
        selectedIconPath: '/static/tabbar/home-active.png',
      },
      {
        pagePath: '/pages/message/index',
        text: '消息',
        iconPath: '/static/tabbar/message.png',
        selectedIconPath: '/static/tabbar/message-active.png',
      },
      {
        pagePath: '/pages/profile/index',
        text: '个人',
        iconPath: '/static/tabbar/profile.png',
        selectedIconPath: '/static/tabbar/profile-active.png',
      },
    ],
  },

  // 生命周期函数
  attached() {
    // 组件挂载时的初始化逻辑
    const globalSelected = getApp().globalData.selected || 0;
    this.setData({
      selected: globalSelected,
    });
    console.log('custom-tab-bar component attached');
  },

  methods: {
    // Tab点击切换方法
    switchTab(e) {
      // 获取点击的索引
      const index = e.currentTarget.dataset.index;
      // 执行页面跳转
      const pagePath = this.data.list[index].pagePath;
      getApp().globalData.selected = index;
      wx.switchTab({
        url: pagePath,
      });
    },
    // 提供给外部调用的更新选中状态方法
    updateSelected(selectedIndex) {
      this.setData({
        selected: selectedIndex,
      });
    },
  },
});
css 复制代码
/* 自定义TabBar样式 */
.tab-bar {
  width: 100%;
  /* 固定在底部 */
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* 增加高度以适应图标和文字 */
  background: white;
  display: flex;
  justify-content: space-around;
  padding-top: 14rpx;
  /* 适配iPhone底部安全区域 */
  padding-bottom: calc(constant(safe-area-inset-bottom) / 2);
  padding-bottom: calc(env(safe-area-inset-bottom) / 2);
  /* 添加顶部阴影 */
  box-shadow: 0 -1px 6px rgba(0, 0, 0, 0.1);
  z-index: 999;
  /* 确保TabBar始终显示 */
  overflow: visible;
}

/* 单个TabBar项 */
.tab-bar-item {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  font-size: 22rpx;
  color: #aab2b0;
  line-height: 22rpx;
}

.animate {
  animation: enlarge 0.2s ease-out 1;
  transition: all 0.2s ease-out;
}

@keyframes enlarge {
  0% {
    transform: scale(0.5);
  }
  100% {
    transform: scale(1);
  }
}

/* TabBar图标 */
.tab-bar-icon {
  width: 48rpx;
  height: 48rpx;
  display: block;
}

/* TabBar文字 */
.tab-bar-text {
  font-size: 22rpx;
  color: #aab2b0;
  line-height: 14rpx;
  font-weight: bold;
}
.active {
  color: #0f60ed;
}
css 复制代码
{
  "component": true,
  "usingComponents": {}
}

使用

TypeScript 复制代码
<template>
  <view class="profile-container">
    <TopBg />
    <view class="top">
      <TopTitle title="个人"></TopTitle>
      <view class="profile">
        <image src="/static/profile/avatar.png" class="avatar"></image>
        <view class="user-info">
          <text class="user-name">{{ userInfo?.nickName || '' }}</text>
          <view class="user-dept">
            <text class="company">{{ userInfo?.dept.deptName || '' }}</text>
          </view>
        </view>
      </view>
    </view>

    <view class="content">
      <view>
        <text>用户账号</text>
        <text>{{ userInfo?.userName || '' }}</text>
      </view>
      <view>
        <text>您的电话</text>
        <text>{{ userInfo?.phonenumber || '' }}</text>
      </view>
      <view>
        <text>您的邮箱</text>
        <text>{{ userInfo?.email || '' }}</text>
      </view>
    </view>
  </view>
  <!-- <CustomTabBar /> -->
</template>

<script setup lang="ts">
import { inject, ref } from 'vue';
import type { Ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import TopBg from '@/components/TopBg/index.vue';
import TopTitle from '@/components/TopTitle/index.vue';
import { getUserInfo } from './api';

const selected = inject<Ref<number>>('selected', ref(0));
onShow(() => {
  selected.value = 2;
});

const userInfo = ref();
const _getUserInfo = async () => {
  const res: any = await getUserInfo();
  userInfo.value = res.data;
  userInfo.value.postGroup = res.postGroup;
  userInfo.value.roleGroup = res.roleGroup;
};
_getUserInfo();
</script>
<script lang="ts">
export default {
  onLoad() {
    // @ts-ignore
    const tabBar = this.$scope?.getTabBar();
    const app = getApp();
    // @ts-ignore
    app.globalData.selected = 2;
  },
};
</script>

<style scoped lang="scss">
.profile-container {
  width: 100vw;
  position: relative;
  height: calc(100vh - 100rpx - constant(safe-area-inset-bottom) / 2);
  height: calc(100vh - 100rpx - env(safe-area-inset-bottom) / 2);

  .top {
    height: 418rpx;
    width: 100%;
    position: relative;
    padding: 30rpx;
    display: flex;
    flex-direction: column;

    .profile {
      height: 224rpx;
      width: 100vw;
      display: flex;
      align-items: center;
      .avatar {
        width: 132rpx;
        height: 132rpx;
        margin-right: 24rpx;
      }
      .user-info {
        display: flex;
        flex-direction: column;
        .user-name {
          font-weight: bold;
          font-size: 42rpx;
          color: #0f0f0f;
          line-height: 42rpx;
        }

        .user-dept {
          font-weight: 400;
          font-size: 26rpx;
          color: #4c4d4d;
          line-height: 26rpx;
          margin-top: 32rpx;

          .gap {
            width: 1rpx;
            height: 26rpx;
            // background: rgba(76, 77, 77, 0.6);
            margin: 0 24rpx;
          }
        }
      }
    }
  }

  .content {
    width: calc(100% - 60rpx - 60rpx);
    height: 282rpx;
    position: relative;
    text-align: right;
    background: #ffffff;
    margin: 0 30rpx;
    box-sizing: content-box;
    border-radius: 12rpx;
    font-weight: 400;
    font-size: 34rpx;
    color: #0f0f0f;
    line-height: 34rpx;
    padding: 30rpx;
    view {
      width: 100%;
      height: 33%;
      display: flex;
      align-items: center;
      border-bottom: 1rpx solid #dbdbdb;
      &:last-child {
        border-bottom: none;
      }
      text:first-child {
        width: 30%;
        text-align: left;
      }
      text:last-child {
        width: 70%;
        text-align: right;
      }
    }
  }
}
</style>
相关推荐
90后的晨仔3 分钟前
在macOS上无缝整合:为Claude Code配置魔搭社区免费API完全指南
前端
沿着路走到底33 分钟前
JS事件循环
java·前端·javascript
子春一21 小时前
Flutter 2025 可访问性(Accessibility)工程体系:从合规达标到包容设计,打造人人可用的数字产品
前端·javascript·flutter
白兰地空瓶1 小时前
别再只会调 API 了!LangChain.js 才是前端 AI 工程化的真正起点
前端·langchain
jlspcsdn2 小时前
20251222项目练习
前端·javascript·html
行走的陀螺仪2 小时前
Sass 详细指南
前端·css·rust·sass
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ2 小时前
React 怎么区分导入的是组件还是函数,或者是对象
前端·react.js·前端框架
LYFlied3 小时前
【每日算法】LeetCode 136. 只出现一次的数字
前端·算法·leetcode·面试·职场和发展
子春一23 小时前
Flutter 2025 国际化与本地化工程体系:从多语言支持到文化适配,打造真正全球化的应用
前端·flutter
QT 小鲜肉3 小时前
【Linux命令大全】001.文件管理之file命令(实操篇)
linux·运维·前端·网络·chrome·笔记