前端 Vue 操作文件方法(导出下载、图片压缩、文件上传和转换)

news2024/11/23 21:52:16

一、前言

    本文对前端 Vue 项目开发过程中,经常遇到要对文件做一些相关操作,比如:文件导出下载、文件上传、图片压缩、文件转换等一些处理方法进行归纳整理,方便后续查阅和复用。

二、具体内容

1、后端的文件导出接口,返回数据是文件流 blob,转成 url 链接下载

浏览器 F12 调试器打开查看,返回数据长这样的。

注意:记得在定义的接口,响应头部加上 responseType: 'blob'

import request from '@/utils/request' // 一般基于 axios 封装的 request

export function exportApi(data) { // 导出下载接口
  return request({
    url: '/list/export',
    method: 'post',
    data: data,
    responseType: 'blob',
    timeout: 120000
  })
}

通过 JavaScript 的 window.URL.createObjectURL(new Blob([...])),将 blob 转成可操作的 url 链接,然后模拟 <a> 标签链接点击下载。

import { exportApi } from '@/api/index' // 某 vue 文件,引入上一步定义的 api

exportApi().then(res => { // 对应的文件下载 / 导出的方法中,写入该部分代码
  const url = window.URL.createObjectURL(new Blob([res.data])) // 文件流 blob 转成 URL
  const a = document.createElement('a')
  a.style.display = 'none'
  a.href = url
  a.download = '文件名.xlsx' // 自定义下载文件的名称
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
})

2、后端接口返回数据是文件下载的 url 链接

  • 相对第 1 点来说
  • 去掉 responseType: 'blob'
  • 去掉 window.URL.createObjectURL(new Blob([...]))
  • 写上 a.href = res.data.url
  • 注意:a.download 是 href 属性地址和前端地址同源情况下,才会起作用;否则不同域的情况下,不会起作用,就需要采用文件流 blob 的形式下载来强制修改文件名
  • 直接模拟点击 <a> 标签链接下载,其实链接下载还能采用:
// 缺点:体验感不好,屏幕会闪一下,因为这个实际是打开新窗口的然后才关闭
window.open(res.data.url)

3、文件 file 或文件流 blob 转成 base64

后端返回的文件是图片,需要转成 base64(或者第 1 点转成 url 链接)才能在前端展示出来,但这种用的比较少,因为后端一般专门存储图片会直接采用 url 链接的方式。

// file 或者 blob 转成 base64
export function fileToBase64(file) {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader()
    fileReader.readAsDataURL(file)
    fileReader.onload = (e) => {
      resolve(e.target.result)
    }
    fileReader.onerror = () => {
      reject(new Error('文件异常'))
    }
  })
}
import { exportApi } from '@/api/index' // 某 vue 文件,引入定义的 api
import { fileToBase64 } from '@/utils/index' // 引入上一步封装的方法

exportApi().then(res => { // 对应的文件下载 / 导出的方法中,写入该部分代码
  const blob = new Blob([res.data])
  fileToBase64(blob).then(str => {
    console.log('转换后的 base64', str)
    // 将获取的 base64 写入 <img> 标签的 src 属性可展示图片出来
  })
})

这里主要用了 FileReader 这个 API,具体内容可以参考:FileReader - Web API | MDN

4、base64 转成文件 file 或文件流 blob

/**
 * base64 转成 file 或者 blob
 * @param str {String} base64 字符串
 * @param fileName {String} 自定义的文件名
 */
export function base64ToFile(str, fileName) {
  let arr = str.split(',')
  let mime = arr[0].match(/:(.*?);/)[1]
  let bStr = atob(arr[1])
  let n = bStr.length
  let u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bStr.charCodeAt(n)
  }
  return new File([u8arr], fileName, { type: mime }) // file
  // return new Blob([u8arr], { type: mime }) // blob
}

5、压缩图片文件的方法封装

封装的工具方法,假设写在 utils 文件夹下的 index.js 里,然后在页面文件里通过 import 引入,注意要采用异步的形式(async/await 或 .then)调用,具体使用可以参考第 6 点:

/**
 * 压缩图片方法
 * @param {file} file 文件
 * @param {Number} quality 图片质量(取值 0-1 之间默认 0.52)
 */
export function compressImg(file, quality) {
  let qualitys = 0.52
  if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
    qualitys = 0.85
  }
  if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
    qualitys = 0.92
  }
  if (quality) {
    qualitys = quality
  }
  if (file[0]) {
    return Promise.all(Array.from(file).map(e => this.compressImg(e, qualitys))) // 如果是 file 数组返回 Promise 数组
  } else {
    return new Promise((resolve) => {
      if ((file.size / 1024).toFixed(2) < 300) {
        resolve({
          file: file
        })
      } else {
        const reader = new FileReader() // 创建 FileReader
        reader.readAsDataURL(file)
        reader.onload = ({
          target: {
            result: src
          }
        }) => {
          const image = new Image() // 创建 img 元素
          image.onload = async () => {
            const canvas = document.createElement('canvas') // 创建 canvas 元素
            const context = canvas.getContext('2d')
            const originWidth = image.width
            const originHeight = image.height
            let targetWidth = image.width
            let targetHeight = image.height
            if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
              var maxWidth = 1600
              var maxHeight = 1600
              targetWidth = originWidth
              targetHeight = originHeight
              // 图片尺寸超过的限制
              if (originWidth > maxWidth || originHeight > maxHeight) {
                if (originWidth / originHeight > maxWidth / maxHeight) {
                  // 更宽,按照宽度限定尺寸
                  targetWidth = maxWidth
                  targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                } else {
                  targetHeight = maxHeight
                  targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                }
              }
            }
            if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
              maxWidth = 1400
              maxHeight = 1400
              targetWidth = originWidth
              targetHeight = originHeight
              // 图片尺寸超过的限制
              if (originWidth > maxWidth || originHeight > maxHeight) {
                if (originWidth / originHeight > maxWidth / maxHeight) {
                  // 更宽,按照宽度限定尺寸
                  targetWidth = maxWidth
                  targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                } else {
                  targetHeight = maxHeight
                  targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                }
              }
            }
            canvas.width = targetWidth
            canvas.height = targetHeight
            context.clearRect(0, 0, targetWidth, targetHeight)
            context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
            const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
            const buffer = atob(canvasURL.split(',')[1])
            let length = buffer.length
            const bufferArray = new Uint8Array(new ArrayBuffer(length))
            while (length--) {
              bufferArray[length] = buffer.charCodeAt(length)
            }
            const miniFile = new File([bufferArray], file.name, {
              type: 'image/jpeg'
            })
            resolve({
              origin: file,
              file: miniFile,
              beforeSrc: src,
              afterSrc: canvasURL,
              beforeKB: Number((file.size / 1024).toFixed(2)),
              afterKB: Number((miniFile.size / 1024).toFixed(2)),
              qualitys: qualitys
            })
          }
          image.src = src
        }
      }
    })
  }
}

6、通过第三方库 image-conversion 压缩图片

安装并学习使用,参考相关官方文档:image-conversion - npm

Vue2 + Vant2 的文件上传组件,示例代码如下(也可以用第 5 点封装的方法,相关代码注释了):

<template>
  <div>
    <!-- 图片上传区域 -->
    <van-uploader :before-read="uploadBefore" :after-read="uploadAfter" v-model="imgList"></van-uploader>
  </div>
</template>

<script>
import * as imageConversion from 'image-conversion'
import { Toast, Notify } from 'vant'
import { compressImg } from '@/utils/compressImg'

export default {
  data() {
    return {
      imgList: [] // 当前图片列表,用于页面回显
    }
  },
  methods: {
    uploadBefore(file) { // 文件读取前的回调函数,返回 false 可终止文件读取,支持返回 Promise
      return new Promise(async (resolve, reject) => {
        if (!/image\/[a-zA-z]+/.test(file.type)) { // 判断文件类型是否为图片,不是则取消上传
          Notify({
            type: 'warning',
            message: '请上传图片类型的文件'
          })
          reject()
        } else {
          console.log(`当前选择的图片文件,大小为:${file.size / (1024 * 1024)} MB`, file)
          if (file.size / (1024 * 1024) >= 0.4) { // 大于 0.4 MB 的图片需要处理
            Toast({
              type: 'loading',
              message: '正在处理图片...',
              duration: 0,
              forbidClick: true
            })
            // let handleFile = await compressImg(file, 0.8) // 若使用该方法,底下代码改成 resolve(handleFile.file)
            let handleFile = new File([await imageConversion.compressAccurately(file, 200)], file.name, { type: 'image' }) // 数值参数,表示指定压缩后图像的大小(KB)
            // let handleFile = new File([await imageConversion.compress(file, 0.7)], file.name, { type: 'image' }) // 0-1 数值参数,表示图片质量
            Toast.clear()
            console.log('处理后的图片文件', handleFile)
            resolve(handleFile) // 返回处理后的 file
          } else {
            resolve(file)
          }
        }
      })
    },
    uploadAfter(param) { // 文件读取完成后的回调函数
      console.log(`读取完成后得到的图片文件,大小为:${param.file.size / (1024 * 1024)} MB`, param)
      console.log('当前图片文件列表', this.imgList)
    }
  }
}
</script>
  • 图片文件的相关获取和处理,都需要用到 JavaScript 本身提供的相关对象和 API,而这些 API 有涉及到异步操作且多层嵌套,所以一般采用 async/await 的形式
  • 该第三方库处理后返回结果是文件流 blob,需要通过 new 一个 File 对象实例来转换成文件,并 resolve,这样才能被文件读取完成后的回调函数接收
  • 对于 Vant 提供的上传组件,before-read 里如果校验方法涉及异步操作,校验不通过时采用 return false,会导致拦截失效,文件仍能上传,所以不建议用 return false,建议采用本文的 Promise 形式

运行结果如下图所示:

7、前端上传图片或文件,请求后端接口

有重要两点:

  • new FormData()
  • HTTP 的请求头 Content-Type: multipart/form-data; boundary=----...string

    一般前端会通过 axios 请求后端接口,但 axios 发送 HTTP 请求头部里的 Content-Type(内容类型)默认是 application/json;charset=UTF-8,所以默认传参的数据类型是纯文本类型的 JSON 对象,不适合带有文件类型的数据,若需要传参带有文件数据,那么需要把 Content-Type 指定为 multipart/form-data,这样传参可以带上文件类型数据。那么这就需要 JavaScript 提供的 FormData 类型的对象数据,既可以上传文件等二进制数据,也可以上传表单键值对,会转换成为一条信息。示例代码如下:

<button @click="handleUpload()">上传文件</button>
// 结合上一步的内容,给出的关键代码部分
handleUpload(){ // 确认上传文件
  Toast({
    type: 'loading',
    message: '正在提交...',
    duration: 0,
    forbidClick: true
  })
  let postData = new FormData()
  postData.append('name', 'hxhpg')
  postData.append('gender', 'male')
  postData.append('height', '175cm')
  postData.append('weight', '60kg')
  for (let i = 0; i < this.imgList.length; i++) {
    // 之前已上传成功的图片一般返回的是 url
    postData.append(`imgFile${i + 1}`, this.imgList[i].file || this.imgList[i].content || this.imgList[i].url)
  }
  uploadImageFileApi(postData).then(res => { // 某后端接口,通过 axios 封装定义的
    Toast.clear()
    console.log('响应结果', res)
  }).catch(err => {
    Toast.clear()
  })
}

    这里简单说下 application/x-www-form-urlencoded,它是标准的默认编码格式(在原始的 AJAX 中,不是 axios),只能上传键值对,并且键值对都是间隔分开的,不能用于上传文件等二进制数据。当采用 get 方式时,会把表单数据转成一串由 key1=value1&key2=value2&key3=value3... 组成的字符串作为 URL 的参数拼接在后面。当采用 post 方式时,则会把表单数据加入 HTTP 的请求体 body 中。

小结:如果是需要键值形式的数据,有文件时采用 multipart/form-data,没有文件时采用 application/x-www-form-urlencoded


    这是我本人在工作学习中做的一些总结,同时也分享出来给需要的小伙伴哈 ~ 供参考学习,有什么建议也欢迎评论留言,转载请注明出处哈,感谢支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1796393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用node将页面转为pdf?(puppeteer实现)

本文章适合win系统下实验&#xff08;linux&#xff0c;mac可能会出现些莫名其妙的bug我也不会解决&#xff09; 具体过程 首先了解什么时无头浏览器启动无头浏览器打开指定的url页面设置导出pdf格式开始转化完整基础代码 首先了解什么时无头浏览器 没有界面的浏览器下载pupp…

【uni-app】JS动态修改scss样式变量

需求&#xff1a; 1、 当H5嵌入到APP时&#xff0c;使用H5自身的头部&#xff0c;需要兼容手机自带的navbar高度&#xff0c;因此在嵌入APP时&#xff0c;需要固定H5 navbar距离手机自带头部高度&#xff1a;$uni-head:44px; 而paging-head-top是由于z-paging定位导致会覆盖或高…

使用kafka tools工具连接带有用户名密码的kafka

使用kafka tools工具连接带有用户名密码的kafka 创建kafka连接&#xff0c;配置zookeeper 在Security选择Type类型为SASL Plaintext 在Advanced页面添加如下图红框框住的内容 在JAAS_Config加上如下配置 需要加的配置&#xff1a; org.apache.kafka.common.security.plain.Pla…

【大事件】docker可能无法使用了

今天本想继续学习docker的命令&#xff0c;突然发现官方网站的文档页面打不开了。 难道是被墙了&#xff1f; 我用同事的翻了一下&#xff0c;能进&#xff0c;果然&#xff01; 正好手头的工作告一段落&#xff0c;将代码上传&#xff0c;然后通过jenkins将服务器自动部署到…

GAT1399协议分析(7)--pycharm anaconde3 配置pyside2

一、安装pyside2 pip install pyside2 二、配置qtdesigner File->Settings->Tools->External tools 点击添加&#xff0c;添加QtDesigner&#xff0c; 注意designer.exe路径&#xff0c;需要在虚拟环境下。 三、添加pySide2 File->Settings->Tools->Ext…

【Java】应用分层解密:优化代码结构与增强项目可维护性

目录 应用分层介绍代码重构应用分层的好处企业规范 应用分层 通过上⾯的练习, 我们学习了Spring MVC简单功能的开发, 但是我们也发现了⼀些问题 ⽬前我们程序的代码有点"杂乱", 然⽽当前只是"⼀点点功能"的开发. 如果我们把整个项⽬功能完成呢? 代码会…

蓝桥杯物联网竞赛 比赛总结

CUBEMX配置建议&#xff1a; 对于CUBEMX配置来说stm32l071kbu6的引脚不算太多&#xff0c;功能模块相对的也不多&#xff0c;所以我建议直接熟练到能将所有模块烂熟于心&#xff0c;不用看原理图就能熟练配置下来&#xff0c;因为国赛看原理图去配置太花费时间 我建议学习的时…

NIST 电子病历中的疫苗部分的认证

美国国家标准与技术研究院&#xff08;National Institute of Standards and Technology&#xff0c;NIST&#xff09;对电子病历的认证 分几个阶段&#xff0c;每个阶段又分门诊和住院&#xff0c;然后又分若干模块。下面是疫苗模块的数据提交的测试脚本。 170.302k_Immuniza…

socket网络编程——多进程、多线程处理并发

如下图所示&#xff0c; 当一个客户端与服务器建立连接以后&#xff0c;服务器端 accept()返回&#xff0c;进而准备循环接收客户端发过来的数据。 如果客户端暂时没发数据&#xff0c;服务端会在 recv()阻塞。此时&#xff0c;其他客户端向服务器发起连接后&#xff0c;由于服…

DP:子序列模型

子数组vs子数列 1、子数组&#xff08;n^2&#xff09; 子序列(2^n) 2、子数组是子序列的一个子集 3、子数组必须连续&#xff0c;子序列可以不连续 一、最长递增子序列 . - 力扣&#xff08;LeetCode&#xff09; 算法原理&#xff1a; 1、状态表示&#xff…

智能电销机器人的作用和原理是什么?

要问世界上更火爆的创新技术&#xff0c;人工智能必然要算其一&#xff0c;人工智能正不断的改变着我们的生活&#xff0c;比如智能手机、智能家居、智能门锁等产品已经不断的渗透在了我们的生活之中&#xff0c;而近几年兴起的人工智能语音识别机器人&#xff0c;也迅速俘获了…

Centos7安装ElasticSearch

Centos7安装ElasticSearch 准备工作 下载elasticsearch https://www.elastic.co/cn/elasticsearch 将下载好的包上传到/usr/local/elasticsearch/ 路径下 安装 安装elasticsearch解压缩即可&#xff01; tar -zxvf elasticsearch-8.12.2-linux-x86_64.tar.gz进入/usr/loca…

使用Django Channels和WebSocket构建聊天应用

一、引言 WebSocket提供了一种在客户端和服务器之间进行实时双向通信的方式。结合Django Channels&#xff0c;我们可以轻松地在Django项目中实现WebSocket功能。本文将通过一个简单的聊天应用示例&#xff0c;展示如何使用Django Channels和WebSocket。 二、环境搭建 项目的…

Spring Boot 整合开源 Tess4J库 实现OCR图片文字识别

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Linux基础 (十二):Linux 线程的创建与同步

本篇博客详细介绍与线程有关的内容&#xff0c;这部分也是笔试面试的重点&#xff0c;需要我们对线程有深刻的理解&#xff0c;尤其是线程的并发运行以及线程同步的控制&#xff01;接下来&#xff0c;让我们走进线程的世界&#xff0c;去理解线程&#xff0c;使用线程&#xf…

修改west扩展命令的路径

west命令是zephyr工程中非常重要的工具。使用west命令&#xff0c;可以高效的创建工程&#xff0c;管理代码&#xff0c;此外&#xff0c;通过扩展命令&#xff0c;还可以支持编译&#xff0c;烧录等功能。 从下图中可以看出&#xff0c;extension commands from project mani…

线性代数|机器学习-P8矩阵低秩近似eckart-young

文章目录 1. SVD奇异值分解2. Eckart-Young2.1 范数 3. Q A Q U Σ V T QAQU\Sigma V^T QAQUΣVT4. 主成分分析图像表示 1. SVD奇异值分解 我们知道&#xff0c;对于任意矩阵A来说&#xff0c;我们可以将其通过SVD奇异值分解得到 A U Σ V T AU\Sigma V^T AUΣVT&#xff0…

[ue5]建模场景学习笔记(4)——必修内容可交互的地形,交互沙(1)

1.需求分析&#xff1a; 现在的沙漠场景仅仅只是一张贴图&#xff0c;人物走过不会留下任何痕迹&#xff0c;很不真实&#xff0c;尝试优化一下&#xff0c;做出可交互的沙漠效果。 2.操作实现&#xff1a; 1.思路&#xff1a;这是一个相对复杂的工程&#xff0c;要考虑玩家踩…

深入理解C++三五零法则

三五零法则就是三法则&#xff08;The Rule of Three&#xff09;、五法则&#xff08;The Rule of Five&#xff09;、零法则&#xff08;The Rule of Zero&#xff09;。三五零法则是和C的特殊成员函数有关&#xff0c;特别是那些涉及对象如何被创建、复制、移动和销毁的函数…

ESD防护SP3232E真+3.0V至+5.5V RS-232收发器

特征 采用3.0V至5.5V电源&#xff0c;符合真正的EIA/TIA-232-F标准 满载时最低 120Kbps 数据速率 1μA 低功耗关断&#xff0c;接收器处于活动状态 &#xff08;SP3222E&#xff09; 可与低至 2.7V 电源的 RS-232 互操作 增强的ESD规格&#xff1a; 15kV人体模型 15kV IEC1000…