likes
comments
collection
share

【Electron】实现下载、上传、系统提示功能(dialog模块)

作者站长头像
站长
· 阅读数 40

一、前言

本文主要介绍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 【Electron】实现下载、上传、系统提示功能(dialog模块)

之后我们将这个文件在主进程中引入即可

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 {
      // 这里是退出
    }
})

【Electron】实现下载、上传、系统提示功能(dialog模块)

这样就实现了如图所示的系统提示窗。(个人感觉啊,系统提示框慎用,因为用户会觉得,你的应用问题大大的)

以下是官网中参数的解析

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
评论
请登录