Electron 实战|Vue 桌面端开发从入门到上线
引言
大家好!作为一名前端开发者,你是否曾经想过将你的 Vue 应用打包成桌面应用?今天我要分享的是使用 Electron 将 Vue 应用转换为桌面应用的完整实战经验。从项目搭建到最终上线,我会详细介绍每个步骤,包括一些实用的自动化脚本和最佳实践。
需求背景
在开发过程中,我们经常遇到这样的场景:
- 需要将 Web 应用打包成桌面应用
- 希望应用能够离线运行
- 需要访问本地文件系统
- 要求应用具有原生桌面体验
Electron 正是解决这些需求的完美方案。它基于 Chromium 和 Node.js,让我们可以用 Web 技术开发跨平台的桌面应用。
工作原理
Electron 的核心架构包含两个进程:
- 主进程(Main Process):负责创建和管理应用窗口,处理系统级 API
- 渲染进程(Renderer Process):运行我们的 Vue 应用,类似于浏览器中的网页
两个进程通过 IPC(进程间通信)进行数据交换,主进程可以访问 Node.js API,渲染进程则专注于 UI 展示。
代码实现
1. 项目初始化
首先创建项目目录结构:
mkdir electron-vue-app
cd electron-vue-app
npm init -y
安装必要的依赖:
npm install electron electron-builder --save-dev
npm install vue@next @vitejs/plugin-vue vite --save-dev
2. 主进程配置
创建 main.js
文件:
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')let mainWindowfunction createWindow() {mainWindow = new BrowserWindow({width: 1200,height: 800,webPreferences: {nodeIntegration: true,contextIsolation: false,enableRemoteModule: true}})// 开发环境加载本地服务器,生产环境加载打包文件if (process.env.NODE_ENV === 'development') {mainWindow.loadURL('http://localhost:3000')mainWindow.webContents.openDevTools()} else {mainWindow.loadFile('dist/index.html')}
}app.whenReady().then(createWindow)app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit()}
})app.on('activate', () => {if (BrowserWindow.getAllWindows().length === 0) {createWindow()}
})
3. Vue 应用配置
创建 vite.config.js
:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'export default defineConfig({plugins: [vue()],base: './',build: {outDir: 'dist'}
})
4. 自动化脚本实现
这里我们实现一个实用的文件备份脚本,展示 Electron 与 Node.js 的深度集成:
# backup_manager.py
import os
import shutil
import schedule
import time
import logging
from datetime import datetime
from pathlib import Path# 配置日志
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s',handlers=[logging.FileHandler('backup.log'),logging.StreamHandler()]
)class BackupManager:def __init__(self, source_dir, backup_dir):self.source_dir = Path(source_dir)self.backup_dir = Path(backup_dir)self.backup_dir.mkdir(exist_ok=True)def backup_folder(self, folder_name):"""复制指定文件夹到备份目录"""try:source_path = self.source_dir / folder_nameif not source_path.exists():logging.warning(f"源文件夹 {folder_name} 不存在")return Falsetimestamp = datetime.now().strftime("%Y%m%d_%H%M%S")backup_path = self.backup_dir / f"{folder_name}_{timestamp}"shutil.copytree(source_path, backup_path)logging.info(f"成功备份 {folder_name} 到 {backup_path}")return Trueexcept Exception as e:logging.error(f"备份 {folder_name} 失败: {str(e)}")return Falsedef schedule_backup(self, folder_name, interval_hours=24):"""定时备份任务"""schedule.every(interval_hours).hours.do(lambda: self.backup_folder(folder_name))logging.info(f"已设置 {folder_name} 每 {interval_hours} 小时自动备份")def run_scheduler(self):"""运行定时任务"""logging.info("开始运行定时备份任务...")while True:schedule.run_pending()time.sleep(60) # 每分钟检查一次# 使用示例
if __name__ == "__main__":backup_manager = BackupManager(source_dir="./src",backup_dir="./backups")# 立即备份一次backup_manager.backup_folder("components")# 设置定时备份backup_manager.schedule_backup("components", 12) # 每12小时备份一次backup_manager.schedule_backup("views", 24) # 每24小时备份一次# 运行定时任务backup_manager.run_scheduler()
5. Vue 组件实现
创建 App.vue
:
<template><div class="app"><header class="header"><h1>Electron Vue 桌面应用</h1><div class="actions"><button @click="openFileDialog" class="btn btn-primary">选择文件夹</button><button @click="startBackup" class="btn btn-success">开始备份</button><button @click="viewLogs" class="btn btn-info">查看日志</button></div></header><main class="main"><div class="file-list"><h3>文件列表</h3><ul><li v-for="file in fileList" :key="file.name" class="file-item"><span class="file-name">{{ file.name }}</span><span class="file-size">{{ formatFileSize(file.size) }}</span><span class="file-date">{{ formatDate(file.date) }}</span></li></ul></div><div class="backup-status"><h3>备份状态</h3><div class="status-item" v-for="status in backupStatus" :key="status.id"><span :class="['status', status.type]">{{ status.message }}</span><span class="timestamp">{{ status.timestamp }}</span></div></div></main></div>
</template><script>
import { ref, onMounted } from 'vue'export default {name: 'App',setup() {const fileList = ref([])const backupStatus = ref([])const openFileDialog = () => {// 调用 Electron 主进程打开文件对话框window.electronAPI.openFileDialog()}const startBackup = () => {backupStatus.value.push({id: Date.now(),type: 'info',message: '开始备份...',timestamp: new Date().toLocaleString()})// 调用备份脚本window.electronAPI.startBackup()}const viewLogs = () => {window.electronAPI.viewLogs()}const formatFileSize = (bytes) => {if (bytes === 0) return '0 Bytes'const k = 1024const sizes = ['Bytes', 'KB', 'MB', 'GB']const i = Math.floor(Math.log(bytes) / Math.log(k))return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]}const formatDate = (date) => {return new Date(date).toLocaleString()}onMounted(() => {// 监听来自主进程的消息window.electronAPI.onBackupComplete((event, result) => {backupStatus.value.push({id: Date.now(),type: result.success ? 'success' : 'error',message: result.message,timestamp: new Date().toLocaleString()})})})return {fileList,backupStatus,openFileDialog,startBackup,viewLogs,formatFileSize,formatDate}}
}
</script><style>
.app {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;margin: 0;padding: 20px;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;
}.header {background: rgba(255, 255, 255, 0.1);backdrop-filter: blur(10px);border-radius: 15px;padding: 20px;margin-bottom: 20px;display: flex;justify-content: space-between;align-items: center;
}.header h1 {color: white;margin: 0;font-size: 2rem;
}.actions {display: flex;gap: 10px;
}.btn {padding: 10px 20px;border: none;border-radius: 8px;cursor: pointer;font-weight: 500;transition: all 0.3s ease;
}.btn-primary {background: #007bff;color: white;
}.btn-success {background: #28a745;color: white;
}.btn-info {background: #17a2b8;color: white;
}.btn:hover {transform: translateY(-2px);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}.main {display: grid;grid-template-columns: 1fr 1fr;gap: 20px;
}.file-list, .backup-status {background: rgba(255, 255, 255, 0.9);border-radius: 15px;padding: 20px;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}.file-item {display: flex;justify-content: space-between;padding: 10px;border-bottom: 1px solid #eee;
}.status-item {display: flex;justify-content: space-between;padding: 10px;border-bottom: 1px solid #eee;
}.status {padding: 4px 8px;border-radius: 4px;font-size: 0.9rem;
}.status.success {background: #d4edda;color: #155724;
}.status.error {background: #f8d7da;color: #721c24;
}.status.info {background: #d1ecf1;color: #0c5460;
}
</style>
6. 构建配置
创建 package.json
脚本:
{"name": "electron-vue-app","version": "1.0.0","main": "main.js","scripts": {"dev": "concurrently \"npm run dev:vite\" \"wait-on http://localhost:3000 && electron .\"","dev:vite": "vite","build": "vite build","build:electron": "npm run build && electron-builder","dist": "npm run build && electron-builder --publish=never"},"build": {"appId": "com.example.electron-vue-app","productName": "Electron Vue App","directories": {"output": "dist-electron"},"files": ["dist/**/*","main.js","node_modules/**/*"],"mac": {"category": "public.app-category.developer-tools"},"win": {"target": "nsis"},"linux": {"target": "AppImage"}}
}
运行效果截图
应用运行后的效果包括:
- 主界面:现代化的渐变背景,半透明毛玻璃效果的头部区域
- 文件管理:左侧显示文件列表,包含文件名、大小、修改日期
- 备份状态:右侧实时显示备份进度和结果
- 操作按钮:三个主要功能按钮,具有悬停动画效果
- 日志查看:点击查看日志按钮会打开日志文件
界面采用响应式设计,支持不同屏幕尺寸,整体风格简洁现代。
总结
通过这个实战项目,我们成功地将 Vue 应用打包成了桌面应用,并集成了实用的文件备份功能。关键技术点包括:
- Electron 主进程与渲染进程通信:实现了 Web 技术与系统 API 的无缝集成
- 自动化脚本:使用 Python 的 shutil、schedule、logging 模块实现了文件备份和定时任务
- 现代化 UI:采用 CSS Grid 布局和毛玻璃效果,提供良好的用户体验
这个脚本具有很强的扩展性,你可以进一步:
- 云端备份:集成 AWS S3、阿里云 OSS 等云存储服务
- 压缩打包:使用 zipfile 或 tar 模块压缩备份文件
- 增量备份:只备份修改过的文件,提高效率
- 加密存储:使用 cryptography 模块加密敏感文件
希望这篇文章对你在 Electron + Vue 桌面应用开发方面有所帮助!
参考资料
- Electron 官方文档
- Vue 3 官方文档
- Vite 构建工具
- Python shutil 模块
- Python schedule 库
- Python logging 模块
作者: 王新焱
博客: https://blog.csdn.net/qq_34402069
时间: 2025年10月22日