Electron 本地文件与系统交互
🎯 引言
这篇文章只完成一个功能:点击按钮选择一个 .txt 文件,并把文件内容显示到页面中。
学完本篇,你将能够:
- 使用
dialog.showOpenDialog打开系统文件选择框。 - 取得用户选择的文件路径。
- 使用
readFile读取文本文件。 - 把文件名和文件内容显示到页面中。
🧱 选择文件和读取文件的区别
选择文件和读取文件是两个不同的操作:
| 操作 | 使用的 API | 得到什么 |
|---|---|---|
| 选择文件 | dialog.showOpenDialog | 文件路径 |
| 读取文件 | readFile | 文件中的内容 |
showOpenDialog 只负责让用户选择文件,它不会自动读取文件内容。取得路径后,还要把路径交给 readFile。
这两个操作都写在 main.js 中。页面只调用预加载脚本提供的 openTextFile(),不直接接触 Electron 和 Node.js 模块。
🛠 在主进程选择并读取文件
下面是完整的 main.js。请替换原文件,不要把它追加到已有代码后面。
main.js 中只能保留一个 ipcMain.handle('file:open-text', ...),否则会出现 Attempted to register a second handler。const { app, BrowserWindow, dialog, ipcMain } = require('electron/main');
const { readFile } = require('node:fs/promises');
const path = require('node:path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
win.loadFile(path.join(__dirname, 'index.html'));
}
async function openTextFile() {
const result = await dialog.showOpenDialog({
title: '选择文本文件',
properties: ['openFile'],
filters: [
{
name: '文本文件',
extensions: ['txt'],
},
],
});
if (result.canceled || result.filePaths.length === 0) {
return null;
}
const filePath = result.filePaths[0];
const content = await readFile(filePath, 'utf8');
return {
name: path.basename(filePath),
content,
};
}
app.whenReady().then(() => {
ipcMain.handle('file:open-text', openTextFile);
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
先看打开对话框的部分:
properties: ['openFile']:只能选择文件。extensions: ['txt']:只显示.txt文件。result.canceled:用户是否取消选择。result.filePaths[0]:用户选择的第一个文件路径。
取得路径后,下面这行代码读取文件内容:
const content = await readFile(filePath, 'utf8');
'utf8' 表示按 UTF-8 编码读取文本。path.basename(filePath) 则从完整路径中取得文件名。
🌉 给页面提供 openTextFile
main.js 已经能选择和读取文件,接下来在 preload.js 中给页面提供调用方法:
const { contextBridge, ipcRenderer } = require('electron/renderer');
contextBridge.exposeInMainWorld('electronAPI', {
openTextFile: () => ipcRenderer.invoke('file:open-text'),
});
页面最终可以调用:
window.electronAPI.openTextFile();
它会请求 main.js 中同名频道 file:open-text 的处理函数,并等待文件数据返回。
openTextFile(),不要把完整的 ipcRenderer 或 fs 模块暴露给页面。🖥 准备页面
在 index.html 中添加一个按钮,以及显示文件名和内容的位置:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>文本文件预览</title>
</head>
<body>
<h1>文本文件预览</h1>
<button id="open-file" type="button">选择文件</button>
<p id="file-name">还没有选择文件</p>
<pre id="file-content"></pre>
<script src="./renderer.js"></script>
</body>
</html>
pre 元素会保留文本中的换行和空格,适合预览纯文本文件。
🖱 点击按钮并显示内容
最后在 renderer.js 中处理按钮点击:
const openButton = document.querySelector('#open-file');
const fileName = document.querySelector('#file-name');
const fileContent = document.querySelector('#file-content');
openButton.addEventListener('click', async () => {
try {
const file = await window.electronAPI.openTextFile();
if (!file) {
fileName.textContent = '已取消选择';
return;
}
fileName.textContent = file.name;
fileContent.textContent = file.content;
} catch (error) {
console.error('读取文件失败:', error);
fileName.textContent = '读取文件失败';
fileContent.textContent = '';
}
});
这里需要处理三种结果:
- 用户选择文件:显示文件名和内容。
- 用户取消选择:显示“已取消选择”。
- 文件读取失败:显示错误提示。
文件内容使用 textContent 显示。即使文本中包含 HTML 标签,也只会显示为普通文字。
运行项目:
npm start
点击“选择文件”,选择一个 UTF-8 编码的 .txt 文件,页面就会显示文件名和内容。
🪤 常见问题
出现 Attempted to register a second handler
这表示 main.js 中重复注册了 file:open-text。搜索这个频道名,只保留一个 ipcMain.handle;直接使用本文的完整 main.js 替换旧文件即可避免重复。
点击按钮后没有反应
先检查 BrowserWindow 是否正确配置了 preload.js:
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
还要确认预加载脚本和主进程使用了相同的频道名:file:open-text。
取消选择后为什么没有文件数据
取消属于正常操作。openTextFile() 会返回 null,页面判断 !file 后直接结束即可。
为什么看不到 Markdown 文件
当前 filters 只包含 txt。如果还要选择 Markdown 文件,可以改成:
extensions: ['txt', 'md'],
扩展名不要写点号,应写成 'md',而不是 '.md'。
为什么中文内容显示乱码
本例按照 UTF-8 读取文件。如果原文件使用其他编码,需要先把文件转换为 UTF-8,或使用对应的解码方式。
🧾 小节总结
showOpenDialog负责打开系统文件选择框。showOpenDialog返回路径,不会自动读取文件。readFile(filePath, 'utf8')用于读取文本内容。- 用户取消选择时,应该正常结束,不需要显示错误。
- 预加载脚本只向页面提供用途明确的
openTextFile()。 - 页面使用
textContent显示文件内容。
❓ 知识问答
Q1:为什么 filePaths 是数组?
A:因为 showOpenDialog 支持多选。本文只选择一个文件,所以读取第一个元素。
Q2:filters 能证明文件一定是文本吗?
A:不能。它只按扩展名筛选文件,不能检查文件内部的内容。
Q3:为什么 readFile 要写 utf8?
A:因为本文读取的是文本。指定 utf8 后,返回值才是字符串。
Q4:为什么 dialog 写在 main.js 中?
A:dialog 是主进程模块,不能直接在普通页面脚本中使用。
🧪 小练习
练习一:让文件选择框同时支持 .txt 和 .md 文件。
filters: [
{
name: '文本文件',
// 请在这里编写代码
},
],
练习二:成功读取文件后,把页面标题改成文件名。
if (file) {
// 请在这里编写代码
}
🎉 恭喜你已经完成 Electron 文本文件选择与读取功能啦!
