选择文件后修改再上传失败(element-自定义上传)
上传的场景
先说一下是怎么遇到这个问题的,项目里面有上传组件的使用,这个是非常正常的,几乎没什么项目没有这个玩意,element-ui里面也有el-upload组件,一般的就是选择完文件就自动上传了,如图:
(自动上传效果)
我这里的需求是:
- 点击【选择】按钮先选择文件,展示文件的详情:类型,大小,日期......
- 点击【上传】按钮这个时候才去上传文件
如图:
(选择再上传效果)
修改文件导致的上传bug
然后开发好了之后,某天......测试同学反馈了一个bug,在测试上传功能的时候选择了一个文件,发现自己填漏了一个数据,后端立刻又打开文件填上去,继续点击上传按钮,发现这个时候就报错了。
这个时候我就开始思考(baidu),发现是浏览器的安全机制,如果要上传的文件先选择然后进行了修改再上传则会被拒绝上传,这个时候两个思路
- 通过把选择的文件转成base64,从而使得和本地的文件状态失去关联,无论你怎么修改文件再点击上传,我上传的还是原来那份 ❌
这种做法我个人不是很推荐,毕竟用户去做了修改就是想得到可以上传到最新的文件的意愿,这个时候你没有通知到用户这样不可行,还是按照原文件去上传了,会让用户误会已经按照自己意愿传了新的文件上去。(如果真的需要这个做法参考的链接也放在下面了,本人没有实验过,就是看到有这种方法)
- 反馈提示给到用户,告知用户文件已经修改请重新上传 ⭕️
这个是我采取的一种方法,既然用户做了修改,我们还是遵循浏览器的本意,让用户把最新的文件重新上传一遍,毕竟这也不是一个费时费力的操作,那下面就说一下这个做法
开始解决问题
整理思路
首先我们来整理一下思路,我们做自定义上传的往往都是拿到上传上来的文件变量,也就是一个file
,拿到后我们往往是创建一个formData
对象然后把file
作为一个属性append
进去,然后还要append
一些别的我们需要自定义的属性,然后把组装好的formData
上传也就是像下面这样:
const file = '假装我是一个文件'; // 上传控件拿到的file文件,也就是你上传的文件
const remark = '备注'; // 也是需要上传接口添加的属性
const formData = new FromData(); // 创建formData对象
formData.append('file', file); // 添加文件
formData.append('remark', remark); // 添加备注
axios.post('attachment/upload', formData); // 把formData作为参数请求接口
前置知识,看看我们拿到得file文件是怎么样得:
{status: "success", uid: 1664333398524, lastModified: 1663224825714, lastModifiedDate: Thu Sep 15 2022 14:53:45 GMT+0800 (中国标准时间) {}, name: "我的待办.xls", size: 106496, type: "application/vnd.ms-excel", webkitRelativePath: ""}
上传的流程就是这样,那么我们通过选择文件的操作拿到了file
之后要怎么判断文件是否有过更改呢?
- 是否有监听文件的改变的监听函数?
- 答:好像没有,反正我没找到 ❌
- 既然我们的接口请求会失败那是否可以通过
promise
的catch
捕获错误然后提示用户? - 答:这个确实会
catch
到个错误,但是没有可以捕获一个特定的异常,例如你以为有类似file has change
这种报错信息就没有,没办法确定这是通过修改了文件无法上传报的错误 ❌
- 想通过
file
的改变时间来判断是否文件有更改 - 答:你会发现你的
file
文件的时间是一样的并没有更改 ❌
- 通过读取当前的文件判断是否有更改
- 答: 这个是可取的,因为如果文件进行了修改,你去读取的同样会得到拒绝,因为文件发生了更改也是无法读取的,这就可以判断到是否有过更改,这就是我努力思考(google)的结果 ✅
代码实现
先看代码
// 判断是否修改了选择的文件
try {
// 这里读取文件
// file文件是个对象按道理不能slice,但是继承自blob
// blob可以看成二进制字符串,有slice方法
// blob得arrayBuffer会把二进制转成arraybuffer,并且返回promise,转得过程就要读取文件
await this.file.slice(0 ,1).arrayBuffer();
}catch (e) {
this.$error(`选择的文件已经修改,请重新选择上传:${e}`);
return;
}
再看解析
- 我们是可以得到一个
file
文件,这个是确定的,其实我们的file
对象是特殊的Blob
,file
接口也继承了Blob
接口的属性 - 那我们读取这个文件是没必要去读取整个文件我们通过
file.slice(0, 1)
截取一点即可(你会说为什么file
是个对象也可以slice
,因为Blob
对象表示一个不可变、原始数据的类文件对象,file
继承了Blob
,所以也有slice
的方法调用) - 然后我们把截取到的转
arrayBuffer
,file.slice(0, 1).arrayBuffer()
这个转的过程中,就会读取到文件返回一个promise
,这个时候如果读取成功了说明文件没有进行更改,如果被拒绝了则说明文件进行了更改,我们就告知用户需要重新选择文件
参考文档
转载自:https://juejin.cn/post/7148260919045750815