这篇文章我会持续更新,建议收藏。如果方便,请点个赞。小弟在这里谢谢了。
2021年11月25日更新(ICON锯齿解决方案)
如果你的项目发现桌面端的图标有锯齿:
VS
你可能用的是256 * 256的ICO. 你需要让UI设计一个128 * 128大小的PNG,然后生成ICO
我找了很多的PNG转ICO的地址,转换出来都没有用。现在把下面这个地址分享给大家:
www.convertico.com/#google_vig…
2021年10月22日更新(electron下载失败解决方案)
遇到安装依赖无法安装的问题,可以使用淘宝的镜像。使用方式如下:
在根目录下新建.npmrc
, 写入代码如下
electron_mirror="https://npm.taobao.org/mirrors/electron/"
建议使用这个方法,下文中的解决方案可以不用看了。
前言
你好, 我是阿飞
。
在过去的一年里面,我用Vue + Electron开发了一个桌面应用,目前有大约1000余客户在使用。
一直想写一个关于Electron的踩坑记录,今天终于下决心写下了这篇文章,希望可以帮小伙伴们开发的时候节约一些时间,先上一张项目图片。
今天的内容,主要是讲一下,我是如何完成这个项目的,又踩了那些坑。
如果你未来有可能要写一个Electron的项目,你可以收藏一下。
为什么要做这个桌面端应用?
这是一个对接第三方平台(饿了么、美团、京东到家、京东药寄送、达达、顺丰等)的项目,刚开始的时候,我们做的是一个网页版的应用。后来有两个需求,不得不转到桌面端。
- 来单的声音提醒
- 来单使用打印机自动打印
浏览器对播放声音的自动播放有限制,必须用户和页面交互之后
其次,我们需要做来单自动打印,浏览器的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://127.0.0.1:端口
这里的端口是你的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对象,该对象和主线程的ipcMain通讯
ipcRenderer.send('getPrinterList');
在主进程的createWindow当方中,执行获取设备方法
ipcMain.on('getPrinterList', event => {
// 主线程获取打印机列表
const list = win.webContents.getPrinters();
// 通过webContents发送事件到渲染线程,同时将打印机列表也传过去
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('打印中, 请稍后')
// 当vue节点渲染完成后,获取<webview>节点
const webview = this.$refs.printWebview;
webview.send('webview-print-render', {
printName: activeDevice,
// style: res.styleStr,
html: res.htmlStr
});
}
注意,下面是关键: print.html中代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<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代码...自行编写`;
// let styleStr =
var nod = document.createElement('style')
nod.type='text/css';
if (nod.styleSheet) { //ie下
nod.styleSheet.cssText = strBodyStyle;
} else {
nod.innerHTML = strBodyStyle; //或者写成 nod.appendChild(document.createTextNode(str))
}
document.getElementsByTagName('head')[0].appendChild(nod);
// 这里的info.html就是在点击的时候,传入的html。这是因为我这边的订单信息是动态的,所以html每次都是动态生成的。
document.getElementById('bd').innerHTML = info.html
ipcRenderer.sendToHost('webview-print-do')
})
</script>
自动打印
自动打印的触发条件是:websocket接受到消息,就触发打印事件。这里可以根据各位小伙伴的实际情况去处理。
如果有什么问题,可以留言给我。
软件自动升级
软件自动升级,网上教程也有不少,但是都不是很完善。
我这边做的是,每10分钟,监听一次。如果发现远程服务器有新的版本,就会自动升级。
自动更新需要用到electron-updater
包。
这个包直接安装即可。 完成效果如下:
自动升级的步骤
第一步:
创建窗口的时候,执行更新方法:
import { autoUpdater } from 'electron-updater';
function createWindow() {
// 根据不同的环境,配置不同的远程下载地址
// 32位 64位 以及预发布环境
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) {
// console.log('出现错误');
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中,获取到当前的版本信息,发送给渲染进程。
// 通过main进程发送事件给renderer进程,提示更新信息
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');
// 每隔10分钟再次检查
setInterval(() =>{
// 每 10 分钟检查一次是否需要更新
ipcRenderer.send('checkForUpdate');
}, 600000)
// // 注意:“downloadProgress”事件可能存在无法触发的问题,只需要限制一下下载网速就好了
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的原因找不到模块
具体的编写位置如下:
// 这里的配置卸载vue.config.js
pluginOptions: {
electronBuilder: {
builderOptions: {
productName: '门店看板',
appId: '123',
win: {
icon: './public/desttop.ico'
},
publish: [
{
provider: 'generic',
url: 'xxxxx'
}
],
"nsis": {
"oneClick": false, // 是否一键安装
"perMachine": true,
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerSidebar": './public/installer.bmp',
// "installerIcon": './public/desttop.ico',// 安装图标
// "uninstallerIcon": './public/desttop.ico',//卸载图标
// "installerHeaderIcon": './public/desttop.ico', // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "门店看板", // 图标名称
// "include": "build/script/installer.nsh", // 包含的自定义nsis脚本
}
}
}
},
其他的基础配置
electron的基础配置,我把代码贴在这里,大家想了解,去看一些electron的官方介绍,我不多说了。
设置托盘
const setAppTray = () => {
// 托盘对象
let appTray = null;
// 系统托盘右键菜单
let trayMenuTemplate = [
{
label: '退出',
click: function() {
// ipc.send('close-main-window');
// isQuit = true;
app.quit()
// win.close('close')
// console.log('执行了。。。。')
// win.on('closed', () => {})
}
}
];
// 系统托盘图标目录
// let trayIcon = path.join(__dirname, '../public')
// const iconPath = path.join(__static, './logo.png');
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() {
// darwin表示macOS,针对macOS的设置
if (process.platform === 'darwin') {
const template = [
{
label: 'App Demo',
submenu: [
{
role: 'about'
},
{
role: 'quit'
}]
}]
let menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
} else {
// windows及linux系统
Menu.setApplicationMenu(null)
}
}
createWindow的时候配置以及devtools的打开关闭
这里包括:
- 打开最大化窗口
- 通过F12来打开调试工具等等
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
// fullscreen: true,
show: false,
frame: true,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
webSecurity: false,
webviewTag: true,
nodeIntegration: true
},
// eslint-disable-next-line no-undef
icon: `${__static}/logo.ico`
});
win.maximize();
win.show();
updateHandle();
ipcMain.on('toggleDevTools', event => {
win.webContents.toggleDevTools();
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
// if (!process.env.IS_TEST) win.webContents.openDevTools();
// win.webContents.openDevTools();
} else {
createProtocol('app');
// Load the index.html when not in development
win.loadURL('app://./index.html');
// win.webContents.openDevTools();
}
win.on('closed', (e) => {
win = null;
});
// 去除菜单
createMenu()
// 设置托盘
setAppTray()
}