260 行
5.9 KiB
Vue
260 行
5.9 KiB
Vue
<template>
|
||
<view class="fc-navbar" :class="[`fc-navbar--${theme}`]">
|
||
<!-- 系统状态栏占位 -->
|
||
<view class="fc-navbar__status" :style="{ height: statusBarHeight + 'px' }"></view>
|
||
<!-- 导航内容条:精确对齐胶囊高度并避让右侧胶囊 -->
|
||
<view
|
||
class="fc-navbar__bar"
|
||
:style="{
|
||
height: barHeight + 'px',
|
||
paddingRight: capsulePadding ? (capsulePadding + 'px') : '24rpx',
|
||
}"
|
||
>
|
||
<!-- 返回按钮(层级置顶,保证可点击,增加点击热区) -->
|
||
<view v-if="showBack" class="fc-navbar__back" @click.stop="onBack">
|
||
<text class="fc-navbar__back-icon">‹</text>
|
||
</view>
|
||
|
||
<!-- 标题区域:默认居中显示 -->
|
||
<view class="fc-navbar__title" :class="{ 'fc-navbar__title--center': center, 'fc-navbar__title--noback': !showBack }">
|
||
<slot>
|
||
<text class="fc-navbar__title-text fc-ellipsis">{{ title }}</text>
|
||
</slot>
|
||
</view>
|
||
|
||
<!-- 右侧功能区(位于胶囊左侧) -->
|
||
<view class="fc-navbar__right">
|
||
<slot name="right"></slot>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { getNavMetrics } from '@/common/utils/navbar.js'
|
||
|
||
export default {
|
||
name: 'FcNavbar',
|
||
props: {
|
||
/** 导航标题文字 */
|
||
title: { type: String, default: '' },
|
||
/** 风格主题:gradient(主色渐变白字) | white(白底黑字) | transparent(透明底黑字) */
|
||
theme: { type: String, default: 'gradient' },
|
||
/** 是否展示左侧返回按钮 */
|
||
showBack: { type: Boolean, default: false },
|
||
/** 标题文字是否居中,默认 true(居中排列) */
|
||
center: { type: Boolean, default: true },
|
||
/** 自定义返回兜底跳转路径(默认返回首页 /pages/index/index) */
|
||
fallbackUrl: { type: String, default: '' },
|
||
},
|
||
data() {
|
||
return {
|
||
statusBarHeight: 0,
|
||
barHeight: 44,
|
||
capsulePadding: 0,
|
||
}
|
||
},
|
||
created() {
|
||
this.initMetrics()
|
||
},
|
||
mounted() {
|
||
this.syncStatusBarStyle()
|
||
},
|
||
methods: {
|
||
/**
|
||
* 初始化获取设备状态栏和胶囊参数
|
||
*/
|
||
initMetrics() {
|
||
const m = getNavMetrics()
|
||
this.statusBarHeight = m.statusBarHeight
|
||
this.barHeight = m.navBarHeight
|
||
this.capsulePadding = m.capsulePadding
|
||
this.syncStatusBarStyle()
|
||
},
|
||
/**
|
||
* 同步原生系统状态栏文字与胶囊颜色
|
||
*/
|
||
syncStatusBarStyle() {
|
||
try {
|
||
if (typeof uni !== 'undefined' && uni.setNavigationBarColor) {
|
||
uni.setNavigationBarColor({
|
||
frontColor: '#000000',
|
||
backgroundColor: this.theme === 'gradient' ? '#eafbf1' : '#ffffff',
|
||
})
|
||
}
|
||
} catch (e) {}
|
||
},
|
||
/**
|
||
* 点击返回上一页
|
||
* 优先 navigateBack;若无上一页历史(如分享直达、热重载、单页直开)或返回失败,则立即执行安全降级,绝不卡死在原地
|
||
*/
|
||
onBack() {
|
||
this.$emit('back')
|
||
let pages = []
|
||
try {
|
||
pages = (typeof getCurrentPages === 'function') ? getCurrentPages() : []
|
||
} catch (e) {
|
||
pages = []
|
||
}
|
||
|
||
// 页面栈深度 > 1 时,执行系统级返回
|
||
if (pages && pages.length > 1) {
|
||
uni.navigateBack({
|
||
delta: 1,
|
||
fail: () => {
|
||
this.fallbackNavigation()
|
||
},
|
||
})
|
||
} else {
|
||
// 页面栈仅有 1 页(分享直达、编译刷新),直接执行降级跳转,避免微信底层静默丢弃导致卡死原地
|
||
this.fallbackNavigation()
|
||
}
|
||
},
|
||
/**
|
||
* 导航失败或无页面栈时的降级逻辑(优先降级至指定 fallbackUrl,否则平滑回到同城首页)
|
||
*/
|
||
fallbackNavigation() {
|
||
const target = this.fallbackUrl || '/pages/index/index'
|
||
const tabPages = ['/pages/index/index', '/pages/category/index', '/pages/post/create', '/pages/message/index', '/pages/mine/index']
|
||
const isTabBar = tabPages.includes(target)
|
||
|
||
if (isTabBar) {
|
||
uni.switchTab({
|
||
url: target,
|
||
fail: () => {
|
||
uni.reLaunch({ url: target })
|
||
},
|
||
})
|
||
} else {
|
||
uni.redirectTo({
|
||
url: target,
|
||
fail: () => {
|
||
uni.reLaunch({
|
||
url: target,
|
||
fail: () => {
|
||
uni.switchTab({ url: '/pages/index/index' })
|
||
},
|
||
})
|
||
},
|
||
})
|
||
}
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.fc-navbar {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 99;
|
||
&--gradient {
|
||
background: $fc-gradient-hero;
|
||
.fc-navbar__title-text {
|
||
color: $fc-text;
|
||
}
|
||
.fc-navbar__back-icon {
|
||
color: $fc-text;
|
||
}
|
||
}
|
||
&--white {
|
||
background: #ffffff;
|
||
border-bottom: 2rpx solid $fc-border;
|
||
.fc-navbar__title-text {
|
||
color: $fc-text;
|
||
}
|
||
.fc-navbar__back-icon {
|
||
color: $fc-text;
|
||
}
|
||
}
|
||
&--transparent {
|
||
background: transparent;
|
||
.fc-navbar__title-text {
|
||
color: #0c2b1c;
|
||
font-weight: 600;
|
||
}
|
||
.fc-navbar__back-icon {
|
||
color: #0c2b1c;
|
||
}
|
||
}
|
||
}
|
||
.fc-navbar__bar {
|
||
display: flex;
|
||
align-items: center;
|
||
padding-left: 12rpx;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
width: 100%;
|
||
}
|
||
.fc-navbar__back {
|
||
width: 96rpx;
|
||
height: 100%;
|
||
min-height: 80rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
cursor: pointer;
|
||
position: relative;
|
||
z-index: 25;
|
||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||
&:active {
|
||
transform: scale(0.92);
|
||
opacity: 0.75;
|
||
}
|
||
}
|
||
.fc-navbar--white .fc-navbar__back:active {
|
||
opacity: 0.7;
|
||
}
|
||
.fc-navbar__back-icon {
|
||
font-size: 54rpx;
|
||
line-height: 1;
|
||
font-weight: 400;
|
||
margin-top: -6rpx;
|
||
pointer-events: none;
|
||
}
|
||
.fc-navbar__title {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
padding-left: 4rpx;
|
||
|
||
&--noback {
|
||
padding-left: 20rpx;
|
||
}
|
||
|
||
&--center {
|
||
position: absolute;
|
||
left: 80rpx;
|
||
right: 80rpx;
|
||
top: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
pointer-events: none;
|
||
padding: 0 80rpx;
|
||
box-sizing: border-box;
|
||
z-index: 1;
|
||
}
|
||
}
|
||
.fc-navbar__title-text {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
letter-spacing: 0.5rpx;
|
||
max-width: 100%;
|
||
text-align: center;
|
||
}
|
||
.fc-navbar__right {
|
||
position: relative;
|
||
z-index: 3;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
min-width: 68rpx;
|
||
justify-content: flex-end;
|
||
}
|
||
</style>
|