Files
mini-yu/uni_modules/tdesign-uniapp/components/navbar/navbar.vue

294 lines
8.1 KiB
Vue
Raw Normal View History

2026-02-10 08:05:03 +08:00
<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>