vue使用h函數(shù)封裝dialog組件(以命令的形式使用dialog組件)
場景
有些時(shí)候我們的頁面是有很多的彈窗
如果我們把這些彈窗都寫html中會有一大坨
因此:我們需要把彈窗封裝成命令式的形式
命令式彈窗
// 使用彈窗的組件
<template>
<div>
<el-button @click="openMask">點(diǎn)擊彈窗</el-button>
</div>
</template>
<script setup lang="ts">
import childTest from '@/components/childTest.vue'
import { renderDialog } from '@/hooks/dialog'
function openMask(){
// 第1個(gè)參數(shù):表示的是組件,你寫彈窗中的組件
// 第2個(gè)參數(shù):表示的組件屬性,比如:確認(rèn)按鈕的名稱等
// 第3個(gè)參數(shù):表示的模態(tài)框的屬性。比如:模態(tài)寬的寬度,標(biāo)題名稱,是否可移動(dòng)
renderDialog(childTest,{},{title:'測試彈窗'})
}
</script>// 封裝的彈窗
import { createApp, h } from "vue";
import { ElDialog } from "element-plus";
export function renderDialog(component:any,props:any, modalProps:any){
const dialog = h(
ElDialog, // 模態(tài)框組件
{
...modalProps, // 模態(tài)框?qū)傩?
modelValue:true, // 模態(tài)框是否顯示
}, // 因?yàn)槭悄B(tài)框組件,肯定是模態(tài)框的屬性
{
default:()=>h(component, props ) // 插槽,el-dialog下的內(nèi)容
}
)
console.log(dialog)
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}//childTest.vue 組件
<template>
<div>
<span>It's a modal Dialog</span>
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="Activity name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="please select your zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref,reactive } from 'vue'
const dialogVisible = ref(true)
const form = reactive({
name: '',
region: '',
})
const onSubmit = () => {
console.log('submit!')
}
</script>

為啥彈窗中的表單不能夠正常展示呢?
在控制臺會有下面的提示信息:
Failed to resolve component:
el-form If this is a native custom element,
make sure to exclude it from component resolution via compilerOptions.isCustomElement
翻譯過來就是
無法解析組件:el-form如果這是一個(gè)原生自定義元素,
請確保通過 compilerOptions.isCustomElement 將其從組件解析中排除

其實(shí)就是說:我重新創(chuàng)建了一個(gè)新的app,這個(gè)app中沒有注冊組件。
因此會警告,頁面渲染不出來。
// 我重新創(chuàng)建了一個(gè)app,這個(gè)app中沒有注冊 element-plus 組件。 const app = createApp(dialog)
現(xiàn)在我們重新注冊element-plus組件。
準(zhǔn)確的說:我們要注冊 childTest.vue 組件使用到的東西
給新創(chuàng)建的app應(yīng)用注冊childTest組件使用到的東西
我們將會在這個(gè)命令式彈窗中重新注冊需要使用到的組件
// 封裝的彈窗
import { createApp, h } from "vue";
import { ElDialog } from "element-plus";
// 引入組件和樣式
import ElementPlus from "element-plus";
// import "element-plus/dist/index.css";
export function renderDialog(component:any,props:any, modalProps:any){
const dialog = h(
ElDialog, // 模態(tài)框組件
{
...modalProps, // 模態(tài)框?qū)傩?
modelValue:true, // 模態(tài)框顯示
}, // 因?yàn)槭悄B(tài)框組件,肯定是模態(tài)框的屬性
{
default:()=>h(component, props ) // 插槽,el-dialog下的內(nèi)容
}
)
console.log(dialog)
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}
現(xiàn)在我們發(fā)現(xiàn)可以正常展示彈窗中的表單了。因?yàn)槲覀冏粤薳lement-plus組件。
但是我們發(fā)現(xiàn)又發(fā)現(xiàn)了另外一個(gè)問題。
彈窗底部沒有取消和確認(rèn)按鈕。
需要我們再次通過h函數(shù)來創(chuàng)建
關(guān)于使用createApp創(chuàng)建新的應(yīng)用實(shí)例
在Vue 3中,我們可以使用 createApp 來創(chuàng)建新的應(yīng)用實(shí)例
但是這樣會創(chuàng)建一個(gè)完全獨(dú)立的應(yīng)用
它不會共享主應(yīng)用的組件、插件等。
因此我們需要重新注冊
彈窗底部新增取消和確認(rèn)按鈕
我們將會使用h函數(shù)中的插槽來創(chuàng)建底部的取消按鈕
// 封裝的彈窗
import { createApp, h } from "vue";
import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";
import ElementPlus from "element-plus";
export function renderDialog(component: any, props: any, modalProps: any) {
// 創(chuàng)建彈窗實(shí)例
const dialog = h(
ElDialog,
{
...modalProps,
modelValue: true,
},
{
// 主要內(nèi)容插槽
default: () => h(component, props),
// 底部插槽
footer:() =>h(
'div',
{ class: 'dialog-footer' },
[
h(
ElButton,
{
onClick: () => {
console.log('取消')
}
},
() => '取消'
),
h(
ElButton,
{
type: 'primary',
onClick: () => {
console.log('確定')
}
},
() => '確定'
)
]
)
}
);
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}
點(diǎn)擊關(guān)閉彈窗時(shí),需要移除之前創(chuàng)建的div
卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div。
2個(gè)地方需要移除:1,點(diǎn)擊確認(rèn)按鈕。 2,點(diǎn)擊其他地方的關(guān)閉

關(guān)閉彈窗正確銷毀相關(guān)組件
// 封裝的彈窗
import { createApp, h } from "vue";
import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";
import ElementPlus from "element-plus";
export function renderDialog(component: any, props: any, modalProps: any) {
console.log('111')
// 創(chuàng)建彈窗實(shí)例
const dialog = h(
ElDialog,
{
...modalProps,
modelValue: true,
onClose: ()=> {
console.log('關(guān)閉的回調(diào)')
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
{
// 主要內(nèi)容插槽
default: () => h(component, props),
// 底部插槽
footer:() =>h(
'div',
{
class: 'dialog-footer',
},
[
h(
ElButton,
{
onClick: () => {
console.log('點(diǎn)擊取消按鈕')
// 卸載一個(gè)已掛載的應(yīng)用實(shí)例。卸載一個(gè)應(yīng)用會觸發(fā)該應(yīng)用組件樹內(nèi)所有組件的卸載生命周期鉤子。
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
() => '取消'
),
h(
ElButton,
{
type: 'primary',
onClick: () => {
console.log('確定')
}
},
() => '確定'
)
]
)
}
);
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
// 這個(gè)div元素在在銷毀應(yīng)用時(shí)需要被移除哈
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}
點(diǎn)擊確認(rèn)按鈕時(shí)驗(yàn)證規(guī)則
有些時(shí)候,我們彈窗中的表單是需要進(jìn)行規(guī)則校驗(yàn)的。
我們下面來實(shí)現(xiàn)這個(gè)功能點(diǎn)
傳遞的組件
<template>
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
>
<el-form-item label="Activity name" prop="name">
<el-input v-model="ruleForm.name" />
</el-form-item>
<el-form-item label="Activity zone" prop="region">
<el-select v-model="ruleForm.region" placeholder="Activity zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time" required>
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker
v-model="ruleForm.date1"
type="date"
aria-label="Pick a date"
placeholder="Pick a date"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col class="text-center" :span="2">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker
v-model="ruleForm.date2"
aria-label="Pick a time"
placeholder="Pick a time"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="Resources" prop="resource">
<el-radio-group v-model="ruleForm.resource">
<el-radio value="Sponsorship">Sponsorship</el-radio>
<el-radio value="Venue">Venue</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form" prop="desc">
<el-input v-model="ruleForm.desc" type="textarea" />
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
interface RuleForm {
name: string
region: string
date1: string
date2: string
resource: string
desc: string
}
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
name: 'Hello',
region: '',
date1: '',
date2: '',
resource: '',
desc: '',
})
const rules = reactive<FormRules<RuleForm>>({
name: [
{ required: true, message: 'Please input Activity name', trigger: 'blur' },
{ min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
],
region: [
{
required: true,
message: 'Please select Activity zone',
trigger: 'change',
},
],
date1: [
{
type: 'date',
required: true,
message: 'Please pick a date',
trigger: 'change',
},
],
date2: [
{
type: 'date',
required: true,
message: 'Please pick a time',
trigger: 'change',
},
],
resource: [
{
required: true,
message: 'Please select activity resource',
trigger: 'change',
},
],
desc: [
{ required: true, message: 'Please input activity form', trigger: 'blur' },
],
})
const submitForm = async () => {
if (!ruleFormRef.value) {
console.error('ruleFormRef is not initialized')
return false
}
try {
const valid = await ruleFormRef.value.validate()
if (valid) {
console.log('表單校驗(yàn)通過', ruleForm)
return Promise.resolve(ruleForm)
}
} catch (error) {
// 為啥submitForm中,valid的值是false會執(zhí)行catch ?
// el-form 組件的 validate 方法的工作機(jī)制導(dǎo)致的。 validate 方法在表單驗(yàn)證失敗時(shí)會拋出異常
console.error('err', error)
return false
/**
* 下面這樣寫為啥界面會報(bào)錯(cuò)呢?
* return Promise.reject(error)
* 當(dāng)表單驗(yàn)證失敗時(shí),ruleFormRef.value.validate() 會拋出一個(gè)異常。
* 雖然你用了 try...catch 捕獲這個(gè)異常,并且在 catch 塊中通過 return Promise.reject(error) 返回了一個(gè)被拒絕的 Promise
* 但如果調(diào)用 submitForm 的地方?jīng)]有正確地處理這個(gè)被拒絕的 Promise(即沒有使用 .catch() 或者 await 來接收錯(cuò)誤),
* 那么瀏覽器控制臺就會顯示一個(gè) "Uncaught (in promise)" 錯(cuò)誤。
* 在 catch 中再次 return Promise.reject(error) 是多余的, 直接return false
* */
/**
* 如果你這樣寫
* throw error 直接拋出錯(cuò)誤即可
* 那么就需要再調(diào)用submitForm的地方捕獲異常
* */
}
}
defineExpose({
submitForm:submitForm
})
</script>// 封裝的彈窗
import { createApp, h, ref } from "vue";
import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";
import ElementPlus from "element-plus";
export function renderDialog(component: any, props: any, modalProps: any) {
const instanceElement = ref()
console.log('111', instanceElement)
// 創(chuàng)建彈窗實(shí)例
const dialog = h(
ElDialog,
{
...modalProps,
modelValue: true,
onClose: ()=> {
console.log('關(guān)閉的回調(diào)')
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
{
// 主要內(nèi)容插槽,這里的ref必須接收一個(gè)ref
default: () => h(component, {...props, ref: instanceElement}),
// 底部插槽
footer:() =>h(
'div',
{
class: 'dialog-footer',
},
[
h(
ElButton,
{
onClick: () => {
console.log('點(diǎn)擊取消按鈕')
// 卸載一個(gè)已掛載的應(yīng)用實(shí)例。卸載一個(gè)應(yīng)用會觸發(fā)該應(yīng)用組件樹內(nèi)所有組件的卸載生命周期鉤子。
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
() => '取消'
),
h(
ElButton,
{
type: 'primary',
onClick: () => {
instanceElement?.value?.submitForm().then((res:any) =>{
console.log('得到的值',res)
})
console.log('確定')
}
},
() => '確定'
)
]
)
}
);
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
// 這個(gè)div元素在在銷毀應(yīng)用時(shí)需要被移除哈
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}
關(guān)鍵的點(diǎn):通過ref拿到childTest組件中的方法,childTest要暴露需要的方法
如何把表單中的數(shù)據(jù)暴露出去
可以通過回調(diào)函數(shù)的方式把數(shù)據(jù)暴露出去哈。
// 封裝的彈窗
import { createApp, h, ref } from "vue";
import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";
import ElementPlus from "element-plus";
export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {
// 第4個(gè)參數(shù)是回調(diào)函數(shù)
const instanceElement = ref()
console.log('111', instanceElement)
// 創(chuàng)建彈窗實(shí)例
const dialog = h(
ElDialog,
{
...modalProps,
modelValue: true,
onClose: ()=> {
console.log('關(guān)閉的回調(diào)')
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
{
// 主要內(nèi)容插槽,這里的ref必須接收一個(gè)ref
default: () => h(component, {...props, ref: instanceElement}),
// 底部插槽
footer:() =>h(
'div',
{
class: 'dialog-footer',
},
[
h(
ElButton,
{
onClick: () => {
console.log('點(diǎn)擊取消按鈕')
// 卸載一個(gè)已掛載的應(yīng)用實(shí)例。卸載一個(gè)應(yīng)用會觸發(fā)該應(yīng)用組件樹內(nèi)所有組件的卸載生命周期鉤子。
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
() => '取消'
),
h(
ElButton,
{
type: 'primary',
onClick: () => {
// submitForm 調(diào)用表單組件中需要驗(yàn)證或者暴露出去的數(shù)據(jù)
instanceElement?.value?.submitForm().then((res:any) =>{
console.log('得到的值',res)
// 驗(yàn)證通過后調(diào)用回調(diào)函數(shù)傳遞數(shù)據(jù), 如驗(yàn)證失敗,res 的值有可能是一個(gè)false。
onConfirm(res)
// 怎么把這個(gè)事件傳遞出去,讓使用的時(shí)候知道點(diǎn)擊了確認(rèn)并且知道驗(yàn)證通過了
}).catch((error: any) => {
// 驗(yàn)證失敗時(shí)也可以傳遞錯(cuò)誤信息
console.log('驗(yàn)證失敗', error)
})
console.log('確定')
}
},
() => '確定'
)
]
)
}
);
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
// 這個(gè)div元素在在銷毀應(yīng)用時(shí)需要被移除哈
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}<template>
<div>
<el-button @click="openMask">點(diǎn)擊彈窗</el-button>
</div>
</template>
<script setup lang="ts">
import childTest from '@/components/childTest.vue'
import { renderDialog } from '@/hooks/dialog'
import { getCurrentInstance } from 'vue';
const currentInstance = getCurrentInstance();
function openMask(){
console.log('currentInstance',currentInstance)
renderDialog(childTest,{},{title:'測試彈窗', width: '700'}, (res)=>{
console.log('通過回調(diào)函數(shù)返回值', res)
})
}
</script>
點(diǎn)擊確定時(shí),業(yè)務(wù)完成后關(guān)閉彈窗
現(xiàn)在想要點(diǎn)擊確定,等業(yè)務(wù)處理完成之后,才關(guān)閉彈窗。
需要在使用完成業(yè)務(wù)的時(shí)候返回一個(gè)promise,讓封裝的彈窗調(diào)用這個(gè)promise
這樣就可以知道什么時(shí)候關(guān)閉彈窗了
// 封裝的彈窗
import { createApp, h, ref } from "vue";
import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";
import ElementPlus from "element-plus";
export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {
// 第4個(gè)參數(shù)是回調(diào)函數(shù)
const instanceElement = ref()
console.log('111', instanceElement)
// 創(chuàng)建彈窗實(shí)例
const dialog = h(
ElDialog,
{
...modalProps,
modelValue: true,
onClose: ()=> {
console.log('關(guān)閉的回調(diào)')
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
{
// 主要內(nèi)容插槽,這里的ref必須接收一個(gè)ref
default: () => h(component, {...props, ref: instanceElement}),
// 底部插槽
footer:() =>h(
'div',
{
class: 'dialog-footer',
},
[
h(
ElButton,
{
onClick: () => {
console.log('點(diǎn)擊取消按鈕')
// 卸載一個(gè)已掛載的應(yīng)用實(shí)例。卸載一個(gè)應(yīng)用會觸發(fā)該應(yīng)用組件樹內(nèi)所有組件的卸載生命周期鉤子。
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
() => '取消'
),
h(
ElButton,
{
type: 'primary',
onClick: () => {
// submitForm 調(diào)用表單組件中需要驗(yàn)證或者暴露出去的數(shù)據(jù)
instanceElement?.value?.submitForm().then((res:any) =>{
console.log('得到的值',res)
// 驗(yàn)證通過后調(diào)用回調(diào)函數(shù)傳遞數(shù)據(jù),如驗(yàn)證失敗,res 的值有可能是一個(gè)false。
const callbackResult = onConfirm(res);
// 如果回調(diào)函數(shù)返回的是 Promise,則等待業(yè)務(wù)完成后再關(guān)閉彈窗
if (callbackResult instanceof Promise) {
// 注意這里的finally,這樣寫在服務(wù)出現(xiàn)異常的時(shí)候會有問題,這里是有問題的,需要優(yōu)化
// 注意這里的finally,這樣寫在服務(wù)出現(xiàn)異常的時(shí)候會有問題,這里是有問題的,需要優(yōu)化
callbackResult.finally(() => {
// 彈窗關(guān)閉邏輯
app.unmount()
document.body.removeChild(div)
});
} else {
// 如果不是 Promise,立即關(guān)閉彈窗
app.unmount()
document.body.removeChild(div)
}
}).catch((error: any) => {
// 驗(yàn)證失敗時(shí)也可以傳遞錯(cuò)誤信息
console.log('驗(yàn)證失敗', error)
})
}
},
() => '確定'
)
]
)
}
);
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
// 這個(gè)div元素在在銷毀應(yīng)用時(shí)需要被移除哈
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}<template>
<div>
<el-button @click="openMask">點(diǎn)擊彈窗</el-button>
</div>
</template>
<script setup lang="ts">
import childTest from '@/components/childTest.vue'
import { renderDialog } from '@/hooks/dialog'
import { getCurrentInstance } from 'vue';
const currentInstance = getCurrentInstance();
function openMask(){
console.log('currentInstance',currentInstance)
renderDialog(childTest,{},{title:'測試彈窗', width: '700'}, (res)=>{
console.log('通過回調(diào)函數(shù)返回值', res)
// 這里返回一個(gè)promise對象,這樣就可以讓業(yè)務(wù)完成后才關(guān)閉彈窗
return fetch("https://dog.ceo/api/breed/pembroke/images/random")
.then((res) => {
return res.json();
})
.then((res) => {
console.log('獲取的圖片地址為:', res.message);
});
})
}
</script>
優(yōu)化業(yè)務(wù)組件
// 封裝的彈窗
import { createApp, h, ref } from "vue";
import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";
import ElementPlus from "element-plus";
export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {
// 關(guān)閉彈窗,避免重復(fù)代碼
const closeDialog = () => {
// 成功時(shí)關(guān)閉彈窗
app.unmount();
// 檢查div是否仍然存在且為body的子元素,否者可能出現(xiàn)異常
if (div && div.parentNode) {
document.body.removeChild(div)
}
}
// 第4個(gè)參數(shù)是回調(diào)函數(shù)
const instanceElement = ref()
console.log('111', instanceElement)
// 創(chuàng)建彈窗實(shí)例
const dialog = h(
ElDialog,
{
...modalProps,
modelValue: true,
onClose: ()=> {
console.log('關(guān)閉的回調(diào)')
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
{
// 主要內(nèi)容插槽,這里的ref必須接收一個(gè)ref
default: () => h(component, {...props, ref: instanceElement}),
// 底部插槽
footer:() =>h(
'div',
{
class: 'dialog-footer',
},
[
h(
ElButton,
{
onClick: () => {
console.log('點(diǎn)擊取消按鈕')
// 卸載一個(gè)已掛載的應(yīng)用實(shí)例。卸載一個(gè)應(yīng)用會觸發(fā)該應(yīng)用組件樹內(nèi)所有組件的卸載生命周期鉤子。
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
() => '取消'
),
h(
ElButton,
{
type: 'primary',
onClick: () => {
// submitForm 調(diào)用表單組件中需要驗(yàn)證或者暴露出去的數(shù)據(jù)
instanceElement?.value?.submitForm().then((res:any) =>{
console.log('得到的值',res)
// 驗(yàn)證通過后調(diào)用回調(diào)函數(shù)傳遞數(shù)據(jù),如驗(yàn)證失敗,res 的值有可能是一個(gè)false。
const callbackResult = onConfirm(res);
// 如果回調(diào)函數(shù)返回的是 Promise,則等待業(yè)務(wù)完成后再關(guān)閉彈窗
if (callbackResult instanceof Promise) {
callbackResult.then(() => {
if(res){
console.log('111')
closeDialog()
}
}).catch(error=>{
console.log('222')
console.error('回調(diào)函數(shù)執(zhí)行出錯(cuò),如:網(wǎng)絡(luò)錯(cuò)誤', error);
// 錯(cuò)誤情況下也關(guān)閉彈窗
closeDialog()
});
} else {
// 如果不是 Promise,并且驗(yàn)證時(shí)通過了的。立即關(guān)閉彈窗
console.log('333', res)
if(res){
closeDialog()
}
}
}).catch((error: any) => {
console.log('44444')
// 驗(yàn)證失敗時(shí)也可以傳遞錯(cuò)誤信息
console.log('驗(yàn)證失敗', error)
})
}
},
() => '確定'
)
]
)
}
);
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
// 這個(gè)div元素在在銷毀應(yīng)用時(shí)需要被移除哈
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}<template>
<div>
<el-button @click="openMask">點(diǎn)擊彈窗</el-button>
</div>
</template>
<script setup lang="ts">
import childTest from '@/components/childTest.vue'
import { renderDialog } from '@/hooks/dialog'
import { getCurrentInstance } from 'vue';
const currentInstance = getCurrentInstance();
function openMask(){
console.log('currentInstance',currentInstance)
renderDialog(childTest,{},{title:'測試彈窗', width: '700'}, (res)=>{
console.log('通過回調(diào)函數(shù)返回值', res)
// 這里返回一個(gè)promise對象,這樣就可以讓業(yè)務(wù)完成后才關(guān)閉彈窗
return fetch("https://dog.ceo/api/breed/pembroke/images/random")
.then((res) => {
return res.json();
})
.then((res) => {
console.log('獲取的圖片地址為:', res.message);
});
})
}
</script>眼尖的小伙伴可能已經(jīng)發(fā)現(xiàn)了這一段代碼。
1,驗(yàn)證不通過會也會觸發(fā)卸載彈窗
2,callbackResult.finally是不合適的
3.


最終的代碼
// 封裝的彈窗
import { createApp, h, ref } from "vue";
import { ElDialog, ElButton, ElForm, ElFormItem, ElInput, ElSelect, ElOption } from "element-plus";
import ElementPlus from "element-plus";
export function renderDialog(component: any, props: any, modalProps: any, onConfirm: (data: any) => any ) {
// 關(guān)閉彈窗,避免重復(fù)代碼
const closeDialog = () => {
// 成功時(shí)關(guān)閉彈窗
app.unmount();
// 檢查div是否仍然存在且為body的子元素,否者可能出現(xiàn)異常
if (div && div.parentNode) {
document.body.removeChild(div)
}
}
// 第4個(gè)參數(shù)是回調(diào)函數(shù)
const instanceElement = ref()
console.log('111', instanceElement)
const isLoading = ref(false)
// 創(chuàng)建彈窗實(shí)例
const dialog = h(
ElDialog,
{
...modalProps,
modelValue: true,
onClose: ()=> {
isLoading.value = false
console.log('關(guān)閉的回調(diào)')
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
{
// 主要內(nèi)容插槽,這里的ref必須接收一個(gè)ref
default: () => h(component, {...props, ref: instanceElement}),
// 底部插槽,noShowFooterBool是true,不顯示; false的顯示底部
footer: props.noShowFooterBool ? null : () =>h(
'div',
{
class: 'dialog-footer',
},
[
h(
ElButton,
{
onClick: () => {
console.log('點(diǎn)擊取消按鈕')
// 卸載一個(gè)已掛載的應(yīng)用實(shí)例。卸載一個(gè)應(yīng)用會觸發(fā)該應(yīng)用組件樹內(nèi)所有組件的卸載生命周期鉤子。
app.unmount() // 這樣卸載會讓動(dòng)畫消失
// 卸載的同時(shí)需要把我們創(chuàng)建的div元素移除,否則頁面上會出現(xiàn)很多div
document.body.removeChild(div)
}
},
() => props.cancelText || '取消'
),
h(
ElButton,
{
type: 'primary',
loading: isLoading.value,
onClick: () => {
isLoading.value = true
// submitForm 調(diào)用表單組件中需要驗(yàn)證或者暴露出去的數(shù)據(jù)
instanceElement?.value?.submitForm().then((res:any) =>{
if(!res){
isLoading.value = false
}
console.log('得到的值',res)
// 驗(yàn)證通過后調(diào)用回調(diào)函數(shù)傳遞數(shù)據(jù),如驗(yàn)證失敗,res 的值有可能是一個(gè)false。
const callbackResult = onConfirm(res);
// 如果回調(diào)函數(shù)返回的是 Promise,則等待業(yè)務(wù)完成后再關(guān)閉彈窗
if (callbackResult instanceof Promise) {
callbackResult.then(() => {
if(res){
console.log('111')
closeDialog()
}else{
isLoading.value = false
}
}).catch(error=>{
console.log('222')
console.error('回調(diào)函數(shù)執(zhí)行出錯(cuò),如:網(wǎng)絡(luò)錯(cuò)誤', error);
// 錯(cuò)誤情況下也關(guān)閉彈窗
closeDialog()
});
} else {
// 如果不是 Promise,并且驗(yàn)證時(shí)通過了的。立即關(guān)閉彈窗
console.log('333', res)
if(res){
closeDialog()
}else{
isLoading.value = false
}
}
}).catch((error: any) => {
console.log('44444')
isLoading.value = false
// 驗(yàn)證失敗時(shí)也可以傳遞錯(cuò)誤信息
console.log('驗(yàn)證失敗', error)
})
}
},
() => props.confirmText || '確定'
)
]
)
}
);
// 創(chuàng)建一個(gè)新的 Vue 應(yīng)用實(shí)例。這個(gè)應(yīng)用實(shí)例是獨(dú)立的,與主應(yīng)用分離。
const app = createApp(dialog)
// 在新實(shí)例中注冊 Element Plus, 這彈窗中的組件就可以正常顯示了
app.use(ElementPlus);
// 這個(gè)div元素在在銷毀應(yīng)用時(shí)需要被移除哈
const div = document.createElement('div')
document.body.appendChild(div)
app.mount(div)
}<template>
<div>
<el-button @click="openMask">點(diǎn)擊彈窗</el-button>
</div>
</template>
<script setup lang="ts">
import childTest from '@/components/childTest.vue'
import { renderDialog } from '@/hooks/dialog'
import { getCurrentInstance } from 'vue';
const currentInstance = getCurrentInstance();
function openMask(){
console.log('currentInstance',currentInstance)
const otherProps = {cancelText:'取消哈', confirmText: '確認(rèn)哈',showFooterBool:true }
const dialogSetObject = {title:'測試彈窗哈', width: '700', draggable: true}
renderDialog(childTest,otherProps,dialogSetObject, (res)=>{
console.log('通過回調(diào)函數(shù)返回值', res)
// 這里返回一個(gè)promise對象,這樣就可以讓業(yè)務(wù)完成后才關(guān)閉彈窗
return fetch("https://dog.ceo/api/breed/pembroke/images/random")
.then((res) => {
return res.json();
})
.then((res) => {
console.log('獲取的圖片地址為:', res.message);
});
})
}
</script>
<style lang="scss" scoped>
</style><template>
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
>
<el-form-item label="Activity name" prop="name">
<el-input v-model="ruleForm.name" />
</el-form-item>
<el-form-item label="Activity zone" prop="region">
<el-select v-model="ruleForm.region" placeholder="Activity zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time" required>
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker
v-model="ruleForm.date1"
type="date"
aria-label="Pick a date"
placeholder="Pick a date"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col class="text-center" :span="2">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker
v-model="ruleForm.date2"
aria-label="Pick a time"
placeholder="Pick a time"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="Resources" prop="resource">
<el-radio-group v-model="ruleForm.resource">
<el-radio value="Sponsorship">Sponsorship</el-radio>
<el-radio value="Venue">Venue</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form" prop="desc">
<el-input v-model="ruleForm.desc" type="textarea" />
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
interface RuleForm {
name: string
region: string
date1: string
date2: string
resource: string
desc: string
}
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
name: 'Hello',
region: '',
date1: '',
date2: '',
resource: '',
desc: '',
})
const rules = reactive<FormRules<RuleForm>>({
name: [
{ required: true, message: 'Please input Activity name', trigger: 'blur' },
{ min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
],
region: [
{
required: true,
message: 'Please select Activity zone',
trigger: 'change',
},
],
date1: [
{
type: 'date',
required: true,
message: 'Please pick a date',
trigger: 'change',
},
],
date2: [
{
type: 'date',
required: true,
message: 'Please pick a time',
trigger: 'change',
},
],
resource: [
{
required: true,
message: 'Please select activity resource',
trigger: 'change',
},
],
desc: [
{ required: true, message: 'Please input activity form', trigger: 'blur' },
],
})
const submitForm = async () => {
if (!ruleFormRef.value) {
console.error('ruleFormRef is not initialized')
return false
}
try {
const valid = await ruleFormRef.value.validate()
if (valid) {
// 驗(yàn)證通過后,就會可以把你需要的數(shù)據(jù)暴露出去
return Promise.resolve(ruleForm)
}
} catch (error) {
// 為啥submitForm中,valid的值是false會執(zhí)行catch ?
// el-form 組件的 validate 方法的工作機(jī)制導(dǎo)致的。 validate 方法在表單驗(yàn)證失敗時(shí)會拋出異常
console.error('err', error)
return false
/**
* 下面這樣寫為啥界面會報(bào)錯(cuò)呢?
* return Promise.reject(error)
* 當(dāng)表單驗(yàn)證失敗時(shí),ruleFormRef.value.validate() 會拋出一個(gè)異常。
* 雖然你用了 try...catch 捕獲這個(gè)異常,并且在 catch 塊中通過 return Promise.reject(error) 返回了一個(gè)被拒絕的 Promise
* 但如果調(diào)用 submitForm 的地方?jīng)]有正確地處理這個(gè)被拒絕的 Promise(即沒有使用 .catch() 或者 await 來接收錯(cuò)誤),
* 那么瀏覽器控制臺就會顯示一個(gè) "Uncaught (in promise)" 錯(cuò)誤。
* 在 catch 中再次 return Promise.reject(error) 是多余的, 直接return false
* */
/**
* 如果你這樣寫
* throw error 直接拋出錯(cuò)誤即可
* 那么就需要再調(diào)用submitForm的地方捕獲異常
* */
}
}
defineExpose({
submitForm:submitForm
})
</script>到此這篇關(guān)于vue使用h函數(shù)封裝dialog組件(以命令的形式使用dialog組件)的文章就介紹到這了,更多相關(guān)vue h函數(shù)封裝dialog組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue addRoutes路由動(dòng)態(tài)加載操作
這篇文章主要介紹了vue addRoutes路由動(dòng)態(tài)加載操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
使用 vue 實(shí)現(xiàn)滅霸打響指英雄消失的效果附demo
這篇文章主要介紹了使用 vue 實(shí)現(xiàn)滅霸打響指英雄消失的效果 demo,需要的朋友可以參考下2019-05-05
使用Vue純前端實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼并實(shí)現(xiàn)倒計(jì)時(shí)
在實(shí)際的應(yīng)用開發(fā)中,涉及用戶登錄驗(yàn)證、密碼重置等場景時(shí),通常需要前端實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的功能,以提升用戶體驗(yàn)和安全性,以下是一個(gè)簡單的前端實(shí)現(xiàn),演示了如何在用戶點(diǎn)擊發(fā)送驗(yàn)證碼按鈕時(shí)觸發(fā)短信驗(yàn)證碼的發(fā)送,并開始一個(gè)倒計(jì)時(shí)2024-04-04
vue頁面跳轉(zhuǎn)過渡動(dòng)畫與防止抖動(dòng)方式
這篇文章主要介紹了vue頁面跳轉(zhuǎn)過渡動(dòng)畫與防止抖動(dòng)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2026-03-03
vue配置electron使用electron-builder進(jìn)行打包的操作方法
這篇文章主要介紹了vue配置electron使用electron-builder進(jìn)行打包的操作方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08

