uniapp 封装自定义头部导航栏

307 阅读2分钟

封装原因

项目中有时候需要使用自定义的头部导航栏,原生的无法满足需求

参数

属性名描述示例
title标题字符串:'首页'
titleStyle标题的style样式对象:{}
navbarStyle导航栏的style样式对象:{}
bgColor导航栏的背景色字符串:'#fff'
type左侧胶囊的内容字符串:'all',详细值请在下方查看
isPlaceholder是否占位Boolean: true
zIndex导航栏的层级Number

参数解释

type

  • 用于控制左侧胶囊的内容
  • all 有返回和回到首页
  • back 只有返回
  • home 只有回到首页
  • none 什么都没有

注意点

  • 组件提供3个插槽:left、title、right
  • 在小程序端right插槽不生效(一般来说,因为小程序右侧胶囊的存在,也不会有设计在右侧的内容)
  • 如果使用了left插槽,那么参数type会失效
  • 如果使用了title插槽,那么参数title会失效

代码

<template>
	<view
		class="navbar-box"
		:style="{ ...props.navbarStyle, 'background-color': props.bgColor, 'z-index': zIndex }"
		v-if="placeholderHeight"
	>
		<!-- 状态栏 -->
		<view :style="{ height: statusBarHeight + 'px' }"></view>
		<!-- 导航栏 -->
		<view :style="{ height: navBarHeight + 'px' }">
			<div class="navbar-default">
				<div class="left-box">
					<slot name="left">
						<div
							class="default-left"
							v-if="props.type != 'none'"
						>
							<view
								class="default-all"
								v-if="props.type == 'all'"
							>
								<text @click="goBack">返回</text>
								<view class="default-line"></view>
								<text @click="goHome">首页</text>
							</view>
							<view
								class="default-back"
								v-if="props.type == 'back'"
							>
								<text @click="goBack">返回</text>
							</view>
							<view
								class="default-home"
								v-if="props.type == 'home'"
							>
								<text @click="goHome">首页</text>
							</view>
						</div>
					</slot>
				</div>
				<slot name="title">
					<view
						class="default-title"
						:style="props.titleStyle"
						>{{ props.title }}</view
					>
				</slot>
				<!-- #ifndef MP -->
				<div class="right-box">
					<slot name="right"> </slot>
				</div>
				<!-- #endif -->
			</div>
		</view>
	</view>
	<!-- 占位 -->
	<view
		:style="{ height: placeholderHeight + 'px' }"
		v-if="props.isPlaceholder"
	></view>
</template>
<script setup>
	import { ref, onMounted, computed, watchEffect } from 'vue';
	import { onLoad } from '@dcloudio/uni-app';
	const props = defineProps({
		title: {
			type: String,
			default: '',
		},
		titleStyle: {
			type: Object,
		},
		navbarStyle: {
			type: Object,
		},
		bgColor: {
			type: String,
			default: '#fff',
		},
		type: {
			type: String,
			default: 'all',
		},
		isPlaceholder: {
			type: Boolean,
			default: true,
		},
		zIndex: {
			type: Number,
			default: 2000,
		},
	});
	const emit = defineEmits(['getNavbarHeight']);

	onMounted(() => {
		geStatusBarHeight();
		getNavBarHeight();
		emit('getNavbarHeight', statusBarHeight.value + navBarHeight.value);
	});

	let statusBarHeight = ref(0);
	let navBarHeight = ref(0);

	// 小程序右侧胶囊按钮信息
	let menuButtonInfo = ref('');

	// 计算占位盒子高度
	const placeholderHeight = computed(() => {
		return statusBarHeight.value + navBarHeight.value;
	});

	// 获取状态栏高度
	function geStatusBarHeight() {
		// #ifdef H5
		statusBarHeight.value = 0;
		// #endif

		// #ifdef MP-WEIXIN || APP-PLUS
		statusBarHeight.value = uni.getWindowInfo().statusBarHeight;
		// #endif
	}

	// 获取导航栏高度
	function getNavBarHeight() {
		// #ifdef MP-WEIXIN
		menuButtonInfo.value = uni.getMenuButtonBoundingClientRect();
		navBarHeight.value =
			menuButtonInfo.value.height +
			(menuButtonInfo.value.top - uni.getWindowInfo().statusBarHeight) * 2 +
			2;
		// #endif

		// #ifdef APP-PLUS
		if (uni.getDeviceInfo().osName == 'android') {
			navBarHeight.value = 48;
		} else {
			navBarHeight.value = 44;
		}
		// #endif

		// #ifdef H5
		navBarHeight.value = 44;
		// #endif
	}

	// 返回上一页
	function goBack() {
		const pages = getCurrentPages();
		if (pages.length == 1) {
			uni.reLaunch({
				url: '/pages/home/index',
			});
			return;
		}
		uni.navigateBack();
	}
	// 去首页
	function goHome() {
		uni.reLaunch({
			url: '/pages/home/index',
		});
	}
</script>

<style lang="scss" scoped>
	.navbar-box {
		position: fixed;
		top: 0;
		left: 0;
		width: 100%;
	}
	.navbar-default {
		height: 100%;
		display: flex;
		justify-content: center;
		align-items: center;
		position: relative;
	}
	.left-box {
		position: absolute;
		left: 20rpx;
		top: 50%;
		transform: translateY(-50%);
	}
	.default-left {
		.default-all {
			display: flex;
			justify-content: center;
			align-items: center;
			padding: 10rpx 30rpx;
			background: rgba(0, 0, 0, 0.5);
			border-radius: 32rpx;
			.default-line {
				width: 2rpx;
				height: 40rpx;
				background: #afafaf;
				margin: 0 20rpx;
			}
			color: #fff;
		}
		.default-back,
		.default-home {
			width: 63rpx;
			height: 63rpx;
			background: rgba(0, 0, 0, 0.5);
			border-radius: 50%;
			display: flex;
			justify-content: center;
			align-items: center;
			color: #fff;
		}
	}
	.default-title {
		color: #000;
		font-weight: bold;
		font-size: 16px;
	}
	.right-box {
		position: absolute;
		right: 20rpx;
		top: 50%;
		transform: translateY(-50%);
	}
</style>

页面使用

<myNavbar title="标题" @getNavbarHeight="getNavbarHeight">
		<template #left> 左侧内容 </template>
		<template #title> 标题内容 </template>
		<template #right> 右侧内容 </template>
	</myNavbar>
        
function getNavbarHeight(height) {
		console.log(height);
	}

监听页面滚动距离

:bgColor="scrollTop > 20 ? '#D2F5DA' : 'transparent'"

// 监听页面滚动距离
	const scrollTop = ref(0);
	onPageScroll((e) => {
		scrollTop.value = e.scrollTop;
	});