当前位置: 首页 > news >正文

uniapp vue3 ts自定义底部 tabbar菜单

组件源码

<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><viewclass="u-tabbar__content safe-area-inset-bottom":style="{height: height + (typeof height === 'number' ? 'rpx' : ''),backgroundColor: bgColor,}":class="{ 'u-border-top': borderTop }"><viewv-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-badgev-show="item.count || item.isDot":count="item.count":is-dot="item.isDot":offset="[-2, getOffsetRight(item.count, item.isDot)]"/> --></view><viewclass="u-tabbar__content__item__text":style="{ color: elColor(index) }"><text class="u-line-1">{{ item.text }}</text></view></view><viewv-show="midButton"class="u-tabbar__content__circle__border":class="{ 'u-border': borderTop }":style="{ backgroundColor: bgColor, left: midButtonLeft }"/></view><viewclass="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,// 如果是凸起按钮项,需配置此值为truemidButton: 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>
http://www.xdnf.cn/news/1337383.html

相关文章:

  • AUTOSAR自适应平台(AP)中元类(Metaclass)、建模(Modeling) 和 ARXML 这三者的核心关系与区别
  • AR眼镜在制造业的生产设备智慧运维方案介绍
  • Multi Agents Collaboration OS:Browser Automation System
  • 自动驾驶GOD:3D空间感知革命
  • C++析构函数
  • 训练后数据集后部署PaddleOCR转trt流程
  • 使用C++17标准 手写一个vector
  • [Mysql数据库] Mysql安全知识
  • 12KM无人机高清图传通信模组——打造未来空中通信新高度
  • Docker操作速查表
  • 动态规划----6.单词拆分
  • AI重塑软件测试:质量保障的下一站
  • 【clion】cmake脚本1:调试脚本并构建Fargo项目win32版本
  • Linux: network: arp: arp_accept
  • HTML应用指南:利用POST请求获取全国刘文祥麻辣烫门店位置信息
  • 我从零开始学习C语言(12)- 循环语句 PART1
  • DRF序列化器
  • PyTorch API 7
  • 数据安全事件分级
  • 嵌入式的各个要点总结(不断更新)
  • KubeBlocks for ClickHouse 容器化之路
  • 第三十三天(信号量)
  • GO环境变量中GO111MODULE到底是干啥的?
  • 【NFTurbo】基于Redisson滑动窗口实现验证码发送限流
  • 【运维】githubvercel学习使用
  • nginx-下载功能-状态统计-访问控制
  • Qt 中最经典、最常用的多线程通信场景
  • 安装electron报错的解决方法
  • 【Express零基础入门】 | 构建简易后端服务的核心知识
  • jvm三色标记