开发一个完整的Electron应用程序
以下是一个基础但功能完整的Electron应用结构:
📁 项目结构
my-electron-app/
├── package.json
├── main.js
├── preload.js
├── renderer.js
├── index.html
├── style.css
└── README.md
1. 📦 package.json
{"name": "my-electron-app","version": "1.0.0","description": "A minimal Electron application","main": "main.js","scripts": {"start": "electron .","dev": "electron .","build": "electron-builder","pack": "electron-builder --dir"},"repository": {"type": "git","url": "https://github.com/your-username/my-electron-app"},"keywords": ["Electron","quick","start","tutorial"],"author": "Your Name","license": "MIT","devDependencies": {"electron": "^28.0.0","electron-builder": "^24.0.0"},"dependencies": {"axios": "^1.6.0"},"build": {"appId": "com.example.my-electron-app","productName": "My Electron App","directories": {"output": "dist"},"files": ["dist/**/*","node_modules/**/*","*.js","*.html","*.css"],"win": {"target": "nsis"},"mac": {"category": "public.app-category.utilities"},"linux": {"target": "AppImage"}}
}
2. 🖥️ main.js (主进程)
const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron');
const path = require('path');
const fs = require('fs');// 全局变量
let mainWindow;// 创建窗口函数
function createWindow() {// 创建浏览器窗口mainWindow = new BrowserWindow({width: 1200,height: 800,minWidth: 800,minHeight: 600,webPreferences: {preload: path.join(__dirname, 'preload.js'),nodeIntegration: false,contextIsolation: true,},icon: path.join(__dirname, 'assets/icon.png')});// 加载应用的index.htmlmainWindow.loadFile('index.html');// 开发环境下打开开发者工具if (process.env.NODE_ENV === 'development') {mainWindow.webContents.openDevTools();}// 窗口关闭事件mainWindow.on('closed', function () {mainWindow = null;});
}// 应用准备就绪时创建窗口
app.whenReady().then(() => {createWindow();// macOS应用在Dock中点击时重新创建窗口app.on('activate', function () {if (BrowserWindow.getAllWindows().length === 0) createWindow();});
});// 所有窗口关闭时退出应用(Windows & Linux)
app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit();
});// IPC通信处理
ipcMain.handle('dialog:openFile', async () => {const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {properties: ['openFile']});if (canceled) {return null;} else {return filePaths[0];}
});ipcMain.handle('dialog:saveFile', async (event, data) => {const { canceled, filePath } = await dialog.showSaveDialog(mainWindow, {defaultPath: 'document.txt'});if (canceled) {return false;} else {try {fs.writeFileSync(filePath, data);return true;} catch (error) {return false;}}
});ipcMain.handle('read-file', async (event, filePath) => {try {const data = fs.readFileSync(filePath, 'utf8');return { success: true, data };} catch (error) {return { success: false, error: error.message };}
});// 菜单模板
const menuTemplate = [{label: '文件',submenu: [{label: '打开',accelerator: 'CmdOrCtrl+O',click: async () => {const result = await dialog.showOpenDialog(mainWindow, {properties: ['openFile']});if (!result.canceled) {mainWindow.webContents.send('file-opened', result.filePaths[0]);}}},{label: '保存',accelerator: 'CmdOrCtrl+S',click: () => {mainWindow.webContents.send('save-file');}},{ type: 'separator' },{label: '退出',accelerator: 'CmdOrCtrl+Q',click: () => {app.quit();}}]},{label: '编辑',submenu: [{ role: 'undo', label: '撤销' },{ role: 'redo', label: '重做' },{ type: 'separator' },{ role: 'cut', label: '剪切' },{ role: 'copy', label: '复制' },{ role: 'paste', label: '粘贴' }]},{label: '视图',submenu: [{ role: 'reload', label: '重新加载' },{ role: 'forcereload', label: '强制重新加载' },{ role: 'toggledevtools', label: '切换开发者工具' },{ type: 'separator' },{ role: 'resetzoom', label: '重置缩放' },{ role: 'zoomin', label: '放大' },{ role: 'zoomout', label: '缩小' },{ type: 'separator' },{ role: 'togglefullscreen', label: '切换全屏' }]},{label: '帮助',submenu: [{label: '学习更多',click: async () => {const { shell } = require('electron');await shell.openExternal('https://electronjs.org');}}]}
];// 构建菜单
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);// 导出用于测试
module.exports = { createWindow, mainWindow };
3. 🔧 preload.js (预加载脚本)
const { contextBridge, ipcRenderer } = require('electron');// 安全地暴露API到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {// 对话框相关openFile: () => ipcRenderer.invoke('dialog:openFile'),saveFile: (data) => ipcRenderer.invoke('dialog:saveFile', data),// 文件操作readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),// 事件监听onFileOpened: (callback) => ipcRenderer.on('file-opened', (_event, filePath) => callback(filePath)),onSaveFile: (callback) => ipcRenderer.on('save-file', () => callback()),// 移除监听器removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel)
});// 环境变量
contextBridge.exposeInMainWorld('env', {isDev: process.env.NODE_ENV === 'development'
});
4. 🎨 index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>My Electron App</title><link rel="stylesheet" href="style.css">
</head>
<body><div class="header"><h1>Electron 应用示例</h1><div class="toolbar"><button id="openBtn" class="btn">打开文件</button><button id="saveBtn" class="btn primary">保存文件</button><button id="clearBtn" class="btn">清空</button></div></div><div class="container"><div class="sidebar"><h3>功能列表</h3><ul><li class="active">文本编辑器</li><li>文件管理</li><li>系统信息</li><li>设置</li></ul></div><div class="main-content"><div class="editor-container"><textarea id="editor" placeholder="在这里输入您的内容..."spellcheck="false"></textarea></div><div class="status-bar"><span id="status">就绪</span><span id="charCount">字符数: 0</span></div></div></div><div class="notification" id="notification">操作成功!</div><script src="renderer.js"></script>
</body>
</html>
5. 🎨 style.css
* {margin: 0;padding: 0;box-sizing: border-box;
}body {font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;background-color: #f5f5f5;color: #333;height: 100vh;overflow: hidden;
}.header {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;padding: 20px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}.header h1 {margin-bottom: 15px;
}.toolbar {display: flex;gap: 10px;
}.btn {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;font-size: 14px;transition: all 0.3s ease;
}.btn:hover {transform: translateY(-2px);box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}.btn:active {transform: translateY(0);
}.btn.primary {background-color: #007bff;color: white;
}.btn.primary:hover {background-color: #0056b3;
}.container {display: flex;height: calc(100vh - 120px);
}.sidebar {width: 200px;background-color: #2c3e50;color: white;padding: 20px 0;overflow-y: auto;
}.sidebar h3 {padding: 0 20px 15px;border-bottom: 1px solid #34495e;margin-bottom: 15px;
}.sidebar ul {list-style: none;
}.sidebar li {padding: 12px 20px;cursor: pointer;transition: background-color 0.3s;
}.sidebar li:hover {background-color: #34495e;
}.sidebar li.active {background-color: #3498db;
}.main-content {flex: 1;display: flex;flex-direction: column;
}.editor-container {flex: 1;padding: 20px;
}#editor {width: 100%;height: 100%;border: 1px solid #ddd;border-radius: 4px;padding: 15px;font-family: 'Consolas', 'Monaco', monospace;font-size: 14px;resize: none;outline: none;background-color: white;box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
}#editor:focus {border-color: #3498db;box-shadow: inset 0 1px 3px rgba(0,0,0,0.1), 0 0 0 2px rgba(52, 152, 219, 0.2);
}.status-bar {display: flex;justify-content: space-between;align-items: center;padding: 10px 20px;background-color: #ecf0f1;border-top: 1px solid #ddd;font-size: 12px;color: #7f8c8d;
}.notification {position: fixed;top: 20px;right: 20px;padding: 12px 20px;background-color: #27ae60;color: white;border-radius: 4px;box-shadow: 0 4px 12px rgba(0,0,0,0.15);transform: translateX(400px);transition: transform 0.3s ease;z-index: 1000;
}.notification.show {transform: translateX(0);
}@media (max-width: 768px) {.container {flex-direction: column;}.sidebar {width: 100%;height: auto;}.sidebar ul {display: flex;overflow-x: auto;}.sidebar li {white-space: nowrap;}
}
6. 🖱️ renderer.js (渲染进程)
// 渲染进程逻辑
document.addEventListener('DOMContentLoaded', function() {const editor = document.getElementById('editor');const openBtn = document.getElementById('openBtn');const saveBtn = document.getElementById('saveBtn');const clearBtn = document.getElementById('clearBtn');const status = document.getElementById('status');const charCount = document.getElementById('charCount');const notification = document.getElementById('notification');// 显示通知function showNotification(message) {notification.textContent = message;notification.classList.add('show');setTimeout(() => {notification.classList.remove('show');}, 3000);}// 更新状态栏function updateStatusBar() {const text = editor.value;const count = text.length;charCount.textContent = `字符数: ${count}`;}// 更新状态文本function updateStatus(text) {status.textContent = text;}// 打开文件openBtn.addEventListener('click', async () => {try {updateStatus('正在打开文件...');const filePath = await window.electronAPI.openFile();if (filePath) {const result = await window.electronAPI.readFile(filePath);if (result.success) {editor.value = result.data;updateStatusBar();updateStatus(`已打开: ${filePath}`);showNotification('文件打开成功!');} else {updateStatus('打开文件失败');showNotification('文件打开失败!');}} else {updateStatus('取消打开文件');}} catch (error) {console.error('打开文件错误:', error);updateStatus('打开文件时发生错误');showNotification('打开文件时发生错误!');}});// 保存文件saveBtn.addEventListener('click', async () => {try {updateStatus('正在保存文件...');const success = await window.electronAPI.saveFile(editor.value);if (success) {updateStatus('文件保存成功');showNotification('文件保存成功!');} else {updateStatus('文件保存失败');showNotification('文件保存失败!');}} catch (error) {console.error('保存文件错误:', error);updateStatus('保存文件时发生错误');showNotification('保存文件时发生错误!');}});// 清空编辑器clearBtn.addEventListener('click', () => {editor.value = '';updateStatusBar();updateStatus('编辑器已清空');showNotification('编辑器已清空!');});// 编辑器内容变化事件editor.addEventListener('input', () => {updateStatusBar();updateStatus('文档已修改');});// 键盘快捷键document.addEventListener('keydown', (e) => {// Ctrl+S 保存if (e.ctrlKey && e.key === 's') {e.preventDefault();saveBtn.click();}// Ctrl+O 打开if (e.ctrlKey && e.key === 'o') {e.preventDefault();openBtn.click();}});// 监听主进程发送的事件window.electronAPI.onFileOpened((filePath) => {// 这里可以处理文件打开后的逻辑console.log('文件已打开:', filePath);});window.electronAPI.onSaveFile(() => {saveBtn.click();});// 初始化状态栏updateStatusBar();updateStatus('应用已启动');// 开发环境提示if (window.env.isDev) {showNotification('开发模式');}
});// 页面卸载时清理监听器
window.addEventListener('beforeunload', () => {window.electronAPI.removeAllListeners('file-opened');window.electronAPI.removeAllListeners('save-file');
});
7. 📝 README.md
# My Electron App一个基于 Electron 的桌面应用程序示例。## 功能特性- 📝 文本编辑器
- 📁 文件打开/保存
- 🎨 美观的用户界面
- ⌨️ 键盘快捷键支持
- 🖥️ 跨平台支持 (Windows, macOS, Linux)## 安装```bash
# 克隆仓库
git clone your-repo-url
cd my-electron-app# 安装依赖
npm install
开发
# 启动开发服务器
npm start# 或者
