技術難題初登場
家人們,最近在開發一個超復雜的后臺管理系統項目,里面有各種數據展示、表單提交、權限控制等功能,在這個過程中,我頻繁地使用到了element-plus
組件庫中的el-dialog
組件 。它就像一個小彈窗,可以用來顯示各種提示信息、編輯表單之類的。比如說在用戶點擊 “編輯” 按鈕時,就彈出一個el-dialog
,里面放著編輯表單,讓用戶修改數據。
但隨著功能的不斷完善,我發現原生的el-dialog
組件在某些場景下真的不夠靈活。比如說,我想在不同的組件中根據不同的業務邏輯動態地控制對話框的顯示和隱藏,還要傳遞不同的數據給對話框,原生組件用起來就很麻煩,每次都要寫很多重復的代碼,這可太影響開發效率了。所以,我就決定對el-dialog
進行二次封裝,實現動態調用,讓它更好地滿足我的項目需求。接下來,就把我的經驗分享給大家,一起看看怎么解決這個問題。
實現效果
代碼實現
import { ElButton, ElDialog } from "element-plus";
import { createApp, h } from "vue";// 創建一個 DialogManager 類來管理對話框
class DialogManager {constructor() {this.dialogs = [];}/*** 創建并顯示一個動態對話框* @param {Object} options - 對話框配置選項* @returns {Object} - 返回對話框實例*/create(options = {}) {// 合并默認配置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 };// 創建一個容器元素const container = document.createElement("div");document.body.appendChild(container);// 創建 Dialog 組件實例const app = createApp({data() {return {dialogVisible: dialogOptions.visible,};},provide() {return {manager: this.$options.manager, // 提供manager};},inject: ["manager"], // 注入managerrender() {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);});// 改進 content 渲染方式const renderContent = () => {if (typeof dialogOptions.content === "string") {return h("div", { class: "dialog-content" }, dialogOptions.content);} else if (dialogOptions.content) {// 只傳遞必要的屬性和方法const props = {// 提供關閉對話框的回調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);}// 延遲銷毀,讓關閉動畫完成setTimeout(() => {this.destroy();}, 300);},},{// 默認插槽用于對話框內容default: renderContent,footer: () =>h("div",{class: "dialog-footer",},footerNodes),});},methods: {// 關閉對話框close() {this.dialogVisible = false;},// 銷毀對話框實例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// 掛載應用const instance = app.mount(container);instance.manager = this; // 直接在實例上設置managerreturn instance;}/*** 關閉所有對話框*/closeAll() {this.dialogs.forEach((dialog) => {dialog.close();});}
}// 創建單例實例
const dialogManager = new DialogManager();// 導出創建對話框的函數
export function createDialog(options) {return dialogManager.create(options);
}// 導出關閉所有對話框的函數
export function closeAllDialogs() {dialogManager.closeAll();
}
調用示例
-------------------------------- 基本文本對話框 -------------------------------------
import { createDialog } from '@/utils/dialog-manager';export default {methods: {showSimpleDialog() {createDialog({title: '確認操作',content: '你確定要執行這個操作嗎?',onClose: () => {console.log('對話框已關閉');},footerBtns: [{label: '取消',handler: (instance) => {console.log('點擊了取消');instance.close();},},{label: '確認',type: 'primary',handler: (instance) => {console.log('執行確認操作');instance.close();},},],});},},
};-------------------------- 包含組件的對話框 --------------------------------
import { createDialog } from '@/utils/dialog-manager';
import MyComponent from '@/components/MyComponent.vue';export default {methods: {showComponentDialog() {createDialog({title: '自定義組件對話框',content: MyComponent,contentProps: { // 這里可以傳入組件的propsmessage: '這是來自父組件的消息',src: 'xxx',},width: '600px',footerBtns: [{label: '關閉',handler: (instance) => {instance.close();},},],});},},
};-------------------------- 異步操作對話框 --------------------------------import { createDialog } from '@/utils/dialog-manager';export default {methods: {async showAsyncDialog() {const dialog = createDialog({title: '正在加載...',content: '數據加載中,請稍候...',showClose: false, // 隱藏關閉按鈕footerBtns: [], // 不顯示底部按鈕});try {// 模擬異步操作const result = await this.fetchData();dialog.close();// 顯示成功消息createDialog({title: '操作成功',content: '數據加載完成',footerBtns: [{label: '確定',type: 'primary',handler: (instance) => instance.close(),},],});} catch (error) {dialog.close();// 顯示錯誤消息createDialog({title: '操作失敗',content: `錯誤: ${error.message}`,footerBtns: [{label: '關閉',handler: (instance) => instance.close(),},],});}},},
};
原理剖析
這里面的原理其實也不難理解,主要是利用了Vue
的響應式原理 。我們都知道,在Vue
中,數據發生變化時,視圖會自動更新。我們封裝的組件就是基于這個特性,通過一個響應式的變量來控制el-dialog
的顯示與隱藏。比如說,我們定義一個isDialogVisible
變量,當它為true
時,el-dialog
就顯示,為false
時就隱藏 。
而動態傳遞參數呢,則是通過props
來實現的。我們在封裝的組件中定義好props
,然后在調用組件的時候,就可以把需要的數據通過props
傳遞進去,這樣對話框就能根據不同的數據展示不同的內容啦 。比如說,我們要在對話框里顯示不同的提示信息,就可以把提示信息作為props
傳遞給對話框組件 。再結合provide
和inject
,它們就像是一座橋梁,能夠讓不同層級的組件之間方便地進行通信,讓數據的傳遞更加靈活 。
實際應用場景
經過二次封裝實現動態調用的el-dialog
在實際項目中的應用場景可太廣泛了 。比如說在用戶信息編輯場景,當用戶點擊 “編輯” 按鈕,就可以動態彈出我們封裝好的對話框,里面填充好用戶當前的信息,用戶修改完成后點擊確認,就能提交新的信息,整個過程非常流暢自然 。
在文件上傳確認場景,當用戶選擇好文件準備上傳時,彈出對話框讓用戶確認文件信息,比如文件名、文件大小等 。如果沒問題再點擊上傳,這樣可以避免用戶誤操作上傳錯誤的文件 。還有在權限管理中,當管理員要給某個用戶分配新的權限時,通過動態調用對話框,展示所有權限選項,管理員勾選后提交,就能完成權限分配,操作簡單又高效 。
總結
經過二次封裝實現動態調用的el-dialog
組件,不僅大大提高了代碼的復用性,讓我們在不同的業務場景中能夠輕松應對對話框的各種需求,還增強了項目的靈活性和可維護性。以前寫一堆重復代碼的日子一去不復返啦!
強烈建議各位小伙伴在自己的項目中也嘗試應用這種二次封裝的方法 ,相信你們會發現它的強大之處。要是在實踐過程中有什么經驗,或者遇到了問題,都歡迎在評論區留言分享。咱們一起交流,共同進步 !