Vue自定义render统一项目组弹框功能

一、本文收获

pick

二、为什么要统一封装弹框;

要封装成怎样

通过举例常规弹框的写法。我们可以体会到,通常要弹出一个页面,需要创建一个页面 normalDialog.vue 包裹 dialogBody.vue (弹框主体);需要 parent.vue 设置flag控制弹框显示隐藏, normalDialog.vue 关闭的时候设置 parent.vue 对应 flag 。缺点: 流程繁杂、配置繁琐、不灵活、样式不统一和参数传递麻烦等 。如果一个项目弹框较多的时候,弊端将会更明显,大量的 isXxxDialogShow ,大量的 vue 文件。因此项目组急需一个能简单配置就能弹出弹框的 API

1. 常规弹框写法 dialoBody.vue (弹框主体) ,此处采用 Composition API 的写法。只做了简单的页面,包含校验,抽取保存数据的常规逻辑。

<template>
 <div class="dialog-body">
 <div class="item">
 <div>名称</div>
 <el-input v-model="name"></el-input>
 </div>
 <div class="item">
 <el-radio-group v-model="attention">
 <el-radio label="已关注"></el-radio>
 <el-radio label="等下关注"></el-radio>
 </el-radio-group>
 </div>
 <div class="item">
 <el-radio-group v-model="like">
 <el-radio label="已点赞"></el-radio>
 <el-radio label="等下点赞"></el-radio>
 </el-radio-group>
 </div>
 </div>
</template>

<script>
import { reactive, toRefs } from '@vue/composition-api'
import pick from 'lodash/pick'
import { Message } from 'element-ui'

export default {
 props: {
 defaultName: String,
 },
 setup(props, ctx) {
 const ATTENTIONED = '已关注'
 const LIKED = '已点赞'
 const state = reactive({
 name: props.defaultName, // 名称
 attention: '已关注', // 关注
 like: '已点赞', // 点赞
 })
 /*************************************************************
 * 页面绑定的事件
 * 建议写法:
 * 1. 定义methods常量
 * 2. 处理相关业务逻辑的时候,需要绑定事件到页面的时
 * 建议通过methods.onXxx = ()=>{ // 相关逻辑 }的形式定义
 * 好处1: onXxx定义的位置和相关业务逻辑代码关联一起
 * 好处2: 可以统一通过...methods的形式在setup统一解构
 * 好处3: 当页面逻辑复杂,需要操作的数据关联性强,不可拆解组件;
 *  可将相关业务的代码在独立模块定义;
 *  独立模块暴露API handleXxx(methods,state),流水线加工methods;
 *  和Vue2源码一样,流水线加工的思想.
 */
 const methods = {}
 // 校验名称
 methods.onNameBlur = () => {}

 // ************************ 向外暴露的API ************************
 const apiMethods = {
 // 保存前校验
 isCanSave() {
 if (state.attention !== ATTENTIONED || state.like !== LIKED) {
  Message.error('未关注或者点赞,不能关闭,嘻嘻')
  return false
 }
 return true
 },
 // 获取保存数据
 getSaveData() {
 // ******* lodash pick 从对象中抽取数据
 return pick(state, ['name', 'attention', 'like'])
 },
 }
 return {
 ...toRefs(state),
 ...methods,
 apiMethods,
 }
 },
}
</script>

<style lang="less">
.dialog-body {
 width: 100%;
 height: 100px;
}
</style>

2.normalDialog.vue 包裹弹框主体 dialoBody.vue

<template>
 <el-dialog
 title="帅哥,美女,我是标题"
 :visible.sync="isShow"
 width="30%"
 :before-close="onClose"
 >
 <dialog-body default-name="参数传递的名称" ref="inner"></dialog-body>
 <span slot="footer" class="dialog-footer">
 <el-button @click="onClose">取 消</el-button>
 <el-button type="primary" @click="onOK">确 定</el-button>
 </span>
 </el-dialog>
</template>

<script>
import dialogBody from './dialogBody.vue'
export default {
 components: {
 dialogBody,
 },
 data() {
 return {
 isShow: true,
 }
 },
 methods: {
 onClose() {
 // *********** 修改parent.vue ********
 this.$parent.isNormalDialogShow = false
 },
 // ******* 控制保存流程 ********
 onOK() {
 const inner = this.$refs.inner
 // 校验是否可以保存
 if (inner.apiMethods.isCanSave()) {
 // 获取保存数据
 const postData = inner.apiMethods.getSaveData()
 console.log('>>>>> postData >>>>>', postData)
 // 保存成功后关闭弹框
 this.onClose()
 }
 },
 },
}
</script>

parent.vue

// html 部分
<normal-dialog v-if="isNormalDialogShow" />

// Js部分
data(){
	isNormalDialogShow:false
}
methods:{
 onDialogShow(){ // ******控制弹框显示*****
 this.isNormalDialogShow = true
 }
}

2. 要封装成怎样

2.1 API诉求:

isXxxDialogShow
el-dialog

2.2 理想API:

import dialogBody from './dialogBody.vue'
const dialog = new JSDialog({
 comonent: dialogBody,
 dialogOpts: { // 可扩展配置
 title: 'JSDialog设置的弹框标题',
 width: '400px'
 },
 props: {
 defaultName: 'JSDialog传递的参数',
 },
 onOK() {
 const inner = dialog.getInner() // 能取到dialogBody的引用
 // 控制流程
 if (inner.apiMethods.isCanSave()) {
 // 获取保存数据
 const postData = inner.apiMethods.getSaveData()
 console.log('>>>>> postData >>>>>', postData)
 // 关闭弹框
 dialog.close()
 }
 },
 onCancel() {
 dialog.close() // 弹框关闭
 },
})
dialog.show() // 弹框显示

三、如何封装

动态控制显示内容,脑海浮现的三个方案: 卡槽、动态组件和重写 render 。下面在动态弹框场景下简单对比三个方案。

  • slot(卡槽) ,和 el-dialog 原理类似,只是再封装了一层,少定义了 normalDialog.vue 文件。 缺点:调用复杂,不灵活;不容易控制关闭的流程;只能在 template 中定义 。
  • component(动态组件) ,创建 commonDialog.vue ,统一挂在 App.vue 下,利用 <component :is="componentId"></component> 动态切换弹框主体, commonDialog.vue 监听 componentId 变化来切换弹框主体。 缺点:要提前将所有弹框主体组件注册到commonDialog.vue页面的components上;依赖于vuex,侵入性较强;纯js文件通过vuex弹出弹框相对复杂,不灵活 。
  • 重写 render , render 是 Vue 对造轮子开发者开放的后门。动态弹框可作为独立的功能模块,内部通过new Vue ,重写 render 控制渲染内容。 独立 Vue 实例,可预先创建,可在任何位置控制弹框,灵活,清晰 。 缺点:暂无

1. 整体代码

先整体预览一下代码,下面再细分讲解。

import Vue from 'vue'
import merge from 'lodash/merge'
import orderBy from 'lodash/orderBy'

// 按钮配置项构造器
function btnBuilder(options) {
 const defaultBtn = {
 text: '按钮', // 显示文本
 clickFn: null, // 点击回调
 type: 'default', // 样式
 isHide: false, // 是否隐藏
 order: 2 // 顺序
 }
 return { ...defaultBtn, ...options }
}

export default class JSDialog {
 constructor(originOptions) {
 this.options = {}
 this.vm = null
 this._mergeOptions(originOptions)
 this._initVm()
 }
 // 参数合并
 _mergeOptions(originOptions) {
 const defaultOptions = {
 component: '', // 弹框主体vue页面
 // 可扩展el-dialog官方api所有配置项,小驼峰aaaBbbCcc
 dialogOpts: {
 width: '40%',
 title: '默认标题'
 },
 // 传入弹框主体vue组件的参数
 props: {},
 // 点击确定回调
 onOK: () => {
 console.log('JSDialog default OK'), this.close()
 },
 // 点击取消回调
 onCancel: () => {
 console.log('JSDialog default cancel'), this.close()
 },
 footer: {
 ok: btnBuilder({
  text: '确定',
  type: 'primary',
  order: 0
 }),
 cancel: btnBuilder({
  text: '取消',
  order: 1
 })
 }
 }
 // 参数合并到this.options
 merge(this.options, defaultOptions, originOptions)
 const footer = this.options.footer
 Object.entries(footer).forEach(([key, btnOptions]) => {
 // 确定和取消默认按钮
 if (['ok', 'cancel'].includes(key)) {
 const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel
 // 默认按钮回调优先级: footer配置的clickFn > options配置的onOK和onCancel
 btnOptions.clickFn = btnOptions.clickFn || clickFn
 } else {
 // 新增按钮
 // 完善配置
 footer[key] = btnBuilder(btnOptions)
 }
 })
 }
 _initVm() {
 const options = this.options
 const beforeClose = this.options.footer.cancel.clickFn // 弹框右上角关闭按钮回调
 this.vm = new Vue({
 data() {
 return {
  // 需要响应式的数据
  footer: options.footer, // 底部按钮
  visible: false // 弹框显示及关闭
 }
 },
 methods: {
 show() {
  // 弹框显示
  this.visible = true
 },
 close() {
  // 弹框关闭
  this.visible = false
 },
 clearVm() {
  // 清除vm实例
  this.$destroy()
 }
 },
 mounted() {
 // 挂载到body上
 document.body.appendChild(this.$el)
 },
 destroyed() {
 // 从body上移除
 document.body.removeChild(this.$el)
 },
 render(createElement) {
 // 弹框主体
 const inner = createElement(options.component, {
  props: options.props, // 传递参数
  ref: 'inner' // 引用
 })
 // 控制按钮显示隐藏
 const showBtns = Object.values(this.footer).filter(btn => !btn.isHide)
 // 控制按钮顺序
 const sortBtns = orderBy(showBtns, ['order'], ['desc'])
 // 底部按钮 jsx 写法
 const footer = (
  <div slot="footer">
  {sortBtns.map(btn => (
  <el-button type={btn.type} onClick={btn.clickFn}>
  {btn.text}
  </el-button>
  ))}
  </div>
 )
 // 弹框主体
 const elDialog = createElement(
  'el-dialog',
  {
  // el-dialog 配置项
  props: {
  ...options.dialogOpts,
  visible: this.visible,
  beforeClose
  },
  // **** 看这里,visible置为false后,el-dialog销毁后回调 *****
  on: {
  closed: this.clearVm
  },
  ref: 'elDialog'
  },
  // 弹框内容:弹框主体和按钮
  [inner, footer]
 )
 return elDialog
 }
 }).$mount()
 }
 // 封装API
 // 关闭弹框
 close() {
 this.vm.close()
 }
 // 显示弹框
 show() {
 this.vm.show()
 }
 // 获取弹框主体实例,可访问实例上的方法
 getInner() {
 return this.vm.$refs.inner
 }
}

2. 参数合并

​ 要做到 API 诉求中的:调用简单、传参简便和可扩展控制弹框样式。参数合并便是 成本最小 的实现方案,配合 TS 效果更佳。定义默认参数,通过 lodash 的 merge ,合并深层属性。通过参数合并还能做到自定义 footer 按钮,控制文本,样式,顺序和执行回调。

// 参数合并
_mergeOptions(originOptions) {
 const defaultOptions = {
 component: '', // 弹框主体vue页面
 // 可扩展el-dialog官方api所有配置项,小驼峰aaaBbbCcc
 dialogOpts: {
 width: '40%',
 title: '默认标题'
 },
 // 传入弹框主体vue组件的参数
 props: {},
 // 点击确定回调
 onOK: () => {
 console.log('JSDialog default OK'), this.close()
 },
 // 点击取消回调
 onCancel: () => {
 console.log('JSDialog default cancel'), this.close()
 },
 footer: {
 ok: btnBuilder({
 text: '确定',
 type: 'primary',
 order: 0
 }),
 cancel: btnBuilder({
 text: '取消',
 order: 1
 })
 }
 }
 // 参数合并到this.options
 merge(this.options, defaultOptions, originOptions)
 const footer = this.options.footer
 Object.entries(footer).forEach(([key, btnOptions]) => {
 // 确定和取消默认按钮
 if (['ok', 'cancel'].includes(key)) {
 const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel
 // 默认按钮回调优先级: footer配置的clickFn > options配置的onOK和onCancel
 btnOptions.clickFn = btnOptions.clickFn || clickFn
 } else { // 新增按钮
 // 完善配置
 footer[key] = btnBuilder(btnOptions)
 }
 })
}

3. render函数

​ 摘取一段 渲染函数 & JSX 官方文档关于 render 的描述: Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用 渲染函数 ,它比模板更接近编译器。 ​ ​ 官方文档对渲染函数的写法,参数,对应JSX写法介绍已经很详细,这里就不再赘述。下面代码是在最新vue-cli创建项目上运行的,尝试了JS参数创建元素和JSX创建元素两种写法。

render(createElement) {
 // 弹框主体
 const inner = createElement(options.component, {
 props: options.props, // 传递参数
 ref: 'inner' // 引用
 })
 // 控制按钮显示隐藏
 const showBtns = Object.values(this.footer).filter(btn => !btn.isHide)
 // 控制按钮顺序
 const sortBtns = orderBy(showBtns, ['order'], ['desc'])
 // 底部按钮 jsx 写法
 const footer = (
 <div slot="footer">
 {sortBtns.map(btn => (
 <el-button type={btn.type} onClick={btn.clickFn}>
  {btn.text}
 </el-button>
 ))}
 </div>
 )
 // 弹框主体
 const elDialog = createElement(
 'el-dialog',
 {
 // el-dialog 配置项
 props: {
 ...options.dialogOpts,
 visible: this.visible
 },
 on: {
 closed: this.clearVm
 },
 ref: 'elDialog'
 },
 // 弹框内容:弹框主体和按钮
 [inner, footer]
 )
 return elDialog
}

4. 封装API

​ 暂时只封装了三个 API ,可根据不同的场景扩展 API ,比如弹框不销毁隐藏,弹框刷新等。

show() ,弹框显示

显示主要是修改 el-dialog 的 visible 为 true ,控制挂载到 body 上的弹框显示。

show() {
 this.vm.show()
}

close() ,弹框关闭

关闭处理流程:修改 el-dialog 的 visible 为 false ;触发 el-dialog 的 closed 事件;执行 clearVm ;执行 vm 的 $destroy() ; destroyed() 回调中将 $el 从 body 中移除。

close() {
 this.vm.close()
}

getInner() ,获取弹框主体实例,可用于访问实例上的方法,控制按钮流程

getInner() {
 return this.vm.$refs.inner
}

四、如何使用

1. 最简单场景,只配置页面

按钮事件回调采用默认的回调,确定和取消按钮都可关闭弹框

import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
 component: dialogBody,
})
dialog.show() // 弹框显示

效果如下:

2. 控制弹框样式及确定流程

可自定义el-dialog支持的配置项,见 Dialog 对话框 ;比如:title、 customClass 。通过customClass可统一控制项目内弹框的风格;可控制确定取消按钮代码回调。

import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
 component: dialogBody,
 dialogOpts: {
 title: '靓仔,美女欧嗨呦',
 customClass:'js-dialog'
 },
 props: {
 defaultName: 'JSDialog传递的参数'
 },
 onOK() {
 const inner = dialog.getInner() // 能取到dialogBody的引用
 // 控制流程
 if (inner.apiMethods.isCanSave()) {
  // 获取保存数据
  const postData = inner.apiMethods.getSaveData()
  console.log('>>>>> postData >>>>>', postData)
  // 关闭弹框
  dialog.close()
 }
 },
 onCancel() {
 dialog.close() // 弹框关闭
 }
})

效果如下:

3. 自定义footer

自定义按钮可控制执行回调,样式,顺序,显示与隐藏

import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
 component: dialogBody,
 footer: {
 ok: { // 修改默认按钮
  text: '新增'
 },
 cancel: { // 隐藏默认按钮
  isHide: true
 },
 add: { // 新增按钮
  text: '另存为',
  clickFn() {
  dialog.close()
  },
  order: -1 // 控制按钮顺序,order小的显示在右边
 },
 add2: {
  text: '新增按钮2',
  clickFn() {
  dialog.close()
  },
  order: 3
 }
 }
})
dialog.show() // 弹框显示

效果如下:

总结

到此这篇关于Vue自定义render统一项目组弹框功能的文章就介绍到这了,更多相关Vue自定义render项目组弹框内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue多种弹框的弹出形式的示例代码

    1.父组件引入子组件,子组件的加载问题 products.vue引入dlAddProd弹框(dlAddProd.vue),由于<div v-show="visible">,所以在products页面加载时,dlAddProd会被加载.但是el-dialog中的body部分不会被加载(不管有没有加v-if指令):dlAddProd弹框中又引入了dlBlankAdd弹框和dlEditProd弹框,但此时只有dlBlankAdd会被加载,dlEditProd不会被加载(<d

  • vue.js实现只弹一次弹框

    核心代码是 getCookie()部分,控制弹框的显示隐藏则在 created()中. <template> <div v-if="isShow"> <!--最外层背景--> <div class="popup_container"> <!--居中的容器--> <img @click="noPopup" src="delete.png" alt="&q

  • vue.js中toast用法及使用toast弹框的实例代码

    1.首先引入 import { Toast } from 'vant' 写个小列子 绑定一个click事件 2.写事件 在methods写方法 showToast() { this.$toast({ message: "今日签到+3", }) }, 3.效果图如下 一个简单的toast提示成就好了 下面通过实例代码看下vue 中使用 Toast弹框 import { ToastPlugin,ConfirmPlugin,AlertPlugin} from 'vux' Vue.use(To

  • vue教程之toast弹框全局调用示例详解

    本文实例为大家分享了vue toast弹框全局调用示例,供大家参考,具体内容如下 1.首选新建一个toast.vue模板文件: <template> <transition :name="fadeIn"> <div class="alertBox" v-show="show"> <div class="alert-mask" v-show="isShowMask"&

  • vue实现弹框遮罩点击其他区域弹框关闭及v-if与v-show的区别介绍

    vue如何简单的实现弹框,遮罩,点击其他区域关闭弹框, 简单的思路是以一个div作为遮罩, 控制其的v-if(v-show)即可, 掌握到技巧既可以任意扩展. v-if 是直接删除dom节点, 就是这个div就不存在了 v-show 是控制dom的css样式设置为 display: none; 来实现,dom还是存在; 实现如下 maskshow来控制控制遮罩的显示隐藏,绑定一个时间点击遮罩的时候关闭它 <div class="mask" v-show="maskSho

  • vue中简单弹框dialog的实现方法

    效果如下,dialog中内容自行添加 <template> <div> <div class="dialog-wrap"> <div class="dialog-cover" v-if="isShow" @click="closeMyself"></div> <transition name="drop"> <div class=

  • vue+iview写个弹框的示例代码

    iView 是一套基于Vue.js的开源UI组件库,主要服务于PC界面的中后台产品. 1.iView的特性 1) 高质量.功能丰富 2) 友好的API ,自由灵活地使用空间 3) 细致.漂亮的 UI 4) 事无巨细的文档 5) 可自定义主题 2.iView的安装: 1) 使用npm: npm install --save iview 2) CDN引入: <link rel="stylesheet" href="css/iview.css" rel="

  • Vue自定义render统一项目组弹框功能

    一.本文收获 pick 二.为什么要统一封装弹框: 要封装成怎样 通过举例常规弹框的写法.我们可以体会到,通常要弹出一个页面,需要创建一个页面 normalDialog.vue 包裹 dialogBody.vue (弹框主体):需要 parent.vue 设置flag控制弹框显示隐藏, normalDialog.vue 关闭的时候设置 parent.vue 对应 flag .缺点: 流程繁杂.配置繁琐.不灵活.样式不统一和参数传递麻烦等 .如果一个项目弹框较多的时候,弊端将会更明显,大量的 is

  • vue自定义全局组件实现弹框案例

    本文实例为大家分享了vue自定义全局组件实现弹框案例的具体代码,供大家参考,具体内容如下 说明:本案例是封装的一个弹框页面,因为只想要一个遮罩,内容自定义.因为ElementUI上已经提供了一个弹框组件,但觉得elementUI的组件内容有点多,所有自己就封装了全局组件.自己封装的参考了elementUI组件的源码. 主要步骤如下 1.创建一个文件夹my-dialog2.在my-dialog文件夹下创建MyDialog.vue和index.js3.index.js需要引入MyDiloag并封装和

  • Vue项目结合Vue-layer实现弹框式编辑功能(实例代码)

    1. 实现效果 2.实现原理 在父组件中点击编辑按钮,将当前点击对象的id传给子组件,子组件根据id修改相应的内容 父组件中代码: //放置编辑按钮的位置 <button type="button" class="layui-btn layui-btn-normal layui-btn-sm" v-on:click="edit(manage.id)"><i class="layui-icon"><

  • 通过vue.extend实现消息提示弹框的方法记录

    前提回顾 在项目开发中我们经常使用的组件注册分为两种,一个是全局注册和另一个是局部注册,假设我们的业务场景是用户在浏览注册页面时,点击页面中的注册按钮后,前端根据用户的注册信息先做一次简单的验证,并根据验证弹出一个对应消息提示弹框 我们拿到这个需求后,便开始着手准备要通过局部注册消息弹框组件的方法来实现这个场景,在通过局部注册消息弹框组件的方法解决完这个需求后,自然是沾沾自喜,紧接着又迎来了一个需求,该需求是用户在点击该注册按钮时,点击几次就要出现几次这个消息弹框,你开始犯了难,并思考难道我要在

  • Vue自定义指令实现checkbox全选功能的方法

    最近做的一个项目需要用到Vue实现全选功能,参考了一下网上的做法,发现用属性计算的复用性不高,于是选用自定义指令,但网上的做法大多是会对原始数据有一定的格式要求,而且没有返回结果,于是做了改进. 上代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id

  • Android实现圆角弹框功能

    自定义弹窗类--Android 透明圆角弹窗 import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.Window; import android.widget.ImageView; import android.widget.TextVie

  • 原生JS实现自定义下拉单选选择框功能

    浏览器自带的原生下拉框不太美观,而且各个浏览器表现也不一致,UI一般给的下拉框也是和原生的下拉框差别比较大的,这就需要自己写一个基本功能的下拉菜单/下拉选择框了.最近,把项目中用到的下拉框组件重新封装了一下,以构造函数的方式进行封装,主要方法和事件定义在原型上,下面是主要的实现代码并添加了比较详细的注释,分享出来供大家参考.代码用了ES6部分写法如需兼容低版本浏览器请把相关代码转成es5写法,或者直接bable转下. 先放个预览图吧,后面有最终的动态效果图:(样式和交互参考了阿里和Iview U

  • vue 自定义指令自动获取文本框焦点的方法

    HTML: <p><b v-show="show">{{tag}}</b><input v-focus v-model="tag" :hidden="show" type="text"></p> js: 官方例子: directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() } } } 我的

  • vue项目中仿element-ui弹框效果的实例代码

    最近要写个弹框,发现element-ui弹框样式还可以,就copy下来改吧改吧.也不分步骤详细介绍了直接上代码. 在组件目录中新建文件夹message 我把message目录里的东西给大家贴出来 message文件夹 src文件夹 index.js import Message from './src/main.js'; export default Message; mian.js import Vue from 'vue'; import Main from './main.vue'; le

  • Android中 TeaScreenPopupWindow多类型筛选弹框功能的实例代码

    Github地址 YangsBryant/TeaScreenPopupWindow (Github排版比较好,建议进入这里查看详情,如果觉得好,点个star吧!) 引入module allprojects { repositories { google() jcenter() maven { url 'https://www.jitpack.io' } } } implementation 'com.github.YangsBryant:TeaScreenPopupWindow:1.0.2' 主

随机推荐