【兼容多端】UNIAPP popper气泡弹层vue3+typescript unibest

news2024/11/25 10:27:25

最近要实习一个泡泡弹层。看了下市场的代码,要么写的不怎么好,要么过于复杂。于是拿个轮子自己加工。200行代码撸了个弹出层组件。兼容H5和APP和小程序。

功能:

  1)只支持上下左右4个方向的弹层不支持侧边靠齐

  2)不对屏幕边界适配

  3)支持弹层外边点击自动隐藏

  4)支持3种内容模式:

    1. 弹出提示文本

    2. slot内容占位

    3. 支持菜单模式


BWT:弹层外点击自动隐藏基于unibest框架的页面模板技术,这里就不放代码了,自己想想怎么弄😏 。提示:使用事件总线模式,放出的代码也提示了部分用法。

效果,H5下:

b110396642724f8d928b414181dc3c64.png

APP下:

e4c5dc8325844c3384c646f30c1d162f.png

小程序下:

8ead18c3c577452f980695132297d8f3.png

组件代码:
 

<!--
  自定义弹出层/菜单组件
  1)只支持上下左右4个方向的弹层不支持侧边靠齐
  2)不对屏幕边界适配
  3)支持弹层外边点击自动隐藏
  4)支持3种内容模式:
    1. 文本为内容
    2. slot内容占位
    3. 菜单模式
  @Author Jim 24/10/08
 -->
<template>
  <view>
    <view class="cc_popper" @click.stop="handleClick">
      <slot></slot>
      <view
        class="cc_popper_layer border-2rpx border-solid"
        @click.stop="() => {}"
        :style="[
          data.layerStyle,
          {
            visibility: data.isShow ? 'visible' : 'hidden',
            opacity: data.isShow ? 1 : 0,
            color: props.textColor,
            backgroundColor: props.bgColor,
            borderColor: 'var(--cc-box-border)'
          }
        ]"
      >
        <view class="px-20rpx py-10rpx" v-if="content.length > 0 || $slots.content">
          <!-- 内容模式 -->
          <slot name="content">{{ content }}</slot>
        </view>
        <view v-else class="py-5rpx px-10rpx">
          <template v-for="(conf, index) in props.menu" :key="index">
            <view v-if="index > 0" class="bg-box-border opacity-70 h-2rpx w-full" />
            <view
              class="px-20rpx py-10rpx menu-item my-5rpx"
              @click="
                () => {
                  conf.callback()
                  data.isShow = false
                }
              "
            >
              {{ conf.title }}
            </view>
          </template>
        </view>
        <view
          :class="['w-0', 'h-0', 'z-9', 'absolute', 'popper-arrow-on-' + props.direction]"
          :style="[data.arrowStyle]"
        />
      </view>
    </view>
  </view>
</template>
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import * as utils from '@/utils'
let instance

const { screenWidth } = uni.getSystemInfoSync()

const pixelUnit = screenWidth / 750 // rpx->px比例基数

export interface MenuConf {
  icon?: string // 指示图标
  title: string // 菜单文本
  callback: () => void // 点击事件
}

const props = withDefaults(
  defineProps<{
    textColor?: string // 指定内部文本颜色
    bgColor?: string
    borderColor?: string
    content?: string // 可以指定文本content,或者指定 slot content来显示弹窗内容
    menu?: Array<MenuConf> // 下拉菜单模式
    direction?: 'top' | 'bottom' | 'left' | 'right' // 弹层位置
    alwaysShow: boolean
  }>(),
  {
    textColor: 'var(--cc-txt)',
    bgColor: 'var(--cc-box-fill)', // 默认弹框色
    borderColor: 'var(--cc-box-border)', // 默认弹框边框色
    content: '',
    menu: () => [],
    direction: 'top',
    alwaysShow: false
  }
)

const data = reactive<{
  isShow: boolean
  layerStyle: CSSProperties // CSS定义一层够了
  arrowStyle: CSSProperties
}>({
  isShow: false,
  layerStyle: {},
  arrowStyle: {}
})

onMounted(() => {
  instance = getCurrentInstance()
  if (props.alwaysShow) {
    nextTick(() => handleClick())
  }
})

onUnmounted(() => {
  if (!props.alwaysShow) {
    utils.off(utils.Global.CC_GLOBAL_CLICK, hideLayer) // 移除全局点击监听
  }
})

const hideLayer = (event: MouseEvent) => {
  data.isShow = false
  utils.off(utils.Global.CC_GLOBAL_CLICK, hideLayer)
}

const handleClick = async () => {
  if (data.isShow) {
    if (props.alwaysShow) {
      return
    }
    utils.off(utils.Global.CC_GLOBAL_CLICK, hideLayer)
    return (data.isShow = false)
  }
  const rects: UniApp.NodeInfo[] = await utils.getRectAll('.cc_popper,.cc_popper_layer', instance)
  const srcRect: UniApp.NodeInfo = rects[0]
  const layerRect: UniApp.NodeInfo = rects[1]
  data.arrowStyle['border' + props.direction.charAt(0).toUpperCase() + props.direction.slice(1)] =
    '10rpx solid var(--cc-box-border)'
  switch (props.direction) {
    case 'top': {
      data.layerStyle.left = `${(srcRect.width - layerRect.width) / 2}px`
      data.layerStyle.bottom = `${srcRect.height + 16 * pixelUnit}px`
      data.arrowStyle.left = `${layerRect.width / 2 - 12 * pixelUnit}px`
      console.log(data.arrowStyle.left)
      break
    }
    case 'bottom': {
      data.layerStyle.left = `${(srcRect.width - layerRect.width) / 2}px`
      data.layerStyle.top = `${srcRect.height + 16 * pixelUnit}px`
      data.arrowStyle.left = `${layerRect.width / 2 - 12 * pixelUnit}px`
      break
    }
    case 'left': {
      data.layerStyle.right = `${srcRect.width + 16 * pixelUnit}px`
      data.layerStyle.top = `${(srcRect.height - layerRect.height) / 2}px`
      data.arrowStyle.top = `${layerRect.height / 2 - 12 * pixelUnit}px`
      break
    }
    case 'right': {
      data.layerStyle.left = `${srcRect.width + 16 * pixelUnit}px`
      data.layerStyle.top = `${(srcRect.height - layerRect.height) / 2}px`
      data.arrowStyle.top = `${layerRect.height / 2 - 12 * pixelUnit}px`
      break
    }
  }

  data.isShow = true
  if (!props.alwaysShow) {
    utils.on(utils.Global.CC_GLOBAL_CLICK, hideLayer)
  }
}
</script>
<style lang="scss" scoped>
$arrow-size: 12rpx;
$arrow-offset: -12rpx;

.cc_popper {
  position: relative;
  display: inline-block;
}

.cc_popper_layer {
  position: absolute;
  display: inline-block;
  white-space: nowrap;
  border-radius: 10rpx;
  transition: opacity 0.3s ease-in-out;
}

.popper-arrow-on-top {
  bottom: $arrow-offset;
  border-right: $arrow-size solid transparent;
  border-left: $arrow-size solid transparent;
}

.popper-arrow-on-right {
  left: $arrow-offset;
  border-top: $arrow-size solid transparent;
  border-bottom: $arrow-size solid transparent;
}

.popper-arrow-on-left {
  right: $arrow-offset;
  border-top: $arrow-size solid transparent;
  border-bottom: $arrow-size solid transparent;
}

.popper-arrow-on-bottom {
  top: $arrow-offset;
  border-right: $arrow-size solid transparent;
  border-left: $arrow-size solid transparent;
}

.menu-item {
  &:active {
    background-color: #88888840;
  }
}
</style>

测试页面:

<template>
  <view class="text-txt w-full h-full">
    <view>消息</view>
    <view class="x-items-between px-200rpx pt-100rpx">
      <cc-popper direction="left" content="说啥好呢" alwaysShow>
        <view class="w-100rpx"><u-button text="左边" /></view>
      </cc-popper>
      <view class="w-100rpx">
        <cc-popper direction="top" content="向上看" alwaysShow>
          <view class="w-100rpx"><u-button text="上面" /></view>
        </cc-popper>
        <cc-popper direction="bottom" content="下边也没有" alwaysShow>
          <view class="w-100rpx mt-20rpx"><u-button text="下面" /></view>
        </cc-popper>
      </view>
      <cc-popper direction="right" content="右边找找" alwaysShow>
        <view class="w-100rpx"><u-button text="右边" /></view>
      </cc-popper>
    </view>
    <view class="x-items-between px-150rpx pt-400rpx">
      <cc-popper alwaysShow>
        <view class="w-200rpx"><u-button shape="circle" text="烎" /></view>
        <template #content><text class="text-100rpx">🤩</text></template>
      </cc-popper>
      <cc-popper alwaysShow :menu="data.menu">
        <div class="w-100rpx h-100rpx bg-red"></div>
      </cc-popper>
    </view>
  </view>
</template>
<script lang="ts" setup>
import { MenuConf } from '@/components/ccframe/cc-popper.vue'

const data = reactive<{
  menu: Array<MenuConf>
}>({
  menu: [
    {
      title: '口袋1',
      callback: () => {
        console.log('糖果')
      }
    },
    {
      title: '口袋2',
      callback: () => {
        console.log('退出系统')
      }
    },
    {
      title: '口袋3',
      callback: () => {
        console.log('空的')
      }
    }
  ]
})
</script>

对了,菜单的图标支持还没写。等用到的时候再加上去,代码放这存档,后面再更新:)

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2207780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[含文档+PPT+源码等]精品基于asp.net实现的原生Andriod病例管理随访系统[包运行成功+永久免费答疑辅导]

基于ASP.NET实现的原生Android病例管理随访系统背景&#xff0c;可以从以下几个方面进行阐述&#xff1a; 一、技术背景 ASP.NET技术框架 ASP.NET是由微软开发的一种用于构建动态Web应用程序和服务的开源服务器端Web应用框架。它提供了一套丰富的工具和库&#xff0c;支持多种…

真实世界数据,重构临床试验在药物研发的价值!

近年来&#xff0c;真实世界数据&#xff08;RWD&#xff09;和真实世界证据&#xff08;RWE&#xff09;在学术界和工业界备受瞩目。为了促进国际间的交流合作&#xff0c;并提升中国RWE在全球的影响力&#xff0c;阿斯利康于2023年成立了真实世界证据外部咨询委员会&#xff…

FP8013:单节锂电池降压 切五路调光 补光灯/摄影灯 芯片,3A无频闪调光 体积小、效率高、静态功耗低

随着直播行业的不断发展&#xff0c;补光灯的关键性能也日益受到重视。为了提供更好的补光视觉效果&#xff0c;我们需要一种高效、稳定的调光芯片来驱动补光灯的亮度。 一、芯片特色 1、FP8013 工作电压 2.5V~5.5V 适用于单节锂电池和USB口5V输入。 2、内置高低侧切换 MOS&am…

STM32 DMA直接存储器访问 USART串口DMA发送 F407寄存器

DMA介绍&#xff1a; 特点&#xff1a; DMA:直接存储器访问 用于外设与存储器间以及存储器与存储器之间 提高数据传输的一种工具&#xff08;片上外设&#xff09; CPU相当于餐厅老板&#xff0c;只需要告诉DMA快递员 …

在Java程序中监听mysql的binlog

文章目录 1、背景2、mysql-binlog-connector-java简介3、准备工作1、验证数据库是否开启binlog2、开启数据库的binlog3、创建具有REPLICATION SLAVE权限的用户4、事件类型 eventType 解释1、TABLE_MAP 的注意事项2、获取操作的列名 5、监听binlog的position1、从最新的binlog位…

大数据-166 Apache Kylin Cube 流式构建 整体流程详细记录

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

SpringBoot开发——SpringSecurity安全框架17个业务场景案例(三)

文章目录 一、Spring Security 常用应用场景介绍二、Spring Security场景案例12 表达式支持(Expression-Based)12.1 Spring Security 配置12.2 业务逻辑代码12.3 控制器13、安全上下文(Security Context)13.1 Spring Security 配置13.2 业务逻辑代码13.3 控制器14、安全过滤…

Modnet 人像抠图(论文复现)

Modnet 人像抠图&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 Modnet 人像抠图&#xff08;论文复现&#xff09;论文概述论文方法复现WebUI部署 论文概述 人像抠图(Portrait matting)旨在预测一个精确的 alpha 抠图&#xff0c;可以用…

宠物浮毛的危害有哪些?适合养宠家庭的宠物空气净化器推荐!

上周有位患者来医院&#xff0c;他说自从养猫以来&#xff0c;喉咙有毛的感觉日益明显&#xff0c;吐也吐不出来&#xff0c;鼻子里也觉得充斥着毛毛&#xff0c;半夜呼吸的时候也时常受阻&#xff0c;现在一直咳嗽鼻塞。让他拍了片子后发现猫毛吸入肺部导致了肺炎。作为一位呼…

如何用ChatGPT 8小时写出一篇完整论文(附完整提示词)

今天教大家如何利用ChatGPT完成一篇完整的论文。只需要一个标题&#xff0c;剩下全部由ChatGPT完成。总耗时8小时。 阅前提醒&#xff1a; 1.适用人群&#xff1a;这个方法适合应付简单的学术任务&#xff0c;比如日常小论文或投稿一般期刊。但如果你要写高水平的论文&#xf…

【磁盘清理】linux df -h 命令不更新磁盘大小解决方法

centos-root df -h不更新磁盘大小 [rootkafka1 ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/centos-root 238G 194G 44G 82% / devtmpfs 7.8G 0 7.8G 0% /dev tmpfs 7.8G 0 7.8G …

深度学习基础知识-01 数据操作

三维数组&#xff1a;通常用于表示单个图像&#xff0c;如&#xff08;高度&#xff0c;宽度&#xff0c;通道数&#xff09;。 四维数组&#xff1a;用于表示图像批次&#xff0c;如&#xff08;批次大小&#xff0c;高度&#xff0c;宽度&#xff0c;通道数&#xff09;。 五…

公开选拔!产业实践教授

产业实践教授&#xff0c;这一相对新兴的概念旨在通过产教融合方式促进高校与产业界的深度融合。 通常由来自产业界、拥有丰富实践经验的企业专家担任&#xff0c;承担特定教学任务和科研指导&#xff0c;或利用自身产业资源为学生提供实践机会和就业指导。 随着产教融合政策…

博睿数据Bonree ONE全面适配HarmonyOS NEXT,守护鸿蒙原生应用稳健前行

10月8日&#xff0c;华为官方正式宣布&#xff0c;华为HarmonyOS NEXT&#xff08;也被称作“纯血鸿蒙”&#xff09;系统开启公测&#xff0c;迎来国产系统里程碑一刻。作为IT运维监控和可观测性领域的领导者&#xff0c;博睿数据&#xff08;股票代码&#xff1a;688229&…

交叉熵损失 在PyTorch 中的计算过程

其实就是根据 真实值的结果&#xff0c;当成索引去取的值 import torch import torch.nn as nnaaaa torch.tensor([[2.0,1.0,3.0],[2.0,4.0,2.0]])l1 nn.LogSoftmax(dim-1) result l1(aaaa) print(result) import torch import torch.nn as nn# 定义交叉熵损失函数 criterio…

电影《荒野机器人》观后感

上上周看了电影《荒野机器人》&#xff0c;电影整体是比较偏向温馨的&#xff0c;通过动物与机器人视角&#xff0c;展现人类为情感。 &#xff08;1&#xff09;承载-托举-学习-感情 在电影中&#xff0c;有个场景让自己感觉特别温馨&#xff0c;就是机器人为了让大雁宝宝学习…

盛⽔最多的容器 04

盛⽔最多的容器 算法思想&#xff1a;对撞指针&#xff1a; 我写的代码&#xff1a; class Solution {public int maxArea(int[] height) {int left0;int rightheight.length-1;int minHight-1;//防止[2,0]这种特殊情况int maxV0;while(left<right) {if(height[left]>…

【hot100-java】随机链表的复制

链表篇 /* // Definition for a Node. class Node {int val;Node next;Node random;public Node(int val) {this.val val;this.next null;this.random null;} } */class Solution {public Node copyRandomList(Node head) {if(headnull){return null;}java.util.Map<Node…

三品PLM系统赋能中小企业实现数字化转型迈向管理智能化

在全球化的浪潮中&#xff0c;发达国家的企业在管理体系上更具优势&#xff0c;常采用先进的PLM体系提升运营和战略规划效率。相较之下&#xff0c;国内中小企业在PLM系统的应用上明显滞后&#xff0c;中高层管理人员普遍缺乏相应的认知与实践经验&#xff0c;这限制了企业的创…

面向车辆路线问题的泛化神经方法

文章目录 Abstract1 Introduction2 相关工作3 PreliminariesAbstract 学习车辆路径问题(VRPs)的启发式方法由于减少了对手工制定规则的依赖而受到了广泛关注。然而,现有的方法通常在具有固定大小和节点分布的同一任务上进行训练和测试,因此在泛化性能上受到限制。本文研究…