Element UI Dialog 组件详解:从基本用法到源码级实现原理
发布时间:2026/9/18 17:09:51 锦皓数字建站

Element UI Dialog 组件详解从基本用法到源码级实现原理【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/elementDialog对话框是 Element 中用于在不离开当前页面的前提下向用户传递信息的模态容器也是 element-ui 中交互能力最完整的弹层组件之一。本文以官方西班牙语文档 Dialog 组件说明 为主体完整覆盖基本用法、嵌套组件、对话框嵌套、内容居中等官方示例并结合当前仓库中的组件源码 packages/dialog/src/component.vue、弹层公共混入 src/utils/popup/index.js 与 src/utils/popup/popup-manager.js、样式 packages/theme-chalk/src/dialog.scss 及单元测试 test/unit/specs/dialog.spec.js解释每个属性、事件背后的真实行为帮助你既能直接复制使用也能在遇到懒渲染、z-index、锁滚动等细节问题时快速定位原理。基本用法visible、.sync 与 before-closeDialog 会打开一个可高度定制的对话框。核心用法是将visible属性绑定为一个布尔值当它为true时对话框显示。对话框由两部分组成body默认插槽和footer需要名为footer的插槽title属性是可选的默认为空字符串用于定义标题。官方示例如下同时展示了before-close的用法el-button typetext clickdialogVisible trueclick to open the Dialog/el-button el-dialog titleTips :visible.syncdialogVisible width30% :before-closehandleClose spanThis is a message/span span slotfooter classdialog-footer el-button clickdialogVisible falseCancel/el-button el-button typeprimary clickdialogVisible falseConfirm/el-button /span /el-dialog script export default { data() { return { dialogVisible: false }; }, methods: { handleClose(done) { this.$confirm(Are you sure to close this dialog?) .then(_ { done(); }) .catch(_ {}); } } }; /script.sync修饰符为什么是关键。从源码看Dialog 组件内部并没有直接修改visible属性而是在关闭时向外抛出update:visible事件。在 component.vue 的hide(cancel)方法中可以看到hide(cancel) { if (cancel ! false) { this.$emit(update:visible, false); this.$emit(close); this.closed true; } }也就是说.sync展开后等价于:visibleupdate:visible负责把组件内部的“请求关闭”写回到你的数据变量上。如果你手写:visibledialogVisible而不加.sync那么点击右上角关闭按钮或点击遮罩后变量并不会被更新对话框将无法正常收起。before-close 的触发范围。before-close只在用户点击右上角关闭图标或点击遮罩modal时生效。如果footer插槽里有自定义的关闭按钮可以手动把before-close的逻辑写进这些按钮的事件处理器中。源码中对应的是 handleClose 方法handleClose() { if (typeof this.beforeClose function) { this.beforeClose(this.hide); } else { this.hide(); } }注意done参数实际就是组件内部的hide方法本身——不调用它对话框就不会关闭这正是官方示例中“先弹$confirm二次确认、用户同意后再调done()”能够成立的原因。关闭入口有两条路径右上角关闭按钮的clickhandleClose模板第 27 行以及遮罩层点击v-showvisible的外层 wrapper 上的click.selfhandleWrapperClick见 component.vue 模板。提示before-close只在用户点击关闭图标或点击遮罩时触发。footer插槽中自定义的关闭按钮不会走这条路径需要你在按钮事件里自行实现相同的拦截逻辑。自定义内容在 Dialog 中嵌套 Table 与 FormDialog 的内容可以是任意东西包括表格或表单。下面的官方示例演示了如何用 Element 的 Table 和 Form 组件与 Dialog 配合这也是实际业务中最常见的“弹窗内详情/编辑”场景!-- Table -- el-button typetext clickdialogTableVisible trueopen a Table nested Dialog/el-button el-dialog titleShipping address :visible.syncdialogTableVisible el-table :datagridData el-table-column propertydate labelDate width150/el-table-column el-table-column propertyname labelName width200/el-table-column el-table-column propertyaddress labelAddress/el-table-column /el-table /el-dialog !-- Form -- el-button typetext clickdialogFormVisible trueopen a Form nested Dialog/el-button el-dialog titleShipping address :visible.syncdialogFormVisible el-form :modelform el-form-item labelPromotion name :label-widthformLabelWidth el-input v-modelform.name autocompleteoff/el-input /el-form-item el-form-item labelZones :label-widthformLabelWidth el-select v-modelform.region placeholderPlease select a zone el-option labelZone No.1 valueshanghai/el-option el-option labelZone No.2 valuebeijing/el-option /el-select /el-form-item /el-form span slotfooter classdialog-footer el-button clickdialogFormVisible falseCancel/el-button el-button typeprimary clickdialogFormVisible falseConfirm/el-button /span /el-dialog script export default { data() { return { gridData: [{ date: 2016-05-02, name: John Smith, address: No.1518, Jinshajiang Road, Putuo District }, { date: 2016-05-04, name: John Smith, address: No.1518, Jinshajiang Road, Putuo District }, { date: 2016-05-01, name: John Smith, address: No.1518, Jinshajiang Road, Putuo District }, { date: 2016-05-03, name: John Smith, address: No.1518, Jinshajiang Road, Putuo District }], dialogTableVisible: false, dialogFormVisible: false, form: { name: , region: , date1: , date2: , delivery: false, type: [], resource: , desc: }, formLabelWidth: 120px }; } }; /script由于 body 插槽的内容是延迟渲染的见下文“事件与懒渲染”一节把较重的 Table/Form 放进 Dialog 时只有对话框真正打开后才会产生这部分 DOM这一点在承载复杂内容的场景中是有利的。对话框嵌套与 append-to-body如果一个对话框嵌套在另一个对话框内部需要设置append-to-body。官方文档明确说明一般不推荐嵌套使用 Dialog如果页面需要展示多个对话框更好的做法是“拍平”成彼此平级的兄弟元素。确有必要把内层 Dialog 渲染到body下时把内层 Dialog 的append-to-body设为true这样它会被添加到body而不是父节点两个对话框都能正确渲染。template el-button typetext clickouterVisible trueopen the outer Dialog/el-button el-dialog titleOuter Dialog :visible.syncouterVisible el-dialog width30% titleInner Dialog :visible.syncinnerVisible append-to-body /el-dialog div slotfooter classdialog-footer el-button clickouterVisible falseCancel/el-button el-button typeprimary clickinnerVisible trueopen the inner Dialog/el-button /div /el-dialog /template script export default { data() { return { outerVisible: false, innerVisible: false }; } } /script为什么必须移到 bodyDialog 的外层 wrapper 是一个position: fixed覆盖全视口的容器见 dialog.scss 中el-dialog__wrapper的定义。当内层 Dialog 渲染在外层 Dialog 内部时它继承外层元素的层叠上下文其固定定位和 z-index 都无法超出外层对话框的范围。源码中append-to-body的实现出现在两个时机见 component.vue 第 129–131 行与 195–203 行// visible 由 false 变 true 时 if (this.appendToBody) { document.body.appendChild(this.$el); }组件mounted时若初始即为可见也会执行同样的移动而在destroyed钩子中会把节点从 DOM 移除避免残留第 205–210 行。单元测试 test/unit/specs/dialog.spec.js 中的 “append to body” 直接断言了dialog.$el.parentNode等于document.body。另一个需要留意的点是Dialog 打开时会向ElSelectDropdown和ElDropdownMenu广播updatePopper事件updatePopper 方法用于让对话框内的下拉弹层在滚动时重新定位。内容居中对齐center 属性Dialog 的头部和底部可以水平居中对齐。将center设为true即可。注意center只影响标题和 footer不影响 body——body 可以是任意内容强制居中后往往观感不佳若需要居中也请自行编写 CSS。el-button typetext clickcenterDialogVisible trueClick to open the Dialog/el-button el-dialog titleWarning :visible.synccenterDialogVisible width30% center spanIt should be noted that the content will not be aligned in center by default/span span slotfooter classdialog-footer el-button clickcenterDialogVisible falseCancel/el-button el-button typeprimary clickcenterDialogVisible falseConfirm/el-button /span /el-dialog script export default { data() { return { centerDialogVisible: false }; } }; /script从模板看center会给根元素加上el-dialog--center修饰类component.vue 第 15 行对应样式在 dialog.scss 中text-align: center作用于对话框整体el-dialog__body恢复text-align: initialel-dialog__footer继承居中——这正是“只居中头部和底部、body 不受影响”的样式来源。属性、插槽与事件全表以下属性表完整继承自官方文档默认值均与 component.vue 的 props 定义 逐一对应属性说明类型可选值默认值visible对话框的显示与否支持.sync修饰符boolean—falsetitle对话框的标题也可通过title插槽传递string—width对话框的宽度string—50%见下方说明fullscreen对话框是否全屏boolean—falsetop对话框 CSS 的margin-top值string—15vhmodal是否显示遮罩boolean—truemodal-append-to-body遮罩是否挂载到body元素为false时遮罩挂载到对话框的主元素boolean—trueappend-to-body对话框本体是否挂载到bodyboolean—falselock-scroll对话框显示期间是否禁用 body 滚动boolean—truecustom-class对话框的自定义类名string—close-on-click-modal点击遮罩是否可以关闭对话框boolean—trueclose-on-press-escape按 ESC 是否可以关闭对话框boolean—trueshow-close是否显示右上角关闭按钮boolean—truebefore-close关闭前的回调可实现“阻止关闭”done用于执行关闭function(done)——center标题与底部是否居中对齐boolean—falsedestroy-on-close关闭时销毁对话框内的元素boolean—false关于width的默认值需要说明组件的widthprop 本身没有default未传即为undefined此时 style 计算属性 不设置行内宽度实际宽度由 dialog.scss 中.el-dialog { width: 50% }兜底因此文档标注的默认值为50%。top同理只有非全屏时才会写入margin-topstyle.marginTop this.top全屏模式下margin-top、width均不生效全屏尺寸由is-fullscreen修饰类的 CSSwidth: 100%; height: 100%; overflow: auto接管。插槽Slots名称说明—默认对话框的主体内容title对话框的标题内容覆盖title属性footer对话框的底部内容从模板结构看body 部分仅在rendered为true时渲染v-ifrenderedfooter 部分仅在提供了footer插槽时渲染v-if$slots.footer即不写 footer 就不会产生空的底部容器。事件Events事件名说明参数open对话框打开时触发—opened打开动画结束0.3s 淡入时触发—close对话框关闭时触发—closed关闭动画结束时触发—这四个事件的触发点在源码中清晰可查open/close由 component.vue 中visible的 watcher 发出opened/closed则绑定在外层transition namedialog-fade的after-enter/after-leave钩子上第 187–192 行对应的 0.3s 动画定义在 dialog.scss 的dialog-fade-in/dialog-fade-out。单元测试 “events” 验证了四个回调都会按预期触发。源码级细节懒渲染、destroy-on-close 与锁滚动懒渲染lazy rendering。Dialog 的内容是延迟渲染的默认插槽在第一次打开之前不会进入 DOM。这一行为来自公共混入 src/utils/popup/index.jsvisible首次变为true时先把rendered置为true并等待nextTick再执行open()此后rendered保持为true关闭时只切换v-show内容不再重建。由此得出两条实践结论需要在打开后操作对话框内部 DOM、或通过ref访问其中组件时请把逻辑放在open事件回调中首次打开时 DOM 恰好在这一刻可用正因内容在首次打开前不渲染把重型组件放进 Dialog 不会拖累首屏。destroy-on-close 的实现。设置destroy-on-close后每次关闭都会让对话框内容“失忆”表单输入、组件内部状态全部重置。源码实现非常简洁关闭时在$nextTick中让内部key自增:key变化强制 Vue 销毁并重建.el-dialog元素树。测试用例 “destroyOnClose” 验证了重新打开后输入框内容已被清空。锁滚动与 body 补偿。当modal与lock-scroll均为true默认时popup 混入的 doOpen 会测量滚动条宽度在 body 上追加等宽padding-right并添加el-popup-parent--hidden类防止遮罩出现时页面宽度抖动关闭时通过 restoreBodyStyle 还原。多个弹层叠加时只有第一个弹层负责加锁、最后一个负责解锁。z-index 管理。Dialog 与遮罩的层级由 popup-manager.js 统一分配nextZIndex()全局递增初始基准值取自全局配置Vue.prototype.$ELEMENT.zIndex未配置时默认2000第 154–166 行。所有共享遮罩的弹层Dialog、Message、Notification 等按打开顺序压入modalStack点击遮罩时只作用于栈顶实例doOnModalClickESC 键的监听同样只命中当前最顶层弹层且要求其closeOnPressEscape为true第 179–192 行。如果你的页面中 Dialog 与其他弹层互相遮挡可以从全局$ELEMENT.zIndex入手调整基准值。与 Vuex 配合时的注意事项官方文档给出了一条重要提示如果绑定visible的变量由 Vuex store 管理.sync无法正常工作。原因是 Vuex 的 state 只能通过 mutation 修改而update:visible事件会尝试直接赋值。正确做法是去掉.sync改为监听 Dialog 的open和close事件在事件处理器中 dispatch/commit 对应的 mutation 来更新 store 中的变量。快速上手清单安装与注册ElDialog.install会将组件以ElDialog名称注册见 packages/dialog/index.js在完整引入 element-ui 时全局可用。常规弹窗:visible.synctitle 默认插槽需要底部按钮时再加slotfooter。关闭拦截用before-close接收done不回调done即保持打开。多层弹层内层 Dialog 加append-to-body能拍平成兄弟关系时优先拍平。全屏fullscreen此时width/top不生效。需要每次打开都重置内容destroy-on-close。隐藏关闭入口show-close、close-on-click-modal、close-on-press-escape三个开关可分别控制右上角按钮、遮罩点击与 ESC 三种关闭路径测试用例 “click dialog to close” 与 “click header btn” 分别验证了前两条路径。TypeScript 用户可参考 types/dialog.d.ts 中的ElDialog声明其属性与上表一一对应。【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。