开发一个完整的Electron应用程序

以下是一个基础但功能完整的Electron应用结构:

📁 项目结构

复制代码
my-electron-app/
├── package.json
├── main.js
├── preload.js
├── renderer.js
├── index.html
├── style.css
└── README.md

1. 📦 package.json

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 (主进程)

javascript 复制代码
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.html
  mainWindow.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 (预加载脚本)

javascript 复制代码
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

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

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 (渲染进程)

javascript 复制代码
// 渲染进程逻辑
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

markdown 复制代码
# My Electron App

一个基于 Electron 的桌面应用程序示例。

## 功能特性

- 📝 文本编辑器
- 📁 文件打开/保存
- 🎨 美观的用户界面
- ⌨️ 键盘快捷键支持
- 🖥️ 跨平台支持 (Windows, macOS, Linux)

## 安装

```bash
# 克隆仓库
git clone your-repo-url
cd my-electron-app

# 安装依赖
npm install

开发

bash 复制代码
# 启动开发服务器
npm start

# 或者
相关推荐
故作春风2 小时前
手把手实现一个前端 AI 编程助手:从 MCP 思想到 VS Code 插件实战
前端·人工智能
不会算法的小灰2 小时前
Vue.js 基础教程:从入门到实践
前端·javascript·vue.js
掘金一周2 小时前
没开玩笑,全框架支持的 dialog 组件,支持响应式| 掘金一周 11.6
前端·人工智能
拉不动的猪3 小时前
浏览器&Websocket&热更新
前端·javascript·vue.js
Never_Satisfied3 小时前
在JavaScript中,将包含HTML实体字符的字符串转换为普通字符
开发语言·javascript·html
im_AMBER3 小时前
React 12
前端·javascript·笔记·学习·react.js·前端框架
开开心心就好3 小时前
电脑音质提升:杜比全景声安装详细教程
java·开发语言·前端·数据库·电脑·ruby·1024程序员节
午安~婉3 小时前
HTML CSS八股
前端·css·html
有事没事实验室3 小时前
css变量
前端·css