在桌面应用中,直接与操作系统对话是刚需。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 实用建议与常见问题
- 始终处理取消操作:
canceled为true时用户点了“取消”,别让代码误判。 - 避免在主进程同步调用
showOpenDialog:它返回的是 Promise,即使你不await也不会阻塞其他任务,但注意不要在回调外用。 - macOS 上,若父窗口被置为模态,对话框会自动关联;可以用
dialog.showOpenDialog(mainWindow, options)传入父窗口引用,使对话框相对于父窗口居中显示。 - Linux 部分桌面环境对
filter支持不佳,建议备选“所有文件”以避免用户无法手动输入无扩展名文件。 - 文件路径安全:获得路径后请勿直接在渲染进程通过
require('fs')读取(除非你显式允许并在预加载暴露安全的 API)。正确做法是把路径发给主进程,让主进程用fs模块读写,再将内容返回渲染进程。
掌握这些系统对话框,你的 Electron 应用就有了“桌面软件”的质感——用户不需要去理解 Web 应用的限制,他们感受到的就是一个标准的、可以自由访问本地文件的桌面程序。