我们使用electron下载文件时,会发现不像浏览器一样会有地方展示下载进度,这导致下载一些大文件时不知道下载进度到哪里了
下面我们通过electron提供的will-download
监听和element-plus
中的ElNotification
和ElProgress
组件实现这一功能
实现逻辑
- 触发下载文件这一操作
- 监听下载开始、下载进度、下载结束
- 根据监听内容操作vnode展示加载进度
1、触发下载文件这一操作
使用electron中ipcMain
模块接受页面中发送来的指令
// main.js 部分代码 通过这个打开****.vue界面
var win = new BrowserWindow();
// download.js 部分代码
const { ipcMain, dialog } = require('electron')
let filePath = '';
// 监听渲染进程发出的download事件
const webContents = win.webContents;
ipcMain.on('download', (evt, args) => {
const fileArr = args.split("/");
let ext = path.extname(args)
let filters = [{ name: '全部文件', extensions: ['*'] }]
if (ext && ext !== '.') {
filters.unshift({
name: '',
extensions: [ext.match(/[a-zA-Z]+$/)[0]]
})
}
dialog.showSaveDialog(win, {
filters,
defaultPath:args
}).then( res => {
if(res.filePath){
filePath = res.filePath
webContents.downloadURL(args) // 注意这里必须是下载文件可访问路径。而不是存储路径
}
})
})
// vue页面中的逻辑 ***.vue
import { ipcRenderer } from "electron"
// 触发
var url = '下载地址.***'
ipcRenderer.send('download', url)
执行完上面的代码会弹窗另存为下载,会发现下载没有显示进度的地方
2、监听下载开始、下载进度、下载结束
监听webContents中的will-download事件会返回下载相关的一些信息,这里把下载过程中的一些状态和用到的一些参数发送给webContents中打开的页面
// download.js 部分代码
webContents.session.on('will-download', (event, item, webContents) => {
item.setSavePath(filePath) // 这里是存储路径或者存储文件名称
var filName = path.basename(filePath)
win.webContents.send('win-will-download',{type:'start',params:{filName}})
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 {
win.webContents.send('win-will-download',{type:"progress",params:{
totalBytes:item.getTotalBytes(),
receivedBytes:item.getReceivedBytes()
}})
}
}
})
item.once('done', (event, state) => {
if (state === 'completed') {
win.webContents.send('win-will-download',{type:"completed"})
console.log('Download successfully')
} else {
console.log(`Download failed: ${state}`)
}
})
})
// ***.vue
//download是展示下载进度的组件,下面会有相应的代码 这里通过ref创建响应数据 vue2的话可以通过 Vue.observable API进行创建
import download from "@/components/download/index.vue"
import { ElNotification } from 'element-plus'
import { h, ref } from 'vue'
var totalBytes = ref(0)
var receivedBytes = ref(0)
mounted(){
ipcRenderer.removeAllListeners('win-will-download')
ipcRenderer.on('win-will-download', this.winDownLoadFun)
},
methods:{
winDownLoadFun(event, data) {
if (data.type == 'start') {
this.notice && this.notice.close && this.notice.close()
var progress = h(download, {
totalBytes: totalBytes,
receivedBytes: receivedBytes,
filName: data.params.filName,
onClose: () => {
totalBytes.value = 0
receivedBytes.value = 0
this.notice && this.notice.close && this.notice.close()
}
})
this.notice = ElNotification({
title: '下载进度',
position: 'bottom-right',
duration: 0,
showClose: false,
message: progress,
onClose: () => {
this.notice = null
}
})
}
else if (data.type == 'progress') {
totalBytes.value = data.params.totalBytes
receivedBytes.value = data.params.receivedBytes
} else if (data.type == 'completed') {
receivedBytes.value = totalBytes.value
}
},
}
3、根据监听内容操作vnode展示加载进度
下面是download/index.vue完整文件
<template>
<div style="width: 100%;">
<div>
<div @click="$emit('close')" v-if="percentage == 100" style="position: absolute;top: 15px;right: 15px;cursor: pointer;">关闭</div>
<div class="task-item">
<img class="img" src="@/assets/image/zip-1.png"></img>
<div class="name">
<div>{{filName}}</div>
<div class="progress1">{{limitFormat(receivedBytes.value)}}/{{limitFormat(totalBytes.value)}}</div>
</div>
</div>
<div>
<el-progress :show-text="false" :percentage="percentage" />
</div>
</div>
</div>
</template>
<script>
import { ElMessage, ElProgress } from 'element-plus'
import { ref } from 'vue'
export default {
name: 'download',
props: {
filName:{
type:String,
default:""
},
totalBytes: {
default() {
return ref(0)
}
},
receivedBytes: {
default() {
return ref(0)
}
}
},
computed:{
percentage(){
return parseFloat((((this.receivedBytes.value / this.totalBytes.value) ||0 )* 100).toFixed(2))
}
},
watch:{
percentage(){
if(this.percentage == 100 && this.totalBytes.value != 0){
ElMessage({
message:"下载完成!",
type:"success"
})
}
},
},
methods: {
limitFormat(limit) {
var size = "";
if (limit < 0.1 * 1024) { //小于0.1KB,则转化成B
size = limit.toFixed(2) + "B"
} else if (limit < 0.1 * 1024 * 1024) { //小于0.1MB,则转化成KB
size = (limit / 1024).toFixed(2) + "KB"
} else if (limit < 0.1 * 1024 * 1024 * 1024) { //小于0.1GB,则转化成MB
size = (limit / (1024 * 1024)).toFixed(2) + "MB"
} else { //其他转化成GB
size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB"
}
var sizeStr = size + ""; //转成字符串
var index = sizeStr.indexOf("."); //获取小数点处的索引
var dou = sizeStr.substr(index + 1, 2) //获取小数点后两位的值
if (dou == "00") { //判断后两位是否为00,如果是则删除00
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size;
}
},
}
</script>
<style scoped>
.task-item {
width: 280px;
display: flex;
align-items: center;
margin-bottom: 6px;
}
.progress1 {
font-size: 12px;
margin-top: -4px;
color: #999;
}
.task-item i {}
.img {
width: 32px;
height: 32px;
display: block;
margin-right: 14px;
}
</style>
download.js完整代码
const { ipcMain, dialog, shell } = require('electron')
const path = require('path')
const fs = require('fs');
const { type } = require('os');
exports.initDownload = function (win) {
let filePath = '';
// 监听渲染进程发出的download事件
const webContents = win.webContents;
ipcMain.on('download', (evt, args) => {
const fileArr = args.split("/");
let ext = path.extname(args)
let filters = [{ name: '全部文件', extensions: ['*'] }]
if (ext && ext !== '.') {
filters.unshift({
name: '',
extensions: [ext.match(/[a-zA-Z]+$/)[0]]
})
}
dialog.showSaveDialog(win, {
filters,
defaultPath:args
}).then( res => {
if(res.filePath){
filePath = res.filePath
webContents.downloadURL(args) // 注意这里必须是下载文件可访问路径。而不是存储路径
}
})
})
webContents.session.on('will-download', (event, item, webContents) => {
item.setSavePath(filePath) // 这里是存储路径或者存储文件名称
var filName = path.basename(filePath)
win.webContents.send('win-will-download',{type:'start',params:{filName}})
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 {
win.webContents.send('win-will-download',{type:"progress",params:{
totalBytes:item.getTotalBytes(),
receivedBytes:item.getReceivedBytes()
}})
}
}
})
item.once('done', (event, state) => {
if (state === 'completed') {
win.webContents.send('win-will-download',{type:"completed"})
console.log('Download successfully')
} else {
console.log(`Download failed: ${state}`)
}
})
})
}
main.js中使用代码
这里的main.js是electron的主进程文件,不是vue相关的问题
const { BrowserWindow } = require("electron");
const {initDownload} = require('@/utils/download.js')
var win = null;
async function createWindow() {
win = new BrowserWindow({
// 创建相关的参数
});
// 为创建的win窗口绑定下载事件
initDownload(win)
}
createWindow()