命令式弹窗
TypeScript
import type { DialogProps } from 'element-plus'
import type { App, Component } from 'vue'
import { ElButton, ElConfigProvider, ElDialog } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { createApp, h, ref } from 'vue'
let parentApp: App
export function installRenderDialog(app: App) {
parentApp = app
}
/**
* 继承上层 app 的配置
* @param app
*/
function extendsApp(app: App) {
if (parentApp) {
const _app = app._context.app
Object.assign(app._context, parentApp._context)
app._context.app = _app
}
}
type ComponentProps<T> = T extends Component<infer P> ? P & Record<string, any> : Record<string, any>
const noop: (...args: any[]) => void = () => {}
interface ModalProps extends Partial<DialogProps> {
footerAlign?: 'left' | 'center' | 'right'
onClosed?: () => void
}
/**
* 命令式弹框
* @param component
* @param props
* @param modalProps
*/
export function renderDialog<T extends Component>(
component: T,
props: ComponentProps<T> = {} as ComponentProps<T>,
modalProps: ModalProps = {},
) {
const modelValue = ref(true)
const instance = ref()
const loading = ref(false)
// 保存 resolve 和 reject 方法
let resolve = noop
let reject = noop
// 如果用户调用过 catch,则调用 reject,因为可能用户在 catch 中处理了一些逻辑
let callReject = false
/**
* 弹框关闭时调用,默认会在用户调用过 catch 后调用 reject 方法
* 如果用户点击确认,提交成功后,handler = resolve 方法,调用时会执行 resolve 方法
*/
let handler = () => {
if (callReject)
reject()
}
/**
* 点击确认时调用
*/
function handleOk() {
/**
* 调用子组件 的 submit 方法,支持返回 Promise
*/
const res = instance.value?.submit?.()
if (res instanceof Promise) {
/**
* 如果是 Promise,等待 Promise 结果
*/
loading.value = true
res.then(() => {
// 提交成功,关闭弹框
close()
handler = resolve
}).finally(() => {
// 关闭 loading
loading.value = false
})
}
else {
/**
* 不是 Promise,直接关闭弹框
*/
close()
handler = resolve
}
}
/**
* 函数式组件
* ElConfigProvider 国际化中文
* ElDialog 弹框组件
* default 插槽渲染传入的组件
* footer 插槽渲染确认和取消按钮
*/
const dialog = () => h(ElConfigProvider, { locale: zhCn }, () => h(ElDialog, {
...modalProps,
modelValue: modelValue.value,
onClosed() {
// 弹框关闭后调用 handler 方法,处理 Promise 状态
handler()
// 调用用户传入的 onClosed 方法
modalProps.onClosed?.()
// 卸载 app
unmount()
},
}, {
default: () => h(component, { ...props, ref: instance }),
footer: () => h('div', {
style: { textAlign: modalProps.footerAlign || 'right' },
}, [
h(ElButton, { type: 'primary', loading: loading.value, onClick: handleOk }, () => '确认'),
h(ElButton, {
onClick: close,
}, () => '取消'),
]),
}))
/**
* 创建容器用于挂载弹框
*/
const container = document.createElement('div')
document.body.appendChild(container)
/**
* 创建 app
*/
const app = createApp(dialog)
/**
* 继承上层 app 的配置
*/
extendsApp(app)
app.mount(container)
/**
* 卸载 App
*/
function unmount() {
app.unmount()
document.body.removeChild(container)
}
/**
* 调用 close 方法会关闭弹框
*/
function close() {
modelValue.value = false
}
const promise = new Promise<true>((_resolve, _reject) => {
resolve = _resolve.bind(null, true)
reject = _reject
})
close.finally = promise.finally.bind(promise)
close.then = promise.then.bind(promise)
close.catch = (_onRejected) => {
callReject = true
return promise.catch(_onRejected)
}
/**
* close 是一个函数,同时也是一个 PromiseLike,意味着它可以被当做函数调用
* 也可以被当做 Promise 使用,注意这个是一个 PromiseLike,并非 ES6 的 Promise,不过它也可以被 await
*/
return close as typeof promise & (() => void)
}