Modal 模态框
模态对话框,在当前页面打开一个浮层,承载相关操作。
基础用法
最简单的用法。默认为中等尺寸(520px)。
<template>
<ExButton type="primary" @click="visible = true">打开模态框</ExButton>
<ExModal v-model="visible" title="基础模态框">
<p>这是模态框的内容</p>
</ExModal>
</template>
<script setup>
import { ref } from 'vue';
const visible = ref(false);
</script>不同类型
通过 type 属性可以设置不同类型的模态框,不同类型会有不同的边框颜色和发光效果。
<template>
<ExButton type="primary" @click="visible = true">主要模态框</ExButton>
<ExModal v-model="visible" title="主要模态框" type="primary" size="medium">
<template #icon>
<img
src="https://api.iconify.design/ri/information-line.svg"
alt="信息"
width="24"
height="24"
/>
</template>
<p>这是一个主要类型的模态框,带有霓虹蓝色边框和发光效果。</p>
</ExModal>
</template>不同尺寸
通过 size 属性可以设置不同尺寸的模态框。
<template>
<ExButton @click="visible = true">小尺寸</ExButton>
<ExModal v-model="visible" title="小尺寸模态框" size="small">
<p>这是一个小尺寸的模态框。</p>
</ExModal>
</template>自定义宽度
通过 width 属性可以自定义模态框宽度。
<template>
<ExButton type="primary" @click="visible = true">自定义宽度</ExButton>
<ExModal v-model="visibleWidth" title="自定义宽度" :width="800">
<p>这个模态框的宽度是 800px。</p>
</ExModal>
</template>居中显示
设置 centered 属性可以让模态框垂直居中显示。
<template>
<ExButton type="primary" @click="visible = true">居中模态框</ExButton>
<ExModal v-model="visibleCenter" title="居中显示" size="medium" centered>
<div style="text-align: center;">
<div style="font-size: 48px;">🏆</div>
<div>恭喜获胜!</div>
</div>
</ExModal>
</template>自定义按钮文字
通过 okText 和 cancelText 属性可以自定义按钮文字。
<template>
<ExButton type="primary" @click="visible = true">自定义按钮</ExButton>
<ExModal
v-model="visible"
title="删除确认"
ok-text="删除"
cancel-text="我再想想"
ok-type="danger"
>
<p>确定要删除这条记录吗?</p>
</ExModal>
</template>自定义底部
通过 footer 插槽可以自定义底部内容。
<template>
<ExButton type="primary" @click="visible = true">自定义底部</ExButton>
<ExModal v-model="visibleCustomFooter" title="自定义底部" size="medium">
<p>这是一个自定义底部的模态框。</p>
<template #footer>
<ExButton type="default" @click="visibleCustomFooter = false">知道了</ExButton>
</template>
</ExModal>
</template>确认加载状态
通过 confirmLoading 属性可以让确认按钮处于加载状态。需要配合 before-close 属性来阻止模态框自动关闭。
<template>
<ExButton type="primary" @click="visible = true">加载状态</ExButton>
<ExModal
v-model="visible"
title="提交表单"
:confirm-loading="confirmLoading"
:before-close="() => !confirmLoading"
@ok="handleOk"
>
<p>点击确定按钮后,按钮会显示加载状态。</p>
<p>2秒后自动关闭。</p>
</ExModal>
</template>
<script setup>
import { ref } from 'vue';
const visible = ref(false);
const confirmLoading = ref(false);
const handleOk = () => {
confirmLoading.value = true;
// 模拟异步操作(如API请求)
setTimeout(() => {
confirmLoading.value = false;
visible.value = false; // 操作完成后关闭模态框
}, 2000);
};
</script>不可点击遮罩关闭
设置 :mask-closable="false" 可以禁止点击遮罩层关闭。
<template>
<ExButton type="primary" @click="visible = true">不可点击遮罩关闭</ExButton>
<ExModal v-model="visible" title="重要提示" :mask-closable="false">
<p>这个模态框不能通过点击遮罩层关闭。</p>
</ExModal>
</template>自定义样式
通过 bodyStyle 属性可以自定义内容区域样式。
<template>
<ExButton type="primary" @click="visible = true">自定义样式</ExButton>
<ExModal
v-model="visible"
title="自定义样式"
:body-style="{
background: 'linear-gradient(135deg, rgba(0, 240, 255, 0.1), rgba(255, 0, 127, 0.1))',
minHeight: '200px',
}"
>
<div>自定义内容样式</div>
</ExModal>
</template>嵌套模态框
模态框可以嵌套使用。
<template>
<ExButton type="primary" @click="outerVisible = true">打开外层模态框</ExButton>
<ExModal v-model="outerVisible" title="外层模态框">
<p>这是外层模态框的内容。</p>
<ExButton type="primary" @click="innerVisible = true">打开内层模态框</ExButton>
<ExModal v-model="innerVisible" title="内层模态框" :z-index="1010">
<p>这是内层模态框的内容。</p>
</ExModal>
</ExModal>
</template>
<script setup>
import { ref } from 'vue';
const outerVisible = ref(false);
const innerVisible = ref(false);
</script>API
Modal Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
model-value / v-model | 是否显示模态框 | boolean | false |
title | 模态框标题 | string | '' |
size | 模态框尺寸 | 'small' | 'medium' | 'large' | 'medium' |
type | 模态框类型 | 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default' |
show-close | 是否显示关闭按钮 | boolean | true |
mask | 是否显示遮罩层 | boolean | true |
mask-closable | 是否可以通过点击遮罩层关闭 | boolean | true |
keyboard | 是否可以通过按下 ESC 键关闭 | boolean | true |
ok-text | 确认按钮文本 | string | '确定' |
cancel-text | 取消按钮文本 | string | '取消' |
ok-type | 确认按钮类型 | 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'primary' |
confirm-loading | 确认按钮加载状态 | boolean | false |
centered | 是否居中显示 | boolean | false |
width | 宽度 | string | number | undefined |
z-index | 层级 | number | 1000 |
append-to-body | 是否追加到 body | boolean | true |
destroy-on-close | 是否在关闭时销毁内容 | boolean | false |
wrap-class-name | 包装器自定义类名 | string | '' |
body-style | 内容区域自定义样式 | Record<string, string | number> | undefined |
before-close | 模态框关闭前的回调 | () => boolean | Promise<boolean> | undefined |
aria-label | 无障碍标签 | string | undefined |
Modal Events
| 事件名 | 说明 | 类型 |
|---|---|---|
update:model-value | 更新 modelValue | (value: boolean) => void |
ok | 点击确定按钮时触发 | () => void |
cancel | 点击取消按钮时触发 | () => void |
after-close | 模态框关闭动画结束后触发 | () => void |
Modal Slots
| 插槽名 | 说明 |
|---|---|
default | 模态框内容 |
title | 自定义标题 |
icon | 自定义图标 |
footer | 自定义底部内容 |
closeIcon | 自定义关闭按钮图标 |
Modal Methods
| 方法名 | 说明 | 类型 |
|---|---|---|
getElement | 获取模态框 DOM 元素 | () => HTMLDivElement | null |
无障碍支持
- 使用
role="dialog"和aria-modal属性 - 支持 ESC 键关闭
- 完整的 ARIA 标签支持
- 支持屏幕阅读器
- 自动锁定焦点在模态框内
受控 / 非受控
Modal 通过 v-model(modelValue)受控:
<template>
<ExModal v-model="visible" title="标题" @ok="onOk" @cancel="onCancel">内容</ExModal>
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(false)
</script>- 点击确定先触发
ok,再经before-close校验,通过后自动关闭;点击取消/遮罩/ESC 触发cancel。 - 异步提交时用
:confirm-loading="true"锁定确定按钮,并用before-close在校验未过时返回false阻止关闭。
Modal 与 Dialog 功能相近:Modal 偏 Ant Design 风格(
ok/cancel、mask、keyboard),Dialog 偏 Element 风格(confirm/cancel、modal、close-on-press-escape)。同一项目建议只选其一以保持一致。
常见问题
Q:ok 和 cancel 与 before-close 的执行顺序? 先 emit ok/cancel,再执行 before-close;before-close 返回 false 时不更新 modelValue,模态框保持打开。
Q:如何禁止 ESC / 点击遮罩关闭? 设置 :keyboard="false" 与 :mask-closable="false"。
Q:多个模态框叠加? 支持叠加:滚动锁引用计数、ESC 只关最顶层、关闭后焦点归还触发元素。
最佳实践
- 二次确认/表单提交用
confirm-loading+before-close,提交成功后关闭。 - 危险操作用
ok-type="danger"强化视觉警示。 - 始终设置
title或aria-label以满足无障碍。
主题定制
Modal 不定义独立的组件级变量,而是消费 ExUI 全局设计令牌。定制方式:
- 单个实例:用
wrap-class-name/body-style定制。 - 全局:覆盖所消费的全局令牌:
:root {
--ex-color-bg-surface: #11151c; /* 模态框背景 */
--ex-color-border-primary: #1f2a37; /* 边框 / 分割线 */
}