前言
之前都是在渲染进程中直接设置的可调用Node的方法,这样是不太合理的。
合理的做法是所有的NodeJS的方法都在主进程中调用。
如果需要渲染进程发起,则使用IPC事件交互,ipcRenderer则通过preload注入。
窗口及preload
窗口设置
1 2 3 4 5 6 7 8 9 10 11 12 13
| win = new BrowserWindow({ width: 800, height: 600, icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'), title: "图片工具", webPreferences: { preload: path.join(__dirname, 'preload.mjs'), nodeIntegration: false, webSecurity: true, contextIsolation: true, webviewTag: true }, })
|
其中
nodeIntegration 是否可以直接使用 Node.js API(如 require, process, fs, path 等)。
contextIsolation渲染进程的 JavaScript 上下文是否与预加载脚本(preload)隔离。
建议设置为true,防止原型污染、API 劫持。contextBridge导出的变量能被访问但不能被修改。
preload.ts
注入脚本,这样渲染进程则可以使用window.ipcRenderer获取ipcRenderer对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { ipcRenderer, contextBridge } from 'electron'
contextBridge.exposeInMainWorld('ipcRenderer', { on(...args: Parameters<typeof ipcRenderer.on>) { const [channel, listener] = args return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args)) }, off(...args: Parameters<typeof ipcRenderer.off>) { const [channel, ...omit] = args return ipcRenderer.off(channel, ...omit) }, send(...args: Parameters<typeof ipcRenderer.send>) { const [channel, ...omit] = args return ipcRenderer.send(channel, ...omit) }, invoke(...args: Parameters<typeof ipcRenderer.invoke>) { const [channel, ...omit] = args return ipcRenderer.invoke(channel, ...omit) },
})
|
文件选择
主进程
1 2 3 4 5 6 7 8 9 10 11 12
| import { ipcMain, dialog } from "electron";
ipcMain.handle("open-file-dialog", async () => { return dialog.showOpenDialog(win, { properties: ["openFile"], filters: [ { name: "文本", extensions: ["txt"] }, { name: "All Files", extensions: ["*"] }, ], }); });
|
如果允许多选
1 2 3 4 5 6 7 8 9 10
| ipcMain.handle("open-file-dialog", async () => { return dialog.showOpenDialog(win, { properties: ["openFile", "multiSelections"], filters: [ { name: "文本", extensions: ["txt"] }, { name: "All Files", extensions: ["*"] }, ], }); });
|
渲染进程
1 2 3 4 5 6 7 8 9 10 11 12
| async selectFileClick() { try { const result = await window.ipcRenderer.invoke("open-file-dialog"); if (!result.canceled) { console.log("选择的文件路径:", result.filePaths); } else { console.log("用户取消了选择"); } } catch (error) { console.error("选择文件时出错:", error); } },
|
注意
返回的路径是数组
文件夹选择
主进程
1 2 3 4 5 6 7 8 9 10 11
| import {ipcMain, dialog} from "electron";
ipcMain.handle('open-folder-dialog', async () => { if (!win) { return "" } return dialog.showOpenDialog(win, { properties: ['openDirectory'] }); });
|
渲染进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const folderPath = ref("")
async function selectFolderClick() { try { const result = await window.ipcRenderer.invoke('open-folder-dialog'); if (!result.canceled) { if (result.filePaths.length > 0) { folderPath.value = result.filePaths[0] } } else { console.log('用户取消了选择'); } } catch (error) { console.error('选择文件夹时出错:', error); } }
|
文件弹窗保存
主进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const { ipcMain,dialog } = require("electron"); const path = require("path");
ipcMain.on("savefile", (event, arg) => { dialog .showSaveDialog({ defaultPath: path.join( global.sharedObject.appBasePath, arg ? arg : "filename.txt" ), }) .then((result) => { if (!result.canceled) { const filePath = result.filePath; console.log("保存路径:", filePath); event.reply("savefile-result", filePath); } else { console.log("取消保存文件"); } }) .catch((err) => { console.log("保存文件出错:", err); }); });
|
渲染进程
1 2 3 4
| window.ipcRenderer.send("savefile", "新脑图.txt"); window.ipcRenderer.on('savefile-result', (event, arg) => { console.log(arg) })
|