electron-vue 笔记
1、安装@vue/cli 新版本,如果有旧版本,删除后重新安装
mac 下出现一下报错的解决办法:
cd /用户/用户名/ 然后 command + shift + . 命令显示隐藏文件 找到 .vuerc 文件删除
ERROR ~/.vuerc may be outdated. Please delete it and re-run vue-cli in manual mode.
2、用 vue/cli 创建vue项目
3、vue add electron-builder
4、项目搭建好后:在不同系统下执行yarn electron:build打包就生成对应系统的安装包
问题一:打包下载慢
推荐从这里下载对应链接版本electron 安装包(这里边有多种版本的安装包,推荐手动下载)
mac:把下载好的安装包放到 ~/资源库/Caches/electron/
下,注意放的是zip文件,不是解压后的文件,然后在执行打包,就很流畅了
window: 同样把下载好的安装包,放在%LOCALAPPDATA%\electron\cache
下,需要注意的是,同样要把对应的SHASUMS256.txt-文件也下载下来放进去, 可以去这篇文章中看看(😂:主要是文章是做完后写的,忘记当时的过程了,不过大体上是一样的,下载各种文件到\electron-builder\Cache\
)
问题二:更换图标
mac:在根目录下放一个png图片(512 X 512 或者 256 X 256)在执行打包就ok了
window:根目录下放一个favicon.ico
(256 X 256)图标就ok
问题三:托盘功能(整体代码放在最后)
mac:消息数添加:
var messageCount = 0
tray.setTitle(messageCount.toString()) // 设置托盘图标上的消息数方法
app.dock.setBadge(messageCount.toString()) // 设置app图标上的消息数方法
window:定时器设置图标闪烁就好
问题四:检查自动更新功能(整体代码放在最后)
自动更新用到了electron-updater 中的 autoUpdater 模块
mac要想自动更新,需要打包签名,本人没有开发者账号,所以用的方法比较low,就是把打包后生的 latest-mac.yml
(window是latest.yml
)文件放在能访问的服务器上,然后写了一个静态页面(内容就是window、mac的下载包链接),代码中检查是否是最新版本,不是的话去静态页面下载
具体代码:
background.js
'use strict'
import { app, protocol, BrowserWindow, globalShortcut, Menu, Tray, MenuItem, dialog, shell} from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const { autoUpdater } = require('electron-updater')
const uploadUrl = 'https://cdn9.xxx.cn/download' // 安装包latest.yml所在服务器地址
const isDevelopment = process.env.NODE_ENV !== 'production'
const path = require('path')
const ipcMain = require('electron').ipcMain
let win = null
let forceQuit = false;
let tray
let flashTrayTimer
let iconPath = path.join(__static, './icon.png');
let iconEmptyPath = path.join(__static, './empty.png');
let messageCount = 0
global.appToken = { // 全局数据
chat_token: '',
jwt_token: '',
now_time: new Date().getTime()
}
function sendUpdateMessage(win, text) { // 发送更新消息
win.webContents.send('updata-message', text)
}
const updateHandle = (win) => { // 自动更新事件
let message = {
error: { status: -1, msg: '检测更新查询异常', error: '' },
checking: { status: 0, msg: '正在检查更新...' },
updateAva: { status: 1, msg: '检测到新版本' },
updateNotAva: { status: 2, msg: '您现在使用的版本为最新版本,无需更新!' },
}
autoUpdater.setFeedURL(uploadUrl)
//在下载之前将autoUpdater的autoDownload属性设置成false,通过渲染进程触发主进程事件来实现这一设置
autoUpdater.autoDownload = false;
// 检测更新查询异常
autoUpdater.on('error', function (err) {
sendUpdateMessage(win, {...message.error, error: err})
})
// 当开始检查更新的时候触发
autoUpdater.on('checking-for-update', function () {
sendUpdateMessage(win, message.checking)
})
// 当发现有可用更新的时候触发,更新包下载会自动开始
autoUpdater.on('update-available', function (info) {
// 主进程向renderer进程发送是否确认更新
win.webContents.send('isUpdateNow', info)
sendUpdateMessage(win, message.updateAva)
})
// 当发现版本为最新版本触发
autoUpdater.on('update-not-available', function (info) {
sendUpdateMessage(win, 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() // 包下载完成后,重启当前的应用并且安装更新
})
// 收到renderer进程确认更新(暂时不用,让用户跳转到浏览器中下载)
ipcMain.on('updateNow', (e, arg) => {
// autoUpdater.downloadUpdate(); // 这是自动更新的代码
dialog.showMessageBox({
type: 'info',
title: '温馨提示',
cancelId:2,
defaultId: 0,
message: '检测到新的更新版本,请到浏览器中下载!',
buttons: ['取消','去下载']
}).then(result => {
if (result.response == 0) {
e.preventDefault(); //阻止默认行为,一定要有
} else if(result.response == 1) {
shell.openExternal('https://www.jjl.cn/zt/kefu_download/index.html') // 地址为安装包地址
}
})
})
ipcMain.on('checkForUpdate', () => {
// 收到renderer进程的检查通知后,开始检查更新
autoUpdater.checkForUpdates()
})
}
const appTray = {
// 创建托盘图标
createTray() {
tray = new Tray(iconPath)
const menu = Menu.buildFromTemplate([
{
label: '页面刷新',
click: () => {
if(win) {
win.show()
win.focus()
forceQuit = false
win.reload()
}
}
},
{type: 'separator'},
{
label: '退出',
click: () => {
dialog.showMessageBox({
type: 'info',
title: 'Information',
cancelId:2,
defaultId: 0,
message: '确定要关闭吗?',
buttons: ['取消','直接退出']
}).then(result => {
if (result.response == 0) {
e.preventDefault(); //阻止默认行为,一定要有
} else if(result.response == 1) {
win.webContents.send('logout')
}
})
}
},
])
tray.setContextMenu(menu)
tray.setToolTip('xxx工作台')
// 托盘点击事件
tray.on('click', () => {
if(win) {
win.show()
win.focus()
forceQuit = false
}
})
},
// 托盘图标闪烁
flashTray(flash) {
let hasIco = false
if (flash) {
if (flashTrayTimer) return
flashTrayTimer = setInterval(() => {
tray.setImage(hasIco ? iconPath : iconEmptyPath)
hasIco = !hasIco
}, 500)
} else {
if (flashTrayTimer) {
clearInterval(flashTrayTimer)
flashTrayTimer = null
}
tray.setImage(iconPath)
}
},
// 销毁托盘图标
destroyTray() {
this.flashTray(false)
tray.destroy()
tray = null
}
}
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
async function createWindow() {
win = new BrowserWindow({
width: 1200,
height: 1000,
minHeight: 700,
minWidth: 1000,
autoHideMenuBar: true,
title: 'xxx工作台',
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
preload: path.join(__dirname, 'preload.js'),
}
})
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const mywin = BrowserWindow.fromWebContents(webContents)
mywin.setTitle(title)
})
ipcMain.on('open-url', (event,url) => { // 取消a标签跳转,改为事件跳转,该方法是跳出electron 打开你电脑设置的默认浏览器
shell.openExternal(url)
})
ipcMain.on('down-load-url', (event, url) => { // electron 自带的下载弹框
win.webContents.downloadURL(url)
})
ipcMain.on('set-timer', () => {
win.webContents.send('timer')
})
ipcMain.on('window-focus', () => {
if(win) {
win.show()
win.focus()
forceQuit = false
}
})
ipcMain.on('err-info', () => {
win.flashFrame(true)
win.webContents.send('play-offline-video')
})
ipcMain.on('asynchronous-message', () => {
win.flashFrame(true)
})
ipcMain.on('message-notification', (event, type) => {
if (process.platform !== 'darwin') {
appTray.flashTray(true)
}
if(type === 'inCome') {
// 。。。。
} else if (type === 'message') {
messageCount += 1
if(process.platform === 'darwin') {
tray.setTitle(messageCount.toString())
app.dock.setBadge(messageCount.toString())
}
}
})
ipcMain.on('logout',() => {
win = null;
if(appTray) {
appTray.destroyTray()
}
app.exit(); //exit()直接关闭客户端,不会执行quit();
})
// 按f12 打开调试模式
win.webContents.on('before-input-event', (event, input) => {
if (input.key.toLowerCase() === 'f12') {
win.webContents.toggleDevTools()
event.preventDefault()
}
})
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')
}
appTray.createTray()
win.on('close', (e)=>{ // 关闭窗口弹框
e.preventDefault()
if (process.platform !== 'darwin') {
dialog.showMessageBox({
type: 'info',
title: '温馨提示',
cancelId:2,
defaultId: 0,
message: '确定要关闭吗?',
buttons: ['取消','直接退出']
}).then(result => {
if (result.response == 0) {
e.preventDefault(); //阻止默认行为,一定要有
} else if(result.response == 1) {
win.webContents.send('logout')
}
})
} else {
if (!forceQuit) {
win.hide()
forceQuit = true
}
}
})
if(process.platform !== 'darwin') {
win.on('session-end', (e)=>{ //因为强制关机或机器重启或会话注销而导致窗口会话结束时触发 window
// e.preventDefault()
win.webContents.send('logout')
})
}
updateHandle(win)
}
// 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(win) {
win.show()
win.focus()
forceQuit = false
}
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 () => {
createWindow()
globalShortcut.register('CommandOrControl+R', () => { // 禁止刷新
return false
})
})
app.whenReady().then(() => {
app.on('browser-window-focus', () => { // 页面展示、聚焦时的操作
win.flashFrame(false)
let time = new Date().getTime()
if(messageCount > 0) {
messageCount = 0
if( process.platform === 'darwin') {
tray.setTitle('')
app.dock.setBadge('')
} else {
appTray.flashTray(false)
}
}else {
if( process.platform !== 'darwin'){
appTray.flashTray(false)
}
}
})
})
// 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()
})
}
}
preload.js
预加载js ,这里可以放置你所需要的渲染进程于主进程交互的所有事件
const { ipcRenderer } = require('electron')
let timer = null
window.electronAPI = {
setTitle: (title) => { // 设置项目名称
ipcRenderer.send('set-title', title)
},
setTimer: () => { // 设置主进程定时器
if(timer) {
clearInterval(timer)
}
timer = setInterval(()=>{
ipcRenderer.send('set-timer')
}, 2000)
},
clearTimer: () => { // 清楚主进程定时器
if(timer) {
clearInterval(timer)
}
},
setMessage: () => { // 推送消息(进线、消息)
ipcRenderer.send('asynchronous-message')
},
setNotification: (type) => { // 推送通知
ipcRenderer.send('message-notification', type)
},
setLogout: () => { // 退出主进程
ipcRenderer.send('logout')
},
errorInfo: () => { // 断网、token超时等异常
ipcRenderer.send('err-info')
},
windoeFocus: () => { // 聚焦
ipcRenderer.send('window-focus')
},
checkForUpdate: () => { // 开始检查更新
ipcRenderer.send('checkForUpdate')
},
updateNow: () => { // 确认开始更新
ipcRenderer.send('updateNow')
},
openUrl: (url) => { // 默认浏览器打开链接
ipcRenderer.send('open-url', url)
},
downLoadUrl: (url) => { // 下载文件
ipcRenderer.send('down-load-url', url)
}
}
vue.config.js
vue 配置文件,里边包含了electron 打包的配置
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
productionSourceMap: false,
lintOnSave: false,
runtimeCompiler: true,
publicPath: './',
assetsDir: 'static',
filenameHashing: true,
configureWebpack: {
name: 'xxx工作台',
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'axios': 'axios',
'element-ui': 'ELEMENT'
}
},
pluginOptions:{
electronBuilder:{
preload:'src/preload.js',
builderOptions: {
"productName":"xxx工作台", //项目名 这也是生成的exe文件的前缀名
"appId": "com.xxx.app", //包名
"copyright":"1.0.1", //版权信息
"nsis": { //nsis相关配置,打包方式为nsis时生效
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升,如果为false,则用户必须使用提升的权限重新启动安装程序。
"perMachine": true,
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "favicon.ico", // 安装图标
"uninstallerIcon": "favicon.ico", //卸载图标
"installerHeaderIcon": "favicon.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true, // 创建开始菜单图标
"shortcutName": "xxx工作台", // 图标名称
},
"publish": [{ // 这个配置比较重要,是来放置你打包后生成的yml文件放置位置的配置
"provider": "generic",
"url": "http://127.0.0.1:8080/"
}],
"dmg": {
"title": "xxx工作台"
},
"win": {
"icon": "favicon.ico", // window 必须为ico格式
"target": [
{
"target": "nsis", //使用nsis打成安装包,"portable"打包成免安装版
"arch": [
"ia32", //32位
"x64" //64位
]
}
]
},
"mac": {
"icon": "icon.png" // mac图标
}
}
}
},
devServer: {
port: 5300,//端口号
}
})
渲染进程与主进程交互、检查更新,读取全局变量、设置全局变量的方法
let ipcRenderer = window.require('electron').ipcRenderer
const remote = window.require('electron').remote
mounted () {
window.electronAPI.setTitle('xxx工作台') // 渲染进程发送消息到主进程 设置项目名称
window.electronAPI.clearTimer() // 渲染进程发送消息到主进程 清除定时器交互
window.electronAPI.checkForUpdate() // 渲染进程发送消息到主进程 检查更新交互
remote.getGlobal('appToken').now_time = new Date().getTime() // 设置全局变量
ipcRenderer.on('logout', () => { // 监听主进程发过来的消息方法 退出登录
window.electronAPI.setLogout()
})
ipcRenderer.on('updata-message', (e,msg) => { // 监听主进程发过来的消息方法 更新状态
console.log(msg, '-----------------')
if(msg.status === 1) {
window.electronAPI.updateNow() // 自动更新
}
})
ipcRenderer.on('downloadProgress', (e, msg) => { // 监听主进程发过来的消息方法 下载进度
console.log(msg, '------下载进度-----')
})
}
笔记:引用别人的文章记录一下
electron的镜像在国外,下载的时候很慢,有时候还会出现下载不成功的情况。
为了解决这个问题,我们可以把镜像改成国内的。
1.先清空缓存,再把npm改成改成国内镜像
#清空缓存
npm cache clean -f
#设置华为镜像源
npm config set registry https://mirrors.huaweicloud.com/repository/npm/
2.把浏览器引擎驱动镜像改成国内镜像
npm config set chromedriver_cdnurl https://mirrors.huaweicloud.com/chromedriver
3.把electron镜像改成国内镜像
npm config set electron_mirror https://mirrors.huaweicloud.com/electron/
4.把electron_builder_binaries镜像改成国内镜像
npm config set electron_builder_binaries_mirror https://mirrors.huaweicloud.com/electron-builder-binaries/
5.如果安装了yarn,把yarn镜像也改成国内镜像
yarn config set registry https://mirrors.huaweicloud.com/repository/npm/