当前位置: 首页 > article >正文

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

看了下市场的代码,要么写的不怎么好,要么过于复杂。于是把市场的代码下下来了自己改。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.kler.cn/news/339338.html

相关文章:

  • LC538 - 把二叉搜索树转换为累加树
  • C++ 线性表、内存操作、 迭代器,数据与算法分离。
  • Java访问器方法和更改器方法
  • 以后再也不要说程序员不能拿诺贝尔了
  • Linux 计划任务
  • HAR笔记--kinectis-400数据集下载和简单处理代码
  • 用户说 | 天合光能以“光”速响应一线需求,开启“追光”新篇章
  • 专业高清录屏软件!Mirillis Action v4.40 解锁版下载,小白看了都会的安装方法
  • Metal之旅——数据
  • 现代身份和访问管理 IAM 如何降低风险
  • 【AUTOSAR 基础软件】PduR模块详解(通信路由)
  • golang中如何读取nacos配置中心的信息
  • 自闭症能不能摘帽?科学判断方法与依据
  • Linux线程(七)线程安全详解
  • 系列二、案例实操
  • express,接口 token 验证
  • 【VUE】会员管理(增删改查)
  • Day47【最小生成树】
  • QT 优化登录框
  • C++ day03(作用域限定符、this、static、const)