Files

366 lines
8.2 KiB
Vue
Raw Normal View History

2023-11-14 17:21:03 +08:00
<!--
@name: index
@author: kahu4
@date: 2023-11-09 10:56
@descriptionindex
@update: 2023-11-09 10:56
-->
<script setup>
import { computed, defineProps, ref, toRefs, unref, watch } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { useRouter } from "@/hooks/useRouter";
import { createAnimation } from "@/utils/utils";
2023-11-15 19:59:37 +08:00
import { useScroll } from "@/hooks/useScroll";
2023-11-14 17:21:03 +08:00
const HEADER_HEIGHT = 40 // header高度
const {goBack} = useRouter()
/**
* @property {String} systemBarAreaBg 系统导航条区域背景颜色
* @property {String} headerAreaBg header区域背景颜色
* @property {String} headerAreaTextColor header区域字体
* @property {Boolean} showReturn 是否展示返回按钮
* @property {String} returnColor 返回按钮的颜色
* @property {Number} returnSize 返回按钮的大小
* @property {Boolean||String} textShadow 字体阴影
* @property {Boolean} bgChangeByScroll 是否随着页面滚动增加header背景包括system-bar-area
* @property {String} bgChangeColor 滚动时候变换的颜色
* @property {Number} scrollTop 当前页面高度
* @property {Boolean} showRight 是否需要右边
* @property {number} leftWidth 左侧返回宽度设置了此属性title将不再居中
*
*/
const props = defineProps({
systemBarAreaBg: {
type: String,
default: () => '#FFFFFF00' // 透明 #FFFFFF00
},
headerAreaBg: {
type: String,
default: () => '#FFFFFF00' // 透明 #FFFFFF00
},
headerAreaTextColor: {
type: String,
default: () => '#000000'
},
showReturn: {
type: Boolean,
default: () => true
},
returnColor: {
type: String,
default: () => '#000'
},
returnSize: {
type: Number,
default: () => 22
},
textShadow: {
type: [Boolean, String],
default: () => false
},
bgChangeByScroll: {
type: Boolean,
default: () => true
},
bgChangeColor: {
type: String,
default: () => '#fff'
},
propUp: {
type: Boolean,
default: () => true
},
showRight: {
type: Boolean,
default: () => true
},
leftWidth: {
type: Number,
default: () => 0
}
})
const {
systemBarAreaBg,
headerAreaBg,
headerAreaTextColor,
showReturn,
returnColor,
returnSize,
textShadow,
bgChangeByScroll,
bgChangeColor,
propUp,
showRight,
leftWidth
} = toRefs(props)
const emits = defineEmits(['getSystemInfo', 'animation'])
// 高度信息 单位px
const heightInfo = ref({
safeAreaInsets: {bottom: 0, top: 0, left: 0, right: 0}, // 安全区信息
statusBarHeight: 0, // 状态栏高度
screenWidth: 0, // 屏幕内部宽度
screenHeight: 0, // 屏幕内部高度
})
/**
* 获取系统信息设置导航条
*/
function getSystemInfo() {
const res = uni.getSystemInfoSync();
const heightObj = unref(heightInfo)
heightObj.safeAreaInsets = res.safeAreaInsets
heightObj.statusBarHeight = res.statusBarHeight
heightObj.screenWidth = res.screenWidth || res.windowWidth
heightObj.screenHeight = res.screenHeight || res.windowHeight
}
// 胶囊信息 单位px
const menuInfo = ref({
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0
})
/**
* 获取小程序右上角信息
*/
function getMenuInfo() {
const menuButtonBoundingClientRect = uni.getMenuButtonBoundingClientRect();
if (menuButtonBoundingClientRect) {
menuInfo.value = {...menuButtonBoundingClientRect}
}
}
// scss全局变量
const scssVarStyle = computed(() => {
return {
2023-11-15 19:59:37 +08:00
'--header-height': `${ HEADER_HEIGHT }px`
2023-11-14 17:21:03 +08:00
}
})
// 系统导航条区域样式
const systemBarAreaStyle = computed(() => {
return {
width: '100%',
2023-11-15 19:59:37 +08:00
height: `${ unref(heightInfo).statusBarHeight * 2 }rpx`,
2023-11-14 17:21:03 +08:00
background: unref(systemBarAreaBg)
}
})
// header区域样式
const headerAreaStyle = computed(() => {
// 计算margin top
// margin-top (导航条高度 - 胶囊高度) / 2 永远确保胶囊在header中央
2023-11-15 19:59:37 +08:00
const marginTop = unref(menuInfo).height > 0 ? `-${((HEADER_HEIGHT - unref(menuInfo).height))/2}px` : 0
2023-11-14 17:21:03 +08:00
return {
width: '100%',
background: unref(headerAreaBg),
color: unref(headerAreaTextColor),
marginTop
}
})
// 文本样式
const textShadowStyle = computed(() => {
return {
textShadow: unref(textShadow) ? unref(textShadow) : 'none',
}
})
const titleStyle = computed(() => {
let width = unref(leftWidth) <= 0 ? '' : `calc( 100vw - var(--side-distance) - var(--side-distance) - ${ leftWidth.value }rpx )`
// #ifdef MP-WEIXIN
width = unref(leftWidth) <= 0 ? '' : `calc( 100vw - var(--side-distance) - var(--side-distance) - ${ leftWidth.value }rpx - ${ menuInfo.value.width }px )`
// #endif
return {
width,
left: unref(leftWidth) <= 0 ? '50%' : `calc( var(--side-distance) + ${ leftWidth.value }rpx )`,
textShadow: unref(textShadow) ? unref(textShadow) : 'none',
transform: unref(leftWidth) <= 0 ? 'translateX(-50%) translateY(-50%)' : 'translateX(0) translateY(-50%)'
}
})
// 滚动后背景样式
const scrollMaskStyle = computed(() => {
return {
background: unref(bgChangeColor),
opacity: unref(scrollTransparency)
}
})
// 总高度
const containerHeight = computed(() => {
2023-11-15 19:59:37 +08:00
return (unref(heightInfo).statusBarHeight + HEADER_HEIGHT)
2023-11-14 17:21:03 +08:00
})
let animation
const scrollTransparency = ref(0) // 滚动透明度
function doCreateAnimation() {
const scrollEnd = heightInfo.value.safeAreaInsets.bottom + HEADER_HEIGHT + 100
animation = createAnimation(0, scrollEnd, 0, 1)
}
2023-11-15 19:59:37 +08:00
const {scrollTop} = useScroll();
2023-11-14 17:21:03 +08:00
watch(scrollTop, () => {
if (!bgChangeByScroll.value) return
if (!animation) doCreateAnimation()
scrollTransparency.value = animation(unref(scrollTop));
emits('animation', scrollTransparency.value)
})
defineExpose({containerHeight, heightInfo})
onLoad(() => {
getSystemInfo()
doCreateAnimation()
// #ifdef MP-WEIXIN
getMenuInfo()
// #endif
})
</script>
<template>
<view
class="container"
:style="scssVarStyle"
>
<view class="header-container">
<!-- 头部系统导航条区域 -->
<view
class="system-bar-area"
:style="systemBarAreaStyle"
>
</view>
<!-- header -->
<view
class="header-row"
:style="headerAreaStyle"
>
<view
class="left"
:style="textShadowStyle"
v-if="showReturn"
>
<slot name="left">
<uv-icon
name="arrow-left"
:color="returnColor"
:size="returnSize"
@click="goBack"
/>
</slot>
</view>
<view
class="title"
:style="titleStyle"
>
<slot>
</slot>
</view>
<view
class="right"
v-if="showRight"
>
<slot name="right">
</slot>
</view>
</view>
<!-- 背景 mask -->
<view
class="bg-mask"
:style="scrollMaskStyle"
></view>
</view>
<!-- 撑起 -->
<view
class="prop-up"
2023-11-15 19:59:37 +08:00
:style="{height:`${containerHeight}px`}"
2023-11-14 17:21:03 +08:00
v-if="propUp"
></view>
</view>
</template>
<style
scoped
lang="scss"
>
.container {
--header-height: 0rpx;
--side-distance: 30rpx;
$side-distance: var(--side-distance); // 侧边距
$header-height: var(--header-height); // header高度
.header-container {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 99;
.system-bar-area {
width: 100%;
z-index: 1;
}
.header-row {
z-index: 1;
width: 100%;
height: $header-height;
position: relative;
.left, .right, .title {
position: absolute;
top: 50%;
height: 100%;
transform: translateY(-50%);
display: flex;
align-items: center;
}
.left {
left: $side-distance;
}
.right {
right: $side-distance;
}
.title {
left: 50%;
transform: translateX(-50%) translateY(-50%);
color: #333;
}
}
.bg-mask {
z-index: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
box-shadow: 0 0 15rpx rgba(162, 162, 162, 0.5);
}
}
.prop-up {
width: 100%;
height: $header-height;
}
}
</style>