引言
大家好!作为一名前端开发者,你是否曾经想过将你的 Vue 应用打包成桌面应用?今天我要分享的是使用 Electron 将 Vue 应用转换为桌面应用的完整实战经验。从项目搭建到最终上线,我会详细介绍每个步骤,包括一些实用的自动化脚本和最佳实践。
需求背景
在开发过程中,我们经常遇到这样的场景:
- 需要将 Web 应用打包成桌面应用
- 希望应用能够离线运行
- 需要访问本地文件系统
- 要求应用具有原生桌面体验
Electron 正是解决这些需求的完美方案。它基于 Chromium 和 Node.js,让我们可以用 Web 技术开发跨平台的桌面应用。
工作原理
Electron 的核心架构包含两个进程:
- 主进程(Main Process):负责创建和管理应用窗口,处理系统级 API
- 渲染进程(Renderer Process):运行我们的 Vue 应用,类似于浏览器中的网页
两个进程通过 IPC(进程间通信)进行数据交换,主进程可以访问 Node.js API,渲染进程则专注于 UI 展示。
代码实现
1. 项目初始化
首先创建项目目录结构:
bash
mkdir electron-vue-app
cd electron-vue-app
npm init -y
安装必要的依赖:
bash
npm install electron electron-builder --save-dev
npm install vue@next @vitejs/plugin-vue vite --save-dev
2. 主进程配置
创建 main.js
文件:
javascript
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
let mainWindow
function 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
:
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
base: './',
build: {
outDir: 'dist'
}
})
4. 自动化脚本实现
这里我们实现一个实用的文件备份脚本,展示 Electron 与 Node.js 的深度集成:
python
# 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_name
if not source_path.exists():
logging.warning(f"源文件夹 {folder_name} 不存在")
return False
timestamp = 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 True
except Exception as e:
logging.error(f"备份 {folder_name} 失败: {str(e)}")
return False
def 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
:
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 = 1024
const 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
脚本:
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 桌面应用开发方面有所帮助!
参考资料
作者 : 王新焱
博客 : https://blog.csdn.net/qq_34402069
时间: 2025年10月22日