Vue Vben Admin 弹窗组件 Vben Modal 完全指南:用法、API 与源码剖析
【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin
Vben Modal 是 Vue Vben Admin 框架内置的统一弹窗封装组件,提供可拖拽(draggable)、全屏(fullscreen)、自动高度、loading 状态、组件连接(connectedComponent)以及命令式 API 等完整能力。本文基于官方文档与 popup-ui 弹窗源码 展开,帮助你掌握useVbenModal的调用方式、modalApi命令式接口、内外组件数据共享的类型推导机制,并能结合源码理解其底层实现。
快速上手
Vben Modal 的最基础用法是调用useVbenModal组合式函数,它会返回[Modal, modalApi]元组——前者是可渲染的弹窗组件,后者是控制弹窗的命令式 API:
const [Modal, modalApi] = useVbenModal({ // props 配置 // events 事件 });在模板中渲染Modal并挂载到页面,即可得到一个功能完整的弹窗:
<script lang="ts" setup> import { useVbenModal, VbenButton } from '@vben/common-ui'; const [Modal, modalApi] = useVbenModal(); </script> <template> <div> <VbenButton @click="() => modalApi.open()">Open</VbenButton> <Modal class="w-150" title="基础示例"> modal content </Modal> </div> </template>完整可运行示例见 basic 示例。
使用注意事项
优先使用连接组件与 API
- 当存在
connectedComponent时,内外层组件通过modalApi.setData()与modalApi.getData()共享数据; - 连接了
connectedComponent后,应避免在连接侧继续传额外的弹窗 props,推荐改用useVbenModal(...)或modalApi.setState(...)来调整弹窗行为; - 默认弹窗行为可在
apps/<app>/src/bootstrap.ts中通过setDefaultModalProps(...)全局调整。
源码层面,use-modal.ts中的checkProps会在开发环境对传入属性进行校验,若检测到连接组件时仍传入弹窗 props/slots,会输出警告提示改用useVbenModal或 API(见 use-modal.ts)。
默认配置调整
每个应用入口的bootstrap.ts中默认保留了setDefaultModalProps的注释示例,取消注释即可全局生效,例如:
// apps/web-antd/src/bootstrap.ts // 设置弹窗的默认配置 setDefaultModalProps({ fullscreenButton: false, });该函数在 use-modal.ts 中实现,会将传入的 props 合并进模块级DEFAULT_MODAL_PROPS,并在每次创建弹窗时与局部配置合并(优先级从低到高为默认配置 → 注入配置 → 局部配置)。参见 apps/web-antd/src/bootstrap.ts。
共享数据类型
推荐的数据契约做法:在连接组件(内部组件)中声明一次数据类型,并通过defineExpose暴露modalApi,外层调用即可从connectedComponent自动推导数据契约:
// 连接组件(内部组件)中声明类型 const [Modal, modalApi] = useVbenModal<EditData>(); defineExpose({ modalApi });// 外层组件中调用,EditData 被自动推断 const [Modal, modalApi] = useVbenModal({ connectedComponent: EditModal, });当组件类型无法暴露契约时(例如泛型 SFC、函数式组件、被拓宽为Component的组件),需显式指定泛型:
const [Modal, modalApi] = useVbenModal<EditData>({ connectedComponent: EditModal, });对于较大的功能模块,推荐预先在独立模块中绑定一个可复用的数据契约:
export const useEditModal = createVbenModal<EditData>();其中createVbenModal的实现见 use-modal.ts,它返回一个预置了泛型TData的useVbenModal包装函数。
类型推导优先级
类型推导的优先级为:显式泛型 > 连接组件推导 >unknown。普通 SFC 支持通过defineExpose推导;泛型 SFC、函数式组件以及被拓宽为Component的组件则应当使用显式泛型或契约工厂。
底层推导逻辑位于InferModalData类型定义中,它通过读取组件实例上的modalApi类型来提取TData,见 modal.ts。
getData 返回规则
getData()在setData()被调用之前返回undefined。如果null或部分载荷是合法的业务值,应当把它们包含在数据类型中(联合类型),例如EditData | null。
const [Modal, modalApi] = useVbenModal<SharedData>({ onOpenChange(isOpen: boolean) { if (isOpen) { data.value = modalApi.getData(); } }, });核心 Props
| Prop | 说明 | 类型 |
|---|---|---|
appendToMain | 挂载到主内容区而不是body | boolean |
connectedComponent | 将内部组件连接到弹窗外壳 | Component |
animationType | 弹窗进出场动画 | 'slide' \| 'scale' |
fullscreenButton | 显示/隐藏全屏切换按钮 | boolean |
overlayBlur | 遮罩模糊程度 | number |
submitting | 提交中锁定弹窗交互 | boolean |
除此之外,完整的ModalProps还包括(默认值以注释标出,来源于 modal.ts):
| Prop | 说明 | 默认值 |
|---|---|---|
bordered | 是否显示边框 | false |
cancelText/confirmText | 取消/确认按钮文案 | — |
centered | 是否垂直居中 | false |
closable | 是否显示右上角关闭按钮 | true |
closeOnClickModal | 点击遮罩是否关闭 | true |
closeOnPressEscape | 按 ESC 是否关闭 | true |
confirmDisabled | 禁用确认按钮 | false |
confirmLoading | 确认按钮 loading | false |
destroyOnClose | 关闭时销毁弹窗 | false(组件默认) |
draggable | 是否可拖拽 | false |
footer/header | 是否显示底部/顶栏 | true |
fullscreen | 是否全屏 | false |
loading | 弹窗加载状态 | false |
modal | 是否显示遮罩 | true |
openAutoFocus | 是否自动聚焦 | false |
overflow | 拖动范围是否可超出可视区 | false |
showCancelButton/showConfirmButton | 显示取消/确认按钮 | true |
title/titleTooltip | 标题与标题提示 | — |
zIndex | 弹窗层级 | — |
动画类型与遮罩
animationType支持'slide'(默认)与'scale'两种切换动画;overlayBlur传入数值时,遮罩层会叠加对应像素的模糊效果。二者均由 modal.vue 直接透传给底层DialogContent渲染。
拖拽与全屏的联动
弹窗的拖拽由useModalDraggable实现(见 use-modal-draggable.ts),其拖拽句柄为顶栏(header),且仅在draggable && !fullscreen && header同时满足时启用;全屏状态下自动禁用拖拽与居中(见 modal.vue)。该实现参考了 element-plus 的use-draggable思路,可拖拽时会对位移做边界约束,overflow: true时允许超出可视区。
挂载位置
appendToMain: true时,弹窗内容会挂载到主内容区域(#ELEMENT_ID_MAIN_CONTENT)而不是body,这在嵌入 iframe 页面或需要跟随主内容滚动的场景下非常有用。
Events
| Event | 说明 | 类型 |
|---|---|---|
onBeforeClose | 关闭前回调,返回false或 Promise 被 reject 时阻止关闭 | () => Promise<boolean \| undefined> \| boolean \| undefined |
onOpenChange | 打开状态变化时调用 | (isOpen: boolean) => void |
onOpened | 打开动画结束后调用 | () => void |
onClosed | 关闭动画结束后调用 | () => void |
onBeforeClose是拦截关闭的关键钩子。在 modal-api.ts 中,close()会先等待onBeforeClose的结果,只有结果为true/undefined(未返回false)时才真正把isOpen置为false。这使你可以用它实现“表单未保存时二次确认”之类的拦截逻辑:
const [Modal, modalApi] = useVbenModal({ async onBeforeClose() { if (hasUnsavedChanges.value) { return window.confirm('确定放弃未保存的修改吗?'); } return true; }, });onOpenChange在每次isOpen状态变化时触发(由 store 订阅驱动,见 modal-api.ts);onOpened/onClosed则分别由 modal.vue 在动画结束的requestAnimationFrame与关闭回调中调用。
modalApi 方法
| 方法 | 说明 |
|---|---|
setState(...) | 更新弹窗状态 |
open() | 打开弹窗 |
close() | 关闭弹窗 |
setData(data: TData) | 存储类型化的共享数据 |
getData() | 返回TData \| undefined |
lock(isLocked = true) | 将弹窗锁定为提交中状态 |
unlock() | lock(false)的别名 |
modalApi由ModalApi类实现,并扩展了useStore方法(见 modal-api.ts)。其内部通过@vben-core/shared/store的Store维护状态,支持setState传入对象或更新函数两种形式。
lock / unlock 提交锁定
lock()是提交场景的关键方法,它等价于setState({ submitting: true })。源码注释明确说明其行为(见 modal-api.ts):
- 禁用默认的取消按钮;
- 使用 spinner 覆盖弹窗内容;
- 隐藏关闭按钮;
- 阻止手动关闭弹窗;
- 将默认的提交按钮标记为 loading。
配合submittingprop,在 modal.vue 中还会阻止 ESC 关闭、点击遮罩关闭与外部交互。提交完成后再调用unlock()(即lock(false))恢复交互。
async function handleConfirm() { modalApi.lock(); // 锁定 try { await saveData(); modalApi.close(); } finally { modalApi.unlock(); // 解锁 } }setState 更新
setState支持两种调用方式,均返回this以便链式调用:
modalApi.setState({ title: '新标题', fullscreen: true }); modalApi.setState((prev) => ({ ...prev, centered: !prev.centered }));典型业务场景示例
数据共享(connectedComponent)
外层通过setData传参,内部在onOpenChange中通过getData读取,实现内外数据双向传递:
<!-- 外层组件 --> <script lang="ts" setup> import { useVbenModal, VbenButton } from '@vben/common-ui'; import ExtraModal from './modal.vue'; const [Modal, modalApi] = useVbenModal({ connectedComponent: ExtraModal, }); function openModal() { modalApi .setData({ content: '外部传递的数据 content', payload: '外部传递的数据 payload', }) .open(); } </script> <template> <div> <Modal /> <VbenButton @click="openModal">Open</VbenButton> </div> </template><!-- 内部连接组件 modal.vue --> <script lang="ts" setup> import { ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; interface SharedData { content: string; payload: string; } const data = ref<SharedData>(); const [Modal, modalApi] = useVbenModal<SharedData>({ onCancel() { modalApi.close(); }, onConfirm() { console.info('onConfirm'); }, onOpenChange(isOpen: boolean) { if (isOpen) { data.value = modalApi.getData(); } }, }); defineExpose({ modalApi }); </script> <template> <Modal title="数据共享示例"> <div class="flex-col-center">外部传递数据: {{ data }}</div> </Modal> </template>完整代码见 shared-data 示例 与 内部组件。
可拖拽弹窗
只需在useVbenModal配置中开启draggable: true,即可通过顶栏拖拽移动弹窗:
<script lang="ts" setup> import { useVbenModal } from '@vben/common-ui'; const [Modal] = useVbenModal({ draggable: true, }); </script> <template> <Modal title="拖拽示例"> modal content </Modal> </template>见 draggable 示例;其余场景(动画类型、自动高度、动态渲染、底部扩展区)可参考 animation-type、auto-height、dynamic、extra 等示例。
源码架构速览
| 文件 | 职责 |
|---|---|
| use-modal.ts | useVbenModal/createVbenModal/setDefaultModalProps组合式入口,负责 provide/inject 连接与类型推导 |
| modal-api.ts | ModalApi类,命令式 API 与状态 store 管理 |
| modal.ts | ModalProps/ModalState/ModalApiOptions/InferModalData类型定义 |
| modal.vue | 基于 reka-uiDialog的弹窗模板实现 |
| use-modal-draggable.ts | 拖拽逻辑封装 |
| index.ts | 对外导出入口 |
外部使用时从@vben/common-ui导入useVbenModal、VbenModal等即可。弹窗内部通过provide('DISMISSABLE_MODAL_ID', id)标记遮罩,只有点击发生在当前弹窗自己的遮罩上才允许关闭(见 modal.vue);onDeactivated时若未挂载到主内容区,会自动关闭弹窗,保证 KeepAlive 场景下的状态一致。
总结
Vben Modal 通过useVbenModal组合式 API 与命令式modalApi提供了完整且类型安全的弹窗方案:connectedComponent连接模式配合setData/getData与类型推导机制解决了内外组件数据传递的难题;lock/unlock与onBeforeClose覆盖了提交锁定与关闭拦截的常见业务诉求;拖拽、全屏、动画、遮罩模糊等能力则让弹窗在复杂交互场景下依然保持一致性。结合 popup-ui 源码 阅读,可以更深入地理解其状态管理与生命周期设计。
【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考