react+umi项目如何添加electron的功能

流程

1、安装Electron相关依赖包

2、创建Electron主进程文件(main.js)

3、修改package.json添加Electron启动脚本

4、测试Electron应用启动和功能

5、创建Electron预加载脚本(preload.js)

6、配置Electron构建和打包脚本

7、添加测试页面

效果

1、安装Electron相关依赖包

bash 复制代码
npm install --save-dev electron electron-builder 

安装依赖遇到的相关问题

1)发现遇到冲突

bash 复制代码
npm install --save-dev electron electron-builder --legacy-peer-deps 

2) 网络连接问题导致Electron下载失败。使用yarn来安装,或者设置淘宝镜像源来解决网络问题。

bash 复制代码
npm config set registry https://registry.npmmirror.com 

3)设置镜像源成功。现在再次尝试安装Electron相关依赖。发现问题持续存在。设置Electron的镜像源来解决下载问题。

bash 复制代码
npm config set electron_mirror https://npmmirror.com/mirrors/electron/ 

4)设置镜像源成功。现在再次尝试安装Electron相关依赖。问题依旧存在。使用正确的环境变量来设置Electron镜像源。

bash 复制代码
$env:ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/" 

5)现在设置了Electron镜像源,再次尝试安装Electron依赖,安装成功

6)更新todo状态并开始创建Electron主进程文件。

  • 6.1) 现在需要安装concurrently和wait-on依赖,这些是electron:dev脚本需要的工具
bash 复制代码
npm install --save-dev concurrently wait-on --legacy-peer-deps 
  • 6.2)现在添加Electron构建配置到package.json文件中。
  • 6.3)现在测试Electron应用。首先启动开发环境的Electron应用,这会同时启动React开发服务器和Electron窗口。

2、创建Electron主进程文件(main.js)

javascript 复制代码
const { app, BrowserWindow, Menu, ipcMain, dialog, shell, Notification } = require('electron');
const path = require('path');
const isDev = process.env.NODE_ENV === 'development';

// 保持对window对象的全局引用,避免JavaScript垃圾回收时窗口被自动关闭
let mainWindow;

function createWindow() {
  // 创建浏览器窗口
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: false, // 出于安全考虑,禁用node集成
      contextIsolation: true, // 启用上下文隔离
      enableRemoteModule: false, // 禁用remote模块
      preload: path.join(__dirname, 'preload.js') // 预加载脚本
    },
    icon: path.join(__dirname, 'public/icons/favicon.ico'), // 应用图标
    show: false, // 初始时不显示窗口,等加载完成后再显示
    titleBarStyle: 'default'
  });

  // 加载应用
  if (isDev) {
    // 开发环境:先尝试加载开发服务器,如果失败则加载测试页面
    mainWindow.loadURL('http://localhost:8000').catch(() => {
      console.log('开发服务器未启动,加载测试页面');
      mainWindow.loadFile(path.join(__dirname, 'test-electron.html'));
    });
    // 打开开发者工具
    mainWindow.webContents.openDevTools();
  } else {
    // 生产环境:加载打包后的文件
    mainWindow.loadFile(path.join(__dirname, 'dist/index.html'));
  }

  // 当页面加载完成后显示窗口
  mainWindow.once('ready-to-show', () => {
    mainWindow.show();
  });

  // 当窗口被关闭时,取消引用window对象
  mainWindow.on('closed', () => {
    mainWindow = null;
  });

  // 处理外部链接
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
    require('electron').shell.openExternal(url);
    return { action: 'deny' };
  });
}

// Electron初始化完成,创建窗口
app.whenReady().then(() => {
  createWindow();

  // 在macOS上,当点击dock图标且没有其他窗口打开时,重新创建窗口
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

// 当所有窗口都被关闭时退出应用
app.on('window-all-closed', () => {
  // 在macOS上,应用和菜单栏会保持活跃状态,直到用户明确退出
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 在开发环境中启用热重载
if (isDev) {
  try {
    require('electron-reload')(__dirname, {
      electron: path.join(__dirname, 'node_modules', '.bin', 'electron'),
      hardResetMethod: 'exit'
    });
  } catch (err) {
    console.log('electron-reload not found, hot reload disabled');
  }
}

// 设置应用菜单
const template = [
  {
    label: '文件',
    submenu: [
      {
        label: '退出',
        accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
        click: () => {
          app.quit();
        }
      }
    ]
  },
  {
    label: '编辑',
    submenu: [
      { label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
      { label: '重做', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
      { type: 'separator' },
      { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' },
      { label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' },
      { label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' }
    ]
  },
  {
    label: '视图',
    submenu: [
      { label: '重新加载', accelerator: 'CmdOrCtrl+R', role: 'reload' },
      { label: '强制重新加载', accelerator: 'CmdOrCtrl+Shift+R', role: 'forceReload' },
      { label: '开发者工具', accelerator: 'F12', role: 'toggleDevTools' },
      { type: 'separator' },
      { label: '实际大小', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' },
      { label: '放大', accelerator: 'CmdOrCtrl+Plus', role: 'zoomIn' },
      { label: '缩小', accelerator: 'CmdOrCtrl+-', role: 'zoomOut' },
      { type: 'separator' },
      { label: '全屏', accelerator: 'F11', role: 'togglefullscreen' }
    ]
  },
  {
    label: '窗口',
    submenu: [
      { label: '最小化', accelerator: 'CmdOrCtrl+M', role: 'minimize' },
      { label: '关闭', accelerator: 'CmdOrCtrl+W', role: 'close' }
    ]
  }
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

// IPC 处理程序
ipcMain.handle('window-minimize', () => {
  const win = BrowserWindow.getFocusedWindow();
  if (win) win.minimize();
});

ipcMain.handle('window-maximize', () => {
  const win = BrowserWindow.getFocusedWindow();
  if (win) {
    if (win.isMaximized()) {
      win.unmaximize();
    } else {
      win.maximize();
    }
  }
});

ipcMain.handle('window-close', () => {
  const win = BrowserWindow.getFocusedWindow();
  if (win) win.close();
});

ipcMain.handle('dialog-open-file', async () => {
  const result = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [
      { name: 'All Files', extensions: ['*'] },
      { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
      { name: 'Documents', extensions: ['pdf', 'doc', 'docx'] }
    ]
  });
  return result;
});

ipcMain.handle('dialog-save-file', async (event, data) => {
  const result = await dialog.showSaveDialog({
    filters: [
      { name: 'JSON Files', extensions: ['json'] },
      { name: 'Text Files', extensions: ['txt'] },
      { name: 'All Files', extensions: ['*'] }
    ]
  });
  
  if (!result.canceled && result.filePath) {
    const fs = require('fs');
    try {
      fs.writeFileSync(result.filePath, JSON.stringify(data, null, 2));
      return { success: true, filePath: result.filePath };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }
  return { success: false, canceled: true };
});

ipcMain.handle('show-notification', (event, { title, body }) => {
  if (Notification.isSupported()) {
    new Notification({ title, body }).show();
    return { success: true };
  }
  return { success: false, error: 'Notifications not supported' };
});

ipcMain.handle('get-app-version', () => {
  return app.getVersion();
});

ipcMain.handle('set-theme', (event, theme) => {
  // 这里可以实现主题切换逻辑
  console.log('Setting theme to:', theme);
  return { success: true, theme };
});

ipcMain.handle('get-theme', () => {
  // 返回当前主题
  return 'light'; // 默认主题
});

ipcMain.handle('open-dev-tools', () => {
  const win = BrowserWindow.getFocusedWindow();
  if (win) {
    win.webContents.openDevTools();
    return { success: true };
  }
  return { success: false };
});

ipcMain.handle('check-for-updates', () => {
  // 这里可以实现更新检查逻辑
  console.log('Checking for updates...');
  return { hasUpdate: false, version: app.getVersion() };
});

ipcMain.handle('log', (event, { level, message }) => {
  console.log(`[${level.toUpperCase()}] ${message}`);
  return { success: true };
});

3、修改package.json添加Electron启动脚本

json 复制代码
{
  "main": "main.js",
  "homepage": "./",
  "scripts": {
    "electron": "cross-env NODE_ENV=development electron .",
    "electron:dev": "concurrently \"npm run start:dev\" \"wait-on http://localhost:8000 && cross-env NODE_ENV=development electron .\"",
    "electron:build": "npm run build && cross-env NODE_ENV=production electron .",
    "electron:pack": "npm run build && electron-builder",
    "electron:dist": "npm run build && electron-builder --publish=never"
  },
  "devDependencies":{
    "concurrently": "^9.2.1",
    "electron": "^38.0.0",
    "electron-builder": "^26.0.12",
    "wait-on": "^8.0.4"
  }
}

4、测试Electron应用启动和功能

bash 复制代码
npm run electron:dev 

npm run start:dev #单独启动React开发服务器来确保它能正常工作。

npm run electron # 检查Electron的启动状态

5、创建Electron预加载脚本(preload.js)

javascript 复制代码
const { contextBridge, ipcRenderer } = require('electron');

// 通过contextBridge暴露安全的API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 系统信息
  platform: process.platform,
  version: process.versions,
  
  // 窗口控制
  minimize: () => ipcRenderer.invoke('window-minimize'),
  maximize: () => ipcRenderer.invoke('window-maximize'),
  close: () => ipcRenderer.invoke('window-close'),
  
  // 文件操作
  openFile: () => ipcRenderer.invoke('dialog-open-file'),
  saveFile: (data) => ipcRenderer.invoke('dialog-save-file', data),
  
  // 通知
  showNotification: (title, body) => ipcRenderer.invoke('show-notification', { title, body }),
  
  // 应用信息
  getAppVersion: () => ipcRenderer.invoke('get-app-version'),
  
  // 监听器
  onMenuAction: (callback) => {
    ipcRenderer.on('menu-action', callback);
    // 返回清理函数
    return () => ipcRenderer.removeListener('menu-action', callback);
  },
  
  // 主题切换
  setTheme: (theme) => ipcRenderer.invoke('set-theme', theme),
  getTheme: () => ipcRenderer.invoke('get-theme'),
  
  // 开发工具
  openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
  
  // 检查更新
  checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
  
  // 日志
  log: {
    info: (message) => ipcRenderer.invoke('log', { level: 'info', message }),
    warn: (message) => ipcRenderer.invoke('log', { level: 'warn', message }),
    error: (message) => ipcRenderer.invoke('log', { level: 'error', message })
  }
});

// 在窗口加载完成后通知主进程
window.addEventListener('DOMContentLoaded', () => {
  // 可以在这里添加一些初始化逻辑
  console.log('Preload script loaded');
});

// 错误处理
window.addEventListener('error', (event) => {
  console.error('Renderer process error:', event.error);
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
});

6、配置Electron构建和打包脚本

json 复制代码
{
  "main": "main.js",
  "homepage": "./",
  "build": {
    "appId": "com.yunchuang.hdbsmk50",
    "productName": "HdbsMk50",
    "directories": {
      "output": "electron-dist"
    },
    "files": [
      "dist/**/*",
      "main.js",
      "preload.js",
      "node_modules/**/*",
      "public/icons/**/*"
    ],
    "mac": {
      "category": "public.app-category.productivity",
      "target": [
        {
          "target": "dmg",
          "arch": ["x64", "arm64"]
        }
      ]
    },
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": ["x64"]
        }
      ]
    },
    "linux": {
      "target": [
        {
          "target": "AppImage",
          "arch": ["x64"]
        }
      ]
    },
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true
    }
  }
}

7、添加测试页面

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Electron Test</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            text-align: center;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
            padding: 40px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }
        h1 {
            font-size: 2.5em;
            margin-bottom: 20px;
        }
        .info {
            background: rgba(255, 255, 255, 0.2);
            padding: 20px;
            border-radius: 8px;
            margin: 20px 0;
        }
        button {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 12px 24px;
            font-size: 16px;
            border-radius: 5px;
            cursor: pointer;
            margin: 10px;
        }
        button:hover {
            background: #45a049;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🚀 Electron 测试页面</h1>
        <p>恭喜!Electron 应用已成功启动!</p>
        
        <div class="info">
            <h3>系统信息</h3>
            <p id="platform">平台: 加载中...</p>
            <p id="version">版本: 加载中...</p>
        </div>
        
        <div class="info">
            <h3>功能测试</h3>
            <button onclick="testNotification()">测试通知</button>
            <button onclick="testDevTools()">打开开发者工具</button>
            <button onclick="testLog()">测试日志</button>
        </div>
        
        <div class="info">
            <h3>状态</h3>
            <p id="status">✅ Electron 集成成功!</p>
        </div>
    </div>

    <script>
        // 检查是否在Electron环境中
        if (window.electronAPI) {
            document.getElementById('platform').textContent = `平台: ${window.electronAPI.platform}`;
            document.getElementById('version').textContent = `Node版本: ${window.electronAPI.version.node}`;
            
            function testNotification() {
                window.electronAPI.showNotification('测试通知', '这是一个来自Electron的测试通知!');
            }
            
            function testDevTools() {
                window.electronAPI.openDevTools();
            }
            
            function testLog() {
                window.electronAPI.log.info('这是一条测试日志信息');
                alert('日志已发送到控制台');
            }
        } else {
            document.getElementById('platform').textContent = '平台: 浏览器环境';
            document.getElementById('version').textContent = '版本: 非Electron环境';
            document.getElementById('status').textContent = '⚠️ 当前在浏览器中运行,不是Electron环境';
            
            function testNotification() {
                alert('通知功能仅在Electron环境中可用');
            }
            
            function testDevTools() {
                alert('开发者工具功能仅在Electron环境中可用');
            }
            
            function testLog() {
                console.log('这是一条测试日志信息');
                alert('日志已发送到浏览器控制台');
            }
        }
    </script>
</body>
</html>
相关推荐
乖女子@@@3 小时前
React笔记_组件之间进行数据传递
javascript·笔记·react.js
F2E_Zhangmo3 小时前
基于cornerstone3D的dicom影像浏览器 第二章 加载本地文件夹中的dicom文件并归档
前端·javascript·css
念念不忘 必有回响3 小时前
js设计模式-装饰器模式
javascript·设计模式·装饰器模式
weixin_584121434 小时前
vue3+ts导出PDF
javascript·vue.js·pdf
给月亮点灯|4 小时前
Vue基础知识-脚手架开发-使用Axios发送异步请求+代理服务器解决前后端分离项目的跨域问题
前端·javascript·vue.js
叫我阿柒啊4 小时前
从Java全栈到前端框架:一次真实的面试对话与技术解析
java·javascript·typescript·vue·springboot·react·前端开发
@CLoudbays_Martin115 小时前
为什么动态视频业务内容不可以被CDN静态缓存?
java·运维·服务器·javascript·网络·python·php
缘如风7 小时前
easyui 获取自定义的属性
前端·javascript·easyui
诗书画唱7 小时前
【前端教程】JavaScript 实现图片鼠标悬停切换效果与==和=的区别
开发语言·前端·javascript