electron-vue开发总结与踩坑经验(创建、mac、自动更新、日志)
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的ipcMain
和ipcRenderer
模块进行进程间通信。下面是一个简单的示例:
在主进程中(例如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放在国外的服务器上,国内环境访问很慢而导致下载失败,因此需要配置镜像。找到 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 慎用!!!
首先我们要先加载一个包用于获取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',"开始检查更新")
总结
以上两种方法都可以实现日志的输出,需要使用哪一种根据个人需求
更多推荐
所有评论(0)