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. 常见问题与解决方案)
-
- [10.1 构建问题](#10.1 构建问题)
- [10.2 运行时问题](#10.2 运行时问题)
-
- [问题3:require is not defined](#问题3:require is not defined)
- [问题4:CORS 跨域问题](#问题4:CORS 跨域问题)
- [10.3 性能问题](#10.3 性能问题)
- [10.4 调试技巧](#10.4 调试技巧)
- [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 应用由两个主要进程组成:
-
主进程(Main Process)
- 控制应用生命周期
- 管理原生 GUI(窗口、菜单等)
- 运行在 Node.js 环境中
-
渲染进程(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 集成方式
主要有两种集成方式:
- 在现有 Vue 项目中添加 Electron
- 使用脚手架工具创建新项目
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 的优势
- 跨平台能力:一套代码支持多个操作系统
- 开发效率:使用熟悉的 Web 技术栈
- 生态丰富:Vue 生态系统完善
- 原生功能:访问操作系统 API
- UI 灵活性:现代化的界面设计
12.2 面临的挑战
- 应用体积:打包后的应用相对较大
- 内存占用:相比原生应用内存占用较高
- 启动速度:首次启动可能较慢
- 安全性:需要注意安全防护措施
12.3 最佳实践建议
- 模块化开发:合理划分主进程和渲染进程职责
- 性能优化:注意内存管理和性能监控
- 安全防护:实施必要的安全措施
- 用户体验:提供良好的用户交互体验
- 持续集成:建立完善的构建和发布流程
12.4 未来发展趋势
- WebAssembly 集成:提升性能和功能
- PWA 融合:结合 PWA 技术优势
- 云原生支持:更好的云端部署方案
- AI 能力集成:融入人工智能功能
- 低代码平台:可视化开发工具
12.5 学习资源推荐
- 官方文档:Electron 和 Vue 官方文档
- 社区资源:GitHub 开源项目
- 在线教程:视频教程和博客文章
- 开发工具:VS Code 插件和调试工具
- UI 框架:Element Plus、Ant Design Vue 等
总结:Electron + Vue 的组合为桌面应用开发提供了强大而灵活的解决方案。通过合理的架构设计、性能优化和最佳实践,开发者可以构建出高质量的跨平台桌面应用。随着技术的不断发展,这一技术栈将在桌面应用开发领域发挥越来越重要的作用。
作者简介:资深前端开发工程师,专注于 Electron 和 Vue 技术栈,拥有丰富的桌面应用开发经验。