从零到一:Electron + Vue 3 跨平台桌面应用开发完整指南

摘要:本文详细介绍了如何使用 Electron 和 Vue 3 构建跨平台桌面应用程序,包含完整的技术栈选型、项目搭建、功能实现、打包部署等环节,并分享了开发过程中遇到的常见问题及解决方案。适合有一定前端基础的开发者学习参考。
一、项目背景与技术选型
1.1 为什么选择 Electron
在企业级应用开发中,我们经常需要开发能够在 Windows、macOS 和 Linux 上运行的桌面应用。传统的桌面应用开发需要针对不同平台使用不同的技术栈(如 C#/.NET、Swift、Qt 等),开发成本高且维护困难。
Electron 的优势:
- ✅ 一套代码,多平台运行(Windows、macOS、Linux)
- ✅ 使用熟悉的 Web 技术栈(HTML、CSS、JavaScript)
- ✅ 丰富的生态系统和社区支持
- ✅ 可以调用 Node.js API,访问系统底层功能
- ✅ 成熟的案例:VS Code、Discord、Slack、Figma 等
1.2 技术栈选择
经过调研和对比,最终确定了以下技术栈:
| 技术 | 版本 | 作用 |
|---|---|---|
| Electron | 28.x | 跨平台桌面应用框架 |
| Vue 3 | 3.4.x | 渐进式前端框架 |
| Vite | 5.x | 下一代前端构建工具 |
| Element Plus | 2.5.x | UI 组件库 |
| Pinia | 2.1.x | 状态管理 |
| Vue Router | 4.2.x | 路由管理 |
| Axios | 1.6.x | HTTP 请求库 |
技术栈优势分析:
- Vue 3:组合式 API、更好的 TypeScript 支持、性能提升
- Vite:极速的冷启动、即时的热更新、真正的按需编译
- Element Plus:企业级 UI 组件库,开箱即用
- Pinia:轻量级、完整的 TypeScript 支持、模块化设计

二、项目初始化与环境搭建
2.1 环境准备
# 检查 Node.js 版本(建议使用 Node.js 20)
node -v # v20.x.x
# 使用 nvm 切换 Node.js 版本(如果需要)
nvm install 20
nvm use 20
2.2 项目结构设计
一个清晰的项目结构是项目成功的基础:
ElectronDemo/
├── electron/ # Electron 主进程代码
│ ├── main.js # 主进程入口(窗口管理、生命周期)
│ └── preload.js # 预加载脚本(安全通信桥梁)
├── src/ # Vue 前端代码
│ ├── components/ # 公共组件
│ ├── pages/ # 页面组件
│ ├── stores/ # Pinia 状态管理
│ ├── router/ # 路由配置
│ ├── utils/ # 工具函数
│ ├── App.vue # 根组件
│ └── main.js # Vue 入口
├── build/ # 打包资源(图标等)
├── dist/ # 前端构建输出
├── release/ # 应用打包输出
├── electron-builder.yml # 打包配置
├── vite.config.js # Vite 配置
└── package.json # 项目配置
2.3 安装依赖
# 克隆或创建项目后,安装依赖
npm install
# 主要依赖说明
# 生产依赖
npm install vue@3.4.0 vue-router@4.2.5 pinia@2.1.7
npm install element-plus@2.5.0 axios@1.6.0
npm install @vueuse/core@10.9.0 three@0.182.0
# 开发依赖
npm install -D electron@28.2.0 electron-builder@24.9.1
npm install -D vite@5.0.0 @vitejs/plugin-vue@5.0.0
npm install -D concurrently@8.2.2

三、核心功能实现
3.1 Electron 主进程配置
Electron 应用的核心是主进程配置,需要处理窗口创建、环境判断、生命周期管理等。
electron/main.js 关键代码:
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow () {
const win = new BrowserWindow({
width: 1200,
height: 700,
resizable: true,
title: '应用程序',
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
})
// 环境判断:开发环境和生产环境使用不同的加载方式
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
// 开发环境:加载 Vite 开发服务器
win.loadURL('http://localhost:2255/')
win.webContents.openDevTools() // 打开开发者工具
} else {
// 生产环境:加载打包后的 index.html
win.loadFile(path.join(__dirname, '../dist/index.html'))
}
}
// 应用准备就绪时创建窗口
app.whenReady().then(createWindow)
// macOS 特性:点击 dock 图标重新创建窗口
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
// 所有窗口关闭时退出应用(macOS 除外)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
关键点解析:
- 环境判断:开发环境加载本地服务器,生产环境加载打包文件
- 安全配置:
nodeIntegration: false和contextIsolation: true确保安全性 - 平台适配:macOS 平台特殊处理(dock 图标点击事件)

3.2 Vue 应用配置
src/main.js 完整配置:
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
import router from './router'
// 创建 Vue 应用
const app = createApp(App)
// 创建 Pinia 实例并配置持久化
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// 注册插件
app.use(ElementPlus, { locale: zhCn })
app.use(pinia)
app.use(router)
// 移除页面滚动条,适配桌面应用
const style = document.createElement('style')
style.textContent = `
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
#app {
width: 100%;
height: 100%;
}
`
document.head.appendChild(style)
app.mount('#app')
3.3 全局水印组件实现
为了保护应用内容,实现了一个防篡改的全局水印组件:
src/components/Watermark.vue:
<template>
<div class="watermark-container" ref="watermarkRef"></div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const props = defineProps({
text: { type: String, default: '应用程序' },
fontSize: { type: Number, default: 14 },
color: { type: String, default: 'rgba(0, 0, 0, 0.06)' },
rotate: { type: Number, default: -20 },
gap: { type: Array, default: () => [120, 100] }
})
const watermarkRef = ref(null)
let observer = null
// 使用 Canvas 生成水印图案
const createWatermark = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const devicePixelRatio = window.devicePixelRatio || 1
const fontSize = props.fontSize * devicePixelRatio
canvas.width = (200 + props.gap[0]) * devicePixelRatio
canvas.height = (fontSize + props.gap[1]) * devicePixelRatio
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate((Math.PI / 180) * props.rotate)
ctx.font = `${fontSize}px Arial`
ctx.fillStyle = props.color
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(props.text, 0, 0)
return canvas.toDataURL()
}
// 设置水印
const setWatermark = () => {
if (!watermarkRef.value) return
const watermarkUrl = createWatermark()
watermarkRef.value.style.backgroundImage = `url(${watermarkUrl})`
}
// 防止删除水印的监听器
const setupObserver = () => {
if (!watermarkRef.value) return
observer = new MutationObserver(() => {
setWatermark()
})
observer.observe(watermarkRef.value, {
attributes: true,
childList: true,
subtree: true
})
}
onMounted(() => {
setWatermark()
setupObserver()
window.addEventListener('resize', setWatermark)
})
onBeforeUnmount(() => {
if (observer) observer.disconnect()
window.removeEventListener('resize', setWatermark)
})
</script>
<style scoped>
.watermark-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background-repeat: repeat;
z-index: 9999;
}
</style>
特性说明:
- ✅ Canvas 绘制,性能优秀
- ✅ MutationObserver 监听,防止删除
- ✅ 响应式适配,支持窗口大小变化
- ✅ 不影响用户交互(
pointer-events: none)
3.4 路由配置
使用 Hash 模式路由,适配 Electron 应用:
src/router/index.js:
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/pages/Home/index.vue'),
meta: { title: '首页' }
},
{
path: '/tower-crane',
name: 'TowerCrane',
component: () => import('@/pages/TowerCrane/index.vue'),
meta: { title: '塔吊监测' }
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
document.title = to.meta.title || '应用程序'
next()
})
export default router
为什么使用 Hash 模式?
- Electron 打包后使用
file://协议加载文件 - History 模式需要服务器支持,Hash 模式更适合本地文件系统
3.5 状态管理与持久化
使用 Pinia + 持久化插件:
src/stores/user.js:
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: '',
permissions: []
}),
getters: {
isLogin: (state) => !!state.token,
userName: (state) => state.userInfo?.name || '未登录'
},
actions: {
setUserInfo(info) {
this.userInfo = info
},
setToken(token) {
this.token = token
},
logout() {
this.userInfo = null
this.token = ''
this.permissions = []
}
},
// 持久化配置
persist: {
key: 'user-store',
storage: localStorage,
paths: ['userInfo', 'token'] // 只持久化指定字段
}
})
四、应用打包与部署
4.1 图标准备
桌面应用需要为不同平台准备对应的图标:
生成 macOS 图标(.icns):
# 方法1:使用 macOS 自带工具
# 1. 准备一张 1024x1024 的 PNG 图片
# 2. 创建 iconset 目录并生成多尺寸图片
mkdir icon.iconset
sips -z 16 16 icon.png --out icon.iconset/icon_16x16.png
sips -z 32 32 icon.png --out icon.iconset/icon_16x16@2x.png
sips -z 32 32 icon.png --out icon.iconset/icon_32x32.png
sips -z 64 64 icon.png --out icon.iconset/icon_32x32@2x.png
sips -z 128 128 icon.png --out icon.iconset/icon_128x128.png
sips -z 256 256 icon.png --out icon.iconset/icon_128x128@2x.png
sips -z 256 256 icon.png --out icon.iconset/icon_256x256.png
sips -z 512 512 icon.png --out icon.iconset/icon_256x256@2x.png
sips -z 512 512 icon.png --out icon.iconset/icon_512x512.png
cp icon.png icon.iconset/icon_512x512@2x.png
# 3. 生成 icns 文件
iconutil -c icns icon.iconset
mv icon.icns build/
生成 Windows 图标(.ico):
# 使用 ImageMagick(需先安装:brew install imagemagick)
magick icon.png -define icon:auto-resize=256,128,64,48,32,16 build/icon.ico
4.2 打包配置
electron-builder.yml 完整配置:
appId: com.electron.vue3demo
productName: 应用程序
directories:
output: release
buildResources: build
files:
- "dist/**/*"
- "electron/**/*"
- "package.json"
# macOS 配置
mac:
target:
- target: dmg
arch:
- x64
- arm64
category: public.app-category.utilities
icon: build/icon.icns
artifactName: "${productName}-${version}-mac-${arch}.${ext}"
identity: null # 跳过代码签名
# Windows 配置
win:
target:
- target: nsis
arch:
- x64
- ia32
icon: build/icon.ico
artifactName: "${productName}-${version}-win-${arch}.${ext}"
# NSIS 安装程序配置
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
allowElevation: true
installerIcon: build/icon.ico
uninstallerIcon: build/icon.ico
createDesktopShortcut: always
createStartMenuShortcut: true
shortcutName: ${productName}
# DMG 配置
dmg:
contents:
- x: 410
y: 150
type: link
path: /Applications
- x: 130
y: 150
type: file
title: "${productName} ${version}"
icon: build/icon.icns
4.3 打包命令
package.json 中的打包脚本:
{
"scripts": {
"dev": "vite",
"electron:dev": "electron .",
"dev:all": "concurrently \"npm run dev\" \"npm run electron:dev\"",
"build": "vite build",
"build:mac": "npm run build && electron-builder --mac",
"build:win": "npm run build && electron-builder --win",
"build:all": "npm run build && electron-builder --mac --win"
}
}
执行打包(跳过代码签名):
# 打包 macOS 版本
CSC_IDENTITY_AUTO_DISCOVERY=false npm run build:mac
# 打包 Windows 版本
CSC_IDENTITY_AUTO_DISCOVERY=false npm run build:win
# 同时打包所有平台
CSC_IDENTITY_AUTO_DISCOVERY=false npm run build:all
打包输出:

五、常见问题与解决方案
5.1 打包后应用白屏问题
问题描述:
应用打包后双击运行,窗口显示白屏,没有内容。
原因分析:
开发环境和生产环境加载资源的方式不同:
- 开发环境:加载 Vite 开发服务器(
http://localhost:2255/) - 生产环境:需要加载打包后的本地文件(
dist/index.html)
解决方案:
在 electron/main.js 中添加环境判断:
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
win.loadURL('http://localhost:2255/')
} else {
win.loadFile(path.join(__dirname, '../dist/index.html'))
}
5.2 代码签名错误
问题描述:
error: The specified item could not be found in the keychain
原因分析:
打包时 electron-builder 默认会尝试对应用进行代码签名,但如果没有配置证书会报错。
解决方案:
- 开发/测试环境:跳过代码签名
CSC_IDENTITY_AUTO_DISCOVERY=false npm run build:mac
- 生产环境:配置代码签名证书
# electron-builder.yml
mac:
identity: null # 或配置实际的证书标识
5.3 依赖包错误
问题描述:
Could not resolve "universal-cookie" imported by "@vueuse/integrations"
原因分析:
@vueuse/integrations 支持多个第三方库集成,但这些库是可选依赖(peer dependencies),需要手动安装。
解决方案:
npm install universal-cookie
5.4 图标文件找不到
问题描述:
cannot find specified resource "build/icon.icns"
原因分析:
打包配置中指定了图标路径,但实际文件不存在。
解决方案:
- 确保图标文件存在:
ls -la build/icon.icns
ls -la build/icon.ico
- 如果没有图标,注释掉配置使用默认图标:
# icon: build/icon.icns # 注释掉
5.5 Three.js 导出错误
问题描述:
"sRGBEncoding" is not exported by "node_modules/three/build/three.module.js"
原因分析:
Three.js 版本更新,API 发生变化。
解决方案:
更新导入方式:
// 旧版本
import { sRGBEncoding } from 'three'
// 新版本
import * as THREE from 'three'
// 使用 THREE.SRGBColorSpace
六、性能优化建议
6.1 前端性能优化
- 代码分割
// 路由懒加载
const Home = () => import('@/pages/Home/index.vue')
- 按需引入组件
// 按需引入 Element Plus
import { ElButton, ElTable } from 'element-plus'
- 图片优化
- 使用 WebP 格式
- 压缩图片尺寸
- 懒加载图片
6.2 Electron 优化
- 减小打包体积
# electron-builder.yml
files:
- "dist/**/*"
- "electron/**/*"
- "package.json"
- "!**/node_modules/*/{CHANGELOG.md,README.md,README}"
- "!**/node_modules/*/{test,__tests__,tests}"
- 启用 asar 打包
asar: true
- 配置镜像加速
# .npmrc
electron_mirror=https://npmmirror.com/mirrors/electron/
七、项目部署与分发
7.1 应用分发方式
-
直接分发安装包
- macOS:
.dmg文件 - Windows:
.exe安装程序
- macOS:
-
企业内部分发
- 搭建内部下载服务器
- 配置自动更新功能
-
应用商店发布
- Mac App Store(需要 Apple Developer 账号)
- Microsoft Store(需要开发者账号)
7.2 自动更新配置
使用 electron-updater 实现自动更新:
// main.js
const { autoUpdater } = require('electron-updater')
app.whenReady().then(() => {
// 检查更新
autoUpdater.checkForUpdatesAndNotify()
autoUpdater.on('update-available', () => {
console.log('发现新版本')
})
autoUpdater.on('update-downloaded', () => {
console.log('下载完成,准备安装')
})
})
八、最佳实践总结
8.1 开发规范
-
代码规范
- 使用 ESLint + Prettier
- 统一命名规范
- 注释完善
-
目录结构
- 按功能模块划分
- 公共组件独立管理
- 工具函数统一封装
-
版本管理
- 使用语义化版本号
- Git 提交规范
- 分支管理策略
8.2 安全建议
- 禁用 Node 集成
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
- 使用 Content Security Policy
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': ["default-src 'self'"]
}
})
})
- 验证外部链接
win.webContents.on('will-navigate', (event, url) => {
if (!url.startsWith('http://localhost')) {
event.preventDefault()
}
})
8.3 调试技巧
- 开发者工具
// 开发环境自动打开
if (process.env.NODE_ENV === 'development') {
win.webContents.openDevTools()
}
- 日志记录
// 使用 electron-log
const log = require('electron-log')
log.info('应用启动')
- 远程调试
# 启动时添加调试参数
electron . --remote-debugging-port=9222
九、项目展望
9.1 功能扩展方向
- 集成数据库(SQLite/NeDB)
- 实现应用内更新
- 添加系统托盘功能
- 支持多窗口管理
- 集成 WebSocket 实时通信
- 添加截图、录屏功能
9.2 技术升级
- 迁移到 TypeScript
- 集成单元测试(Vitest)
- 添加 E2E 测试(Playwright)
- 配置 CI/CD 自动化部署
- 性能监控与错误追踪
十、总结
本文详细介绍了使用 Electron + Vue 3 构建跨平台桌面应用的完整流程,从技术选型、项目搭建、功能实现到打包部署,覆盖了开发过程中的各个环节。
核心要点回顾:
- ✅ 技术栈选型:Electron + Vue 3 + Vite + Element Plus
- ✅ 环境配置:主进程环境判断,开发/生产环境适配
- ✅ 功能实现:路由配置、状态管理、水印组件
- ✅ 打包部署:跨平台打包,代码签名处理
- ✅ 问题解决:常见问题排查与解决方案
项目优势:
- 🎯 跨平台:一套代码,支持 Windows、macOS、Linux
- ⚡ 性能优秀:Vite 构建,快速开发体验
- 🎨 UI 美观:Element Plus 企业级组件库
- 🛡️ 安全可靠:完善的安全配置和水印防护
- 📦 易于部署:自动化打包,简化分发流程
希望本文能够帮助你快速上手 Electron 桌面应用开发!如有问题欢迎在评论区交流讨论。
参考资源
作者简介:专注于前端技术和跨平台应用开发,欢迎关注我的 CSDN 博客,获取更多技术分享。
如果本文对你有帮助,请点赞👍、收藏⭐、关注➕支持一下!
标签:Electron Vue3 桌面应用 跨平台开发 前端工程化

5888

被折叠的 条评论
为什么被折叠?



