269 lines
9.5 KiB
Vue
269 lines
9.5 KiB
Vue
|
|
<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>
|