核心思路
主要借鉴了messageBox
的创建思路,github链接。这里将它简化,
当调用它时创建一个vnode把自定义的弹窗组件挂载到body下,弹窗按钮分别调用props的promise回调函数(messageBox是发射事件),关闭时发射一个vanish
事件把它清除。
import { Component, createVNode, render } from 'vue'
import DialogRemark from './dialog-remark.vue'
const genContainer = () => {
return document.createElement('div')
}
const initInstance = (Constructor: Component, props: Record<string, any>, container) => {
const vnode = createVNode(Constructor, props)
render(vnode, container)
if (!container.firstElementChild) return
document.body.appendChild(container.firstElementChild)
}
/**
*
* @param Constructor 组件实例
* @param props 组件props传参
* @returns
*/
export const srmBoxFactory = <T>(Constructor: Component, props?: Record<string, any>) => {
const container = genContainer()
return new Promise<T>((resolve, reject) => {
initInstance(
Constructor,
{
...props,
resolve,
reject,
onVanish: () => {
render(null, container)
}
},
container
)
})
}
/**
*
* @returns 备注内容
*/
export const remarkBox = async () => {
return await srmBoxFactory<string>(DialogRemark)
}
组件模板
reject
与resolve
回调实际上根据业务而定不一定要传递的,但是发送一个销毁事件是必需的,要不然body下面每调用一次就会生成一个实例,一般在关闭动画之后发送是过渡是比较自然的。
<template>
<el-dialog v-model="visible" title="添加备注信息" :close-on-click-modal="false" @close="onClose" width="450px" @closed="$emit('vanish')">
<div class="p-5 pt-0">
<el-input
v-model="inputs"
type="textarea"
show-word-limit
placeholder="请输入拒绝原因(非必填)"
:maxlength="500"
:rows="10"
></el-input>
<span class="flex justify-end gap-2 mt-6">
<el-button @click="onCancle">取消</el-button>
<el-button type="primary" @click="onOk">确定</el-button>
</span>
</div>
</el-dialog>
</template>
<script setup lang="ts">
interface Props {
resolve?: (e: string) => void
reject?: () => void
}
const props = defineProps<Props>()
defineEmits(['vanish'])
const visible = ref(true)
const inputs = ref('')
const onClose = () => {
inputs.value = ''
}
const onOk = () => {
visible.value = false
props.resolve?.(inputs.value)
}
const onCancle = () => {
visible.value = false
props.reject?.()
}
</script>
业务调用
const onReject = async (row) => {
const cancleReason = await remarkBox()
console.log('拒绝的原因是'+cancleReason) // 拒绝的原因是我是一条备注
}
对于任意弹窗的调用,只要符合组件模板的传参,只需
const onSubmit = async(id: string) => {
const form = await srmBoxFactory<YourReturnType>(YourComponent, {
id,
// 自定义的prop传参
})
}
更复杂的交互,可以通过发射一些事件来完成,例如
export const onExport = async (query) => {
const { handleExport } = useExport(authenticationExport)
await srmBoxFactory<Record<string, any>>(DialogExport, {
// emit('ok', {requiredFields, type})
onOk: ({requiredFields, type}) => {
handleExport(type, { requiredFields: requiredFields['PROVIDER_AUTHENTICATION_LIST_HEADER'], ...query }, '供应商资质认证')
},
headers: [
{ value: 'PROVIDER_AUTHENTICATION_LIST_HEADER', text: '汇总信息' }
]
})
}
export const onExportD = async (row: ListItem) => {
const { handleExport } = useExport(authenticationDExport)
await srmBoxFactory<Record<string, any>>(DialogExport, {
// emit('ok', {requiredFields, type})
onOk: ({requiredFields, type}) => {
handleExport(type, { requiredFields, authenticationSn: row.authenticationSn }, row.authenticationSn)
},
headers: [
{ value: 'PROVIDER_AUTHENTICATION_INFO_HEADER', text: '汇总信息' },
{ value: 'BASE_QUESTIONNAIRE_ANSWER_INFO_HEADER', text: '基本问卷全部内容' },
{ value: 'ZZ_QUESTIONNAIRE_ANSWER_INFO_HEADER', text: '资质问卷全部内容' }
]
})
}
这样可以省略保存一些中间状态, 只依赖函数作用域传参就行了。