做者:long.woojavascript
文件下載是咱們開發中比較常見的業務需求,好比:導出 excel。前端
web 應用文件下載存在一些侷限性,一般是讓後端將響應的頭信息改爲 Content-Disposition: attachment; filename=xxx.pdf
,觸發瀏覽器的下載行爲。java
在 electron 中的下載行爲,都會觸發 session 的 will-download 事件。在該事件裏面能夠獲取到 downloadItem 對象,經過 downloadItem 對象實現一個簡單的文件下載管理器:
node
因爲 electron 是基於 chromium 實現的,經過調用 webContents 的 downloadURL 方法,至關於調用了 chromium 底層實現的下載,會忽略響應頭信息,觸發 will-download 事件。linux
// 觸發下載 win.webContents.downloadURL(url) // 監聽 will-download session.defaultSession.on('will-download', (event, item, webContents) => {})
實現一個簡單的文件下載管理器功能包含:git
若是沒有設置保存路徑,electron 會自動彈出系統的保存對話框。不想使用系統的保存對話框,可使用 setSavePath 方法,當有重名文件時,會直接覆蓋下載。github
item.setSavePath(path)
爲了更好的用戶體驗,可讓用戶本身選擇保存位置操做。當點擊位置輸入框時,渲染進程經過 ipc 與主進程通訊,打開系統文件選擇對話框。
主進程實現代碼:web
/** * 打開文件選擇框 * @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))
渲染進程代碼:typescript
const path = await ipcRenderer.invoke('openFileDialog', 'PATH')
拿到 downloadItem 後,暫停、恢復和取消分別調用 pause
、resume
和 cancel
方法。當咱們要刪除列表中正在下載的項,須要先調用 cancel 方法取消下載。shell
在 DownloadItem 中監聽 updated 事件,能夠實時獲取到已下載的字節數據,來計算下載進度和每秒下載的速度。
// 計算下載進度 const progress = item.getReceivedBytes() / item.getTotalBytes()
在下載的時候,想在 Mac 系統的程序塢和 Windows 系統的任務欄展現下載信息,好比:
因爲 Mac 和 Windows 系統差別,下載數僅在 Mac 系統中生效。加上 process.platform === 'darwin' 條件,避免在非 Mac、Linux 系統下出現異常錯誤。
下載進度(Windows 系統任務欄、Mac 系統程序塢)顯示效果:
// mac 程序塢顯示下載數: // 方式一 app.badgeCount = 1 // 方式二 app.dock.setBadge('1') // mac 程序塢、windows 任務欄顯示進度 win.setProgressBar(progress)
因爲 downloadItem 沒有直接爲咱們提供方法或屬性獲取下載速度,須要本身實現。
思路:在 updated 事件裏經過 getReceivedBytes 方法拿到本次下載的字節數據減去上一次下載的字節數據。
// 記錄上一次下載的字節數據 let prevReceivedBytes = 0 item.on('updated', (e, state) => { const receivedBytes = item.getReceivedBytes() // 計算每秒下載的速度 downloadItem.speed = receivedBytes - prevReceivedBytes prevReceivedBytes = receivedBytes })
須要注意的是,updated 事件執行的時間約 500ms 一次。
當一個文件下載完成、中斷或者被取消,須要通知渲染進程修改狀態,經過監聽 downloadItem 的 done 事件。
item.on('done', (e, state) => { downloadItem.state = state downloadItem.receivedBytes = item.getReceivedBytes() downloadItem.lastModifiedTime = item.getLastModifiedTime() // 通知渲染進程,更新下載狀態 webContents.send('downloadItemDone', downloadItem) })
使用 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 }
很方便的是使用 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() }
隨着下載的歷史數據愈來愈多,使用 electron-store 將下載記錄保存在本地。
對 Electron 感興趣?請關注咱們的開源項目 Electron Playground,帶你極速上手 Electron。
咱們每週五會精選一些有意思的文章和消息和你們分享,來掘金關注咱們的 曉前端週刊。
咱們是好將來 · 曉黑板前端技術團隊。
咱們會常常與你們分享最新最酷的行業技術知識。
歡迎來 知乎、掘金、Segmentfault、CSDN、簡書、開源中國、博客園 關注咱們。