赞
踩
最近有个需求,需要动态切换tabbar,还有导航栏,刷新等等,看了原生的不是很满足,干脆自己封装了一个
小程序自定义tabbar,用了各种办法,最好的还是通过单页面的形式去做
下面上代码
// pages.json
"pages": [
...
{
"path": "pages/tabbar/tabbar",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom", // 这里开启自定义导航栏
"onReachBottomDistance": 120,
"usingComponents": {
"van-tag": "/wxcomponents/vant/tag/index",
"van-cascader": "/wxcomponents/vant/cascader/index",
"van-popup": "/wxcomponents/vant/popup/index",
"van-calendar": "/wxcomponents/vant/calendar/index"
}
}
},
],
"tabBar": {
"custom": true, // 这里开启自定义tabbar
"color": "rgba(0, 0, 0, 1)",
"selectedColor": "#004ed2",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"height": "0",
"fontSize": "22rpx",
"iconWidth": "24px",
"spacing": "3px",
"list": [ // 这里定义两个页面,空白的就行
{
"pagePath": "pages/inspect/inspect",
"iconPath": "static/imgs/invite.png",
"selectedIconPath": "static/imgs/invite_active.png",
"text": "邀约/预约"
},
{
"pagePath": "pages/search/search",
"iconPath": "static/imgs/record.png",
"selectedIconPath": "static/imgs/record_active.png",
"text": "到访记录"
}
]
}
// pages/tabbar/tabbar 这里开始写tabbar页面
// 这里是自定义tabbar,引入了四个,自行判断是否显示
<view class="container">
<invite-tabbar ref="inviteTabbar" v-if="store.tabbarActiveIndex === 'invite'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></invite-tabbar>
<record-tabbar v-if="store.tabbarActiveIndex === 'record'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></record-tabbar>
<inspect-tabbar v-if="store.tabbarActiveIndex === 'inspect'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></inspect-tabbar>
<statistic-tabbar v-if="store.tabbarActiveIndex === 'statistic'"></statistic-tabbar>
<my-tabbar v-if="store.tabbarActiveIndex === 'my'"></my-tabbar>
</view>
<Header :title="title" :icon="false"></Header> // 这里是自定义导航栏,后面会写
// 这里是自定义上拉刷新,下拉加载
<scroll-view show-scrollbar="true" class="scroll" :scroll-top="scrollTop" scroll-y="true"
:refresher-enabled="isOpenRefresh" :refresher-triggered="triggered" :refresher-threshold="100"
refresher-background="gray" @refresherpulling="onPulling" @refresherrefresh="onRefresh"
@refresherrestore="onRestore" @refresherabort="onAbort" @scrolltoupper="scrolltoupper"
@scrolltolower="scrolltolower" @scroll="scroll">
// 这里是自定义tabbar,引入了四个,自行判断是否显示
<view class="container">
<invite-tabbar ref="inviteTabbar" v-if="store.tabbarActiveIndex === 'invite'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></invite-tabbar>
<record-tabbar v-if="store.tabbarActiveIndex === 'record'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></record-tabbar>
<inspect-tabbar v-if="store.tabbarActiveIndex === 'inspect'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></inspect-tabbar>
<statistic-tabbar v-if="store.tabbarActiveIndex === 'statistic'"></statistic-tabbar>
<my-tabbar v-if="store.tabbarActiveIndex === 'my'"></my-tabbar>
</view>
</scroll-view>
// 这里引入tabbar具体业务组件
import InviteTabbar from "@/components/invite/invite.vue";
import RecordTabbar from "@/components/record/record.vue";
import InspectTabbar from "@/components/inspect/inspect.vue";
import StatisticTabbar from "@/components/statistic/statistic.vue";
import MyTabbar from "@/components/my/my.vue";
// 这里是tabbar列表
import TabbarList from "@/components/tabbar/tabbar.vue";
// 这里引入的封装自定义导航栏
import Header from "@/components/header/header.vue";
import Bus from "@/utils/bus"; // 使用mitt封装一下引入就行
下面是tabbar封装组件列表,会引入到上面
// /components/tabbar/tabbar.vue
<template>
<view class="tabbar">
<template v-for="item in tabbarList" :key="item.id">
<view :class="{ 'tabbar-item': true, active: store.tabbarActiveIndex === item.id }"
v-if="!(item.id === 'inspect' && !store.approvalAuthority)" @tap="handleChangeTabbar(item)">
<img class="icon" :src="store.tabbarActiveIndex === item.id ? item.selectedIconPath : item.iconPath" alt="">
<view>{{ item.text }}</view>
</view>
</template>
</view>
</template>
<script setup lang="ts">
import type { ITabbarList } from "@/types/common";
import { tabbarList } from '@/utils/tabbar'
import useUser from '@/store/user'
const store = useUser()
const handleChangeTabbar = (item: ITabbarList) => {
// console.log(item);
// 这里切换tabbar,通过pinia存储
store.SET_tabbarActiveIndex(item.id)
}
</script>
// /utils/tabbar.ts 这里存放有哪些tabbar页面
import type { ITabbarList } from "@/types/common";
export const tabbarList: ITabbarList[] = [
{
id: "invite",
pagePath: "/pages/invite/invite",
iconPath: "/static/imgs/invite.png",
selectedIconPath: "/static/imgs/invite_active.png",
text: "邀约/预约",
},
...自行添加即可
];
// 要引入的页面
<Header :title="title" :icon="false"></Header> // 这里是自定义导航栏,后面会写
// 封装导航栏
<template>
<!-- <van-sticky class="sticky"> -->
<view class="header">
<img src="/static/imgs/right_arrow.png" alt="" v-if="props.icon" class="icon" @tap="goBack">
<!-- <van-icon v-if="props.icon" class="icon arrow-left" name="arrow-left" @tap="goBack" /> -->
<text class="title">{{ title }}</text>
</view>
<!-- </van-sticky> -->
<view class="header-height"></view>
<van-notify class="van-notify" />
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import useUser from '@/store/user'
import Notify from '@/wxcomponents/vant/notify/notify';
import Bus from '@/utils/bus'
const store = useUser()
// 使用uni.getMenuButtonBoundingClientRect()获取高度,存储到store
const top = ref<string>(store.menuButtonBoundingClientRect.top + 'px')
const height = ref<string>(store.menuButtonBoundingClientRect.height + 'px')
interface IHeader {
title: string, // 标题
icon: boolean, // 是否需要返回上一层图标
color: string, // 标题栏颜色
goBack?: Function // 点击返回上一层传递的函数
}
const props = withDefaults(defineProps<IHeader>(), {
icon: false,
color: '#fff'
})
const goBack = () => {
props.goBack && props.goBack() // 这里可以执行接收到的函数
uni.navigateBack({
delta: 1
})
}
</script>
<style scoped lang="scss">
.sticky {
z-index: 999999;
}
.header {
position: fixed;
top: 0;
right: 0;
left: 0;
display: flex;
justify-content: center;
padding-top: v-bind(top);
padding-bottom: 26rpx;
height: v-bind(height);
line-height: v-bind(height);
background-color: #fff;
font-weight: 700;
font-size: 36rpx;
z-index: 999999;
.icon {
position: absolute;
left: 18rpx;
bottom: 26rpx;
width: 64rpx;
height: 64rpx;
}
}
.header-height {
padding-top: v-bind(top);
padding-bottom: 26rpx;
height: v-bind(height);
line-height: v-bind(height);
background-color: #fff;
z-index: 999999;
}
</style>
// 在tabbar界面使用
<scroll-view show-scrollbar="true" class="scroll" :scroll-top="scrollTop" scroll-y="true"
:refresher-enabled="isOpenRefresh" :refresher-triggered="triggered" :refresher-threshold="100"
refresher-background="gray" @refresherpulling="onPulling" @refresherrefresh="onRefresh"
@refresherrestore="onRestore" @refresherabort="onAbort" @scrolltoupper="scrolltoupper"
@scrolltolower="scrolltolower" @scroll="scroll">
// 这里是自定义tabbar,引入了四个,自行判断是否显示
<view class="container">
<invite-tabbar ref="inviteTabbar" v-if="store.tabbarActiveIndex === 'invite'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></invite-tabbar>
<record-tabbar v-if="store.tabbarActiveIndex === 'record'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></record-tabbar>
<inspect-tabbar v-if="store.tabbarActiveIndex === 'inspect'" v-model:triggered="triggered"
v-model:_freshing="_freshing"></inspect-tabbar>
<statistic-tabbar v-if="store.tabbarActiveIndex === 'statistic'"></statistic-tabbar>
<my-tabbar v-if="store.tabbarActiveIndex === 'my'"></my-tabbar>
</view>
</scroll-view>
// scroll view
const isOpenRefresh = ref(true);
const triggered = ref<string | boolean>(false);
const _freshing = ref(false);
const dataList = ref<Array<number>>([]);
const isAllowRefresh = ref(false);
const scrollTop = ref(0) // 设置滚动条位置
const scrollTopOld = ref(0) // 设置滚动条位置
// 自定义下拉刷新控件被下拉
const onPulling = (e: any) => {
// console.log("onpulling", e);
if (e.detail.dy < 0) return; // 防止上滑页面也触发下拉
if (isAllowRefresh.value) return;
triggered.value = true;
};
// 自定义下拉刷新被触发
const onRefresh = () => {
if (_freshing.value) return;
_freshing.value = true;
// 这里触发刷新数据,具体到业务组件接收
Bus.emit(`refresh-${store.tabbarActiveIndex}`);
setTimeout(() => {
triggered.value = false;
_freshing.value = false;
}, 500);
};
// 自定义下拉刷新被复位
const onRestore = () => {
triggered.value = "restore"; // 需要重置
console.error("onRestore");
};
// 自定义下拉刷新被中止
const onAbort = () => {
console.error("onAbort");
setTimeout(() => {
triggered.value = false;
_freshing.value = false;
}, 0);
};
// 自定义上拉加载
const scrolltolower = (e: any) => {
// console.log(e);
// 这里触发上拉加载,添加数据,具体到业务组件接收
Bus.emit(`push-${store.tabbarActiveIndex}`);
};
// 触顶操作-准入
const scrolltoupper = () => {
isAllowRefresh.value = true;
};
const scroll = (e: any) => {
// console.log(e)
scrollTopOld.value = e.detail.scrollTop // 赋值滚动条位置
}
watch(
() => store.tabbarActiveIndex,
(newVal) => {
// console.log(newVal);
// 注意!!切换tabbar的时候滚动条会保持原位,下面恢复到最顶端
scrollTop.value = scrollTopOld.value
nextTick(function () {
scrollTop.value = 0 // 置顶
});
}
);
// app.vue
<template>
<van-notify class="van-notify" />
</template>
<script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
import useUser from '@/store/user'
import Notify from '@/wxcomponents/vant/notify/notify';
import Bus from '@/utils/bus'
import { ref, reactive, onMounted } from 'vue'
const store = useUser()
// 通知数据
interface INotify {
message?: string,
background?: string,
}
onMounted(() => {
// console.log('app');
// 这里是通知组件
Bus.on('notify', (notifyData) => {
console.log('app notify');
handleRunNotify(notifyData as INotify)
})
})
const handleRunNotify = (notifyData?: INotify) => {
Notify({
message: '请阅读并勾选同意下方协议22',
duration: 3000,
top: (store.menuButtonBoundingClientRect?.top + store.menuButtonBoundingClientRect.height + 24),
selector: '.van-notify',
background: '#fe6262',
...notifyData
})
}
onLaunch(() => {
console.log("App Launch");
console.log(store);
if (store.acces_token) {
console.log('acces_token', store.acces_token);
store.SET_tabbarActiveIndex('invite')
uni.redirectTo({
url: '/pages/tabbar/tabbar',
});
}
// 进入页面开启监听,
Bus.on('notify', (notifyData) => {
console.log('app notify');
handleRunNotify(notifyData as INotify)
})
// 有的页面返回上一层并通知,所以这里需要做一个转发,也可以上面做一个延迟,最好是分开
Bus.on('invite-again', (notify) => {
console.log('invite-again');
setTimeout(() => {
Bus.emit('notify', notify)
}, 500);
})
});
onShow(() => {
console.log("App Show");
});
onHide(() => {
console.log("App Hide");
});
</script>
<style lang="scss">
@import '@/wxcomponents/vant/common/index.wxss';
page {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica,
Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei',
sans-serif;
font-family: 'PingFang SC';
background-color: #f4f5fa;
height: 100vh;
}
.container {
padding: 24rpx 32rpx 120rpx;
box-sizing: border-box;
}
:deep(.van-notify.van-notify--danger) {
// position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: calc(100% - 64rpx) !important;
margin: 0 32rpx !important;
height: 88rpx;
// line-height: 88rpx;
border-radius: 16rpx;
opacity: 0.8;
// transform: translateY(32rpx);
box-sizing: border-box;
}
.input-placeholder,
.textarea-placeholder {
font-size: 28rpx;
color: #ccc;
}
/* dialog弹窗 */
.dialog {
:deep(.van-scale-enter-to) {
width: 622rpx !important;
}
:deep(.van-dialog__message) {
padding: 72rpx 88rpx 50rpx !important;
line-height: 50.4rpx !important;
color: #333 !important;
font-size: 36rpx !important;
}
:deep(.van-dialog__footer) {
button {
color: #004ed2;
font-size: 32rpx !important;
}
.van-hairline--right {
button {
color: #333;
}
}
}
}
</style>
使用通知组件
// 需要使用通知组件的页面
<van-notify class="van-notify" />
import Bus from '@/utils/bus'
let notify = {
message: '要发送的消息',
background: '通知的背景色',
}
Bus.emit('notify', notify)
到这里就全部实现了uniapp开发微信小程序自定义tabbar,自定义导航栏,自定义上拉刷新,下拉加载,封装通知组件等功能,有问题可以留言交流~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。