人人都会AI编程

11.1 系统对话框:文件选择、保存、消息提示、错误弹窗

更新时间:2026-07-11

在桌面应用中,直接与操作系统对话是刚需。Electron 的 dialog 模块就是你和用户操作系统之间的“翻译官”——它可以把你的意图转化为 Windows、macOS、Linux 上的原生对话框,包括打开文件、保存文件、弹出消息提示和错误警告。这一节我们会把所有常用场景一次性讲清楚,让你拿来就能用。

11.1.1 文件选择对话框:dialog.showOpenDialog

最常用的场景是让用户选择一个或多个文件,比如导入配置、上传本地图片、打开项目文件夹。showOpenDialog 会打开一个原生的 “打开” 对话框,用户选完后返回文件路径列表。

基本用法
在主进程(比如菜单点击或 IPC 响应)里调用:

const { dialog } = require('electron');

async function openFile() {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    title: '选择一个图片文件',       // 对话框标题(Windows 友好,macOS 可能不显示)
    defaultPath: app.getPath('pictures'), // 默认打开的路径
    filters: [
      { name: '图片文件', extensions: ['jpg', 'png', 'gif'] },
      { name: '所有文件', extensions: ['*'] }
    ],
    properties: ['openFile', 'multiSelections'] // 允许多选
  });

  if (canceled) {
    console.log('用户取消了选择');
    return [];
  } else {
    console.log('用户选择了:', filePaths); // 返回一个数组,例如 ['/Users/me/photo.jpg']
    return filePaths;
  }
}

常用 properties 配置项

  • 'openFile':允许选择文件(默认行为,通常省略)
  • 'openDirectory':允许选择文件夹(用于打开项目目录)
  • 'multiSelections':允许按住 Ctrl/Command 多选文件
  • 'showHiddenFiles':显示隐藏文件
  • 'createDirectory':macOS 上显示“新建文件夹”按钮

真实开发中,一般会结合具体功能选配。比如一个 Markdown 编辑器,选择“打开文件夹”作为工作区:

const result = await dialog.showOpenDialog({
  properties: ['openDirectory']
});
if (!result.canceled) {
  const folderPath = result.filePaths[0];
  // 接下来加载该文件夹下的所有 .md 文件
}

11.1.2 保存文件对话框:dialog.showSaveDialog

要保存文件时,需要弹出“另存为”对话框,让用户指定文件名和位置。showSaveDialog 返回用户输入的完整路径。

基本用法

async function saveFile() {
  const { canceled, filePath } = await dialog.showSaveDialog({
    title: '导出数据为 CSV 文件',
    defaultPath: path.join(app.getPath('documents'), 'data.csv'), // 默认文件名
    filters: [
      { name: 'CSV 文件', extensions: ['csv'] },
      { name: '所有文件', extensions: ['*'] }
    ]
  });

  if (!canceled && filePath) {
    // 用 Node.js 写文件
    fs.writeFileSync(filePath, '这里是导出的数据');
    console.log('文件已保存至:', filePath);
  }
}

需要注意的点

  • 对话框中用户输入的文件名可能会被自动添加扩展名,取决于 filters;如果没有合适的 filter,不会自动添加后缀,你需要自己在代码里补上。
  • 如果用户选择的路径已经存在一个同名文件,操作系统会弹出“是否覆盖”的原生确认框,Electron 不干预。
  • defaultPath 只是建议路径,用户最终可以改。

11.1.3 消息提示对话框:dialog.showMessageBox

当你需要通知用户一些信息,或者让用户做一个简单确认(是/否),可以使用 showMessageBox。它比 Web 的 alert / confirm 更原生、更灵活。

四种常见形态

// 1. 纯信息提示(仅确定按钮)
await dialog.showMessageBox({
  type: 'info',
  title: '操作完成',
  message: '文件已经成功导出。',
  buttons: ['好的']
});

// 2. 提问确认(是 / 否)
const { response } = await dialog.showMessageBox({
  type: 'question',
  title: '确认删除',
  message: '确定要删除选中的 5 个文件吗?',
  detail: '此操作无法撤销。',
  buttons: ['取消', '确认删除'],   // 注意:索引从 0 开始,取消是 0,确认是 1
  defaultId: 0,                  // 回车触发第 0 个按钮
  cancelId: 0                   // ESC 键也触发第 0 个按钮
});
if (response === 1) {
  // 执行删除
}

// 3. 警告提示(带警告图标)
await dialog.showMessageBox({
  type: 'warning',
  title: '磁盘空间不足',
  message: '你的剩余空间小于 100MB,扩展功能可能无法使用。',
  buttons: ['我知道了']
});

// 4. 错误提示
await dialog.showMessageBox({
  type: 'error',
  title: '无法读取文件',
  message: '文件可能已被移动或权限不足。',
  detail: '错误代码: EACCES'
});

type 可取值'none', 'info', 'error', 'question', 'warning',不同平台图标略有差异,但视觉效果都是操作系统原生风格。

实际应用场景:保存前有未保存改动时弹出确认框;删除操作时二次确认;后台任务最终失败时给出错误警告;升级成功时弹出信息回执。

11.1.4 错误弹窗直接使用原生提示

虽然 showMessageBox 可以显示 type 为 'error' 的消息,但还有一种更直接的原生错误弹窗:dialog.showErrorBox。它是同步的,不接受自定义按钮,只显示一个带错误图标的简单弹窗。

// 仅在主进程中使用,立即弹出
dialog.showErrorBox('启动失败', '请检查配置文件是否正确。');

这个 API 适合致命错误:应用启动时缺少必要资源、数据库连接失败等。因为它会阻断式地弹出,不要滥用。

11.1.5 在渲染进程中调用对话框

出于安全考虑,渲染进程不能直接调用 dialog 模块(除非你关闭了安全隔离,但极不推荐)。标准做法是通过 IPC 让主进程代劳。推荐使用 contextBridge + ipcRenderer.invoke 的异步模式:

主进程(ipc handlers)

const { ipcMain, dialog } = require('electron');

ipcMain.handle('dialog:openFile', async (event, options) => {
  const result = await dialog.showOpenDialog(options);
  return result;
});

ipcMain.handle('dialog:saveFile', async (event, options) => {
  const result = await dialog.showSaveDialog(options);
  return result;
});

ipcMain.handle('dialog:messageBox', async (event, options) => {
  const result = await dialog.showMessageBox(options);
  return result;
});

预加载脚本

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  openFileDialog: (options) => ipcRenderer.invoke('dialog:openFile', options),
  saveFileDialog: (options) => ipcRenderer.invoke('dialog:saveFile', options),
  showMessageBox: (options) => ipcRenderer.invoke('dialog:messageBox', options),
});

渲染进程(Vue/React 任意环境)

// 用户点击“打开文件”按钮
async function handleOpen() {
  const result = await window.electronAPI.openFileDialog({
    properties: ['openFile'],
    filters: [{ name: '文本文件', extensions: ['txt'] }]
  });
  if (!result.canceled) {
    // 处理选中的路径
    console.log(result.filePaths);
  }
}

这种模式安全、可维护,并且不会阻塞主进程 UI。

11.1.6 实用建议与常见问题

  • 始终处理取消操作canceledtrue 时用户点了“取消”,别让代码误判。
  • 避免在主进程同步调用 showOpenDialog:它返回的是 Promise,即使你不 await 也不会阻塞其他任务,但注意不要在回调外用。
  • macOS 上,若父窗口被置为模态,对话框会自动关联;可以用 dialog.showOpenDialog(mainWindow, options) 传入父窗口引用,使对话框相对于父窗口居中显示。
  • Linux 部分桌面环境对 filter 支持不佳,建议备选“所有文件”以避免用户无法手动输入无扩展名文件。
  • 文件路径安全:获得路径后请勿直接在渲染进程通过 require('fs') 读取(除非你显式允许并在预加载暴露安全的 API)。正确做法是把路径发给主进程,让主进程用 fs 模块读写,再将内容返回渲染进程。

掌握这些系统对话框,你的 Electron 应用就有了“桌面软件”的质感——用户不需要去理解 Web 应用的限制,他们感受到的就是一个标准的、可以自由访问本地文件的桌面程序。