從0到1解鎖Element-Plus組件二次封裝El-Dialog動(dòng)態(tài)調(diào)用的原理解析
技術(shù)難題初登場(chǎng)
家人們,最近在開發(fā)一個(gè)超復(fù)雜的后臺(tái)管理系統(tǒng)項(xiàng)目,里面有各種數(shù)據(jù)展示、表單提交、權(quán)限控制等功能,在這個(gè)過程中,我頻繁地使用到了element-plus組件庫(kù)中的el-dialog組件 。它就像一個(gè)小彈窗,可以用來顯示各種提示信息、編輯表單之類的。比如說在用戶點(diǎn)擊 “編輯” 按鈕時(shí),就彈出一個(gè)el-dialog,里面放著編輯表單,讓用戶修改數(shù)據(jù)。
但隨著功能的不斷完善,我發(fā)現(xiàn)原生的el-dialog組件在某些場(chǎng)景下真的不夠靈活。比如說,我想在不同的組件中根據(jù)不同的業(yè)務(wù)邏輯動(dòng)態(tài)地控制對(duì)話框的顯示和隱藏,還要傳遞不同的數(shù)據(jù)給對(duì)話框,原生組件用起來就很麻煩,每次都要寫很多重復(fù)的代碼,這可太影響開發(fā)效率了。所以,我就決定對(duì)el-dialog進(jìn)行二次封裝,實(shí)現(xiàn)動(dòng)態(tài)調(diào)用,讓它更好地滿足我的項(xiàng)目需求。接下來,就把我的經(jīng)驗(yàn)分享給大家,一起看看怎么解決這個(gè)問題。
實(shí)現(xiàn)效果

代碼實(shí)現(xiàn)
import { ElButton, ElDialog } from "element-plus";
import { createApp, h } from "vue";
// 創(chuàng)建一個(gè) DialogManager 類來管理對(duì)話框
class DialogManager {
constructor() {
this.dialogs = [];
}
/**
* 創(chuàng)建并顯示一個(gè)動(dòng)態(tài)對(duì)話框
* @param {Object} options - 對(duì)話框配置選項(xiàng)
* @returns {Object} - 返回對(duì)話框?qū)嵗?
*/
create(options = {}) {
// 合并默認(rèn)配置
const defaultOptions = {
title: "提示",
visible: true,
fullscreen: false,
top: "15vh",
modal: true,
lockScroll: true,
closeOnClickModal: true,
closeOnPressEscape: true,
beforeClose: null,
footerBtns: [
{
label: "取消",
type: "default",
handler: (instance) => {
instance.close();
},
},
{
label: "確定",
type: "primary",
handler: (instance) => {
instance.close();
},
},
],
};
const dialogOptions = { ...defaultOptions, ...options };
// 創(chuàng)建一個(gè)容器元素
const container = document.createElement("div");
document.body.appendChild(container);
// 創(chuàng)建 Dialog 組件實(shí)例
const app = createApp({
data() {
return {
dialogVisible: dialogOptions.visible,
};
},
provide() {
return {
manager: this.$options.manager, // 提供manager
};
},
inject: ["manager"], // 注入manager
render() {
const footerNodes = dialogOptions.footerBtns.map((btn, index) => {
return h(
ElButton,
{
key: index,
type: btn.type || "default",
size: "small",
onClick: () => {
if (btn.handler) {
btn.handler(this);
}
},
},
() => btn.label
);
});
// 改進(jìn) content 渲染方式
const renderContent = () => {
if (typeof dialogOptions.content === "string") {
return h("div", { class: "dialog-content" }, dialogOptions.content);
} else if (dialogOptions.content) {
// 只傳遞必要的屬性和方法
const props = {
// 提供關(guān)閉對(duì)話框的回調(diào)
closeDialog: () => {
this.close();
},
// 可以添加其他必要的屬性
};
// 如果有額外的 props,合并它們
if (dialogOptions.contentProps) {
Object.assign(props, dialogOptions.contentProps);
}
return h(dialogOptions.content, props);
} else {
return h("div", { class: "dialog-content" }, "No content provided");
}
};
return h(
ElDialog,
{
title: dialogOptions.title,
modelValue: this.dialogVisible,
"onUpdate:modelValue": (val) => {
this.dialogVisible = val;
},
fullscreen: dialogOptions.fullscreen,
top: dialogOptions.top,
modal: dialogOptions.modal,
lockScroll: dialogOptions.lockScroll,
closeOnClickModal: dialogOptions.closeOnClickModal,
closeOnPressEscape: dialogOptions.closeOnPressEscape,
beforeClose: dialogOptions.beforeClose,
onClose: () => {
this.dialogVisible = false;
if (dialogOptions.onClose) {
dialogOptions.onClose(this);
}
// 延遲銷毀,讓關(guān)閉動(dòng)畫完成
setTimeout(() => {
this.destroy();
}, 300);
},
},
{
// 默認(rèn)插槽用于對(duì)話框內(nèi)容
default: renderContent,
footer: () =>
h(
"div",
{
class: "dialog-footer",
},
footerNodes
),
}
);
},
methods: {
// 關(guān)閉對(duì)話框
close() {
this.dialogVisible = false;
},
// 銷毀對(duì)話框?qū)嵗?
destroy() {
app.unmount();
if (container.parentNode) {
container.parentNode.removeChild(container);
}
// 從管理列表中移除
if (this.manager) {
const index = this.manager.dialogs.indexOf(this);
if (index !== -1) {
this.manager.dialogs.splice(index, 1);
}
}
},
},
mounted() {
// 添加到管理列表
if (this.manager) {
this.manager.dialogs.push(this);
} else {
console.error("Manager is not initialized");
}
},
}).provide("manager", this); // 全局提供manager
// 掛載應(yīng)用
const instance = app.mount(container);
instance.manager = this; // 直接在實(shí)例上設(shè)置manager
return instance;
}
/**
* 關(guān)閉所有對(duì)話框
*/
closeAll() {
this.dialogs.forEach((dialog) => {
dialog.close();
});
}
}
// 創(chuàng)建單例實(shí)例
const dialogManager = new DialogManager();
// 導(dǎo)出創(chuàng)建對(duì)話框的函數(shù)
export function createDialog(options) {
return dialogManager.create(options);
}
// 導(dǎo)出關(guān)閉所有對(duì)話框的函數(shù)
export function closeAllDialogs() {
dialogManager.closeAll();
}調(diào)用示例
-------------------------------- 基本文本對(duì)話框 -------------------------------------
import { createDialog } from '@/utils/dialog-manager';
export default {
methods: {
showSimpleDialog() {
createDialog({
title: '確認(rèn)操作',
content: '你確定要執(zhí)行這個(gè)操作嗎?',
onClose: () => {
console.log('對(duì)話框已關(guān)閉');
},
footerBtns: [
{
label: '取消',
handler: (instance) => {
console.log('點(diǎn)擊了取消');
instance.close();
},
},
{
label: '確認(rèn)',
type: 'primary',
handler: (instance) => {
console.log('執(zhí)行確認(rèn)操作');
instance.close();
},
},
],
});
},
},
};
-------------------------- 包含組件的對(duì)話框 --------------------------------
import { createDialog } from '@/utils/dialog-manager';
import MyComponent from '@/components/MyComponent.vue';
export default {
methods: {
showComponentDialog() {
createDialog({
title: '自定義組件對(duì)話框',
content: MyComponent,
contentProps: { // 這里可以傳入組件的props
message: '這是來自父組件的消息',
src: 'xxx',
},
width: '600px',
footerBtns: [
{
label: '關(guān)閉',
handler: (instance) => {
instance.close();
},
},
],
});
},
},
};
-------------------------- 異步操作對(duì)話框 --------------------------------
import { createDialog } from '@/utils/dialog-manager';
export default {
methods: {
async showAsyncDialog() {
const dialog = createDialog({
title: '正在加載...',
content: '數(shù)據(jù)加載中,請(qǐng)稍候...',
showClose: false, // 隱藏關(guān)閉按鈕
footerBtns: [], // 不顯示底部按鈕
});
try {
// 模擬異步操作
const result = await this.fetchData();
dialog.close();
// 顯示成功消息
createDialog({
title: '操作成功',
content: '數(shù)據(jù)加載完成',
footerBtns: [
{
label: '確定',
type: 'primary',
handler: (instance) => instance.close(),
},
],
});
} catch (error) {
dialog.close();
// 顯示錯(cuò)誤消息
createDialog({
title: '操作失敗',
content: `錯(cuò)誤: ${error.message}`,
footerBtns: [
{
label: '關(guān)閉',
handler: (instance) => instance.close(),
},
],
});
}
},
},
};原理剖析
這里面的原理其實(shí)也不難理解,主要是利用了Vue的響應(yīng)式原理 。我們都知道,在Vue中,數(shù)據(jù)發(fā)生變化時(shí),視圖會(huì)自動(dòng)更新。我們封裝的組件就是基于這個(gè)特性,通過一個(gè)響應(yīng)式的變量來控制el-dialog的顯示與隱藏。比如說,我們定義一個(gè)isDialogVisible變量,當(dāng)它為true時(shí),el-dialog就顯示,為false時(shí)就隱藏 。
而動(dòng)態(tài)傳遞參數(shù)呢,則是通過props來實(shí)現(xiàn)的。我們?cè)诜庋b的組件中定義好props,然后在調(diào)用組件的時(shí)候,就可以把需要的數(shù)據(jù)通過props傳遞進(jìn)去,這樣對(duì)話框就能根據(jù)不同的數(shù)據(jù)展示不同的內(nèi)容啦 。比如說,我們要在對(duì)話框里顯示不同的提示信息,就可以把提示信息作為props傳遞給對(duì)話框組件 。再結(jié)合provide和inject,它們就像是一座橋梁,能夠讓不同層級(jí)的組件之間方便地進(jìn)行通信,讓數(shù)據(jù)的傳遞更加靈活 。
實(shí)際應(yīng)用場(chǎng)景
經(jīng)過二次封裝實(shí)現(xiàn)動(dòng)態(tài)調(diào)用的el-dialog在實(shí)際項(xiàng)目中的應(yīng)用場(chǎng)景可太廣泛了 。比如說在用戶信息編輯場(chǎng)景,當(dāng)用戶點(diǎn)擊 “編輯” 按鈕,就可以動(dòng)態(tài)彈出我們封裝好的對(duì)話框,里面填充好用戶當(dāng)前的信息,用戶修改完成后點(diǎn)擊確認(rèn),就能提交新的信息,整個(gè)過程非常流暢自然 。
在文件上傳確認(rèn)場(chǎng)景,當(dāng)用戶選擇好文件準(zhǔn)備上傳時(shí),彈出對(duì)話框讓用戶確認(rèn)文件信息,比如文件名、文件大小等 。如果沒問題再點(diǎn)擊上傳,這樣可以避免用戶誤操作上傳錯(cuò)誤的文件 。還有在權(quán)限管理中,當(dāng)管理員要給某個(gè)用戶分配新的權(quán)限時(shí),通過動(dòng)態(tài)調(diào)用對(duì)話框,展示所有權(quán)限選項(xiàng),管理員勾選后提交,就能完成權(quán)限分配,操作簡(jiǎn)單又高效 。
總結(jié)
經(jīng)過二次封裝實(shí)現(xiàn)動(dòng)態(tài)調(diào)用的el-dialog組件,不僅大大提高了代碼的復(fù)用性,讓我們?cè)诓煌臉I(yè)務(wù)場(chǎng)景中能夠輕松應(yīng)對(duì)對(duì)話框的各種需求,還增強(qiáng)了項(xiàng)目的靈活性和可維護(hù)性。以前寫一堆重復(fù)代碼的日子一去不復(fù)返啦!
強(qiáng)烈建議各位小伙伴在自己的項(xiàng)目中也嘗試應(yīng)用這種二次封裝的方法 ,相信你們會(huì)發(fā)現(xiàn)它的強(qiáng)大之處。要是在實(shí)踐過程中有什么經(jīng)驗(yàn),或者遇到了問題,都?xì)g迎在評(píng)論區(qū)留言分享。咱們一起交流,共同進(jìn)步 !
到此這篇關(guān)于從0到1解鎖Element-Plus組件二次封裝El-Dialog動(dòng)態(tài)調(diào)用的文章就介紹到這了,更多相關(guān)Element-Plus二次封裝El-Dialog內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue使用axios實(shí)現(xiàn)動(dòng)態(tài)追加數(shù)據(jù)
在vuejs中使用axios時(shí),有時(shí)候需要追加數(shù)據(jù),比如,移動(dòng)端下拉觸底加載,分頁加載,滑動(dòng)滾動(dòng)條等,下面小編就來為大家介紹一下如何使用使用axios實(shí)現(xiàn)動(dòng)態(tài)追加數(shù)據(jù)吧2023-10-10
使用reactive導(dǎo)致數(shù)據(jù)失去響應(yīng)式的原因和解決方案
在 Vue 的響應(yīng)式系統(tǒng)中,reactive 對(duì)象是一個(gè)深度代理,它會(huì)追蹤對(duì)象屬性的變更,但如果你將整個(gè)對(duì)象重新賦值,那么 Vue 無法繼續(xù)追蹤新的對(duì)象,本文給大家介紹了使用reactive導(dǎo)致數(shù)據(jù)失去響應(yīng)式的原因和解決方案,需要的朋友可以參考下2024-09-09
Vue實(shí)現(xiàn)各種類型文件的預(yù)覽功能
這篇文章主要介紹了如何在Vue3中使用aceEditor插件和vue-ipynb插件實(shí)現(xiàn)不同類型的文件預(yù)覽,包括txt、md、json、pkl、mps、py、ipynb、doc、docx、pdf、xlsx、csv等文件,需要的朋友可以參考下2025-03-03
誤引用vuex-persistedstate導(dǎo)致用戶信息無法清除問題及解決
這篇文章主要介紹了誤引用vuex-persistedstate導(dǎo)致用戶信息無法清除問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
vue.js實(shí)現(xiàn)雙擊放大預(yù)覽功能
這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)雙擊放大預(yù)覽功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
使用element-ui的Pagination分頁的注意事項(xiàng)及說明
這篇文章主要介紹了使用element-ui的Pagination分頁的注意事項(xiàng)及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02

