最近接手了一个老项目的技术升级任务:将现有的Vue2管理系统Web端打包成Windows应用程序安装包。这个需求在传统企业环境中非常典型——许多内部管理系统最初都是基于Web技术开发的,但随着业务发展,往往需要提供更稳定的桌面端体验。
我们现有的系统是一个典型的Vue2+Element UI后台管理系统,包含用户权限管理、数据报表、文件上传等常见功能。客户提出的核心诉求是:
目前将Web应用打包为桌面程序的主流方案有:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Electron | 生态完善、社区活跃 | 打包体积大(>100MB) | 功能复杂的跨平台应用 |
| NW.js | 支持更旧的浏览器内核 | 文档较少、更新慢 | 需要兼容老系统的项目 |
| Tauri | 打包体积小(~3MB) | 对前端构建工具要求高 | 追求轻量级的应用 |
| WebView2 | 原生集成、性能好 | 需要用户预装运行时 | Windows专属应用 |
| PWA+安装 | 无需额外打包 | 功能受限、无法系统集成 | 简单增强型Web应用 |
经过综合评估,我们选择Electron作为基础框架,主要基于以下考虑:
技术栈组合:
首先在现有Vue项目中添加Electron依赖:
bash复制# 安装核心依赖
npm install electron electron-builder --save-dev
# 安装常用工具库
npm install electron-updater electron-log --save
项目结构调整建议:
code复制project/
├── public/ # 原有Vue静态资源
├── src/ # 原有Vue源码
├── electron/ # 新增Electron主进程代码
│ ├── main.js # 主进程入口
│ └── preload.js # 预加载脚本
└── vue.config.js # 修改Vue配置
electron/main.js 基础配置示例:
javascript复制const { app, BrowserWindow, Tray } = require('electron')
const path = require('path')
let mainWindow
let tray = null
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true
}
})
// 加载Vue开发服务器或打包后的文件
if(process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:8080')
mainWindow.webContents.openDevTools()
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
}
// 系统托盘实现
tray = new Tray(path.join(__dirname, 'icon.png'))
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
})
}
app.whenReady().then(createWindow)
vue.config.js支持Electron:javascript复制module.exports = {
publicPath: './',
outputDir: 'dist',
assetsDir: 'static',
devServer: {
port: 8080,
// 允许Electron访问
headers: { "Access-Control-Allow-Origin": "*" }
}
}
javascript复制// router/index.js
const router = new VueRouter({
mode: 'hash', // 必须使用hash模式
base: process.env.BASE_URL,
routes
})
javascript复制// src/utils/electronAPI.js
const { ipcRenderer } = window.require('electron')
export default {
minimize() {
ipcRenderer.send('window-minimize')
},
closeApp() {
ipcRenderer.send('window-close')
},
// 其他系统级操作...
}
package.json关键配置:
json复制{
"name": "my-system",
"version": "1.0.0",
"main": "electron/main.js",
"scripts": {
"electron:serve": "vue-cli-service serve & electron .",
"electron:build": "vue-cli-service build && electron-builder"
},
"build": {
"appId": "com.example.mysystem",
"productName": "业务管理系统",
"directories": {
"output": "release"
},
"win": {
"target": "nsis",
"icon": "public/icon.ico"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
}
}
javascript复制const { autoUpdater } = require('electron-updater')
function checkForUpdates() {
autoUpdater.checkForUpdatesAndNotify()
autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox({
type: 'info',
buttons: ['现在重启', '稍后'],
message: '更新已下载',
detail: '新版本已下载完成,是否立即安装?'
}).then(({ response }) => {
if(response === 0) autoUpdater.quitAndInstall()
})
})
}
javascript复制// 在App.vue中
export default {
data() {
return {
updateStatus: ''
}
},
mounted() {
window.electronAPI.onUpdateStatus((event, status) => {
this.updateStatus = status
})
}
}
结合LocalStorage和Electron的本地文件存储:
javascript复制const fs = require('fs')
const path = require('path')
function cacheData(key, data) {
const cachePath = path.join(app.getPath('userData'), 'cache')
if(!fs.existsSync(cachePath)) {
fs.mkdirSync(cachePath)
}
fs.writeFileSync(
path.join(cachePath, `${key}.json`),
JSON.stringify(data)
)
}
javascript复制const { globalShortcut } = require('electron')
app.whenReady().then(() => {
globalShortcut.register('CommandOrControl+Shift+D', () => {
mainWindow.webContents.send('open-debug')
})
})
javascript复制function showNotification(title, body) {
new Notification({ title, body }).show()
}
json复制"build": {
"compression": "maximum",
"asar": true,
"extraResources": [
{
"from": "public/",
"to": "static"
}
]
}
javascript复制externals: {
'electron': 'require("electron")',
'fs': 'require("fs")'
}
loadURL或loadFile路径正确webPreferences配置vue.config.js中publicPath设置为./electron-builder的extraResources配置contextIsolation和nodeIntegration配置正确javascript复制// preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
minimize: () => ipcRenderer.send('window-minimize')
})
在实际将Vue2管理系统转为Electron应用的过程中,有几个关键点值得注意:
渐进式迁移:不要一次性改造所有功能,建议:
性能监控:Electron应用需要特别关注:
安全实践:
contextIsolationnodeIntegration(在渲染进程)用户习惯适配:
这个改造项目最终生成的安装包约120MB,支持Windows 7及以上系统,实现了所有预期功能。最大的收获是认识到Web技术与桌面环境融合时需要考虑的细节差异,特别是在系统集成和性能优化方面。