vue-vben-admin 弹窗组件 Vben Alert 深度指南:alert、confirm、prompt 函数式调用与源码实现
【免费下载链接】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 Alert是 vue-vben-admin 中基于 JavaScript 驱动的轻量级弹窗体系,通过alert、confirm、prompt三个函数即可在任意业务代码中弹出对话框,而无需在模板中显式维护弹窗组件。本文以官方文档 vben-alert.md 为主线,结合popup-ui包的源码与官方 Demo,完整讲解三种弹窗的调用方式、AlertProps/PromptProps全部参数、useAlertContext上下文用法,以及底层 Promise 化封装的实现原理,帮助你写出可复制的弹窗代码并理解其工作机制。
Vben Alert 是什么
Alert提供了一组轻量级、由 JavaScript 驱动的对话框,用于完成简单的alert(仅确认)、confirm(确认/取消)和prompt(用户输入)三类交互。它不同于传统"在模板中放一个<Modal>+v-model"的写法——调用方只需要写一行函数,返回的 Promise 让结果处理变得非常直观。
在仓库中,Vben Alert 的实现集中在 packages/@core/ui-kit/popup-ui/src/alert/ 目录:
- alert.ts:定义
AlertProps、PromptProps、IconType、BeforeCloseScope等核心类型,以及useAlertContext; - AlertBuilder.ts:实现
vbenAlert、vbenConfirm、vbenPrompt三个函数式入口与clearAllAlerts; - alert.vue:基于 shadcn-ui
AlertDialog封装的弹窗视图组件; - index.ts:对外统一导出。
从 index.ts 可以看到对外暴露的完整 API:
export type { AlertProps, BeforeCloseScope, IconType, PromptProps } from './alert'; export { useAlertContext } from './alert'; export { default as Alert } from './alert.vue'; export { vbenAlert as alert, clearAllAlerts, vbenConfirm as confirm, vbenPrompt as prompt } from './AlertBuilder';也就是说,在业务代码中你通常直接使用alert、confirm、prompt三个别名函数(由@vben/common-ui汇总导出,官方 Demo 中均以import { alert, confirm, prompt, useAlertContext, VbenButton } from '@vben/common-ui'方式引入)。
基本用法
alert:单确认按钮弹窗
alert用于"告知"场景,只有一个确认按钮。最简单的调用是直接传入字符串作为内容(官方 Demo 见 docs/src/demos/vben-alert/alert/index.vue):
import { alert, VbenButton } from '@vben/common-ui'; function showAlert() { alert('This is an alert message'); }传入对象可以配置图标:
function showIconAlert() { alert({ content: 'This is an alert message with icon', icon: 'success', }); }内容不仅限于字符串,还支持通过h()渲染自定义组件:
import { h } from 'vue'; import { Result } from 'antdv-next'; function showCustomAlert() { alert({ buttonAlign: 'center', content: h(Result, { status: 'success', subTitle: '已成功创建订单。订单ID:2017182818828182881', title: '操作成功', }), }); }confirm:确认/取消交互
confirm提供"确认 + 取消"两个按钮,返回的 Promise 在确认时resolve、取消时reject,因此可以用.then/.catch分流结果(官方 Demo 见 docs/src/demos/vben-alert/confirm/index.vue):
import { alert, confirm, VbenButton } from '@vben/common-ui'; function showConfirm() { confirm('This is an alert message') .then(() => { alert('Confirmed'); }) .catch(() => { alert('Canceled'); }); }带图标、自定义按钮文案、以及自定义footer(与按钮同容器,可用于追加"不再提示"等附加内容):
import { h, ref } from 'vue'; import { Checkbox, message } from 'antdv-next'; function showfooterConfirm() { const checked = ref(false); confirm({ cancelText: '不要虾扯蛋', confirmText: '是的,我们都是NPC', content: '刚才发生的事情,为什么我似乎早就经历过一般?……', footer: () => h(Checkbox, { checked: checked.value, class: 'flex-1', 'onUpdate:checked': (v) => (checked.value = v), }, '不再提示'), icon: 'question', title: '未解之谜', }).then(() => { if (checked.value) { message.success('我不会再拿这个问题烦你了'); } else { message.info('下次还要继续问你哟'); } }); }异步确认:通过beforeClose钩子在关闭前执行异步操作,只有返回非false才会真正关闭:
function showAsyncConfirm() { confirm({ beforeClose({ isConfirm }) { if (isConfirm) { // 这里可以执行一些异步操作。如果最终返回了false,将阻止关闭弹窗 return new Promise((resolve) => setTimeout(resolve, 2000)); } }, content: 'This is an alert message with async confirm', icon: 'success', }).then(() => { alert('Confirmed'); }); }prompt:获取用户输入
prompt在确认/取消的基础上内置了一个输入组件,确认后 Promise 会resolve出用户输入的值(官方 Demo 见 docs/src/demos/vben-alert/prompt/index.vue):
import { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui'; function showPrompt() { prompt({ content: '请输入一些东西' }) .then((val) => { alert(`已收到你的输入:${val}`); }) .catch(() => { alert('Canceled'); }); }通过component指定任意输入组件(默认是Input),并用modelPropName指定值绑定的属性名。下面的例子在组件函数内调用useAlertContext()拿到doConfirm,实现"输入框内按回车触发确认":
import { h } from 'vue'; import { Input } from 'antdv-next'; import { BadgeJapaneseYen } from '@lucide/vue'; function showSlotsPrompt() { prompt({ component: () => { // 获取弹窗上下文。注意:只能在setup或者函数式组件中调用 const { doConfirm } = useAlertContext(); return h(Input, { onKeydown(e: KeyboardEvent) { if (e.key === 'Enter') { e.preventDefault(); // 调用弹窗提供的确认方法 doConfirm(); } }, placeholder: '请输入', prefix: '充值金额:', type: 'number', }, { addonAfter: () => h(BadgeJapaneseYen), }); }, content: '……在输入框中按下回车键会触发确认操作。', icon: 'question', modelPropName: 'value', }).then((val) => { if (val) alert(`你输入的是${val}`); }); }传入已有组件实例(如Select),并通过componentProps传参。官方 Demo 特别指出:弹窗会设置 body 的pointer-events: none,这会阻断下拉浮层点击,因此下拉类组件需要显式声明popupClassName: 'pointer-events-auto':
import { Select } from 'antdv-next'; function showSelectPrompt() { prompt({ component: Select, componentProps: { options: [ { label: 'Option A', value: 'Option A' }, { label: 'Option B', value: 'Option B' }, { label: 'Option C', value: 'Option C' }, ], placeholder: '请选择', // 弹窗会设置body的pointer-events为none,这会影响到下拉框的点击事件 popupClassName: 'pointer-events-auto', }, content: '此弹窗演示了如何使用component传递自定义组件', icon: 'question', modelPropName: 'value', }).then((val) => { if (val) { alert(`你选择了${val}`); } }); }prompt同样支持异步校验:在beforeClose的scope中可通过scope.value拿到当前输入值,返回false阻止关闭:
function showAsyncPrompt() { prompt({ async beforeClose(scope) { if (scope.isConfirm) { if (scope.value) { // 模拟异步操作,如果不成功,可以返回false await sleep(2000); } else { alert('请选择一个选项'); return false; } } }, component: RadioGroup, componentProps: { class: 'flex flex-col', options: [ { label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' }, { label: 'Option 3', value: 'option3' }, ], }, content: '选择一个选项后再点击[确认]', icon: 'question', modelPropName: 'value', }).then((val) => { alert(`${val} 已设置。`); }); }函数签名与重载
从源码 AlertBuilder.ts 可以看到vbenAlert支持三种重载形式:
export function vbenAlert(options: AlertProps): Promise<void>; export function vbenAlert(message: string, options?: Partial<AlertProps>): Promise<void>; export function vbenAlert(message: string, title?: string, options?: Partial<AlertProps>): Promise<void>;vbenConfirm具有同样的三种重载(见 AlertBuilder.ts),其内部逻辑是:若传入的是字符串则自动补上{ showCancel: true }默认属性,再委托给vbenAlert完成渲染;vbenPrompt则统一接收PromptProps<T>对象并最终调用vbenConfirm。因此:
alert('msg')、alert('msg', '标题')、alert({ ... })均可;confirm('msg')自动带取消按钮;prompt({ ... })返回值是Promise<T | undefined>。
核心类型详解
文档给出的核心类型定义与源码 alert.ts 完全一致:
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning'; export type BeforeCloseScope = { isConfirm: boolean; }; export type AlertProps = { beforeClose?: ( scope: BeforeCloseScope, ) => boolean | Promise<boolean | undefined> | undefined; bordered?: boolean; buttonAlign?: 'center' | 'end' | 'start'; cancelText?: string; centered?: boolean; confirmText?: string; containerClass?: string; content: Component | string; contentClass?: string; contentMasking?: boolean; footer?: Component | string; icon?: Component | IconType; overlayBlur?: number; showCancel?: boolean; title?: string; }; export type PromptProps<T = any> = { beforeClose?: (scope: { isConfirm: boolean; value: T | undefined; }) => boolean | Promise<boolean | undefined> | undefined; component?: Component; componentProps?: Recordable<any>; componentSlots?: | (() => any) | Recordable<unknown> | VNode | VNodeArrayChildren; defaultValue?: T; modelPropName?: string; } & Omit<AlertProps, 'beforeClose'>;AlertProps 参数一览
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
beforeClose | (scope: BeforeCloseScope) => boolean \| Promise<boolean \| undefined> \| undefined | - | 关闭前的回调,返回false则终止关闭;scope.isConfirm表示本次关闭是否由确认触发 |
bordered | boolean | true | 是否显示边框;关闭边框时改用shadow-3xl阴影(见 alert.vue) |
buttonAlign | 'center' \| 'end' \| 'start' | 'end' | 底部按钮对齐方式 |
cancelText | string | 国际化$t('cancel') | 取消按钮文案 |
centered | boolean | true | 是否居中显示 |
confirmText | string | 国际化$t('confirm') | 确认按钮文案 |
containerClass | string | - | 弹窗容器的额外样式类 |
content | Component \| string | -(必填) | 弹窗提示内容,支持字符串或组件 |
contentClass | string | - | 弹窗内容区域的额外样式类 |
contentMasking | boolean | - | 执行beforeClose回调期间,在内容区域显示一个 loading 遮罩 |
escapeKeyClose | boolean | true | 按下 Esc 是否关闭弹窗(源码额外提供的属性,受全局globalEscapeShortcutKey配置共同控制) |
footer | Component \| string | - | 弹窗底部内容,与按钮处于同一容器 |
icon | Component \| IconType | - | 弹窗图标(位于标题之前),可用内置类型或自定义组件 |
overlayBlur | number | - | 弹窗遮罩的模糊程度 |
showCancel | boolean | false(alert)/true(confirm) | 是否显示取消按钮 |
title | string | $t('prompt') | 弹窗标题 |
其中部分默认值定义在 alert.vue:
const props = withDefaults(defineProps<AlertProps>(), { bordered: true, buttonAlign: 'end', centered: true, escapeKeyClose: true, });标题默认值在 AlertBuilder.ts 中补充为$t.value('prompt')(即"提示"类国际化文案),按钮文案的兜底则来自 alert.vue 中的cancelText || $t('cancel')与confirmText || $t('confirm'),这也解释了为什么系统语言切换后按钮文字会自动跟随。
PromptProps 专属参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
component | Component | 内置Input | 用于接收用户输入的组件 |
componentProps | Recordable<any> | - | 输入组件的属性 |
componentSlots | () => any \| Recordable<unknown> \| VNode \| VNodeArrayChildren | - | 输入组件的插槽 |
defaultValue | T | undefined | 输入组件的默认值 |
modelPropName | string | 'modelValue' | 输入组件的值属性名(如 antdv 的Input用value) |
beforeClose | (scope: { isConfirm; value }) => ... | - | 与AlertProps不同,scope中额外携带当前输入值value |
注意PromptProps是Omit<AlertProps, 'beforeClose'>与上述字段的交集,因为prompt的beforeClose签名中scope多了value字段,其余AlertProps参数(content、icon、title等)同样适用。
内置 IconType 与图标映射
icon传入字符串时,alert.vue 会将其映射为对应图标组件并套用主题色:
error→CircleX,颜色hsl(var(--destructive))info→Info,颜色hsl(var(--info))question→CircleHelpsuccess→CircleCheckBig,颜色hsl(var(--success))warning→CircleAlert,颜色hsl(var(--warning))
useAlertContext:在自定义内容中操作弹窗
当content、footer或icon通过自定义组件渲染时,如果需要在组件内部主动触发确认/取消(例如上文的"回车确认"、自定义表单中的"确定"按钮),可以调用useAlertContext()获取当前弹窗的动作:
| 方法 | 说明 | 类型 |
|---|---|---|
doConfirm | 触发确认操作 | () => void |
doCancel | 触发取消操作 | () => void |
import { useAlertContext } from '@vben/common-ui'; const { doConfirm, doCancel } = useAlertContext();从源码看,该上下文由 alert.ts 基于 shadcn-ui 的createContext提供:
export const [injectAlertContext, provideAlertContext] = createContext<AlertContext>('VbenAlertContext'); export function useAlertContext() { const context = injectAlertContext(); if (!context) { throw new Error('useAlertContext must be used within an AlertProvider'); } return context; }而上下文的生产方是 alert.vue,doConfirm/doCancel内部会先更新isConfirm标记再触发handleOpenChange(false)走关闭流程。
使用限制(官方 Demo 注释中明确说明):useAlertContext只能在setup或函数式组件中调用。这也对应了文档中的场景——它必须运行在由弹窗内容(content/footer/icon)所渲染的组件内部,因为上下文是通过组件树provide/inject传递的;若在组件外调用,injectAlertContext会取到undefined并抛出useAlertContext must be used within an AlertProvider错误。
底层实现原理:函数调用如何变成弹窗
Promise 化封装
vbenAlert的核心流程(AlertBuilder.ts)如下:
- 归一化参数:字符串会被包装为
{ content: arg0 },第二、三个参数(标题字符串或配置对象)会合并进options; - 创建一个
div容器并追加到document.body; - 通过
h(Alert, props)创建 VNode,再用render(vnode, container)将其挂载到容器上,实现"无模板声明"的动态渲染; - 监听
onClosed(isConfirm):关闭时从alerts数组移除实例、render(null, container)卸载组件、从 DOM 中移除容器(恢复页面到打开前的状态),最后按用户操作结果resolve()或reject(new Error('dialog cancelled'))。
因此.then对应确认、.catch对应取消/关闭,这是整个函数式 API 的语义根基。
beforeClose 关闭拦截流程
alert.vue 中的handleOpenChange是关闭拦截的核心:
async function handleOpenChange(val: boolean) { await nextTick(); // 等待标记isConfirm状态 if (!val && props.beforeClose) { loading.value = true; try { const res = await props.beforeClose({ isConfirm: isConfirm.value }); if (res !== false) { open.value = false; } } finally { loading.value = false; } } else { open.value = val; } }当弹窗要关闭(val === false)且配置了beforeClose时,先置loading = true,执行回调;只有回调结果!== false才真正关闭。结合contentMasking: true时会在内容区域渲染VbenLoading遮罩(alert.vue),这就是"异步确认期间按钮禁用 + 内容遮罩"的实现来源。
Esc 键行为
onEscapeKeyDown(alert.vue)中,只有当组件参数escapeKeyClose与全局偏好globalEscapeShortcutKey都为false时才阻止默认关闭行为,即两者任一为true都允许按 Esc 关闭。
prompt 的输入管理
vbenPrompt(AlertBuilder.ts)的关键实现:
- 用
modelValueref 保存输入值,modelPropName默认'modelValue',渲染输入组件时绑定[modelPropName]: modelValue.value和onUpdate:${modelPropName}回调,实现双向同步; beforeClose被包装,把scope.value = modelValue.value注入给用户回调;onOpened在弹窗打开后自动聚焦输入组件:优先调用组件exposed.focus,否则回退到原生el.focus或查询input, select, textarea, button聚焦,保证用户打开即可输入;- 最后
await vbenConfirm(props)复用确认弹窗渲染,确认后返回modelValue.value。
批量清理
clearAllAlerts(AlertBuilder.ts)遍历内部维护的alerts数组,逐个render(null, container)卸载并移除 DOM 容器,可用于路由切换或登出时强制关闭所有弹窗。
实践建议
- 校验类交互优先用
prompt+beforeClose:在回调里校验scope.value,不合法就return false,同时用alert('请选择一个选项')提示,避免打开第二个弹窗造成嵌套; - 下拉/日期等浮层组件需要
pointer-events-auto:因为弹窗会对 body 设置pointer-events: none,像Select的popupClassName必须显式声明才能正常点击选项; - 自定义内容中需要控制弹窗时用
useAlertContext:注意调用位置必须在setup/函数式组件内,回车提交、自定义表单"确定"都是典型场景; - 异步确认记得配
contentMasking:vbenPrompt已默认开启;alert/confirm手动传contentMasking: true可以在异步beforeClose期间给出视觉反馈并防止重复点击; - 需要批量关闭时调用
clearAllAlerts():该函数由 index.ts 一并导出。
更多资源
- 官方文档:docs/src/en/components/common-ui/vben-alert.md
- 中文文档:docs/src/components/common-ui/vben-alert.md
- 官方 Demo:alert、confirm、prompt
- 源码实现:alert.ts、AlertBuilder.ts、alert.vue、index.ts
- 测试用例:alert-builder.test.ts、alert.test.ts
【免费下载链接】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),仅供参考