前言
在开发过程中经常遇到需要打开各种弹窗功能,就比如删除提示框或者密码输入框等其他简单的弹窗。但是在vue引入弹窗需要再做很多工作如引入弹窗组件,控制弹窗显示隐藏等。这里提供了直接用js调用弹窗的方法,如引用ElementPlus的ElMessageBox组件一样,直接调用打开。
具体代码参考
/src/components/simple-dialog/index.ts
import { createApp, type App, type Ref } from 'vue'
import SimpleDialog from '~/components/simple-dialog/index.vue'
export interface SimpleDialogInstance extends App<Element> {
showBtnLoading: () => void
hideBtnLoading: () => void
destroy: () => void
}
interface SimpleDialogItf {
title?: string
width?: string | number
warnIconVisible?: boolean
tips?: string
description?: string
onClose?: () => void
onClosed?: () => void
onCancel?: () => void
onConfirm?: () => any
// 确定事件:开发者自定义设置按钮加载loading和弹窗销毁
onCustomConfirm?: (instance: SimpleDialogInstance) => void
contentRender?: () => any // 自定义弹窗content
footerRender?: () => any // 自定义弹窗footer
}
const simpleDialogInstance = ({
title,
width,
tips,
warnIconVisible,
description,
onClose,
onClosed,
onCancel,
onConfirm,
onCustomConfirm,
contentRender,
footerRender
}: SimpleDialogItf) => {
const wrapNode = document.createElement('div')
const createAppOption = {
modelValue: true,
title,
width,
warnIconVisible,
tips,
description,
contentRender,
footerRender,
onClose: () => {
onClose?.()
},
onClosed: () => {
onClosed?.()
instance.unmount()
document.body.removeChild(wrapNode)
},
onCancel: () => {
onCancel?.()
instance.destroy()
},
onConfirm: async () => {
if (onConfirm) {
instance.showBtnLoading()
const flag = await onConfirm()
instance.hideBtnLoading()
// 如果返回false(Promise.resolve(false)),则不销毁弹窗
if (flag !== false) {
instance.destroy()
}
} else if (onCustomConfirm) {
onCustomConfirm(instance)
}
},
onInit: (dialogRef: Ref<any>, btnLoading: boolean) => {
instance.config.globalProperties.dialogRef = dialogRef
instance.config.globalProperties.btnLoading = btnLoading
}
}
const instance = createApp(SimpleDialog, createAppOption) as SimpleDialogInstance
document.body.appendChild(wrapNode)
instance.mount(wrapNode)
instance.showBtnLoading = () => {
instance.config.globalProperties.btnLoading && (instance.config.globalProperties.btnLoading.value = true)
}
instance.hideBtnLoading = () => {
instance.config.globalProperties.btnLoading && (instance.config.globalProperties.btnLoading.value = false)
}
instance.destroy = () => {
instance.config.globalProperties.dialogRef && (instance.config.globalProperties.dialogRef.value.visible = false)
setTimeout(() => {
instance.unmount()
// document.body.removeChild(wrapNode)
}, 1500)
}
return instance
}
export default simpleDialogInstance
/src/components/simple-dialog/index.vue
<template>
<el-dialog
ref="simpleDialogRef"
class="hn-simple-dialog"
v-model="visible"
:title="title"
:width="width"
:close-on-click-modal="closeOnClickModal"
:close-on-press-escape="closeOnPressEscape"
:draggable="draggable"
:destroy-on-close="true"
@close="emits('close')"
@closed="emits('closed')"
>
<div class="question" v-if="tips">
<svg-icon v-if="warnIconVisible" class="question-icon" name="warning" color="#1472FF" width="16" height="16"></svg-icon>
<span class="question-txt">
{{ tips }}
</span>
</div>
<div class="description" v-if="description">{{ description }}</div>
<ContentRender />
<template #footer>
<el-button v-if="!footerRender" @click="handleCancel">{{ cancelText }}</el-button>
<el-button v-if="!footerRender" type="primary" @click="handleConfirm" :loading="btnLoading">{{ confirmText }}</el-button>
<FooterRender />
</template>
</el-dialog>
</template>
<script lang="ts">
export default {
name: 'simple-dialog'
}
</script>
<script lang="ts" setup>
import { computed, ref, h, onMounted } from 'vue'
interface Props {
title?: string
modelValue: boolean
width?: string | number
draggable?: boolean
closeOnClickModal?: boolean
closeOnPressEscape?: boolean
warnIconVisible?: boolean
tips?: string
description?: string
cancelText?: string
confirmText?: string
contentRender?: Function
footerRender?: Function
}
const props = withDefaults(defineProps<Props>(), {
title: '提示',
width: 380,
draggable: true,
closeOnClickModal: false,
closeOnPressEscape: true,
warnIconVisible: true,
tips: '',
description: '',
cancelText: '取消',
confirmText: '确定'
})
const emits = defineEmits(['update:modelValue', 'close', 'closed', 'cancel', 'confirm', 'init'])
const simpleDialogRef = ref()
const visible = computed({
get () {
return props.modelValue
},
set (value) {
emits('update:modelValue', value)
}
})
type RenderType = 'contentRender' | 'footerRender'
const createRender = (type: RenderType) => {
return {
render: () => {
const methods = props[type]
return methods ? methods(h) : ''
}
}
}
const ContentRender = createRender('contentRender')
const FooterRender = createRender('footerRender')
const btnLoading = ref(false)
const handleCancel = () => {
emits('cancel')
}
const handleConfirm = async () => {
emits('confirm')
}
const visibleChange = (val: boolean) => {
simpleDialogRef.value.visible = val
}
onMounted(() => {
emits('init', simpleDialogRef, btnLoading)
})
defineExpose({
btnLoading,
visibleChange
})
</script>
<style lang="scss">
.hn-simple-dialog {
border-radius: 8px;
overflow: hidden;
}
.hn-simple-dialog .el-dialog__header {
height: 48px;
box-sizing: border-box;
background: #f6f7f9;
margin-right: 0;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 20px;
padding: 0 20px;
}
.hn-simple-dialog .el-dialog__title {
@include boldFont;
font-size: 16px;
font-weight: 500;
color: #222324;
line-height: 22px;
}
.hn-simple-dialog .el-dialog__headerbtn {
top: 0px;
width: 48px;
height: 48px;
}
.hn-simple-dialog .el-dialog__header .el-icon {
width: 24px;
height: 24px;
}
.hn-simple-dialog .el-dialog__header .el-icon svg {
width: 24px;
height: 24px;
}
.hn-simple-dialog .el-dialog__body {
padding: 24px;
}
</style>
<style lang="scss" scoped>
.question {
.question-icon {
margin-right: 4px;
vertical-align: middle;
}
.question-txt {
font-size: 14px;
color: #5d6064;
line-height: 20px;
vertical-align: middle;
}
}
.description {
margin-top: 4px;
font-size: 12px;
color: #8a8d95;
line-height: 16px;
}
</style>
/src/views/order/index.vue
<template>
<button @click="open1">打开弹窗</button>
<button @click="open2">打开弹窗2</button>
</template>
<script lang="ts">
export default { name: 'order-list' }
</script>
<script lang="ts" setup>
import simpleDialogInstance from '~/components/simple-dialog'
// 调用方法1:
const open1 = () => {
simpleDialogInstance({
title: '删除店铺',
tips: '确认删除该条店铺信息',
description: '删除后无法恢复该店铺数据',
onConfirm: async () => {
// 项目封装的请求方法,这里需要自己修改
const [res] = await errorCaptured(() => otherShopApi.otherShopDel(row.id))
if (res && res.code === 200) {
ElMessage.success('操作成功')
search()
} else {
// 不关闭弹窗
return Promise.resolve(false)
}
}
})
}
// 调用方法2:
const open2 = () => {
simpleDialogInstance({
title: '删除店铺',
contentRender: (h: any) => {
return h('p', { style: { color: '#e00f1b' } }, '确认删除该条店铺信息?')
},
// 确定事件:开发者自定义设置按钮加载和弹窗销毁
onCustomConfirm: async (instance: SimpleDialogInstance) => {
// 显示按钮加载动画
instance.showBtnLoading()
// 项目封装的请求方法,这里需要自己修改
const [res] = await errorCaptured(() => otherShopApi.otherShopDel(row.id))
if (res && res.code === 200) {
ElMessage.success('操作成功')
search()
// 销毁弹窗
instance.destroy()
} else {
ElMessage.error(msg)
}
// 移除按钮加载动画
instance.hideBtnLoading()
}
})
}
</script>