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>