294 lines
8.1 KiB
Vue
294 lines
8.1 KiB
Vue
<template>
|
||
<view
|
||
:class="tools.cls(classPrefix, [['fixed', fixed]]) + ' ' + visibleClass + ' ' + tClass"
|
||
:style="tools._style([boxStyle, customStyle])"
|
||
>
|
||
<view
|
||
v-if="fixed && placeholder"
|
||
:class="classPrefix + '__placeholder ' + tClassPlaceholder"
|
||
/>
|
||
<view :class="classPrefix + '__content ' + tClassContent">
|
||
<view :class="classPrefix + '__left ' + (hideLeft ? classPrefix + '__left--hide' : '') + ' ' + tClassLeft">
|
||
<view
|
||
v-if="leftArrow"
|
||
:class="classPrefix + '__btn'"
|
||
aria-role="button"
|
||
aria-label="返回"
|
||
@click="goBack"
|
||
>
|
||
<t-icon
|
||
name="chevron-left"
|
||
:custom-style="leftArrowCustomStyle"
|
||
:t-class="classPrefix + '__left-arrow'"
|
||
/>
|
||
</view>
|
||
<slot name="left" />
|
||
<view :class="classPrefix + '__capsule ' + tClassCapsule">
|
||
<slot name="capsule" />
|
||
</view>
|
||
</view>
|
||
<view :class="classPrefix + '__center ' + (hideCenter ? classPrefix + '__center--hide' : '') + ' ' + tClassCenter">
|
||
<slot name="title" />
|
||
<text
|
||
v-if="title"
|
||
:class="classPrefix + '__center-title ' + tClassTitle"
|
||
>
|
||
{{ showTitle }}
|
||
</text>
|
||
</view>
|
||
|
||
<view
|
||
:class="classPrefix + '__right'"
|
||
@click="onClickRight"
|
||
>
|
||
<slot name="right" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<script>
|
||
import TIcon from '../icon/icon';
|
||
import { uniComponent } from '../common/src/index';
|
||
import { getRect, systemInfo } from '../common/utils';
|
||
import { prefix } from '../common/config';
|
||
import props from './props';
|
||
import tools from '../common/utils.wxs';
|
||
|
||
|
||
const name = `${prefix}-navbar`;
|
||
|
||
const BASE_MENU_RECT = {
|
||
width: 87,
|
||
height: 32,
|
||
top: 24,
|
||
right: systemInfo.windowWidth - 10,
|
||
};
|
||
|
||
|
||
export default uniComponent({
|
||
name,
|
||
options: {
|
||
styleIsolation: 'shared',
|
||
},
|
||
externalClasses: [
|
||
`${prefix}-class`,
|
||
`${prefix}-class-placeholder`,
|
||
`${prefix}-class-content`,
|
||
`${prefix}-class-title`,
|
||
`${prefix}-class-left`,
|
||
`${prefix}-class-center`,
|
||
`${prefix}-class-left-icon`,
|
||
`${prefix}-class-home-icon`,
|
||
`${prefix}-class-capsule`,
|
||
`${prefix}-class-nav-btn`,
|
||
],
|
||
components: {
|
||
TIcon,
|
||
},
|
||
props: {
|
||
...props,
|
||
},
|
||
emits: [
|
||
'fail',
|
||
'complete',
|
||
'success',
|
||
'go-back',
|
||
'right-click',
|
||
],
|
||
data() {
|
||
return {
|
||
timer: null,
|
||
prefix,
|
||
classPrefix: name,
|
||
boxStyle: '',
|
||
showTitle: '',
|
||
hideLeft: false,
|
||
hideCenter: false,
|
||
_menuRect: null,
|
||
_leftRect: null,
|
||
_boxStyle: {},
|
||
tools,
|
||
|
||
visibleClass: '',
|
||
|
||
};
|
||
},
|
||
computed: {
|
||
leftArrowCustomStyle() {
|
||
return 'font-size: var(--td-navbar-left-arrow-size, 24px);';
|
||
},
|
||
},
|
||
watch: {
|
||
visible(visible) {
|
||
const { animation } = this;
|
||
const visibleClass = `${name}${visible ? '--visible' : '--hide'}`;
|
||
this.visibleClass = `${visibleClass}${animation ? '-animation' : ''}`;
|
||
|
||
if (this.timer) {
|
||
clearTimeout(this.timer);
|
||
}
|
||
if (animation) {
|
||
this.timer = setTimeout(() => {
|
||
this.visibleClass = visibleClass;
|
||
}, 300);
|
||
}
|
||
},
|
||
|
||
title: 'onWatchTitle',
|
||
titleMaxLength: 'onWatchTitle',
|
||
},
|
||
|
||
mounted() {
|
||
this.onWatchTitle();
|
||
this.initStyle();
|
||
this.getLeftRect();
|
||
this.onMenuButtonBoundingClientRectWeightChange();
|
||
},
|
||
|
||
beforeUnMount() {
|
||
this.offMenuButtonBoundingClientRectWeightChange();
|
||
},
|
||
methods: {
|
||
initStyle() {
|
||
this.getMenuRect();
|
||
|
||
const { _menuRect, _leftRect } = this;
|
||
|
||
if (!_menuRect || !_leftRect || !systemInfo) return;
|
||
|
||
const _boxStyle = {
|
||
'--td-navbar-padding-top': `${systemInfo.statusBarHeight}px`,
|
||
'--td-navbar-right': `${systemInfo.windowWidth - _menuRect.left}px`, // 导航栏右侧小程序胶囊按钮宽度
|
||
'--td-navbar-left-max-width': `${_menuRect.left}px`, // 左侧内容最大宽度
|
||
'--td-navbar-capsule-height': `${_menuRect.height}px`, // 胶囊高度
|
||
'--td-navbar-capsule-width': `${_menuRect.width}px`, // 胶囊宽度
|
||
'--td-navbar-height': `${(_menuRect.top - systemInfo.statusBarHeight) * 2 + _menuRect.height}px`,
|
||
};
|
||
// #ifdef H5 || APP-PLUS
|
||
delete _boxStyle['--td-navbar-height'];
|
||
// #endif
|
||
|
||
this.calcCenterStyle(_leftRect, _menuRect, _boxStyle);
|
||
},
|
||
onWatchTitle() {
|
||
const { title } = this;
|
||
const titleMaxLength = this.titleMaxLength || Number.MAX_SAFE_INTEGER;
|
||
let temp = title.slice(0, titleMaxLength);
|
||
if (titleMaxLength < title.length) temp += '...';
|
||
|
||
this.showTitle = temp;
|
||
},
|
||
|
||
calcCenterStyle(
|
||
leftRect,
|
||
menuRect,
|
||
defaultStyle,
|
||
) {
|
||
const maxSpacing = Math.max(leftRect.right, systemInfo.windowWidth - menuRect.left);
|
||
const _boxStyle = {
|
||
...defaultStyle,
|
||
'z-index': this.zIndex,
|
||
'--td-navbar-center-left': `${maxSpacing}px`, // 标题左侧距离
|
||
'--td-navbar-center-width': `${Math.max(menuRect.left - maxSpacing, 0)}px`, // 标题宽度
|
||
};
|
||
|
||
const boxStyle = Object.entries(_boxStyle)
|
||
.map(([k, v]) => `${k}: ${v}`)
|
||
.join('; ');
|
||
|
||
this.boxStyle = boxStyle;
|
||
this._boxStyle = _boxStyle;
|
||
},
|
||
|
||
getLeftRect() {
|
||
getRect(this, `.${name}__left`).then((res) => {
|
||
if (res.right > this._leftRect.right) {
|
||
this.calcCenterStyle(res, this._menuRect, this._boxStyle);
|
||
}
|
||
});
|
||
},
|
||
|
||
getMenuRect() {
|
||
// 场景值为1177(视频号直播间)和1175 (视频号profile页)时,小程序禁用了 uni.getMenuButtonBoundingClientRect
|
||
let rect = {
|
||
...BASE_MENU_RECT,
|
||
bottom: BASE_MENU_RECT.top + BASE_MENU_RECT.height,
|
||
left: BASE_MENU_RECT.right - BASE_MENU_RECT.width,
|
||
};
|
||
if (uni.getMenuButtonBoundingClientRect
|
||
&& typeof uni.getMenuButtonBoundingClientRect === 'function'
|
||
&& typeof uni.getMenuButtonBoundingClientRect() === 'object'
|
||
) {
|
||
rect = uni.getMenuButtonBoundingClientRect() || {};
|
||
}
|
||
|
||
this._menuRect = rect;
|
||
this._leftRect = {
|
||
right: systemInfo.windowWidth - rect.left,
|
||
};
|
||
},
|
||
|
||
onMenuButtonBoundingClientRectWeightChange() {
|
||
if (uni.onMenuButtonBoundingClientRectWeightChange) {
|
||
this.onMenuButtonBoundingClientRectWeightChangeCallback = res => this.queryElements(res);
|
||
|
||
uni.onMenuButtonBoundingClientRectWeightChange(this.onMenuButtonBoundingClientRectWeightChangeCallback);
|
||
}
|
||
},
|
||
|
||
offMenuButtonBoundingClientRectWeightChange() {
|
||
if (this.onMenuButtonBoundingClientRectWeightChangeCallback) {
|
||
uni.offMenuButtonBoundingClientRectWeightChange(this.onMenuButtonBoundingClientRectWeightChangeCallback);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 比较胶囊条和navbar内容,决定是否隐藏
|
||
* @param capsuleRect API返回值,胶囊条的位置信息
|
||
*/
|
||
queryElements(capsuleRect) {
|
||
Promise.all([
|
||
getRect(this, `.${this.classPrefix}__left`),
|
||
getRect(this, `.${this.classPrefix}__center`),
|
||
]).then(([leftRect, centerRect]) => {
|
||
// 部分安卓机型中(目前仅在Magic6/7中复现),仍存在精度问题,暂使用 Math.round() 取整规避
|
||
const leftRight = Math.round(leftRect.right);
|
||
const centerRight = Math.round(centerRect.right);
|
||
const capsuleLeft = capsuleRect.left;
|
||
|
||
this.hideLeft = leftRight > capsuleLeft;
|
||
this.hideCenter = leftRight > capsuleLeft ? true : centerRight > capsuleLeft;
|
||
});
|
||
},
|
||
|
||
goBack() {
|
||
const { delta } = this;
|
||
// eslint-disable-next-line
|
||
const that = this;
|
||
this.$emit('go-back');
|
||
if (delta > 0) {
|
||
uni.navigateBack({
|
||
delta,
|
||
fail(e) {
|
||
that.$emit('fail', e);
|
||
},
|
||
complete(e) {
|
||
that.$emit('complete', e);
|
||
},
|
||
success(e) {
|
||
that.$emit('success', e);
|
||
},
|
||
});
|
||
}
|
||
},
|
||
|
||
onClickRight() {
|
||
this.$emit('right-click');
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
<style scoped>
|
||
@import './navbar.css';
|
||
</style>
|