基于Vue3.0开发PC桌面端自定义对话框组件V3Layer。

前两天有分享一个vue3.0移动端弹出层组件,今天分享的是最新开发的vue3.0版pc端弹窗组件。

V3Layer 一款使用vue3.0开发的多功能PC网页端自定义弹窗组件。拥有超过10+种弹窗类型、30+种参数配置,支持拖拽(自定义拖拽区域)、缩放、最大化、全屏及自定义置顶层叠等功能。

v3layer的开发灵感同样来自之前v2版本,在功能效果上和之前的保持同步一致性。

◆ 引入组件

  1. // 在main.js中全局引入组件
  2. import { createApp } from 'vue'
  3. import App from './App.vue'
  4.  
  5. // 引入Element-Plus组件库
  6. import ElementPlus from 'element-plus'
  7. import 'element-plus/lib/theme-chalk/index.css'
  8.  
  9. // 引入弹窗组件v3layer
  10. import V3Layer from './components/v3layer'
  11.  
  12. createApp(App).use(ElementPlus).use(V3Layer).mount('#app')

v3layer同样的支持组件式+函数式调用方式。

  • 组件式写法
  1. <v3-layer
  2. v-model="showDialog"
  3. title="标题内容"
  4. content="<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>"
  5. z-index="2021"
  6. lockScroll="false"
  7. xclose
  8. resize
  9. dragOut
  10. :btns="[
  11. {text: '取消', click: () => showDialog=false},
  12. {text: '确认', style: 'color:#f90;', click: handleSure},
  13. ]"
  14. />
  15. <template v-slot:content>这里是自定义插槽内容信息!</template>
  16. </v3-layer>
  • 函数式写法

v3layer支持30+种参数灵活配置,当弹窗类型为  message | notify | popover  调用如下:

v3layer.message({...})  v3layer.notify({...})  v3layer.popover({...})

  1. let $el = v3layer({
  2. title: '标题内容',
  3. content: '<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>',
  4. shadeClose: false,
  5. zIndex: 2021,
  6. lockScroll: false,
  7. xclose: true,
  8. resize: true,
  9. dragOut: true,
  10. btns: [
  11. {text: '取消', click: () => { $el.close() }},
  12. {text: '确认', click: () => handleSure},
  13. ]
  14. });

大家都知道vue2中可以通过 prototype 原型链来实现全局函数。那么vue3中如何实现全局方法呢?

其实vue3中也提供了两种挂载全局方法。 app.config.globalProperties 或 app.provide

  • 通过 app.config.globalProperties.$v3layer = V3Layer 方式
  1. // vue2中调用
  2. methods: {
  3. showDialog() {
  4. this.$v3layer({...})
  5. }
  6. }
  7.  
  8. // vue3中调用
  9. setup() {
  10. // 获取上下文
  11. const { ctx } = getCurrentInstance()
  12. ctx.$v3layer({...})
  13. }
  • 通过 app.provide('v3layer', V3Layer) 方式
  1. // vue2中调用
  2. methods: {
  3. showDialog() {
  4. this.v3layer({...})
  5. }
  6. }
  7.  
  8. // vue3中调用
  9. setup() {
  10. const v3layer = inject('v3layer')
  11.  
  12. const showDialog = () => {
  13. v3layer({...})
  14. }
  15.  
  16. return {
  17. v3layer,
  18. showDialog
  19. }
  20. }

大家可以根据喜好,选择一种方式即可。

◆ 预览图

◆ 编码实现

  • v3layer默认配置参数

支持如下参数灵活使用,实现个性化弹窗效果。

  1. |props参数|
  2. v-model 是否显示弹框
  3. id 弹窗唯一标识
  4. title 标题
  5. content 内容(支持String、带标签内容、自定义插槽内容)***如果content内容比较复杂,推荐使用标签式写法
  6. type 弹框类型(toast|footer|actionsheet|actionsheetPicker|android|ios|contextmenu|drawer|iframe
  7. layerStyle 自定义弹窗样式
  8. icon toast图标(loading | success | fail
  9. shade 是否显示遮罩层
  10. shadeClose 是否点击遮罩时关闭弹窗
  11. lockScroll 是否弹窗出现时将body滚动锁定
  12. opacity 遮罩层透明度
  13. xclose 是否显示关闭图标
  14. xposition 关闭图标位置(left | right | top | bottom
  15. xcolor 关闭图标颜色
  16. anim 弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown | fadeInLeft | fadeInRight
  17. position 弹出位置(auto | ['100px','50px'] | t | r | b | l | lt | rt | lb | rb
  18. drawer 抽屉弹窗(top | right | bottom | left
  19. follow 跟随元素定位弹窗(支持元素.kk #kk 或 [e.clientX, e.clientY])
  20. time 弹窗自动关闭秒数(123
  21. zIndex 弹窗层叠(默认8080
  22. teleport 指定挂载节点(默认是挂载组件标签位置,可通过teleport自定义挂载位置) teleport="body | #xxx | .xxx"
  23. topmost 置顶当前窗口(默认false
  24. area 弹窗宽高(默认auto)设置宽度area: '300px' 设置高度area:['', '200px'] 设置宽高area:['350px', '150px']
  25. maxWidth 弹窗最大宽度(只有当area:'auto'时,maxWidth的设定才有效)
  26. maximize 是否显示最大化按钮(默认false
  27. fullscreen 全屏弹窗(默认false
  28. fixed 弹窗是否固定
  29. drag 拖拽元素(可定义选择器drag:'.xxx' | 禁止拖拽drag:false
  30. dragOut 是否允许拖拽到窗口外(默认false
  31. lockAxis 限制拖拽方向可选: v 垂直、h 水平,默认不限制
  32. resize 是否允许拉伸尺寸(默认false
  33. btns 弹窗按钮(参数:text|style|disabled|click
  34. ++++++++++++++++++++++++++++++++++++++++++++++
  35. |emit事件触发|
  36. success 层弹出后回调(@success="xxx"
  37. end 层销毁后回调(@end="xxx"
  38. ++++++++++++++++++++++++++++++++++++++++++++++
  39. |event事件|
  40. onSuccess 层打开回调事件
  41. onEnd 层关闭回调事件
  • v3layer模板组件及核心逻辑处理。
  1. <template>
  2. <div ref="elRef" v-show="opened" class="vui__layer" :class="{'vui__layer-closed': closeCls}" :id="id">
  3. <!-- //蒙版 -->
  4. <div v-if="JSON.parse(shade)" class="vlayer__overlay" @click="shadeClicked" :style="{opacity}"></div>
  5. <div class="vlayer__wrap" :class="['anim-'+anim, type&&'popui__'+type, tipArrow]" :style="[layerStyle]">
  6. <div v-if="title" class="vlayer__wrap-tit" v-html="title"></div>
  7. <div v-if="type=='toast'&&icon" class="vlayer__toast-icon" :class="['vlayer__toast-'+icon]" v-html="toastIcon[icon]"></div>
  8. <div class="vlayer__wrap-cntbox">
  9. <!-- 判断插槽是否存在 -->
  10. <template v-if="$slots.content">
  11. <div class="vlayer__wrap-cnt"><slot name="content" /></div>
  12. </template>
  13. <template v-else>
  14. <template v-if="content">
  15. <iframe v-if="type=='iframe'" scrolling="auto" allowtransparency="true" frameborder="0" :src="content"></iframe>
  16. <!-- message|notify|popover -->
  17. <div v-else-if="type=='message' || type=='notify' || type=='popover'" class="vlayer__wrap-cnt">
  18. <i v-if="icon" class="vlayer-msg__icon" :class="icon" v-html="messageIcon[icon]"></i>
  19. <div class="vlayer-msg__group"><div v-if="title" class="vlayer-msg__title" v-html="title"></div><div v-html="content"></div></div>
  20. </div>
  21. <div v-else class="vlayer__wrap-cnt" v-html="content"></div>
  22. </template>
  23. </template>
  24. <slot />
  25. </div>
  26. <div v-if="btns" class="vlayer__wrap-btns">
  27. <span v-for="(btn,index) in btns" :key="index" class="btn" :style="btn.style" @click="btnClicked($event,index)" v-html="btn.text"></span>
  28. </div>
  29. <span v-if="xclose" class="vlayer__xclose" :class="!maximize&&xposition" :style="{'color': xcolor}" @click="close"></span>
  30. <span v-if="maximize" class="vlayer__maximize" @click="maximizeClicked($event)"></span>
  31. <span v-if="resize" class="vlayer__resize"></span>
  32. </div>
  33. <!-- 优化拖拽卡顿 -->
  34. <div class="vlayer__dragfix"></div>
  35. </div>
  36. </template>
  1. /**
  2. * @Desc Vue3.0桌面端弹窗组件V3Layer
  3. * @Time andy by 2021-1
  4. * @About Q:282310962 wx:xy190310
  5. */
  6. <script>
  7. import { onMounted, onUnmounted, ref, reactive, watch, toRefs, nextTick } from 'vue'
  8. import domUtils from './utils/dom.js'
  9. // 索引,蒙层控制,定时器
  10. let $index = 0, $locknum = 0, $timer = {}, $closeTimer = null
  11. export default {
  12. props: {
  13. // ...
  14. },
  15. emits: [
  16. 'update:modelValue'
  17. ],
  18. setup(props, context) {
  19. const elRef = ref(null);
  20.  
  21. const data = reactive({
  22. opened: false,
  23. closeCls: '',
  24. toastIcon: {
  25. // ...
  26. },
  27. messageIcon: {
  28. // ...
  29. },
  30. vlayerOpts: {},
  31. tipArrow: null,
  32. })
  33.  
  34. onMounted(() => {
  35. if(props.modelValue) {
  36. open();
  37. }
  38. window.addEventListener('resize', autopos, false);
  39. })
  40.  
  41. onUnmounted(() => {
  42. window.removeEventListener('resize', autopos, false);
  43. clearTimeout($closeTimer);
  44. })
  45.  
  46. // 监听弹层v-model
  47. watch(() => props.modelValue, (val) => {
  48. // console.log('V3Layer is now [%s]', val ? 'show' : 'hide')
  49. if(val) {
  50. open();
  51. }else {
  52. close();
  53. }
  54. })
  55.  
  56. // 打开弹窗
  57. const open = () => {
  58. if(data.opened) return;
  59. data.opened = true;
  60. typeof props.onSuccess === 'function' && props.onSuccess();
  61.  
  62. const dom = elRef.value;
  63. // 弹层挂载位置
  64. if(props.teleport) {
  65. nextTick(() => {
  66. let teleportNode = document.querySelector(props.teleport);
  67. teleportNode.appendChild(dom);
  68.  
  69. auto();
  70. })
  71. }
  72.  
  73. callback();
  74. }
  75.  
  76. // 关闭弹窗
  77. const close = () => {
  78. if(!data.opened) return;
  79.  
  80. let dom = elRef.value;
  81. let vlayero = dom.querySelector('.vlayer__wrap');
  82. let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
  83. let omax = dom.querySelector('.vlayer__maximize');
  84.  
  85. data.closeCls = true;
  86. clearTimeout($closeTimer);
  87. $closeTimer = setTimeout(() => {
  88. data.opened = false;
  89. data.closeCls = false;
  90. if(data.vlayerOpts.lockScroll) {
  91. $locknum--;
  92. if(!$locknum) {
  93. document.body.style.paddingRight = '';
  94. document.body.classList.remove('vui__body-hidden');
  95. }
  96. }
  97. if(props.time) {
  98. $index--;
  99. }
  100. // 清除弹窗样式
  101. vlayero.style.width = vlayero.style.height = vlayero.style.top = vlayero.style.left = '';
  102. ocnt.style.height = '';
  103. omax && omax.classList.contains('maximized') && omax.classList.remove('maximized');
  104.  
  105. data.vlayerOpts.isBodyOverflow && (document.body.style.overflow = '');
  106.  
  107. context.emit('update:modelValue', false);
  108. typeof props.onEnd === 'function' && props.onEnd();
  109. }, 200)
  110. }
  111.  
  112. // 弹窗位置
  113. const auto = () => {
  114. // ...
  115.  
  116. autopos();
  117.  
  118. // 全屏弹窗
  119. if(props.fullscreen) {
  120. full();
  121. }
  122.  
  123. // 弹窗拖动|缩放
  124. move();
  125. }
  126.  
  127. const autopos = () => {
  128. if(!data.opened) return;
  129. let oL, oT
  130. let pos = props.position;
  131. let isFixed = JSON.parse(props.fixed);
  132. let dom = elRef.value;
  133. let vlayero = dom.querySelector('.vlayer__wrap');
  134.  
  135. if(!isFixed || props.follow) {
  136. vlayero.style.position = 'absolute';
  137. }
  138.  
  139. let area = [domUtils.client('width'), domUtils.client('height'), vlayero.offsetWidth, vlayero.offsetHeight]
  140.  
  141. oL = (area[0] - area[2]) / 2;
  142. oT = (area[1] - area[3]) / 2;
  143.  
  144. if(props.follow) {
  145. offset();
  146. }else {
  147. typeof pos === 'object' ? (
  148. oL = parseFloat(pos[0]) || 0, oT = parseFloat(pos[1]) || 0
  149. ) : (
  150. pos == 't' ? oT = 0 :
  151. pos == 'r' ? oL = area[0] - area[2] :
  152. pos == 'b' ? oT = area[1] - area[3] :
  153. pos == 'l' ? oL = 0 :
  154. pos == 'lt' ? (oL = 0, oT = 0) :
  155. pos == 'rt' ? (oL = area[0] - area[2], oT = 0) :
  156. pos == 'lb' ? (oL = 0, oT = area[1] - area[3]) :
  157. pos == 'rb' ? (oL = area[0] - area[2], oT = area[1] - area[3]) :
  158. null
  159. )
  160.  
  161. vlayero.style.left = parseFloat(isFixed ? oL : domUtils.scroll('left') + oL) + 'px';
  162. vlayero.style.top = parseFloat(isFixed ? oT : domUtils.scroll('top') + oT) + 'px';
  163. }
  164. }
  165.  
  166. // 元素跟随定位
  167. const offset = () => {
  168. let oW, oH, pS
  169. let dom = elRef.value
  170. let vlayero = dom.querySelector('.vlayer__wrap');
  171.  
  172. oW = vlayero.offsetWidth;
  173. oH = vlayero.offsetHeight;
  174. pS = domUtils.getFollowRect(props.follow, oW, oH);
  175. data.tipArrow = pS[2];
  176.  
  177. vlayero.style.left = pS[0] + 'px';
  178. vlayero.style.top = pS[1] + 'px';
  179. }
  180.  
  181. // 最大化弹窗
  182. const full = () => {
  183. // ...
  184. }
  185.  
  186. // 恢复弹窗
  187. const restore = () => {
  188. let dom = elRef.value;
  189. let vlayero = dom.querySelector('.vlayer__wrap');
  190. let otit = dom.querySelector('.vlayer__wrap-tit');
  191. let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
  192. let obtn = dom.querySelector('.vlayer__wrap-btns');
  193. let omax = dom.querySelector('.vlayer__maximize');
  194.  
  195. let t = otit ? otit.offsetHeight : 0
  196. let b = obtn ? obtn.offsetHeight : 0
  197.  
  198. if(!data.vlayerOpts.lockScroll) {
  199. data.vlayerOpts.isBodyOverflow = false;
  200. document.body.style.overflow = '';
  201. }
  202.  
  203. props.maximize && omax.classList.remove('maximized')
  204.  
  205. vlayero.style.left = parseFloat(data.vlayerOpts.rect[0]) + 'px';
  206. vlayero.style.top = parseFloat(data.vlayerOpts.rect[1]) + 'px';
  207. vlayero.style.width = parseFloat(data.vlayerOpts.rect[2]) + 'px';
  208. vlayero.style.height = parseFloat(data.vlayerOpts.rect[3]) + 'px';
  209. }
  210.  
  211. // 拖动|缩放弹窗
  212. const move = () => {
  213. // ...
  214. }
  215.  
  216. // 事件处理
  217. const callback = () => {
  218. // 倒计时关闭
  219. if(props.time) {
  220. $index++
  221. // 防止重复点击
  222. if($timer[$index] !== null) clearTimeout($timer[$index])
  223. $timer[$index] = setTimeout(() => {
  224. close();
  225. }, parseInt(props.time) * 1000)
  226. }
  227. }
  228.  
  229. // 点击最大化按钮
  230. const maximizeClicked = (e) => {
  231. let o = e.target
  232. if(o.classList.contains('maximized')) {
  233. // 恢复
  234. restore();
  235. } else {
  236. // 最大化
  237. full();
  238. }
  239. }
  240. // 点击遮罩层
  241. const shadeClicked = () => {
  242. if(JSON.parse(props.shadeClose)) {
  243. close();
  244. }
  245. }
  246. // 按钮事件
  247. const btnClicked = (e, index) => {
  248. let btn = props.btns[index]
  249. if(!btn.disabled) {
  250. typeof btn.click === 'function' && btn.click(e)
  251. }
  252. }
  253.  
  254. return {
  255. ...toRefs(data),
  256. elRef,
  257. close,
  258. maximizeClicked,
  259. shadeClicked,
  260. btnClicked,
  261. }
  262. }
  263. }
  264. </script>

大家可以看到在vue3中的写法和vue2中不一样了,很多都是在setup中处理,最后return出去,不过之前的vue2 optionsAPI写法仍然可用。

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

当配置 topmost:true 当前活动窗口会保持置顶状态。

Okey,基于Vue3开发自定义弹窗组件就分享到这里。希望对大家有所帮助哈!✍

最后附上一个Nuxt.js实例项目

Nuxt.js+Vue 手机端聊天:https://www.cnblogs.com/xiaoyan2017/p/13823195.html

vue3系列:vue3.0自定义全局弹层V3Layer|vue3.x pc桌面端弹窗组件的更多相关文章

  1. 基于Vue.js PC桌面端弹出框组件|vue自定义弹层组件|vue模态框

    vue.js构建的轻量级PC网页端交互式弹层组件VLayer. 前段时间有分享过一个vue移动端弹窗组件,今天给大家分享一个最近开发的vue pc端弹出层组件. VLayer 一款集Alert.Dia ...

  2. React15.6.0实现Modal弹层组件

    代码地址如下:http://www.demodashi.com/demo/12315.html 注:本文Demo环境使用的是我平时开发用的配置:这里是地址. 本文适合对象 了解React. 使用过we ...

  3. vue3系列:vue3.0自定义虚拟滚动条V3Scroll|vue3模拟滚动条组件

    基于Vue3.0构建PC桌面端自定义美化滚动条组件V3Scroll. 前段时间有分享一个Vue3 PC网页端弹窗组件,今天带来最新开发的Vue3.0版虚拟滚动条组件. V3Scroll 使用vue3. ...

  4. svelte组件:svelte3自定义桌面PC端对话框组件svelte-layer

    基于Svelte3.x开发pc网页版自定义弹窗组件svelteLayer. svelte-layer:基于svelte.js轻量级多功能pc桌面端对话框组件.支持多种弹窗类型.30+参数随意组合配置, ...

  5. 弹层组件-layer

    layer是Layui的一个弹层组建,功能强大,总之我很喜欢,下面介绍这个组件的基本用法. 首先如果只需要使用layer而不想使用Layui可以单独下载layer组件包,页面引入jquery1.8以上 ...

  6. javascript右下角弹层及自动隐藏

    在编写项目中总会需要有个右下角弹层提示公告的需求,怎么用更简单方面,更简洁代码,实现更好用户体验这个就是我们的所要做的内容.市场这块弹层很多,但功能不尽如人意.下面分享早些时候自己编写,以及现在还在应 ...

  7. Vue.js 桌面端自定义滚动条组件|vue美化滚动条VScroll

    基于vue.js开发的小巧PC端自定义滚动条组件VScroll. 前段时间有给大家分享一个vue桌面端弹框组件,今天再分享最近开发的一个vue pc端自定义滚动条组件. vscroll 一款基于vue ...

  8. vue3系列:vue3.0自定义弹框组件V3Popup|vue3.x手机端弹框组件

    基于Vue3.0开发的轻量级手机端弹框组件V3Popup. 之前有分享一个vue2.x移动端弹框组件,今天给大家带来的是Vue3实现自定义弹框组件. V3Popup 基于vue3.x实现的移动端弹出框 ...

  9. vue3.0自定义指令(drectives)

    在大多数情况下,你都可以操作数据来修改视图,或者反之.但是还是避免不了偶尔要操作原生 DOM,这时候,你就能用到自定义指令. 举个例子,你想让页面的文本框自动聚焦,在没有学习自定义指令的时候,我们可能 ...

随机推荐

  1. Python音视频开发:消除抖音短视频Logo和去电视台标的实现详解

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 对于带Logo(如抖音Logo.电视台标)的视频,有三种方案进行Logo消除: 直接将对应区域用对应图像替换: 直接将对应区域模糊化: 通过变换将要 ...

  2. Python中使用百分号占位符的字符串格式化方法中%s和%r的输出内容有何不同?

    Python中使用百分号占位符的字符串格式化方法中%s和%r表示需要显示的数据对应变量x会以str(x)还是repr(x)输出内容展示. 关于str和repr的关系请见: <Python中rep ...

  3. PyQt(Python+Qt)学习随笔:QAbstractItemView的editTriggers属性以及平台编辑键(platform edit key )

    老猿Python博文目录 老猿Python博客地址 editTriggers属性 editTriggers属性用于确认哪些用户操作行为会触发ItemView中的数据项进入编辑模式. 此属性是由枚举类E ...

  4. django 自定义存储上传文件的文件名

    一.需求: Django实现自定义文件名存储文件 使文件名看起来统一 避免收到中文文件导致传输.存储等问题 相同的文件也需要使用不同的文件名 二.实现思路: 思路: 生成14位随机字母加数字.后10位 ...

  5. [BJDCTF 2nd]xss之光

    [BJDCTF 2nd]xss之光 进入网址之后发现存在.git泄露,将源码下载下来,只有index.php文件 <?php $a = $_GET['yds_is_so_beautiful']; ...

  6. gulp-sourcemaps的用法

    1.项目文件夹中,安装gulp-sourcemaps插件 npm install --save gulp-sourcemaps 2.gulpfile.js文件,导入要用到的插件. 如: // 引入gu ...

  7. asp.net在线人数限制

    1.网站启动初始化在线人数变量 Application["WebsiteCount"] = 0; 2.新的会话进来 只有在全新的会话进来的时候,该方法才会执行.可以过滤掉某些不需要 ...

  8. 自顶向下redis4.0(1)启动

    redis4.0的启动流程 目录 redis4.0的启动流程 简介 正文 全局server对象 初始化配置 初始化服务器 事件主循环 参考文献 简介 redis 在接收客户端连接之前,大概做了以下几件 ...

  9. Tokyo 五年 IT 生活

    今天阳光甚好,在家中小屋,闲来无事,回顾一下这五年的历程.我想从来东京的缘由.东京的环境.生活.IT这四个方面介绍一下. 首先,说一下为什么我会来到东京. 电子信息专业毕业,大学实验室学习IT,毕业后 ...

  10. ubuntu 设置apple主题

    ubuntu 设置apple主题 参考地址,主要是看这个,很详细 https://linuxhint.com/gnome-tweak-tool-ubuntu-17-10/ 效果图 终端命令 $ sud ...