文件上传及相关操作

1. 基础知识

数据传输到后端分为两种:

  • 二级制blob传输:formDate传输
  • base64传输:转为base64传输

相关对象:

  • files:通过input标签过来的文件
  • blob:二进制内容(不可变),包含众多操作方法
  • formDate:用于与后端传输的对象
  • fileReader:把文件读取为某种形式,如base64、text

注意:files本质是blob的子类,文件的断点上传、切片都是基于blob。

2. 文件处理

首先给定一个input标签,type属性值必须是file。

1
<input type="file" name="file" @change="fileChange" />

接着开始对上传过来的文件进行相关处理。

2.1 文件属性限制

当你需要对文件的大小、类型做出限制时,可以直接抓取其对应属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { ElNotification } from 'element-plus';
const fileChange = function(e){
let file = e.target.files[0];
if(file.size > 10 * 24 * 24){
ElNotification({
title: 'Warning',
message: '文件不能大于10M!',
type: 'warning',
})
}
if(file.type !== 'video/mp4'){
ElNotification({
title: 'Warning',
message: '文件格式不正确!',
type: 'warning',
})
}
}

2.2 文件相关操作

  • 文件切割

    在1.1提过相关基础知识,file其实本质是blob的子类。那么可以将上传的文件转化成二级制格式进行切割,接着再将切割后的对象转化为file。

    1
    2
    3
    4
    5
    const fileChange = function(e){
    let file = e.target.files[0];
    let _sliceBlob = new Blob([file]).slice(0, 5000);
    let _sliceFile = new File([_sliceBlob], "test.png");
    }

    此时完成了文件的切割操作,当然实际没有这部分需求,可以省略。

  • 格式转换

    我们刚才介绍过文件传输到后端有多种格式,如果我们需要按照base64的格式进行传输,那可以进行以下操作。

    1
    2
    3
    4
    let fr = new FileReader();
    fr.readAsDataURL(_sliceFile);
    //读取文本
    //fr.readAsText(_sliceFile);
  • 图片预览、截取

    1
    <img :src="imgbase64" />
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { ref } from 'vue';
    let imgbase64 = ref("");
    const fileChange = function(e){
    let file = e.target.files[0];
    let _sliceBlob = new Blob([file]).slice(0, 5000);
    let _sliceFile = new File([_sliceBlob], "test.png");
    let fr = new FileReader();
    //图片预览,只要再设置大小即可进行图片缩略
    fr.readAsDataURL(file);
    //图片截取
    //fr.readAsDataURL(_sliceFile)
    fr.onload = function(){
    imgbase64.value = fr.result;
    }
    }

    这里我们也能发现,文本预览其实本质也一样,只不过是读取方式不一样。

    1
    fr.readAsText(file)
  • 文件提交

    文件传输形式其实跟Content-Type有关,主要为两种:

    1. form表单,我们在表单中输入数据,如果不设置enctype,以默认application/x-www-form-urlencoded 方式提交数据;但通常表单上传文件会设置enctype,以multipart/form-data方式。
    
       
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let _fileObj = ref({});
    const fileChange = function(e){
    _fileObj.value = e.target.files[0];
    }
    async submit(){
    let _formData = new FormData();
    _formData.append("file", _fileObj);
    axios.post("/xx_url", _formData);
    }
    1. 以字符串形式,通过fileReader以base64或txt进行传递。

3. 实际操作

虽然标题是实际操作哈,但是前面展示的代码都可在实际中粘贴直接运行。

单文件上传前面已经提到了,2.2文件相关操作中文件提交就是单文件上传,非常简单。

3.1 多文件上传

很多教程都是说input标签直接加multiple即可,确实可以完成对应功能,但是用户体验感不好,需要进行优化。

1
<input type="file" name="file" @change="fileChange" multiple/>

这其实需要用户按住ctrl进行操作,但是用户更加偏向于一份份上传,所以可以进行改良。

1
2
3
4
5
6
7
8
9
import { ref } from 'vue';
let imgList = ref([]);
const fileChange = function(e){
if(e.target.files.length > 1){
imgList.value.concat(e.target.files);
}else{
imgList.value.push(e.target.files[0]);
}
}

上传的时候直接遍历整个imgList,一份份文件上传即可。

1
2
3
4
5
6
7
async submit(){
imgList.forEach((item) => {
let _formData = new FormData();
_formData.append(item.name + "file", item);
axios.post("/xx_url", _formData);
})
}

这里我们可以看出来,多文件上传本质就是循环的单文件上传。

3.2 切片上传

当文件体积过大时,可以使用切片上传,以2M为单位进行切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let _fileObj = ref({});
let percentage = ref(0);
const fileChange = function(e){
_fileObj.value = e.target.files[0];
}
async submit(){
const size = 2 * 1024 * 1024;
const fileSize = _fileObj.size;
let current = 0;
while(current < fileSize){
let _formData = new FormData();
_formData.append(_fileObj.name, _fileObj.slice(current, current + size));
await axios.post("/xx_url", _formData);
precentage = Math.min((current / fileSize) * 100, 100);
current += size;
}
}

3.3 断点续传

我实际没遇到过,这玩意实际应该是后端的活。本质就是切片上传中current存入缓存中,以便下次从current直接开始继续传输。

1
loaclStorage.setItem(_fileObj.name, current);

3.4 图片处理

图片本质也是文件的一种,所以上述文件的操作,图片皆可使用。但是图片上传存在与普通文件不同点,如图片压缩、剪裁等,现进行简要介绍一下概念。

  • 图片压缩

    思路为首先获取文件对象,转成base64,接着创建canvas对象,调用drawImage进行绘制(具体参数分别为对象、横纵坐标、长宽)。最后转化为blob/base64,进行压缩。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <template>
    <div>
    <input type="file" name="file" @change="fileChange"/>
    <img ref="img1" :src="imgbase64" />
    </div>
    </template>

    <script setup>
    import { ref } from 'vue';
    let imgbase64 = ref("");
    // 获取图片的真实DOM
    let img1 = ref(null);
    function fileChange(e){
    // 读取
    let file = e.target.files[0];
    // 预览
    let fr = new FileReader();
    fr.readAsDataURL(file);
    fr.onload = function(){
    imgbase64.value = fr.result;
    // VUE异步,也可以使用$nexttick
    setTimeout(() => {
    // 创建canvas对象,且是2d的
    let pressCanvas = document.createElement("canvas");
    pressCanvas.width = img1.value.width;
    pressCanvas.height = img1.value.height;
    let ctx = pressCanvas.getContext("2d");
    // 绘制图片
    ctx.drawImage(img1.value, 0, 0, img1.value.width, img1.value.height);
    // 进行转换,blob还是base64
    // 注意,转换对象得是真实DOM
    // 参数说明:回调函数,转换类型,压缩值(0-1)
    pressCanvas.toBlob((blob) => {
    // 调用上传接口
    let _formData = new FormData();
    _formData.append("file", blob);
    axios.post("/xx_url", _formData);
    }, "image/jpeg", 0.6)
    })
    }
    }
    </script>
  • 截图保存

    截图保存分为两种:

    1. 截取 图片DOM / canvasDOM / videoDOM,这种只要在绘制完成(drawImage),直接格式转换后直接filesaver即可;
    2. 截取页面上的DOM(div / document.body),则要使用 html2canvas;