Electron-Vue开发总结与踩坑经验

一、Electron介绍

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。通过将Chromium和Node.js嵌入到其二进制文件中,Electron 允许您维护一个 JavaScript 代码库并创建可在 Windows、macOS 和 Linux 上运行的跨平台应用程序——无需原生开发经验。

这里附上Elcetron官方文档

我开发的这个项目本来是一个Web端的应用,但是需求更改,需要检测用户电脑上的一些数据,保证文件安全性,所以临时改为了使用Electron这个框架,主要是配置有点复杂,页面使用vue写是一样的。

1、通信原理

Electron通讯是在主进程与渲染进程之间

安装后,需要配置一个主进程,如果是新建的项目,大部分是自己配置的main.js。我的是在原项目里面安装的Electron,所以自己生成了background.js作为主进程。

**主进程(background.js)**代码如下,有其他需求可以自行配置

'use strict'

import { app, protocol, BrowserWindow} from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])

async function createWindow() {
  // Create the browser window.
  const win = new BrowserWindow({
    width: 1100,
    height: 750,
    
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION
    }
  })
  
  // 开发环境
  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    // 生产环境
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.

app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
      await installExtension(VUEJS_DEVTOOLS)
    } catch (e) {
      console.error('Vue Devtools failed to install:', e.toString())
    }
  }
  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()
    })
  }
}

渲染进程就是你写的页面,入口一般是main.js或者是renderer.js如果在页面中需要使用一些主进程中的配置,比如说获取安装位置,就需要使用通讯方法。

常见的方法是使用Electron的ipcMainipcRenderer模块进行进程间通信。下面是一个简单的示例:

在主进程中(例如main.js

const { app, ipcMain } = require('electron');

// 监听从渲染进程发送过来的消息
ipcMain.on('getAppPath', (event) => {
  const appPath = app.getAppPath();
  
  // 发送应用程序路径给渲染进程
  event.sender.send('appPath', appPath);
});

在渲染进程中(例如renderer.js):

const { ipcRenderer } = require('electron');

// 请求应用程序路径
ipcRenderer.send('getAppPath');

// 监听来自主进程的应用程序路径
ipcRenderer.on('appPath', (event, appPath) => {
  console.log(appPath);
  // 在这里可以使用应用程序路径进行其他操作
});

2、开始开发前的注意事项

Electron会使用到Nodejs的东西,对Nodejs不了解的同学可以先去学习一下Node的写法。

并且开发过程中会涉及到es6和commonJs的区别

这里推荐一篇博客供大家参考

https://www.jb51.net/article/278533.htm

二、通过Vue-Cli创建一个Electron

1、安装Vue-Cli

在安装Vue-Cli前请确保已经有了node环境

cmd进入控制台,任何文件位置都可以,输入下方指令,全局安装

npm install -g @vue/cli

2、创建项目

脚手架安装好以后,开始创建项目

首先进入你项目需要存放的文件路径,在该路径下打开控制台,输入vue create your-project-name,your-project-name是你的项目名称。

切记!!!!!这个路径不能包含中文,有中文就无法打包,我被这个问题困扰了很久,换了全英文路径就好了。

vue create your-project-name

选择你想要创建的版本,回车,vue2/vue3都行,我这里是vue2的。
在这里插入图片描述
和下面这个图一样就是创建好了,可以cd进入这个文件夹,然后npm run serve运行试试看。
在这里插入图片描述

3、安装electron-builder插件

在刚刚创建的项目目录下,打开控制台,输入

vue add electron-builder

electron-builder官网

进行安装electron-builder插件, 因为electron放在国外的服务器上,国内环境访问很慢而导致下载失败,因此需要配置镜像。找到 npm 的配置文件 .npmrc ,加入如下代码到配置文件中:

electron_mirror=https://npmmirror.com/mirrors/electron/

不加入也可以,需要耐心等待一下。
在这里插入图片描述
这里我直接选择的最新版,可以根据自己需要选择版本
在这里插入图片描述
这样就是已经安装好了,可以使用vscode或其他编辑器打开该项目。

项目目录如下:
在这里插入图片描述

4、运行与打包

package.json文件中必须有main这一项,这是主进程的入口文件,我这里面是background.js,可以根据个人需要修改。

在这里插入图片描述

本地运行该项目:

npm run elecron:serve

出现这个窗口就是运行成功了,旁边的一栏和在浏览器中的控制台一模一样
在这里插入图片描述

项目打包

npm run electron:build

控制台出现如下信息后表示打包成功,
在这里插入图片描述

安装包在dist_electron文件夹下,至此,一个ELectron-vue项目就创建完成了。
在这里插入图片描述

三、获取主机mac 慎用!!!

getmac()API文档

首先我们要先加载一个包用于获取mac地址

npm install getmac

在需要获取mac的地方这样使用

import getMAC, { isMAC } from 'getmac'

// Fetch the computer's MAC address
console.log(getMAC())

// Fetch the computer's MAC address for a specific interface
console.log(getMAC('eth0'))

// Validate that an address is a MAC address
if (isMAC('e4:ce:8f:5b:a7:fc')) {
    console.log('valid MAC')
} else {
    console.log('invalid MAC')
}

四、软件安装后自动更新

业务场景:用户安装软件以后,不需要再从网上下载。当有新版本以后,自动通知用户,让用户选择是否更新,确定更新会自动重新安装软件。

1、安装electron-updater插件

npm install electron-updater --save

2、关键代码

updateApp.js

在合适的位置添加updateApp.js,下面的代码是通用的,可以直接拿过去用

/**
 * 自动更新
 */
const {ipcMain} = require('electron')
const build = require("../../vue.config")
// 注意这个autoUpdater不是electron中的autoUpdater
const {autoUpdater} = require('electron-updater')
const path = require('path')
const fs = require('fs-extra')
const config = require('../../package.json')

// 更新地址
const updateURL = build.pluginOptions.electronBuilder.builderOptions.publish.url // 安装包下载地址

// 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写
function handleUpdate(mainWindow) {
  // 若执行删除操作,每次检查更新都会重新下载更新包,
  // 若不执行删除操作,在已有更新包的情况下,会直接跳过下载事件,直接进行安装操作
  deleteUpdate();

  const message = {
    error: {status: -1,msg:'更新出错'},
    checking: {status: 0,msg:'正在检查更新……'},
    updateAva: {status: 1,msg:'检测到新版本'},
    updateNotAva: {status: -1,msg:'已经是最新版本'},
    updateDownload:{status: 2,msg: '正在下载'}
  }
  // 设置版本更新服务器地址
  autoUpdater.setFeedURL(updateURL)
  // 设置是否自动下载,默认是true,当点击检测到新版本时,会自动下载安装包,所以设置为false
  autoUpdater.autoDownload = false 
  // 如果安装包下载好了,当应用退出后是否自动安装更新
  autoUpdater.autoInstallOnAppQuit = true;  

  // 自动执行更新检查
  setTimeout(() => {
    autoUpdater.checkForUpdates();
  }, 100);

  // 更新发生错误时触发
  autoUpdater.on('error', function() {
    sendUpdateMessage(message.error)
  })

  // 开始检查更新事件
  autoUpdater.on('checking-for-update', function() {
    sendUpdateMessage(message.checking)
  })

  // 没有可更新版本
  autoUpdater.on('update-not-available', function(info) {
    sendUpdateMessage(message.updateNotAva)
  })

  // 发现可更新版本
  autoUpdater.on('update-available', function(info) {
    // 获取当前版本信息
    sendUpdateMessage(message.updateAva)
    mainWindow.webContents.send('update-available',info);
  })

  // 更新下载进度事件
  autoUpdater.on('download-progress', function(progressObj) {
    let info = {
      bytesPerSecond: progressObj.bytesPerSecond,
      percent: progressObj.percent,
      transferred: progressObj.transferred,
      total: progressObj.total
    }
    mainWindow.webContents.send('downloadProgress', info)
  })

  // 下载监听
  autoUpdater.on('update-downloaded', function(event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
    let data = {
      releaseDate,
      releaseName,
      releaseNotes,
      updateUrl,
      quitAndUpdate
    }
    // autoUpdater.quitAndInstall();
    // callback()
    // 接收到立即更新的信号,退出程序并更新
    ipcMain.on('isUpdateNow', (e, arg) => {
      // 3秒后退出并安装,可控制
      setTimeout(()=>{
        autoUpdater.quitAndInstall();
      },3000)      
    })
    mainWindow.webContents.send("isUpdateNow",data)
  })
  
  // 检查更新
  ipcMain.on('checkForUpdate', () => {
    // 执行自动更新检查
    autoUpdater.checkForUpdates()
  })

  // 下载
  ipcMain.on('downloadUpdate', () => {
    autoUpdater.downloadUpdate()
  })

  // 立即安装
  ipcMain.on('handleUpdateNow', (e, arg) => {
    console.log("开始安装")
    // 3秒后退出并安装,可控制
    setTimeout(()=>{
      autoUpdater.quitAndInstall();
    },3000)      
  })


  // 向渲染进程发送消息
  function sendUpdateMessage(text){
    mainWindow.webContents.send("message",text)
  }

}

// 更新前先删除本地已经下载的更新包文件
function deleteUpdate(){
  let updateCacheDirName = "sdp-desktop-client-updater"
  // 更新包下载路径
  const updatePendingPath = path.join(autoUpdater.app.baseCachePath,updateCacheDirName,'pending');
  // 删除本地安装包
  fs.emptyDir(updatePendingPath)
}

module.exports = {
  handleUpdate,
}

vue页面中

你可以在单独的组件或app.vue中添加代码,实现主进程与渲染进程通信,从而执行更新代码。我这里使用了element ui,使用其他ui的可以换成其他的,具体代码如下:

<script>
	import config from "../../package.json"
	const build = require("../../vue.config")
	import { ipcRenderer } from "electron";
	export default {
        data(){
            return{
                version: config.version,
				percent: 0,//进度条
                progressDialogVisible:false,
            }
        },
        mounted() {
            // 主进程返回检查状态
            ipcRenderer.on("message",(e,data) => {
				// txtConsole.log('info',data.msg)
                switch(data.status){
                    // 检查更新出错 or 已经是最新版本
                    case -1:
                        this.$message.error(data.msg);
                        break;
                    // 正在检查更新
                    case 0:
                        this.$message({
                            message: data.msg,
                            type:"warning"
                        })
                        break;
                    // 检测到新本版
                    case 1:
                        this.$confirm("检测到新版本,是立即下载","提示",{
                            closeOnClickModal: false, // 禁止点击遮罩关闭弹框
                            closeOnPressEscape: false, // 禁止按 ECS 关闭弹框
                            confirmButtonText: '确定',
                            cancelButtonText: '取消',
                            type: 'warning',
                            }).then(()=>{
                                // logger.info("确定下载新版本")
                                ipcRenderer.send('downloadUpdate')
                            }).catch(()=>{
                                // logger.info("取消下载新版本")
                                this.$message({                               
                                message: "取消下载",
                                type:"warning"
                                })
                            })
                    // 正在下载
                    case 2:
                        this.$message({
                            message: data.msg,
                            type:"warning"
                        });
                }
            });
			// 有可用更新包
            ipcRenderer.on("update-available",(e,info) => {
                // 获取当前版本信息
                console.log("当前版本=",config.version)
                console.log("info--->",info)
            })
            // 更新进度
            ipcRenderer.on('downloadProgress',(e,progressObj) => {
				this.progressDialogVisible=true//进度条的弹出框显示
                console.log("progressObj--->",progressObj);              
                this.percent = (progressObj.percent).toFixed(2) || 0;

                console.log("this.percent---->",Math.trunc(this.percent))

                if(Math.trunc(this.percent) === 100){
					this.progressDialogVisible=false
                }
            });

            // 是否立即下载
            ipcRenderer.on('isUpdateNow',(e,data)=>{
                console.log("data---->",data);
                this.$confirm('下载已完成,是否立即安装','提示',{
                    closeOnClickModal: false, // 禁止点击遮罩关闭弹框
                    closeOnPressEscape: false, // 禁止按 ECS 键关闭弹框
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }) .then(()=>{
                    // logger.info("下载完成,立即安装")
                    ipcRenderer.send('isUpdateNow');
                    this.$message({
                        type: 'success',
                        message: '开始安装'
                    })
                }).catch(()=>{
                    // logger.info("下载完成,取消安装")
                    this.$message({
                        type:'info',
                        message: '已取消安装'
                    })
                })
            });
        },
        methods: {
        	// 下面方法点击按钮开始检查更新,
            // 若要实现应用打开就开始检查更新,将此文件写在项目的app.vue页面,执行这个方法即可!
            //如果在updaeApp.js中已经设置了自动执行更新检查,下面这三行代码,就不需要使用CheckUpdate()了
  			//setTimeout(() => {
    			//autoUpdater.checkForUpdates();
  			//}, 100);
            CheckUpdate() {
                ipcRenderer.send("checkForUpdate")
                console.log("url--->",build.pluginOptions.electronBuilder.builderOptions.publish.url);
            },
            // 手动下载
            downloadUpdate(){ 
                ipcRenderer.send("downloadUpdate")
            },          
   		}
    }
</script>

3、各文件配置

打包配置中添加

publish:{
	provider:"generic",
	// url:"http://xxx.xxx.xxx.xxx/xxx/xxx", 
	// 升级包在服务器地址,不用指向具体的升级包文件
	// 在本地起的http服务器
	rl: "http://127.0.0.1:8888/update/"
},

这个打包配置应该在package.json或者vue.config.js

我的是在vue.config.js里面,付上我的代码

module.exports = {
	// transpileDependencies: true,
  	lintOnSave: false, // 引入插件不使用时不报错
  	configureWebpack: {
    	externals: {
      		'electron': 'require("electron")'
    	}
  	},
	// 第三方插件配置
	pluginOptions: {
		// vue-cli-plugin-electron-builder 配置
		electronBuilder: {
		  nodeIntegration: true,
		  // 设置应用主进程的入口
		  mainProcessFile: "src/background.js",
		  // 设置应用渲染进程的入口
		//   rendererProcessFile: "src/main.js",
		//   customFileProtocol: "../",
		  // 打包选项
		  builderOptions: {
			// 解决的问题:在安装到电脑后,系统通知无法工作
			appId: "plm.front.client", // 软件id
			productName: "PLM看图", // 打包后的名称
			extraResources: [//获取注册表的配置,可以不要
				{
					"from": "node_modules/regedit/vbs",
					"to": "vbs",
					"filter": [
						"**/*"
					]
				}
			],
			// windows系统相关配置
			win: {
			  // 应用图标路径(Windows 系统中 icon 需要 256 * 256 的 ico 格式图片)
			  icon: "src/assets/login-icon.png",
			  target: {
				target: "nsis",
				// 支持 64 位的 Windows 系统
				arch: ["x64"],
			  },
			},
			nsis: {
			  // 如果为false,想要给电脑所有用户安装必须使用管理员权限
			  allowElevation: true,
			  // 是否一键安装
			  oneClick: false,
			  // 允许修改安装目录
			  allowToChangeInstallationDirectory: true,
			  "guid": "plm.front.client", // 软件id
			  "include": "./installer.nsh"
			},
			publish:{
				provider:"generic",
					// url:"http://xxx.xxx.xxx.xxx/xxx/xxx", 
					// 升级包在服务器地址,不用指向具体的升级包文件
					// 在本地起的http服务器
				url: "http://127.0.0.1:8888/update/"
			   },
		},
	},
  },
}

主进程配置

import autoUpdate from "./utils/updateApp"

function createWindow(){
	...
    autoUpdate.handleUpdate(mainWindow)// 在createWindow()或下方app的ready阶段添加都可以,选择一种即可
}

app.on("ready",()=>{
	createWindow();    
	// 执行自动更新
	//autoUpdate.handleUpdate(mainWindow)
})

4、本地测试

首先打包一个高版本的安装包,新建一个文件夹,必须包含下面这三个文件
在这里插入图片描述

在update文件夹的上一级,也就是Documents文件夹下打开cmd,输入这条指令

// 端口号 8888
npx http-server -p 8888

执行后会出现下图所示:
在这里插入图片描述

然后在浏览器中打开这连接,然后在链接上加上/updata,也就是输入http://127.0.0.1:8888/update/ 这个网址,就能看到下面这个
在这里插入图片描述

点击latest.yml文件就可以看到文件内容,这个插件的原理就是通过对比线上的latest.yml文件内容与本地的内容,如果不一样,就更新。

然后在本地安装一个版本较低的安装包,打开软件会显示
在这里插入图片描述
在这里插入图片描述

至此,自动更新就算是成功了。

五、将日志信息保存在安装目录

1、方法一(electron-log)

安装electron-log模块

npm i electron-log	

新建log.js文件

import logger from 'electron-log'
import {remote} from 'electron'
 
logger.transports.file.level = 'debug'
logger.transports.file.maxSize = 1002430 // 最大不超过10M
logger.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}' // 设置文件内容格式
let date = new Date()
date = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
logger.transports.file.fileName = date + '.log' // 创建文件名格式为 '时间.log' (2023-02-01.log)
 
 // 可以将文件放置到指定文件夹中,例如放到安装包文件夹中
 const path = require('path')
 const exePath = path.dirname(remote.app.getPath('exe')) // 获取到安装目录的文件夹名称
 // 指定日志文件夹位置
 Logger.transports.file.resolvePath = ()=> exePath+'\\'+'log\\'+date+'.log'

// 有六个日志级别error, warn, info, verbose, debug, silly。默认是silly
export default {
  info (param) {
    logger.info(param)
  },                   
  warn (param) {
    logger.warn(param)
  },
  error (param) {
    logger.error(param)
  },
  debug (param) {
    logger.debug(param) 
  },
  verbose (param) {
    logger.verbose(param)
  },
  silly (param) {
    logger.silly(param)
  }
}

在需要的页面引用

import logger from './log.js'

logger.info('this is message')

2、方法二(通过fs模块操作文件)

该方式较为麻烦,适合无法使用electron-log的情况

新建log.js文件

/**
 * 日志
 */
const fs = require('fs');
const moment = require('moment');
 // 可以将文件放置到指定文件夹中,例如放到安装包文件夹中
const { app } = require('electron');
 const path = require('path');
const exePath = path.dirname(app.getPath('exe'));// 获取到安装目录的文件夹名称
 let date = new Date()
date = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
 // 指定日志文件夹位置
 let dirPath = exePath+'\\'+'logs'
 let logPath = exePath+'\\'+'logs\\'+date+'.txt'
const logger = (level = '', mes = '')=>{
  let message = `\n [${moment().format('Y-MM-DD HH:mm:ss')}] :[${level}]:${mes}`;
  if (!fs.existsSync(dirPath)) {
    fs.mkdirSync(dirPath, { recursive: true });
  }
  if (!fs.existsSync(logPath)) {
    fs.writeFileSync(logPath, '');
  }
  fs.appendFileSync(logPath, message);
};
//日志文件
module.exports = logger;

在主进程添加

const logger = require('./utils/log.js');
....
app.on('ready', async () => {
  ...
  createWindow()

  //监听渲染进程的消息
  ipcMain.on('logger', (event, ...arg) => {
    logger(arg[0],arg[1])
  });
})
....
//主进程直接使用
logger('info',"创建浏览器窗口成功")

在渲染进程使用

在渲染进程使用较为麻烦,因为无法获取到安装路径,因此需要进行通信获取路径,在使用的页面添加下面的代码

import { ipcRenderer } from "electron";
//在需要输出日志的地方使用
ipcRenderer.send("logger",'info',"开始检查更新")

总结

以上两种方法都可以实现日志的输出,需要使用哪一种根据个人需求

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐