【Electron】实现下载、上传、系统提示功能(dialog模块)
一、前言
本文主要介绍electron应用的dialog模块的适用场景,主要包括下载保存、文件上传、消息提示等场景。其中上传下载配合使用node的request模块。
二、实施内容
1.实现下载功能
这里我们主要用到的是electron的dialog模块中的showSaveDialog
方法。主要实现方式如下,这里我们创建一个ElectronDownloadFile.js文件,用于封装下载方法
import { ipcMain, dialog } from 'electron'
import path from 'path'
const fs = require('fs')
const request = require('request')
let _win // 当前窗口实例
let isMac = process.platform === 'darwin' // 判断是否为macOS
export function downloadFile (win) {
// 解决M1芯片可能会报证书失效问题
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
_win = win
// 创建监听事件,接收来自渲染进程的下载请求
// file参数为一个对象{name: '文件名,例***.jpg', url: '文件远端路径'}
ipcMain.on('downloadFile', (event, file) => {
/* 为解决文件名包含特殊字符引起无发下载 --- start */
var reg = /\\|\/|\?|\?|\*|\"|\“|\”|\'|\‘|\’|\<|\>|\{|\}|\[|\]|\[|\]|\:|\:|\^|\$|\!|\~|\`|\|/g
const fileNameSplitArr = file.name.split('/')
fileNameSplitArr[fileNameSplitArr.length - 1] = fileNameSplitArr[fileNameSplitArr.length - 1].replace(reg, '')
file.name = fileNameSplitArr.join('')
/* 为解决文件名包含特殊字符引起无发下载 --- end */
// 这里设置了保存弹出框的默认名称(如下图1),设置openDirectory表示可以选择文件夹
dialog.showSaveDialog(win, { defaultPath: file.name, properties: ['openDirectory'] }).then(result => {
// 承载文件源地址
const fileURL = file.url
// 这里获取用户通过弹框做选择的保存至本地的地址
if (result.filePath.length !== 0) {
let localPath = result.filePath
if (!isMac) { // 处理windows系统反斜杠问题
const arr = localPath.split(path.sep)
localPath = arr.reduce((pre, cue) => {
return pre ? `${pre}//${cue}` : cue
}, '')
}
// 最终处理好写入本地文件路径,去下载了
toDownloadFile(fileURL, localPath)
}
}).catch(err => {
console.log(err)
})
})
}
const toDownloadFile (file_url, targetPath) => {
var received_bytes = 0 // 当前下载量
var total_bytes = 0 // 文件总大小
var req = request({
method: 'GET',
uri: file_url,
strictSSL: false,
rejectUnauthorized: false
})
// 下载中命名后加:.download,提示用户未下完
var out = fs.createWriteStream(targetPath + '.download')
req.pipe(out)
req.on('response', (data) => {
// 获取文件大小
total_bytes = parseInt(data.headers['content-length'])
})
req.on('data', (chunk) => {
// 更新下载进度
received_bytes += chunk.length
showProgress(received_bytes, total_bytes, file_url)
})
// 下载完成
req.on('end', () => {
const oldPath = targetPath + '.download'
if (_win) { // 判断当前窗口实例存在
// 异步改名,如果异常则e有值,抛出downloaded_rename_error,在Home.vue处理,
fs.rename(oldPath, targetPath, (e) => {
if (!e) {
// eslint-disable-next-line camelcase
_win.webContents.send('downloaded' + file_url, targetPath)
} else {
_win.webContents.send('downloaded_rename_error', e, targetPath)
}
})
}
})
}
const showProgress (received, total, file_url) => {
var percentage = (received * 100) / total
if (_win) {
// 更新下载进度给渲染进程, 这里注意加上file_url为了防止,多个文件同时下载,让渲染进程进行分辨的,只监听自己的下载进度
_win.webContents.send('download-progress' + file_url, percentage)
}
}
图1
之后我们将这个文件在主进程中引入即可
import { downloadFile } from './ElectronDownloadFile'
// 在创建完窗口实例之后调用此方法
// 监听文件下载
downloadFile(win)
在渲染进程调用,如下
const { ipcRenderer } = require('electron')
ipcRenderer.send('downloadFile', {
url: '下载地址',
name: '文件名字(要带类型后缀哦)'
})
ipcRenderer.on('download-progress' + url, (evt, percent) => {
console.log('文件下载进度===',percent)
})
这样 我们就完成了下载的功能。
以下是 showSaveDialog
的参数介绍
dialog.showSaveDialog([browserWindow, ]options)
-
browserWindow
BrowserWindow (可选) -
选项
对象-
title
string (可选) - 对话框标题。 无法在一些 Linux 桌面环境中显示。 -
defaultPath
string (可选) - 默认情况下使用的绝对目录路径、绝对文件路径或文件名。 -
buttonLabel
string (可选) - 「确认」按钮的自定义标签, 当为空时, 将使用默认标签。 -
filters
FileFilter[] (可选) -
message
string (可选) macOS -显示在对话框上的消息。 -
nameFieldLabel
string (可选) macOS - 文件名输入框对应的自定义标签名。 -
showsTagField
boolean (可选) macOS - 显示标签输入框,默认为true
。 -
properties
string[] (可选)showHiddenFiles
-显示对话框中的隐藏文件。createDirectory
macOS -允许你通过对话框的形式创建新的目录。treatPackageAsDirectory
macOS -将包 (如.app
文件夹) 视为目录而不是文件。showOverwriteConfirmation
Linux - 设置如果用户输入了已存在的文件名,是否会向用户显示确认对话框。dontAddToRecent
Windows - 不要将正在保存的项目添加到最近的文档列表中。
-
securityScopedBookmarks
boolean (可选) macOS mas - 在打包提交到Mac App Store时创建 security scoped bookmarks 当该选项被启用且文件尚不存在时,那么在选定的路径下将创建一个空文件。
-
返回 Promise<Object>
- resolve包含以下内容的object:
canceled
boolean - 对话框是否被取消。filePath
string (可选) - 如果对话框被取消,该值为undefined
。bookmark
string(optional) macOS mas - 包含了安全作用域的书签数据 Base64 编码的字符串来保存文件。securityScopedBookmarks
必须启用才有效。 (返回值见 这里的表格。)
browserWindow
参数允许该对话框将自身附加到父窗口, 作为父窗口的模态框。
filters
可以指定可显示文件的数组类型,详见 dialog.showOpenDialog
事例
注意: 在macOS上,建议使用异步版本,以避免展开和折叠对话框时出现问题。
2.实现上传功能
这里我们主要用到的是electron的dialog模块中的showOpenDialog
方法。主要实现方式如下,这里我们创建一个ElectronUploadfile.js文件,用于封装下载方法
// ElectronUploadfile.js
import { ipcMain, dialog } from 'electron'
const fs = require('fs')
const request = require('request')
export const uploadFile = (win) => {
// 解决M1芯片可能会报证书失效问题
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
// fileType是类型数组,具体类型见下方介绍
ipcMain.handle('uploadFile', async (event, fileType) => {
let backData = false // 返回值
const filters = [] // 筛选文件选择类型
if (fileType && fileType.length > 0) {
fileType.forEach((item) => {
if (item === 'img') {
const obj = { name: 'Images', extensions: ['jpg', 'png', 'jpeg'] }
filters.push(obj)
}
})
}
console.log(filters)
// 这里使用异步弹窗方法,参数解析见下方
const result = await dialog.showOpenDialog({ properties: ['openFile', 'createDirectory', 'promptToCreate'], filters })
const uploadFolder = result.filePaths[0]
if (uploadFolder) {
// 设置上传接口地址
var uploadUrl = process.env.VUE_APP_ACTIONUPLOAD
var formData = {
file: fs.createReadStream(uploadFolder)
}
// 调用封装好的request上传方法
const body = await fetchPost(uploadUrl, formData)
backData = JSON.parse(body)
}
return backData
})
}
/*
multipart/form-data (上传文件)
*/
const fetchPost = (path, params) => {
return new Promise((resolve, reject) => {
request.post({
url: path,
method: 'POST',
formData: params,
headers: { // 若有的话,可以设置 }
}, (err, httpResponse, body) => {
if (err) {
reject(err)
} else {
resolve(body)
}
})
})
}
然后,我们在主进程中引入一下这个方法
import { uploadFile } from './ElectronUploadfile'
// 文件上传--win是当前窗口实例
uploadFile(win)
之后是在选然进程调用
const { ipcRenderer } = require('electron')
// 打开头像上传弹窗
async changeAvatar () {
const { data } = await ipcRenderer.invoke('uploadFile', ['img'])
console.log(data)
}
以上 就完成了上传功能。
下面是 showOpenDialog
的参数介绍
dialog.showOpenDialog([browserWindow, ]options)
-
browserWindow
BrowserWindow (可选) -
选项
对象-
title
string (可选) - 对话框窗口的标题 -
defaultPath
string (可选) - 对话框的默认展示路径 -
buttonLabel
string (可选) - 「确认」按钮的自定义标签, 当为空时, 将使用默认标签。 -
filters
FileFilter[] (可选) -
properties
string[] (可选) - 包含对话框相关属性。 支持以下属性值:openFile
- 允许选择文件openDirectory
- 允许选择文件夹multiSelections
-允许多选。showHiddenFiles
-显示对话框中的隐藏文件。createDirectory
macOS -允许你通过对话框的形式创建新的目录。promptToCreate
Windows-如果输入的文件路径在对话框中不存在, 则提示创建。 这并不是真的在路径上创建一个文件,而是允许返回一些不存在的地址交由应用程序去创建。noResolveAliases
macOS-禁用自动的别名路径(符号链接) 解析。 所选别名现在将会返回别名路径而非其目标路径。treatPackageAsDirectory
macOS -将包 (如.app
文件夹) 视为目录而不是文件。dontAddToRecent
Windows - 不要将正在打开的项目添加到最近的文档列表中。
-
message
string (可选) macOS -显示在输入框上方的消息。 -
securityScopedBookmarks
boolean (可选) macOS mas - 在打包提交到Mac App Store时创建 security scoped bookmarks
-
返回 Promise<Object>
- resolve包含以下内容的object:
canceled
boolean - 对话框是否被取消。filePaths
string[] - 用户选择的文件路径的数组. 如果对话框被取消,这将是一个空的数组。bookmarks
string[] (optional) macOS mas - 一个数组,filePaths
数组,base64编码字符串包含安全范围书签数据。securityScopedBookmarks
必须启用才能捕获数据。 (返回值见 这里的表格。)
browserWindow
参数允许该对话框将自身附加到父窗口, 作为父窗口的模态框。
filters
指定一个文件类型数组,用于规定用户可见或可选的特定类型范围。 例如:
{
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
{ name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
{ name: 'Custom File Type', extensions: ['as'] },
{ name: 'All Files', extensions: ['*'] }
]
}
extensions
数组应为没有通配符或点的扩展名 (例如, "png"
是正确的, 而 ".png"
和 *. png "
就是错误的)。 若要显示所有文件, 请使用 "*"
通配符 (不支持其他通配符)。
注意: 在 Windows 和 Linux 上, 打开对话框不能同时是文件选择器和目录选择器, 因此如果在这些平台上将 properties
设置为["openFile"、"openDirectory"]
, 则将显示为目录选择器。
dialog.showOpenDialog(mainWindow, {
properties: ['openFile', 'openDirectory']
}).then(result => {
console.log(result.canceled)
console.log(result.filePaths)
}).catch(err => {
console.log(err)
})
3.实现系统提示弹出窗
这里我们主要用到的是electron的dialog模块中的showMessageBox
方法。主要实现方式如下
const options = {
type: 'error',
title: '进程崩溃了',
message: '这个进程已经崩溃.',
buttons: ['重载', '退出']
}
dialog.showMessageBox(options).then(({ response }) => {
if (response === 0) {
// 这里是重载
} else {
// 这里是退出
}
})
这样就实现了如图所示的系统提示窗。(个人感觉啊,系统提示框慎用,因为用户会觉得,你的应用问题大大的)
以下是官网中参数的解析
dialog.showMessageBox([browserWindow, ]options)
-
browserWindow
BrowserWindow (可选) -
选项
对象message
string - message box 的内容.type
string (可选) - 可以为"none"
,"info"
,"error"
,"question"
或者"warning"
. 在 Windows 上,"question"
与"info"
显示相同的图标, 除非你使用了"icon"
选项设置图标。 在 macOS 上,"warning"
和"error"
显示相同的警告图标buttons
string[] (可选) - 按钮文本数组。 在 Windows上,一个空数组将导致按钮被标为“OK”。defaultId
Integer (可选) - 在 message box 对话框打开的时候,设置默认选中的按钮,值为在 buttons 数组中的索引.signal
AbortSignal (可选) - 通过 AbortSignal 信号实例可选地关闭消息框,消息框的行为就像用户点击取消一样。 在 macOS,signal
不适用于没有父窗口的消息框。因为平台限制,这些消息框同步运行title
string (可选) - message box 的标题,一些平台不显示.detail
string (可选) - 额外信息.checkboxLabel
string (可选) - 如果使用了,消息框将包含带有给定标签的复选框。checkboxChecked
boolean (可选) - checkbox 的初始值。 默认值为false
icon
(NativeImage | string) (可选)textWidth
Integer (可选) macOS - 自定义消息框中文本的宽度cancelId
Integer (可选) - 用于取消对话框的按钮的索引,例如Esc
键. 默认情况下,它被分配给第一个按钮,文字为 “cancel” 或 “no”。 如果不存在这个标签的按钮,同时该选项又未设置,返回值为0
。noLink
boolean (可选) - 在Windows上,应用将尝试找出哪个buttons
是常用按钮(例如 "Cancel" 或 "Yes"),然后在对话框中以链接命令的方式展现其它的按钮。 这可以使对话框以现代Windows应用程序的风格显示。 如果你不喜欢这个行为, 你可以设置noLink
为true
.normalizeAccessKeys
boolean (可选) -规范跨平台的键盘访问键。 默认值为false
. 用&
连接和转换键盘访问键, 以便它们在每个平台上正常工作.&
字符会在macOS上被删除,在 Linux 上会被转换为_
,在 Windows 上保持不变。 例如Vie&w
的按钮标签在 Linux 上会被转换为Vie_w
,在 macOS 转换为View
并且可以被选择。而Windows和Linux上表示Alt-W
。
返回 Promise<Object>
- resolve包含以下属性的promise:
response
number - 点击的按钮的索引。checkboxChecked
boolean - 如果设置了checkboxLabel
,返回复选框是否被选中的状态。 否则,返回false
。
显示一个消息框
browserWindow
参数允许该对话框将自身附加到父窗口, 作为父窗口的模态框。
三、后记
今天我们实现了electron应用的上传、下载、系统提示的这三个功能, 都是用dialog模块配合request去实现的。其实在渲染进程中有些功能也可以实现的,比如上传、下载。咱们这里下载的优势在于,可以返回下载进度,可以对下载弹出框做设置,这些是渲染进程中不好实现或不能实现的;上传的优势,在于可以减少代码量,不用写对应的上传组件。
本篇完结! 撒花 ! 谢谢观看! 希望能帮助到你!
转载自:https://juejin.cn/post/7140085468917399583