preface

Hi, I’m Alfy.

Over the past year, I have developed a desktop application with Vue + Electron, which is currently being used by more than 1000 customers.

I have always wanted to write a record of the trampling pits on Electron. Today, I finally decided to write this article, hoping to save some time for my friends when developing Electron. First, I will post a picture of the project.

Today’s content is mainly to talk about how I finished the project, and stepped on those pits.

If you might want to write a project on Electron in the future, you can bookmark it.

Why make this desktop application?

This is a project to connect with third-party platforms (Ele. me, Meituan, JINGdong Home, JINGdong Medicine Delivery, Dada, SF Express, etc.). At the beginning, we made a web version of the application. Then I had two requirements and had to move to the desktop.

  1. Come to single voice remind
  2. Use the printer to print automatically

The browser has restrictions on automatic playback of playback sounds until the user interacts with the page

Chrome adjusts the audio policy description

Second, we need to do automatic printing of the order. The window. Print of the browser cannot print automatically. If automatic printing is to be implemented, only C-lodoP can be used for printing.

In fact, this plug-in is very good to solve my problem.

But there are two drawbacks:

  • When automatically printed, the receipt has a “print provided by Clodup” at the bottom, which is removed at a charge
  • Printing requires the installation of an additional c-Lodup plug-in

start

I hadn’t done the desktop before, and we were completely done with the Web, and it would have been too expensive to rewrite the desktop. So you have to think about how to convert the current code from the Web side of vUE to the desktop version.

After some inquiry, I found that the perfect migration from VUE to electron can be realized through VUE-CLI-plugin-electron – Builder.

Yarn add VUE-cli-plugin-electron – Builder. Then you can use NPM run electron:serve to start the local project.

This article is mainly about the tread pit record, the installation process is not said. There are many articles on the web, so I put a link here.

Simply install the dependency and execute the command.

File changes

After the installation, we will find that some of the files in our project have changed, and there is an extra file.

  • Package. json changes found

  • Added background.js file

packaging

We can see that in package.json, there are the electron:serve and electron:build options.

But when it comes time to pack, it’s not as simple as performing NPM Run electron: Build.

Due to resource reasons, there will be various timeouts when downloading, resulting in packaging failure.

If you are packing the electron file on Windows, you may need to do the following

The first step:

After NPM run build, if an error occurs for the first time, you need to download the electron v2.0.18-win32-x64.zip (I need this version of the file here, select the corresponding version to download according to your own error information), select the version 2.0.18 in the image, and click enter. Then choose to download the electron v2.0.18-win32-x64.zip and shasums256. TXT. After downloading, change the shasums256. TXT file to shasums256.txt-2.0.18 and copy the two files to the location as shown in the picture:

The second step:

After completing step1, continue NPM run build, find another file download failure wincodesign-2.4.0 (I need this version of the file here, according to their own error information, choose the corresponding version to download), Then manually download github.com/electron-us…

Then copy it to the following folder:

Step 3:

After completing step2, continue the NPM run build, find another file download failure nsis-3.0.3.2 (ibid.), then manually download github.com/electron-us…

Then copy it to the following folder:

Step 4:

Step4: Step3 finished, continue to NPM run the build, found that have failed to file download nsis – resources – 3.3.0, but according to the above method, finally still complains, and then I try and use step3 download unpacked the nsis – 3.0.3.2 version a try, Copy all files as shown:

Then copy it to the following folder:

At this point, NPM Run electron: Build is ready.

Package dependencies are installed using proxies

According to the above method, the packaging can only be done on the current computer, the problem will still occur when you change the computer.

Then I found a new way, through an agent.

However, the conventional proxy method is useless, because when scaling the wall and NPM run electron:build, the corresponding foreign resources will not be downloaded through the proxy.

So here we need to set up the Linux proxy.

Run the following command in terminal

Export http_proxy = http://127.0.0.1: portCopy the code

The port here is the port of your FQ’s tool agent.

For example, mine should be the following port:

I can do this on the Mac, but I haven’t tried it on Windows. Interested partners can try their own. After trying, you can leave a message!

How to distinguish 32-bit packaging from 64-bit packaging, and setting environment variables

I have the following command in my 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",
Copy the code

–win: indicates that the Windows version is packaged. — IA32: indicates that the 32-bit version is packaged. If this parameter is not specified, the 64-bit version is packaged by default

The 32-bit version of the package results are compatible with 64-bit computers, but 64-bit projects are not compatible with 32-bit.

–mode: indicates the environment variable configured by me, which is consistent with the configuration in the vuE-CLI3 official document. Just create the corresponding file in the root directory:

Automatic printing

Articles about automatic printing on the web are few and scattered.

Finally, with my own research, I got it done.

Automatic printing involves several issues:

  • How to obtain equipment?
  • How to write a print template?
  • How to automatically trigger device printing?

Access to equipment

Let’s start with acquiring the device:

In app.js, the trigger gets the printer device

const { ipcRenderer } = window.require('electron'); Ipcrenderer. send('getPrinterList'); // This object communicates with the main thread's ipcMain.Copy the code

In createWindow of the main process, perform the get device method

IpcMain. On (' getPrinterList 'event = > {/ / main thread for the printer list const list = win. WebContents. GetPrinters (); Win.webcontents. Send ('printerList', list); });Copy the code

Mounted in app.js accepts a list of printers

Ipcrenderer. once('printerList', (event, data) => {console.log(' printerList', data) // set printerList... });Copy the code

IpcMain and ipcRenderer are the main and renderer processes, respectively, and they interact via the ON and Send methods. Let’s go with the observer mode. I don’t know if I’m wrong.

Event delivery is similar to eventBus in VUE.

Print by what?

In electron, printing is done in the form of a WebView.

Place the written print template on a remote server or locally.

The advantage of being on a remote server is that you do not need to repackage templates when they change.

Again in app.js, insert webView (I put it here because my printing is global)

When you click Print, execute the following method:

handlePrintTest (res) {
    let activeDevice = localStorage.getItem('activeDevice');
    if(! activeDevice) {this.$Message.error('Please select printer! ')
        return;
    }
    this.$Message.success('Printing, please wait')
    // Get the < webView > node when the vue node is rendered
    const webview = this.$refs.printWebview;
    webview.send('webview-print-render', {
        printName: activeDevice,
        // style: res.styleStr,
        html: res.htmlStr
    });

}
Copy the code

Note that here’s the key: the code in print.html looks like this:

<! DOCTYPEhtml>
<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) = > {
        / / executive share
        console.log('info')
        var strBodyStyle = ` CSS code... Self-written ';
        // 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 is the HTML passed in when clicked. This is because the order information on my side is dynamic, so the HTML is dynamically generated each time.
        document.getElementById('bd').innerHTML = info.html
        ipcRenderer.sendToHost('webview-print-do')})</script>
Copy the code

Automatic printing

The triggering conditions for automatic printing are as follows: When webSocket receives a message, the printing event is triggered. Here you can deal with it according to the actual situation of your friends.

If you have any questions, please leave a message.

Automatic software upgrade

There are also many online tutorials, but none of them are perfect.

What I’m doing here, every 10 minutes, is listening in. If the remote server is found to have a new version, it is automatically upgraded.

Automatic updates require the electron-updater package.

This package can be installed directly. The finished effect is as follows:

Automatic upgrade steps

The first step:

When creating a window, execute the update method:

import { autoUpdater } from 'electron-updater';
function createWindow() {
	// Configure different remote download addresses according to different environments
    // 32-bit 64-bit and pre-release environments
	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('Perform check update');
    let updaterCacheDirName = 'electron-admin-updater';
    const updatePendingPath = path.join(autoUpdater.app.baseCachePath, updaterCacheDirName, 'pending');
    fs.emptyDir(updatePendingPath);
    let message = {
        error: 'Check update error'.checking: 'Checking for updates... '.updateAva: 'New version detected, downloading... '.updateNotAva: 'This is the latest version, don't update it'
    };
    const os = require('os');

    autoUpdater.setFeedURL(uploadUrl);
    autoUpdater.autoDownload = false;
    autoUpdater.on('error'.function(err) {
        // console.log(' error ');
        sendUpdateMessage(err);
    });
    autoUpdater.on('checking-for-update'.function() {
        console.log('Check for updates');
        sendUpdateMessage(message.checking);
    });
    autoUpdater.on('update-available'.function(info) {
        console.log('Discover the new version. ', info);
        sendUpdateMessage(info)
    });
    autoUpdater.on('update-not-available'.function(info) {
        console.log('No update required');
        sendUpdateMessage(message.updateNotAva);
    });

    // Update the download progress event
    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'.() = > {
        // Perform automatic update checks
        autoUpdater.checkForUpdates();
    });
}
Copy the code

In background, the current version information is retrieved and sent to the renderer.

// Send an event to the renderer via main to prompt the renderer to update
function sendUpdateMessage(text) {
    win.webContents.send('message', text);
}
Copy the code

The renderer process gets the update information and decides if it needs to dye out the update window

<Modal
    v-model="modal"
    title="Version Update Prompt"
    @on-ok="ok"
    @on-cancel="cancel">
    <p>A new version is waiting to be updated. The version number is {{version}}.</p>
    <p v-if="versionDes">Version update: {{versionDes}}</p>
</Modal>
<Modal
    v-model="progressModal"
    footer-hide
    title="Download Progress">
    <Progress :percent="downloadPercent" status="active" />
</Modal>
Copy the code
mounted () {
	// Check for updates when the page is refreshed
	ipcRenderer.send('checkForUpdate');
    // Check again every 10 minutes
    setInterval(() = >{
        // Check every 10 minutes for updates
        ipcRenderer.send('checkForUpdate');
    }, 600000)
    // // Note: the "downloadProgress" event may not trigger the problem, just limit the download speed
    ipcRenderer.on('downloadProgress'.(event, progressObj) = > {
    	// If a new version needs to be updated, the update dialog box is displayed immediately
        this.progressModal = true;
        this.downloadPercent = progressObj.percent.toFixed(2) | |0;
    });
    
    ipcRenderer.on("message".(event, text) = > {
        // New version detected
        if(text.version) {
            this.modal = true;
            this.version = text.version;
            this.versionDes = text.versionDes ? text.versionDes : ' ';
            this.tips = text; }}); }Copy the code

AutoUpdater. AutoDownload = false; autoUpdater. . Otherwise, it will be updated as soon as a new version is found to need updating.

A note on latest.yml

After the project is packaged, these two files are generated.

Version: 1.1.6 Files: - URL: Store Kanban Setup 1.1.6.exe sha512: uWV2SYo7kTK4aYcBeyQx9Y5c62fW50VLQcs96cvgj/ygz15YrEFsc4GVWOmGOJ8LbpKhw5gHYCjBj0s6yGwBpg== size: 59999596 path: Sha512: uWV2SYo7kTK4aYcBeyQx9Y5c62fW50VLQcs96cvgj/ygz15YrEFsc4GVWOmGOJ8LbpKhw5gHYCjBj0s6yGwBpg== releaseDate: '2020-12-30T08:59:40.271z' Custom parameter: versionDes: 'This is the new version of the description'Copy the code

In this file, when you get the update information, you get your new version information that you can add here.

The two files I highlighted in red above need to be placed on a remote server, and the Autoupdater plugin will read the latest.yml file and determine if it needs to be upgraded.

Note that these two files match each other and cannot be updated if not packaged at the same time.

Other post-packing questions

Electron packs software for Windows 7+ only. Those with XP requirements please take a detour.

Windows 7 part of the computer open white screen

Net frameork package is missing. Net frameork package is missing

  1. For Windows 7 or later, install the.net package with version 4.8 and reopen kanban

Website address is as follows: dotnet.microsoft.com/download/do…

  1. Some unupdated Windows 7 systems are still not supported after installing the.NET package. In this case, you can update the system with the Windows 7 patch and try to install it again

  1. The. Netframework cannot be installed on some models

Install.net package pop-up “cannot be established to trust agency’s certificate chain”, the trust, follow these steps to install the certificate after reinstalling. Net bag root certificate download address: download.microsoft.com/download/2/…

1) Download the root certificate and install it

2) Click start – Run or Win +R and type MMC

3) File – Add delete snap-in – Double-click the certificate

4) Select computer account – next – done

5) Import the certificate. Browse the certificate to be imported

6) After completing the above steps, reinstall the.NET package

Xx application has stopped working

Don’t panic, it’s a software compatibility issue. Follow the solution below.

A) Right-click – click Properties – open compatibility

B) Click To run this program in compatibility mode – select Windows Server 2008- click to run this program as administrator – finally click Apply, OK

Other problems

Printer related problems are very many, there are driver problems, there are serial port or USB problems, here I do not list.

If you have a problem with the printer, please leave a message. I will help you solve the problem within my ability.

A weird problem I had in my second Electron application

Another project of mine, using Node-Adodb, had very strange problems.

When I was in the local environment electron: Serve, I could normally read the resources of the Access database, but after packing, I was told that I could not find the module

Of course, I searched the Internet all day and couldn’t find a solution. Not only that, but the logging plugin for log-4 also has this problem

Finally I found a solution.

  1. First: Check to see if your plug-in is producing dependencies
  2. Second: if you use window.require to import a third party plugin in a production dependency process, the dependency will not be found after packaging, so you need to remove the require module from the background main process file
  3. Third: some plug-ins cannot find modules due to ASAR after packaging

The specific writing location is as follows:

// The configuration here uninstalls vue.config.js
pluginOptions: {
        electronBuilder: {
        builderOptions: {
            productName: 'Store Kanban'.appId: '123'.win: {
                icon: './public/desttop.ico'
             },
            publish: [{provider: 'generic'.url: 'xxxxx'}]."nsis": {
              "oneClick": false.// Whether to install with one click
                "perMachine": true."allowElevation": true.// Allow request promotion. If false, the user must restart setup with the promoted permissions.
              "allowToChangeInstallationDirectory": true.// Allows you to change the installation directory
                "installerSidebar": './public/installer.bmp'.// "installerIcon": './public/desttop.ico',// Install icon
              // "uninstallerIcon": './public/desttop.ico',// Uninstall icon
              // "installerHeaderIcon": './public/desttop.ico', // Install header icon
              "createDesktopShortcut": true.// Create a desktop icon
              "createStartMenuShortcut": true.// Create start menu icon
              "shortcutName": "Store Board".// Icon name
              / / "include" : "build/script/installer. NSH, / / contains custom nsis script}}}},Copy the code

Other basic configurations

The basic configuration of electron, I posted the code here, you want to know, go to see some of the official introduction of electron, I won’t go into the details.

Set the tray

const setAppTray = () = > {
    // The tray object
    let appTray = null;
    // System tray right click menu
    let trayMenuTemplate = [
        {
            label: 'exit'.click: function() {
                // ipc.send('close-main-window');
                // isQuit = true;
                app.quit()
                // win.close('close')
                // console.log(' Executed... ')
                // win.on('closed', () => {})}}];// System tray icon directory
    // let trayIcon = path.join(__dirname, '.. /public')
    // const iconPath = path.join(__static, './logo.png');

    appTray = new Tray(`${__static}/logo.png`)
    // Icon context menu
    const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)

    appTray.on('click'.function (Event) {
        win.show();
    })

    // Set the hover prompt for this tray icon
    appTray.setToolTip('Neighborhood Medicine Quick Cure')

    // Sets the context menu for this icon
    appTray.setContextMenu(contextMenu)
}
Copy the code

The hidden menu

function createMenu() {
   // Darwin stands for macOS, for macOS Settings

   if (process.platform === 'darwin') {
       const template = [
       {
           label: 'App Demo'.submenu: [{role: 'about'
               },
               {
                   role: 'quit'}}]]let menu = Menu.buildFromTemplate(template)
       Menu.setApplicationMenu(menu)
   } else {
       // Windows and Linux
       Menu.setApplicationMenu(null)}}Copy the code

Configure createWindow and turn devTools on and off

This includes:

  • Open the maximize window
  • Use F12 to open debugging tools and so on
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;
    });
    // Remove the menu
    createMenu()
    // Set the tray
    setAppTray()
}
Copy the code

Write in the end: I made a public account green lemon reading club, write some of my own reading feelings, if you have a little interest, pay attention to it, progress together.