作者:long.woo
檔案下載是我們開發中比較常見的業務需求,比如:匯出 excel,
web 應用檔案下載存在一些局限性,通常是讓后端將回應的頭資訊改成 Content-Disposition: attachment; filename=xxx.pdf,觸發瀏覽器的下載行為,
在 electron 中的下載行為,都會觸發 session 的 will-download 事件,在該事件里面可以獲取到 downloadItem 物件,通過 downloadItem 物件實作一個簡單的檔案下載管理器:

1. 如何觸發下載
由于 electron 是基于 chromium 實作的,通過呼叫 webContents 的 downloadURL 方法,相當于呼叫了 chromium 底層實作的下載,會忽略回應頭資訊,觸發 will-download 事件,
// 觸發下載
win.webContents.downloadURL(url)
// 監聽 will-download
session.defaultSession.on('will-download', (event, item, webContents) => {})
2. 下載流程

3. 功能設計
實作一個簡單的檔案下載管理器功能包含:
- 設定保存路徑
- 暫停/恢復和取消
- 下載進度
- 下載速度
- 下載完成
- 打開檔案和打開檔案所在位置
- 檔案圖示
- 下載記錄
3.1 設定保存路徑
如果沒有設定保存路徑,electron 會自動彈出系統的保存對話框,不想使用系統的保存對話框,可以使用 setSavePath 方法,當有重名檔案時,會直接覆寫下載,
item.setSavePath(path)
為了更好的用戶體驗,可以讓用戶自己選擇保存位置操作,當點擊位置輸入框時,渲染行程通過 ipc 與主行程通信,打開系統檔案選擇對話框,

主行程實作代碼:
/**
* 打開檔案選擇框
* @param oldPath - 上一次打開的路徑
*/
const openFileDialog = async (oldPath: string = app.getPath('downloads')) => {
if (!win) return oldPath
const { canceled, filePaths } = await dialog.showOpenDialog(win, {
title: '選擇保存位置',
properties: ['openDirectory', 'createDirectory'],
defaultPath: oldPath,
})
return !canceled ? filePaths[0] : oldPath
}
ipcMain.handle('openFileDialog', (event, oldPath?: string) => openFileDialog(oldPath))
渲染行程代碼:
const path = await ipcRenderer.invoke('openFileDialog', 'PATH')
3.2 暫停/恢復和取消
拿到 downloadItem 后,暫停、恢復和取消分別呼叫 pause、resume 和 cancel 方法,當我們要洗掉串列中正在下載的項,需要先呼叫 cancel 方法取消下載,
3.3 下載進度
在 DownloadItem 中監聽 updated 事件,可以實時獲取到已下載的位元組資料,來計算下載進度和每秒下載的速度,
// 計算下載進度
const progress = item.getReceivedBytes() / item.getTotalBytes()

在下載的時候,想在 Mac 系統的程式塢和 Windows 系統的任務欄展示下載資訊,比如:
- 下載數:通過 app 的 badgeCount 屬性設定,當為 0 時,不會顯示,也可以通過 dock 的 setBadge 方法設定,該方法支持的是字串,如果不要顯示,需要設定為 '',
- 下載進度:通過視窗的 setProgressBar 方法設定,
由于 Mac 和 Windows 系統差異,下載數僅在 Mac 系統中生效,加上 process.platform === 'darwin' 條件,避免在非 Mac、Linux 系統下出現例外錯誤,
下載進度(Windows 系統任務欄、Mac 系統程式塢)顯示效果:


// mac 程式塢顯示下載數:
// 方式一
app.badgeCount = 1
// 方式二
app.dock.setBadge('1')
// mac 程式塢、windows 任務欄顯示進度
win.setProgressBar(progress)
3.4 下載速度
由于 downloadItem 沒有直接為我們提供方法或屬性獲取下載速度,需要自己實作,
思路:在 updated 事件里通過 getReceivedBytes 方法拿到本次下載的位元組資料減去上一次下載的位元組資料,
// 記錄上一次下載的位元組資料
let prevReceivedBytes = 0
item.on('updated', (e, state) => {
const receivedBytes = item.getReceivedBytes()
// 計算每秒下載的速度
downloadItem.speed = receivedBytes - prevReceivedBytes
prevReceivedBytes = receivedBytes
})
需要注意的是,updated 事件執行的時間約 500ms 一次,

3.5 下載完成
當一個檔案下載完成、中斷或者被取消,需要通知渲染行程修改狀態,通過監聽 downloadItem 的 done 事件,
item.on('done', (e, state) => {
downloadItem.state = state
downloadItem.receivedBytes = item.getReceivedBytes()
downloadItem.lastModifiedTime = item.getLastModifiedTime()
// 通知渲染行程,更新下載狀態
webContents.send('downloadItemDone', downloadItem)
})
3.6 打開檔案和打開檔案所在位置
使用 electron 的 shell 模塊來實作打開檔案(openPath)和打開檔案所在位置(showItemInFolder),
由于 openPath 方法支持回傳值
Promise<string>,當不支持打開的檔案,系統會有相應的提示,而 showItemInFolder 方法回傳值是void,如果需要更好的用戶體驗,可使用 nodejs 的 fs 模塊,先檢查檔案是否存在,
import fs from 'fs'
// 打開檔案
const openFile = (path: string): boolean => {
if (!fs.existsSync(path)) return false
shell.openPath(path)
return true
}
// 打開檔案所在位置
const openFileInFolder = (path: string): boolean => {
if (!fs.existsSync(path)) return false
shell.showItemInFolder(path)
return true
}
3.7 檔案圖示
很方便的是使用 app 模塊的 getFileIcon 方法來獲取系統關聯的檔案圖示,回傳的是 Promise<NativeImage> 型別,我們可以用 toDataURL 方法轉換成 base64,不需要我們去處理不同檔案型別顯示不同的圖示,
const getFileIcon = async (path: string) => {
const iconDefault = './icon_default.png'
if (!path) Promise.resolve(iconDefault)
const icon = await app.getFileIcon(path, {
size: 'normal'
})
return icon.toDataURL()
}
3.8 下載記錄
隨著下載的歷史資料越來越多,使用 electron-store 將下載記錄保存在本地,
對 Electron 感興趣?請關注我們的開源專案 Electron Playground,帶你極速上手 Electron,
我們每周五會精選一些有意思的文章和訊息和大家分享,來掘金關注我們的 曉前端周刊,
我們是好未來 · 曉黑板前端技術團隊,
我們會經常與大家分享最新最酷的行業技術知識,
歡迎來 知乎、掘金、Segmentfault、CSDN、簡書、開源中國、博客園 關注我們,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/236472.html
標籤:其他
上一篇:Taro 周報 #7: 識訓「e代駕」案例,發布 v2.2.16 和 v3.2.0-canary.2
下一篇:JS 終止執行的方法
