做這個真的要瘋掉,但是開始了就要做出來。
下面就說一下整個過程吧,因爲真的查閱了不少資料,發現似乎沒幾個桌面應用會選擇做下載文件的,所以需要一直查資料查資料,查到要瘋掉。
進入正題
初識electron
electron文檔:https://github.com/electron/electron
文檔裏面有中文的,所以還是蠻容易理解的。
文檔主要就是介紹electron項目的結構如下:
其中package.json
是用來描述一些配置信息以及一些快速啓動的指令等信息的
main.js
則是用來整個項目的主線程,用於創建窗口和處理系統事件。
index.html
就是佈局啦。
運行
package.json
文件裏有這樣一句描述:
這就是快速啓動指令,只需要npm start
就可以啓動你的electron應用了。
創建渲染器進程
對於渲染器進程,大家給的理解是主線程是無法顯示的,需要通過BrowserWindow
來創建新的窗口,每一個窗口維護一個渲染器進程。
我的文件目錄如下:
app目錄下的js/index.js
就可以看作一個渲染器進程。
主進程和渲染器進程的區別
界面
界面可以通過html+css實現
我的界面如下:
選擇文件存放文件夾
此處需要用到electron的一個模塊dialog
,但是由於其爲主線程內可用的模塊,所以在渲染器進程使用的時候必須加上.remote
。
remote 模塊提供了一種在渲染進程(網頁)和主進程之間進行進程間通訊(IPC)的簡便途徑。
所以在渲染器進程處加上以下語句獲取dialog模塊
const {dialog} = require('electron').remote
選擇文件夾具體語句如下
button_choose.addEventListener("click", function(){
dialog.showOpenDialog({
//默認路徑
defaultPath :'../Desktop',
//選擇操作,此處是打開文件夾
properties: [
'openDirectory',
],
//過濾條件
filters: [
{ name: 'All', extensions: ['*'] },
]
},function(res){
//回調函數內容,此處是將路徑內容顯示在input框內
downloadFolder.value = res[0];
})
});
實現下載文件
這就是我最爆炸的部分。完成這個應用的一半時間都貢獻給了這部分。
先進行簡單的校驗
button_download.addEventListener("click", function(){
var tips = document.getElementsByClassName("tips")[0];
if(downloadFolder.value!=""
&&downloadAddress.value!="") {
//下載文件
} else if(downloadAddress.value=="") {
tips.innerText = "未填寫下載地址";
} else {
tips.innerText = "未選擇文件夾"
}
})
下載文件我使用的是will-download
,尋找一個合適的模塊實現真的很心累,這裏就不贅述了。
由於will-download
是主進程的模塊調用的,所以此處就需要主進程和渲染進程的通信。
ipcMain和ipcRenderer
通信原理如下:
// 後臺進程
const {ipcMain} = require('electron')
ipcMain.on('create', (event, person) => {
console.log('creating', person) // 輸出:"creating harttle"
event.sender.send('born', person)
});
// 渲染進程
const {ipcRenderer} = require('electron')
ipcRenderer.on('born', (event, person) => {
console.log(person, 'born') // 輸出 "harttle born"
});
ipcRenderer.send('create', 'harttle')
所以我的想法是,在主進程裏監聽一個download
事件,在渲染進程裏當點擊下載時調用這個事件。
//主進程代碼
ipcMain.on('download', (evt, args) => {
var arr = args.split("+");
downloadpath = arr[0];
folderpath = arr[1];
evt.sender.send('tips',downloadpath);
mainWindow.webContents.downloadURL(downloadpath);
});
//渲染器進程代碼
ipcRenderer.send('download',downloadAddress.value+"+"+downloadFolder.value);
下面這句會觸發will-download
事件
mainWindow.webContents.downloadURL(downloadpath);
下面來設置監聽will-download
事件的回調函數
mainWindow.webContents.session.on('will-download', (event, item, webContents) => {
//設置文件存放位置
item.setSavePath(folderpath+`\\${item.getFilename()}`);
item.on('updated', (event, state) => {
if (state === 'interrupted') {
console.log('Download is interrupted but can be resumed')
} else if (state === 'progressing') {
if (item.isPaused()) {
console.log('Download is paused')
} else {
console.log(`Received bytes: ${item.getReceivedBytes()}`)
}
}
})
item.once('done', (event, state) => {
if (state === 'completed') {
console.log('Download successfully')
} else {
console.log(`Download failed: ${state}`)
}
})
})
ipc到ipcMain和ipcRender
其實我一開始使用的是ipc進行兩個進程之間的通信,但是不論我怎麼定義,ipc總是報錯,報其沒有send和on方法。
又瘋狂找資料,遇到這個問題的人還真不多,所以沒有找到解決方法。
但是後來發現,與主進程的通信可以使用ipcMain,所以就棄用ipc了。
找不到爲什麼錯真的很傷自尊啊,希望下次可以發現。
主進程更改後刷新頁面沒有改變
這個的解決方法是退出應用然後重新npm start一次。
這個很好理解吧,重新刷新頁面只是更新了渲染器進程而非主進程。
item.setSavePath()問題
這個方法接受一個參數即文件存放路徑。
但是!一定保證路徑裏包含了文件名!一定!
否則,就算路徑不正確它也不會報錯而是選擇存放在默認路徑下。
ipcMain和ipcRender的傳參問題
ipcMain/ipcRender.send(eventname, arg);
此處的arg
是一個參數而不是參數數組。
所以由於要傳下載地址和文件存放地址,我選擇使用“+”把它們連接起來。
總結:
除了找出錯在哪裏其他地方還是蠻有趣的吧。
感覺electron的機制和redux還是蠻像的,其中有主進程負責處理系統調用,而渲染進程負責觸發事件。