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

            <div id="content_views" class="htmledit_views">
                <h3><a name="t0"></a>一、前言</h3> 

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

二、具体内容

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

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

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


 
 
  1. import request from '@/utils/request' // 一般基于 axios 封装的 request
  2. export function exportApi( data) { // 导出下载接口
  3. return request({
  4. url: '/list/export',
  5. method: 'post',
  6. data: data,
  7. responseType: 'blob',
  8. timeout: 120000
  9. })
  10. }

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


 
 
  1. import { exportApi } from '@/api/index' // 某 vue 文件,引入上一步定义的 api
  2. exportApi(). then( res => { // 对应的文件下载 / 导出的方法中,写入该部分代码
  3. const url = window. URL. createObjectURL( new Blob([res. data])) // 文件流 blob 转成 URL
  4. const a = document. createElement( 'a')
  5. a. style. display = 'none'
  6. a. href = url
  7. a. download = '文件名.xlsx' // 自定义下载文件的名称
  8. document. body. appendChild(a)
  9. a. click()
  10. document. body. removeChild(a)
  11. })
2、后端接口返回数据是文件下载的 url 链接
  • 相对第 1 点来说
  • 去掉 responseType: 'blob'
  • 去掉 window.URL.createObjectURL(new Blob([...]))
  • 写上 a.href = res.data.url
  • 注意:a.download 是 href 属性地址和前端地址同源情况下,才会起作用;否则不同域的情况下,不会起作用,就需要采用文件流 blob 的形式下载来强制修改文件名
  • 直接模拟点击 <a> 标签链接下载,其实链接下载还能采用:

 
 
  1. // 缺点:体验感不好,屏幕会闪一下,因为这个实际是打开新窗口的然后才关闭
  2. window. open(res. data. url)

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

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


 
 
  1. // file 或者 blob 转成 base64
  2. export function fileToBase64( file) {
  3. return new Promise( (resolve, reject) => {
  4. const fileReader = new FileReader()
  5. fileReader. readAsDataURL(file)
  6. fileReader. onload = (e) => {
  7. resolve(e. target. result)
  8. }
  9. fileReader. onerror = () => {
  10. reject( new Error( '文件异常'))
  11. }
  12. })
  13. }

 
 
  1. import { exportApi } from '@/api/index' // 某 vue 文件,引入定义的 api
  2. import { fileToBase64 } from '@/utils/index' // 引入上一步封装的方法
  3. exportApi(). then( res => { // 对应的文件下载 / 导出的方法中,写入该部分代码
  4. const blob = new Blob([res. data])
  5. fileToBase64(blob). then( str => {
  6. console. log( '转换后的 base64', str)
  7. // 将获取的 base64 写入 <img> 标签的 src 属性可展示图片出来
  8. })
  9. })

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

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

 
 
  1. /**
  2. * base64 转成 file 或者 blob
  3. * @param str {String} base64 字符串
  4. * @param fileName {String} 自定义的文件名
  5. */
  6. export function base64ToFile( str, fileName) {
  7. let arr = str. split( ',')
  8. let mime = arr[ 0]. match( /:(.*?);/)[ 1]
  9. let bStr = atob(arr[ 1])
  10. let n = bStr. length
  11. let u8arr = new Uint8Array(n)
  12. while (n--) {
  13. u8arr[n] = bStr. charCodeAt(n)
  14. }
  15. return new File([u8arr], fileName, { type: mime }) // file
  16. // return new Blob([u8arr], { type: mime }) // blob
  17. }

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

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


 
 
  1. /**
  2. * 压缩图片方法
  3. * @param {file} file 文件
  4. * @param {Number} quality 图片质量(取值 0-1 之间默认 0.52)
  5. */
  6. export function compressImg( file, quality) {
  7. let qualitys = 0.52
  8. if ( parseInt((file. size / 1024). toFixed( 2)) < 1024) {
  9. qualitys = 0.85
  10. }
  11. if ( 5 * 1024 < parseInt((file. size / 1024). toFixed( 2))) {
  12. qualitys = 0.92
  13. }
  14. if (quality) {
  15. qualitys = quality
  16. }
  17. if (file[ 0]) {
  18. return Promise. all( Array. from(file). map( e => this. compressImg(e, qualitys))) // 如果是 file 数组返回 Promise 数组
  19. } else {
  20. return new Promise( (resolve) => {
  21. if ((file. size / 1024). toFixed( 2) < 300) {
  22. resolve({
  23. file: file
  24. })
  25. } else {
  26. const reader = new FileReader() // 创建 FileReader
  27. reader. readAsDataURL(file)
  28. reader. onload = ({
  29. target: {
  30. result: src
  31. }
  32. }) => {
  33. const image = new Image() // 创建 img 元素
  34. image. onload = async () => {
  35. const canvas = document. createElement( 'canvas') // 创建 canvas 元素
  36. const context = canvas. getContext( '2d')
  37. const originWidth = image. width
  38. const originHeight = image. height
  39. let targetWidth = image. width
  40. let targetHeight = image. height
  41. if ( 1 * 1024 <= parseInt((file. size / 1024). toFixed( 2)) && parseInt((file. size / 1024). toFixed( 2)) <= 10 * 1024) {
  42. var maxWidth = 1600
  43. var maxHeight = 1600
  44. targetWidth = originWidth
  45. targetHeight = originHeight
  46. // 图片尺寸超过的限制
  47. if (originWidth > maxWidth || originHeight > maxHeight) {
  48. if (originWidth / originHeight > maxWidth / maxHeight) {
  49. // 更宽,按照宽度限定尺寸
  50. targetWidth = maxWidth
  51. targetHeight = Math. round(maxWidth * (originHeight / originWidth))
  52. } else {
  53. targetHeight = maxHeight
  54. targetWidth = Math. round(maxHeight * (originWidth / originHeight))
  55. }
  56. }
  57. }
  58. if ( 10 * 1024 <= parseInt((file. size / 1024). toFixed( 2)) && parseInt((file. size / 1024). toFixed( 2)) <= 20 * 1024) {
  59. maxWidth = 1400
  60. maxHeight = 1400
  61. targetWidth = originWidth
  62. targetHeight = originHeight
  63. // 图片尺寸超过的限制
  64. if (originWidth > maxWidth || originHeight > maxHeight) {
  65. if (originWidth / originHeight > maxWidth / maxHeight) {
  66. // 更宽,按照宽度限定尺寸
  67. targetWidth = maxWidth
  68. targetHeight = Math. round(maxWidth * (originHeight / originWidth))
  69. } else {
  70. targetHeight = maxHeight
  71. targetWidth = Math. round(maxHeight * (originWidth / originHeight))
  72. }
  73. }
  74. }
  75. canvas. width = targetWidth
  76. canvas. height = targetHeight
  77. context. clearRect( 0, 0, targetWidth, targetHeight)
  78. context. drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
  79. const canvasURL = canvas. toDataURL( 'image/jpeg', qualitys)
  80. const buffer = atob(canvasURL. split( ',')[ 1])
  81. let length = buffer. length
  82. const bufferArray = new Uint8Array( new ArrayBuffer(length))
  83. while (length--) {
  84. bufferArray[length] = buffer. charCodeAt(length)
  85. }
  86. const miniFile = new File([bufferArray], file. name, {
  87. type: 'image/jpeg'
  88. })
  89. resolve({
  90. origin: file,
  91. file: miniFile,
  92. beforeSrc: src,
  93. afterSrc: canvasURL,
  94. beforeKB: Number((file. size / 1024). toFixed( 2)),
  95. afterKB: Number((miniFile. size / 1024). toFixed( 2)),
  96. qualitys: qualitys
  97. })
  98. }
  99. image. src = src
  100. }
  101. }
  102. })
  103. }
  104. }

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

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

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


 
 
  1. <template>
  2. <div>
  3. <!-- 图片上传区域 -->
  4. <van-uploader :before-read="uploadBefore" :after-read="uploadAfter" v-model="imgList"> </van-uploader>
  5. </div>
  6. </template>
  7. <script>
  8. import * as imageConversion from 'image-conversion'
  9. import { Toast, Notify } from 'vant'
  10. import { compressImg } from '@/utils/compressImg'
  11. export default {
  12. data( ) {
  13. return {
  14. imgList: [] // 当前图片列表,用于页面回显
  15. }
  16. },
  17. methods: {
  18. uploadBefore( file) { // 文件读取前的回调函数,返回 false 可终止文件读取,支持返回 Promise
  19. return new Promise( async (resolve, reject) => {
  20. if (! /image\/[a-zA-z]+/. test(file. type)) { // 判断文件类型是否为图片,不是则取消上传
  21. Notify({
  22. type: 'warning',
  23. message: '请上传图片类型的文件'
  24. })
  25. reject()
  26. } else {
  27. console. log( `当前选择的图片文件,大小为:${file.size / (1024 * 1024)} MB`, file)
  28. if (file. size / ( 1024 * 1024) >= 0.4) { // 大于 0.4 MB 的图片需要处理
  29. Toast({
  30. type: 'loading',
  31. message: '正在处理图片...',
  32. duration: 0,
  33. forbidClick: true
  34. })
  35. // let handleFile = await compressImg(file, 0.8) // 若使用该方法,底下代码改成 resolve(handleFile.file)
  36. let handleFile = new File([ await imageConversion. compressAccurately(file, 200)], file. name, { type: 'image' }) // 数值参数,表示指定压缩后图像的大小(KB)
  37. // let handleFile = new File([await imageConversion.compress(file, 0.7)], file.name, { type: 'image' }) // 0-1 数值参数,表示图片质量
  38. Toast. clear()
  39. console. log( '处理后的图片文件', handleFile)
  40. resolve(handleFile) // 返回处理后的 file
  41. } else {
  42. resolve(file)
  43. }
  44. }
  45. })
  46. },
  47. uploadAfter( param) { // 文件读取完成后的回调函数
  48. console. log( `读取完成后得到的图片文件,大小为:${param.file.size / (1024 * 1024)} MB`, param)
  49. console. log( '当前图片文件列表', this. imgList)
  50. }
  51. }
  52. }
  53. </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>
 
 

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

    这里简单说下 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


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值