vue3方法调用打开自定义弹窗

246 阅读2分钟

核心思路

主要借鉴了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)
}


组件模板

rejectresolve回调实际上根据业务而定不一定要传递的,但是发送一个销毁事件是必需的,要不然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) // 拒绝的原因是我是一条备注
}

image.png

对于任意弹窗的调用,只要符合组件模板的传参,只需

const onSubmit = async(id: string) => {
  const form = await srmBoxFactory<YourReturnType>(YourComponent, {
    id,
    // 自定义的prop传参
  })
}

更复杂的交互,可以通过发射一些事件来完成,例如

image.png

image.png

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: '资质问卷全部内容' }
    ]
  })
}

这样可以省略保存一些中间状态, 只依赖函数作用域传参就行了。