手里有个老项目是基于element admin框架下的,之前写的时候没考虑到要打包成桌面端,后期需要打包成客户端,然后就开始了一些列版本操作,看着百度了都很简单,把electron 加入到自己项目中各种不兼容,升级版本,改代码各种百度,一个国庆假期就搞了这个事情,为了后面大家少踩点坑,打算详细的写写我的踩坑之路还有版本配置(版本配置真的很有必要,不要嫌麻烦,一步一步走哈)
1.大家比较关注的版本配置表
node.js | v15.0.0 |
electron | V14.2.9 |
vue | 2.7.8 |
sass-loader | 8.0.2 |
1.如果环境中没有安装cnpm 建议安装下,毕竟electron 用npm 安装基本99% 是安装失败的,别太侥幸了,已经走了很多遍了我(版本太高也会报错)
npm install cnpm@7.1.0 -g
2.安装 electron 环境
(如果 输入 vue add electron-buider 没反应需要 vue/cli升级下这个东西)
(1).在终端输入命令
Electron安装 | |
npm install electron -g | |
Vue项目添加Electron-builder打包工具 | |
vue add electron-builder |
(2)设置镜像方法(不配置也会报错,什么githhub)
npm config edit
使用该命令会弹出npm的配置文档,将以下类容复制到文件末尾。
electron_mirror=https://npm.taobao.org/mirrors/electron/ | |
electron-builder-binaries_mirror=https://npm.taobao.org/mirrors/electron-builder-binaries/ |
3.测试下的代码
npm run serve--网页 | |
npm run electron:serve--客户端 |
4.打包命令:npm run electron:build
打包完成了之后,会出.exe
这个就很顺利的操作了
我遇到的bug总结
打包好了之后启动,发现后台连不上,这个地方好像
不需要代理了。问就不知道啥原因
好不容易登录上去了,发现路由不跳转(真的晕死在厕所算了我)
好了好 了改个mode:hash 就ok了
还是点不动还需要改改cookie:
const TokenKey = 'Admin-Token' | |
// if (process.env.NODE_ENV === 'production') { | |
// store = new Store() | |
// } | |
export function getToken() { | |
return localStorage.getItem(TokenKey) | |
} | |
export function setToken(token) { | |
return localStorage.setItem(TokenKey, token) | |
} | |
export function removeToken() { | |
// if (process.env.NODE_ENV === 'production') { | |
// return store.delete(TokenKey) | |
// } | |
return localStorage.removeItem(TokenKey) | |
} |
最后忘记了该有的background.js和preload 两个文件代码(放在src 文件夹底下)(这个有啥用呢,在package.json 文件中加入一句 "main": "background.js",)
import { | |
app, | |
protocol, | |
BrowserWindow, | |
ipcMain, | |
Menu, | |
dialog, | |
globalShortcut | |
} from 'electron' | |
import { | |
createProtocol | |
} from 'vue-cli-plugin-electron-builder/lib'; | |
import installExtension, { | |
VUEJS3_DEVTOOLS | |
} from "electron-devtools-installer"; | |
const fs = require('fs') // 引入node原生fs模块 | |
const os = require('os') | |
const isDevelopment = process.env.NODE_ENV !== 'production'; | |
let mainWindow | |
// Scheme must be registered before the app is ready | |
protocol.registerSchemesAsPrivileged([{ | |
scheme: 'app', | |
privileges: { | |
secure: true, | |
standard: true | |
} | |
}]); | |
const path = require('path') | |
const menus = [{ | |
label: '视图', | |
submenu: [{ | |
label: '刷新', | |
role: 'reload' | |
}, | |
{ | |
label: '退出', | |
role: 'quit' | |
} | |
] | |
}] | |
const menu = Menu.buildFromTemplate(menus) | |
function createWindow() { | |
mainWindow = new BrowserWindow({ | |
width: 800, | |
height: 600, | |
show: false, | |
webPreferences: { | |
enableRemoteModule: true, // 允许弹框 | |
webSecurity: false, | |
nodeIntegration: true, | |
nodeIntegrationInWorker: true, // 在Web工作器中启用了Node集成 | |
preload: path.join(__dirname, 'preload.js'), | |
defaultEncoding: 'utf-8' | |
} | |
}) | |
mainWindow.once('ready-to-show', () => { | |
mainWindow.show() | |
}) | |
if (process.env.WEBPACK_DEV_SERVER_URL) { // 开发环境 | |
// Load the url of the dev server if in development mode | |
mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL) | |
if (!process.env.IS_TEST) mainWindow.webContents.openDevTools(); | |
globalShortcut.register('CommandOrControl+Shift+i', function () { // 使用快捷键shift+ctrl+i 调起开发工具 | |
mainWindow.webContents.openDevTools() | |
}) | |
} else { // 生产环境 | |
// mainWindow.webContents.openDevTools() // 生产环境关闭调试工具 | |
// Load the index.html when not in development | |
createProtocol('app') | |
// Load the index.html when not in development | |
mainWindow.loadURL('app://./index.html') | |
} | |
Menu.setApplicationMenu(menu) | |
} | |
/** | |
* 初始化 | |
*/ | |
app.whenReady().then(() => { | |
createWindow() | |
app.setAppUserModelId('管理平台') | |
// 点击MacOS底部菜单时重新启动窗口 | |
app.on('activate', () => { | |
if (BrowserWindow.getAllWindows().length === 0) { | |
createWindow() | |
} | |
}) | |
}) | |
// 兼容MacOS系统的窗口关闭功能 | |
app.on('window-all-closed', () => { | |
if (process.platform !== 'darwin') { | |
app.quit() | |
} | |
mainWindow = null | |
}) | |
// 最小化窗口(自定义导航条时) | |
ipcMain.on('window-min', () => { | |
mainWindow.minimize() | |
}) | |
// 最大化窗口(自定义导航条时) | |
ipcMain.on('window-max', () => { | |
// 如果已经是最大化窗口就还原 | |
if (mainWindow.isMaximized()) { | |
mainWindow.restore(); | |
} else { | |
mainWindow.maximize() | |
} | |
}) | |
// 关闭窗口 | |
ipcMain.on('window-close', () => { | |
mainWindow.close() | |
mainWindow = null; | |
app.exit(); | |
}) | |
// 主进程给进程通信 | |
ipcMain.on('toMain', function (event, arg) { | |
event.sender.send('fromMain', arg); // 返回给渲染进程 | |
}); | |
// 下载进程 | |
ipcMain.on('downLoad', function (event, arg) { | |
mainWindow.webContents.downloadURL(arg.url); | |
}); | |
// 调用文件读入方法 | |
ipcMain.on('judgeUse', function (event, arg) { | |
// 读入文件 | |
// 异步返回 | |
fs.readFile('./authorize.bin', { | |
encoding: 'utf-8' | |
}, (err, data) => { | |
// if (err) { | |
// // dialog.showMessageBox({ | |
// // type: 'error', | |
// // title: '找不到authorize.bin文件', | |
// // message: err.path, | |
// // buttons: ['ok'] | |
// // }).then((index) => { | |
// // if (index.response === 0) { | |
// // mainWindow.close() | |
// // mainWindow = null; | |
// // app.exit(); | |
// // } | |
// // }) | |
// event.sender.send('fromMain', null); // 返回给渲染进程 | |
// } else { | |
// event.sender.send('fromMain', data); // 返回给渲染进程 | |
// } | |
event.reply('authorizeBack', data); // 返回给渲染进程 | |
}) | |
}); | |
// 读取本地服务的IP 地址 同步 | |
ipcMain.on('syncGetLocalServer', function (event, arg) { | |
// 读入文件,同步返回数据 | |
fs.readFile('./localServer.xml', { | |
encoding: 'utf-8' | |
}, (err, data) => { | |
event.returnValue = data; // 返回给渲染进程 | |
}) | |
}); | |
// 读取本地服务的IP 地址 异步 | |
ipcMain.on('asyncGetLocalServer', function (event, arg) { | |
// 读入文件 | |
// 异步返回 | |
fs.readFile('./localServer.xml', { | |
encoding: 'utf-8' | |
}, (err, data) => { | |
event.reply('asyncBackLocalServer', data); // 返回给渲染进程 | |
}) | |
}); | |
// 隐藏按钮 | |
ipcMain.on('hideMenu', function (event, arg) { | |
mainWindow.setMenu(null); | |
}); | |
// 显示按钮 | |
ipcMain.on('showMenu', function (event, arg) { | |
mainWindow.setMenu(menu); | |
}); | |
app.on("ready", async () => { | |
if (isDevelopment && !process.env.IS_TEST) { | |
try { | |
await installExtension(VUEJS3_DEVTOOLS); | |
} catch (e) { | |
console.error("Vue Devtools failed to install:", e.toString()); | |
} | |
} | |
await createWindow(); | |
}); | |
// Exit cleanly on request from parent process in development mode. | |
if (isDevelopment) { | |
if (process.platform === 'win32') { | |
process.on('message', data => { | |
if (data === 'graceful-exit') { | |
app.quit(); | |
} | |
}); | |
} else { | |
process.on('SIGTERM', () => { | |
app.quit(); | |
}); | |
} | |
} | |
import { | |
contextBridge, | |
ipcRenderer | |
} from 'electron' | |
import { | |
autoUpdater | |
} from 'electron-updater' | |
window.ipcRenderer = ipcRenderer | |
contextBridge.exposeInMainWorld('ipcRenderer', { | |
// 异步向主进程 发送消息 | |
send: (channel, data) => { | |
const validChannels = ['toMain', 'downLoad', 'judgeUse', 'hideMenu', 'showMenu', 'window-close', 'asyncGetLocalServer'] | |
if (validChannels.includes(channel)) { | |
ipcRenderer.send(channel, data) | |
} | |
}, | |
// 同步向主进程 发送消息, | |
sendSync: (channel, data) => { | |
const validChannels = ['syncGetLocalServer'] | |
if (validChannels.includes(channel)) { | |
return ipcRenderer.sendSync(channel, data) | |
} | |
}, | |
// 异步接收主进程返回的数据 | |
receive: async (channel) => { | |
const validChannels = ['authorizeBack', 'asyncBackLocalServer'] | |
if (validChannels.includes(channel)) { | |
return new Promise((resolve) => { | |
ipcRenderer.on(channel, (event, ...args) => { | |
resolve(...args) | |
}) | |
}); | |
} | |
} | |
}) | |
// 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写 | |
! function updateHandle() { | |
let message = { | |
error: '检查更新出错', | |
checking: '正在检查更新……', | |
updateAva: '检测到新版本,正在下载……', | |
updateNotAva: '现在使用的就是最新版本,不用更新', | |
}; | |
const uploadUrl = "http://61.4.184.177:7799/download/"; // 下载地址,不加后面的**.exe | |
autoUpdater.setFeedURL(uploadUrl); | |
autoUpdater.on('error', function (error) { | |
sendUpdateMessage(message.error) | |
}); | |
autoUpdater.on('checking-for-update', function () { | |
sendUpdateMessage(message.checking) | |
}); | |
autoUpdater.on('update-available', function (info) { | |
sendUpdateMessage(message.updateAva) | |
}); | |
autoUpdater.on('update-not-available', function (info) { | |
sendUpdateMessage(message.updateNotAva) | |
}); | |
// 更新下载进度事件 | |
autoUpdater.on('download-progress', function (progressObj) { | |
mainWindow.webContents.send('downloadProgress', progressObj) | |
}) | |
autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) { | |
ipcMain.on('isUpdateNow', (e, arg) => { | |
console.log(arguments); | |
console.log("开始更新"); | |
//some code here to handle event | |
autoUpdater.quitAndInstall(); | |
}); | |
mainWindow.webContents.send('isUpdateNow') | |
}); | |
ipcMain.on("checkForUpdate", () => { | |
//执行自动更新检查 | |
autoUpdater.checkForUpdates(); | |
}) | |
}() | |
// 通过main进程发送事件给renderer进程,提示更新信息 | |
function sendUpdateMessage(text) { | |
mainWindow.webContents.send('message', text) | |
} |