248 lines
6.0 KiB
Vue
248 lines
6.0 KiB
Vue
<template>
|
||
<!--
|
||
自绘导航栏:把原生导航栏左侧的 "<" 换成 "‹ 返回" 文字
|
||
|
||
背景:微信小程序原生导航栏的返回按钮是客户端系统控件,pages.json 的任何配置、
|
||
官方 <navigation-bar> 组件(只支持 title / front-color / background-color / loading)
|
||
都无法修改它的内容。要显示"返回"二字,只能关闭原生栏后自绘。
|
||
|
||
使用前提:
|
||
1. 该页面在 pages.json 中开启 "navigationStyle": "custom"(组件只负责渲染,不自动改配置)
|
||
2. 该页面不能是 web-view 页面 —— 官方明确 "navigationStyle: custom 对 web-view 组件无效",
|
||
且 web-view 会铺满整页覆盖其他组件,自绘的导航栏不会显示
|
||
|
||
布局:组件是普通流式节点(非 fixed),放在页面顶部即可把内容自然下推。
|
||
-->
|
||
<view
|
||
v-if="visible"
|
||
class="ns-mp-navbar"
|
||
:class="{ 'is-fixed': fixed }"
|
||
:style="{ background: bgColor, paddingTop: statusBarHeight + 'px' }"
|
||
>
|
||
<view class="ns-mp-navbar__inner" :style="{ height: navBarHeight + 'px' }">
|
||
<view class="ns-mp-navbar__side">
|
||
<view
|
||
v-if="showBack && canBack"
|
||
class="ns-mp-navbar__back"
|
||
hover-class="ns-mp-navbar__back--hover"
|
||
hover-stay-time="80"
|
||
@tap="onBack"
|
||
>
|
||
<text class="iconfont icon-back_light ns-mp-navbar__arrow" :style="{ color }"></text>
|
||
<text class="ns-mp-navbar__text" :style="{ color }">{{ backText }}</text>
|
||
</view>
|
||
<slot name="left"></slot>
|
||
</view>
|
||
|
||
<view class="ns-mp-navbar__title" :style="{ color }">{{ title }}</view>
|
||
|
||
<view class="ns-mp-navbar__side ns-mp-navbar__side--right">
|
||
<slot name="right"></slot>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 状态栏 / 胶囊信息:用于算出与原生导航栏一致的布局高度
|
||
let systemInfo = {};
|
||
let menuButtonInfo = { top: 0, height: 32, right: 0, width: 0 };
|
||
|
||
try {
|
||
systemInfo = uni.getSystemInfoSync() || {};
|
||
} catch (e) {
|
||
systemInfo = {};
|
||
}
|
||
|
||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-HARMONY
|
||
try {
|
||
menuButtonInfo = uni.getMenuButtonBoundingClientRect() || menuButtonInfo;
|
||
} catch (e) {}
|
||
// #endif
|
||
|
||
export default {
|
||
name: 'ns-mp-navbar',
|
||
props: {
|
||
// 中间标题
|
||
title: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 导航栏背景色
|
||
bgColor: {
|
||
type: String,
|
||
default: '#ffffff'
|
||
},
|
||
// 文字 / 图标颜色
|
||
color: {
|
||
type: String,
|
||
default: '#000000'
|
||
},
|
||
// 返回按钮文字(原生导航栏只能显示 "<",这里可自定义)
|
||
backText: {
|
||
type: String,
|
||
default: '返回'
|
||
},
|
||
// 是否显示返回按钮
|
||
showBack: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 是否悬浮在页面之上(沉浸式页面用;false 时占文档流,会把内容往下推)
|
||
fixed: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 无页面栈时(如分享直接进入)跳转的首页地址
|
||
homePath: {
|
||
type: String,
|
||
default: '/pages/index/index'
|
||
},
|
||
// 自定义返回逻辑
|
||
customBack: {
|
||
type: Function,
|
||
default: null
|
||
},
|
||
// H5 端是否渲染(H5 由 uni-app 自带头像栏,默认不渲染,避免双导航栏)
|
||
showOnH5: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
statusBarHeight: systemInfo.statusBarHeight || 20,
|
||
navBarHeight: 44,
|
||
visible: true,
|
||
// 页面栈里是否还有上级页面(无上级页面时原生导航栏也不显示返回键)
|
||
canBack: true
|
||
};
|
||
},
|
||
computed: {
|
||
// 导航栏总高度(状态栏 + 内容区),页面如需自行占位可取该值
|
||
navbarHeight() {
|
||
return this.statusBarHeight + this.navBarHeight;
|
||
}
|
||
},
|
||
created() {
|
||
// 导航栏内容区高度:MP 端按胶囊位置推算,与原生导航栏保持一致;其余端用 44
|
||
// #ifdef MP
|
||
const statusBarHeight = this.statusBarHeight;
|
||
const h = (menuButtonInfo.top - statusBarHeight) * 2 + menuButtonInfo.height;
|
||
// 兜底:胶囊信息异常时退回 44
|
||
this.navBarHeight = h > 0 ? h : 44;
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
this.visible = this.showOnH5;
|
||
// #endif
|
||
},
|
||
mounted() {
|
||
// 页面栈只有一页(分享/扫码直达)时,原生导航栏也无返回键,这里保持一致
|
||
const pages = (typeof getCurrentPages === 'function' && getCurrentPages()) || [];
|
||
this.canBack = pages.length > 1;
|
||
// 把实际总高度抛给页面,便于页面修正内容区高度,避免多出一屏空白
|
||
this.$emit('ready', this.statusBarHeight + this.navBarHeight);
|
||
},
|
||
methods: {
|
||
onBack() {
|
||
if (typeof this.customBack === 'function') {
|
||
this.customBack();
|
||
return;
|
||
}
|
||
const pages = (typeof getCurrentPages === 'function' && getCurrentPages()) || [];
|
||
if (pages.length > 1) {
|
||
uni.navigateBack();
|
||
return;
|
||
}
|
||
// 顶层页面(分享 / 扫码直接进入):回首页
|
||
uni.switchTab({
|
||
url: this.homePath,
|
||
fail: () => {
|
||
uni.reLaunch({ url: this.homePath });
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.ns-mp-navbar {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.ns-mp-navbar.is-fixed {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 990;
|
||
}
|
||
|
||
.ns-mp-navbar__inner {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.ns-mp-navbar__side {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
height: 100%;
|
||
min-width: 160rpx;
|
||
padding-left: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.ns-mp-navbar__side--right {
|
||
justify-content: flex-end;
|
||
padding-left: 0;
|
||
padding-right: 20rpx;
|
||
}
|
||
|
||
.ns-mp-navbar__back {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 10rpx 20rpx 10rpx 0;
|
||
}
|
||
|
||
.ns-mp-navbar__back--hover {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.ns-mp-navbar__arrow {
|
||
font-size: 34rpx;
|
||
line-height: 1;
|
||
}
|
||
|
||
.ns-mp-navbar__text {
|
||
margin-left: 2rpx;
|
||
font-size: 30rpx;
|
||
line-height: 1;
|
||
}
|
||
|
||
.ns-mp-navbar__title {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 160rpx;
|
||
font-size: 32rpx;
|
||
line-height: 1.2;
|
||
text-align: center;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
</style>
|