组件源码
<script lang="ts" setup>
import { ref, computed, onMounted, getCurrentInstance } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import mIcon from '../m-icon/m-icon.vue';
interface TabItem {
text: string;
iconPath: string;
selectedIconPath: string;
count?: number;
isDot?: boolean;
midButton?: boolean;
pagePath?: string;
customIcon?: boolean;
}
const props = defineProps({
show: { type: Boolean, default: true },
modelValue: { type: [String, Number], default: 0 },
bgColor: { type: String, default: '#ffffff' },
height: { type: [String, Number], default: '50px' },
iconSize: { type: [String, Number], default: 40 },
midButtonSize: { type: [String, Number], default: 90 },
activeColor: { type: String, default: '#303133' },
inactiveColor: { type: String, default: '#606266' },
midButton: { type: Boolean, default: false },
list: { type: Array as () => TabItem[], default: () => [] },
beforeSwitch: { type: Function as unknown as () => (index: number) => Promise<boolean> | boolean, default: null },
borderTop: { type: Boolean, default: true },
hideTabBar: { type: Boolean, default: true },
});
const emit = defineEmits(['change', 'update:modelValue']);
const midButtonLeft = ref<string>('50%');
const pageUrl = ref<string>('');
onMounted(() => {
if (props.hideTabBar) uni.hideTabBar();
const pages = getCurrentPages();
pageUrl.value = pages[pages.length - 1]?.route || '';
if (props.midButton) getMidButtonLeft();
});
const normalizePath = (path: string) => path.replace(/^\/+/, '').split('?')[0];
const elIconPath = (index: number) => {
const item = props.list[index];
const pagePath = item.pagePath;
if (pagePath) {
const current = normalizePath(pageUrl.value);
const target = normalizePath(pagePath);
return current === target ? item.selectedIconPath : item.iconPath;
} else {
return index === props.modelValue ? item.selectedIconPath : item.iconPath;
}
};
const elColor = (index: number) => {
const item = props.list[index];
const pagePath = item.pagePath;
if (pagePath) {
return (pagePath === pageUrl.value || '/' + pagePath === pageUrl.value)
? props.activeColor
: props.inactiveColor;
} else {
return index === props.modelValue ? props.activeColor : props.inactiveColor;
}
};
const clickHandler = async (index: number) => {
if (typeof props.beforeSwitch === 'function') {
const result = props.beforeSwitch(index);
if (result instanceof Promise) {
try {
const res = await result;
if (res) switchTab(index);
} catch {}
} else if (result === true) {
switchTab(index);
}
} else {
switchTab(index);
}
};
const switchTab = (index: number) => {
emit('change', index);
const item = props.list[index];
if (item.pagePath) {
uni.switchTab({ url: item.pagePath });
} else {
emit('update:modelValue', index);
}
};
const getOffsetRight = (count?: number, isDot?: boolean) => {
if (isDot) return -20;
else if ((count || 0) > 9) return -40;
else return -30;
};
const getMidButtonLeft = () => {
const res = uni.getSystemInfoSync();
midButtonLeft.value = res.windowWidth / 2 + 'px';
};
</script>
<template>
<view v-show="show" class="u-tabbar" @touchmove.stop.prevent>
<view
class="u-tabbar__content safe-area-inset-bottom"
:style="{
height: height + (typeof height === 'number' ? 'rpx' : ''),
backgroundColor: bgColor,
}"
:class="{ 'u-border-top': borderTop }"
>
<view
v-for="(item, index) in list"
:key="index"
class="u-tabbar__content__item"
:class="{ 'u-tabbar__content__circle': midButton && item.midButton }"
@tap.stop="() => clickHandler(index)"
:style="{ backgroundColor: bgColor }"
>
<view
:class="[
midButton && item.midButton
? 'u-tabbar__content__circle__button'
: 'u-tabbar__content__item__button'
]"
>
<!-- {{ elIconPath(index) }} -->
<m-images mode="aspectFit" :url="elIconPath(index)" width="48rpx" height="48rpx"></m-images>
<!-- <m-icon
:size="midButton && item.midButton ? midButtonSize : iconSize"
:name="elIconPath(index)"
img-mode="scaleToFill"
:color="elColor(index)"
:custom-prefix="item.customIcon ? 'custom-icon' : 'uicon'"
/> -->
<!-- <u-badge
v-show="item.count || item.isDot"
:count="item.count"
:is-dot="item.isDot"
:offset="[-2, getOffsetRight(item.count, item.isDot)]"
/> -->
</view>
<view
class="u-tabbar__content__item__text"
:style="{ color: elColor(index) }"
>
<text class="u-line-1">{{ item.text }}</text>
</view>
</view>
<view
v-show="midButton"
class="u-tabbar__content__circle__border"
:class="{ 'u-border': borderTop }"
:style="{ backgroundColor: bgColor, left: midButtonLeft }"
/>
</view>
<view
class="u-fixed-placeholder safe-area-inset-bottom"
:style="{ height: `calc(${height}${typeof height === 'number' ? 'rpx' : ''} + ${midButton ? 48 : 0}rpx)` }"
/>
</view>
</template>
<style scoped lang="scss">
//@import "../../libs/css/style.components.scss";
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}
.u-fixed-placeholder {
box-sizing: content-box;
}
.u-tabbar {
&__content {
padding-bottom: env(safe-area-inset-bottom);
height: calc(50px + env(safe-area-inset-bottom));
@include vue-flex;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 998;
box-sizing: content-box;
&__circle__border {
border-radius: 100%;
width: 110rpx;
height: 110rpx;
top: -48rpx;
position: absolute;
z-index: 4;
background-color: #ffffff;
left: 50%;
transform: translateX(-50%);
&:after {
border-radius: 100px;
}
}
&__item {
flex: 1;
justify-content: center;
height: 100%;
padding: 12rpx 0;
@include vue-flex;
flex-direction: column;
align-items: center;
position: relative;
&__button {
position: absolute;
top: 14rpx;
left: 50%;
transform: translateX(-50%);
}
&__text {
color: #ff7300;
font-size: 26rpx;
line-height: 28rpx;
position: absolute;
bottom: 14rpx;
left: 50%;
transform: translateX(-50%);
width: 100%;
text-align: center;
}
}
&__circle {
position: relative;
@include vue-flex;
flex-direction: column;
justify-content: space-between;
z-index: 10;
height: calc(100% - 1px);
&__button {
width: 90rpx;
height: 90rpx;
border-radius: 100%;
@include vue-flex;
justify-content: center;
align-items: center;
position: absolute;
background-color: #ffffff;
top: -40rpx;
left: 50%;
z-index: 6;
transform: translateX(-50%);
}
}
}
}
</style>
说明
let list = [
{
// 非凸起按钮未激活的图标,可以是uView内置图标名或自定义扩展图标库的图标
// 或者png图标的【绝对路径】,建议尺寸为80px * 80px
// 如果是中间凸起的按钮,只能使用图片,且建议为120px * 120px的png图片
iconPath: "home",
// 激活(选中)的图标,同上
selectedIconPath: "home-fill",
// 显示的提示文字
text: "首页",
// 红色角标显示的数字,如果需要移除角标,配置此参数为0即可
count: 2,
// 如果配置此值为true,那么角标将会以红点的形式显示
isDot: true,
// 如果使用自定义扩展的图标库字体,需配置此值为true
// 自定义字体图标库教程
customIcon: false,
// 如果是凸起按钮项,需配置此值为true
midButton: false,
// 点击某一个item时,跳转的路径,此路径必须是pagees.json中tabBar字段中定义的路径
pagePath: "", 路径需要以"/"开头
},
];
代码
<view>
<view class="u-page">
<!-- 所有内容的容器 -->
</view>
<!-- 与包裹页面所有内容的元素u-page同级,且在它的下方 -->
<m-tabbar v-model="current" :list="list" :mid-button="true"></m-tabbar>
</view>
</template>
<script>
export default {
data() {
return {
list: [
{
iconPath: "home",
selectedIconPath: "home-fill",
text: "首页",
count: 2,
isDot: true,
customIcon: false,
},
{
iconPath: "photo",
selectedIconPath: "photo-fill",
text: "放映厅",
customIcon: false,
},
{
iconPath: xxxx",
selectedIconPath: "xxx",
text: "发布",
midButton: true,
customIcon: false,
},
{
iconPath: "play-right",
selectedIconPath: "play-right-fill",
text: "直播",
customIcon: false,
},
{
iconPath: "account",
selectedIconPath: "account-fill",
text: "我的",
count: 23,
isDot: false,
customIcon: false,
},
],
current: 0,
};
},
};
</script>
-
height配置导航栏高度,建议使用默认值即可,默认为50px,与 uni-app 自带系统导航栏高度一致
-
bg-color组件的背景颜色
-
active-color与inactive-color配置提示文字和图标的颜色(如果是字体图标的话),可以搭配bg-color达到自定义导航栏主题的效果
自定义 tabbar 场景,我们不建议在一个页面内通过几个组件,用v-if切换去模拟各个页面,而应该使用 uni-app 自带的 tabbar 系统,同时隐藏原生的 tabbar, 再引入自定导航栏,这样可以保证原有性能,同时又能自定义 tabbar,思路如下:
在 pages.json 中正常定义 tabbar 逻辑和字段,只需配置tabBar字段list中的pagePath(需以"/"开头)属性即可
在各个 tabbar 页面引入u-tabbar组件,组件会默认自动通过uni.hideTabBar()隐藏系统 tabbar
通过vuex引用同一份 tabbar 组件的list参数,这样可以做到修改某一个页面的u-tabbar数据,其他页面的u-tabbar也能同步更新
组件内部会自动处理各种跳转的逻辑,同时需要注意以下两点:
要在list参数中配置pagePath路径,此路径为pages.json中定义的 tabbar 字段的路径
此种方式,无需通过v-model绑定活动项,内部会自动进行判断和跳转
底部菜单配置
// 底部菜单
// import { useImageAssets } from '@/utils/useImageAssets';
// const images=useImageAssets()
// "/static/tabbar/home1.png"
const listTabbar =[{
iconPath:"/static/images/tabbar/home.png",
selectedIconPath: "/static/images/tabbar/home1.png",
text: "首页",
pagePath:"/pages/index/index",
customIcon: false,
},
{
iconPath: "/static/images/tabbar/class.png",
selectedIconPath: "/static/images/tabbar/class1.png",
text: "分类",
customIcon: false,
pagePath:"/pages/classList/index",
},
{
iconPath: "/static/images/tabbar/shapping.png",
selectedIconPath: "/static/images/tabbar/shapping1.png",
text: "购物车",
midButton: true,
pagePath:"/pages/shapping/index",
customIcon: false,
},
{
iconPath: "/static/images/tabbar/fujin.png",
selectedIconPath: "/static/images/tabbar/fujin1.png",
text: "附近",
customIcon: false,
pagePath:"/pages/near/index",
},
{
iconPath: "/static/images/tabbar/my.png",
selectedIconPath: "/static/images/tabbar/my1.png",
text: "我的",
pagePath:"/pages/my/my",
customIcon: false,
},]
export {
listTabbar
}
注意
-
小程序 "custom": true,添加这个
uniap pages.json添加配置
"tabBar": {
"color": "#666666",
"selectedColor": "#40AE36",
"custom": true,
"borderStyle": "white",
"backgroundColor": "#ffffff",
"list": [
{
"iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home1.png",
"pagePath": "pages/index/index",
"text": "首页"
},
{
"iconPath": "static/images/tabbar/class.png",
"selectedIconPath": "static/images/tabbar/class1.png",
"pagePath": "pages/classList/index",
"text": "分类"
},
{
"iconPath": "static/images/tabbar/shapping.png",
"selectedIconPath": "static/images/tabbar/shapping1.png",
"pagePath": "pages/shapping/index",
"text": "购物车"
},
{
"iconPath": "static/images/tabbar/fujin.png",
"selectedIconPath": "static/images/tabbar/fujin1.png",
"pagePath": "pages/near/index",
"text": "附近"
},
{
"iconPath": "static/images/tabbar/my.png",
"selectedIconPath": "static/images/tabbar/my1.png",
"pagePath": "pages/my/my",
"text": "我的"
}
]
}
app.vue中隐藏
这样自定义tabbar就可以使用uni.switchTab跳转了
onLaunch(async () => {
uni.hideTabBar()
});
使用
index.vue
<m-tabbar v-model="current" :list="listTabbar" :show="true" :hideTabBar="true"></m-tabbar>
listTabbar 菜单配置
hideTabBar 是否隐藏
current 当前的菜单
示例代码
<template>
<view>
<text>哈哈哈</text>
<m-tabbar v-model="current" :list="listTabbar" :show="true" :hideTabBar="true"></m-tabbar>
</view>
</template>
<script lang="ts" setup>
import {listTabbar} from "@/config/tabbarConfig"
import { ref } from "vue";
const current = ref(1)
</script>