vue弹窗补坑帖
摘要
先上效果
调用方式,这里我着重处理了确定时的非空验证及请求返回关闭的坑,一个表单的弹窗就这样轻松搞定,这里只是一个便捷的弹窗调用,结合我之前进行了两次尝试,最终我在formilyjs中找到了想要的答案
注意jsx写法上的差异, 如果你使用了El-Form的mode属性,在jsx扩展中默认会把v-mode写法与属性mode写法搞成一样的,区别请参考
效果图
回顾
最开始的封装,当然这块在去年经受住了考验,如果你看过去年发布的内容,自动化生成结合此项,给我带来了极大的便利
但Page的curd本来就是为了快速开发而来,弹窗这块的处理因为进度的原因没有深究,只是做了简单封装,勉强够用,后来觉得有点儿随意又尝试进行了 Promise 形式的封装,对JQ之前的$.Deferred的实现进行了一次探索,觉得思路挺新奇,但针对表单验证不通过的情况,没有进行后续的补充处理,之前实在太忙,一直没有时间去补这里的坑,因此这两天趁着时间把之前的留坑补充完善。
function generateDeferredPromise() {
return (() => {
let resolve;
let reject;
let p = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise: p,
reject,
resolve
};
})();
}
之前主要用Promise的延迟处理去进行实现的,但如果验证不通过,此项延迟无法再生成新的,本质就是没有增加队列,处理的有点儿潦草
因此给予一些封装完善的补充。
主体内容
关键的实现代码如下
import {
applyMiddleware
} from './comm'
import { Loading } from 'element-ui'
import Vue, { Component, VNode, h } from 'vue'
import "./index.css"
/**
* 前置判断逻辑
*/
const isType =(type) =>
(obj) =>getType(obj) === `[object ${type}]`
export const isFn = (val)=> typeof val === 'function'
export const isArr = Array.isArray
export const isPlainObj = isType('Object')
export const isStr = isType('String')
export const isBool = isType('Boolean')
export const isNum = isType('Number')
export const stylePrefix = 'ProDrawer-element';
export const loading = async (
loadingText = 'Loading...',
processor= () => {}
) => {
let loadingInstance = null
let loading = setTimeout(() => {
loadingInstance = Loading.service({
text: loadingText,
background: 'transparent',
})
}, 100)
try {
return await processor()
} finally {
loadingInstance?.close()
clearTimeout(loading)
}
}
export function isVnode(element) {
return (
element &&
typeof element === 'object' &&
'componentOptions' in element &&
'context' in element &&
element.tag !== undefined
)
}
export function isVueOptions(options) {
return (
options &&
(typeof options.template === 'string' ||
typeof options.render === 'function')
)
}
export function isValidElement(element) {
return (
isVueOptions(element) ||
(element &&
typeof element === 'object' &&
'componentOptions' in element &&
'context' in element &&
element.tag !== undefined)
) // remove text node
}
export const resolveComponent = (
child,
props
) => {
if (child) {
if (typeof child === 'string' || typeof child === 'number') {
return child
} else if (typeof child === 'function') {
return (child)(props)
} else if (isVnode(child)) {
return child
} else {
return h(toRaw(child), { props })
}
}
return null
}
export function FormDrawer(
title,
content
){
const prefixCls = `${stylePrefix}-form-drawer`
const env = {
root: document.createElement('div'),
form: null,
promise: null,
instance: null,
openMiddlewares: [],
confirmMiddlewares: [],
cancelMiddlewares: [],
}
document.body.appendChild(env.root)
const props = {title:title};
const drawerProps = {
...props,
onClosed: () => {
props.onClosed?.()
env.instance.$destroy()
env.instance = null
env.root?.parentNode?.removeChild(env.root)
env.root = undefined
},
}
const render = (visible = true, resolve, reject) => {
if (!env.instance) {
const ComponentConstructor = Vue.extend({
props: ['drawerProps'],
data() {
return {
visible: false,
}
},
render() {
const {
onClose,
onClosed,
onOpen,
onOpend,
onOK,
onCancel,
title,
footer,
okText,
cancelText,
okButtonProps,
cancelButtonProps,
...drawerProps
} = this.drawerProps
return (<el-drawer
{...this.drawerProps}
{...{ on: {
'update:visible': (val) => {
this.visible = val
},
close: () => {
onClose?.()
},
closed: () => {
onClosed?.()
},
open: () => {
onOpen?.()
},
opend: () => {
onOpend?.()
},
},}}
title={title}
class={prefixCls}
size="80%"
// modal="modal"
visible={this.visible}
>
{resolveComponent(content,{ref:"form"})}
{/* {h(content, { form: env.form}, {}) } */}
<div class="drawer__footer">
<el-button type="primary" icon="el-icon-circle-check" on-click={ (e) => {
onOK?.(e)
resolve()
}}>
确 定
</el-button>
<el-button icon="el-icon-circle-close" on-click={(e) => {
onCancel?.(e)
reject()
}}>
取 消
</el-button>
</div>
</el-drawer>);
},
})
env.instance = new ComponentConstructor({
propsData: {
drawerProps,
},
//parent: getProtalContext(id as string | symbol),
})
env.instance.$mount(env.root)
}
env.instance.visible = visible
}
const formDrawer = {
forOpen: (middleware) => {
if (isFn(middleware)) {
env.openMiddlewares.push(middleware)
}
return formDrawer
},
forConfirm: (middleware) => {
if (isFn(middleware)) {
env.confirmMiddlewares.push(middleware)
}
return formDrawer
},
forCancel: (middleware) => {
if (isFn(middleware)) {
env.cancelMiddlewares.push(middleware)
}
return formDrawer
},
open: (props) => {
if (env.promise) return env.promise
env.promise = new Promise(async (resolve, reject) => {
try {
props = await loading(drawerProps.loadingText, () =>
applyMiddleware(props, env.openMiddlewares)
)
} catch (e) {
reject(e)
}
render(
true,
async () => {
//回调逻辑
const callbck=async(val)=>{
const rec= await applyMiddleware(env.form, env.confirmMiddlewares)
if(rec!=false){
resolve(val)
if (drawerProps.beforeClose) {
setTimeout(() => {
drawerProps.beforeClose(() => {
formDrawer.close()
})
})
} else {
formDrawer.close()
}
}
return true;
}
//检索路径下是否有form表单
var form= env.instance.$children[0].$slots.default.find(x=>x.componentOptions&&x.componentOptions.tag=="ProForm");
if(form){
env.form = form.componentInstance
}
if(env.form){
env.form.validate(async (valid) => {
if(valid){
const res= await callbck(env.form.model)
return res;
}else{
//验证不通过
return false;
}
})
// .catch(() => {})
}else{
//没有检索到form时,正常处理
callbck()
}
},
async () => {
await loading(drawerProps.loadingText, () =>
applyMiddleware(env.form, env.cancelMiddlewares)
)
if (drawerProps.beforeClose) {
drawerProps.beforeClose(() => {
formDrawer.close()
})
} else {
formDrawer.close()
}
}
)
})
return env.promise
},
close: () => {
if (!env.root) return
render(false)
},
}
return formDrawer
}
export default FormDrawer
此处先重点说一下回调部分,处理了表单验证非空的处理,因为Form表单的封装本身又一个validate回调验证,而我们使用的弹窗又存在了表单和非表单两种情况,因此在此处做了重点的区分,若找到表单,则执行相关的表单验证,若没有,则正常进行逻辑处理,之所以有此项是因为去年在实际使用过程中,经常遇到非表单的弹窗情况,封装的略显尴尬、
因为本身我这边用的是自己封装的ProForm,所以找的是该组件,如果是自用可改为查找Form
最好再重点说一下此项Promise实现的机制
可以看到,这里的回调事件根Emitter那种事件机制差不多,通过点击追加Promise处理的方式,解决了之前回调处理的坑。
总结
之前留的没处理完的内容终于补齐了,算是有了初步的交代,因为之前的贴浏览量还行,怕影响判断,因此在此处不全。
转载自:https://juejin.cn/post/7230810820267540517