Vue项目中的Electron桌面应用开发实践指南

Vue项目中的Electron桌面应用开发实践指南

文章目录

  • Vue项目中的Electron桌面应用开发实践指南
    • [1. 引言](#1. 引言)
      • [为什么选择 Electron + Vue?](#为什么选择 Electron + Vue?)
    • [2. Electron基础概念](#2. Electron基础概念)
      • [2.1 Electron架构](#2.1 Electron架构)
      • [2.2 Electron与Vue的结合](#2.2 Electron与Vue的结合)
    • [3. Vue项目集成Electron](#3. Vue项目集成Electron)
      • [3.1 集成方式](#3.1 集成方式)
      • [3.2 技术栈选择](#3.2 技术栈选择)
    • [4. 项目初始化与配置](#4. 项目初始化与配置)
      • [4.1 使用Vue CLI插件](#4.1 使用Vue CLI插件)
      • [4.2 手动集成方式](#4.2 手动集成方式)
        • [4.2.1 安装依赖](#4.2.1 安装依赖)
        • [4.2.2 项目结构调整](#4.2.2 项目结构调整)
        • [4.2.3 基础配置文件](#4.2.3 基础配置文件)
      • [4.3 主进程配置](#4.3 主进程配置)
      • [4.4 预加载脚本](#4.4 预加载脚本)
    • [5. 主进程与渲染进程通信](#5. 主进程与渲染进程通信)
      • [5.1 IPC通信基础](#5.1 IPC通信基础)
      • [5.2 设置IPC通信](#5.2 设置IPC通信)
      • [5.3 Vue组件中使用IPC](#5.3 Vue组件中使用IPC)
    • [6. 窗口管理与菜单系统](#6. 窗口管理与菜单系统)
      • [6.1 自定义菜单](#6.1 自定义菜单)
      • [6.2 多窗口管理](#6.2 多窗口管理)
      • [6.3 窗口控制组件](#6.3 窗口控制组件)
    • [7. 文件系统与本地存储](#7. 文件系统与本地存储)
      • [7.1 文件系统操作](#7.1 文件系统操作)
      • [7.2 本地存储管理](#7.2 本地存储管理)
    • [8. 应用打包与发布](#8. 应用打包与发布)
      • [8.1 Electron Builder 配置](#8.1 Electron Builder 配置)
      • [8.2 打包脚本](#8.2 打包脚本)
      • [8.3 自动更新](#8.3 自动更新)
    • [9. 性能优化最佳实践](#9. 性能优化最佳实践)
      • [9.1 渲染进程优化](#9.1 渲染进程优化)
      • [9.2 主进程优化](#9.2 主进程优化)
      • [9.3 构建优化](#9.3 构建优化)
    • [10. 常见问题与解决方案](#10. 常见问题与解决方案)
    • [11. 实战案例分析](#11. 实战案例分析)
      • [11.1 代码编辑器应用](#11.1 代码编辑器应用)
      • [11.2 数据库管理工具](#11.2 数据库管理工具)
    • [12. 总结与展望](#12. 总结与展望)
      • [12.1 Electron + Vue 的优势](#12.1 Electron + Vue 的优势)
      • [12.2 面临的挑战](#12.2 面临的挑战)
      • [12.3 最佳实践建议](#12.3 最佳实践建议)
      • [12.4 未来发展趋势](#12.4 未来发展趋势)
      • [12.5 学习资源推荐](#12.5 学习资源推荐)

深入探讨如何在 Vue 项目中集成 Electron,构建跨平台桌面应用

1. 引言

随着桌面应用需求的不断增长,Electron 作为构建跨平台桌面应用的利器,结合 Vue.js 的组件化开发优势,为开发者提供了一套完整的桌面应用解决方案。本文将详细介绍如何在 Vue 项目中集成 Electron,从基础概念到实际项目开发,帮助开发者快速掌握 Electron + Vue 的开发技巧。

为什么选择 Electron + Vue?

  • 跨平台支持:一套代码,支持 Windows、macOS、Linux
  • Web技术栈:使用熟悉的 HTML/CSS/JavaScript 技术
  • Vue生态:丰富的 Vue 组件和工具链
  • 开发效率:快速开发和迭代
  • 原生能力:访问操作系统原生 API

2. Electron基础概念

2.1 Electron架构

Electron 应用由两个主要进程组成:

  1. 主进程(Main Process)

    • 控制应用生命周期
    • 管理原生 GUI(窗口、菜单等)
    • 运行在 Node.js 环境中
  2. 渲染进程(Renderer Process)

    • 负责 UI 渲染
    • 运行 Web 页面
    • 每个窗口对应一个渲染进程
javascript 复制代码
// 主进程示例 (main.js)
const { app, BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  });

  win.loadFile('index.html');
}

app.whenReady().then(createWindow);

2.2 Electron与Vue的结合

Vue 作为渲染进程的 UI 框架,负责界面展示和用户交互,而 Electron 提供原生桌面应用的能力,两者结合可以创建功能丰富的桌面应用。

3. Vue项目集成Electron

3.1 集成方式

主要有两种集成方式:

  1. 在现有 Vue 项目中添加 Electron
  2. 使用脚手架工具创建新项目

3.2 技术栈选择

  • Vue 3 + Vite:现代化的构建工具,开发体验更好
  • Vue 2 + Webpack:成熟的生态系统,稳定性高
  • TypeScript:类型安全,更好的开发体验

4. 项目初始化与配置

4.1 使用Vue CLI插件

bash 复制代码
# 安装 Vue CLI(如果尚未安装)
npm install -g @vue/cli

# 创建 Vue 项目
vue create electron-vue-app

# 进入项目目录
cd electron-vue-app

# 添加 Electron 插件
vue add electron-builder

# 选择 Electron 版本
? Choose Electron Version (Use arrow keys)
  ❯ ^13.0.0
    ^12.0.0
    ^11.0.0

4.2 手动集成方式

4.2.1 安装依赖
bash 复制代码
# 安装 Electron
npm install electron --save-dev

# 安装 Electron Builder(打包工具)
npm install electron-builder --save-dev

# 安装 concurrently(同时运行多个命令)
npm install concurrently --save-dev

# 安装 wait-on(等待资源准备就绪)
npm install wait-on --save-dev
4.2.2 项目结构调整
复制代码
electron-vue-app/
├── public/
│   ├── index.html
│   └── electron-icon.png
├── src/
│   ├── main/           # Electron 主进程
│   │   ├── index.js
│   │   ├── menu.js
│   │   └── ipc.js
│   ├── renderer/       # Vue 渲染进程
│   │   ├── main.js
│   │   ├── App.vue
│   │   └── components/
│   └── preload/        # 预加载脚本
│       └── preload.js
├── package.json
├── vue.config.js
└── electron-builder.json
4.2.3 基础配置文件

package.json

json 复制代码
{
  "name": "electron-vue-app",
  "version": "1.0.0",
  "description": "Vue + Electron Desktop App",
  "main": "dist_electron/index.js",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "electron:serve": "concurrently \"npm run serve\" \"wait-on http://localhost:8080 && electron .\"",
    "electron:build": "vue-cli-service build && electron-builder",
    "postinstall": "electron-builder install-app-deps"
  },
  "dependencies": {
    "vue": "^3.2.0",
    "vue-router": "^4.0.0",
    "vuex": "^4.0.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-router": "~5.0.0",
    "@vue/cli-plugin-vuex": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "electron": "^18.0.0",
    "electron-builder": "^23.0.0",
    "concurrently": "^7.0.0",
    "wait-on": "^6.0.0"
  }
}

vue.config.js

javascript 复制代码
const { defineConfig } = require('@vue/cli-service');

module.exports = defineConfig({
  transpileDependencies: true,
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      contextIsolation: false,
      customFileProtocol: './',
      builderOptions: {
        appId: 'com.example.electron-vue-app',
        productName: 'Electron Vue App',
        directories: {
          output: 'dist_electron'
        },
        files: [
          'dist/**/*',
          'dist_electron/**/*'
        ],
        mac: {
          icon: 'build/icon.icns'
        },
        win: {
          icon: 'build/icon.ico'
        },
        linux: {
          icon: 'build/icon.png'
        }
      }
    }
  }
});

4.3 主进程配置

src/main/index.js

javascript 复制代码
const { app, BrowserWindow, Menu } = require('electron');
const path = require('path');
const { createMenu } = require('./menu');
const { setupIPC } = require('./ipc');

// 保持窗口对象的全局引用
let mainWindow;

function createWindow() {
  // 创建浏览器窗口
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      enableRemoteModule: true,
      preload: path.join(__dirname, '../preload/preload.js')
    },
    icon: path.join(__dirname, '../../build/icon.png'),
    show: false, // 先不显示窗口
    titleBarStyle: 'default'
  });

  // 加载应用界面
  const isDevelopment = process.env.NODE_ENV === 'development';
  
  if (isDevelopment) {
    mainWindow.loadURL('http://localhost:8080');
    mainWindow.webContents.openDevTools();
  } else {
    mainWindow.loadFile(path.join(__dirname, '../../dist/index.html'));
  }

  // 窗口准备就绪时显示
  mainWindow.once('ready-to-show', () => {
    mainWindow.show();
  });

  // 窗口关闭事件
  mainWindow.on('closed', () => {
    mainWindow = null;
  });

  // 创建菜单
  createMenu();
  
  // 设置 IPC 通信
  setupIPC();
}

// 应用准备就绪时创建窗口
app.whenReady().then(() => {
  createWindow();

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

// 所有窗口关闭时退出应用
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 安全:在加载页面之前设置权限
app.on('web-contents-created', (event, contents) => {
  contents.on('will-navigate', (event, navigationUrl) => {
    const parsedUrl = new URL(navigationUrl);
    
    if (parsedUrl.origin !== 'http://localhost:8080') {
      event.preventDefault();
    }
  });
});

4.4 预加载脚本

src/preload/preload.js

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

// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 窗口控制
  minimizeWindow: () => ipcRenderer.send('minimize-window'),
  maximizeWindow: () => ipcRenderer.send('maximize-window'),
  closeWindow: () => ipcRenderer.send('close-window'),
  
  // 文件操作
  readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
  writeFile: (filePath, data) => ipcRenderer.invoke('write-file', filePath, data),
  
  // 应用信息
  getAppVersion: () => ipcRenderer.invoke('get-app-version'),
  
  // 监听事件
  onUpdateAvailable: (callback) => ipcRenderer.on('update-available', callback),
  onDownloadProgress: (callback) => ipcRenderer.on('download-progress', callback)
});

5. 主进程与渲染进程通信

5.1 IPC通信基础

Electron 使用 IPC(Inter-Process Communication)机制实现主进程和渲染进程之间的通信。

5.2 设置IPC通信

src/main/ipc.js

javascript 复制代码
const { ipcMain, dialog, app } = require('electron');
const fs = require('fs').promises;
const path = require('path');

function setupIPC() {
  // 窗口控制
  ipcMain.on('minimize-window', (event) => {
    const window = require('./index').getMainWindow();
    window.minimize();
  });

  ipcMain.on('maximize-window', (event) => {
    const window = require('./index').getMainWindow();
    if (window.isMaximized()) {
      window.unmaximize();
    } else {
      window.maximize();
    }
  });

  ipcMain.on('close-window', (event) => {
    const window = require('./index').getMainWindow();
    window.close();
  });

  // 文件操作 - 读取文件
  ipcMain.handle('read-file', async (event, filePath) => {
    try {
      const data = await fs.readFile(filePath, 'utf-8');
      return { success: true, data };
    } catch (error) {
      return { success: false, error: error.message };
    }
  });

  // 文件操作 - 写入文件
  ipcMain.handle('write-file', async (event, filePath, data) => {
    try {
      await fs.writeFile(filePath, data, 'utf-8');
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  });

  // 文件对话框
  ipcMain.handle('show-open-dialog', async (event, options) => {
    const window = require('./index').getMainWindow();
    const result = await dialog.showOpenDialog(window, options);
    return result;
  });

  ipcMain.handle('show-save-dialog', async (event, options) => {
    const window = require('./index').getMainWindow();
    const result = await dialog.showSaveDialog(window, options);
    return result;
  });

  // 应用信息
  ipcMain.handle('get-app-version', async (event) => {
    return app.getVersion();
  });

  // 获取应用数据路径
  ipcMain.handle('get-app-data-path', async (event) => {
    return app.getPath('userData');
  });

  // 数据库操作(使用 SQLite)
  ipcMain.handle('db-query', async (event, query, params = []) => {
    const db = require('./database');
    try {
      const result = await db.query(query, params);
      return { success: true, data: result };
    } catch (error) {
      return { success: false, error: error.message };
    }
  });
}

module.exports = { setupIPC };

5.3 Vue组件中使用IPC

src/renderer/components/FileManager.vue

vue 复制代码
<template>
  <div class="file-manager">
    <div class="toolbar">
      <button @click="openFile" class="btn btn-primary">打开文件</button>
      <button @click="saveFile" class="btn btn-success">保存文件</button>
      <button @click="createNewFile" class="btn btn-info">新建文件</button>
    </div>
    
    <div class="editor-container">
      <textarea 
        v-model="fileContent" 
        class="file-editor"
        placeholder="在这里编辑文件内容..."
      ></textarea>
    </div>
    
    <div class="status-bar">
      <span>{{ currentFile || '未选择文件' }}</span>
      <span>{{ fileContent.length }} 字符</span>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';

export default {
  name: 'FileManager',
  setup() {
    const fileContent = ref('');
    const currentFile = ref('');
    const isModified = ref(false);

    // 打开文件
    const openFile = async () => {
      try {
        const result = await window.electronAPI.showOpenDialog({
          title: '选择文件',
          filters: [
            { name: '文本文件', extensions: ['txt', 'md', 'js', 'json'] },
            { name: '所有文件', extensions: ['*'] }
          ],
          properties: ['openFile']
        });

        if (!result.canceled && result.filePaths.length > 0) {
          const filePath = result.filePaths[0];
          const readResult = await window.electronAPI.readFile(filePath);
          
          if (readResult.success) {
            fileContent.value = readResult.data;
            currentFile.value = filePath;
            isModified.value = false;
            ElMessage.success('文件加载成功');
          } else {
            ElMessage.error('读取文件失败: ' + readResult.error);
          }
        }
      } catch (error) {
        ElMessage.error('打开文件失败: ' + error.message);
      }
    };

    // 保存文件
    const saveFile = async () => {
      try {
        let filePath = currentFile.value;
        
        // 如果没有当前文件,显示保存对话框
        if (!filePath) {
          const result = await window.electronAPI.showSaveDialog({
            title: '保存文件',
            filters: [
              { name: '文本文件', extensions: ['txt'] },
              { name: 'Markdown', extensions: ['md'] },
              { name: 'JavaScript', extensions: ['js'] },
              { name: 'JSON', extensions: ['json'] },
              { name: '所有文件', extensions: ['*'] }
            ]
          });

          if (result.canceled) {
            return;
          }
          
          filePath = result.filePath;
        }

        const writeResult = await window.electronAPI.writeFile(filePath, fileContent.value);
        
        if (writeResult.success) {
          currentFile.value = filePath;
          isModified.value = false;
          ElMessage.success('文件保存成功');
        } else {
          ElMessage.error('保存文件失败: ' + writeResult.error);
        }
      } catch (error) {
        ElMessage.error('保存文件失败: ' + error.message);
      }
    };

    // 创建新文件
    const createNewFile = () => {
      if (isModified.value) {
        ElMessage.warning('请先保存当前文件');
        return;
      }
      
      fileContent.value = '';
      currentFile.value = '';
      isModified.value = false;
      ElMessage.info('已创建新文件');
    };

    // 监听内容变化
    const handleContentChange = () => {
      isModified.value = true;
    };

    return {
      fileContent,
      currentFile,
      openFile,
      saveFile,
      createNewFile,
      handleContentChange
    };
  }
};
</script>

<style scoped>
.file-manager {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: #f5f5f5;
}

.toolbar {
  display: flex;
  gap: 10px;
  padding: 10px;
  background: white;
  border-bottom: 1px solid #ddd;
}

.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.3s;
}

.btn-primary {
  background: #007bff;
  color: white;
}

.btn-primary:hover {
  background: #0056b3;
}

.btn-success {
  background: #28a745;
  color: white;
}

.btn-success:hover {
  background: #1e7e34;
}

.btn-info {
  background: #17a2b8;
  color: white;
}

.btn-info:hover {
  background: #138496;
}

.editor-container {
  flex: 1;
  padding: 10px;
  overflow: hidden;
}

.file-editor {
  width: 100%;
  height: 100%;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 15px;
  font-family: 'Consolas', 'Monaco', monospace;
  font-size: 14px;
  line-height: 1.5;
  resize: none;
  outline: none;
}

.status-bar {
  display: flex;
  justify-content: space-between;
  padding: 8px 15px;
  background: white;
  border-top: 1px solid #ddd;
  font-size: 12px;
  color: #666;
}
</style>

6. 窗口管理与菜单系统

6.1 自定义菜单

src/main/menu.js

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

function createMenu() {
  const template = [
    {
      label: '文件',
      submenu: [
        {
          label: '新建',
          accelerator: 'CmdOrCtrl+N',
          click: (menuItem, browserWindow) => {
            browserWindow.webContents.send('menu-new-file');
          }
        },
        {
          label: '打开',
          accelerator: 'CmdOrCtrl+O',
          click: (menuItem, browserWindow) => {
            browserWindow.webContents.send('menu-open-file');
          }
        },
        {
          label: '保存',
          accelerator: 'CmdOrCtrl+S',
          click: (menuItem, browserWindow) => {
            browserWindow.webContents.send('menu-save-file');
          }
        },
        { type: 'separator' },
        {
          label: '退出',
          accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
          click: () => {
            require('electron').app.quit();
          }
        }
      ]
    },
    {
      label: '编辑',
      submenu: [
        { role: 'undo' },
        { role: 'redo' },
        { type: 'separator' },
        { role: 'cut' },
        { role: 'copy' },
        { role: 'paste' },
        { role: 'selectall' }
      ]
    },
    {
      label: '视图',
      submenu: [
        { role: 'reload' },
        { role: 'forceReload' },
        { role: 'toggleDevTools' },
        { type: 'separator' },
        { role: 'resetZoom' },
        { role: 'zoomIn' },
        { role: 'zoomOut' },
        { type: 'separator' },
        { role: 'togglefullscreen' }
      ]
    },
    {
      label: '窗口',
      submenu: [
        { role: 'minimize' },
        { role: 'close' }
      ]
    },
    {
      label: '帮助',
      submenu: [
        {
          label: '关于',
          click: (menuItem, browserWindow) => {
            require('electron').dialog.showMessageBox(browserWindow, {
              type: 'info',
              title: '关于',
              message: 'Electron Vue App',
              detail: `版本: 1.0.0\nElectron: ${process.versions.electron}\nNode.js: ${process.versions.node}`
            });
          }
        },
        {
          label: '开发者工具',
          accelerator: 'F12',
          click: (menuItem, browserWindow) => {
            browserWindow.webContents.toggleDevTools();
          }
        },
        {
          label: '访问官网',
          click: () => {
            shell.openExternal('https://electronjs.org');
          }
        }
      ]
    }
  ];

  // macOS 特殊处理
  if (process.platform === 'darwin') {
    template.unshift({
      label: app.getName(),
      submenu: [
        { role: 'about' },
        { type: 'separator' },
        { role: 'services' },
        { type: 'separator' },
        { role: 'hide' },
        { role: 'hideothers' },
        { role: 'unhide' },
        { type: 'separator' },
        { role: 'quit' }
      ]
    });
  }

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

module.exports = { createMenu };

6.2 多窗口管理

src/main/windowManager.js

javascript 复制代码
const { BrowserWindow } = require('electron');
const path = require('path');

class WindowManager {
  constructor() {
    this.windows = new Map();
    this.windowIdCounter = 0;
  }

  createWindow(options = {}) {
    const windowId = ++this.windowIdCounter;
    
    const defaultOptions = {
      width: 1200,
      height: 800,
      minWidth: 600,
      minHeight: 400,
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
        enableRemoteModule: true,
        preload: path.join(__dirname, '../preload/preload.js')
      },
      show: false,
      titleBarStyle: 'default'
    };

    const windowOptions = { ...defaultOptions, ...options };
    const window = new BrowserWindow(windowOptions);

    // 加载页面
    const isDevelopment = process.env.NODE_ENV === 'development';
    if (isDevelopment) {
      window.loadURL('http://localhost:8080');
    } else {
      window.loadFile(path.join(__dirname, '../../dist/index.html'));
    }

    // 窗口事件
    window.once('ready-to-show', () => {
      window.show();
    });

    window.on('closed', () => {
      this.windows.delete(windowId);
    });

    // 保存窗口引用
    this.windows.set(windowId, window);
    
    return { windowId, window };
  }

  getWindow(windowId) {
    return this.windows.get(windowId);
  }

  getAllWindows() {
    return Array.from(this.windows.values());
  }

  closeWindow(windowId) {
    const window = this.windows.get(windowId);
    if (window) {
      window.close();
      this.windows.delete(windowId);
    }
  }

  closeAllWindows() {
    this.windows.forEach((window) => {
      window.close();
    });
    this.windows.clear();
  }

  sendToWindow(windowId, channel, ...args) {
    const window = this.windows.get(windowId);
    if (window && !window.isDestroyed()) {
      window.webContents.send(channel, ...args);
    }
  }

  broadcast(channel, ...args) {
    this.windows.forEach((window) => {
      if (!window.isDestroyed()) {
        window.webContents.send(channel, ...args);
      }
    });
  }
}

module.exports = WindowManager;

6.3 窗口控制组件

src/renderer/components/WindowControls.vue

vue 复制代码
<template>
  <div class="window-controls">
    <div class="control-btn minimize" @click="minimizeWindow">
      <svg width="12" height="12" viewBox="0 0 12 12">
        <rect x="1" y="6" width="10" height="1" fill="currentColor"/>
      </svg>
    </div>
    <div class="control-btn maximize" @click="maximizeWindow">
      <svg width="12" height="12" viewBox="0 0 12 12">
        <rect x="1" y="1" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1"/>
      </svg>
    </div>
    <div class="control-btn close" @click="closeWindow">
      <svg width="12" height="12" viewBox="0 0 12 12">
        <path d="M1 1 L11 11 M11 1 L1 11" stroke="currentColor" stroke-width="1"/>
      </svg>
    </div>
  </div>
</template>

<script>
export default {
  name: 'WindowControls',
  methods: {
    minimizeWindow() {
      window.electronAPI.minimizeWindow();
    },
    maximizeWindow() {
      window.electronAPI.maximizeWindow();
    },
    closeWindow() {
      window.electronAPI.closeWindow();
    }
  }
};
</script>

<style scoped>
.window-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
}

.control-btn {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.3s ease;
}

.control-btn:hover {
  transform: scale(1.1);
}

.control-btn.minimize {
  background: #ffbd2e;
  color: #333;
}

.control-btn.maximize {
  background: #28ca42;
  color: #333;
}

.control-btn.close {
  background: #ff5f57;
  color: white;
}

.control-btn svg {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.control-btn:hover svg {
  opacity: 1;
}
</style>

7. 文件系统与本地存储

7.1 文件系统操作

src/main/fileManager.js

javascript 复制代码
const fs = require('fs').promises;
const path = require('path');
const { app } = require('electron');

class FileManager {
  constructor() {
    this.appDataPath = app.getPath('userData');
    this.recentFiles = [];
    this.maxRecentFiles = 10;
  }

  async readFile(filePath) {
    try {
      const data = await fs.readFile(filePath, 'utf-8');
      await this.addToRecentFiles(filePath);
      return { success: true, data };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async writeFile(filePath, data) {
    try {
      await fs.writeFile(filePath, data, 'utf-8');
      await this.addToRecentFiles(filePath);
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async createDirectory(dirPath) {
    try {
      await fs.mkdir(dirPath, { recursive: true });
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async readDirectory(dirPath) {
    try {
      const files = await fs.readdir(dirPath, { withFileTypes: true });
      const fileList = files.map(file => ({
        name: file.name,
        isDirectory: file.isDirectory(),
        path: path.join(dirPath, file.name),
        size: file.isDirectory() ? 0 : null,
        modified: null
      }));

      // 获取文件详细信息
      for (const file of fileList) {
        if (!file.isDirectory) {
          try {
            const stats = await fs.stat(file.path);
            file.size = stats.size;
            file.modified = stats.mtime;
          } catch (error) {
            console.error(`获取文件信息失败: ${file.path}`, error);
          }
        }
      }

      return { success: true, data: fileList };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async deleteFile(filePath) {
    try {
      await fs.unlink(filePath);
      await this.removeFromRecentFiles(filePath);
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async deleteDirectory(dirPath) {
    try {
      await fs.rmdir(dirPath, { recursive: true });
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async copyFile(sourcePath, destPath) {
    try {
      await fs.copyFile(sourcePath, destPath);
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async moveFile(sourcePath, destPath) {
    try {
      await fs.rename(sourcePath, destPath);
      await this.updateRecentFilePath(sourcePath, destPath);
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async getFileInfo(filePath) {
    try {
      const stats = await fs.stat(filePath);
      return {
        success: true,
        data: {
          size: stats.size,
          modified: stats.mtime,
          created: stats.birthtime,
          isDirectory: stats.isDirectory(),
          isFile: stats.isFile()
        }
      };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  async addToRecentFiles(filePath) {
    // 移除已存在的相同文件
    this.recentFiles = this.recentFiles.filter(file => file !== filePath);
    
    // 添加到开头
    this.recentFiles.unshift(filePath);
    
    // 限制数量
    if (this.recentFiles.length > this.maxRecentFiles) {
      this.recentFiles = this.recentFiles.slice(0, this.maxRecentFiles);
    }

    // 保存到配置文件
    await this.saveRecentFiles();
  }

  async removeFromRecentFiles(filePath) {
    this.recentFiles = this.recentFiles.filter(file => file !== filePath);
    await this.saveRecentFiles();
  }

  async updateRecentFilePath(oldPath, newPath) {
    const index = this.recentFiles.indexOf(oldPath);
    if (index !== -1) {
      this.recentFiles[index] = newPath;
      await this.saveRecentFiles();
    }
  }

  async saveRecentFiles() {
    const configPath = path.join(this.appDataPath, 'recent-files.json');
    try {
      await fs.writeFile(configPath, JSON.stringify(this.recentFiles, null, 2));
    } catch (error) {
      console.error('保存最近文件列表失败:', error);
    }
  }

  async loadRecentFiles() {
    const configPath = path.join(this.appDataPath, 'recent-files.json');
    try {
      const data = await fs.readFile(configPath, 'utf-8');
      this.recentFiles = JSON.parse(data);
    } catch (error) {
      this.recentFiles = [];
    }
  }

  getRecentFiles() {
    return this.recentFiles;
  }
}

module.exports = FileManager;

7.2 本地存储管理

src/renderer/utils/storage.js

javascript 复制代码
class StorageManager {
  constructor() {
    this.dbName = 'electron-vue-app';
    this.version = 1;
    this.db = null;
  }

  // 初始化 IndexedDB
  async initDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);

      request.onerror = () => {
        reject(request.error);
      };

      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        
        // 创建存储对象
        if (!db.objectStoreNames.contains('settings')) {
          const settingsStore = db.createObjectStore('settings', { keyPath: 'key' });
          settingsStore.createIndex('key', 'key', { unique: true });
        }

        if (!db.objectStoreNames.contains('documents')) {
          const documentsStore = db.createObjectStore('documents', { keyPath: 'id', autoIncrement: true });
          documentsStore.createIndex('title', 'title', { unique: false });
          documentsStore.createIndex('created', 'created', { unique: false });
        }

        if (!db.objectStoreNames.contains('cache')) {
          const cacheStore = db.createObjectStore('cache', { keyPath: 'key' });
          cacheStore.createIndex('key', 'key', { unique: true });
          cacheStore.createIndex('expiry', 'expiry', { unique: false });
        }
      };
    });
  }

  // 设置值
  async setItem(storeName, key, value) {
    if (!this.db) await this.initDB();

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      
      const request = store.put({ key, value, timestamp: Date.now() });

      request.onsuccess = () => {
        resolve(request.result);
      };

      request.onerror = () => {
        reject(request.error);
      };
    });
  }

  // 获取值
  async getItem(storeName, key) {
    if (!this.db) await this.initDB();

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      
      const request = store.get(key);

      request.onsuccess = () => {
        const result = request.result;
        resolve(result ? result.value : null);
      };

      request.onerror = () => {
        reject(request.error);
      };
    });
  }

  // 删除值
  async removeItem(storeName, key) {
    if (!this.db) await this.initDB();

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      
      const request = store.delete(key);

      request.onsuccess = () => {
        resolve();
      };

      request.onerror = () => {
        reject(request.error);
      };
    });
  }

  // 获取所有值
  async getAllItems(storeName) {
    if (!this.db) await this.initDB();

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      
      const request = store.getAll();

      request.onsuccess = () => {
        resolve(request.result);
      };

      request.onerror = () => {
        reject(request.error);
      };
    });
  }

  // 缓存操作(带过期时间)
  async setCache(key, value, expiryMinutes = 60) {
    const expiry = Date.now() + (expiryMinutes * 60 * 1000);
    return this.setItem('cache', key, { value, expiry });
  }

  async getCache(key) {
    const cached = await this.getItem('cache', key);
    
    if (!cached) return null;
    
    if (Date.now() > cached.expiry) {
      await this.removeItem('cache', key);
      return null;
    }
    
    return cached.value;
  }

  // 清理过期缓存
  async clearExpiredCache() {
    if (!this.db) await this.initDB();

    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['cache'], 'readwrite');
      const store = transaction.objectStore('cache');
      const index = store.index('expiry');
      
      const now = Date.now();
      const range = IDBKeyRange.upperBound(now);
      const request = index.openCursor(range);

      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor) {
          cursor.delete();
          cursor.continue();
        } else {
          resolve();
        }
      };

      request.onerror = () => {
        reject(request.error);
      };
    });
  }
}

// 创建单例实例
const storageManager = new StorageManager();

// 初始化数据库
storageManager.initDB().catch(console.error);

export default storageManager;

8. 应用打包与发布

8.1 Electron Builder 配置

electron-builder.json

json 复制代码
{
  "appId": "com.example.electron-vue-app",
  "productName": "Electron Vue Desktop App",
  "directories": {
    "output": "dist_electron"
  },
  "files": [
    "dist/**/*",
    "dist_electron/**/*",
    "node_modules/**/*",
    "package.json"
  ],
  "mac": {
    "icon": "build/icon.icns",
    "category": "public.app-category.productivity",
    "target": [
      {
        "target": "dmg",
        "arch": ["x64", "arm64"]
      },
      {
        "target": "zip",
        "arch": ["x64", "arm64"]
      }
    ]
  },
  "win": {
    "icon": "build/icon.ico",
    "target": [
      {
        "target": "nsis",
        "arch": ["x64", "ia32"]
      },
      {
        "target": "portable",
        "arch": ["x64", "ia32"]
      }
    ]
  },
  "linux": {
    "icon": "build/icon.png",
    "target": [
      {
        "target": "AppImage",
        "arch": ["x64"]
      },
      {
        "target": "deb",
        "arch": ["x64"]
      }
    ],
    "category": "Utility"
  },
  "dmg": {
    "title": "${productName} ${version}",
    "icon": "build/icon.icns",
    "iconSize": 100,
    "contents": [
      {
        "x": 380,
        "y": 240,
        "type": "link",
        "path": "/Applications"
      },
      {
        "x": 110,
        "y": 240,
        "type": "file"
      }
    ]
  },
  "nsis": {
    "oneClick": false,
    "allowToChangeInstallationDirectory": true,
    "createDesktopShortcut": true,
    "createStartMenuShortcut": true,
    "shortcutName": "${productName}"
  },
  "publish": {
    "provider": "github",
    "owner": "your-github-username",
    "repo": "your-repo-name"
  }
}

8.2 打包脚本

scripts/build.js

javascript 复制代码
const { build } = require('electron-builder');
const path = require('path');

const config = {
  config: {
    appId: 'com.example.electron-vue-app',
    productName: 'Electron Vue Desktop App',
    directories: {
      output: 'dist_electron'
    },
    files: [
      'dist/**/*',
      'dist_electron/**/*',
      'node_modules/**/*',
      'package.json'
    ],
    extraMetadata: {
      main: 'dist_electron/index.js'
    }
  }
};

async function buildApp() {
  try {
    console.log('开始构建应用...');
    
    // 构建 Vue 应用
    console.log('构建 Vue 应用...');
    await require('child_process').execSync('npm run build', { stdio: 'inherit' });
    
    // 构建 Electron 应用
    console.log('构建 Electron 应用...');
    await build({
      ...config,
      win: ['nsis', 'portable'],
      mac: ['dmg', 'zip'],
      linux: ['AppImage', 'deb']
    });
    
    console.log('构建完成!');
  } catch (error) {
    console.error('构建失败:', error);
    process.exit(1);
  }
}

// 如果直接运行此脚本
if (require.main === module) {
  buildApp();
}

module.exports = { buildApp };

8.3 自动更新

src/main/updater.js

javascript 复制代码
const { app, autoUpdater, dialog } = require('electron');
const { ipcMain } = require('electron');

class AppUpdater {
  constructor() {
    this.updateURL = 'https://your-update-server.com/updates';
    this.setupAutoUpdater();
  }

  setupAutoUpdater() {
    const server = 'https://your-update-server.com';
    const url = `${server}/update/${process.platform}/${app.getVersion()}`;

    autoUpdater.setFeedURL({ url });

    // 自动更新事件
    autoUpdater.on('checking-for-update', () => {
      console.log('正在检查更新...');
    });

    autoUpdater.on('update-available', (info) => {
      console.log('发现可用更新:', info);
      dialog.showMessageBox({
        type: 'info',
        title: '发现更新',
        message: '发现新版本,是否现在更新?',
        buttons: ['现在更新', '稍后提醒']
      }).then(result => {
        if (result.response === 0) {
          autoUpdater.downloadUpdate();
        }
      });
    });

    autoUpdater.on('update-not-available', (info) => {
      console.log('当前为最新版本:', info);
    });

    autoUpdater.on('error', (err) => {
      console.log('更新错误:', err);
      dialog.showErrorBox('更新错误', '检查更新时发生错误: ' + err.message);
    });

    autoUpdater.on('download-progress', (progressObj) => {
      let log_message = `下载速度: ${progressObj.bytesPerSecond}`;
      log_message += ` - 已下载 ${progressObj.percent}%`;
      log_message += ` (${progressObj.transferred}/${progressObj.total})`;
      console.log(log_message);
      
      // 通知渲染进程下载进度
      const windows = require('./index').getAllWindows();
      windows.forEach(window => {
        window.webContents.send('download-progress', progressObj);
      });
    });

    autoUpdater.on('update-downloaded', (info) => {
      console.log('更新下载完成:', info);
      dialog.showMessageBox({
        type: 'info',
        title: '更新已下载',
        message: '更新已下载完成,应用将重启以应用更新。',
        buttons: ['立即重启', '稍后重启']
      }).then(result => {
        if (result.response === 0) {
          autoUpdater.quitAndInstall();
        }
      });
    });

    // 检查更新
    autoUpdater.checkForUpdates();

    // 定时检查更新(每4小时)
    setInterval(() => {
      autoUpdater.checkForUpdates();
    }, 4 * 60 * 60 * 1000);
  }

  // 手动检查更新
  checkForUpdates() {
    autoUpdater.checkForUpdates();
  }

  // 下载更新
  downloadUpdate() {
    autoUpdater.downloadUpdate();
  }

  // 退出并安装更新
  quitAndInstall() {
    autoUpdater.quitAndInstall();
  }
}

module.exports = AppUpdater;

9. 性能优化最佳实践

9.1 渲染进程优化

src/renderer/utils/performance.js

javascript 复制代码
class PerformanceOptimizer {
  constructor() {
    this.isDevelopment = process.env.NODE_ENV === 'development';
    this.init();
  }

  init() {
    if (!this.isDevelopment) {
      this.disableDevTools();
      this.optimizeImages();
      this.enableHardwareAcceleration();
    }
  }

  // 禁用开发者工具(生产环境)
  disableDevTools() {
    document.addEventListener('keydown', (e) => {
      if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && e.key === 'I')) {
        e.preventDefault();
      }
    });
  }

  // 图片优化
  optimizeImages() {
    // 懒加载图片
    const images = document.querySelectorAll('img[data-src]');
    const imageObserver = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.removeAttribute('data-src');
          observer.unobserve(img);
        }
      });
    });

    images.forEach(img => imageObserver.observe(img));
  }

  // 启用硬件加速
  enableHardwareAcceleration() {
    const style = document.createElement('style');
    style.textContent = `
      * {
        -webkit-transform: translateZ(0);
        -webkit-backface-visibility: hidden;
        -webkit-perspective: 1000;
      }
    `;
    document.head.appendChild(style);
  }

  // 内存管理
  cleanup() {
    // 清理事件监听器
    window.removeEventListener('resize', this.handleResize);
    window.removeEventListener('scroll', this.handleScroll);
    
    // 清理定时器
    if (this.timers) {
      this.timers.forEach(timer => clearTimeout(timer));
    }
    
    // 清理缓存
    if (window.caches) {
      window.caches.keys().then(names => {
        names.forEach(name => {
          window.caches.delete(name);
        });
      });
    }
  }

  // 优化长列表渲染
  virtualizeList(container, items, itemHeight, renderItem) {
    const visibleCount = Math.ceil(container.clientHeight / itemHeight);
    const totalHeight = items.length * itemHeight;
    
    let startIndex = 0;
    let endIndex = visibleCount;

    const updateVisibleItems = () => {
      const scrollTop = container.scrollTop;
      startIndex = Math.floor(scrollTop / itemHeight);
      endIndex = Math.min(startIndex + visibleCount + 1, items.length);
      
      const offsetY = startIndex * itemHeight;
      
      // 渲染可见项目
      container.innerHTML = '';
      const fragment = document.createDocumentFragment();
      
      for (let i = startIndex; i < endIndex; i++) {
        const item = renderItem(items[i], i);
        item.style.position = 'absolute';
        item.style.top = `${i * itemHeight - offsetY}px`;
        item.style.width = '100%';
        fragment.appendChild(item);
      }
      
      container.appendChild(fragment);
    };

    // 设置容器样式
    container.style.position = 'relative';
    container.style.height = `${totalHeight}px`;
    
    // 初始渲染
    updateVisibleItems();
    
    // 监听滚动事件
    container.addEventListener('scroll', updateVisibleItems);
    
    return {
      update: updateVisibleItems,
      destroy: () => {
        container.removeEventListener('scroll', updateVisibleItems);
      }
    };
  }

  // 防抖函数
  debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }

  // 节流函数
  throttle(func, limit) {
    let inThrottle;
    return function() {
      const args = arguments;
      const context = this;
      if (!inThrottle) {
        func.apply(context, args);
        inThrottle = true;
        setTimeout(() => inThrottle = false, limit);
      }
    };
  }

  // 监控性能指标
  monitorPerformance() {
    // 监控内存使用
    if (performance.memory) {
      setInterval(() => {
        const memory = performance.memory;
        console.log('Memory Usage:', {
          used: Math.round(memory.usedJSHeapSize / 1048576), // MB
          total: Math.round(memory.totalJSHeapSize / 1048576), // MB
          limit: Math.round(memory.jsHeapSizeLimit / 1048576) // MB
        });
      }, 30000); // 每30秒检查一次
    }

    // 监控帧率
    let lastTime = performance.now();
    let frameCount = 0;
    
    const measureFPS = () => {
      const currentTime = performance.now();
      frameCount++;
      
      if (currentTime >= lastTime + 1000) {
        const fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
        console.log('FPS:', fps);
        
        frameCount = 0;
        lastTime = currentTime;
      }
      
      requestAnimationFrame(measureFPS);
    };
    
    requestAnimationFrame(measureFPS);
  }
}

export default new PerformanceOptimizer();

9.2 主进程优化

src/main/performance.js

javascript 复制代码
const { app } = require('electron');

class MainProcessOptimizer {
  constructor() {
    this.init();
  }

  init() {
    this.optimizeAppStartup();
    this.manageMemory();
    this.optimizeWindowCreation();
  }

  // 优化应用启动
  optimizeAppStartup() {
    // 禁用硬件加速(在某些系统上可能有问题)
    if (process.platform === 'linux') {
      app.disableHardwareAcceleration();
    }

    // 优化 GPU 设置
    app.commandLine.appendSwitch('disable-gpu-compositing');
    app.commandLine.appendSwitch('disable-gpu-vsync');
    
    // 优化内存设置
    app.commandLine.appendSwitch('js-flags', '--max-old-space-size=4096');
  }

  // 内存管理
  manageMemory() {
    // 定期垃圾回收
    setInterval(() => {
      if (global.gc) {
        global.gc();
        console.log('手动垃圾回收完成');
      }
    }, 60000); // 每分钟一次

    // 监控内存使用
    setInterval(() => {
      const memoryUsage = process.memoryUsage();
      console.log('主进程内存使用:', {
        rss: Math.round(memoryUsage.rss / 1048576) + ' MB',
        heapTotal: Math.round(memoryUsage.heapTotal / 1048576) + ' MB',
        heapUsed: Math.round(memoryUsage.heapUsed / 1048576) + ' MB',
        external: Math.round(memoryUsage.external / 1048576) + ' MB'
      });
    }, 30000); // 每30秒
  }

  // 优化窗口创建
  optimizeWindowCreation() {
    // 预加载常用模块
    const commonModules = [
      'fs',
      'path',
      'electron',
      './ipc',
      './menu',
      './fileManager'
    ];

    commonModules.forEach(module => {
      try {
        require(module);
      } catch (error) {
        console.warn(`预加载模块失败: ${module}`, error);
      }
    });
  }

  // 优化数据库连接
  optimizeDatabase() {
    // 连接池管理
    const connectionPool = {
      maxConnections: 10,
      idleTimeout: 30000,
      connections: []
    };

    // 定期清理空闲连接
    setInterval(() => {
      const now = Date.now();
      connectionPool.connections = connectionPool.connections.filter(conn => {
        if (now - conn.lastUsed > connectionPool.idleTimeout) {
          conn.close();
          return false;
        }
        return true;
      });
    }, 60000);
  }

  // 优化文件系统操作
  optimizeFileSystem() {
    // 文件缓存
    const fileCache = new Map();
    const cacheTimeout = 300000; // 5分钟

    // 定期清理文件缓存
    setInterval(() => {
      const now = Date.now();
      for (const [key, value] of fileCache.entries()) {
        if (now - value.timestamp > cacheTimeout) {
          fileCache.delete(key);
        }
      }
    }, 300000); // 每5分钟
  }
}

module.exports = new MainProcessOptimizer();

9.3 构建优化

vue.config.js 优化配置

javascript 复制代码
const { defineConfig } = require('@vue/cli-service');
const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = defineConfig({
  transpileDependencies: true,
  
  // 生产环境优化
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // 代码压缩
      config.optimization.minimizer = [
        new TerserPlugin({
          terserOptions: {
            compress: {
              drop_console: true,
              drop_debugger: true
            },
            output: {
              comments: false
            }
          }
        })
      ];

      // Gzip 压缩
      config.plugins.push(
        new CompressionPlugin({
          algorithm: 'gzip',
          test: /\.(js|css|html|svg)$/,
          threshold: 8192,
          minRatio: 0.8
        })
      );
    }
  },

  // 开发服务器配置
  devServer: {
    port: 8080,
    host: 'localhost',
    https: false,
    hot: true,
    open: false
  },

  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      contextIsolation: false,
      
      // 主进程文件
      mainProcessFile: 'src/main/index.js',
      
      // 预加载脚本
      preload: 'src/preload/preload.js',
      
      // 构建选项
      builderOptions: {
        appId: 'com.example.electron-vue-app',
        productName: 'Electron Vue Desktop App',
        
        // 文件配置
        files: [
          'dist/**/*',
          'dist_electron/**/*',
          'node_modules/**/*',
          'package.json',
          '!**/node_modules/**/*.{md,txt}',
          '!**/node_modules/**/LICENSE',
          '!**/node_modules/**/README*',
          '!**/node_modules/**/test/**/*',
          '!**/node_modules/**/tests/**/*'
        ],
        
        // macOS 配置
        mac: {
          icon: 'build/icon.icns',
          category: 'public.app-category.productivity',
          hardenedRuntime: true,
          gatekeeperAssess: false,
          entitlements: 'build/entitlements.mac.plist',
          entitlementsInherit: 'build/entitlements.mac.plist'
        },
        
        // Windows 配置
        win: {
          icon: 'build/icon.ico',
          target: [
            {
              target: 'nsis',
              arch: ['x64', 'ia32']
            }
          ]
        },
        
        // Linux 配置
        linux: {
          icon: 'build/icon.png',
          target: [
            {
              target: 'AppImage',
              arch: ['x64']
            }
          ],
          category: 'Utility'
        },
        
        // NSIS 安装程序配置
        nsis: {
          oneClick: false,
          allowToChangeInstallationDirectory: true,
          createDesktopShortcut: true,
          createStartMenuShortcut: true
        }
      }
    }
  }
});

10. 常见问题与解决方案

10.1 构建问题

问题1:原生模块编译失败

解决方案:

bash 复制代码
# 安装 windows-build-tools(Windows)
npm install --global windows-build-tools

# 安装 node-gyp
npm install -g node-gyp

# 重新编译原生模块
npm rebuild
问题2:打包后应用白屏

解决方案:

javascript 复制代码
// vue.config.js 中确保正确配置
module.exports = {
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      contextIsolation: false,
      mainProcessFile: 'src/main/index.js'
    }
  }
};

10.2 运行时问题

问题3:require is not defined

解决方案:

javascript 复制代码
// 在 vue.config.js 中启用 nodeIntegration
pluginOptions: {
  electronBuilder: {
    nodeIntegration: true,
    contextIsolation: false
  }
}
问题4:CORS 跨域问题

解决方案:

javascript 复制代码
// main.js 中配置 webSecurity
const mainWindow = new BrowserWindow({
  webPreferences: {
    webSecurity: false,
    allowRunningInsecureContent: true
  }
});

10.3 性能问题

问题5:应用启动慢

解决方案:

javascript 复制代码
// 延迟加载非关键模块
setTimeout(() => {
  require('./heavy-module');
}, 1000);

// 使用动态导入
const loadModule = async () => {
  const module = await import('./heavy-module');
  return module.default;
};

10.4 调试技巧

主进程调试:

bash 复制代码
# 使用 VS Code 调试
# .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Main Process",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
      "windows": {
        "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
      },
      "args": ["."],
      "env": {
        "NODE_ENV": "development"
      }
    }
  ]
}

11. 实战案例分析

11.1 代码编辑器应用

功能特性:

  • 多标签页编辑
  • 语法高亮
  • 文件树浏览
  • 全局搜索
  • 主题切换

技术实现:

javascript 复制代码
// src/renderer/components/CodeEditor.vue
<template>
  <div class="code-editor">
    <div class="editor-tabs">
      <div 
        v-for="tab in openTabs" 
        :key="tab.id"
        :class="['tab', { active: tab.id === activeTabId }]"
        @click="switchTab(tab.id)"
      >
        <span class="tab-title">{{ tab.title }}</span>
        <button class="tab-close" @click.stop="closeTab(tab.id)">×</button>
      </div>
    </div>
    
    <div class="editor-container">
      <textarea 
        ref="editor"
        v-model="currentContent"
        @input="onContentChange"
        class="editor-textarea"
      ></textarea>
    </div>
  </div>
</template>

<script>
import { ref, onMounted, nextTick } from 'vue';
import Prism from 'prismjs';
import 'prismjs/themes/prism-tomorrow.css';

export default {
  name: 'CodeEditor',
  setup() {
    const editor = ref(null);
    const openTabs = ref([]);
    const activeTabId = ref(null);
    const currentContent = ref('');

    const switchTab = (tabId) => {
      activeTabId.value = tabId;
      const tab = openTabs.value.find(t => t.id === tabId);
      if (tab) {
        currentContent.value = tab.content;
        nextTick(() => {
          highlightCode();
        });
      }
    };

    const highlightCode = () => {
      if (editor.value) {
        const code = editor.value.value;
        const highlighted = Prism.highlight(code, Prism.languages.javascript, 'javascript');
        // 应用高亮
      }
    };

    const onContentChange = () => {
      const tab = openTabs.value.find(t => t.id === activeTabId.value);
      if (tab) {
        tab.content = currentContent.value;
        tab.isModified = true;
      }
      highlightCode();
    };

    const openFile = async (filePath) => {
      try {
        const result = await window.electronAPI.readFile(filePath);
        if (result.success) {
          const tab = {
            id: Date.now(),
            title: filePath.split('/').pop(),
            content: result.data,
            filePath,
            isModified: false
          };
          
          openTabs.value.push(tab);
          switchTab(tab.id);
        }
      } catch (error) {
        console.error('打开文件失败:', error);
      }
    };

    return {
      editor,
      openTabs,
      activeTabId,
      currentContent,
      switchTab,
      onContentChange,
      openFile
    };
  }
};
</script>

11.2 数据库管理工具

功能特性:

  • 多数据库连接
  • SQL 编辑器
  • 数据表管理
  • 数据导入导出
  • 查询历史记录

技术实现:

javascript 复制代码
// src/renderer/components/DatabaseManager.vue
<template>
  <div class="database-manager">
    <div class="sidebar">
      <div class="connections">
        <h3>数据库连接</h3>
        <div v-for="conn in connections" :key="conn.id" class="connection-item">
          <span @click="connectDatabase(conn)">{{ conn.name }}</span>
          <button @click="deleteConnection(conn.id)">删除</button>
        </div>
      </div>
      
      <div class="tables" v-if="currentConnection">
        <h3>数据表</h3>
        <div v-for="table in tables" :key="table.name" class="table-item">
          <span @click="loadTableData(table.name)">{{ table.name }}</span>
        </div>
      </div>
    </div>
    
    <div class="main-content">
      <div class="sql-editor">
        <textarea 
          v-model="sqlQuery" 
          placeholder="输入 SQL 查询..."
          class="sql-input"
        ></textarea>
        <button @click="executeQuery" class="execute-btn">执行</button>
      </div>
      
      <div class="results" v-if="queryResults">
        <table class="results-table">
          <thead>
            <tr>
              <th v-for="column in queryResults.columns" :key="column">
                {{ column }}
              </th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="row in queryResults.rows" :key="row.id">
              <td v-for="column in queryResults.columns" :key="column">
                {{ row[column] }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  name: 'DatabaseManager',
  setup() {
    const connections = ref([]);
    const currentConnection = ref(null);
    const tables = ref([]);
    const sqlQuery = ref('');
    const queryResults = ref(null);

    const connectDatabase = async (connection) => {
      try {
        const result = await window.electronAPI.connectDatabase(connection);
        if (result.success) {
          currentConnection.value = connection;
          await loadTables();
        }
      } catch (error) {
        console.error('连接数据库失败:', error);
      }
    };

    const loadTables = async () => {
      if (!currentConnection.value) return;
      
      try {
        const result = await window.electronAPI.getTables(currentConnection.value.id);
        if (result.success) {
          tables.value = result.data;
        }
      } catch (error) {
        console.error('获取数据表失败:', error);
      }
    };

    const loadTableData = async (tableName) => {
      sqlQuery.value = `SELECT * FROM ${tableName} LIMIT 100`;
      await executeQuery();
    };

    const executeQuery = async () => {
      if (!currentConnection.value || !sqlQuery.value) return;
      
      try {
        const result = await window.electronAPI.executeQuery({
          connectionId: currentConnection.value.id,
          query: sqlQuery.value
        });
        
        if (result.success) {
          queryResults.value = result.data;
        }
      } catch (error) {
        console.error('执行查询失败:', error);
      }
    };

    const deleteConnection = async (connectionId) => {
      try {
        await window.electronAPI.deleteConnection(connectionId);
        connections.value = connections.value.filter(c => c.id !== connectionId);
      } catch (error) {
        console.error('删除连接失败:', error);
      }
    };

    return {
      connections,
      currentConnection,
      tables,
      sqlQuery,
      queryResults,
      connectDatabase,
      loadTableData,
      executeQuery,
      deleteConnection
    };
  }
};
</script>

12. 总结与展望

12.1 Electron + Vue 的优势

  1. 跨平台能力:一套代码支持多个操作系统
  2. 开发效率:使用熟悉的 Web 技术栈
  3. 生态丰富:Vue 生态系统完善
  4. 原生功能:访问操作系统 API
  5. UI 灵活性:现代化的界面设计

12.2 面临的挑战

  1. 应用体积:打包后的应用相对较大
  2. 内存占用:相比原生应用内存占用较高
  3. 启动速度:首次启动可能较慢
  4. 安全性:需要注意安全防护措施

12.3 最佳实践建议

  1. 模块化开发:合理划分主进程和渲染进程职责
  2. 性能优化:注意内存管理和性能监控
  3. 安全防护:实施必要的安全措施
  4. 用户体验:提供良好的用户交互体验
  5. 持续集成:建立完善的构建和发布流程

12.4 未来发展趋势

  1. WebAssembly 集成:提升性能和功能
  2. PWA 融合:结合 PWA 技术优势
  3. 云原生支持:更好的云端部署方案
  4. AI 能力集成:融入人工智能功能
  5. 低代码平台:可视化开发工具

12.5 学习资源推荐

  • 官方文档:Electron 和 Vue 官方文档
  • 社区资源:GitHub 开源项目
  • 在线教程:视频教程和博客文章
  • 开发工具:VS Code 插件和调试工具
  • UI 框架:Element Plus、Ant Design Vue 等

总结:Electron + Vue 的组合为桌面应用开发提供了强大而灵活的解决方案。通过合理的架构设计、性能优化和最佳实践,开发者可以构建出高质量的跨平台桌面应用。随着技术的不断发展,这一技术栈将在桌面应用开发领域发挥越来越重要的作用。

作者简介:资深前端开发工程师,专注于 Electron 和 Vue 技术栈,拥有丰富的桌面应用开发经验。

相关推荐
漂流瓶jz8 小时前
Webpack中各种devtool配置的含义与SourceMap生成逻辑
前端·javascript·webpack
这是个栗子8 小时前
【问题解决】用pnpm创建的 Vue3项目找不到 .eslintrc.js文件 及 后续的eslint配置的解决办法
javascript·vue.js·pnpm·eslint
前端架构师-老李8 小时前
React 中 useCallback 的基本使用和原理解析
前端·react.js·前端框架
木易 士心9 小时前
CSS 中 `data-status` 的使用详解
前端·css
明月与玄武9 小时前
前端缓存战争:回车与刷新按钮的终极对决!
前端·缓存·回车 vs 点击刷新
花姐夫Jun9 小时前
基于Vue+Python+Orange Pi Zero3的完整视频监控方案
vue.js·python·音视频
牧马少女9 小时前
css 画一个圆角渐变色边框
前端·css
zy happy9 小时前
RuoyiApp 在vuex,state存储nickname vue2
前端·javascript·小程序·uni-app·vue·ruoyi
小雨青年9 小时前
Cursor 项目实战:AI播客策划助手(二)—— 多轮交互打磨播客文案的技术实现与实践
前端·人工智能·状态模式·交互