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页面刷新,数据丢失的问题
Nov 24 Vue.js
vue调用微信JSDK 扫一扫,相册等需要注意的事项
Jan 03 Vue.js
Vue实现摇一摇功能(兼容ios13.3以上)
Jan 26 Vue.js
vue穿梭框实现上下移动
Jan 29 Vue.js
vue 使用 v-model 双向绑定父子组件的值遇见的问题及解决方案
Mar 01 Vue.js
vue2实现provide inject传递响应式
May 21 Vue.js
Vue+Element UI实现概要小弹窗的全过程
May 30 Vue.js
vue中利用mqtt服务端实现即时通讯的步骤记录
Jul 01 Vue.js
详细聊聊vue中组件的props属性
Nov 02 Vue.js
Vue如何清空对象
Mar 03 Vue.js
vue中this.$http.post()跨域和请求参数丢失的解决
Apr 08 Vue.js
关于vue-router-link选择样式设置
Apr 30 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完整的日历类(CLASS)
2006/11/27 PHP
PHP中动态显示签名和ip原理
2007/03/28 PHP
php cache类代码(php数据缓存类)
2010/04/15 PHP
从PHP的源码中深入了解stdClass类
2014/04/18 PHP
基于JQuery+PHP编写砸金蛋中奖程序
2015/09/08 PHP
PHP使用PDO实现mysql防注入功能详解
2019/12/20 PHP
javascript下判断一个元素是否存在的代码
2010/03/05 Javascript
js传参数受特殊字符影响错误的解决方法
2013/10/21 Javascript
javascript中几个容易混淆的概念总结
2015/04/14 Javascript
jQuery中attr()与prop()函数用法实例详解(附用法区别)
2015/12/29 Javascript
关于JavaScript作用域你想知道的一切
2016/02/04 Javascript
Fullpage.js固定导航栏-实现定位导航栏
2016/03/17 Javascript
轻松实现js选项卡切换效果
2016/09/24 Javascript
AngularJS实现在ng-Options加上index的解决方法
2016/11/03 Javascript
写给vue新手们的vue渲染页面教程
2017/09/01 Javascript
vue3.0 CLI - 3.2 路由的初级使用教程
2018/09/20 Javascript
详解Js里的for…in和for…of的用法
2019/03/28 Javascript
快速解决element的autofocus失效问题
2020/09/08 Javascript
[00:43]TI7不朽珍藏III——幽鬼不朽展示
2017/07/15 DOTA
详解Python文本操作相关模块
2017/06/22 Python
python编程之requests在网络请求中添加cookies参数方法详解
2017/10/25 Python
Linux下通过python获取本机ip方法示例
2019/09/06 Python
python 从list中随机取值的方法
2020/11/16 Python
CAT鞋英国官网:坚固耐用的靴子和鞋
2016/10/21 全球购物
Nike英国官网:Nike.com (UK)
2017/02/13 全球购物
小学作文评语大全
2014/04/21 职场文书
初中班主任经验交流材料
2014/05/16 职场文书
出纳试用期自我鉴定范文
2014/09/16 职场文书
2014年护士长工作总结
2014/11/11 职场文书
邀请函范文
2015/02/02 职场文书
英语教师个人工作总结
2015/02/09 职场文书
储备店长岗位职责
2015/04/14 职场文书
2016优秀大学生个人事迹材料范文
2016/03/01 职场文书
幼儿园大班教学反思
2016/03/02 职场文书
CSS巧用渐变实现高级感背景光动画
2021/12/06 HTML / CSS
Python用any()函数检查字符串中的字母以及如何使用all()函数
2022/04/14 Python