201 lines
4.5 KiB
Vue
201 lines
4.5 KiB
Vue
<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>
|
||
<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>
|