刘俊凯 发表于 2024-7-16 09:45:52

【前端】-【前端文件操作与文件上传】-【前端接受后端传输文件指南】

前端文件操作与文件上传

一、前端文件上传有两种思路:

[*]二进制blob传输:典范案例是formData传输,相当于用formData搭载二进制的blob传给后端
[*]base64传输:转为base64传输,后端再将base64转返来。简便、耗时
二、与文件相关的对象

[*]files:通过input标签读过来的文件对象,是blob的子类。
[*]blob:不可变的二进制内容,包罗许多操作方法,切片上传、断点续传都是基于blob的
[*]formData:用于后端传输的对象。files是一个前端的对象,不能直接传给后端,以是我们需要一个前后端都认可的载体来传递文件,这个载体就是formData。formData就像一辆汽车用来搭载files,这样才气让文件以二进制的形式传到后端
[*]fileReader:多用于把文件读取为某种形式(如base64、text文件)直接传给后端
三、file参数、blob切割文件、FileReader将文件转成base64,浓缩图/文本预览:
<template>
<div>
    <input type="file" name="file" @change="fileChange" />
    <!-- 缩略图,文本预览 -->
    <img style="width:200px;" :src="imgbase64" />
    <button @click="submit">提交</button>
</div>
</template>

<script>
import axios from "axios"
import { fstat } from "fs";
let _fileObj;
export default {
name: 'HelloWorld',
data() {
    return {
      imgbase64: "",
    }
},
methods: {
    fileChange(e) {
       let file = e.target.files// files是个数组
       _fileObj = file;
       // file常用属性:size(大小)、type(类型)、name(文件名)
      if (file.size > 10 * 24 * 24) {
      alert("文件不能大于十兆")
      }
      if (file.type !== 'video/mp4') {
      alert("必须是mp4")
      }
      // 使用blob的slice方法可以切割文件
      let _sliceBlob = new Blob().slice(0, 5000);// 切割二进制的0-5000位
      // 将切割后的Blob对象转成File对象(第二个参数是文件名),之后就可以上传切割后的File对象
      let _sliceFile = new File(, "test.png");
      
      //将File对象或者Blob对象转成base64或text
      let fr = new FileReader();
      fr.readAsDataURL(_sliceFile);// readAsDataURL是转成base64的方法
      let self = this;
      fr.onload = function () {
      // base64是异步的转换,通过onload方法获得转换结果
      self.imgbase64 = fr.result// 直接将转换结果赋值给img的src,设置src的大小即可获得浓缩图
      console.log(fr.result)// 打印结果见下图一
      }
    },
    async submit() {
    // 这部分的代码后面讲
            let _formData = new FormData();
      _formData.append("user", "asdasd");
      _formData.append("file", _fileObj)
      axios.post("/xx", _formData);
    }
    }
}
}
</script>
https://img-blog.csdnimg.cn/direct/5bd5ac0c4a7c4a37b620dcee1dc18d06.jpeg
四、formData:不但可以搭载文件,还可以搭载笔墨、input的其他输入
<form action="xxx" method="post">默认情况下提交为query参数</from>
<form action="xxx" method="post" enctype="multipart/form-data">enctype指定提交为formData</from>
五、文件上传:将blob转成FormData上传
async submit() {
        let _formData = new FormData();
    _formData.append("user", "asdasd");
    _formData.append("file", _fileObj)
    axios.post("/xx", _formData);
}
通过查看网络可以发现:

[*]上传内容:
https://img-blog.csdnimg.cn/direct/fc4135d7ac8c451b961b3d5d7ad83011.jpeg
[*]请求头content-Type指定了传输内容为form-data,且boundary指定了分割符
https://img-blog.csdnimg.cn/direct/4fd56741516c4c05a7735bab97cd9a45.jpeg
[*]分隔了两个内容(下面另有一个分割线,即以分割线末了)
https://img-blog.csdnimg.cn/direct/961b627a8d4243ffa2ebd22d11d05cc6.jpeg
六、转换关系
https://img-blog.csdnimg.cn/direct/50213d9a735e4d4bbee776286f007959.jpeg
[*]我们直接拿到的是file对象,file对象可以转换成Blob对象,Blob对象也可以转成file对象
[*]file、blob都可以根据FileReader读成base64或text文本
[*]通过接口传输给后端时,只能传输base64、text文本、formData,以是,假如不把file、blob通过FileReader读成base64或text文本,那么就需要把file或者blob append到formData才气通过接口传输
七、具体功能

[*]单文件上传(详见前述代码)
[*]多文件上传:
<template>
<div>
    <input type="file" name="file" @change="fileChange" multiple />
    <span v-for="item in imgList">{{ item.name }}</span>
    <button @click="submit">提交</button>
</div>
</template>

<script>
import axios from "axios"
import { fstat } from "fs";
let _fileObj;
export default {
name: 'HelloWorld',
data() {
    return {
      imgList: [],
    }
},
methods: {
    fileChange(e) {
      //多文件上传
      // multiple的多文件上传其实很不友好,需要用户在选择文件时按住ctrl多选,否则就会变成单选
      // 所以使用imgList数组将用户每次选择的内容都push进去
      if (e.target.files.length > 1) {
      
      this.imgList.concat(e.target.files)
      } else {
      this.imgList.push(e.target.files);
      }
      //切片上传
      _fileObj = e.target.files
    },
    async submit() {
      // 遍历,一个一个上传
      this.imgList.forEach((item) => {
      let _formData = new FormData();
      _formData.append(item.name + "file", item)
      axios.post("/xx", _formData);
      })
    }
}
}
</script>

[*]切片上传
<template>
<div>

    <input type="file" name="file" @change="fileChange" multiple />
    <div>{{ precent }}%</div>
    <button @click="submit">提交</button>
</div>
</template>

<script>
import axios from "axios"
import { fstat } from "fs";
let _fileObj;
export default {
name: 'HelloWorld',
data() {
    return {
      precent: 0
    }
},
methods: {
    fileChange(e) {
      //切片上传,在上传的时候进行切片,文件改变时只赋值
      _fileObj = e.target.files
    },
    async submit() {
      let size = 2 * 1024 * 1024;// 每次切片的大小,片为2m
      let fileSize = _fileObj.size;
      let current = 0;// 当前已经传了多少
      // 断点续传,永久记录中断的地方,下次上传时直接从断点开始传
      //localStorage.setItem(_fileObj.name, current);
      while (current < fileSize) {
      let _formData = new FormData();
      _formData.append(_fileObj.name, _fileObj.slice(current, current + size))
      await axios.post("http://localhost:4000/upload",_formData)
      // 进度条可以用axios的onUploadProgress方法,且发送切片时可以并行发送请求,后续可以自行优化
      this.precent = Math.min((current / fileSize) * 100, 100)
      current += size;
      }
    }
}
}
</script>
   File System Access API 允许直接读取、写入或生存对用户装备上的文件和文件夹的更改。从 Chrome 86 开始支持 File System Access API,目前只有基于 Chromium 系列的欣赏器全面支持,Safari 部分支持(支持读取,不支持写入和生存),而 Firefox 未支持。
前端接受后端传输文件指南

一、前端把后端传过来的文件下载下来(后端返回给我们的文件一样平常是二进制的),有以下几种方式:

[*]直接打开下载所在:无法命名,只适用于get直接返回blob接口;没有通过项目去发,直接打开了一个窗口,相当于是欣赏器请求的,就不会有token,无法做验证
https://img-blog.csdnimg.cn/direct/9737c633e1014c91b9c3f72d807c9d06.jpeg
直接在欣赏器输入:localhost:8000/download就可以下载了,或者在代码中使用window.open(所在)下载
https://img-blog.csdnimg.cn/direct/bb35080115434cf29fa29c2b1386c34c.jpeg
[*]利用a标签的download:比较符合的方案
   几个告急的概念:

[*]createObjectURL:把blob对象的内存所在以URL形式给出。接口返回的是二进制传输的文件,那前端就可以拿到这个文件的blob对象,前端把这个blob对象传给createObjectURL,得到一个URL,然后就可以使用a标签加载这个URL,从而实现下载
[*]msSaveBlob:ie不支持a标签下载,用的是msSaveBlob方案。假如是ie欣赏器,直接调用msSaveBlob方法,把blob对象给进去,就直接下载下来了
[*]a标签的download属性:表面该a标签的举动时下载,并设置文件名
文件下载流程:
(1)
https://img-blog.csdnimg.cn/direct/cdccacc5d3b8468ca61f5c9074a39284.jpeg
(1)使用blob吸收后端传过来的二进制文件
(2)判断欣赏器有没有msSaveBlob方法,假如有,说用户使用ie欣赏器,直接使用msSaveBlob下载
(3)假如msSaveBlob……(见上图)
https://img-blog.csdnimg.cn/direct/f42890143a7e4909ba601a279b923c96.jpeg
注意:msSaveBlob中type的值在打印的res.data中

[*]file- saver:现成的库,简单方便
(1)安装:npm i file-saver
(2)在代码中引入:import {saveAs} from 'file-saver'
https://img-blog.csdnimg.cn/direct/a2aece8adefa487ca1b6abac4486712c.jpeg
二、前端把后端传过来的blob文件流展示出来(即预览功能)思路:URL除了线上所在外,另有个base64的URL。将后端返回的blob转为base64(使用fileReader),将base64给预览工具
前端excel、word操作指南

excel

对excel操作所用库
https://img-blog.csdnimg.cn/direct/b463c215113c4a9c8fcd53af56f60afe.jpeg
xlsx产物转化表示图
https://img-blog.csdnimg.cn/direct/6c50ee2d610d49e991f39140a7605efa.jpeg
调用blob文件流的方法将blob文件流转换为arrayBuffer,将arrayBuffer给xlsx的read方法,read方法将arrayBuffer读取为一个book对象(可以理解为,将整个excel文件转换为js对象,这个js对象就是book对象),然后从book对象内里提取出对应的sheet对象,接下来就可以将这个sheet对象转换为你想要的前端产物(html或json)了,反过来,也可以把前端的html,一样平常是一个table(一个dom)给过去,转换成一个sheet对象,也可以将前端的json对象转换为sheet对象,然后输出一个excel文件。可以创建一个新的workbook对象,把sheet对象加入到这个新的workbook对象中,然后输出为excel文件
<template>
<div class="home">
    <!-- 如果是react,则使用该组件
    <fileviews fileType="指定预览的文件类型" fielPath="路径"></fileviews>
    -->
    <!--直接给vueofficeExcel组件的src指定地址即可实现预览,可以给1,线上的路径地址2,dataURL->base64(将二进制流转换成base64) -->
    <vueofficeExcel v-if="excelSrc" :src="excelSrc" style="height:500px"></vueofficeExcel>
    <button @click="createExcel">创建</button>
    <input type="file" @change="change" />
    <button @click="loadExcel">加载</button>
    <div class="excel-content" v-html="excelHTML"></div>
    <table ref="excelTable">
      <thead>
      <tr>
          <td>科目</td>
          <td>人数</td>
          <td>平均分</td>
      </tr>
      </thead>
      <tr>
      <td rowspan="3">数学</td>
      <td>90</td>
      <td>86</td>
      </tr>
      <tr>

      <td>85</td>
      <td>82</td>
      </tr>
      <tr>

      <td>78</td>
      <td>32</td>
      </tr>
    </table>
</div>
</template>

<script>
//1,本地选择-》读取为前端html和数据对象
//2,请求-》读取为前端html和数据对象

//1,把前端的一个table dom转化为excel
//2,把一个前端对象转化为excel
import axios from "axios";
import vueofficeExcel from "@vue-office/excel";// 由于使用xlsx库进行预览时,预览的excel很原生,很丑,需要我们自己写样式,所以可以使用@vue-office/excel组件,他的预览功能和原生excel一样,假如我们在excel中设置了样式,vueofficeExcel也会展示出来
// 与此相似的还有"@vue-office/docx"、"@vue-office/pdf",他们定义了不同的预览方式
// 如果使用的是react框架,则使用react-file-viewer,该组件支持很多种类型的预览,不管是word、excel还是ppt都使用这一个组件进行预览
// 他也是一个组件,引入注册后直接使用
import "@vue-office/excel/lib/index.css"
import { read, writeFile, utils } from "xlsx"// xlsx是函数式编程
export default {
name: 'Home',
components: {// 注册
    vueofficeExcel
},
data() {
    return {
      excelHTML: "",
      excelSrc: ""
    }
},

methods: {
    loadExcel() {
      axios.get("http://localhost:8000/download", { responseType: "blob" }).then((res) => {
      console.log(res.data);
      res.data.arrayBuffer().then((res) => {
          const wb = read(res);
          const sheet1 = wb.Sheets.Sheet1
          const _data = utils.sheet_to_json(sheet1);
          const _html = utils.sheet_to_html(sheet1);
          this.excelHTML = _html
      })
      })
    },
    createExcel() {
      let data = [
      { name: "张三", id: 100, score: 99 },
      { name: "张四", id: 200, score: 99 },
      { name: "张五", id: 300, score: 99 }
      ]
      //转化data数组
      const ws = utils.json_to_sheet(data);// 将前端的数据转换为sheet
      const wb = utils.book_new();// 创建空的excel文件,即book对象
      utils.book_append_sheet(wb, ws, "people");// 将sheet表加入excel(book对象),并给这个sheet表命名为people
      writeFile(wb, "test1.xlsx");// 浏览器下载excel,并将其命名为test1.xlsx
      //转化table dom
      const tableDom = this.$refs.excelTable;// 获取table的dom元素
      const tableWs = utils.table_to_sheet(tableDom);// 将dom元素转换为sheet,也可以用table_to_book()直接将其转换为book,然后使用writeFile
      const wb2 = utils.book_new();// 创建book对象,即excel
      utils.book_append_sheet(wb2, tableWs, "sheet1");// 将sheet表加入excel(book对象),并给这个sheet表命名为sheet1
      writeFile(wb2, "tableTest.xlsx");// 浏览器下载excel,并将其命名为tableTest.xlsx
      //const wb2=utils.table_to_book(tableDom);
    },
    change(e) {
      let _file = e.target.files// e.target.files拿到的就是blob文件流
      const fr = new FileReader();// FileReader用于将文件流读取为你想要的格式
      fr.readAsDataURL(_file);// 将文件流读取为base64
      fr.onload = (e) => {
      // 转换完成后获取转换结果
      this.excelSrc = e.target.result;
      }
      /*_file.arrayBuffer().then((res) => {// arrayBuffer()对象本身会返回一个promise
                // 将blob文件流转换成arrayBuffer,res转换出来的内容
      const wb = read(res);// 读取arrayBuffer,生成一个book对象
      const sheet1 = wb.Sheets.Sheet1// 从Sheets中取出Sheet1这张表
      const _data = utils.sheet_to_json(sheet1);// 将Sheet1表转换为json
      const _html = utils.sheet_to_html(sheet1);// 将Sheet1表转换为html
      this.excelHTML = _html// 需要自己给表格写样式
      })*/
    }
}
}
</script>
<style>
.excel-content table {
border-collapse: collapse
}

.excel-content td {
border: 1px solid black;
}
</style>
https://img-blog.csdnimg.cn/direct/36b4e88c2bd047d3b10d0371251abb28.png
在做文件预览功能时,都需要提供src路径,这个src要么是线上所在,要么是dataURL,以是一样平常我们都会转换一下路径,假如文件是本地选取的,直接将文件线上所在给src,假如文件是接口返回的,那就是个二进制流,使用FileReader将所在转换为dataURL,再给src
word

https://img-blog.csdnimg.cn/direct/2e1df92c0ebc439db4d7b88ddecf192e.png
<template>
<div class="home">
    <!--<vueofficedocx v-if="wordPath" :src="wordPath"></vueofficedocx>-->
    <input type="file" @change="change" />
    <div ref="docxPreview"></div>
</div>
</template>

<script>
import vueofficedocx from "@vue-office/docx";
import { renderAsync } from "docx-preview"
import axios from "axios";
import PizZip from 'pizzip'
import Docxtemplater from 'docxtemplater'// 将word里面的内容作为模版,将html中的数据渲染进去。他接收的的内容是经过压缩的内容,所以还需引入pizzip
import { saveAs } from 'file-saver'// 用于保存文件,你给他一个文件流,他可以把这个文件流作为一个文件导出出来,不仅仅用于word
export default {
name: 'Home',
data() {
    return {
      wordPath: ""
    }
},
components: {
    vueofficedocx
},
methods: {
    change(e) {
      /*let _file = e.target.files
      const fr = new FileReader();
      fr.readAsDataURL(_file);
      fr.onload = (e) => {
      this.wordPath = e.target.result;
      }*/
      //let _file = e.target.files;
      //blob,arrayBuffer
      //renderAsync(_file, this.$refs.docxPreview);// renderAsync接收blob/arrayBuffer,并将其渲染到指定dom中进行预览
      let data = {
      student: [
          { name: "张三", id: 100, score: 99 },
          { name: "张四", id: 200, score: 99 },
          { name: "张五", id: 300, score: 99 }
      ]
      }
      let _file = e.target.files;
      _file.arrayBuffer().then((res) => {
      let zip = new PizZip(res);// PizZip只能压缩arrayBuffer,所以需要将blob转成arrayBuffer
      const doc = new Docxtemplater(zip);
      doc.setData(data)//设置数据
      doc.render();// 渲染
      const out = doc.getZip().generate({
          type: "blob",
          mimeType:
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document"// 指定word文档的类型
      })// 渲染完成的二进制流
      saveAs(out, "test1.docx")// 保存二进制流并将其命名为test1.docx
      })
    }
}
}
</script>
<style>
.excel-content table {
border-collapse: collapse
}

.excel-content td {
border: 1px solid black;

}
</style>
word模版中的内容:
https://img-blog.csdnimg.cn/direct/901dd7441444414b85bd1c3829369dae.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【前端】-【前端文件操作与文件上传】-【前端接受后端传输文件指南】