Dialog 弹出框 - Vant 4
Dialog 弹出框
🎯 介绍
弹出框是用户交互中的重要角色!它就像一个礼貌的服务员,会在关键时刻出现,向用户传达重要信息或请求确认。无论是温馨提示、重要确认,还是复杂的交互操作,Dialog 都能优雅地完成任务。支持组件调用和函数调用两种方式,让你的开发更加灵活。
📦 引入
通过以下方式来全局注册组件,更多注册方式请参考组件注册。
import { createApp } from 'vue';
import { Dialog } from 'vant';
const app = createApp();
app.use(Dialog);🚀 函数调用
为了让你的开发更加便捷,Vant 提供了一系列超实用的辅助函数!通过这些函数,你可以像变魔术一样快速唤起全局的弹窗组件。
比如使用 showDialog 函数,一行代码就能在页面中展示漂亮的弹出框。
import { showDialog } from 'vant';
showDialog({
message: '这是一个友好的提示!'
});🎨 代码演示
💬 消息提示 - 温柔的信息传递者
最简单却最贴心的用法!当你需要向用户传达重要信息时,消息提示就像一位温柔的信使,优雅地出现在用户面前。默认只包含一个确认按钮,简洁明了,让用户专注于信息本身。
import { showDialog } from 'vant';
showDialog({
title: '温馨提示',
message: '代码是写出来给人看的,附带能在机器上运行。',
}).then(() => {
// 用户点击确认后的回调
console.log('用户已确认');
});
showDialog({
message: '生命远不止连轴转和忙到极限,人类的体验远比这辽阔、丰富得多。',
}).then(() => {
// 用户关闭弹窗后的回调
console.log('弹窗已关闭');
});❓ 消息确认 - 智慧的决策助手
当需要用户做出重要决定时,消息确认就像一位智慧的顾问,为用户提供清晰的选择。默认包含确认和取消按钮,让用户在深思熟虑后做出最佳选择。
import { showConfirmDialog } from'vant'; showConfirmDialog({ title: '标题', message: '如果解决方法是丑陋的,那就肯定还有更好的解决方法,只是还没有发现而已。', }) .then(() => { // on confirm }) .catch(() => { // on cancel });🎨 圆角按钮风格 - 柔美的视觉享受
让弹窗变得更加温柔可爱!将 theme 选项设置为 round-button,就能展示圆角按钮风格的弹窗,圆润的边角如春风般温和,为用户带来更加舒适的视觉体验。
import { showDialog } from'vant'; showDialog({ title: '标题', message: '代码是写出来给人看的,附带能在机器上运行。', theme: 'round-button', }).then(() => { // on close }); showDialog({ message: '生命远不止连轴转和忙到极限,人类的体验远比这辽阔、丰富得多。', theme: 'round-button', }).then(() => { // on close });⏳ 异步关闭 - 优雅的告别仪式
有时候告别也需要一个优雅的过程!通过 beforeClose 属性这个贴心的管家,可以传入一个回调函数,在弹窗关闭前进行特定操作,让每一次关闭都变得从容不迫。
import { showConfirmDialog } from'vant'; constbeforeClose = (action) => newPromise((resolve) => { setTimeout(() => { // action !== 'confirm' 拦截取消操作resolve(action === 'confirm'); }, 1000); }); showConfirmDialog({ title: '标题', message: '如果解决方法是丑陋的,那就肯定还有更好的解决方法,只是还没有发现而已。', beforeClose, });🧩 使用 Dialog 组件 - 自由创作的画布
当你需要更多创作自由时,Dialog 组件就像一张空白的画布等待你的创意!如果需要在 Dialog 内嵌入组件或其他自定义内容,可以直接使用 Dialog 组件,并使用默认插槽进行个性化定制。使用前需要通过 app.use 等方式注册组件。
import { ref } from'vue'; exportdefault { setup() { const show = ref(false); return { show }; }, };API
方法
Vant 中导出了以下 Dialog 相关的辅助函数:
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| showDialog | 展示消息提示弹窗,默认包含确认按钮 | options: DialogOptions | Promise<void> |
| showConfirmDialog | 展示消息确认弹窗,默认包含确认和取消按钮 | options: DialogOptions | Promise<void> |
| closeDialog | 关闭当前展示的弹窗 | - | void |
| setDialogDefaultOptions | 修改默认配置,影响所有的 showDialog 调用 | options: DialogOptions | void |
| resetDialogDefaultOptions | 重置默认配置,影响所有的 showDialog 调用 | - | void |
DialogOptions
调用 showDialog 等方法时,支持传入以下选项:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| title | 标题 | string | - |
| width | 弹窗宽度,默认单位为 px | *number | string* |
| message | 文本内容,支持通过 \n 换行 | *string | () => JSX.ELement* |
| messageAlign | 内容对齐方式,可选值为 left``right | string | center |
| theme | 样式风格,可选值为 round-button | string | default |
| className | 自定义类名 | *string | Array |
| showConfirmButton | 是否展示确认按钮 | boolean | true |
| showCancelButton | 是否展示取消按钮 | boolean | false |
| confirmButtonText | 确认按钮文案 | string | 确认 |
| confirmButtonColor | 确认按钮颜色 | string | #ee0a24 |
| confirmButtonDisabled | 是否禁用确认按钮 | boolean | false | | cancelButtonText | 取消按钮文案 | string | 取消 | | cancelButtonColor | 取消按钮颜色 | string | black | | cancelButtonDisabled | 是否禁用取消按钮 | boolean | false | | destroyOnClose v4.9.18 | 是否在关闭时销毁内容 | boolean | false | | overlay | 是否展示遮罩层 | boolean | true | | overlayClass | 自定义遮罩层类名 | string | Array | object | - | | overlayStyle | 自定义遮罩层样式 | object | - | | closeOnPopstate | 是否在页面回退时自动关闭 | boolean | true | | closeOnClickOverlay | 是否在点击遮罩层后关闭弹窗 | boolean | false | | lockScroll | 是否锁定背景滚动 | boolean | true | | allowHtml | 是否允许 message 内容中渲染 HTML | boolean | false | | beforeClose | 关闭前的回调函数,返回 false 可阻止关闭,支持返回 Promise | (action: string) => boolean | Promise<boolean> | - | | transition | 动画类名,等价于 transition 的 name 属性 | string | - |
| teleport | 指定挂载的节点,等同于 Teleport 组件的 to 属性 | string | Element | body |
| keyboardEnabled | 是否启用键盘能力,在展示确认和取消按钮的时候,默认情况下键盘的 Enter 和 Esc 会执行 confirm 和 cancel 函数 | boolean | true |
Props
通过组件调用 Dialog 时,支持以下 Props:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| v-model:show | 是否显示弹窗 | boolean | - |
| title | 标题 | string | - |
| width | 弹窗宽度,默认单位为 px | *number | string* |
| message | 文本内容,支持通过 \n 换行 | *string | () => JSX.Element* |
| message-align | 内容水平对齐方式,可选值为 left``right``justify | string | center |
| theme | 样式风格,可选值为 round-button | string | default |
| show-confirm-button | 是否展示确认按钮 | boolean | true |
| show-cancel-button | 是否展示取消按钮 | boolean | false |
| confirm-button-text | 确认按钮文案 | string | 确认 |
| confirm-button-color | 确认按钮颜色 | string | #ee0a24 |
| confirm-button-disabled | 是否禁用确认按钮 | boolean | false | | cancel-button-text | 取消按钮文案 | string | 取消 | | cancel-button-color | 取消按钮颜色 | string | black | | cancel-button-disabled | 是否禁用取消按钮 | boolean | false | | destroy-on-close v4.9.18 | 是否在关闭时销毁内容 | boolean | false | | z-index | 将弹窗的 z-index 层级设置为一个固定值 | number | string | 2000+ | | overlay | 是否展示遮罩层 | boolean | true | | overlay-class | 自定义遮罩层类名 | string | - | | overlay-style | 自定义遮罩层样式 | object | - | | close-on-popstate | 是否在页面回退时自动关闭 | boolean | true | | close-on-click-overlay | 是否在点击遮罩层后关闭弹窗 | boolean | false | | lazy-render | 是否在显示弹层时才渲染节点 | boolean | true | | lock-scroll | 是否锁定背景滚动 | boolean | true | | allow-html | 是否允许 message 内容中渲染 HTML | boolean | false | | before-close | 关闭前的回调函数,返回 false 可阻止关闭,支持返回 Promise | (action: string) => boolean | Promise<boolean> | - | | transition | 动画类名,等价于 transition 的 name 属性 | string | - |
| teleport | 指定挂载的节点,等同于 Teleport 组件的 to 属性 | string | Element | - |
| keyboard-enabled | 是否启用键盘能力,在展示确认和取消按钮的时候,默认情况下键盘的 Enter 和 Esc 会执行 confirm 和 cancel 函数 | boolean | true |
Events
通过组件调用 Dialog 时,支持以下事件:
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| confirm | 点击确认按钮时触发 | - |
| cancel | 点击取消按钮时触发 | - |
| open | 打开弹窗时触发 | - |
| close | 关闭弹窗时触发 | - |
| opened | 打开弹窗且动画结束后触发 | - |
| closed | 关闭弹窗且动画结束后触发 | - |
Slots
通过组件调用 Dialog 时,支持以下插槽:
| 名称 | 说明 |
|---|---|
| default | 自定义内容 |
| title | 自定义标题 |
| footer | 自定义底部按钮区域 |
类型定义
组件导出以下类型定义:
importtype { DialogProps, DialogTheme, DialogMessage, DialogOptions, DialogMessageAlign, } from'vant';主题定制
样式变量
组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 ConfigProvider 组件。
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-dialog-width | 320px | - |
| --van-dialog-small-screen-width | 90% | - |
| --van-dialog-font-size | var(--van-font-size-lg) | - |
| --van-dialog-transition | var(--van-duration-base) | - |
| --van-dialog-radius | 16px | - |
| --van-dialog-background | var(--van-background-2) | - |
| --van-dialog-header-font-weight | var(--van-font-bold) | - |
| --van-dialog-header-line-height | 24px | - |
| --van-dialog-header-padding-top | 26px | - |
| --van-dialog-header-isolated-padding | var(--van-padding-lg) 0 | - |
| --van-dialog-message-padding | var(--van-padding-lg) | - |
| --van-dialog-message-font-size | var(--van-font-size-md) | - |
| --van-dialog-message-line-height | var(--van-line-height-md) | - |
| --van-dialog-message-max-height | 60vh | - |
| --van-dialog-has-title-message-text-color | var(--van-gray-7) | - |
| --van-dialog-has-title-message-padding-top | var(--van-padding-xs) | - |
| --van-dialog-button-height | 48px | - |
| --van-dialog-round-button-height | 36px | - |
| --van-dialog-confirm-button-text-color | var(--van-primary-color) | - |
常见问题
引用 showDialog 时出现编译报错?
如果引用 showDialog 方法时出现以下报错,说明项目中使用了 babel-plugin-import 插件,导致代码被错误编译。
These dependencies were not found: * vant/es/show-dialog in ./src/xxx.js * vant/es/show-dialog/style in ./src/xxx.jsVant 从 4.0 版本开始不再支持 babel-plugin-import 插件,请参考 迁移指南 移除该插件。
在 beforeRouteLeave 里调用 Dialog 无法展示?
将 closeOnPopstate 属性设置为 false 即可。
import { showDialog } from'vant'; showDialog({ title: '标题', message: '弹窗内容', closeOnPopstate: false, }).then(() => { // on close });🎯 最佳实践
智能弹窗管理系统
// 创建一个智能的弹窗管理器
class DialogManager {
constructor() {
this.queue = [];
this.isShowing = false;
}
// 添加弹窗到队列
add(options) {
return new Promise((resolve, reject) => {
this.queue.push({
options,
resolve,
reject
});
this.processQueue();
});
}
// 处理弹窗队列
async processQueue() {
if (this.isShowing || this.queue.length === 0) return;
this.isShowing = true;
const { options, resolve, reject } = this.queue.shift();
try {
await showDialog(options);
resolve();
} catch (error) {
reject(error);
} finally {
this.isShowing = false;
// 处理下一个弹窗
setTimeout(() => this.processQueue(), 100);
}
}
}
// 使用示例
const dialogManager = new DialogManager();
// 批量显示弹窗,自动排队
dialogManager.add({ message: '第一个弹窗' });
dialogManager.add({ message: '第二个弹窗' });
dialogManager.add({ message: '第三个弹窗' });响应式弹窗设计
// 根据设备类型调整弹窗样式
const createResponsiveDialog = (options) => {
const isMobile = window.innerWidth <= 768;
const isTablet = window.innerWidth > 768 && window.innerWidth <= 1024;
const responsiveOptions = {
...options,
width: isMobile ? '90%' : isTablet ? '400px' : '480px',
className: `dialog-${isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop'}`
};
return showDialog(responsiveOptions);
};
// 使用示例
createResponsiveDialog({
title: '响应式弹窗',
message: '这个弹窗会根据设备尺寸自动调整样式'
});主题化弹窗配置
// 预设主题配置
const dialogThemes = {
success: {
confirmButtonColor: '#07c160',
title: '✅ 成功',
theme: 'round-button'
},
warning: {
confirmButtonColor: '#ff976a',
title: '⚠️ 警告',
theme: 'round-button'
},
error: {
confirmButtonColor: '#ee0a24',
title: '❌ 错误',
theme: 'round-button'
},
info: {
confirmButtonColor: '#1989fa',
title: 'ℹ️ 提示',
theme: 'round-button'
}
};
// 快捷方法
const showSuccessDialog = (message) => showDialog({
...dialogThemes.success,
message
});
const showErrorDialog = (message) => showDialog({
...dialogThemes.error,
message
});💡 使用技巧
动态内容弹窗
// 支持动态更新内容的弹窗
const showDynamicDialog = (initialMessage) => {
let dialogInstance;
const updateMessage = (newMessage) => {
if (dialogInstance) {
// 通过重新调用来更新内容
closeDialog();
setTimeout(() => {
dialogInstance = showDialog({
message: newMessage,
showCancelButton: true
});
}, 100);
}
};
dialogInstance = showDialog({
message: initialMessage,
showCancelButton: true,
beforeClose: (action) => {
if (action === 'confirm') {
updateMessage('内容已更新!');
return false; // 阻止关闭
}
return true;
}
});
return { updateMessage };
};表单验证弹窗
// 表单验证结果弹窗
const showValidationDialog = (errors) => {
const errorList = errors.map(error => `• ${error}`).join('\n');
return showDialog({
title: '表单验证失败',
message: `请修正以下错误:\n\n${errorList}`,
confirmButtonText: '我知道了',
confirmButtonColor: '#ff976a',
allowHtml: false
});
};
// 使用示例
const validateForm = (formData) => {
const errors = [];
if (!formData.name) errors.push('姓名不能为空');
if (!formData.email) errors.push('邮箱不能为空');
if (formData.age < 18) errors.push('年龄必须大于18岁');
if (errors.length > 0) {
showValidationDialog(errors);
return false;
}
return true;
};倒计时确认弹窗
// 带倒计时的确认弹窗
const showCountdownDialog = (message, countdown = 5) => {
let timer;
let currentCount = countdown;
const updateDialog = () => {
const buttonText = currentCount > 0 ? `确认 (${currentCount}s)` : '确认';
return showDialog({
message,
confirmButtonText: buttonText,
confirmButtonDisabled: currentCount > 0,
showCancelButton: true,
beforeClose: (action) => {
if (timer) clearInterval(timer);
return true;
}
});
};
// 启动倒计时
timer = setInterval(() => {
currentCount--;
if (currentCount >= 0) {
closeDialog();
setTimeout(() => updateDialog(), 50);
} else {
clearInterval(timer);
}
}, 1000);
return updateDialog();
};🔧 常见问题解决
弹窗层级管理
// 弹窗层级管理器
class DialogZIndexManager {
constructor() {
this.baseZIndex = 2000;
this.currentZIndex = this.baseZIndex;
}
getNextZIndex() {
return ++this.currentZIndex;
}
resetZIndex() {
this.currentZIndex = this.baseZIndex;
}
}
const zIndexManager = new DialogZIndexManager();
// 使用示例
const showLayeredDialog = (options) => {
return showDialog({
...options,
overlayStyle: {
zIndex: zIndexManager.getNextZIndex()
}
});
};内存泄漏防护
// 防止内存泄漏的弹窗包装器
const createSafeDialog = (options) => {
const cleanup = () => {
// 清理事件监听器
window.removeEventListener('beforeunload', cleanup);
// 清理定时器
if (options._timer) {
clearTimeout(options._timer);
}
};
// 页面卸载时自动清理
window.addEventListener('beforeunload', cleanup);
return showDialog({
...options,
beforeClose: (action) => {
cleanup();
return options.beforeClose ? options.beforeClose(action) : true;
}
});
};无障碍访问优化
// 无障碍访问增强
const showAccessibleDialog = (options) => {
return showDialog({
...options,
// 添加 ARIA 属性
className: 'dialog-accessible',
beforeClose: (action) => {
// 恢复焦点到触发元素
const triggerElement = document.activeElement;
if (triggerElement && triggerElement.focus) {
setTimeout(() => triggerElement.focus(), 100);
}
return options.beforeClose ? options.beforeClose(action) : true;
}
});
};
// 对应的 CSS
const accessibleDialogStyles = `
.dialog-accessible {
outline: none;
}
.dialog-accessible [role="dialog"] {
outline: 2px solid #1989fa;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.dialog-accessible * {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
`;🎨 设计灵感
毛玻璃效果弹窗
/* 毛玻璃效果 */
.dialog-glass {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.dialog-glass .van-overlay {
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(5px);
}动画效果增强
/* 弹性动画 */
@keyframes dialogBounceIn {
0% {
opacity: 0;
transform: scale(0.3) translate(-50%, -50%);
}
50% {
opacity: 1;
transform: scale(1.05) translate(-50%, -50%);
}
70% {
transform: scale(0.9) translate(-50%, -50%);
}
100% {
opacity: 1;
transform: scale(1) translate(-50%, -50%);
}
}
.dialog-bounce .van-dialog {
animation: dialogBounceIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* 渐变边框 */
.dialog-gradient-border {
position: relative;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
background-size: 400% 400%;
animation: gradientShift 3s ease infinite;
padding: 2px;
border-radius: 18px;
}
.dialog-gradient-border .van-dialog {
background: white;
border-radius: 16px;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}主题切换动画
// 主题切换动画
const switchDialogTheme = (fromTheme, toTheme) => {
const dialog = document.querySelector('.van-dialog');
if (!dialog) return;
// 添加过渡效果
dialog.style.transition = 'all 0.3s ease';
// 移除旧主题类
dialog.classList.remove(`dialog-theme-${fromTheme}`);
// 添加新主题类
setTimeout(() => {
dialog.classList.add(`dialog-theme-${toTheme}`);
}, 50);
};🚀 高级功能扩展
智能弹窗推荐系统
// 基于用户行为的弹窗推荐
class DialogRecommendationEngine {
constructor() {
this.userBehavior = JSON.parse(localStorage.getItem('dialogBehavior') || '{}');
}
// 记录用户行为
recordBehavior(dialogType, action, duration) {
if (!this.userBehavior[dialogType]) {
this.userBehavior[dialogType] = {
confirmCount: 0,
cancelCount: 0,
avgDuration: 0,
totalShown: 0
};
}
const behavior = this.userBehavior[dialogType];
behavior.totalShown++;
if (action === 'confirm') behavior.confirmCount++;
if (action === 'cancel') behavior.cancelCount++;
// 更新平均持续时间
behavior.avgDuration = (behavior.avgDuration + duration) / 2;
localStorage.setItem('dialogBehavior', JSON.stringify(this.userBehavior));
}
// 获取推荐配置
getRecommendedConfig(dialogType) {
const behavior = this.userBehavior[dialogType];
if (!behavior) return {};
const confirmRate = behavior.confirmCount / behavior.totalShown;
return {
// 如果确认率低,增加警告色彩
confirmButtonColor: confirmRate < 0.3 ? '#ff976a' : '#07c160',
// 如果用户经常取消,默认显示取消按钮
showCancelButton: behavior.cancelCount > behavior.confirmCount,
// 根据平均查看时间调整自动关闭
autoClose: behavior.avgDuration < 2000
};
}
}多语言弹窗系统
// 多语言弹窗支持
class I18nDialogManager {
constructor() {
this.locale = 'zh-CN';
this.messages = {
'zh-CN': {
confirm: '确认',
cancel: '取消',
ok: '好的',
warning: '警告',
error: '错误',
success: '成功'
},
'en-US': {
confirm: 'Confirm',
cancel: 'Cancel',
ok: 'OK',
warning: 'Warning',
error: 'Error',
success: 'Success'
},
'ja-JP': {
confirm: '確認',
cancel: 'キャンセル',
ok: 'はい',
warning: '警告',
error: 'エラー',
success: '成功'
}
};
}
setLocale(locale) {
this.locale = locale;
}
t(key) {
return this.messages[this.locale][key] || key;
}
showDialog(options) {
return showDialog({
...options,
confirmButtonText: options.confirmButtonText || this.t('confirm'),
cancelButtonText: options.cancelButtonText || this.t('cancel')
});
}
}
// 使用示例
const i18nDialog = new I18nDialogManager();
i18nDialog.setLocale('en-US');
i18nDialog.showDialog({
title: 'Confirmation',
message: 'Are you sure you want to delete this item?',
showCancelButton: true
});弹窗分析统计
// 弹窗使用统计分析
class DialogAnalytics {
constructor() {
this.events = [];
}
track(eventType, data) {
this.events.push({
type: eventType,
data,
timestamp: Date.now()
});
// 发送到分析服务
this.sendToAnalytics(eventType, data);
}
sendToAnalytics(eventType, data) {
// 模拟发送到分析服务
console.log('Analytics:', { eventType, data });
}
wrapDialog(options) {
const startTime = Date.now();
this.track('dialog_show', {
type: options.type || 'default',
hasTitle: !!options.title,
hasCancel: !!options.showCancelButton
});
return showDialog({
...options,
beforeClose: (action) => {
const duration = Date.now() - startTime;
this.track('dialog_close', {
action,
duration,
type: options.type || 'default'
});
return options.beforeClose ? options.beforeClose(action) : true;
}
});
}
getStatistics() {
const stats = {
totalShown: 0,
avgDuration: 0,
confirmRate: 0,
cancelRate: 0
};
const showEvents = this.events.filter(e => e.type === 'dialog_show');
const closeEvents = this.events.filter(e => e.type === 'dialog_close');
stats.totalShown = showEvents.length;
if (closeEvents.length > 0) {
stats.avgDuration = closeEvents.reduce((sum, e) => sum + e.data.duration, 0) / closeEvents.length;
stats.confirmRate = closeEvents.filter(e => e.data.action === 'confirm').length / closeEvents.length;
stats.cancelRate = closeEvents.filter(e => e.data.action === 'cancel').length / closeEvents.length;
}
return stats;
}
}📚 延伸阅读
技术文档
- Popup 弹出层 - 弹窗的底层实现基础
- Overlay 遮罩层 - 了解遮罩层的工作原理
- Toast 轻提示 - 轻量级的消息提示方案
设计指南
用户体验
相关组件
- ActionSheet 动作面板 - 底部弹出的选择面板
- Notify 消息通知 - 顶部消息通知
- ImagePreview 图片预览 - 图片预览弹窗
- Picker 选择器 - 选择器弹窗