CNCEC_APP/uni_modules/uview-pro/components/u-navbar/u-navbar.vue

269 lines
9.5 KiB
Vue
Raw Normal View History

2026-03-25 14:54:15 +08:00
<template>
<view>
<view
class="u-navbar"
:style="navbarStyle"
:class="{ 'u-navbar-fixed': props.isFixed, 'u-border-bottom': props.borderBottom }"
>
<!-- <view class="u-status-bar" :style="{ height: statusBarHeight + 'px' }"></view> -->
<view class="u-navbar-inner" :style="navbarInnerStyle">
<view class="u-back-wrap" v-if="props.isBack" @tap="goBack">
<view class="u-icon-wrap">
<u-icon
:name="props.backIconName"
:color="props.backIconColor"
:size="props.backIconSize"
></u-icon>
</view>
<view class="u-icon-wrap u-back-text u-line-1" v-if="props.backText" :style="props.backTextStyle">
{{ props.backText }}
</view>
</view>
<view class="u-slot-left">
<slot name="left"></slot>
</view>
<view class="u-navbar-content-title" v-if="props.title" :style="titleStyle">
<view
class="u-title u-line-1"
:style="{
color: props.titleColor,
fontSize: props.titleSize + 'rpx',
fontWeight: props.titleBold ? 'bold' : 'normal'
}"
>
{{ props.title }}
</view>
</view>
<view class="u-slot-content">
<slot></slot>
</view>
<view class="u-slot-right">
<slot name="right"></slot>
</view>
</view>
</view>
<!-- 解决fixed定位后导航栏塌陷的问题 -->
<view
class="u-navbar-placeholder"
v-if="props.isFixed && !props.immersive"
:style="{ width: '100%', height: `1rpx` }"
></view>
</view>
</template>
<script lang="ts">
export default {
name: 'u-navbar',
options: {
addGlobalClass: true,
// #ifndef MP-TOUTIAO
virtualHost: true,
// #endif
styleIsolation: 'shared'
}
};
</script>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { $u } from '../..';
import { NavbarProps } from './types';
/**
* navbar 自定义导航栏
* @description 此组件一般用于在特殊情况下需要自定义导航栏的时候用到一般建议使用uniapp自带的导航栏
* @tutorial https://uviewpro.cn/zh/components/navbar.html
* @property {String|Number} height 导航栏高度(不包括状态栏高度在内内部自动加上)注意这里的单位是px默认44
* @property {String} back-icon-color 左边返回图标的颜色默认var(--u-content-color)
* @property {String} back-icon-name 左边返回图标的名称只能为uView自带的图标默认arrow-left
* @property {String|Number} back-icon-size 左边返回图标的大小单位rpx默认30
* @property {String} back-text 返回图标右边的辅助提示文字
* @property {Object} back-text-style 返回图标右边的辅助提示文字的样式对象形式默认{ color: 'var(--u-content-color)' }
* @property {String} title 导航栏标题如设置为空字符将会隐藏标题占位区域
* @property {String|Number} title-width 导航栏标题的最大宽度内容超出会以省略号隐藏单位rpx默认250
* @property {String} title-color 标题的颜色默认var(--u-content-color)
* @property {String|Number} title-size 导航栏标题字体大小单位rpx默认32
* @property {Function} custom-back 自定义返回逻辑方法
* @property {String|Number} z-index 固定在顶部时的z-index值默认980
* @property {Boolean} is-back 是否显示导航栏左边返回图标和辅助文字默认true
* @property {Object} background 导航栏背景设置见官网说明默认{ background: 'var(--u-bg-white)' }
* @property {Boolean} is-fixed 导航栏是否固定在顶部默认true
* @property {Boolean} immersive 沉浸式允许fixed定位后导航栏塌陷仅fixed定位下生效默认false
* @property {Boolean} border-bottom 导航栏底部是否显示下边框如定义了较深的背景颜色可取消此值默认true
* @example <u-navbar back-text="返回" title="剑未配妥,出门已是江湖"></u-navbar>
*/
const props = defineProps(NavbarProps);
// 获取系统状态栏的高度
const systemInfo = uni.getSystemInfoSync();
let menuButtonInfo: any = {};
// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API尚未兼容)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
// 状态栏高度
const statusBarHeight = ref(0);
// #ifdef APP-HARMONY || MP-WEIXIN
const windowInfo = uni.getWindowInfo();
statusBarHeight.value = windowInfo.statusBarHeight || 0;
// #endif
// #ifndef APP-HARMONY || MP-WEIXIN
statusBarHeight.value = systemInfo.statusBarHeight || 0;
// #endif
// 转换字符数值为真正的数值
const navbarHeight = computed(() => {
// #ifdef APP || H5
return props.height ? props.height : 44;
// #endif
// #ifdef MP
// 小程序特别处理,让导航栏高度 = 胶囊高度 + 两倍胶囊顶部与状态栏底部的距离之差(相当于同时获得了导航栏底部与胶囊底部的距离)
// 此方法有缺陷,暂不用(会导致少了几个px),采用直接固定值的方式
// return menuButtonInfo.height + (menuButtonInfo.top - this.statusBarHeight) * 2;//导航高度
let height = systemInfo.platform == 'ios' ? 44 : 48;
return props.height ? props.height : height;
// #endif
});
// 导航栏高度加上状态栏高度
const navbarPlaceholderHeight = computed(() => {
return Number(navbarHeight.value) + Number(statusBarHeight.value);
});
// 导航栏内部盒子的样式
const navbarInnerStyle = computed(() => {
let style: Record<string, any> = {};
// 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离
style.height = String(navbarHeight.value) + 'px';
// 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度
// #ifdef MP
let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
style.marginRight = rightButtonWidth + 'px';
// #endif
return style;
});
// 整个导航栏的样式
const navbarStyle = computed(() => {
let style: Record<string, any> = {};
style.zIndex = props.zIndex ? props.zIndex : $u.zIndex.navbar;
// 合并用户传递的背景色对象
Object.assign(style, props.background);
console.log(style)
return style;
});
// 导航中间的标题的样式
const titleStyle = computed(() => {
let style: Record<string, any> = {};
// #ifndef MP
style.left = (systemInfo.windowWidth - uni.upx2px(Number(props.titleWidth))) / 2 + 'px';
style.right = (systemInfo.windowWidth - uni.upx2px(Number(props.titleWidth))) / 2 + 'px';
// #endif
// #ifdef MP
// 此处是为了让标题显示区域即使在小程序有右侧胶囊的情况下也能处于屏幕的中间,是通过绝对定位实现的
let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
style.left = (systemInfo.windowWidth - uni.upx2px(Number(props.titleWidth))) / 2 + 'px';
style.right =
rightButtonWidth -
(systemInfo.windowWidth - uni.upx2px(Number(props.titleWidth))) / 2 +
rightButtonWidth +
'px';
// #endif
style.width = uni.upx2px(Number(props.titleWidth)) + 'px';
return style;
});
/**
* 返回按钮点击事件
* 如果自定义了点击返回按钮的函数则执行否则执行返回逻辑
*/
function goBack() {
if (typeof props.customBack === 'function') {
// 在微信,支付宝等环境(H5正常)会导致父组件定义的customBack()函数体中的this变成子组件的this
// 通过bind()方法绑定父组件的this让this.customBack()的this为父组件的上下文
// props.customBack.bind($u.$parent.call(getCurrentInstance()?.proxy))();
props.customBack();
} else {
uni.navigateBack();
}
}
</script>
<style scoped lang="scss">
@import '../../libs/css/style.components.scss';
.u-navbar {
width: 100%;
}
.u-navbar-fixed {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 991;
}
.u-status-bar {
width: 100%;
}
.u-navbar-inner {
@include vue-flex;
justify-content: space-between;
position: relative;
align-items: center;
}
.u-back-wrap {
@include vue-flex;
align-items: center;
flex: 1;
flex-grow: 0;
padding: 14rpx 14rpx 14rpx 24rpx;
}
.u-back-text {
padding-left: 4rpx;
font-size: 30rpx;
}
.u-navbar-content-title {
@include vue-flex;
align-items: center;
justify-content: center;
flex: 1;
position: absolute;
left: 0;
right: 0;
height: 60rpx;
text-align: center;
flex-shrink: 0;
}
.u-navbar-centent-slot {
flex: 1;
}
.u-title {
line-height: 60rpx;
font-size: 32rpx;
flex: 1;
}
.u-navbar-right {
flex: 1;
@include vue-flex;
align-items: center;
justify-content: flex-end;
}
.u-slot-content {
flex: 1;
@include vue-flex;
align-items: center;
}
</style>