UNIAPP popper气泡弹层【unibest框架下】vue3+typescript

news2024/10/9 6:26:11

看了下市场的代码,要么写的不怎么好,要么过于复杂。于是把市场的代码下下来了自己改。200行代码撸了个弹出层组件。兼容H5和APP。

功能:

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

  2)不对屏幕边界适配

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

  4)支持3种内容模式:

    1. 弹出提示文本

    2. slot内容占位

    3. 支持菜单模式


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

效果,H5下:

APP下:

小程序下:

组件代码:
 

<!--
  自定义弹出层/菜单组件
  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/2198314.html

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

相关文章

重学SpringBoot3-集成Redis(八)之限时任务(延迟队列)

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;八&#xff09;之限时任务&#xff08;延迟队列&#xff09; 1. 延迟任务的场景2. Redis Sorted Set基本原理3. 使用 Redis Sorte…

粗糙表面的仿真和处理软件

首款基于粗糙表面的仿真和处理软件&#xff0c;该软件具有三种方法&#xff0c;主要是二维数字滤波法&#xff0c;相位频谱法和共轭梯度法。可以分别仿真具有高斯和非高斯分布的粗糙表面&#xff0c;其中非高斯表面利用Johnson转换系统进行变换给定偏度和峰度。对生成的粗糙表面…

Mysql高级篇(下)——数据库备份与恢复

Mysql高级篇&#xff08;下&#xff09;——数据库备份与恢复 一、物理备份与逻辑备份1、物理备份2、逻辑备份3、对比4、总结 二、mysqldump实现逻辑备份1、mysqldump 常用选项2、mysqldump 逻辑备份语法&#xff08;1&#xff09;备份一个数据库&#xff08;2&#xff09;备份…

linux自动挂载tf卡

本人使用的是armbian系统&#xff0c;ssh工具使用的是finalshell&#xff0c;挂载的是一张64G TF卡。 1.查看系统所检测到的磁盘&#xff0c;这里的 sda1检测到的硬盘但是没有被挂载 lsblk //查看信息 2.在根目录新建一个目录tfcard用于挂载硬盘&#xff0c;命令如下&#xf…

【万字长文】Word2Vec计算详解(一)

【万字长文】Word2Vec计算详解&#xff08;一&#xff09; 写在前面 本文用于记录本人学习NLP过程中&#xff0c;学习Word2Vec部分时的详细过程&#xff0c;本文与本人写的其他文章一样&#xff0c;旨在给出Word2Vec模型中的详细计算过程&#xff0c;包括每个模块的计算过程&a…

电商选品/跟卖| 亚马逊商品类爬取

电商跟卖,最重要是了解哪些商品可以卖, 哪些商品不能卖, 为了更好了解商品信息,我们会经常爬取商品类目的信息. 需求 亚马逊类目信息链接爬虫 打开亚马逊类目信息地址 https://www.amazon.com/gp/new-releases/automotive/refzg_bsnr_nav_automotive_0 一直递归下去&#x…

云原生(四十七) | PHP软件安装部署

文章目录 PHP软件安装部署 一、PHP软件部署步骤 二、安装与配置PHP PHP软件安装部署 一、PHP软件部署步骤 第一步&#xff1a;安装 EPEL 仓库 与 Remi仓库 第二步&#xff1a;启用 Remi 仓库 第三步&#xff1a;安装 PHP、PHP-FPM 第四步&#xff1a;启动并开机启用 PH…

10.8 sql语句查询(未知的)

1.查询结果去重 关键字:distinct (放在查询的后面) AC: select distinct university from user_profile 2.查询结果限制返回行数 关键字:limit AC: select device_id from user_profile limit 0,2 3.将查询后的列重新命名 关键字:as AC: select device_id as user_infos…

wildcard使用教程,解决绝大多数普通人的海外支付难题

许多人可能已经注意到,国外的一些先进AI工具对国内用户并不开放。而想要使用这些工具,我们通常会面临两个主要障碍:一是网络访问的限制,二是支付问题。网络问题很容易解决&#xff0c;难的是如何解决在国内充值海外软件。 今天给大家推荐一个工具——wildcard&#xff0c;用它…

【CSS in Depth 2 精译_046】7.1 CSS 响应式设计中的移动端优先设计原则(下)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

StoryMaker: Towards Holistic Consistent Characters in Text-to-image Generation

https://arxiv.org/pdf/2409.12576v1https://github.com/RedAIGC/StoryMaker 问题引入 针对的是文生图的模型&#xff0c;现在已经有方法可以实现指定人物id的情况下进行生成&#xff0c;但是还没有办法保持包括服装、发型等整体&#xff0c;本文主要解决这个问题&#xff1b…

时间卷积网络(TCN)原理+代码详解

目录 一、TCN原理1.1 因果卷积&#xff08;Causal Convolution&#xff09;1.2 扩张卷积&#xff08;Dilated Convolution&#xff09; 二、代码实现2.1 Chomp1d 模块2.2 TemporalBlock 模块2.3 TemporalConvNet 模块2.4 完整代码示例 参考文献 在理解 TCN 的原理之前&#xff…

GIS后端工程师岗位职责、技术要求和常见面试题

GIS 后端工程师负责设计、开发与维护地理信息系统的后端服务&#xff0c;包括数据存储、处理、分析以及与前端的交互接口等&#xff0c;以实现高效的地理数据管理和功能支持。 GIS 后端工程师岗位职责 一、系统设计与开发 参与地理信息系统&#xff08;GIS&#xff09;项目的…

安装 Petalinux

资料准备 ubuntu 22.04: 运行内存8G 存储空间500G Petalinux&#xff1a;2024.1 安装流程 安装依赖 sudo apt-get update sudo apt-get upgrade sudo apt-get install iproute2 sudo apt-get install gawk sudo apt-get install build-essential sudo apt-ge…

7.3 物联网平台-Thingsboard使用教程

物联网平台-Thingsboard使用教程 目录概述需求&#xff1a; 设计思路实现思路分析 免费下载参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for chang…

如何使用ssm实现基于web技术的税务门户网站的实现+vue

TOC ssm820基于web技术的税务门户网站的实现vue 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff…

基于matlab的语音信号处理

摘要 利用所学习的数字信号处理知识&#xff0c;设计了一个有趣的音效处理系统&#xff0c;首先设计了几种不同的滤波器对声音进行滤波处理&#xff0c;分析了时域和频域的变化&#xff0c;比较了经过滤波处理后的声音与原来的声音有何变化。同时设计实现了语音的倒放&#xf…

从0开始linux(9)——进程(1)进程管理

欢迎来到博主的专栏&#xff1a;从0开始linux 博主ID&#xff1a;代码小豪 文章目录 查看进程进程管理PID与PPIDfork函数 在上一篇中我们了解到&#xff1a;当运行程序时&#xff0c;操作系统会将磁盘中的二进制文件读取到内存当中&#xff0c;程序运行到结束的过程称为进程&am…

【C++ 11】auto 自动类型推导

文章目录 【 1. 基本用法 】【 2. auto 的 应用 】2.0 auto 的限制2.1 简单实例2.2 auto 与指针、引用、const2.4 auto 定义迭代器2.5 auto 用于泛型编程 问题背景 在 C11 之前的版本&#xff08;C98 和 C 03&#xff09;中&#xff0c;定义变量或者声明变量之前都必须指明它的…

目标检测YOLO实战应用案例100讲-【目标检测】YOLOV11

目录 前言 算法原理 YOLO发展历程 什么是 YOLO11 YOLOv11 的主要特点 YOLO各版本概览 核心优势: YOLOv11改进方向 YOLOv11功能介绍 YOLOv11关键创新 YOLOv11 指标展示 YOLOV11实验 环境设置 准备数据集 训练模型 验证模型 应用领域 一、智慧交通与自动驾…