Files
uniapp-im-shop/components/nav-bar/nav-bar.vue
2026-01-16 17:57:43 +08:00

206 lines
4.6 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { navigateBack } from '@/utils/router'
import { useSlots, computed, ref, onMounted } from 'vue'
const slots = useSlots()
const props = defineProps({
title: {
type: String,
default: ''
},
/** 是否显示左侧返回按钮 */
showBack: {
type: Boolean,
default: true
},
/** 导航栏颜色 */
targetColor: {
type: String,
default: '#ffffff'
},
/** 滚动多少 rpx 后完全变为目标色 */
maxScroll: {
type: Number,
default: 0
},
/** 是否带背景 */
isTopBg: {
type: Boolean,
default: false
},
/** 是否有占位符 */
isPlaceholder: {
type: Boolean,
default: false
},
/** 是否自定义返回事件 */
isCustomBack: {
type: Boolean,
default: false
}
})
const emits = defineEmits(['onBack'])
// 判断是否传入了名为 "back" 的插槽
const hasBackSlot = computed(() => !!slots.back)
/** 全局存储当前滚动位置(供 computed 使用) */
const currentScrollTop = ref(0)
/** 缓存 rpx 转 px 的比例 */
const rpxToPxRatio = ref(1)
/** 将 props.maxScroll (rpx) 转为 px */
const maxScrollPx = computed(() => {
return props.maxScroll * rpxToPxRatio.value
})
/** 当前背景色RGBA 动态计算) */
const bgColor = computed(() => {
const scroll = getCurrentScroll()
const maxPx = maxScrollPx.value
const startFadeAt = maxPx / 3 // 1/3 处开始渐变
let alpha = 0
if (scroll >= maxPx) {
alpha = 1
} else if (scroll > startFadeAt) {
// 在 [startFadeAt, maxPx] 区间内线性插值
alpha = (scroll - startFadeAt) / (maxPx - startFadeAt)
alpha = Math.min(Math.max(alpha, 0), 1) // 安全 clamp
} else {
alpha = 0
}
// 解析颜色
const hex = props.targetColor.replace('#', '')
if (hex.length !== 6) {
return `rgba(0, 122, 255, ${alpha})`
}
const r = parseInt(hex.substring(0, 2), 16)
const g = parseInt(hex.substring(2, 4), 16)
const b = parseInt(hex.substring(4, 6), 16)
return `rgba(${r}, ${g}, ${b}, ${alpha})`
})
const getCurrentScroll = () => {
return currentScrollTop.value
}
/** 暴露方法:供父页面 onPageScroll 调用 */
const updateScroll = scrollTop => {
currentScrollTop.value = scrollTop
}
const onBack = () => {
if (props.isCustomBack) {
emits('onBack')
} else {
navigateBack()
}
}
onMounted(() => {
const sysInfo = uni.getSystemInfoSync()
rpxToPxRatio.value = sysInfo.windowWidth / 750
})
// 向外暴露方法
defineExpose({
updateScroll
})
</script>
<template>
<view>
<view
:style="{
backgroundColor: props.isTopBg ? bgColor : 'transparent'
}"
class="nav-bar"
>
<view class="status_bar">
<!-- 这里是状态栏 -->
</view>
<view class="nav-bar-box">
<!-- 左侧插槽 -->
<view @click="props.showBack && onBack()">
<!-- <image
v-if="props.showBack && !hasBackSlot"
src="/static/images/login/back.png"
mode="heightFix"
class="top_left-icon"
></image> -->
<uni-icons
v-if="props.showBack && !hasBackSlot"
type="back"
size="42rpx"
></uni-icons>
<slot name="back"></slot>
</view>
<!-- 中间标题 -->
<view v-if="props.title" class="nav-bar-title">
<text>{{ props.title }}</text>
</view>
<!-- 右侧插槽 -->
<view>
<slot name="right"></slot>
</view>
</view>
</view>
<!-- 占位符 -->
<view v-if="props.isPlaceholder" class="nav-bar-placeholder"></view>
</view>
</template>
<style lang="scss" scoped>
.nav-bar {
position: fixed;
top: 0;
width: 100%;
z-index: 99;
}
.top_left-icon {
height: 36rpx;
}
.status_bar {
height: var(--status-bar-height);
width: 100%;
}
.nav-bar-box {
padding: 0 36rpx;
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
.nav-bar-title {
position: absolute;
width: calc(100% - 72rpx);
height: 100%;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none;
text {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 32rpx;
color: #333333;
text-align: center;
font-style: normal;
text-transform: none;
}
}
}
.nav-bar-placeholder {
height: calc(var(--status-bar-height) + 44px);
}
</style>