篇教程是使用Electron + React18進行開發,這里主要講electron的使用。首先我們需要一個react的項目環境,react的項目搭建及開發教程可以參考我的react專欄里的文章:react相關技術這里都有
Electron是一個開源的跨平臺桌面應用程序開發框架,允許開發者使用前端 Web 技術(HTML、CSS 和 JavaScript)來構建桌面應用程序 背景和起源 Electron 最初由 GitHub 公司開發,最早用于構建 GitHub Desktop。隨著其成功,Electron 逐漸成為一個受歡迎的開發框架,許多知名應用程序如 Visual Studio Code、Slack、WhatsApp 等也使用 Electron 構建。 基本原理 Electron 使用 Chromium 渲染引擎來顯示 Web 內容,同時結合 Node.js 來提供對操作系統的訪問和控制。這使得開發者能夠使用 Web 技術來構建桌面應用程序,同時還能夠利用底層操作系統的功能。 主要特點
核心組件
開發流程
核心架構圖解
首先,我們需要在一個常規的React項目中,安裝electron,為了使我們功能代碼部分和electron窗口部分更清晰,我們可以在項目的根目錄新建一個desktop文件夾,專門用來存放electron部分代碼和資源。目錄結構大概如圖所示:
我們cd desktop到desktop文件夾下,執行npm init -y初始化包管理器,然后安裝electron相關包: electron:electron核心包 cross-env:cross-env 是一個用于設置跨平臺環境變量的工具。它可以在 Windows、Linux 和 macOS 等操作系統上提供一致的環境變量設置方式,使得在不同平臺上運行腳本時能夠保持一致的行為。 electron-builder:electron-builder 是一個用于打包、構建和部署 Electron 應用程序的強大工具
powershell
復制代碼npm i electron cross-env electron-builder
在electron應用的運行過程中存在著自己的生命周期,在不同的生命周期中我們可以做對應的事情,下面介紹一些常用的生命周期,electron的生命周期通過electron中的app實例監聽,我們在desktop目錄下新建一個index.js文件,作為electron的入口文件,并在其中監聽應用的各個生命周期 ready 觸發時機:當 Electron 初始化完成并且應用程序準備好創建瀏覽器窗口時。 作用:通常用于初始化應用程序的主要界面和一些基礎設施。 示例:在 ready 事件中創建主窗口和初始化托盤。 certificate-error 觸發時機:當在加載網頁時發生證書錯誤時。 作用:可以在這個事件中攔截證書錯誤并決定是否繼續加載頁面。 示例:在證書錯誤時阻止默認行為并返回 true 以繼續加載頁面。 before-quit 觸發時機:當用戶嘗試退出應用程序時,通常是通過關閉所有窗口或者點擊關閉按鈕。 作用:在應用程序退出之前執行一些清理操作。 示例:可以在這個事件中執行一些清理或保存操作。 window-all-closed 觸發時機:所有應用程序窗口都被關閉時。 作用:在此事件中通常用于在應用程序完全退出之前保留某些功能。 示例:在 macOS 下通常會保留菜單欄。 activate 觸發時機:在點擊 Dock 圖標(macOS)或者任務欄圖標(Windows)時。 作用:通常用于在所有窗口都已關閉的情況下,重新創建主窗口。 示例:在 macOS 下,當點擊 Dock 圖標時,可以重新創建主窗口。 quit 觸發時機:應用程序即將退出時。 作用:在應用程序退出之前執行最后的清理操作。 示例:在這個事件中可以銷毀托盤或其他資源。 will-quit 觸發時機:在應用程序即將退出時,但在 quit 事件之前。 作用:在應用程序退出之前執行一些清理或保存操作。 示例:在這個事件中可以執行一些清理或保存操作。 will-finish-launching 觸發時機:在應用程序即將完成啟動時。 作用:可以在此事件中執行一些在應用程序完全啟動之前需要完成的操作。 示例:在這個事件中可以初始化一些啟動時需要的資源。
javascript復制代碼const { app }=require('electron')
const { createMainWindow }=require('./windows/mainWindow')
app.on('ready', ()=> {
createMainWindow()
})
app.on('certificate-error', (event, webContents, url, error, certificate, callback)=> {
event.preventDefault()
callback(true)
})
app.on('before-quit', ()=> {
console.log('app before-quit')
})
app.on('window-all-closed', function () {
console.log('window-all-closed')
})
app.on('activate', function () {
console.log('activate')
})
app.on('quit', function () {
console.log('quit')
getTray() && getTray().destroy()
})
app.on('will-quit', function () {
console.log('will-quit')
})
app.on('will-finish-launching', function () {
console.log('will-finish-launching')
})
我們在desktop文件夾中創建一個windows文件夾,里面存放每個窗口的相關代碼(我們項目中通常不止一個窗口),我們在windows文件夾中創建一個mainWindow.js文件,用于創建一個簡單的窗口
javascript復制代碼// 在主進程中.
const { BrowserWindow }=require('electron')
const path=require('path')
const win=new BrowserWindow({ width: 800, height: 600 })
// Load a remote URL
win.loadURL('http://localhost:8000/')
// Or load a local HTML file
win.loadFile(path.resolve(__dirname, '../../../build/index.html'))
其中loadURL用于加載一個服務器地址,運行后將會在窗口中顯示該地址的內容,我們這里的http://localhost:8000/是代碼運行的本地環境地址 loadFile是加載一個靜態文件,該文件就是渲染層代碼打包后的入口文件。
由于創建窗口需要在app.on('ready', ()=> {})中,因此我們可以把創建窗口封裝成一個函數并導出,在app.on('ready', ()=> {})中執行,例如: 封裝mainWindow.js
javascript復制代碼const { BrowserWindow, ipcMain }=require('electron')
const path=require('path')
const isDevelopment=process.env.NODE_ENV==='development'
let mainWindow=null
function createMainWindow() {
mainWindow=new BrowserWindow({
width: 1160,
height: 752,
minHeight: 632,
minWidth: 960,
show: false,
frame: false,
title: 'Harbour',
webPreferences: {
nodeIntegration: true,
preload: path.resolve(__dirname, '../utils/contextBridge.js')
},
icon: path.resolve(__dirname, '../assets/logo.png')
})
if (isDevelopment) {
mainWindow.loadURL('http://localhost:8000/')
} else {
const entryPath=path.resolve(__dirname, '../../build/index.html')
mainWindow.loadFile(entryPath)
}
mainWindow.once('ready-to-show', ()=> {
mainWindow.show()
})
}
module.exports={ createMainWindow }
代碼解析:我們這里使用process.env.NODE_ENV的值判斷當前的運行環境,這里的運行環境需要說明一下,當我們使用下面配置的npm run dev-electron運行時,該值為"development",當我們將渲染層代碼打包后,使用npm run prod-electron運行時,該值為"production",然而當我們使用electron-builder打包出來的安裝包運行時,該值不存在為undefined。因此只有當該值是"development"時我們才加載一個我們渲染層啟動的服務地址,其他兩種情況下我們都需要加載我們渲染層打包后的入口文件即:build目錄下的index.html`
`我們在窗口觸發"ready-to-show"時顯示窗口是為了使加載時的白屏時間不被用戶看到
index.js導入并在app.on('ready', ()=> {})中執行
javascript復制代碼const { app }=require('electron')
const { createMainWindow }=require('./windows/mainWindow')
app.on('ready', ()=> {
createMainWindow()
})
此時我們就可以運行electron,我們在package.json中配置運行命令
json復制代碼{
"scripts": {
"dev-electron": "cross-env NODE_ENV=development electron main/index.js",
"prod-electron": "cross-env NODE_ENV=production electron main/index.js",
}
}
執行命令,啟動開發環境
powershell
復制代碼npm run dev-electron
運行成功,出現如下窗口(窗口內部內容可自行定義)
使用npm run prod-electron命令可以啟動生產環境,該生產環境指的是渲染層的功能代碼使用webpack打包后的代碼,使其渲染到窗口中。真正的生產環境應該是下面介紹的使用electron-builder打包后的應用程序,此時process.env.NODE_ENV為undefined
上面我們啟動electron的應用都是使用的node_modules中的electron包,我們想要得到一個真正可以安裝的安裝包,還需要使用第三方打包工具進行打包,上面有提到過,我們將使用electron-builder打包成可安裝的安裝包。上面我們已經安裝了electron-builder,下面我們需要在package.json中配置build屬性來自定義安裝配置。(限于自身設備問題,這里只介紹在Windows系統的打包配置,electron可以打包成各種安裝包,使其可以在mac,Linux系統上運行,其他系統的配置可自行查閱資料。)下面我們介紹一下配置內容和各個配置含義。 package.json完整配置
json復制代碼{
"name": "desktop",
"productName": "Harbour",
"version": "1.0.0",
"description": "",
"main": "main/index.js",
"scripts": {
"dev-electron": "cross-env NODE_ENV=development electron main/index.js",
"prod-electron": "cross-env NODE_ENV=production electron main/index.js",
"build-electron-win64": "electron-builder -w --x64"
},
"build": {
"productName": "Harbour",
"appId": "harbour.electron.app",
"files": [
"build/**/*",
"main/**/*"
],
"directories": {
"output": "dist"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./main/assets/logo.ico",
"uninstallerIcon": "./main/assets/logo.ico",
"installerHeaderIcon": "./main/assets/logo.png",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Harbour"
},
"win": {
"icon": "./main/assets/logo.ico",
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
"target": "nsis"
},
"electronDist": "./electron"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"cross-env": "^7.0.3",
"electron": "^26.1.0",
"electron-builder": "^24.6.3"
}
}
配置解釋
特別注意 這里有幾個需要特別注意的點:
打包后的內容
dist目錄下就是打包生成的內容,其中第一個紅框的Harbour.exe是可直接執行的文件,無需安裝,第二個紅框中的.exe可執行文件就是可安裝的文件,在文件夾中,雙擊即可進入安裝流程。
在我們創建窗口時可以配置很多自定義配置,下面是一些常用配置及解析:
javascript復制代碼const { BrowserWindow }=require('electron');
const mainWindow=new BrowserWindow({
width: 800,
height: 600,
x: 100,
y: 100,
fullscreen: false,
resizable: true,
minWidth: 400,
minHeight: 300,
frame: true,
title: 'My Electron App',
icon: '/path/to/icon.png',
backgroundColor: '#ffffff',
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: 'path/to/preload.js',
devTools: true,
webSecurity: true
},
alwaysOnTop: false,
fullscreenable: true,
show: true,
transparent: false,
closable: true
});
mainWindow.loadFile('index.html');
窗口有很多實例事件,使用window.on來監聽,可以在這些事件觸發時做一切操作例如下面是一些常用的實例事件: close 觸發時機:窗口即將關閉時觸發,但實際關閉前。 作用:允許執行一些在窗口關閉前的清理操作,或者阻止窗口關閉。 closed 觸發時機:窗口已經關閉時觸發。 作用:通常用于釋放資源或執行一些在窗口關閉后的最終操作。 resize 觸發時機:窗口大小發生變化時觸發。 作用:允許在窗口大小變化時執行一些操作。 move 觸發時機:窗口位置發生變化時觸發。 作用:允許在窗口位置變化時執行一些操作。 focus 觸發時機:窗口獲得焦點時觸發。 作用:允許在窗口獲得焦點時執行一些操作。 blur 觸發時機:窗口失去焦點時觸發。 作用:允許在窗口失去焦點時執行一些操作。 minimize 觸發時機:窗口被最小化時觸發。 作用:允許在窗口最小化時執行一些操作。 maximize 觸發時機:窗口被最大化時觸發。 作用:允許在窗口最大化時執行一些操作。 unmaximize 觸發時機:窗口從最大化狀態恢復時觸發。 作用:允許在窗口從最大化狀態恢復時執行一些操作。 ready-to-show 觸發時機:當窗口完成初始化并且準備好顯示時觸發。 作用:允許在窗口已準備好顯示之后執行一些操作。這通常在窗口加載內容后并準備好顯示時觸發,用于控制窗口的顯示時機。 show 觸發時機:當窗口被顯示時觸發。 作用:允許在窗口顯示時執行一些操作。 hide 觸發時機:當窗口被隱藏時觸發。 作用:允許在窗口隱藏時執行一些操作。 enter-full-screen 觸發時機:當窗口進入全屏模式時觸發。 作用:允許在窗口進入全屏模式時執行一些操作。 leave-full-screen 觸發時機:當窗口離開全屏模式時觸發。 作用:允許在窗口離開全屏模式時執行一些操作。
javascript復制代碼// main.js
const { app, BrowserWindow }=require('electron');
let mainWindow;
function createMainWindow() {
mainWindow=new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
// 加載你的 HTML 文件
mainWindow.loadFile('index.html');
// 事件: 關閉
mainWindow.on('close', (event)=> {
// 允許或阻止窗口關閉
// event.preventDefault();
// 執行清理操作
});
// 事件: 關閉后
mainWindow.on('closed', ()=> {
// 釋放資源或執行最終操作
mainWindow=null;
});
// 事件: 調整大小
mainWindow.on('resize', ()=> {
// 在窗口調整大小時執行操作
});
// 事件: 移動
mainWindow.on('move', ()=> {
// 在窗口移動時執行操作
});
// 事件: 獲得焦點
mainWindow.on('focus', ()=> {
// 在窗口獲得焦點時執行操作
});
// 事件: 失去焦點
mainWindow.on('blur', ()=> {
// 在窗口失去焦點時執行操作
});
// 事件: 最小化
mainWindow.on('minimize', ()=> {
// 在窗口最小化時執行操作
});
// 事件: 最大化
mainWindow.on('maximize', ()=> {
// 在窗口最大化時執行操作
});
// 事件: 還原
mainWindow.on('unmaximize', ()=> {
// 在窗口從最大化狀態還原時執行操作
});
// 事件: 準備好顯示
mainWindow.on('ready-to-show', ()=> {
// 在窗口準備好顯示后執行操作
mainWindow.show();
});
// 事件: 顯示
mainWindow.on('show', ()=> {
// 在窗口顯示時執行操作
});
// 事件: 隱藏
mainWindow.on('hide', ()=> {
// 在窗口隱藏時執行操作
});
// 事件: 進入全屏模式
mainWindow.on('enter-full-screen', ()=> {
// 在窗口進入全屏模式時執行操作
});
// 事件: 離開全屏模式
mainWindow.on('leave-full-screen', ()=> {
// 在窗口離開全屏模式時執行操作
});
}
窗口自身存在很多的實例屬性,可以使我們獲取到窗口的一些當前狀態。下面是一些常用的實例屬性。
對于一個桌面應用來說,右下角的系統托盤必不可少,electron應用的系統托盤使用tray這個api實現,下面是封裝的專門處理系統托盤的文件 systemTray.js
javascript復制代碼const { app, Tray, Menu }=require('electron')
const path=require('path')
const { getMainWindow, mainWindowIsExist }=require('./windows/mainWindow')
let tray=null
const iconPath=path.resolve(__dirname, './assets/logo.png')
function initTray() {
tray=new Tray(iconPath)
const contextMenu=Menu.buildFromTemplate([
{
label: '打開應用', click: ()=> {
mainWindowIsExist() && getMainWindow().show()
}
},
{ label: '退出應用', click: ()=> { app.quit() } },
])
tray.setToolTip('Harbour') // 設置鼠標懸停時顯示的提示信息
tray.setContextMenu(contextMenu)
tray.on('click', ()=> {
mainWindowIsExist() && getMainWindow().show()
})
}
function getTray() {
return tray
}
module.exports={ initTray, getTray }
代碼解析
初始化系統托盤 系統托盤的初始化需要在app.on('ready')之后,因此我們將初始化系統托盤的方法封裝好導出,在app.on('ready')中執行
javascript復制代碼const { app }=require('electron')
const { createMainWindow }=require('./windows/mainWindow')
const { initTray, getTray }=require('./systemTray')
app.on('ready', ()=> {
createMainWindow()
initTray()
})
應用層和主進程之間的通信流程是:
圖解如下
我們知道ipcMain和ipcRender都是electron的Api,要想在應用層使用ipcRender就需要先將其注入到應用層,在electron中使用contextBridge.exposeInMainWorld方法將electron的Api注入到應用層,注入之后我們就可以在應用層的window上訪問注入的屬性。我們這里將ipcRender和process兩個屬性注入到應用層,分別用來實現通信和判斷當前運行環境。 封裝contextBridge.js文件
javascript復制代碼const { contextBridge, ipcRenderer }=require('electron')
/**
* contextBridge.exposeInMainWorld的作用就是將主進程的某些API注入到渲染進程,
* 供渲染進程使用(主進程并非所有的API或對象都能注入給渲染進程,需要參考文檔)
* ipcRenderer 渲染進程通過window.ipcRenderer調用
*/
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, ...args)=> {
if (args?.length > 0) {
ipcRenderer.send(channel, ...args)
} else {
ipcRenderer.send(channel)
}
},
on: (channel, func)=> {
ipcRenderer.on(channel, func)
},
once: (channel, func)=> {
ipcRenderer.once(channel, func)
},
removeListener: (channel, func)=> {
ipcRenderer.removeListener(channel, func)
},
sendSync: (channel, ...args)=> {
if (args?.length > 0) {
return ipcRenderer.sendSync(channel, ...args)
} else {
return ipcRenderer.sendSync(channel)
}
},
invoke: (channel, ...args)=> {
try {
return ipcRenderer.invoke(channel, ...args)
} catch (error) {
console.error(`Error invoking API: ${channel}`, error)
}
},
})
contextBridge.exposeInMainWorld('process', {
NODE_ENV: process.env.NODE_ENV
})
這里我們將ipcRender的send,on,once,removeListener,sendSync,invoke方法及process.env.NODE_ENV注入到應用層,后續可在應用層進行使用 注意,該方法需要在應用層渲染時執行,因此我們剛好可以用到創建窗口中的option.webPreferences.preload來加載該文件,后續有案例代碼。
我們將ipcRender和process注入到應用層后,為了后期的維護我們可以將所有的方法再次進行封裝,放在一個統一的文件中, 封裝desktopUtils.ts
typescript復制代碼declare global {
interface Window {
ipcRenderer: {
send: (...args: any[])=> void,
on: (channel: string, listener: (...args: any[])=> void)=> void,
once: (channel: string, listener: (...args: any[])=> void)=> void,
removeListener: (channel: string, listener: (...args: any[])=> void)=> void,
sendSync: (...args: any[])=> any,
invoke: (...args: any[])=> Promise<any>,
},
process: {
NODE_ENV: 'development' | 'production'
}
}
}
type ArgsType=string | number | boolean | { [key: string]: any } | any[]
export const isDesktop=()=> {
return !!window.ipcRenderer
}
export const getProcessNodeEnv=()=> {
return window?.process.NODE_ENV
}
export const ipcRendererSend=(eventName: string, ...args: ArgsType[])=> {
window.ipcRenderer?.send(eventName, ...args)
}
export const ipcRendererSendSync=(eventName: string, ...args: ArgsType[])=> {
return window.ipcRenderer?.sendSync(eventName, ...args)
}
export const ipcRendererInvoke=(eventName: string, ...args: ArgsType[])=> {
try {
return window.ipcRenderer?.invoke(eventName, ...args)
} catch (error) {
console.error(`Error invoking IPC: ${eventName}`, error)
return null
}
}
export const ipcRendererOn=(eventName: string, listener: (...args: ArgsType[])=> void)=> {
window.ipcRenderer?.on(eventName, listener)
}
export const ipcRendererOnce=(eventName: string, listener: (...args: ArgsType[])=> void)=> {
window.ipcRenderer?.once(eventName, listener)
}
export const ipcRendererRemoveListener=(eventName: string, listener: (...args: ArgsType[])=> void)=> {
window.ipcRenderer?.removeListener(eventName, listener)
}
這里的isDesktop是用來判斷當前是否是桌面端的,因為很多時候我們使用electron開發的桌面端應用需要兼容web端,由于應用層代碼幾乎相同,我們只需要在一些情況下特別處理桌面端的邏輯即可。由于web端的window上一定沒有ipcRender這個屬性,因此可以根據window.ipcRenderer來判斷 getProcessNodeEnv是用來獲取當前桌面端的運行環境的,這里可以返回當前是開發環境還是生產環境,如果是web端的話,直接用process.env.NODE_ENV即可判斷
我們封裝好了方法之后就可以進行使用了,我們做一個簡單的案例,應用的header,有最小化,最大化,關閉,和恢復按鈕,在點擊時使用ipcRendererSend方法將事件傳給主進程并進行相應操作。 由于我們需要一個狀態判斷顯示最大化按鈕還是恢復按鈕因此需要監聽主進程在執行最大化和恢復之后傳回的事件和當前狀態。 desktopHeader.tsx
tsx復制代碼import React, { memo, useState, useEffect } from 'react'
import './desktopHeader.less'
import SvgIcon from '@components/svgIcon'
import {
ipcRendererSend,
ipcRendererOn,
ipcRendererRemoveListener
} from '@common/desktopUtils'
import logoImage from '@assets/logo.png'
function DesktopHeader() {
const [windowIsMax, setWindowIsMax]=useState(false)
useEffect(()=> {
const handleSetIsMax=(event: any, isMax: boolean)=> {
setWindowIsMax(isMax)
}
ipcRendererOn('mainWindowIsMax', handleSetIsMax)
return ()=> {
ipcRendererRemoveListener('mainWindowIsMax', handleSetIsMax)
}
}, [])
const handleWindow=(eventName: string)=> {
ipcRendererSend(`mainWindow-${eventName}`)
}
return (
<div className="desktop-header">
<div className="header-logo-box">
<img src={logoImage} alt="" />
<span>Harbour</span>
</div>
<div className="header-handle-box">
<div className="handle-icon-box" onClick={handleWindow.bind(this, 'min')}>
<SvgIcon
svgName="min-icon"
needPointer
iconColor="#737780"
iconSize={24}
/>
</div>
{windowIsMax ? (
<div className="handle-icon-box" onClick={handleWindow.bind(this, 'restore')}>
<SvgIcon
svgName="restore-icon"
needPointer
iconColor="#737780"
iconSize={24}
/>
</div>
) : (
<div className="handle-icon-box" onClick={handleWindow.bind(this, 'max')}>
<SvgIcon
svgName="max-icon"
needPointer
iconColor="#737780"
iconSize={24}
/>
</div>
)}
<div className="handle-icon-box handle-close-icon" onClick={handleWindow.bind(this, 'close')}>
<SvgIcon
svgName="close-icon"
needPointer
hasHover
iconColor="#737780"
hoverColor="#fff"
iconSize={24}
/>
</div>
</div>
</div>
)
}
export default memo(DesktopHeader)
在主進程中我們使用ipcMain.on來監聽事件,并進行相應操作,并在相應操作之后在需要的時候發送事件到應用層。下面我們封裝mainWindow.js里面包含創建窗口方法,獲取窗口實例方法,獲取窗口是否存在方法及事件監聽方法 mainWindow.js
javascript復制代碼const { BrowserWindow, ipcMain }=require('electron')
const path=require('path')
const isProduction=process.env.NODE_ENV==='development'
let mainWindow=null
function createMainWindow() {
mainWindow=new BrowserWindow({
width: 1160,
height: 752,
minHeight: 632,
minWidth: 960,
show: false,
frame: false,
title: 'Harbour',
webPreferences: {
nodeIntegration: true,
preload: path.resolve(__dirname, '../utils/contextBridge.js')
},
icon: path.resolve(__dirname, '../assets/logo.png')
})
if (isDevelopment) {
mainWindow.loadURL('http://localhost:8000/')
} else {
const entryPath=path.resolve(__dirname, '../../build/index.html')
mainWindow.loadFile(entryPath)
}
mainWindow.once('ready-to-show', ()=> {
mainWindow.show()
})
mainWindowListenEvents()
}
function mainWindowListenEvents() {
ipcMain.on('mainWindow-min', ()=> {
mainWindowIsExist() && mainWindow.minimize()
})
ipcMain.on('mainWindow-max', ()=> {
if (mainWindowIsExist()) {
mainWindow.maximize()
mainWindow.webContents.send('mainWindowIsMax', true)
}
})
ipcMain.on('mainWindow-restore', ()=> {
if (mainWindowIsExist()) {
mainWindow.unmaximize()
mainWindow.webContents.send('mainWindowIsMax', false)
}
})
ipcMain.on('mainWindow-close', ()=> {
mainWindowIsExist() && mainWindow.hide()
})
ipcMain.on('mainWindow-open-devtool', ()=> {
mainWindowIsExist() && mainWindow.webContents.openDevTools()
})
}
function mainWindowIsExist() {
return mainWindow && !mainWindow.isDestroyed()
}
function getMainWindow() {
return mainWindow
}
module.exports={
getMainWindow,
createMainWindow,
mainWindowIsExist
}
要實現窗口之間的通信,我們實際上就是使用應用層和主進程之間的通信,由于主進程可以接收到任意窗口發過來的事件,因此我們想實現窗口之間的通信,只需要在主進程中進行轉發就好,下面是圖解。
javascript復制代碼// 主窗口
ipcRendererSend('sendToSecond', '123')
// 主進程
ipcMain.on('sendToSecond', (e, data)=> {
secondWindow.webContents.send('sendToSecond', data)
})
// 第二窗口
ipcRendererOn('sendToSecond', handler)
clipboard 是 Electron 提供的模塊之一,用于在應用程序中進行剪貼板操作。它允許你讀取和寫入系統剪貼板中的文本、圖像等數據。以下是一些常用的 clipboard 模塊方法 clipboard.writeText(text[, type]) 將文本寫入剪貼板。 text:要寫入剪貼板的文本內容。 type(可選):可指定數據類型,默認為 clipboard。可以是 selection(用于選區)或 clipboard(用于剪貼板)。
javascript復制代碼const { clipboard }=require('electron')
clipboard.writeText('Hello, World!')
clipboard.readText([type]) 從剪貼板中讀取文本內容。 type(可選):可指定數據類型,默認為 clipboard。可以是 selection(用于選區)或 clipboard(用于剪貼板)。
javascript復制代碼const { clipboard }=require('electron')
const text=clipboard.readText()
console.log(text)
clipboard.writeHTML(markup[, type]) 將 HTML 內容寫入剪貼板。 markup:要寫入剪貼板的 HTML 內容。 type(可選):可指定數據類型,默認為 clipboard。可以是 selection(用于選區)或 clipboard(用于剪貼板)。
javascript復制代碼const { clipboard }=require('electron')
const html='<div><h1>Hello, World!</h1></div>'
clipboard.writeHTML(html)
clipboard.readHTML([type]) 從剪貼板中讀取 HTML 內容。 type(可選):可指定數據類型,默認為 clipboard。可以是 selection(用于選區)或 clipboard(用于剪貼板)。
javascript復制代碼const { clipboard }=require('electron')
const html=clipboard.readHTML()
console.log(html)
clipboard.writeImage(image[, type]) 將圖像寫入剪貼板。 image:要寫入剪貼板的圖像,可以是一個 nativeImage 對象或者一個文件路徑。 type(可選):可指定數據類型,默認為 clipboard。可以是 selection(用于選區)或 clipboard(用于剪貼板)。
javascript復制代碼const { clipboard, nativeImage }=require('electron')
const image=nativeImage.createFromPath('/path/to/image.png')
clipboard.writeImage(image)
clipboard.readImage([type]) 從剪貼板中讀取圖像。 type(可選):可指定數據類型,默認為 clipboard。可以是 selection(用于選區)或 clipboard(用于剪貼板)。
javascript復制代碼const { clipboard }=require('electron')
const image=clipboard.readImage()
clipboard.clear([type]) 清空剪貼板內容。 type(可選):可指定數據類型,默認為 clipboard。可以是 selection(用于選區)或 clipboard(用于剪貼板)。
javascript復制代碼const { clipboard }=require('electron')
clipboard.clear()
dialog 是 Electron 提供的模塊之一,用于在桌面應用程序中創建對話框,以便與用戶進行交互。它可以用于打開文件、保存文件、顯示警告、錯誤等信息,以及進行用戶輸入的獲取等操作。以下是一些常用的 dialog 模塊方法
dialog.showOpenDialog([browserWindow, ]options)打開一個文件選擇對話框,允許用戶選擇一個或多個文件。
javascript復制代碼const { dialog }=require('electron');
const options={
title: '選擇文件',
defaultPath: '/path/to/default/folder',
filters: [
{ name: 'Text Files', extensions: ['txt', 'text'] },
{ name: 'All Files', extensions: ['*'] }
],
properties: ['openFile', 'multiSelections']
};
dialog.showOpenDialog(null, options).then(result=> {
console.log(result.filePaths)
}).catch(err=> {
console.log(err)
})
dialog.showSaveDialog([browserWindow, ]options)打開一個文件保存對話框,允許用戶選擇保存的路徑和文件名。
javascript復制代碼const { dialog }=require('electron');
const options={
title: '保存文件',
defaultPath: '/path/to/default/folder',
filters: [
{ name: 'Text Files', extensions: ['txt', 'text'] },
{ name: 'All Files', extensions: ['*'] }
]
};
dialog.showSaveDialog(null, options).then(result=> {
console.log(result.filePath);
}).catch(err=> {
console.log(err);
});
dialog.showMessageBox([browserWindow, ]options)顯示一個消息框,通常用于警告或者通知用戶。
javascript復制代碼const { dialog }=require('electron');
const options={
type: 'info',
title: '信息',
message: '這是一個信息框。',
buttons: ['OK']
};
dialog.showMessageBox(null, options).then(result=> {
console.log(result.response);
}).catch(err=> {
console.log(err);
});
dialog.showErrorBox(title, content)顯示一個錯誤框,用于顯示錯誤信息。
javascript復制代碼const { dialog }=require('electron');
dialog.showErrorBox('發生錯誤', '這是一個錯誤框的示例。');
globalShortcut 是 Electron 提供的模塊之一,用于注冊和響應全局鍵盤快捷鍵。這允許你在你的 Electron 應用程序中創建全局快捷鍵,以執行特定操作或觸發事件。以下是一些常用的 globalShortcut 模塊方法 globalShortcut.register(accelerator, callback)注冊全局快捷鍵。
javascript復制代碼const { globalShortcut }=require('electron');
globalShortcut.register('CmdOrCtrl+X', ()=> {
// 執行某些操作
});
globalShortcut.isRegistered(accelerator)檢查是否已經注冊了指定的全局快捷鍵。 accelerator:要檢查的快捷鍵。
javascript復制代碼const { globalShortcut }=require('electron');
const isRegistered=globalShortcut.isRegistered('CmdOrCtrl+X');
if (isRegistered) {
console.log('已注冊');
} else {
console.log('未注冊');
}
globalShortcut.unregister(accelerator)注銷已注冊的全局快捷鍵。 accelerator:要注銷的快捷鍵。
javascript復制代碼const { globalShortcut }=require('electron');
globalShortcut.unregister('CmdOrCtrl+X');
globalShortcut.unregisterAll()注銷所有已注冊的全局快捷鍵。
javascript復制代碼const { globalShortcut }=require('electron');
globalShortcut.unregisterAll();
globalShortcut.getRegisteredKeys()獲取當前已注冊的全局快捷鍵的列表。
javascript復制代碼const { globalShortcut }=require('electron');
const registeredKeys=globalShortcut.getRegisteredKeys();
console.log(registeredKeys);
Menu 是 Electron 中用于創建和管理應用程序菜單的模塊。它允許你在應用程序的菜單欄、上下文菜單等位置定義菜單項,以便用戶可以通過點擊菜單項執行特定的操作。以下是一些常用的 Menu 模塊方法和屬性
創建頂級菜單
javascript復制代碼const { Menu }=require('electron');
const template=[
{
label: 'File',
submenu: [
{ role: 'openFile' },
{ role: 'saveFile' },
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'copy' },
{ role: 'paste' }
]
}
];
const menu=Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu)
創建上下文菜單
javascript復制代碼const { Menu }=require('electron');
const template=[
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
];
const contextMenu=Menu.buildFromTemplate(template);
Menu.buildFromTemplate(template)從模板數組創建一個菜單。 template:一個包含菜單項的數組。每個菜單項都是一個對象,包含 label、click 等屬性。 Menu.setApplicationMenu(menu)設置應用程序菜單,通常用于頂級菜單欄。 menu:要設置為應用程序菜單的 Menu 對象。
javascript復制代碼const template=[
{
label: 'File',
submenu: [
{ label: 'Open', accelerator: 'CmdOrCtrl+O', click: ()=> { /* 打開文件 */ } },
{ role: 'save' },
{ type: 'separator' }, // 分隔線
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'copy' },
{ role: 'cut' },
{ role: 'paste' },
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
click: ()=> { /* 選擇所有內容 */ }
}
]
}
];
nativeImage 是 Electron 提供的模塊之一,用于處理圖像。它可以加載圖像文件、從屏幕截取圖像、創建空白圖像等。nativeImage 支持跨平臺,可以在主進程和渲染進程中使用。
javascript復制代碼const { nativeImage }=require('electron');
const emptyImage=nativeImage.createEmpty();
javascript復制代碼const { nativeImage }=require('electron');
const imagePath='/path/to/image.png';
const image=nativeImage.createFromPath(imagePath);
javascript復制代碼const { nativeImage }=require('electron');
const fs=require('fs');
const buffer=fs.readFileSync('/path/to/image.png');
const image=nativeImage.createFromBuffer(buffer);
javascript復制代碼const { nativeImage }=require('electron');
const dataURL='data:image/png;base64,iVBORw0KGg...';
const image=nativeImage.createFromDataURL(dataURL);
javascript復制代碼const { nativeImage }=require('electron');
const image=nativeImage.createFromNamedImage('NSStopProgressTemplate', { h: 0, s: 0, l: 0 });
javascript復制代碼const { nativeImage }=require('electron');
const imagePath='/path/to/image.png';
const size={ width: 100, height: 100 };
nativeImage.createThumbnailFromPath(imagePath, size, (thumbnail)=> {
console.log(thumbnail);
});
javascript復制代碼const { nativeImage }=require('electron');
const imagePath='/path/to/image.png';
const image=nativeImage.createFromPath(imagePath);
console.log(nativeImage.isMacTemplateImage(image)); // true or false
javascript復制代碼const { nativeImage }=require('electron');
const imagePath='/path/to/image.png';
const image=nativeImage.createFromPath(imagePath);
const dataURL=image.toDataURL({ scaleFactor: 2.0 });
screen 是 Electron 提供的模塊之一,用于獲取有關屏幕和顯示器的信息,以及執行與屏幕相關的操作。以下是一些常用的 screen 模塊方法和屬性:
在我們開發過程中,需要經常用的控制臺,而我們在開發時直接打開控制臺又有些不友好。還有生產環境中我們有時也需要打開控制臺定位一些問題,但是生產環境又不能那么輕易讓用戶能打開控制臺,因此我們可以在開發環境和生產環境分別預留一個接口打開控制臺,生產環境的方式要復雜一些。 實現思路 我們可以在應用層監聽鍵盤事件,當在開發環境中,按下ctrl + F12時,我們就打開控制臺。 而在生產環境中我們需要設計的復雜一些,可以在代碼中放一個不顯示的輸入框(設置寬高邊框均為0,并且固定定位就好),當按下特殊組合鍵時,聚焦輸入框,并輸入openDevtool之后打開控制臺,我這里設置的組合鍵為ctrl + win + alt + F12 代碼如下
javascript復制代碼import React, { useEffect, useRef, useCallback, ChangeEvent } from 'react'
import DesktopHeader from '@components/desktopHeader'
import './app.less'
import {
isDesktop,
getProcessNodeEnv,
ipcRendererSend
} from '@common/desktopUtils'
import electronImg from '@assets/electronImg.png'
function App() {
const openDevtoolInput=useRef<HTMLInputElement>(null)
const isDevelopment=useRef(getProcessNodeEnv()==='development')
const openDevtool=useCallback(()=> {
ipcRendererSend('mainWindow-open-devtool')
}, [])
const openDevtoolInputChange=(event: ChangeEvent<HTMLInputElement>)=> {
const { value }=event.target
if (value==='openDevtool') {
openDevtool()
}
}
useEffect(()=> {
document.addEventListener('keydown', (e)=> {
const { ctrlKey, metaKey, altKey, key }=e
// 開發環境使用ctrl + F12打開控制臺
if (isDevelopment.current && ctrlKey && key==='F12') {
openDevtool()
}
// 開發環境使用ctrl + win + alt + F12,然后鍵入'open devtool'打開控制臺
if (!isDevelopment.current && ctrlKey && metaKey && altKey && key==='F12') {
if (openDevtoolInput.current) {
openDevtoolInput.current.focus()
}
}
})
}, [openDevtool])
return (
<div id="electron-app">
{!isDevelopment.current && (
<input
className="open-devtool-input"
ref={openDevtoolInput}
type="text"
onChange={openDevtoolInputChange}
onBlur={(e)=> { e.target.value='' }}
/>
)}
{isDesktop() && <DesktopHeader />}
<div className={isDesktop() ? 'desktop-app-content' : 'app-content'}>
<div className="electron-img">
<img src={electronImg} alt="" />
</div>
</div>
</div>
)
}
export default App
在主進程監聽該事件并打開控制臺
javascript復制代碼ipcMain.on('mainWindow-open-devtool', ()=> {
mainWindowIsExist() && mainWindow.webContents.openDevTools()
})
作者:Harbour 鏈接:https://juejin.cn/post/7277799192961925172 來源:稀土掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
語:有時候在電腦上網的時候看見自己喜歡的網站或特別的頁面,自己已經點擊電腦屏幕上方的星號收藏這個頁面了,但有時候看收藏夾又被密密麻麻的內容弄得頭昏眼花,這時,我的處理方法是把收藏的頁面導出來,新建文件夾分類整理好,這樣下次想看什么內容就不用只看收藏夾里小小的字,而是在文件夾中能夠清楚的,按照自己的習慣,分好類別的,整潔的頁面了。下面是具體操作,根據提示的1、2、3······進行操作,就可以了。
首先打開瀏覽器。找到自己喜歡的頁面,我打開了自己的收藏夾,找到自己喜歡的頁面。然后點擊文字跳轉到具體的那個喜歡的頁面。
然后點擊屏幕上方,瀏覽器的三個點“···”。
然后點擊“更多工具”。
然后點擊“將頁面另存為”。
選擇文件夾,把頁面保存下來。
最后打開電腦你保存文件在什么地方,就能在該地方找到保存的喜歡頁面,該頁面在文件夾里保存的形式為“html”文件。
公司去年對 CDN 資源服務器進行了遷移,由原來的通過 FTP 方式的文件存儲改為了使用 S3 協議上傳的對象存儲,部門內 @柴俊堃 同學開發了一個命令行腳本工具 RapidTrans(睿傳),使用睿傳可以很方便將本地目錄下的資源上傳到 S3 中。
睿傳運行時接收兩個主要參數,一個為待上傳的本地路徑,一個為上傳到 CDN 后的路徑,我們可以在項目的 package.json 中去配置 scripts執行上傳。
npm run rapid-trans -- -s "/home/demo/work/mall2016/release/列表頁" -p "2016/m/list"
用了一段時間后覺得如果選擇本地路徑的時候可以通過可視化的文件選擇器的方式選擇就太好了,團隊一直在做客戶端方向技術的儲備,所以為了更方便團隊的使用產生了將睿傳封裝成 GUI 的跨平臺客戶端的想法。
Electron 是由 Github 開發,基于 Chromium 和 Node.js, 讓你可以使用 HTML, CSS 和 JavaScript 構建跨平臺桌面應用的開源框架。
Electron 可以讓你使用純 JavaScript 調用豐富的原生(操作系統) APIs 來創造桌面應用。 你可以把它看作一個專注于桌面應用的 Node. js 的變體,而不是 Web 服務器。
簡單點說,用 Electron 可以讓我們在網頁中使用 Node.js 的 API 和調用系統 API。
使用 vue-cli 腳手架和 electron-vue模板進行搭建,此處需要注意,由于 electron-vue 模板不支持 vue-cli@3.0,所以要使用 2.0 版本。
# 安裝 vue-cli@2.0,若已安裝則無需重復安裝
npm install -g vue-cli
vue init simulatedgreg/electron-vue s3_upload_tool
# 安裝依賴并運行
cd s3_upload_tool
npm install
npm run dev
├─ .electron-vue
│ ├─ webpack.main.config.js
│ ├─ webpack.renderer.config.js
│ └─ webpack.web.config.js
├─ build
│ └─ icons/
├─ dist
│ ├─ electron/
│ └─ web/
├─ node_modules/
├─ src
│ ├─ main
│ │ ├─ index.dev.js
│ │ └─ index.js
│ ├─ renderer
│ │ ├─ components/
│ │ ├─ router/
│ │ ├─ store/
│ │ ├─ App.vue
│ │ └─ main.js
│ └─ index.ejs
├─ static/
├─ .babelrc
├─ .eslintignore
├─ .eslintrc.js
├─ .gitignore
├─ package.json
└─ README.md
應用的目錄結構和平常我們用 Vue 做 WEB 端時生成的結構基本差異不大,所以本文我只介紹下與 Web 不同的幾個目錄。
該目錄下包含 3 個獨立的 Webpack 配置文件
主進程代碼存放位置,涉及到調取 Node API 、調用原生系統功能的代碼。
渲染進程代碼存放位置,和平常的 Vue 項目基本一樣。
在 Electron 中有兩個進程,分別為主進程和渲染進程,主進程負責 GUI 部分,渲染進程負責頁面的展示。
main.js
const { app, BrowserWindow }=require('electron')
function createWindow () {
// 創建瀏覽器窗口
let win=new BrowserWindow({ width: 800, height: 600 })
// 然后加載 app 的 index.html.
win.loadFile('index.html')
}
app.on('ready', createWindow)
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
主進程使用 BrowserWindow 實例創建頁面。 每個BrowserWindow實例都在自己的渲染進程里運行頁面。 當一個 BrowserWindow 實例被銷毀后,相應的渲染進程也會被終止。
進程間通信(IPC,Interprocess communication)是一組編程接口,讓開發者能夠協調不同的進程,使之能在一個操作系統里同時運行,并相互傳遞、交換信息。
Electron 使用 IPC 的機制,由主進程來創建應用,渲染進程來負責繪制頁面,而兩個進程之間是無法直接通信的。
渲染進程通過ipcRenderer向主進程發送消息,主進程通過 ipcMain監聽事件,當事件響應時對消息進行處理。
主進程監聽事件的回調函數中會存在 event 對象及arg 對象。arg 對象為渲染進程傳遞過來的參數。
如果主進程執行的是同步方法,回復同步信息時,需要設置event.returnValue,如果執行的是異步方法回復時需要使用 event.sender.send向渲染進程發送消息。
下面代碼為渲染進程主動向主進程發送消息,在主進程接收后回復渲染進程。
// 主進程
const { ipcMain }=require('electron')
ipcMain.on('asynchronous-message', (event, arg)=> {
console.log(arg) // prints "ping"
event.sender.send('asynchronous-reply', 'pong')
})
ipcMain.on('synchronous-message', (event, arg)=> {
console.log(arg) // prints "ping"
event.returnValue='pong'
})
// 渲染器進程
const { ipcRenderer }=require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg)=> {
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
有時候我們也需要由主進程主動向渲染進程發送消息,面對這種情況我們可以在主進程中通過 BrowserWindow對象的 webContets.send方法向渲染進程發送消息。
// 主進程
const { app, BrowserWindow }=require('electron')
function createWindow () {
let win=new BrowserWindow({ width: 800, height: 600 })
win.loadFile('index.html')
// 向渲染進程發送消息
win.webContents.send('main-process-message', 'ping')
}
app.on('ready', createWindow)
// 渲染器進程
const { ipcRenderer }=require('electron')
// 監聽主進程發送的消息
ipcRenderer.on('main-process-message', (event, arg)=> {
console.log(arg) // prints "ping"
})
在桌面端應用中一些用戶設置通常需要進行存持久化存儲,方便以后使用的時候獲取。 我們做 Web 時候通常是使用像 MySQL、Mongodb等數據庫進行持久化存儲, 但是當用戶安裝桌面軟件時候不可能讓用戶在本地安裝這類數據庫,所以我們需要一個輕量級的本地化數據庫。
lowdb 是一個基于 Lodash API 的輕量級本地 JSON 數據庫,支持 Node.js、browser、Electron。
在我們要開發的工具中,用戶的 S3 配置,已上傳文件的 CDN目錄等信息是需要進行持久化存儲的,所有我們采用的 lowdb進行數據的存儲。
使用也是非常的簡單,數據的讀寫和平常使用 Lodash差不多。
npm install lowdb -save
Electron 提供了獲取系統目錄的方法,可以很方便的進行一些系統目錄的獲取。
const { app, remote }=require('electron')
app.getPath('home'); // 獲取用戶的 home 文件夾(主目錄)路徑
app.getPath('userData'); // 獲取當前用戶的應用數據文件夾路徑
app.getPath('appData'); // 獲取應用程序設置文件的文件夾路徑,默認是 appData 文件夾附加應用的名稱
app.getPath('temp'); // 獲取臨時文件夾路徑
app.getPath('documents'); // 獲取用戶文檔目錄的路徑
app.getPath('downloads'); // 獲取用戶下載目錄的路徑
app.getPath('music'); // 獲取用戶音樂目錄的路徑
app.getPath('pictures'); // 獲取用戶圖片目錄的路徑
app.getPath('videos'); // 獲取用戶視頻目錄的路徑
app.getPath('logs'); // 獲取應用程序的日志文件夾路徑
app.getPath('desktop'); // 獲取系統桌面路徑
'use strict'
const DataStore=require('lowdb')
const FileSync=require('lowdb/adapters/FileSync')
const path=require('path')
const fs=require('fs-extra')
const { app, remote }=require('electron')
const APP=process.type==='renderer' ? remote.app : app
const STORE_PATH=APP.getPath('userData') // 將數據庫存放在當前用戶的應用數據文件夾
if (process.type !=='renderer') {
if (!fs.pathExistsSync(STORE_PATH)) {
fs.mkdirpSync(STORE_PATH)
}
}
const adapter=new FileSync(path.join(STORE_PATH, '/data.json'))
const db=DataStore(adapter)
// 初始化默認數據
db.defaults({
project: [], // 存儲已上傳項目的 CDN 配置信息
settings: {
ftp: '', // ftp 用戶配置
s3: '', // s3 用戶配置
}
}).write()
module.exports=db
由于睿傳是一個命令行工具,并沒有對外提供 Node.js API,所以用戶點擊上傳按鈕時候需要通過 Electron在后臺運行命令行程序,并且將命令行運行的日志實時渲染到應用的日志界面中,所以在這里利用 Node.js 的 child_process子進程的方式來處理。
'use strict'
import { ipcMain } from 'electron'
import { exec } from 'child_process'
import path from 'path'
import fixPath from 'fix-path'
import { logError, logInfo, logExit } from './log'
const cmdPath=path.resolve(__static, 'lib/rapid_trans') // 睿傳路徑
let workerProcess
ipcMain.on('upload', (e, {dirPath, cdnPath, isCover})=> {
runUpload(dirPath, cdnPath, isCover)
})
function runUpload (dirPath, cdnPath, isCover) {
let cmdStr=`node src/rapid-trans.js -s "${dirPath}" -p "${cdnPath}" -q`
if (isCover) {
cmdStr +=' -f'
}
fixPath()
logInfo('==================開始上傳==================\n')
workerProcess=exec(cmdStr, {
cwd: cmdPath
})
workerProcess.stdout.on('data', function (data) {
logInfo(data)
})
workerProcess.stderr.on('data', function (data) {
logError(data)
})
workerProcess.on('close', function (code) {
logExit(code)
logInfo('==================上傳結束==================\n')
})
}
// log.js
'use strict'
const win=global.mainWindow
export function logInfo (msg) {
win.webContents.send('logInfo', msg)
}
export function logError (msg) {
win.webContents.send('logError', msg)
}
export function logExit (msg) {
win.webContents.send('logExit', msg)
}
export default {
logError,
logExit,
logInfo
}
應用開發完成后需要進行打包,我們可以使用 electron-builder 將應用打包成 Windows、Mac 平臺的應用。
在執行npm run build之前需要在 package.json進行打包配置的編輯。
{
"build": {
"productName": "S3上傳工具", // 應用名稱,最終生成的可執行文件的名稱
"appId": "com.autohome.s3", // 應用 APP.ID
"directories": {
"output": "build" // 打包后的輸出目錄
},
"asar": false, // 關閉 asar 格式
"publish": [
{
"provider": "generic", // 服務器提供商
"url": "http://xxx.com:8003/oss" // 更新服務器地址
}
],
"releaseInfo": {
"releaseNotes": "新版更新" // 更新說明
},
"files": [
"dist/electron/**/*",
{
"from": "dist/electron/static/lib/rapid_trans/node_modules",
"to": "dist/electron/static/lib/rapid_trans/node_modules"
} // 將睿傳的依賴打包進應用
],
// 平臺的一些配置
"dmg": {
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
// 應用圖標
"mac": {
"icon": "build/icons/icon.icns"
},
"win": {
"icon": "build/icons/icon.ico"
},
"linux": {
"icon": "build/icons"
}
}
}
由于軟件不進行 App Store 的上架,只在團隊內部使用沒有配置證書,不配置證書的話 Mac 中無法進行自動更新安裝,所以我們在檢測到用戶的當前版本不是最新版本的時候是采用的彈層提示的方式讓用戶自己下載。
使用 electron-updater 打包的應用自動更新非常方便,將打包后 build 目錄下的 latest-mac.yml文件上傳至package.json 中配置的 publish.url 目錄下,并且在主進程文件中監聽 update-availabl事件。
// 主進程 main.js
import { autoUpdater } from 'electron-updater'
// 關閉自動下載
autoUpdater.autoDownload=false
// 應用可更新
autoUpdater.on('update-available', (info)=> {
// 通知渲染進程應用需要更新
mainWindow.webContents.send('updater', info)
})
app.on('ready', ()=> {
if (process.env.NODE_ENV==='production') autoUpdater.checkForUpdates()
})
// 渲染進程 updater.js
import { ipcRenderer, shell } from 'electron'
import { MessageBox } from 'element-ui'
ipcRenderer.on('updater', (e, info)=> {
MessageBox.alert(info.releaseNotes, `請升級${info.version}版本`, {
confirmButtonText: '立即升級',
showClose: false,
closeOnClickModal: false,
dangerouslyUseHTMLString: true,
callback (action) {
if (action==='confirm') {
// 在用戶的默認瀏覽器中打開存放應用安裝包的網絡地址
shell.openExternal('http://10.168.0.49/songjinda/s3_tool/download/')
return false
}
}
})
})
來源:微信公眾號:之家技術
出處:https://mp.weixin.qq.com/s/ttRU7VtgGknzEmeEkObK-g
*請認真填寫需求信息,我們會在24小時內與您取得聯系。