You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
9.6 KiB
300 lines
9.6 KiB
|
3 years ago
|
<template>
|
||
|
|
<view :class="custom_class">
|
||
|
|
<view v-if="fixed && !immersive" class="s-nav-bar__placeholder" :style="{ height: nav_height + 'px' }"></view>
|
||
|
|
<view class="s-nav-bar" :class="nav_class" :style="nav_style">
|
||
|
|
<view class="s-nav-bar__background" :style="background_style"></view>
|
||
|
|
<view v-if="statusBar" class="s-nav-bar__status-bar" :style="status_bar_style"></view>
|
||
|
|
<view class="s-nav-bar__container" :style="container_style">
|
||
|
|
<slot>
|
||
|
|
<view class="s-nav-bar__content">
|
||
|
|
<view class="s-nav-bar__left">
|
||
|
|
<slot name="left">
|
||
|
|
<view v-if="back" class="s-nav-bar__back" @click="handleBack">
|
||
|
|
<slot name="back">
|
||
|
|
<s-button
|
||
|
|
type="text"
|
||
|
|
size="custom"
|
||
|
|
:icon="backIcon"
|
||
|
|
:icon-size="$addUnit(backIconSize, 'px')"
|
||
|
|
custom-class="s-nav-bar__back-btn"
|
||
|
|
:custom-style="backStyle"
|
||
|
|
>
|
||
|
|
<view v-if="$isDef(backText)">{{ backText }}</view>
|
||
|
|
</s-button>
|
||
|
|
</slot>
|
||
|
|
</view>
|
||
|
|
</slot>
|
||
|
|
</view>
|
||
|
|
<view class="s-nav-bar__center">
|
||
|
|
<slot name="center">
|
||
|
|
<view class="s-nav-bar__title" :class="title_class" :style="title_style">
|
||
|
|
<slot name="title">
|
||
|
|
<text v-if="$isDef(title)" class="s-nav-bar__title-text s-ellipsis">{{ title }}</text>
|
||
|
|
</slot>
|
||
|
|
</view>
|
||
|
|
</slot>
|
||
|
|
</view>
|
||
|
|
<view class="s-nav-bar__right">
|
||
|
|
<slot name="right" />
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</slot>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import componentMixin from '../../mixins/componentMixin';
|
||
|
|
import sUi from '../../index';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* s-nav-bar 导航栏
|
||
|
|
* @description 自定义导航栏
|
||
|
|
* @property {Number|String} height 导航栏高度(不包括状态栏高度在内,内部自动加上),注意这里的单位是px
|
||
|
|
* @property {String} background 导航栏背景设置(APP和小程序上包括状态栏的颜色)
|
||
|
|
* @property {String|Object} backgroundStyle 背景样式
|
||
|
|
* @property {String} color 导航栏文本颜色
|
||
|
|
* @property {Boolean} border 底部是否显示下边框,默认不显示
|
||
|
|
* @property {String} borderColor 边框颜色
|
||
|
|
* @property {Boolean} fixed 导航栏是否固定在顶部,默认固定
|
||
|
|
* @property {Number|String} immersive 沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效
|
||
|
|
* @property {Number|String} zIndex 固定在顶部时的z-index值
|
||
|
|
* @property {String} title 导航栏标题,如设置为空字符,将会隐藏标题占位区域
|
||
|
|
* @property {String} titleAlign = [left|center] 标题位置
|
||
|
|
* @property {Number|String} titleWidth 导航栏标题的最大宽度,内容超出会以省略号隐藏,单位rpx
|
||
|
|
* @property {String|Object} titleStyle 标题样式
|
||
|
|
* @property {Boolean} back 是否显示导航栏左边返回功能
|
||
|
|
* @property {String} backIcon 左边返回图标的名称,只支持s-ui自带的图标
|
||
|
|
* @property {String} backText 左边返回按钮文字
|
||
|
|
* @property {Number|String} backIconSize 左边返回图标的大小,单位px
|
||
|
|
* @property {String|Object} backStyle 返回样式
|
||
|
|
* @property {String} homePath 当没有页面栈记录时,默认返回首页路径
|
||
|
|
* @property {Function} customBack 自定义返回逻辑方法
|
||
|
|
* @property {Boolean} statusBar 是否预留状态栏高度
|
||
|
|
* @property {String|Object} statusBarStyle 状态栏样式
|
||
|
|
* @property {Boolean} rightMenuButton 是否预留右边胶囊菜单宽度
|
||
|
|
* @property {String} transparent = [none|fade-in|fade-out|always] 导航栏整体(前景、背景)透明设置。支持 always 一直透明 / fade-in 从0-1 / fade-out 从1-0 / none 不透明
|
||
|
|
* @property {Number|String} maxOpacity 导航透明度最大数值
|
||
|
|
* @property {Number} scrollTop 页面滚动scrollTop,用来计算透明度
|
||
|
|
* @property {Number|String} startTop 开始计算透明度的scrollTop
|
||
|
|
* @property {Number|String} scrollRatio 滚动计算透明度阈值
|
||
|
|
* @event {Function} height(height) 高度变动触发,单位 px
|
||
|
|
* @event {Function} opacity(opacity) 透明度改变时触发
|
||
|
|
* @example <s-nav-bar title="标题" />
|
||
|
|
*/
|
||
|
|
export default {
|
||
|
|
name: 'SNavBar',
|
||
|
|
mixins: [componentMixin],
|
||
|
|
props: {
|
||
|
|
height: {
|
||
|
|
type: [Number, String],
|
||
|
|
default: 44,
|
||
|
|
},
|
||
|
|
background: String,
|
||
|
|
backgroundStyle: [String, Object],
|
||
|
|
color: String,
|
||
|
|
padding: [Number, String],
|
||
|
|
border: Boolean,
|
||
|
|
borderColor: String,
|
||
|
|
zIndex: [Number, String],
|
||
|
|
fixed: {
|
||
|
|
type: Boolean,
|
||
|
|
default: true,
|
||
|
|
},
|
||
|
|
immersive: Boolean,
|
||
|
|
title: String,
|
||
|
|
titleAlign: {
|
||
|
|
type: String,
|
||
|
|
default: 'center',
|
||
|
|
},
|
||
|
|
titleWidth: {
|
||
|
|
type: [Number, String],
|
||
|
|
default: 420,
|
||
|
|
},
|
||
|
|
titleStyle: [String, Object],
|
||
|
|
back: Boolean,
|
||
|
|
backIcon: {
|
||
|
|
type: String,
|
||
|
|
default: 'arrow-left',
|
||
|
|
},
|
||
|
|
backText: String,
|
||
|
|
backIconSize: [Number, String],
|
||
|
|
backStyle: [String, Object],
|
||
|
|
statusBar: {
|
||
|
|
type: Boolean,
|
||
|
|
default: true,
|
||
|
|
},
|
||
|
|
statusBarStyle: [String, Object],
|
||
|
|
rightMenuButton: {
|
||
|
|
type: Boolean,
|
||
|
|
default: true,
|
||
|
|
},
|
||
|
|
transparent: {
|
||
|
|
type: String,
|
||
|
|
default: 'none',
|
||
|
|
},
|
||
|
|
minOpacity: {
|
||
|
|
type: [Number, String],
|
||
|
|
default: 0,
|
||
|
|
},
|
||
|
|
maxOpacity: {
|
||
|
|
type: [Number, String],
|
||
|
|
default: 1,
|
||
|
|
},
|
||
|
|
scrollTop: {
|
||
|
|
type: Number,
|
||
|
|
default: 0,
|
||
|
|
},
|
||
|
|
startTop: {
|
||
|
|
type: [Number, String],
|
||
|
|
default: 0,
|
||
|
|
},
|
||
|
|
scrollRatio: {
|
||
|
|
type: [Number, String],
|
||
|
|
default: 0.3,
|
||
|
|
},
|
||
|
|
homePath: String,
|
||
|
|
customBack: Function,
|
||
|
|
},
|
||
|
|
data: () => ({
|
||
|
|
windowWidth: 0,
|
||
|
|
statusBarHeight: 0,
|
||
|
|
rightMenuButtonWidth: 0,
|
||
|
|
opacity: 1,
|
||
|
|
}),
|
||
|
|
computed: {
|
||
|
|
nav_class() {
|
||
|
|
return this.$mergeClass({
|
||
|
|
's-nav-bar--fixed': this.fixed,
|
||
|
|
's-nav-bar--immersive': this.immersive,
|
||
|
|
's-hairline--bottom': this.border,
|
||
|
|
});
|
||
|
|
},
|
||
|
|
nav_style() {
|
||
|
|
return this.$mergeStyle({
|
||
|
|
color: this.color,
|
||
|
|
borderColor: this.borderColor,
|
||
|
|
zIndex: this.fixed ? this.zIndex : '',
|
||
|
|
}, this.custom_style);
|
||
|
|
},
|
||
|
|
c_height() {
|
||
|
|
return parseInt(this.height);
|
||
|
|
},
|
||
|
|
nav_height() {
|
||
|
|
return this.c_height + this.statusBarHeight;
|
||
|
|
},
|
||
|
|
status_bar_style() {
|
||
|
|
return this.$mergeStyle({
|
||
|
|
height: this.statusBarHeight + 'px',
|
||
|
|
}, this.statusBarStyle);
|
||
|
|
},
|
||
|
|
background_style() {
|
||
|
|
return this.$mergeStyle({
|
||
|
|
background: this.background,
|
||
|
|
}, this.backgroundStyle);
|
||
|
|
},
|
||
|
|
container_style() {
|
||
|
|
return this.$mergeStyle({
|
||
|
|
height: this.$addUnit(this.c_height, 'px'),
|
||
|
|
padding: this.$addUnit(this.padding, 'px'),
|
||
|
|
marginRight: this.$addUnit(this.rightMenuButtonWidth, 'px'),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
title_class() {
|
||
|
|
return this.$mergeClass({
|
||
|
|
[`s-nav-bar__title--${this.titleAlign}`]: this.titleAlign,
|
||
|
|
});
|
||
|
|
},
|
||
|
|
title_style() {
|
||
|
|
const titleWidth = this.$toPx(this.titleWidth);
|
||
|
|
const style = {};
|
||
|
|
if (this.titleAlign === 'center') {
|
||
|
|
const space = parseInt((this.windowWidth - titleWidth) / 2);
|
||
|
|
style.left = space + 'px';
|
||
|
|
style.right = (space - this.rightMenuButtonWidth) + 'px';
|
||
|
|
} else {
|
||
|
|
style.width = titleWidth + 'px';
|
||
|
|
}
|
||
|
|
return this.$mergeStyle(style, this.titleStyle);
|
||
|
|
},
|
||
|
|
start_top() {
|
||
|
|
return this.$toPx(this.startTop);
|
||
|
|
},
|
||
|
|
opacity_state() {
|
||
|
|
return [
|
||
|
|
this.scrollTop,
|
||
|
|
this.transparent,
|
||
|
|
this.start_top,
|
||
|
|
this.minOpacity,
|
||
|
|
this.maxOpacity,
|
||
|
|
];
|
||
|
|
},
|
||
|
|
},
|
||
|
|
watch: {
|
||
|
|
nav_height() {
|
||
|
|
this.updateHeight();
|
||
|
|
},
|
||
|
|
opacity_state() {
|
||
|
|
this.refreshOpacity();
|
||
|
|
},
|
||
|
|
},
|
||
|
|
created() {
|
||
|
|
const systemInfo = uni.getSystemInfoSync();
|
||
|
|
this.windowWidth = systemInfo.windowWidth;
|
||
|
|
if (this.statusBar) {
|
||
|
|
this.statusBarHeight = systemInfo.statusBarHeight;
|
||
|
|
}
|
||
|
|
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
|
||
|
|
if (this.rightMenuButton) {
|
||
|
|
this.rightMenuButtonWidth = systemInfo.windowWidth - uni.getMenuButtonBoundingClientRect().left;
|
||
|
|
}
|
||
|
|
// #endif
|
||
|
|
this.updateHeight();
|
||
|
|
this.refreshOpacity();
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
updateHeight() {
|
||
|
|
this.$emit('height', this.nav_height);
|
||
|
|
},
|
||
|
|
handleBack() {
|
||
|
|
const customBack = this.$getPropsFn('customBack');
|
||
|
|
if (customBack) {
|
||
|
|
customBack();
|
||
|
|
} else {
|
||
|
|
if (getCurrentPages().length > 1) {
|
||
|
|
uni.navigateBack();
|
||
|
|
} else {
|
||
|
|
uni.reLaunch({ url: this.homePath || sUi.config.homePath });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
refreshOpacity() {
|
||
|
|
switch (this.transparent) {
|
||
|
|
case 'fade-in':
|
||
|
|
case 'fade-out': {
|
||
|
|
const scrollTop = Math.max(0, this.scrollTop - this.start_top);
|
||
|
|
let opacity = scrollTop / (this.windowWidth * Number(this.scrollRatio));
|
||
|
|
opacity = this.transparent === 'fade-out' ? (1 - opacity) : opacity;
|
||
|
|
if ((this.opacity >= this.maxOpacity && opacity >= this.maxOpacity) || (this.opacity <= this.minOpacity && opacity <= this.minOpacity)) return;
|
||
|
|
this.updateOpacity(opacity);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 'always':
|
||
|
|
this.updateOpacity(0);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
this.updateOpacity(1);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
updateOpacity(opacity) {
|
||
|
|
this.opacity = Math.min(Math.max(opacity, this.minOpacity), this.maxOpacity);
|
||
|
|
this.$emit('opacity', this.opacity);
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" src="./index.scss"></style>
|