小程序·安全胶囊距离·容器组件(安全顶部距离升级版)

274 阅读6分钟

《SafeCapsaulContainer》

介绍 SafeCapsaulContainer 是一个用于自动处理小程序顶部状态栏区域的容器组件。

  • 1、它会自动调整 标题栏 内容区域的安全顶部距离,使其与小程序右上角胶囊一致
  • 2、它会自动设置 标题栏 的上下对其位置,使其与 小程序右上角胶囊 保持 上下居中
  • 3、并提供了固定顶部修改背景颜色 设置左右边距 的功能。
  • 4、当使用 fixed 模式时,组件会自动添加一个占位元素,以防止页面内容被遮挡
  • 5、启用了条件编译,自动适配支付宝小程序的奇葩返回键安全距离

功能特性

  • 自动适配顶部状态栏高度
  • 内容区域与胶囊按钮高度对齐
  • 支持固定顶部模式
  • 可自定义背景色
  • 支持左右内边距调整
  • 自动处理内容垂直居中

快速使用示例

代码:

<template>

<SafeCapsaulContainer :fixed="true">
    <!-- 这里的标题栏内容直接使用 wot组件 -->
    <wd-navbar
      title="我的报告"
      left-arrow
      @click-left="handleClickLeft"
      custom-class="custom-navbar"
    ></wd-navbar>

  </SafeCapsaulContainer>
  
  <!-- ......其他内容省略....... -->
</template>

效果展示:

截屏2025-04-27 11.10.07.png

Attributes属性

Props 可选的传入数值

参数说明类型默认值
fixed是否固定在顶部Booleanfalse
bgColor背景颜色String#ffffff(纯白)
showBgColor是否显示背景色Booleantrue
showPlaceholder是否显示占位元素(固定顶部时可能需要)Booleantrue
paddingLeft左侧内边距(默认单位:px,用户只需传入纯数字即可)Number0
paddingRight右侧内边距默认单位:px,用户只需传入纯数字即可))Number0

默认插槽

名称说明
default默认插槽,用于放置内容

注意事项

  1. 当使用 fixed 模式时,组件会自动添加一个占位元素,以防止页面内容被遮挡
  2. 内容区域会自动垂直居中对齐
  3. z-index 默认设置为 998,若有项目有用到遮罩层,可能需要根据实际场景调整
  4. 组件会自动获取胶囊按钮的位置信息来调整布局

样式定制

组件提供了基础的样式定制能力:

  • 通过 bgColor 属性可以修改背景色
  • 通过 paddingLeftpaddingRight 可以调整内容区域的水平间距
  • 内容区域默认垂直居中,使用了 position子绝父相transform 实现

示例场景

1. 固定顶部导航栏

<template>
  <safe-capsaul-container
    :fixed="true"
    :bgColor="#f8f8f8"
    :paddingLeft="16"
    :paddingRight="16"
  >
    <view class="nav-content">
      <text>导航栏标题</text>
    </view>
  </safe-capsaul-container>
</template>

2. 普通内容区域

<template>
  <safe-capsaul-container
    :paddingLeft="12"
    :paddingRight="12"
  >
    <view class="content">
      <text>普通内容</text>
    </view>
  </safe-capsaul-container>
</template>

建议的使用方式

有些项目,一会要fixed,一会不用fixed,建议按下面思路使用

  • 外部起一个container容器,占满全屏且不设padding
  • 内含 《SafeCapsaulContainer》 与 《content的view盒子
  • SafeCapsaulContainer不论是否fixed,都将自动安全的吃掉安全顶部距离
  • content高度被其内容自动撑开,且由content去配置padding内边距挤占子内容,这样不影响安全顶部组件
<template>
  <!-- 页面内容容器(必需) -->
  <view class="container">
    <!-- 安全顶部容器(必需,且仅仅闭合包裹导航栏) -->
    <SafeCapsaulContainer :fixed="true"> 
      <!-- 导航栏(可选) -->
      <wd-navbar
        title="页面标题"
        left-text="返回"
        left-arrow
        @click-left="handleClickLeft"
      />
    </SafeCapsaulContainer>
  
    <!-- 页面内容 -->
    <view class="content">
      <!--在这里编写页面主体内容-->
    </view>
  </view>
</template>
<script setup> 
// 安全顶部容器组件(必需)
import { SafeCapsaulContainer } from "@/components/SafeCapsaulContainer"
// 页面导航(可选,这只是二次封装过的uniapp页面跳转函数)
import { useNavigate, useSwitchTab } from "@/hooks/useNavigate"

// 简单的返回上一级页面,使用uni.navigateBack
const handleClickLeft = () => {
    uni.navigateBack()
}
    
// 复杂的路由跳转,普通子页面使用封装好的: useNavigate
const jumpXxxPage = () => {
  useNavigate("/pages/Xxx/index",{...params})    
}
// 复杂的路由跳转,tabbar页面使用封装好的: useSwitchTab
const jumpXxxTabbar = () => {
  useSwitchTab("/pages/Xxx/index",{...params})    
}
// ......省略其他代码
</script>


<style lang="scss" scoped>
// 页面容器(必需)
.container {
  width: 100%;
  min-height: 100vh;
  box-sizing: border-box; // 若页面涉及padding,统一在padding后续上:box-sizing: border-box;
  
  // 建议的flex布局(可选)
  display: flex;
  flex-direction: column;
  align-items: center;
}
// 页面内容
.content {
  width: 100%;
  padding: 12px; // (可选,建议用padding去挤占子内容)
  box-sizing: border-box;
}
// ....省略其他代码
</style>

更新日志

1.0.0

  • 首次发布
  • 支持基础的顶部安全区域适配
  • 支持固定顶部模式
  • 提供基础的样式定制能力

2.0.0

  • 适配支付宝小程序的左侧返回按键

Bug 反馈与解决

经过实践,当使用wot 的导航栏组件时,会出现组件高度过长而遮挡下方的主体内容

原因:

  • 这是由于前期考虑不足而导致的 填充盒子高度 默认为 顶部距离+胶囊高度

解决办法:

  • 1、可以在content的主体内容区域,增加 padding-top: 8px解决
  • 2、也可以手搓一个导航栏盒子,一般来说这样高度不会太长

组件源代码

可以复制原代码,在components文件创建一个SafeCapsaulContainer.vue组件

例如这样 import { SafeCapsaulContainer } from "@/components/SafeCapsaulContainer"

<!--
description: 自动撑开顶部状态栏安全区域,并且是与胶囊保持一样的高度
description: 条件编译,针对支付宝小程序左侧的返回按键适配,自动计算左侧返回按键的安全距离
-->
<template>
    <view class="safe-top-container" :style="fixedLayoutStyle" :class="{ 'fixed-wrapper': fixed }">
  
      <view class="outer-box" :style="{ width: '100%', height: `${capsualHeight}px`, position: 'relative', zIndex: 998 }">
        <view class="inner-box" :style="diyInnerBoxStyle">
          <slot></slot>
        </view>
      </view>
  
    </view>
    <!-- 配一个不脱离文档流的假盒子,使得页面主要元素不会被遮住 -->
    <view v-if="fixed" :style="{ height: `${capsualTop + capsualHeight}px` }"></view>
  </template>
  <script setup>
  const props = defineProps({
    // 固定在顶部
    fixed: {
      type: Boolean,
      default: false,
    },
    // 背景色
    bgColor: {
      type: String,
      default: '#ffffff',
    },
    // 是否显示背景色
    showBgColor: {
      type: Boolean,
      default: true,
    },
    // 显示占位(如果开启固定到顶部可能会用到
    showPlaceholder: {
      type: Boolean,
      default: true,
    },
    // 左侧内边距
    paddingLeft: {
      type: Number,
      default: 0,
    },
    // 右侧内边距
    paddingRight: {
      type: Number,
      default: 0,
    },
  })
  
  // 旧版·顶部安全距离
  // const safeTop = ref(0)
  
  // 右上角的胶囊信息
  const capsualTop = ref(0)
  
  const capsualWidth = ref(0)
  const capsualHeight = ref(0)
  
  const capsualLeft = ref(0)
  const capsualRight = ref(0)

  // 针对支付宝小程序左侧的返回按键
  const alipayLeftBackBtnDistance = ref(0) // 初始化为0

  
  // 固定布局样式
  const fixedLayoutStyle = computed(() => ({
    width: "100%",
    paddingTop: `${capsualTop.value}px`,  // 来自胶囊的顶部距离
    // paddingLeft: `${props.paddingLeft}px`,  // 传入的左边距 ----- 不要写这里,不然底部的线条会变短
    // paddingRight: `${props.paddingRight}px`,  // 传入的右边距
    backgroundColor: props.showBgColor ? props.bgColor : "transparent",
    zIndex: 998,
  }))
  
  const diyInnerBoxStyle = computed(() => ({
    // height: `${capsualHeight.value}px`,
    
    width: "100%",
    paddingLeft: `${props.paddingLeft + alipayLeftBackBtnDistance.value}px`,  // 传入的左边距+支付宝左侧返回按键的距离(默认0)
    paddingRight: `${props.paddingRight}px`,  // 传入的右边距
    boxSizing: "border-box",
    // 这样子实现上下居中
    position: "absolute",
    top: "50%",
    transform: "translateY(-50%)",
    zIndex: 998,
  }))
  
  onMounted(async () => {
    // const { statusBarHeight } = uni.getSystemInfoSync()  // 旧版·获取状态栏的高度
    // safeTop.value = statusBarHeight  // 旧版·状态栏的高度
  
    const capsaulInfo = uni.getMenuButtonBoundingClientRect()  // 获取胶囊的信息 {width,height,top,left,right 单位:px}
    capsualTop.value = capsaulInfo.top
    capsualWidth.value = capsaulInfo.width
    capsualHeight.value = capsaulInfo.height
    capsualLeft.value = capsaulInfo.left
    capsualRight.value = capsaulInfo.right
    // console.log("-------------------------《SafeTopContainer》信息:-------------------------")
    // console.log("胶囊信息", capsaulInfo)
    // console.log("整体计算样式", fixedLayoutStyle.value)
    // console.log("插槽计算样式", diyInnerBoxStyle.value)
    // #ifdef MP-ALIPAY
      const leftBackBtnInfo = await my.getLeftButtonsBoundingClientRect()
      alipayLeftBackBtnDistance.value = leftBackBtnInfo.backButtonIcon.left + leftBackBtnInfo.backButtonIcon.width
      console.log("支付宝左侧返回按键信息", leftBackBtnInfo)
      console.log("支付宝左侧返回按键距离", alipayLeftBackBtnDistance.value)
    // #endif
      
  })
  </script>
  <style scoped lang="scss">
  .safe-top-container {
    width: 100%;
    z-index: 998;
  }
  .fixed-wrapper {
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    z-index: 998;  // 有些wot组件的遮罩层可能是999,数值太大了的话会导致这个还是白色的
  }
  </style>