对话框与通知 #

一、原生对话框 #

1.1 dialog 模块 #

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

1.2 消息对话框 #

javascript
// 基本消息对话框
dialog.showMessageBox(mainWindow, {
    type: 'info',
    title: '提示',
    message: '操作已完成',
    buttons: ['确定']
});

// 带选项的消息对话框
const result = await dialog.showMessageBox(mainWindow, {
    type: 'question',
    title: '确认删除',
    message: '确定要删除这个文件吗?',
    detail: '此操作无法撤销',
    buttons: ['删除', '取消'],
    defaultId: 1,      // 默认选中第二个按钮
    cancelId: 1,       // ESC 键对应的按钮
    checkboxLabel: '不再询问',
    checkboxChecked: false,
    icon: nativeImage.createFromPath('warning.png')
});

console.log('用户选择:', result.response);        // 按钮索引
console.log('复选框状态:', result.checkboxChecked);

1.3 消息类型 #

类型 图标 用途
none 普通消息
info 信息图标 信息提示
error 错误图标 错误提示
question 问号图标 询问确认
warning 警告图标 警告提示

1.4 文件选择对话框 #

javascript
// 打开文件
const result = await dialog.showOpenDialog(mainWindow, {
    title: '选择文件',
    defaultPath: app.getPath('documents'),
    buttonLabel: '选择',
    filters: [
        { name: '文本文件', extensions: ['txt', 'md'] },
        { name: '图片', extensions: ['jpg', 'png', 'gif'] },
        { name: '所有文件', extensions: ['*'] }
    ],
    properties: [
        'openFile',           // 选择文件
        'openDirectory',      // 选择目录
        'multiSelections',    // 多选
        'showHiddenFiles',    // 显示隐藏文件
        'createDirectory'     // 允许创建目录
    ]
});

if (!result.canceled) {
    console.log('选择的文件:', result.filePaths);
}

1.5 保存对话框 #

javascript
const result = await dialog.showSaveDialog(mainWindow, {
    title: '保存文件',
    defaultPath: path.join(app.getPath('documents'), 'untitled.txt'),
    buttonLabel: '保存',
    filters: [
        { name: '文本文件', extensions: ['txt'] },
        { name: '所有文件', extensions: ['*'] }
    ],
    properties: [
        'createDirectory',
        'showOverwriteConfirmation'
    ]
});

if (!result.canceled) {
    console.log('保存路径:', result.filePath);
    // 执行保存操作
    fs.writeFileSync(result.filePath, content);
}

1.6 错误对话框 #

javascript
// 显示错误对话框(不需要父窗口)
dialog.showErrorBox('错误标题', '错误详细信息');

// 更详细的错误对话框
await dialog.showMessageBox(mainWindow, {
    type: 'error',
    title: '错误',
    message: '操作失败',
    detail: '无法连接到服务器,请检查网络连接。'
});

二、系统通知 #

2.1 基本通知 #

javascript
const { Notification } = require('electron');

// 检查是否支持通知
if (Notification.isSupported()) {
    const notification = new Notification({
        title: '通知标题',
        body: '通知内容',
        icon: path.join(__dirname, 'icon.png'),
        silent: false  // 是否静音
    });

    notification.on('click', () => {
        console.log('通知被点击');
        mainWindow.show();
    });

    notification.on('close', () => {
        console.log('通知被关闭');
    });

    notification.show();
}

2.2 通知选项 #

javascript
const notification = new Notification({
    title: '新消息',
    body: '您有一条新消息',
    subtitle: '副标题',        // macOS
    icon: 'icon.png',
    image: 'preview.png',     // Windows 大图
    silent: false,
    sound: 'default',         // macOS
    urgency: 'normal',        // Linux: normal, low, critical
    closeButtonText: '关闭',  // macOS
    actions: [
        {
            type: 'button',
            text: '查看'
        },
        {
            type: 'button',
            text: '忽略'
        }
    ]
});

notification.on('action', (event, index) => {
    console.log('点击了按钮:', index);
});

notification.show();

2.3 通知权限 #

javascript
// 检查权限状态
const permission = Notification.getPermissionLevel();
// 'granted' - 已授权
// 'denied' - 已拒绝
// 'default' - 未询问

// 请求权限(某些平台需要)
// 注意:Electron 中通常默认已授权

2.4 进度通知 #

javascript
// 带进度的通知(Windows)
const notification = new Notification({
    title: '下载中',
    body: '正在下载文件...',
    progress: 50  // 0-100
});

notification.show();

// 更新进度
function updateProgress(percent) {
    notification.progress = percent;
    notification.show();
}

三、自定义对话框 #

3.1 模态窗口对话框 #

javascript
// 创建自定义对话框窗口
async function showCustomDialog(parent, options) {
    return new Promise((resolve) => {
        const dialogWin = new BrowserWindow({
            width: options.width || 400,
            height: options.height || 300,
            parent: parent,
            modal: true,
            show: false,
            autoHideMenuBar: true,
            resizable: false,
            webPreferences: {
                preload: path.join(__dirname, 'dialog-preload.js'),
                nodeIntegration: false,
                contextIsolation: true
            }
        });

        dialogWin.loadFile('dialog.html');

        dialogWin.once('ready-to-show', () => {
            dialogWin.show();
        });

        // 接收对话框结果
        ipcMain.once('dialog-result', (event, result) => {
            dialogWin.close();
            resolve(result);
        });
    });
}

// 使用
const result = await showCustomDialog(mainWindow, {
    title: '自定义对话框',
    width: 500,
    height: 400
});

3.2 确认对话框封装 #

javascript
// 封装确认对话框
async function confirm(parent, message, options = {}) {
    const result = await dialog.showMessageBox(parent, {
        type: 'question',
        title: options.title || '确认',
        message: message,
        buttons: ['确定', '取消'],
        defaultId: 0,
        cancelId: 1,
        ...options
    });

    return result.response === 0;
}

// 使用
if (await confirm(mainWindow, '确定要删除吗?')) {
    deleteItem();
}

3.3 输入对话框 #

javascript
// 使用自定义窗口实现输入对话框
async function prompt(parent, title, defaultValue = '') {
    return new Promise((resolve) => {
        const promptWin = new BrowserWindow({
            width: 400,
            height: 150,
            parent: parent,
            modal: true,
            show: false,
            autoHideMenuBar: true,
            resizable: false,
            webPreferences: {
                preload: path.join(__dirname, 'prompt-preload.js'),
                nodeIntegration: false,
                contextIsolation: true
            }
        });

        promptWin.loadFile('prompt.html');

        promptWin.webContents.on('did-finish-load', () => {
            promptWin.webContents.send('init', { title, defaultValue });
            promptWin.show();
        });

        ipcMain.once('prompt-result', (event, value) => {
            promptWin.close();
            resolve(value);
        });
    });
}

// 使用
const name = await prompt(mainWindow, '请输入名称:', '默认值');

四、文件拖放 #

4.1 处理文件拖放 #

javascript
// 主进程
mainWindow.webContents.on('did-finish-load', () => {
    mainWindow.webContents.send('setup-drag-drop');
});

// 渲染进程
document.addEventListener('DOMContentLoaded', () => {
    const dropZone = document.getElementById('drop-zone');

    dropZone.addEventListener('dragover', (e) => {
        e.preventDefault();
        e.stopPropagation();
        dropZone.classList.add('drag-over');
    });

    dropZone.addEventListener('dragleave', (e) => {
        e.preventDefault();
        e.stopPropagation();
        dropZone.classList.remove('drag-over');
    });

    dropZone.addEventListener('drop', (e) => {
        e.preventDefault();
        e.stopPropagation();
        dropZone.classList.remove('drag-over');

        const files = Array.from(e.dataTransfer.files);
        console.log('拖放的文件:', files);

        // 处理文件
        files.forEach(file => {
            processFile(file.path);
        });
    });
});

4.2 拖放文件到应用图标 #

javascript
// macOS: 处理文件拖放到 Dock 图标
app.on('open-file', (event, filePath) => {
    event.preventDefault();
    console.log('打开文件:', filePath);
    
    if (mainWindow) {
        mainWindow.webContents.send('open-file', filePath);
    }
});

// Windows/Linux: 从命令行参数获取
if (process.argv.length > 1) {
    const filePath = process.argv[1];
    console.log('打开文件:', filePath);
}

五、进度反馈 #

5.1 任务栏进度 #

javascript
// Windows 任务栏进度条
mainWindow.setProgressBar(0);

async function downloadFile(url) {
    mainWindow.setProgressBar(0);
    
    for (let i = 0; i <= 100; i++) {
        await new Promise(r => setTimeout(r, 100));
        mainWindow.setProgressBar(i / 100);
    }
    
    mainWindow.setProgressBar(-1);  // 完成
}

5.2 加载对话框 #

javascript
// 显示加载对话框
async function showLoadingDialog(parent, task, message) {
    const loadingWin = new BrowserWindow({
        width: 300,
        height: 100,
        parent: parent,
        modal: true,
        show: false,
        frame: false,
        transparent: true,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true
        }
    });

    loadingWin.loadFile('loading.html');
    
    loadingWin.webContents.on('did-finish-load', () => {
        loadingWin.webContents.send('update-message', message);
        loadingWin.show();
    });

    try {
        const result = await task();
        return result;
    } finally {
        loadingWin.close();
    }
}

// 使用
const result = await showLoadingDialog(
    mainWindow,
    () => fetchData(),
    '正在加载数据...'
);

六、最佳实践 #

6.1 对话框封装 #

javascript
// dialogs.js
class DialogManager {
    constructor() {
        this.parentWindow = null;
    }

    setParent(window) {
        this.parentWindow = window;
    }

    async alert(message, title = '提示') {
        await dialog.showMessageBox(this.parentWindow, {
            type: 'info',
            title,
            message
        });
    }

    async confirm(message, title = '确认') {
        const result = await dialog.showMessageBox(this.parentWindow, {
            type: 'question',
            title,
            message,
            buttons: ['确定', '取消'],
            defaultId: 0,
            cancelId: 1
        });
        return result.response === 0;
    }

    async error(message, title = '错误') {
        await dialog.showMessageBox(this.parentWindow, {
            type: 'error',
            title,
            message
        });
    }

    async openFile(options = {}) {
        const result = await dialog.showOpenDialog(this.parentWindow, {
            properties: ['openFile'],
            ...options
        });
        return result.canceled ? null : result.filePaths[0];
    }

    async saveFile(options = {}) {
        const result = await dialog.showSaveDialog(this.parentWindow, options);
        return result.canceled ? null : result.filePath;
    }

    notify(title, body) {
        if (Notification.isSupported()) {
            new Notification({ title, body }).show();
        }
    }
}

module.exports = new DialogManager();

6.2 使用封装 #

javascript
// main.js
const dialogs = require('./dialogs');

// 设置父窗口
dialogs.setParent(mainWindow);

// 使用
if (await dialogs.confirm('确定删除?')) {
    await deleteItem();
    dialogs.notify('成功', '项目已删除');
}

七、总结 #

7.1 核心要点 #

要点 说明
消息对话框 showMessageBox 显示各种类型消息
文件对话框 showOpenDialog/showSaveDialog
系统通知 Notification 类发送通知
自定义对话框 使用 BrowserWindow 实现
进度反馈 任务栏进度条和加载对话框

7.2 下一步 #

现在你已经掌握了对话框与通知的使用,接下来让我们学习 快捷键与系统,深入了解全局快捷键和系统集成!

最后更新:2026-03-28