如何用Vue開(kāi)發(fā)Electron桌面程序? 這篇就夠了!

示例項(xiàng)目地址: https://github.com/klren0312/electronTemplate
分條查看相關(guān)積累知識(shí): https://github.com/klren0312/daliy_knowledge/issues

一、Electron介紹

Electron 是一個(gè)由 GitHub 及眾多貢獻(xiàn)者組成的活躍社區(qū)共同維護(hù)的開(kāi)源項(xiàng)目.
使用 JavaScript,HTML 和 CSS 構(gòu)建跨平臺(tái)的桌面應(yīng)用程序

1. 特點(diǎn)

  • 跨平臺(tái)
    可以打包成Mac胡岔、Windows 和 Linux三個(gè)平臺(tái)的應(yīng)用程序

  • 簡(jiǎn)化桌面端開(kāi)發(fā)
    (1)Electron 基于 Chromium 和 Node.js,可以使用 HTML, CSS 和 JavaScript 構(gòu)建應(yīng)用
    (2)提供Electron api 和 NodeJS api

  • 社區(qū)活躍

2. 兼容性

xp無(wú)緣了, 可能需要使用nwjs等方案

image.png

二仗嗦、項(xiàng)目搭建

1. 使用 vue cli 創(chuàng)建vue項(xiàng)目

vue create electron-test

2. 安裝插件 vue-cli-plugin-electron-builder

vue add electron-builder
image.png

3. 安裝完插件后, 項(xiàng)目中的一些變化

① package.json 新增了幾個(gè)scripts

image.png
npm run electron:serve  electron開(kāi)發(fā)模式
npm run electron:build   electron打包

postinstall 和 postuninstall 是為了確保安裝或者移除依賴(lài)時(shí), 始終跟electron匹配

② 新增了background.js文件

主進(jìn)程相關(guān)操作, 寫(xiě)在這個(gè)文件中


image.png

③ 新增了一個(gè)環(huán)境變量

可以用來(lái)判斷是否在electron狀態(tài)

process.env.IS_ELECTRON

三雪标、開(kāi)發(fā)總結(jié)

1. 配置項(xiàng)目圖標(biāo)

參考文檔: https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#icons

使用electron-icon-builder, 生成符合Electron的圖標(biāo)

① 安裝

npm install --save-dev electron-icon-builder

package.json 中配置生成命令

"electron:generate-icons": "electron-icon-builder --input=./public/icon.png --output=build --flatten"

③ 生成圖標(biāo)

npm run electron:generate-icons

④ 使用

import path from 'path'
const win = new BrowserWindow({
  icon: path.join(__static, 'icon.png')
})

2. 項(xiàng)目開(kāi)發(fā)模式運(yùn)行出現(xiàn) Failed to fetch extension 警告

由于網(wǎng)絡(luò)問(wèn)題, 開(kāi)發(fā)模式無(wú)法下載 vue devtool 導(dǎo)致的警告,
需要在 background.js 中注釋掉下載代碼


image.png

3. 項(xiàng)目使用本地的vue devtools

① 首先可以將vue devtools的代碼clone下來(lái), 然后進(jìn)行編譯

git clone https://github.com/vuejs/vue-devtools.git
cd vue-devtools
npm install
npm run build

然后把vue-devtools/packages/shell-chrome文件夾復(fù)制到項(xiàng)目根目錄

② 在background.js文件的app.on('ready',生命周期中進(jìn)行加載

// 使用本地的vue開(kāi)發(fā)者工具
session.defaultSession.loadExtension(path.resolve('shell-chrome'))

③ 創(chuàng)建窗口的時(shí)候使用下面示例方法, 即可正常顯示出vue開(kāi)發(fā)者工具

// src/background.js
if (process.env.WEBPACK_DEV_SERVER_URL) {
  await transferWin.loadURL(
    process.env.WEBPACK_DEV_SERVER_URL + '/#/test'
  )
  if (!process.env.IS_TEST) transferWin.webContents.openDevTools()
} else {
  transferWin.loadURL('app://./index.html' + '/#/test')
}

4. 渲染進(jìn)程中如何使用NodeJS api

需要在 vue.config.js 中配置 nodeIntegration 為 true

module.exports = {
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true
    }
  }
}

或者在創(chuàng)建窗口時(shí)配置

win = new BrowserWindow({
    width: 500,
    height: 400,
    frame: false,
    transparent: true,
    backgroundColor: '#00000000', // 當(dāng)關(guān)閉開(kāi)發(fā)者工具時(shí), 會(huì)重新創(chuàng)建一個(gè)新的渲染視圖, 所以會(huì)使用配置的背景顏色, 如果沒(méi)配置會(huì)使用默認(rèn)值白色
    webPreferences: {
      nodeIntegration: true, // 渲染層可以使用node
      webSecurity: false, // 跨域
      enableRemoteModule: true // 可以使用remote
    },
    // eslint-disable-next-line no-undef
    icon: path.resolve(__static, 'logo.png')
  })

5. 讓創(chuàng)建的窗口可以跨域

創(chuàng)建窗口的時(shí)候配置 webSecurity: false即可

6. 如何監(jiān)聽(tīng)窗口的狀態(tài), 最小化, 聚焦, 窗口隱藏, 窗口顯示, 窗口關(guān)閉

// 窗口最小化觸發(fā)
win.on('minimize', () => {
  console.log('最小化')
})
win.on('focus', () => {
  console.log('聚焦')
})
// 窗口隱藏, 任務(wù)欄沒(méi)有圖標(biāo)
win.on('hide', () => {
  console.log('隱藏')
})
win.on('show', () => {
  flashTray(false)
  console.log('顯示')
})

7. 如何創(chuàng)建托盤(pán)圖標(biāo)

參考文檔: https://www.electronjs.org/docs/api/tray#%E7%B3%BB%E7%BB%9F%E6%89%98%E7%9B%98

let tray = null
function createTray () {
  tray = new Tray(path.resolve(__static, ‘logo.png’)) // 設(shè)置托盤(pán)圖標(biāo)
  const contextMenu = Menu.buildFromTemplate([
     new MenuItem({
      label: '退出程序',
      click: () => {
        isQuit = true
        app.exit()
      }
    })
  ])
  tray.setContextMenu(contextMenu) // 設(shè)置右鍵菜單
  tray.on(‘click’, () => { // 托盤(pán)點(diǎn)擊事件
    if (win.isVisible()) {
      win.focus()
    } else {
      win.show()
    }
  })
}

監(jiān)聽(tīng)主窗口的關(guān)閉, 如果不是完全退出, 則只是隱藏窗口

win.on('close', e => {
    if (isQuit) {
      win = null
    } else {
      e.preventDefault()
      win.hide()
    }
})

8. 托盤(pán)閃爍與任務(wù)欄閃爍

image.png

① 托盤(pán)閃爍原理就時(shí)定時(shí)的切換托盤(pán)的圖標(biāo), 圖標(biāo)與透明圖標(biāo)的切換

let flashInterval
function flashTray (bool) {
  if (!bool) {
    flashInterval && clearInterval(flashInterval)
    tray.setImage(path.resolve(__static, 'logo.png'))
    return
  }
  flashInterval && clearInterval(flashInterval)
  var count = 0
  flashInterval = setInterval(function() {
    if (count++ % 2 == 0) {
      tray.setImage(path.resolve(__static, 'empty.png'))
    } else {
      tray.setImage(path.resolve(__static, 'logo.png'))
    }
  }, 400)
}

② 任務(wù)欄的閃爍

win.flashFrame(true) // 高亮

9. 如何只運(yùn)行單個(gè)實(shí)例

參考文檔: https://www.electronjs.org/docs/api/app#apprequestsingleinstancelock

如果你的程序是應(yīng)用的主要實(shí)例并且當(dāng)app.requestSingleInstanceLock()返回true時(shí)筷狼,你應(yīng)該繼續(xù)讓你的程序運(yùn)行动看。如果當(dāng)它返回 false, 那就立即退出

const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
  app.quit()
} {
  app.on('second-instance', (event, argv) => {
    if (process.platform === 'win32') {
      if (win) {
        if (win.isMinimized()) {
          win.restore()
        }
        if (win.isVisible()) {
          win.focus()
        } else {
          win.show()
        }
      }     }
  })
}

10. 主進(jìn)程與渲染進(jìn)程如何通信

參考文檔:
ipcMain
ipcRenderer

① 渲染進(jìn)程

const {ipcRenderer} = require('electron')
ipcRenderer.send('message', 'ping') // 發(fā)送給主進(jìn)程
ipcRenderer.on('message-reply', (event, arg) => {
  console.log(arg) // pong
}

② 主進(jìn)程

// 監(jiān)聽(tīng)渲染進(jìn)程信息
ipcMain.on('message', (event, arg) => {
  console.log('ping')
  event.sender.send('message-reply', 'pong') // 回復(fù)子程序
})
// 主進(jìn)程單獨(dú)往渲染進(jìn)程發(fā)信息
win.webContents.send('message-reply', 'pong')

11. 打包問(wèn)題

參考文檔: https://www.electron.build/configuration/nsis

使用nsis打包windows程序的安裝包
在 vue.config.js 中配置打包配置

module.exports = {
  pluginOptions: {
    electronBuilder: {
      builderOptions: {
        win: {
          target: [{ target: 'nsis', arch: ['ia32', 'x64'] }]
        },
        nsis: {
          oneClick: false, // 一鍵安裝
          perMachine: true, // 為所有用戶(hù)安裝
          allowElevation: true, // 允許權(quán)限提升, 設(shè)置 false 的話(huà)需要重新允許安裝程序
          allowToChangeInstallationDirectory: true, // 允許更改安裝目錄
          createDesktopShortcut: true, // 創(chuàng)建桌面圖標(biāo)
          createStartMenuShortcut: true, // 創(chuàng)建開(kāi)始菜單
          deleteAppDataOnUninstall: true,
          include: './public/nsis/installer.nsh', // 包含的腳本
          guid: '53fe4cba-120d-4851-3cdc-dccb3a469019' // 軟件guid
        }
      }
    }
  }
}

12. 從網(wǎng)頁(yè)打開(kāi)程序

① 主進(jìn)程注冊(cè)

app.removeAsDefaultProtocolClient(‘testapp’)
app.setAsDefaultProtocolClient(‘testapp’)

② 在nsis打包配置文件(installer.nsh)中添加配置
在安裝的時(shí)候在注冊(cè)表注冊(cè)URL protocol


image.png
!macro customInstall
  DetailPrint 'Register testapp URI Handler'
  DeleteRegKey HKCR 'testapp'
  WriteRegStr HKCR 'testapp' '' 'URL:testapp'
  WriteRegStr HKCR 'testapp' 'URL Protocol' ''
  WriteRegStr HKCR 'testapp\shell' '' ''
  WriteRegStr HKCR 'testapp\shell\Open' '' ''
  WriteRegStr HKCR 'testapp\shell\Open\command' '' '$INSTDIR\${APP_EXECUTABLE_FILENAME} %1'
!macroend

③ 直接在瀏覽器訪問(wèn)鏈接即可觸發(fā)打開(kāi)客戶(hù)端
testapp://?參數(shù)=值


image.png

④ 獲取網(wǎng)頁(yè)端傳來(lái)的參數(shù)

// window 系統(tǒng)中執(zhí)行網(wǎng)頁(yè)調(diào)起應(yīng)用時(shí)裕偿,處理協(xié)議傳入的參數(shù)
const handleArgvFromWeb = (argv) => {
  console.log(argv)
  const url = argv.find(v => v.indexOf(`${URLSCHEME}://`) !== -1)
  console.log(url)
  if (url) handleUrlFromWeb(url)
}
// 進(jìn)行處理網(wǎng)頁(yè)傳來(lái) url 參數(shù)洞慎,參數(shù)自定義,以下為示例
// 示例調(diào)起應(yīng)用的 url 為 testapp://?token=205bdf49hc97ch4146h8124h8281a81fdcdb
const handleUrlFromWeb = (urlStr) => {
  console.log(urlStr)
  const urlObj = new URL(urlStr)
  const { searchParams } = urlObj
  const token = searchParams.get('token')
  console.log(token)
}

可以看到獲取的參數(shù)是個(gè)數(shù)組, 我們就時(shí)要獲取最后一項(xiàng)


image.png

生產(chǎn)模式下, 如果軟件沒(méi)有提前打開(kāi), 通過(guò)網(wǎng)頁(yè)開(kāi)啟時(shí), 需要按照下圖方式來(lái)獲取參數(shù)


image.png

若提前開(kāi)啟, 則在判斷單例的條件判斷中獲取


image.png

13. 安裝依賴(lài)或打包時(shí)出現(xiàn)electron包下載過(guò)慢問(wèn)題

根目錄創(chuàng)建 .npmrc 文件

registry = https://registry.npm.taobao.org
 
sass_binary_site = https://npm.taobao.org/mirrors/node-sass/
phantomjs_cdnurl = http://cnpmjs.org/downloads
electron_mirror = https://npm.taobao.org/mirrors/electron/
sqlite3_binary_host_mirror = https://foxgis.oss-cn-shanghai.aliyuncs.com/
profiler_binary_host_mirror = https://npm.taobao.org/mirrors/node-inspector/
chromedriver_cdnurl = https://cdn.npm.taobao.org/dist/chromedriver

14. 通過(guò)外面瀏覽器打開(kāi)鏈接

const { shell } = require('electron')
shell.openExternal('https://www.bing.com')

15. 開(kāi)發(fā)模式如果打開(kāi)窗口時(shí), 若開(kāi)啟了開(kāi)發(fā)者工具, 想關(guān)閉窗口, 需要先把開(kāi)發(fā)者工具關(guān)閉, 才能正常關(guān)閉窗口

在窗口關(guān)閉前, 判斷開(kāi)發(fā)者工具是否開(kāi)啟, 若開(kāi)啟則先關(guān)閉開(kāi)發(fā)者工具, 例如

if (callWin.isDevToolsOpened()) {
  callWin.closeDevTools()
}

16. 透明無(wú)邊框窗口, 接觸到屏幕邊緣會(huì)出現(xiàn)黑色邊框問(wèn)題

參考資料: https://github.com/electron/electron/issues/15947

主要就是創(chuàng)建窗口時(shí)添加延時(shí),

setTimeout(() => createWindow(), 400)

然后關(guān)閉硬件加速

app.disableHardwareAcceleration()
app.commandLine.appendSwitch('disable-gpu')
app.commandLine.appendSwitch('disable-software-rasterizer')

17. 透明無(wú)邊框窗口, 當(dāng)關(guān)閉開(kāi)發(fā)者工具時(shí), 背景會(huì)變白色問(wèn)題

參考資料: https://github.com/electron/electron/issues/10420#issuecomment-329964500

當(dāng)關(guān)閉開(kāi)發(fā)者工具時(shí), 會(huì)重新創(chuàng)建一個(gè)新的渲染視圖, 所以會(huì)使用配置的背景顏色, 如果沒(méi)配置會(huì)使用默認(rèn)值白色 所以需要在窗口創(chuàng)建時(shí)設(shè)置backgroundColor屬性為#00000000

18. 渲染進(jìn)程獲取主進(jìn)程環(huán)境變量

https://github.com/electron/electron/issues/5224#issuecomment-212279369

const { remote } = require('electron')
const envData = remote.getGlobal('process').env

19. 打包時(shí), 報(bào)錯(cuò)asar文件被占用

vscode可以再setting.json里配置忽略dist_electron文件夾

"files.exclude": {
  "dist_electron": true,
}

20. 軟件更新

使用electron-updater

① 配置vue.config.js 設(shè)置publish配置, 配置了這個(gè)配置后, 打包后會(huì)生成一個(gè)latest.yml文件, 需要將其和安裝包放在服務(wù)器同一目錄下, url配置成服務(wù)器可以訪問(wèn)到這個(gè)目錄的url, 也可以使用autoUpdater.setFeedURL(url)動(dòng)態(tài)配置url

pluginOptions: {
  electronBuilder: {
    builderOptions: {
      publish: [
        {
          provider: 'generic',
          url: 'http://127.0.0.1:5000'
        }
      ]
    }
  }
}

② 類(lèi)似示例 https://github.com/electron-userland/electron-builder/blob/docs/encapsulated%20manual%20update%20via%20menu.js

21. electron win7問(wèn)題

① win7 sp1 or win8 needed install KB4019990 patch to resolve this issue for now(note:none sp1 win7 needed to upgrade to sp1 before install the patch)
需要安裝補(bǔ)丁解決黑屏問(wèn)題
https://github.com/electron/electron/issues/25186

② 升級(jí).net
https://github.com/electron/electron/issues/19569

③ 關(guān)閉硬件加速
https://github.com/electron/electron/issues/20702#issuecomment-637156596

22. 7z解壓asar插件

目前我用asar命令行解壓會(huì)報(bào)錯(cuò), 但是用7z的插件雖然報(bào)錯(cuò), 卻可以完整解壓
插件地址: https://www.tc4shell.com/en/7zip/asar/

image.png

下載好, 把dll復(fù)制到7z安裝目錄下新建一個(gè)Formats文件夾中

image.png

四嘿棘、參考文檔

  1. vue-cli配置
  2. electron api文檔
  3. vue-cli-plugin-electron-builder
  4. electron-build文檔
  5. electron-updater文檔
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拢蛋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蔫巩,更是在濱河造成了極大的恐慌谆棱,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圆仔,死亡現(xiàn)場(chǎng)離奇詭異垃瞧,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)坪郭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)个从,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事嗦锐∠铀桑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵奕污,是天一觀的道長(zhǎng)萎羔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碳默,這世上最難降的妖魔是什么贾陷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮嘱根,結(jié)果婚禮上髓废,老公的妹妹穿的比我還像新娘。我一直安慰自己该抒,他們只是感情好慌洪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著凑保,像睡著了一般蒋譬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上愉适,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天犯助,我揣著相機(jī)與錄音,去河邊找鬼维咸。 笑死剂买,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的癌蓖。 我是一名探鬼主播瞬哼,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼租副!你這毒婦竟也來(lái)了坐慰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤用僧,失蹤者是張志新(化名)和其女友劉穎结胀,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體责循,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糟港,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了院仿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秸抚。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡速和,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剥汤,到底是詐尸還是另有隱情颠放,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布吭敢,位于F島的核電站碰凶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏省有。R本人自食惡果不足惜痒留,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一谴麦、第九天 我趴在偏房一處隱蔽的房頂上張望蠢沿。 院中可真熱鬧,春花似錦匾效、人聲如沸舷蟀。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)野宜。三九已至,卻和暖如春魔策,著一層夾襖步出監(jiān)牢的瞬間匈子,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工闯袒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虎敦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓政敢,卻偏偏與公主長(zhǎng)得像其徙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喷户,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容