Skip to content

Dialog 弹出框 - Vant 4

Dialog 弹出框

🎯 介绍

弹出框是用户交互中的重要角色!它就像一个礼貌的服务员,会在关键时刻出现,向用户传达重要信息或请求确认。无论是温馨提示、重要确认,还是复杂的交互操作,Dialog 都能优雅地完成任务。支持组件调用和函数调用两种方式,让你的开发更加灵活。

📦 引入

通过以下方式来全局注册组件,更多注册方式请参考组件注册

js
import { createApp } from 'vue';
import { Dialog } from 'vant';

const app = createApp();
app.use(Dialog);

🚀 函数调用

为了让你的开发更加便捷,Vant 提供了一系列超实用的辅助函数!通过这些函数,你可以像变魔术一样快速唤起全局的弹窗组件。

比如使用 showDialog 函数,一行代码就能在页面中展示漂亮的弹出框。

js
import { showDialog } from 'vant';

showDialog({ 
  message: '这是一个友好的提示!' 
});

🎨 代码演示

💬 消息提示 - 温柔的信息传递者

最简单却最贴心的用法!当你需要向用户传达重要信息时,消息提示就像一位温柔的信使,优雅地出现在用户面前。默认只包含一个确认按钮,简洁明了,让用户专注于信息本身。

js
import { showDialog } from 'vant';

showDialog({
  title: '温馨提示',
  message: '代码是写出来给人看的,附带能在机器上运行。',
}).then(() => {
  // 用户点击确认后的回调
  console.log('用户已确认');
});

showDialog({
  message: '生命远不止连轴转和忙到极限,人类的体验远比这辽阔、丰富得多。',
}).then(() => {
  // 用户关闭弹窗后的回调
  console.log('弹窗已关闭');
});

❓ 消息确认 - 智慧的决策助手

当需要用户做出重要决定时,消息确认就像一位智慧的顾问,为用户提供清晰的选择。默认包含确认和取消按钮,让用户在深思熟虑后做出最佳选择。

js
import { showConfirmDialog } from'vant'; showConfirmDialog({ title: '标题', message: '如果解决方法是丑陋的,那就肯定还有更好的解决方法,只是还没有发现而已。', }) .then(() => { // on confirm }) .catch(() => { // on cancel });

🎨 圆角按钮风格 - 柔美的视觉享受

让弹窗变得更加温柔可爱!将 theme 选项设置为 round-button,就能展示圆角按钮风格的弹窗,圆润的边角如春风般温和,为用户带来更加舒适的视觉体验。

js
import { showDialog } from'vant'; showDialog({ title: '标题', message: '代码是写出来给人看的,附带能在机器上运行。', theme: 'round-button', }).then(() => { // on close }); showDialog({ message: '生命远不止连轴转和忙到极限,人类的体验远比这辽阔、丰富得多。', theme: 'round-button', }).then(() => { // on close });

⏳ 异步关闭 - 优雅的告别仪式

有时候告别也需要一个优雅的过程!通过 beforeClose 属性这个贴心的管家,可以传入一个回调函数,在弹窗关闭前进行特定操作,让每一次关闭都变得从容不迫。

js
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 等方式注册组件。

html
js
import { ref } from'vue'; exportdefault { setup() { const show = ref(false); return { show }; }, };

API

方法

Vant 中导出了以下 Dialog 相关的辅助函数:

方法名说明参数返回值
showDialog展示消息提示弹窗,默认包含确认按钮options: DialogOptionsPromise<void>
showConfirmDialog展示消息确认弹窗,默认包含确认和取消按钮options: DialogOptionsPromise<void>
closeDialog关闭当前展示的弹窗-void
setDialogDefaultOptions修改默认配置,影响所有的 showDialog 调用options: DialogOptionsvoid
resetDialogDefaultOptions重置默认配置,影响所有的 showDialog 调用-void

DialogOptions

调用 showDialog 等方法时,支持传入以下选项:

参数说明类型默认值
title标题string-
width弹窗宽度,默认单位为 px*numberstring*
message文本内容,支持通过 \n 换行*string() => JSX.ELement*
messageAlign内容对齐方式,可选值为 left``rightstringcenter
theme样式风格,可选值为 round-buttonstringdefault
className自定义类名*stringArray
showConfirmButton是否展示确认按钮booleantrue
showCancelButton是否展示取消按钮booleanfalse
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 | 动画类名,等价于 transitionname 属性 | string | - |

| teleport | 指定挂载的节点,等同于 Teleport 组件的 to 属性 | string | Element | body |

| keyboardEnabled | 是否启用键盘能力,在展示确认和取消按钮的时候,默认情况下键盘的 EnterEsc 会执行 confirmcancel 函数 | boolean | true |

Props

通过组件调用 Dialog 时,支持以下 Props:

参数说明类型默认值
v-model:show是否显示弹窗boolean-
title标题string-
width弹窗宽度,默认单位为 px*numberstring*
message文本内容,支持通过 \n 换行*string() => JSX.Element*
message-align内容水平对齐方式,可选值为 left``right``justifystringcenter
theme样式风格,可选值为 round-buttonstringdefault
show-confirm-button是否展示确认按钮booleantrue
show-cancel-button是否展示取消按钮booleanfalse
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 | 动画类名,等价于 transitionname 属性 | string | - |

| teleport | 指定挂载的节点,等同于 Teleport 组件的 to 属性 | string | Element | - |

| keyboard-enabled | 是否启用键盘能力,在展示确认和取消按钮的时候,默认情况下键盘的 EnterEsc 会执行 confirmcancel 函数 | boolean | true |

Events

通过组件调用 Dialog 时,支持以下事件:

事件名说明回调参数
confirm点击确认按钮时触发-
cancel点击取消按钮时触发-
open打开弹窗时触发-
close关闭弹窗时触发-
opened打开弹窗且动画结束后触发-
closed关闭弹窗且动画结束后触发-

Slots

通过组件调用 Dialog 时,支持以下插槽:

名称说明
default自定义内容
title自定义标题
footer自定义底部按钮区域

类型定义

组件导出以下类型定义:

ts
importtype { DialogProps, DialogTheme, DialogMessage, DialogOptions, DialogMessageAlign, } from'vant';

主题定制

样式变量

组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 ConfigProvider 组件

名称默认值描述
--van-dialog-width320px-
--van-dialog-small-screen-width90%-
--van-dialog-font-sizevar(--van-font-size-lg)-
--van-dialog-transitionvar(--van-duration-base)-
--van-dialog-radius16px-
--van-dialog-backgroundvar(--van-background-2)-
--van-dialog-header-font-weightvar(--van-font-bold)-
--van-dialog-header-line-height24px-
--van-dialog-header-padding-top26px-
--van-dialog-header-isolated-paddingvar(--van-padding-lg) 0-
--van-dialog-message-paddingvar(--van-padding-lg)-
--van-dialog-message-font-sizevar(--van-font-size-md)-
--van-dialog-message-line-heightvar(--van-line-height-md)-
--van-dialog-message-max-height60vh-
--van-dialog-has-title-message-text-colorvar(--van-gray-7)-
--van-dialog-has-title-message-padding-topvar(--van-padding-xs)-
--van-dialog-button-height48px-
--van-dialog-round-button-height36px-
--van-dialog-confirm-button-text-colorvar(--van-primary-color)-

常见问题

引用 showDialog 时出现编译报错?

如果引用 showDialog 方法时出现以下报错,说明项目中使用了 babel-plugin-import 插件,导致代码被错误编译。

bash
These dependencies were not found: * vant/es/show-dialog in ./src/xxx.js * vant/es/show-dialog/style in ./src/xxx.js

Vant 从 4.0 版本开始不再支持 babel-plugin-import 插件,请参考 迁移指南 移除该插件。

在 beforeRouteLeave 里调用 Dialog 无法展示?

closeOnPopstate 属性设置为 false 即可。

js
import { showDialog } from'vant'; showDialog({ title: '标题', message: '弹窗内容', closeOnPopstate: false, }).then(() => { // on close });

🎯 最佳实践

智能弹窗管理系统

javascript
// 创建一个智能的弹窗管理器
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: '第三个弹窗' });

响应式弹窗设计

javascript
// 根据设备类型调整弹窗样式
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: '这个弹窗会根据设备尺寸自动调整样式'
});

主题化弹窗配置

javascript
// 预设主题配置
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
});

💡 使用技巧

动态内容弹窗

javascript
// 支持动态更新内容的弹窗
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 };
};

表单验证弹窗

javascript
// 表单验证结果弹窗
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;
};

倒计时确认弹窗

javascript
// 带倒计时的确认弹窗
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();
};

🔧 常见问题解决

弹窗层级管理

javascript
// 弹窗层级管理器
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()
    }
  });
};

内存泄漏防护

javascript
// 防止内存泄漏的弹窗包装器
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;
    }
  });
};

无障碍访问优化

javascript
// 无障碍访问增强
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;
  }
}
`;

🎨 设计灵感

毛玻璃效果弹窗

css
/* 毛玻璃效果 */
.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);
}

动画效果增强

css
/* 弹性动画 */
@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%; }
}

主题切换动画

javascript
// 主题切换动画
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);
};

🚀 高级功能扩展

智能弹窗推荐系统

javascript
// 基于用户行为的弹窗推荐
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
    };
  }
}

多语言弹窗系统

javascript
// 多语言弹窗支持
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
});

弹窗分析统计

javascript
// 弹窗使用统计分析
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;
  }
}

📚 延伸阅读

技术文档

设计指南

用户体验

相关组件

基於Vant構建的企業級移動端解決方案