Files

422 lines
9.7 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
2023-11-17 20:55:32 +08:00
const HEADER_HEIGHT = 60 // header高度
2023-11-14 17:21:03 +08:00
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({
2024-02-22 18:37:23 +08:00
scrollTop: {
type: Number,
default: () => 0
2023-11-17 20:55:32 +08:00
},
2023-11-14 17:21:03 +08:00
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,
2024-02-22 18:37:23 +08:00
default: () => 16
2023-11-14 17:21:03 +08:00
},
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
2024-02-22 18:37:23 +08:00
},
circleBack: {
type: Boolean,
default: false
2023-11-14 17:21:03 +08:00
}
})
const {
systemBarAreaBg,
headerAreaBg,
headerAreaTextColor,
showReturn,
returnColor,
returnSize,
textShadow,
bgChangeByScroll,
bgChangeColor,
propUp,
showRight,
2023-11-17 20:55:32 +08:00
leftWidth,
2024-02-22 18:37:23 +08:00
scrollTop,
circleBack
2023-11-14 17:21:03 +08:00
} = 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)
2024-02-22 18:37:23 +08:00
console.log(heightObj)
2023-11-14 17:21:03 +08:00
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}
2024-02-22 18:37:23 +08:00
console.log(menuInfo.value)
2023-11-14 17:21:03 +08:00
}
}
// 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%',
2024-02-22 18:37:23 +08:00
height: `${ unref(heightInfo).statusBarHeight }px`,
2023-11-14 17:21:03 +08:00
background: unref(systemBarAreaBg)
}
})
// header区域样式
const headerAreaStyle = computed(() => {
// 计算margin top
// margin-top (导航条高度 - 胶囊高度) / 2 永远确保胶囊在header中央
2024-02-22 18:37:23 +08:00
let marginTop = 0
if (unref(menuInfo).height > 0) {
// buttonMarginTopDiff 此处 胶囊和statusBar是由一个距离的
const buttonMarginTopDiff = unref(menuInfo).top - unref(heightInfo).statusBarHeight
marginTop = `${ (-1 * ((HEADER_HEIGHT - unref(menuInfo).height) / 2)) + buttonMarginTopDiff }px`
}
2023-11-14 17:21:03 +08:00
return {
width: '100%',
background: unref(headerAreaBg),
color: unref(headerAreaTextColor),
marginTop
}
})
2024-02-22 18:37:23 +08:00
const circleBackStyle = computed(() => {
return {
height: menuInfo.value.height > 0 ? menuInfo.value.height + 'px' : '32px'
}
})
2023-11-14 17:21:03 +08:00
// 文本样式
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%)'
}
})
2024-02-22 18:37:23 +08:00
const rightStyle = computed(() => {
if (menuInfo.value.left > 0) {
return {
right: `${ (heightInfo.value.screenWidth - menuInfo.value.right) + menuInfo.value.width + 5 }px`
}
}
return {}
})
2023-11-14 17:21:03 +08:00
// 滚动后背景样式
const scrollMaskStyle = computed(() => {
return {
background: unref(bgChangeColor),
opacity: unref(scrollTransparency)
}
})
// 总高度
const containerHeight = computed(() => {
2024-02-22 18:37:23 +08:00
const marginTop = unref(menuInfo).height > 0 ? `${ ((HEADER_HEIGHT - unref(menuInfo).height)) / 2 }` : 0
2023-11-17 20:55:32 +08:00
return (unref(heightInfo).statusBarHeight + HEADER_HEIGHT - marginTop)
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)
}
watch(scrollTop, () => {
if (!bgChangeByScroll.value) return
if (!animation) doCreateAnimation()
scrollTransparency.value = animation(unref(scrollTop));
emits('animation', scrollTransparency.value)
})
2024-02-22 18:37:23 +08:00
defineExpose({containerHeight, heightInfo, menuInfo})
2023-11-14 17:21:03 +08:00
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">
2024-02-22 18:37:23 +08:00
<view
v-if="circleBack"
class="circle-back"
:style="circleBackStyle">
<uv-icon
name="arrow-left"
:color="returnColor"
:size="returnSize"
@click="goBack"
/>
</view>
2023-11-14 17:21:03 +08:00
<uv-icon
2024-02-22 18:37:23 +08:00
v-else
2023-11-14 17:21:03 +08:00
name="arrow-left"
:color="returnColor"
:size="returnSize"
@click="goBack"
/>
</slot>
</view>
<view
class="title"
:style="titleStyle"
>
<slot>
</slot>
</view>
<view
class="right"
2024-02-22 18:37:23 +08:00
:style="rightStyle"
2023-11-14 17:21:03 +08:00
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;
}
}
2024-02-22 18:37:23 +08:00
.circle-back {
aspect-ratio: 1/1;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.7);
border-radius: 50%;
box-shadow: 0 0 10rpx rgba(225, 225, 225, 0.48);
border: 1rpx solid rgba(225, 225, 225, 0.8);
}
2023-11-14 17:21:03 +08:00
</style>