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>
相关推荐
国服第二切图仔43 分钟前
Electron for 鸿蒙PC项目开发之模态框组件
javascript·electron·harmonyos
irises44 分钟前
从零实现2D绘图引擎:6.动画系统的实现
前端·数据可视化
一 乐1 小时前
数码商城系统|电子|基于SprinBoot+vue的数码商城系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·springboot
盛夏绽放1 小时前
新手入门:实现聚焦式主题切换动效(Vue3 + Pinia + View Transitions)
前端·vue3·pinia·聚焦式主题切换
Howie Zphile1 小时前
NEXTJS/REACT有哪些主流的UI可选
前端·react.js·ui
fruge1 小时前
React Server Components 实战:下一代 SSR 开发指南
前端·javascript·react.js
hxmmm1 小时前
preconnect、dns-prefetch、prerender、preload、prefetch
javascript
郑州光合科技余经理1 小时前
PHP技术栈:上门系统海外版开发与源码解析
java·开发语言·javascript·git·uni-app·php·uniapp
lichong9511 小时前
harmonyos 大屏设备怎么弹出 u 盘
前端·macos·华为·typescript·android studio·harmonyos·大前端