8. 综合练习:迷你记事本
这是什么:本章把前 7 章全部串起来,做一个真的能用的记事本:打开文件 → 编辑 → 另存为,最后打包成 exe。
为什么用:单一知识点学会了不等于会做软件——记事本五脏俱全:窗口、界面、IPC、对话框、文件读写一样不缺,是检验学习的最好标尺。
如何用:5 个文件照抄 + 逐个理解,跑通后再自己加功能。
8.1 项目结构
新建一个干净项目(或复用原项目清空旧界面),最终结构:
mini-notepad/
├── package.json ← 第 2 章的配置,main 指向 main.js
├── main.js ← 窗口 + 两个 IPC 频道
├── preload.js ← 暴露 open / save 两个 API
├── index.html ← 界面
├── style.css ← 样式
└── renderer.js ← 界面交互
8.2 主进程:窗口与文件读写
main.js(核心就是两个频道):
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const fs = require('fs')
const path = require('path')
function createWindow() {
const win = new BrowserWindow({
width: 900,
height: 650,
title: '迷你记事本',
webPreferences: { preload: path.join(__dirname, 'preload.js') },
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
// 打开:弹对话框选文件 → 读出内容一起返回
ipcMain.handle('note-open', async () => {
const result = await dialog.showOpenDialog({
title: '打开文件',
properties: ['openFile'],
filters: [{ name: '文本文件', extensions: ['txt', 'md'] }],
})
if (result.canceled) return null
const filePath = result.filePaths[0]
return { path: filePath, content: fs.readFileSync(filePath, 'utf-8') }
})
// 保存:弹对话框要路径 → 写入内容 → 返回保存位置
ipcMain.handle('note-save', async (_event, content) => {
const result = await dialog.showSaveDialog({
title: '保存文件',
defaultPath: '未命名.txt',
filters: [{ name: '文本文件', extensions: ['txt'] }],
})
if (result.canceled) return null
fs.writeFileSync(result.filePath, content, 'utf-8')
return result.filePath
})
createWindow()
})
app.on('window-all-closed', () => {
app.quit()
})
| 代码 | 作用 |
|---|---|
showOpenDialog + readFileSync |
选路径(第 6 章)+ 读文件(Node 的 fs),打包成一个"打开"动作 |
showSaveDialog |
弹"另存为"框,defaultPath 是默认文件名 |
writeFileSync(path, content, 'utf-8') |
把界面传来的文本写进硬盘 |
返回 null |
两条约定:取消 = null,界面据此不更新状态 |
8.3 preload:只暴露两个动作
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('noteAPI', {
open: () => ipcRenderer.invoke('note-open'),
save: (content) => ipcRenderer.invoke('note-save', content),
})
界面能做的只有"打开"和"保存"——没暴露的能力一概没有,这就是白名单的美感。
8.4 界面三件套
index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>迷你记事本</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<strong>📒 迷你记事本</strong>
<button id="btn-open">打开</button>
<button id="btn-save">保存</button>
<span id="status">未打开文件</span>
</header>
<textarea id="editor" placeholder="在这里写点什么…"></textarea>
<script src="renderer.js"></script>
</body>
</html>
style.css:
* { margin: 0; box-sizing: border-box; }
body {
height: 100vh;
display: flex;
flex-direction: column;
font-family: system-ui, sans-serif;
}
header {
display: flex;
gap: 8px;
align-items: center;
padding: 8px 12px;
background: #1f2937;
color: #fff;
}
header strong { margin-right: auto; }
button {
padding: 4px 14px;
cursor: pointer;
}
#status {
font-size: 12px;
opacity: 0.7;
}
#editor {
flex: 1;
resize: none;
border: none;
padding: 16px;
font-size: 15px;
line-height: 1.6;
outline: none;
}
renderer.js:
const editor = document.getElementById('editor')
const status = document.getElementById('status')
document.getElementById('btn-open').addEventListener('click', async () => {
const note = await window.noteAPI.open()
if (!note) return
editor.value = note.content
status.textContent = note.path
})
document.getElementById('btn-save').addEventListener('click', async () => {
const saved = await window.noteAPI.save(editor.value)
if (!saved) return
status.textContent = '已保存到 ' + saved
})
npm start,你会看到:
深色工具栏 + 大片编辑区的窗口。
点「打开」选一个 txt → 内容出现在编辑区、状态栏显示路径;
改几个字点「保存」→ 弹另存为框 → 状态栏提示"已保存到 …"。
用系统记事本打开刚存的文件,内容一字不差。
一个能日常应急的文本编辑器,从头到尾不到 100 行。
8.5 打包成品
把第 7 章的 build 配置抄进 package.json(productName 改成"迷你记事本"),然后:
npm run dist
release/ 里的 迷你记事本 1.0.0.exe 就是可分发的成品。
8.6 进阶方向(自己动手)
- 标题联动:打开/保存后把
win.setTitle(文件名)——主进程加频道或用webContents推送 - 快捷键:界面监听
Ctrl+S触发保存(渲染进程keydown就行) - 拖拽打开:
textarea监听drop事件拿file.path,主进程按路径读文件 - 自动更新:正式产品接
electron-updater,发布新版后用户端自动升级 - 前端框架:界面复杂了换 Vue / React 工程化开发,打包前多一步构建,electron-builder 照常收尾
💡 动手改
- 给
filters加json和log扩展名,试着打开一个 JSON 文件 - 加一个「新建」按钮:清空编辑区、状态栏复位为"未打开文件"
- 保存前检测内容没变就
return,跳过弹框(提示:记下上次保存的内容做对比)
📦 本章小结
- 完整桌面软件 = 窗口(第 2 章)+ 进程分工(第 3 章)+ 网页界面(第 4 章)+ IPC(第 5 章)+ 原生能力(第 6 章)+ 打包(第 7 章)
- 记事本的核心数据流:界面文本 →
noteAPI.save→ 主进程fs写盘 - 取消操作返回
null是贯穿始终的小约定 - 恭喜毕业 🐸——前端板块的网页技术加上 Electron,你已经能把作品带出浏览器了
学完 Electron,回到 前端板块总览 看看还能点亮什么!