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

最近要实习一个泡泡弹层。看了下市场的代码,要么写的不怎么好,要么过于复杂。于是拿个轮子自己加工。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>

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

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】你不知道的知识小盲区——柔性数组

文章目录 一、什么是柔性数组二、柔性数组的特点三、柔性数组的使用四、柔性数组的优势 一、什么是柔性数组 也许你从来没有听说过柔性数组&#xff08;flexible array&#xff09;这个概念&#xff0c;但是它确实是存在的。在C99标准中&#xff0c;如果结构体的最后一个成员是…

【jQuery】 jQuery基础及选择器介绍(基本选择器 层次选择器 属性选择器 过滤选择器)

文章目录 jQuery基础1. 优势2. 版本3. 基本语法4. 选择器基本选择器层次选择器属性选择器过滤选择器基本过滤选择器可见性过滤选择器 注意事项 jQuery基础 jQuery 是一个功能强大且易于使用的 JavaScript 库&#xff0c;它极大地简化了前端开发的工作。无论是 DOM 操作、事件处…

健康推荐系统:SpringBoot技术实现

3系统分析 3.1可行性分析 通过对本基于智能推荐的卫生健康系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本基于智能推荐的卫生健康系统采用SSM框架&#…

在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位…

HCIP-HarmonyOS Application Developer 习题(十)

1、HarmonyOS设备A上的应用通过调用分布式任务调度的能力continuesbility&#xff0c;向设备B的应用发起跨端迁移&#xff0c;此过程属于跨端迁移中的哪个流程? A、流转准备 B、流转进行 C、流转结束 D、流转完成 答案&#xff1a;D 分析&#xff1a; 2、为了帮助用户通过全局…

软件测试工程师面试整理 —— 操作系统与网络基础!

在软件测试中&#xff0c;了解操作系统和网络基础知识对于有效地进行测试工作至关重要。无论是在配置测试环境、调试网络问题&#xff0c;还是在进行性能测试和安全测试时&#xff0c;这些知识都是不可或缺的。 1. 操作系统基础 操作系统&#xff08;Operating System, OS&am…

Node.js管理工具NVM

nvm&#xff08;Node Version Manager&#xff09;是一个用于管理多个 Node.js 版本的工具。以下是 nvm 的使用方法和一些常见命令&#xff1a; 一、安装 nvm 下载 nvm&#xff1a; 地址&#xff1a;https://github.com/coreybutler/nvm-windows/releases访问 nvm 的 GitHub 仓…

Python | Leetcode Python题解之第474题一和零

题目&#xff1a; 题解&#xff1a; class Solution:def findMaxForm(self, strs: List[str], m: int, n: int) -> int:count10 []for s in strs:count10.append([0,0])for c in s:if c 0: count10[-1][0]1else: count10[-1][1]1dp [[0]*(n1) for _ in range(m1)]for i …

贪吃蛇游戏(代码篇)

我们并不是为了满足别人的期待而活着。 前言 这是我自己做的第五个小项目---贪吃蛇游戏&#xff08;代码篇&#xff09;。后期我会继续制作其他小项目并开源至博客上。 上一小项目是贪吃蛇游戏&#xff08;必备知识篇&#xff09;&#xff0c;没看过的同学可以去看看&#xf…

文件完整性监控:如何提高企业的数据安全性

企业网络庞大而复杂&#xff0c;需要处理大量关键业务数据&#xff0c;这些敏感文件在企业网络中不断传输&#xff0c;并由多个用户和实体存储、共享和访问。FIM 工具或具有 FIM 功能的 SIEM 解决方案使企业能够跟踪未经授权的文件更改、对敏感信息的恶意访问、数据篡改尝试和内…

ubuntu下实时查看CPU,内存(Mem)和GPU的利用率

一、实时查看CPU和内存&#xff08;Mem&#xff09;利用率 htop官网&#xff1a;htop - an interactive process viewer sudo apt-get install htop htop ①. 顶部状态栏&#xff08;System Metrics Overview&#xff09; 这个区域显示系统的全局资源使用情况&#xff0c;包括…

JavaSE——集合12:Map接口实现类—Properties

目录 一、Properties基本介绍 二、Properties常用方法 一、Properties基本介绍 Properties类继承自HashTable类并且实现了Map接口&#xff0c;也是使用一种键值对的形式&#xff0c;来保存数据。Properties的使用特点和HashTable类似Properties还可以用于从xxx.properties文件…

【实践】快速学会使用阿里云消息队列RabbitMQ版

文章目录 1、场景简介2、实验架构和流程2.1、实验架构2.2、实验流程 3、创建实验资源4、创建阿里云AccessKey5、创建静态用户名密码6、创建Vhost、Exchange、Queue并绑定关系6.1、Vhost 的作用6.2、创建Vhost6.3、Exchange 的作用6.4、创建Exchange6.5、Queue 的作用6.6、创建Q…

基于Python flask的豆瓣电影可视化系统,豆瓣电影爬虫系统

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不…

Mysql(七) --- 索引

文章目录 前言1.简介1.1.索引是什么&#xff1f;1.2.为什么使用索引? 2.索引应该使用什么数据结构&#xff1f;2.1.Hash2.2.二叉搜索树2.3.N叉树2.4.B树2.4.1. 简介2.4.2. B树的特点2.4.3. B树和B树的对比 3.Mysql中的页3.1.为什么要使用页3.2.页文件头和页文件尾3.3.页主体3.…

【Linux】解锁线程基本概念和线程控制,步入多线程学习的大门

目录 1、线程初识 1.1线程的概念 1.2.关于线程和进程的进一步理解 1.3.线程的设计理念 1.4.进程vs线程&#xff08;图解&#xff09; 1.5地址空间的第四谈 2.线程的控制&#xff1a; 2.1.关于线程控制的前置知识 2.2创建线程的系统调用&#xff1a; 这个几号手册具体…

JavaScript | 定时器(setInterval和clearInterval)的使用

效果图如下&#xff1a; 当用户第一次看到这个页面时&#xff0c;按钮是不可点击的&#xff0c;并显示一个5秒的倒计时。倒计时结束后&#xff0c;按钮变为可点击状态&#xff0c;并显示“同意协议”。这样做的目的是确保用户有足够的时间阅读用户协议。 <!DOCTYPE html>…

机器学习:知识蒸馏(Knowledge Distillation,KD)

知识蒸馏&#xff08;Knowledge Distillation&#xff0c;KD&#xff09;作为深度学习领域中的一种模型压缩技术&#xff0c;主要用于将大规模、复杂的神经网络模型&#xff08;即教师模型&#xff09;压缩为较小的、轻量化的模型&#xff08;即学生模型&#xff09;。在实际应…

Vue(3) 组件

文章目录 对组件的理解单文件组件非单文件组件基本使用几个注意点组件的嵌套VueComponent构造函数一个重要的内置关系 组件的自定义事件全局事件总线安装全局事件总线使用事件总线解绑事件消息订阅与发布简介使用步骤范例 $nextTick插槽1.默认插槽2.具名插槽作用域插槽 对组件的…