相关文章推荐
健身的莴苣  ·  sql server ...·  1 月前    · 
买醉的篮球  ·  java ...·  1 年前    · 

你可能用的是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的项目,你可以收藏一下。

为什么要做这个桌面端应用?

这是一个对接第三方平台(饿了么、美团、京东到家、京东药寄送、达达、顺丰等)的项目,刚开始的时候,我们做的是一个网页版的应用。后来有两个需求,不得不转到桌面端。

  • 来单的声音提醒
  • 来单使用打印机自动打印
  • 浏览器对播放声音的自动播放有限制,必须用户和页面交互之后

    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:serveelectron: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">
        <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()
    
    分类:
    前端
    标签: