electron_mirror="https://npm.taobao.org/mirrors/electron/"
建议使用这个方法,下文中的解决方案可以不用看了。
你好, 我是阿飞
。
在过去的一年里面,我用Vue + Electron开发了一个桌面应用,目前有大约1000余客户在使用。
一直想写一个关于Electron的踩坑记录,今天终于下决心写下了这篇文章,希望可以帮小伙伴们开发的时候节约一些时间,先上一张项目图片。
今天的内容,主要是讲一下,我是如何完成这个项目的,又踩了那些坑。
如果你未来有可能要写一个Electron的项目,你可以收藏一下。
为什么要做这个桌面端应用?
这是一个对接第三方平台(饿了么、美团、京东到家、京东药寄送、达达、顺丰等)的项目,刚开始的时候,我们做的是一个网页版的应用。后来有两个需求,不得不转到桌面端。
来单的声音提醒
来单使用打印机自动打印
浏览器对播放声音的自动播放有限制,必须用户和页面交互之后
Chrome对音频调整策略说明
其次,我们需要做来单自动打印,浏览器的window.print打印无法实现自动打印。如果要实现自动打印,只能使用C-lodop来实现打印。
其实这个插件还是非常好的解决了我的问题。
但是有两个缺陷:
自动打印的时候,小票底部有一个“该打印由Clodup提供的字样”,去除需要付费
打印需要额外安装一个C-lodup的插件
我在这之前没有做过桌面端,当时我们的web端已经完全做完了,如果在重新写桌面端,成本就太高了。所以只能考虑,如何把当前的代码,从vue的web端转成桌面版。
我查询一番之后,发现通过vue-cli-plugin-electron-builder可以实现vue向Electron的完美迁移。
这里其实很简单,直接yarn add vue-cli-plugin-electron-builder
,之后就可以使用npm run electron:serve
启动本地项目了。
这边文章主要讲踩坑记录,安装的过程就不多说了。网上的文章很多,这里我放一个链接在这里。
其实就是安装依赖,然后执行命令就可以了。
安装完之后,我们会发现我们的项目中,有一些文件发生了变化,多了一个文件。
package.json发现变化
增加了background.js文件
我们能看到,在package.json的里面,多了electron:serve
和electron:build
的选项。
但是打包的时候,并不是执行npm run electron:build
那么简单的。
因为资源的原因,下载的时候,会出现各种超时,导致打包失败。
如果你是在windows下面对electron的文件进行打包,可能需要做如下 操作
npm run build后,第一次报错需要下载 electron-v2.0.18-win32-x64.zip(我这里是需要该版本的文件,根据自己的错误信息,来选择对应的版本下载即可),在镜像中选取该版本号 2.0.18,点击进入,并选择下载 electron-v2.0.18-win32-x64.zip 和 SHASUMS256.txt, 下载完成后,将SHASUMS256.txt文件改成 SHASUMS256.txt-2.0.18,然后将两个文件拷入如图位置:
完成step1后,继续npm run build,发现又有文件下载失败 winCodeSign-2.4.0(我这里是需要该版本的文件,根据自己的错误信息,来选择对应的版本下载即可),然后自己手动下载github.com/electron-us…
然后拷贝到下面的文件夹中:
完成step2后,继续npm run build,发现又有文件下载失败 nsis-3.0.3.2(同上),然后自己手动下载github.com/electron-us…
然后拷贝到下面的文件夹中:
step4:完成step3后,继续npm run build,发现又有文件下载失败 nsis-resources-3.3.0,但是按照上面的方法操作,最后还是会报错,然后我尝试,用step3中下载解压后的这个nsis-3.0.3.2版本试试,拷贝如图位置所有文件:
然后拷贝到下面的文件夹中:
这个时候,npm run electron:build
就可以了。
使用代理的方式安装打包依赖
按照上面的方式,只能在当前电脑进行打包,换了电脑依旧会出现该问题。
后来我找到一个新的办法,通过代理。
但是常规的代理方式,是没有用的,因为翻墙的时候,npm run electron:build的时候,并不会通过代理去下载相应的国外资源。
所以这里我们需要设置linux的代理。
在terminal中运行下面的命令
export http_proxy=http:
这里的端口是你的FQ的工具代理的端口。
比如我的就应该是下面的这个端口:
这个方法我在Mac
上面使用可以,windows上没有尝试。有兴趣的小伙伴可以自行尝试一下。尝试之后,可以留言哦!
如何区分32位打包和64位打包, 以及环境变量的设置
在我的package.json里面有下面的命令
"electron:build32": "vue-cli-service electron:build --win --ia32 --mode prod",
"electron:buildPre32": "vue-cli-service electron:build --win --ia32 --mode pre",
"electron:build64": "vue-cli-service electron:build --mode prod",
"electron:buildTest32": "vue-cli-service electron:build --win --ia32 --mode development",
"electron:buildTest64": "vue-cli-service electron:build --mode development",
"electron:serve": "vue-cli-service electron:serve",
--win
: 代表打包windows版本 --ia32
: 代表打包32位版本,如果不设置,默认打包64位版本
32位版本的打包结果可以兼容64位电脑使用,但是64位的项目不兼容32位。
--mode
:是我配置的环境变量,和vue-cli3官方文档中配配置意思一致。只需要在根目录下创建相应的文件即可:
网上关于自动打印的文章不多,且零零散散。
最后在我自己的研究下,还是搞定了。
自动打印涉及几个问题点:
如何获取设备?
打印模板怎么编写?
如何自动触发设备打印?
先来讲获取设备:
在App.js,触发获取打印机设备
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('getPrinterList');
在主进程的createWindow当方中,执行获取设备方法
ipcMain.on('getPrinterList', event => {
const list = win.webContents.getPrinters();
win.webContents.send('printerList', list);
App.js中的mounted的时候接受获取到的打印机列表
ipcRenderer.once('printerList', (event, data) => {
console.log('打印机列表', data)
ipcMain 和 ipcRenderer分别是主进程和渲染进程,他们的交互方式是通过on和send方法。也就事监听观察者模式吧,不知道我有没有说错。
类似于vue中的eventBus的事件传递方式。
通过什么打印?
在electron中,打印需要通过webview的形式进行打印。
将写好的打印模板放在远程服务器,或者在本地。
放在远程服务器的好处是,更改模板的时候,不需要重新打包。
同样是在App.js中,插入webview(因为我的打印是全局的,所以放在这里)
在点击打印的时候,执行下面的方法:
handlePrintTest (res) {
let activeDevice = localStorage.getItem('activeDevice');
if(!activeDevice) {
this.$Message.error('请选择打印机!')
return;
this.$Message.success('打印中, 请稍后')
const webview = this.$refs.printWebview;
webview.send('webview-print-render', {
printName: activeDevice,
html: res.htmlStr
注意,下面是关键: print.html中代码如下:
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body id="bd"></body>
<script>
const { ipcRenderer } = require('electron')
ipcRenderer.on('webview-print-render', (event, info) => {
console.log('info')
var strBodyStyle = `css代码...自行编写`;
var nod = document.createElement('style')
nod.type='text/css';
if (nod.styleSheet) {
nod.styleSheet.cssText = strBodyStyle;
} else {
nod.innerHTML = strBodyStyle;
document.getElementsByTagName('head')[0].appendChild(nod);
document.getElementById('bd').innerHTML = info.html
ipcRenderer.sendToHost('webview-print-do')
</script>
自动打印的触发条件是:websocket接受到消息,就触发打印事件。这里可以根据各位小伙伴的实际情况去处理。
如果有什么问题,可以留言给我。
软件自动升级
软件自动升级,网上教程也有不少,但是都不是很完善。
我这边做的是,每10分钟,监听一次。如果发现远程服务器有新的版本,就会自动升级。
自动更新需要用到electron-updater
包。
这个包直接安装即可。 完成效果如下:
自动升级的步骤
创建窗口的时候,执行更新方法:
import { autoUpdater } from 'electron-updater';
function createWindow() {
if ((process.argv[5] && process.argv[5] === 'pre') || (process.argv[6] && process.argv[6] === 'pre')) {
if(process.arch==='x64') {
uploadUrl = 'https://******/upload/backend/template/dist_electron';
} else {
uploadUrl = 'https://******/upload/backend/template/dist_electron32';
} else {
if(process.arch==='x64') {
uploadUrl = 'https://******/upload/backend/template/slyf64';
} else {
uploadUrl = 'https://******/upload/backend/template/slyf32';
autoUpdater.setFeedURL(uploadUrl);
autoUpdater.autoDownload = false;
updateHandle();
function updateHandle() {
console.log('执行检查更新');
let updaterCacheDirName = 'electron-admin-updater';
const updatePendingPath = path.join(autoUpdater.app.baseCachePath, updaterCacheDirName, 'pending');
fs.emptyDir(updatePendingPath);
let message = {
error: '检查更新出错',
checking: '正在检查更新……',
updateAva: '检测到新版本,正在下载……',
updateNotAva: '现在使用的就是最新版本,不用更新'
const os = require('os');
autoUpdater.setFeedURL(uploadUrl);
autoUpdater.autoDownload = false;
autoUpdater.on('error', function(err) {
sendUpdateMessage(err);
autoUpdater.on('checking-for-update', function() {
console.log('检查更新');
sendUpdateMessage(message.checking);
autoUpdater.on('update-available', function(info) {
console.log('发现新的版本。。', info);
sendUpdateMessage(info)
autoUpdater.on('update-not-available', function(info) {
console.log('无需更新');
sendUpdateMessage(message.updateNotAva);
autoUpdater.on('download-progress', function (progressObj) {
win.webContents.send('downloadProgress', progressObj)
autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
autoUpdater.quitAndInstall();
ipcMain.on('startDownload', () => {
autoUpdater.downloadUpdate()
ipcMain.on('checkForUpdate', () => {
autoUpdater.checkForUpdates();
在background中,获取到当前的版本信息,发送给渲染进程。
function sendUpdateMessage(text) {
win.webContents.send('message', text);
渲染进程获取到更新信息,判断是否需要染出更新窗口
<Modal
v-model="modal"
title="版本更新提示"
@on-ok="ok"
@on-cancel="cancel">
<p>有新的版本等待更新, 版本号为:{{version}}</p>
<p v-if="versionDes">版本更新内容:{{versionDes}}</p>
</Modal>
<Modal
v-model="progressModal"
footer-hide
title="下载进度">
<Progress :percent="downloadPercent" status="active" />
</Modal>
mounted () {
ipcRenderer.send('checkForUpdate');
setInterval(() =>{
ipcRenderer.send('checkForUpdate');
}, 600000)
ipcRenderer.on('downloadProgress', (event, progressObj) => {
this.progressModal = true;
this.downloadPercent = progressObj.percent.toFixed(2) || 0;
ipcRenderer.on("message", (event, text) => {
if(text.version) {
this.modal = true;
this.version = text.version;
this.versionDes = text.versionDes ? text.versionDes : '';
this.tips = text;
需要注意的是, 如果想要手动更新,一定要再createWindow的时候,添加autoUpdater.autoDownload = false;
。否则一旦发现有新的版本需要更新,就会立即更新。
关于latest.yml的说明
项目打包完成之后,会生成这两个文件。
version: 1.1.6
files:
- url: 门店看板 Setup 1.1.6.exe
sha512: uWV2SYo7kTK4aYcBeyQx9Y5c62fW50VLQcs96cvgj/ygz15YrEFsc4GVWOmGOJ8LbpKhw5gHYCjBj0s6yGwBpg==
size: 59999596
path: 门店看板 Setup 1.1.6.exe
sha512: uWV2SYo7kTK4aYcBeyQx9Y5c62fW50VLQcs96cvgj/ygz15YrEFsc4GVWOmGOJ8LbpKhw5gHYCjBj0s6yGwBpg==
releaseDate: '2020-12-30T08:59:40.271Z'
自定义参数:
versionDes: '这是新版本的说明'
这个文件中,可以在获取更新信息的时候,获取到,你可以在这添加的你的新版本信息。
我在上面标红的两个文件需要放到远程服务器,autoupdater插件会读取latest.yml文件,判断是否需要升级。
需要注意的是,这两个文件是相互匹配的,如果没有不是同时打包的,是无法更新的。
其他打包后的问题
electron打包的软件只支持win7+的系统。有xp需求的请绕道。
win7部分电脑打开白屏
win7白屏问题,是因为缺少.net frameork包导致的,安装后即可解决
如果是 win 7 及以上系统,安装 4.8 版本的.net 包后,再重新打开看板
官网地址如下:dotnet.microsoft.com/download/do…
部分未更新的 win 7 系统安装.net 包后仍不支持,此时可以用 win 7 补丁更新系统后再次尝试安装
部分机型无法安装.netframework
安装.net 包时弹出“无法建立到信任根颁发机构的证书链”,则按以下步骤安装信任证书后重新安装.net 包 根证书下载地址: download.microsoft.com/download/2/…
1)下载根证书并安装
2)点击开始-运行或 win+R 键,输入 mmc
3)文件-添加删除管理单元-双击证书
4)选择计算机账户-下一步-完成即可
5)导入证书-浏览需要导入的证书-下一步-完成即可
6)完成以上步骤之后,重新安装.net 包即可
xx应用 已停止工作
遇到这个问题不要慌,这是软件的兼容性问题。按照下面的解决方案就可以了。
a)选中右击-点击属性-打开兼容性
b)点击以兼容模式运行这个程序-选择 Windows server 2008-点击以管理员身份运行此程序-最后点击应用、确认
打印机相关问题非常多,有驱动的问题,有串口还是usb的问题,这里我不一一列举。
小伙伴遇到打印机的问题,可以留言,我将在能力范围内,帮你解决问题。
我在第二个Electron应用中遇到的一个奇葩问题
我的另外一个项目,使用到了node-adodb
,出现了十分诡异的问题。
就是我在本地环境electron:serve
的时候,可以正常读取access数据库的资源,但是打包之后告诉我找不到这个模块
当然,网上我查了整整一天,都没有找到解决方案。不止这个问题,log-4的日志插件也会有这个问题
最后我找到了解决方案。
第一:查看您的插件是不是在生产依赖
第二:如果在生产依赖下,渲染进程中使用window.require的方式引入第三方插件,打包后会出现找不到依赖的情况,所以你需要吧require的模块卸载background的主进程文件中
第三:部分插件,打包后因为asar的原因找不到模块
具体的编写位置如下:
pluginOptions: {
electronBuilder: {
builderOptions: {
productName: '门店看板',
appId: '123',
win: {
icon: './public/desttop.ico'
publish: [
provider: 'generic',
url: 'xxxxx'
"nsis": {
"oneClick": false,
"perMachine": true,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerSidebar": './public/installer.bmp',
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "门店看板",
其他的基础配置
electron的基础配置,我把代码贴在这里,大家想了解,去看一些electron的官方介绍,我不多说了。
const setAppTray = () => {
let appTray = null;
let trayMenuTemplate = [
label: '退出',
click: function() {
app.quit()
appTray = new Tray(`${__static}/logo.png`)
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
appTray.on('click', function (Event) {
win.show();
appTray.setToolTip('邻医快药')
appTray.setContextMenu(contextMenu)
function createMenu() {
if (process.platform === 'darwin') {
const template = [
label: 'App Demo',
submenu: [
role: 'about'
role: 'quit'
let menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
} else {
Menu.setApplicationMenu(null)
createWindow的时候配置以及devtools的打开关闭
这里包括:
打开最大化窗口
通过F12来打开调试工具等等
function createWindow() {
win = new BrowserWindow({
show: false,
frame: true,
webPreferences: {
webSecurity: false,
webviewTag: true,
nodeIntegration: true
icon: `${__static}/logo.ico`
win.maximize();
win.show();
updateHandle();
ipcMain.on('toggleDevTools', event => {
win.webContents.toggleDevTools();
if (process.env.WEBPACK_DEV_SERVER_URL) {
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
} else {
createProtocol('app');
win.loadURL('app://./index.html');
win.on('closed', (e) => {
win = null;
createMenu()
setAppTray()