electron-vue 笔记

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/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值