7.3 KiB
title, description
| title | description |
|---|---|
| Modal 对话框 | 模态窗口,支持拖动、缩放、全屏与命令式确认框 |
Modal 对话框
Modal 在当前页面上方打开一个模态窗口,用户必须先处理它才能继续。
何时使用
- 需要用户聚焦处理一件事:填写表单、确认危险操作、查看详情;
- 需要打断当前流程、且不该被忽略时(用
maskClosable=false、keyboard=false、closable=false收紧关闭入口); - 只是提示结果、不需要用户操作时,用 Message 更轻;
- 内容很长、或需要保留页面上下文时,可以考虑 Drawer。
基础用法
v-model:open 控制显隐。三个事件区分关闭来源:confirm(底部确认按钮)、cancel(取消按钮 / 遮罩 / Esc / 右上角关闭)、closed(退场动画结束)。
:::demo{name="modal/basic"} :::
拖动、缩放与全屏
默认允许拖动标题栏移动面板、拖拽四边缩放、右上角切换全屏。三者互相协调:进入全屏会复位拖拽位置并禁用缩放手柄。
:::demo{name="modal/resize"} :::
居中显示
面板默认贴顶(align-items: flex-start)——这样长内容不会把标题栏顶出视口。确认类、短表单可以开 centered 垂直居中。
:::demo{name="modal/resize"} :::
控制关闭入口
maskClosable / keyboard / closable / footer 分别控制遮罩点击、Esc、右上角关闭按钮与底部按钮区。
:::demo{name="modal/mask"} :::
表单弹窗与异步确认
confirmLoading 期间阻止一切关闭(含遮罩、Esc、取消按钮),避免异步提交被打断;底部按钮自动进入加载态。
:::demo{name="modal/form"} :::
命令式确认框
const handle = G3Modal.confirm({
title: '确定删除?',
content: '删除后不可恢复',
confirmText: '删除',
confirmButtonProps: { danger: true },
onConfirm: async () => {
await api.remove() // 返回 Promise 时按钮 loading
},
onCancel: () => {},
})
handle.destroy() // 立即关闭并卸载(不等退场动画)
:::demo{name="modal/confirm"} :::
onConfirm 的 Promise 语义:resolve → 关闭;reject → 保持打开、解除 loading,错误由调用方提示。
onCancel 的触发范围:只有点击「取消」按钮才会调用 onCancel;点遮罩空白、Esc、右上角关闭按钮都是静默关闭(只关窗,不跑取消逻辑),避免用户随手点空白就触发取消副作用。
:::demo{name="modal/confirm-async"} :::
自定义底部与头部
#footer 完全接管底部;#header-extra 放标题右侧的额外动作。
:::demo{name="modal/footer-slot"} :::
API
Props(Modal)
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
open |
boolean |
— | 受控打开状态;不传即非受控 |
defaultOpen |
boolean |
false |
非受控时的初始状态 |
title |
string |
'' |
标题 |
width |
number | string |
520 |
面板宽度。数字按 px,'60%' 之类的字符串原样使用 |
centered |
boolean |
false |
垂直居中(默认贴顶,长内容不会把标题顶出视口) |
mask |
boolean |
true |
是否显示遮罩 |
maskClosable |
boolean |
true |
点击遮罩空白关闭 |
keyboard |
boolean |
true |
Esc 关闭 |
closable |
boolean |
true |
显示右上角关闭按钮 |
fullscreen |
boolean |
true |
显示右上角全屏切换按钮 |
resizable |
boolean |
true |
允许拖拽四边缩放宽高 |
footer |
boolean |
true |
显示底部按钮区(取消 + 确认) |
confirmText |
string |
语言包 common.confirm |
确认按钮文字 |
cancelText |
string |
语言包 common.cancel |
取消按钮文字 |
confirmLoading |
boolean |
false |
确认中:按钮 loading,并阻止一切关闭操作 |
popupContainer |
PopupContainer |
ConfigProvider 配置 | 挂载容器,默认 body |
Events(Modal)
| 名称 | 参数 | 说明 |
|---|---|---|
update:open |
(open: boolean) |
打开状态变化 |
confirm |
() |
点击底部确认按钮(不自动关闭,由你决定何时关) |
cancel |
() |
取消按钮 / 遮罩 / Esc / 右上角关闭被触发 |
closed |
() |
退场动画结束、DOM 已销毁(此时滚动锁已释放、焦点已归还) |
Slots(Modal)
| 名称 | 参数 | 说明 |
|---|---|---|
default |
— | 正文 |
header-extra |
— | 标题右侧的自定义动作(在全屏/关闭按钮之前) |
footer |
— | 自定义底部;不传时渲染「取消 + 确认」 |
G3Modal.confirm 参数
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title |
string |
'' |
标题 |
content |
string |
'' |
正文纯文本 |
confirmText |
string |
语言包 | 确认按钮文字 |
cancelText |
string |
语言包 | 取消按钮文字 |
confirmButtonProps |
Partial<ButtonProps> & { danger?: boolean } |
{} |
透传给确认按钮;danger: true 等价于 type: 'danger' |
onConfirm |
() => void | Promise<unknown> |
— | 确认回调,返回 Promise 时按钮 loading |
onCancel |
() => void |
— | 取消回调(仅点击取消按钮触发) |
返回值:{ destroy: () => void }。
类型定义
export interface ModalProps {
open?: boolean
defaultOpen?: boolean
title?: string
width?: number | string
centered?: boolean
mask?: boolean
maskClosable?: boolean
keyboard?: boolean
closable?: boolean
fullscreen?: boolean
resizable?: boolean
footer?: boolean
confirmText?: string
cancelText?: string
confirmLoading?: boolean
popupContainer?: PopupContainer
}
export interface ModalConfirmOptions {
title?: string
content?: string
confirmText?: string
cancelText?: string
confirmButtonProps?: Partial<ButtonProps> & { danger?: boolean }
onConfirm?: () => void | Promise<unknown>
onCancel?: () => void
}
export interface ModalConfirmHandle {
destroy: () => void
}
实现说明
- 遮罩点击判定:只响应「点击事件的目标就是外层容器」且「mousedown 不在面板内」的情况。后者是为了防拖选文本:在输入框里按下鼠标、拖到遮罩上松开时,
click.target会回溯成共同的祖先容器,如果只看 click 就会误判成「点了遮罩要关闭」; - 焦点与滚动:打开时记录当前焦点、锁 body 滚动、把焦点移进面板,并用 Tab 循环把焦点约束在面板内;关闭时等退场动画结束才解锁滚动、复位尺寸、把焦点还给触发元素——否则动画还在播的时候背景就能滚动、焦点提前跳走,屏幕阅读器会跟丢;
- 滚动锁是引用计数:Modal 里再开 Drawer、或弹窗里开下拉,不会互相提前解锁;
- 命令式挂载:
G3Modal.confirm()自建容器挂到body,open初始即为true,因此内部对isOpen用了immediate: true的 watch、并给Transition加了appear,让「挂载」与「受控打开」走同一段初始化逻辑,退场动画结束后再卸载容器; - 层级:打开时从 z-index 注册表取
modal档,闭包回收;浮层类(下拉、日期面板)取popup档,所以弹窗里的浮层一定盖在弹窗上。