vue3自定义dialog、modal组件的方法


Posted in Vue.js onJanuary 04, 2021

vue3-layer:基于Vue3.0开发的PC桌面端自定义对话框组件。

基于vue3构建的PC网页端自定义弹出框组件。全面覆盖各种弹窗应用场景,拥有10+种弹窗类型、30+种自定义参数配置、7+种弹窗动画效果,支持拖拽、缩放、最大化、全屏及自定义激活当前置顶层等功能。

vue3自定义dialog、modal组件的方法

前几天分享过一个Vue3.0移动端弹层组件V3Popup,如果感兴趣也可以去看看。

v3layer在开发设计之初灵感来自有赞Vant3.0、饿了么Element-Plus等组件化模式。

快速引入

在main.js中全局引入组件。

import { createApp } from 'vue'
import App from './App.vue'
 
// 引入Element-Plus组件库
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
 
// 引入弹窗组件v3layer
import V3Layer from './components/v3layer'
 
createApp(App).use(ElementPlus).use(V3Layer).mount('#app')

vue3自定义dialog、modal组件的方法

v3layer在调用上同样支持组件式+函数式两种方式。

组件写法

<v3-layer 
 v-model="showAlert"
 title="标题信息"
 content="<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>"
 z-index="2030"
 lockScroll="false"
 xclose
 resize
 dragOut
 :btns="[
  {text: '取消', click: () => showAlert=false},
  {text: '确认', style: 'color:#f90;', click: handleConfirm},
 ]"
/>
 <template #content>自定义插槽内容信息!</template>
</v3-layer>

 

函数写法

let $el = v3layer({
 title: '标题信息',
 content: '<div style='color:#ff5252;padding:50px;'>这里是内容信息!</div>', 
 shadeClose: false,
 zIndex: 2030,
 lockScroll: false,
 xclose: true,
 resize: true,
 dragOut: true,
 btns: [
  {text: '取消', click: () => { $el.close() }},
  {text: '确认', click: () => handleConfirm},
 ]
});

遵循极简的调用原则,只需输入参数配置即可快速调用弹窗,实现想要的效果。

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

vue3自定义dialog、modal组件的方法

参数配置

v3layer支持如下30+自定义参数配置,灵活搭配出各种效果。

|props参数|
v-model   是否显示弹框
id    弹窗唯一标识
title   标题
content   内容(支持String、带标签内容、自定义插槽内容)***如果content内容比较复杂,推荐使用标签式写法
type   弹框类型(toast|footer|actionsheet|actionsheetPicker|android|ios|contextmenu|drawer|iframe)
layerStyle  自定义弹窗样式
icon   toast图标(loading | success | fail)
shade   是否显示遮罩层
shadeClose  是否点击遮罩时关闭弹窗
lockScroll  是否弹窗出现时将body滚动锁定
opacity   遮罩层透明度
xclose   是否显示关闭图标
xposition  关闭图标位置(left | right | top | bottom)
xcolor   关闭图标颜色
anim   弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown | fadeInLeft | fadeInRight)
position  弹出位置(auto | ['100px','50px'] | t | r | b | l | lt | rt | lb | rb)
drawer   抽屉弹窗(top | right | bottom | left)
follow   跟随元素定位弹窗(支持元素.kk #kk 或 [e.clientX, e.clientY])
time   弹窗自动关闭秒数(1、2、3)
zIndex   弹窗层叠(默认8080)
teleport  指定挂载节点(默认是挂载组件标签位置,可通过teleport自定义挂载位置) teleport="body | #xxx | .xxx"
topmost   置顶当前窗口(默认false)
area   弹窗宽高(默认auto)设置宽度area: '300px' 设置高度area:['', '200px'] 设置宽高area:['350px', '150px']
maxWidth  弹窗最大宽度(只有当area:'auto'时,maxWidth的设定才有效)
maximize  是否显示最大化按钮(默认false)
fullscreen  全屏弹窗(默认false)
fixed   弹窗是否固定
drag   拖拽元素(可定义选择器drag:'.xxx' | 禁止拖拽drag:false)
dragOut   是否允许拖拽到窗口外(默认false)
lockAxis  限制拖拽方向可选: v 垂直、h 水平,默认不限制
resize   是否允许拉伸尺寸(默认false)
btns   弹窗按钮(参数:text|style|disabled|click)
++++++++++++++++++++++++++++++++++++++++++++++
|emit事件触发|
success   层弹出后回调(@success="xxx")
end    层销毁后回调(@end="xxx")
++++++++++++++++++++++++++++++++++++++++++++++
|event事件|
onSuccess  层打开回调事件
onEnd   层关闭回调事件

v3layer弹窗模板及逻辑处理。

<template>
 <div ref="elRef" v-show="opened" class="vui__layer" :class="{'vui__layer-closed': closeCls}" :id="id">
  <!-- //蒙版 -->
  <div v-if="JSON.parse(shade)" class="vlayer__overlay" @click="shadeClicked" :style="{opacity}"></div>
  <div class="vlayer__wrap" :class="['anim-'+anim, type&&'popui__'+type, tipArrow]" :style="[layerStyle]">
   <div v-if="title" class="vlayer__wrap-tit" v-html="title"></div>
   <div v-if="type=='toast'&&icon" class="vlayer__toast-icon" :class="['vlayer__toast-'+icon]" v-html="toastIcon[icon]"></div>
   <div class="vlayer__wrap-cntbox">
    <!-- 判断插槽是否存在 -->
    <template v-if="$slots.content">
     <div class="vlayer__wrap-cnt"><slot name="content" /></div>
    </template>
    <template v-else>
     <template v-if="content">
      <iframe v-if="type=='iframe'" scrolling="auto" allowtransparency="true" frameborder="0" :src="content"></iframe>
      <!-- message|notify|popover -->
      <div v-else-if="type=='message' || type=='notify' || type=='popover'" class="vlayer__wrap-cnt">
       <i v-if="icon" class="vlayer-msg__icon" :class="icon" v-html="messageIcon[icon]"></i>
       <div class="vlayer-msg__group"><div v-if="title" class="vlayer-msg__title" v-html="title"></div><div v-html="content"></div></div>
      </div>
      <div v-else class="vlayer__wrap-cnt" v-html="content"></div>
     </template>
    </template>
    <slot />
   </div>
   <div v-if="btns" class="vlayer__wrap-btns">
    <span v-for="(btn,index) in btns" :key="index" class="btn" :style="btn.style" @click="btnClicked($event,index)" v-html="btn.text"></span>
   </div>
   <span v-if="xclose" class="vlayer__xclose" :class="!maximize&&xposition" :style="{'color': xcolor}" @click="close"></span>
   <span v-if="maximize" class="vlayer__maximize" @click="maximizeClicked($event)"></span>
   <span v-if="resize" class="vlayer__resize"></span>
  </div>
  <!-- 优化拖拽卡顿 -->
  <div class="vlayer__dragfix"></div>
 </div>
</template>
 
/**
 * @Desc  Vue3.0桌面端弹窗组件V3Layer
 * @Time  andy by 2021-1
 * @About Q:282310962 wx:xy190310
 */
<script>
 import { onMounted, onUnmounted, ref, reactive, watch, toRefs, nextTick } from 'vue'
 import domUtils from './utils/dom.js'
 // 索引,蒙层控制,定时器
 let $index = 0, $locknum = 0, $timer = {}, $closeTimer = null
 export default {
  props: {
   // ...
  },
  emits: [
   'update:modelValue'
  ],
  setup(props, context) {
   const elRef = ref(null);
 
   const data = reactive({
    opened: false,
    closeCls: '',
    toastIcon: {
     // ...
    },
    messageIcon: {
     // ...
    },
    vlayerOpts: {},
    tipArrow: null,
   })
 
   onMounted(() => {
    if(props.modelValue) {
     open();
    }
    window.addEventListener('resize', autopos, false);
   })
 
   onUnmounted(() => {
    window.removeEventListener('resize', autopos, false);
    clearTimeout($closeTimer);
   })
 
   // 监听弹层v-model
   watch(() => props.modelValue, (val) => {
    // console.log('V3Layer is now [%s]', val ? 'show' : 'hide')
    if(val) {
     open();
    }else {
     close();
    }
   })
 
   // 打开弹窗
   const open = () => {
    if(data.opened) return;
    data.opened = true;
    typeof props.onSuccess === 'function' && props.onSuccess();
 
    const dom = elRef.value;
    // 弹层挂载位置
    if(props.teleport) {
     nextTick(() => {
      let teleportNode = document.querySelector(props.teleport);
      teleportNode.appendChild(dom);
 
      auto();
     })
    }
 
    callback();
   }
 
   // 关闭弹窗
   const close = () => {
    if(!data.opened) return;
 
    let dom = elRef.value;
    let vlayero = dom.querySelector('.vlayer__wrap');
    let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
    let omax = dom.querySelector('.vlayer__maximize');
 
    data.closeCls = true;
    clearTimeout($closeTimer);
    $closeTimer = setTimeout(() => {
     data.opened = false;
     data.closeCls = false;
     if(data.vlayerOpts.lockScroll) {
      $locknum--;
      if(!$locknum) {
       document.body.style.paddingRight = '';
       document.body.classList.remove('vui__body-hidden');
      }
     }
     if(props.time) {
      $index--;
     }
     // 清除弹窗样式
     vlayero.style.width = vlayero.style.height = vlayero.style.top = vlayero.style.left = '';
     ocnt.style.height = '';
     omax && omax.classList.contains('maximized') && omax.classList.remove('maximized');
     
     data.vlayerOpts.isBodyOverflow && (document.body.style.overflow = '');
 
     context.emit('update:modelValue', false);
     typeof props.onEnd === 'function' && props.onEnd();
    }, 200)
   }
 
   // 弹窗位置
   const auto = () => {
    // ...
 
    autopos();
 
    // 全屏弹窗
    if(props.fullscreen) {
     full();
    }
 
    // 弹窗拖动|缩放
    move();
   }
 
   const autopos = () => {
    if(!data.opened) return;
    let oL, oT
    let pos = props.position;
    let isFixed = JSON.parse(props.fixed);
    let dom = elRef.value;
    let vlayero = dom.querySelector('.vlayer__wrap');
 
    if(!isFixed || props.follow) {
     vlayero.style.position = 'absolute';
    }
    
    let area = [domUtils.client('width'), domUtils.client('height'), vlayero.offsetWidth, vlayero.offsetHeight]
    
    oL = (area[0] - area[2]) / 2;
    oT = (area[1] - area[3]) / 2;
 
    if(props.follow) {
     offset();
    }else {
     typeof pos === 'object' ? (
      oL = parseFloat(pos[0]) || 0, oT = parseFloat(pos[1]) || 0
     ) : (
      pos == 't' ? oT = 0 : 
      pos == 'r' ? oL = area[0] - area[2] : 
      pos == 'b' ? oT = area[1] - area[3] : 
      pos == 'l' ? oL = 0 : 
      pos == 'lt' ? (oL = 0, oT = 0) : 
      pos == 'rt' ? (oL = area[0] - area[2], oT = 0) : 
      pos == 'lb' ? (oL = 0, oT = area[1] - area[3]) :
      pos == 'rb' ? (oL = area[0] - area[2], oT = area[1] - area[3]) : 
      null
     )
 
     vlayero.style.left = parseFloat(isFixed ? oL : domUtils.scroll('left') + oL) + 'px';
     vlayero.style.top = parseFloat(isFixed ? oT : domUtils.scroll('top') + oT) + 'px';
    }
   }
 
   // 元素跟随定位
   const offset = () => {
    let oW, oH, pS
    let dom = elRef.value
    let vlayero = dom.querySelector('.vlayer__wrap');
 
    oW = vlayero.offsetWidth;
    oH = vlayero.offsetHeight;
    pS = domUtils.getFollowRect(props.follow, oW, oH);
    data.tipArrow = pS[2];
    
    vlayero.style.left = pS[0] + 'px';
    vlayero.style.top = pS[1] + 'px';
   }
 
   // 最大化弹窗
   const full = () => {
    // ...
   }
 
   // 恢复弹窗
   const restore = () => {
    let dom = elRef.value;
    let vlayero = dom.querySelector('.vlayer__wrap');
    let otit = dom.querySelector('.vlayer__wrap-tit');
    let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
    let obtn = dom.querySelector('.vlayer__wrap-btns');
    let omax = dom.querySelector('.vlayer__maximize');
 
    let t = otit ? otit.offsetHeight : 0
    let b = obtn ? obtn.offsetHeight : 0
 
    if(!data.vlayerOpts.lockScroll) {
     data.vlayerOpts.isBodyOverflow = false;
     document.body.style.overflow = '';
    }
    
    props.maximize && omax.classList.remove('maximized')
    
    vlayero.style.left = parseFloat(data.vlayerOpts.rect[0]) + 'px';
    vlayero.style.top = parseFloat(data.vlayerOpts.rect[1]) + 'px';
    vlayero.style.width = parseFloat(data.vlayerOpts.rect[2]) + 'px';
    vlayero.style.height = parseFloat(data.vlayerOpts.rect[3]) + 'px';
   }
 
   // 拖动|缩放弹窗
   const move = () => {
    // ...
   }
 
   // 事件处理
   const callback = () => {
    // 倒计时关闭
    if(props.time) {
     $index++
     // 防止重复点击
     if($timer[$index] !== null) clearTimeout($timer[$index])
     $timer[$index] = setTimeout(() => {
      close();
     }, parseInt(props.time) * 1000)
    }
   }
 
   // 点击最大化按钮
   const maximizeClicked = (e) => {
    let o = e.target
    if(o.classList.contains('maximized')) {
     // 恢复
     restore();
    } else {
     // 最大化
     full();
    }
   }
   // 点击遮罩层
   const shadeClicked = () => {
    if(JSON.parse(props.shadeClose)) {
     close();
    }
   }
   // 按钮事件
   const btnClicked = (e, index) => {
    let btn = props.btns[index]
    if(!btn.disabled) {
     typeof btn.click === 'function' && btn.click(e)
    }
   }
   
   return {
    ...toRefs(data),
    elRef,
    close,
    maximizeClicked,
    shadeClicked,
    btnClicked,
   }
  }
 }
</script>

大家如果感兴趣,可以在此基础上自行定制一些想要的效果。

另外,v3layer组件支持自定义拖拽区域 (drag:'class或id'),是否拖动到窗口外 (dragOut:true)。支持iframe弹窗类型 (type:'iframe')。

当配置 topmost:true 则会将当前活动窗口置顶显示。

vue3自定义dialog、modal组件的方法

好了,基于vue3.x开发自定义弹窗组件就介绍到这里。希望大家能喜欢~~??

到此这篇关于vue3自定义dialog、modal组件的方法的文章就介绍到这了,更多相关vue 自定义dialog、modal组件内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Vue.js 相关文章推荐
Vue项目利用axios请求接口下载excel
Nov 17 Vue.js
Vue如何跨组件传递Slot的实现
Dec 14 Vue.js
Vue实现圆环进度条的示例
Feb 06 Vue.js
vue中data改变后让视图同步更新的方法
Mar 29 Vue.js
Vue vee-validate插件的简单使用
Jun 22 Vue.js
Vue的生命周期一起来看看
Feb 24 Vue.js
Vue2.0搭建脚手架
Mar 13 Vue.js
vue整合百度地图显示指定地点信息
Apr 06 Vue.js
vue实现列表拖拽排序的示例代码
Apr 08 Vue.js
vue实现书本翻页动画效果实例详解
Apr 08 Vue.js
vue修饰符.capture和.self的区别
Apr 22 Vue.js
Vue Element plus使用方法梳理
Dec 24 Vue.js
vue中父子组件的参数传递和应用示例
Jan 04 #Vue.js
如何在VUE中使用vue-awesome-swiper
Jan 04 #Vue.js
vue项目如何监听localStorage或sessionStorage的变化
Jan 04 #Vue.js
手写Vue源码之数据劫持示例详解
Jan 04 #Vue.js
vue+vant 上传图片需要注意的地方
Jan 03 #Vue.js
vue调用微信JSDK 扫一扫,相册等需要注意的事项
Jan 03 #Vue.js
vue中使用echarts的示例
Jan 03 #Vue.js
You might like
php 更新数据库中断的解决方法
2009/06/05 PHP
php上传文件中文文件名乱码的解决方法
2013/11/01 PHP
php 不使用js实现页面跳转
2014/02/11 PHP
mac下多个php版本快速切换的方法
2016/10/09 PHP
ThinkPHP 整合Bootstrap Ajax分页样式
2016/12/23 PHP
PHP使用PDO、mysqli扩展实现与数据库交互操作详解
2019/07/20 PHP
jQuery实现表单input中提示文字value随鼠标焦点移进移出而显示或隐藏的代码
2010/03/21 Javascript
JavaScript返回网页中锚点数目的方法
2015/04/03 Javascript
JS实现DIV容器赋值的方法
2015/12/14 Javascript
Nodejs抓取html页面内容(推荐)
2016/08/11 NodeJs
JS闭包与延迟求值用法示例
2016/12/22 Javascript
通俗解释JavaScript正则表达式快速记忆
2017/08/23 Javascript
JS调用安卓手机摄像头扫描二维码
2018/10/16 Javascript
Javascript三种字符串连接方式及性能比较
2019/05/28 Javascript
vue循环数组改变点击文字的颜色
2019/10/14 Javascript
详解React 元素渲染
2020/07/07 Javascript
vue项目在线上服务器访问失败原因分析
2020/08/14 Javascript
python搭建虚拟环境的步骤详解
2016/09/27 Python
python 编程之twisted详解及简单实例
2017/01/28 Python
python实现聚类算法原理
2018/02/12 Python
python微信公众号开发简单流程
2018/03/23 Python
Python之列表的插入&amp;替换修改方法
2018/06/28 Python
python从子线程中获得返回值的方法
2019/01/30 Python
pygame实现俄罗斯方块游戏(基础篇1)
2019/10/29 Python
python实现简单图书管理系统
2019/11/22 Python
Python实例方法、类方法、静态方法区别详解
2020/09/05 Python
优瑞自动咖啡机官网:Jura
2018/09/29 全球购物
超市业务员岗位职责
2013/12/05 职场文书
教师竞聘演讲稿
2014/05/16 职场文书
求职信结尾怎么写
2014/05/26 职场文书
小学生常见病防治方案
2014/06/06 职场文书
教师四风自我剖析材料
2014/09/30 职场文书
银行贷款委托书范本
2014/10/11 职场文书
务虚会发言材料
2014/12/25 职场文书
2016年三八节红领巾广播稿
2015/12/17 职场文书
Python 读写 Matlab Mat 格式数据的操作
2021/05/19 Python