vue 文件的上传和下载

547 阅读4分钟

formData

FormData 对象用以将数据变成键值对,以便于使用 XMLHttpRequest 来发送数据。其主要有两个作用:1. 用于发送表单数据,2. 发送File或者Blob类型的文件。

FormData对象的创建

let formData = new FormData();

向FormData追加数据

FormData 追加数据有两种方法:

formData.append('name','zs')
formData.set('age',18)

两种方法的区别是:如果指定的键已经存在,FormData.set() 会用新值覆盖已有的值,而 FormData.append() 会把新值添加到已有值集合的后面。

Snipaste_2022-09-13_10-59-11.png

Snipaste_2022-09-13_10-59-26.png

获取FormData中相应的值

let formData = new FormData()
formData.append('name','zs')
formData.append('name','ls')

// 获取key为name的第一个值
console.log(formData.get("name"))
// 获取key为name的所有值,返回为数组类型
console.log(formData.getAll("name"))

Snipaste_2022-09-13_11-20-03.png

判断FormData中是否存在对应的key值

let formData = new FormData();
formData.append('name', 'zs')
formData.append('name', 'ls')

//判断是否包含key为name的数据
console.log(formData.has("name")); //true
//判断是否包含key为age的数据
console.log(formData.has("age")); //false

Snipaste_2022-09-13_11-22-43.png

删除FormData删除数据

let formData = new FormData();

formData.append('name', 'zs')
formData.append('name', 'ls')

formData.delete('name')
console.log(formdata.get("name"))

Snipaste_2022-09-13_11-26-48.png

Blob

什么是Blob

Blob 表示二进制类型的大对象。在数据管理系统中,将二进制数据存储为一个单一个体的集合。

Blob 对象表示一个不可变,原始数据的类文件对象。值得一提的是 File 接口基于 Blob

Blob对象的创建

let blob = new Blob()
console.log(blob)

image.png

Blob对象的参数

let str = '123'
let blob = new Blob([str],{type:'text/plain'})
console.log(blob)

image.png

Blob 在创建的时候,可以传递两个参数,第一个参数是 数组 ,里面的东西会放进 Blob 中;第二个参数是 对象,里面可以传递 type 去指定Blob数组内容的类型。

看前面控制台中有一个 text() 方法,调用这个方法可以获得一个成功的Promise对象,然后,我们就可以用 then 方法去进行下一步的操作啦。

image.png

使用Blob实现文件的下载和预览

下载文件

<body>
    <input type="file" id="input">
    <script>
        let input = document.getElementById('input')

        input.onchange = (e) => {
            let file = e.target.files[0];
            const link = document.createElement('a');
            const blob = new Blob([file]);
            // 设置a标签的href(点击地址)
            link.href = URL.createObjectURL(blob);
            // 设置a标签属性
            link.setAttribute('download', 'hello.xlsx');
            // 点击a标签
            document.body.appendChild(link);
            link.click();
            // 移除a标签
            document.body.removeChild(link);
        }
    </script>
</body>

图片的预览

<body>
    <input type="file" id="input">
    <script>
        let input = document.getElementById('input')
        input.onchange = (e) => {
            let file = e.target.files[0];
            let blob = new Blob([file])

            let img = new Image()
            let fileRead = new FileReader()
            document.body.appendChild(img)
            fileRead.onload = function(){
                img.src = fileRead.result
            }
            fileRead.readAsDataURL(file)
        }

    </script>
</body>

Blob 对象中还提供了许多其他的方法,更加详细的可以参考 MDN:developer.mozilla.org/zh-CN/docs/… 和 这篇文章:zhuanlan.zhihu.com/p/161000123

文件的上传

使用FormData实现单一文件上传

工程结构

<template>
  <div class="oneFile">
    <h4>单一文件上传[FORM-DATA]</h4>
    <div class="box">
      <input
        type="file"
        class="upload_inp"
        accept=".png,.jpg,.jpeg"
        @change="fileChange"
        ref="inputer"
      />
      <div class="button_box">
        <button class="upload_button select" @click="btnSelect">
          选择文件
        </button>
        <button class="upload_button upload" @click="btnUpload">上传到服务器</button>
      </div>
        <img :src="src" alt="" v-if="isShow" />
    </div>
  </div>
</template>

image.png

首先,我们要实现的就是,如何点击 选择文件 这个按钮,从而触发 input 进行选择文件。

btnSelect() {
  this.$refs.inputer.dispatchEvent(new MouseEvent("click"));
},

然后,我们就可以根据 input 这个按钮的 change 去对文件进行操作,获取到我们选择的文件。

fileChange(e) {
  // 获取用户选中的文件
  let files = e.target.files[0];
  if (!files) return;

  //  限制文件上传的格式(这是除了上面的 accept 的另一种校验文件格式的方法)
  // if(!/(PNG|JPG|JPEG)/i.test(file.type)){
  //     alert('上传的文件只能是 PNG|JPG|JPEG')
  // }

  // 限制文件上传的大小
  if (files.size > 2 * 1024 * 1024) {
    alert("上传文件不能超过2MB");
  }

  this.file = files
},

最后,点击上传按钮进行发送请求就可以了。

btnUpload(){
    if(!this.file){
        alert('请您选择需要上传的图片')
    }
    // 把文件传递给服务器:FormData
    let formData = new FormData()
    formData.append('file',this.file)
    formData.append('filename',this.file.name)
    fileAxios.post('/upload_single',formData).then(data => {
        if(data.code === 0){
            alert(`文件上传成功~~~,您可以基于 ${data.servicePath} 访问这个资源`)
            return
        }
        return Promise.reject(data.codeText)
    }).catch(reason => {
        alert('文件上传失败,请您稍后再试 ' + reason)
    })
}

使用BASE64实现单一文件上传

image.png

把文件转成 BASE64 的模式。这里我们借用 FileReader 中的 readAsDataURL 方法。

changeBASE64(files) {
  return new Promise((resolve) => {
    let fileReader = new FileReader();
    fileReader.readAsDataURL(files);
    fileReader.onload = (ev) => {
      resolve(ev.target.result);
    };
  });
},

关于更加详细的 FileReader ,可以阅读这篇文章:(99条消息) 原生js使用FileReader将文件转成base64_杨树林er的博客-CSDN博客_filereader 转base64

然后上传到服务器

async fileChange(e) {
  // 获取用户选中的文件
  let files = e.target.files[0];
  if (!files) return;

  // 拿到 base64 的图片
  let BASE64 = await this.changeBASE64(files);
  try {
    let data = await fileAxios.post(
      "/upload_single_base64",
      {
        file: encodeURIComponent(BASE64),
        filename: files.name,
      },
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      }
    );
    if (+data.code === 0) {
      alert(
        `恭喜您,文件上传成功,您可以基于 ${data.servicePath} 地址去访问~~`
      );
      return;
    }
    throw data.codeText;
  } catch (err) {
    alert("很遗憾,文件上传失败,请您稍后再试~~" + err);
  }
},

进度管控

我们在发送请求的时候,我们可以传入第三个参数,借助 onUploadProgress 这个属性,去进行进度的管控。

async fileChange(e) {
      // 获取用户选中的文件
      let files = e.target.files[0];
      if (!files) return;
      try {
        let formData = new FormData();
        formData.append("file", files);
        formData.append("filename", files.name);
        let data = await fileAxios.post("/upload_single", formData, {
          // 文件上传中的回调函数 xhr.upload.onprogress
          onUploadProgress:(ev) => {
            console.log(ev)
            let { loaded, total } = ev;
            this.loading = `${(loaded / total) * 100}%`;
          },
        });
        if (+data.code === 0) {
          alert(
            `恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`
          );
          return;
        }
        throw data.codeText;
      } catch (err) {
        alert("很遗憾,文件上传失败,请您稍后再试~~"+err);
      } finally {
        this.loading = null
      }
    },

传入的 ev 这个对象中,有一个 total 属性,表示已经上传的; total 这个属性,表示总共需要上传的。所以,我们可以通过 loaded/total 从而得到上传的进度。

image.png

多文件上传

多文件上传的实质其实和单文件上传并没有什么差别。首先,我们需要给 input 增加一个属性 multiple

<input type="file" class="upload_inp" accept=".png,.jpg,.jpeg" @change="fileChange" ref="inputer" multiple/>

这样,我们就可以进行多文件的上传啦。

btnUpload() {
  if (this.files.length === 0) {
    alert("请您先选择要上传的文件~~");
    return;
  }

  // 循环发送请求
  this.files = this.files.map((item) => {
    let fm = new FormData();
    fm.append("file", item.file);
    fm.append("filename", item.filename);
    return fileAxios.post("/upload_single", fm).then((data) => {
      if (+data.code === 0) {
        return;
      }
      return Promise.reject(data.codeText);
    });
  });

  Promise.all(this.files)
    .then(() => {
      alert("恭喜你,所有文件都上传成功");
    })
    .catch((err) => {
      console.log(this.files);
      alert("很遗憾,文件上传失败,请稍后在试~~" + err);
      console.log(err)
    })
    .finally(() => {
      this.files = [];
    });
},

这里其实还是使用的上面上传单文件的接口,所以,我使用了一个循环,去单独给每一个文件发送一次请求,最后在使用 Promise.all() 这个函数,去检测是否全部发送成功。

当然,你也可以将所有的文件全部都放到 FormData 这个对象中,最后在将 FormData 传给后端。

当然,具体的选择,还是要看你和后端的交流。

拖拽上传

拖拽上传的实现,其实主要依靠的是监听几个事件,主要是 drop 这个事件。

<div class="button_box" ref="button_box" v-on="{click:btnSelect,dragenter:dragenterBox,dragleave:dragleaveBox,dragover:dragoverBox,drop:dropBox}">
   <span>拖拽上传</span>
</div>
async uploadFile(file){
    try{
        let fm = new FormData
        fm.append('file',file)
        fm.append('filename',file.name)
        let data = await fileAxios.post('/upload_single',fm)
        if(data.code === 0){
            alert('文件上传成功')
            return
        }

        throw data.codeText
    }catch(err){
        alert('文件上传失败~~'+err)
    }finally{
        this.$refs.button_box.style.backgroundColor = '#fff'
        this.isRun=false
    }
},
    dragenterBox(){
        console.log('进入')
    },
    dragleaveBox(){
        console.log('离开')
    },
    dragoverBox(e){
        console.log('区域内移动')
        e.preventDefault()
    },
    dropBox(e){
        console.log('放到容器中')
        e.preventDefault()
        // 获取文件
        let files = e.dataTransfer.files[0]
        if(!files) return 
        // 上传文件
        this.uploadFile(files)
    },

切片上传

为了避免上传大文件时上传超时,就需要用到切片上传,工作原理是:我们将大文件切割为小文件,然后将切割的若干小文件上传到 服务器 端,服务器端接收到被切割的小文件,然后按照一定的顺序将小文件拼接合并成一个大文件。

结构:

<input type="file" id="videoUpload" value="选择视频">
<button id="uploadBtn">上传视频</button>
<br>
<span id="uploadInfo"></span>
<br>
<progress value="0" id="uploadProgress"></progress>

功能实现:

let oUpload = document.getElementById("videoUpload");
let oBtn = document.getElementById("uploadBtn");
let oInfo = document.getElementById("uploadInfo");
let oProgress = document.getElementById("uploadProgress");

// 设置每一个分段的大小
let chunk_size = 64 * 1024;

// 上传的数量大小
let uploadedSize = 0;

// 上传后返回的结果
let uploadResult;

oBtn.addEventListener("click", async () => {
  const file = oUpload.files[0];

  if (!file) {
    alert("请先选择文件~");
    return;
  }

  const { name, type, size } = file;
  const fileName = new Date().getTime() + "_" + name;

  if (type !== "video/mp4" && type !== "video/ogg") {
    alert("不支持该类型文件~~");
    return;
  }

  oProgress.max = size;

  // 避免发送太多请求
  if (size > chunk_size * 100) {
    chunk_size = size / 100;
  }

  while (uploadedSize < size) {
    const fileChunk = file.slice(uploadedSize, uploadedSize + chunk_size);
    const formData = createFormData({
      name,
      type,
      size,
      uploadedSize,
      fileName,
      file: fileChunk,
    });
    try {
      uploadResult = await axios.post(
        "http://127.0.0.1:3000/upload_video",
        formData
      );
      console.log(uploadResult);
    } catch (err) {
      alert("上传失败" + err.message);
      return;
    }

    uploadedSize += chunk_size;
    oProgress.value = uploadedSize;
  }

  alert("上传成功");
  oUpload.value = null;

  createVideo(uploadResult.data.video_url);
});

function createFormData({ name, type, size, uploadedSize, fileName, file }) {
  const fd = new FormData();

  fd.append("name", name);
  fd.append("type", type);
  fd.append("size", size);
  fd.append("uploadedSize", uploadedSize);
  fd.append("fileName", fileName);
  fd.append("file", file);

  return fd;
}

function createVideo(src) {
  const oVideo = document.createElement("video");
  oVideo.controls = true;
  oVideo.width = "500";
  oVideo.src = src;
  document.body.appendChild(oVideo);
}

参考文献及资料

从FormData到图片上传 - 掘金 (juejin.cn)

zhuanlan.zhihu.com/p/161000123

(99条消息) 原生js使用FileReader将文件转成base64_杨树林er的博客-CSDN博客_filereader 转base64

结语

好啦,本次分享就到这里。

文章如果有不正确的地方,欢迎指正,共同学习,共同进步。

若有侵权,请联系作者删除。