一、之前遇到的一些问题
项目中多出有上传文件的需求,使用现有的UI框架实现的过程中,不知道什么原因,总会有一些莫名其妙的bug。比如用某上传组件,明明注明(:multiple="false"),可实际上还是能多选,上传的时候依然发送了多个文件;又比如只要加上了(:file-list="fileList")属性,希望能手动控制上传列表的时候,上传事件this.refs.[upload(组件ref)].submit()就不起作用了,传不了。总之,懒得再看它怎么实现了,我用的是功能,界面本身还是要重写的,如果坚持用也会使项目多很多不必要的逻辑、样式代码……
之前用Vue做项目用的视图框架有element-ui,团队内部作为补充的zp-ui,以及iview。框架是好用,但是针对自己的项目往往不能全部拿来用,尤其是我们的设计妹子出的界面与现有框架差异很大,改源码效率低又容易导致未知的bug,于是自己就抽时间封装了这个上传组件。
二、代码与介绍
父组件
<template> <div> <label for="my-upload"> <span>上传</span> </label> <my-upload ref="myUpload" :file-list="fileList" action="/uploadPicture" :data="param" :on-change="onChange" :on-progress="uploadProgress" :on-success="uploadSuccess" :on-failed="uploadFailed" multiple :limit="5" :on-finished="onFinished"> </my-upload> <button @click="upload">Upload</button> </div> </template> <script> import myUpload from './components/my-upload' export default { name: 'test', data(){ return { fileList: [],//上传文件列表,无论单选还是支持多选,文件都以列表格式保存 param: {param1: '', param2: '' },//携带参数列表 } }, methods: { onChange(fileList){//监听文件变化,增减文件时都会被子组件调用 this.fileList = [...fileList]; }, uploadSuccess(index, response){//某个文件上传成功都会执行该方法,index代表列表中第index个文件 console.log(index, response); }, upload(){//触发子组件的上传方法 this.$refs.myUpload.submit(); }, removeFile(index){//移除某文件 this.$refs.myUpload.remove(index); }, uploadProgress(index, progress){//上传进度,上传时会不断被触发,需要进度指示时会很有用 const{ percent } = progress; console.log(index, percent); }, uploadFailed(index, err){//某文件上传失败会执行,index代表列表中第index个文件 console.log(index, err); }, onFinished(result){//所有文件上传完毕后(无论成败)执行,result: { success: 成功数目, failed: 失败数目 } console.log(result); } }, components: { myUpload } } </script>
父组件处理与业务有关的逻辑,我特意加入索引参数,便于界面展示上传结果的时候能够直接操作第几个值,并不是所有方法都必须的,视需求使用。
子组件
<template> <div> <input @change="addFile" :multiple="multiple" type="file" :name="name"/> </div> </template>
上传文件,html部分就这么一对儿标签,不喜欢复杂啰嗦
<script> export default { name: 'my-upload', props: { name: String, action: { type: String, required: true }, fileList: { type: Array, default: [] }, data: Object, multiple: Boolean, limit: Number, onChange: Function, onBefore: Function, onProgress: Function, onSuccess: Function, onFailed: Function, onFinished: Function }, methods: {}//下文主要是methods的介绍,此处先省略 } </script>
这里定义了父组件向子组件需要传递的属性值,注意,这里把方法也当做了属性传递,都是可以的。
自己写的组件,没有像流行框架发布的那样完备和全面,另外针对开头提到的绑定file-list就不能上传了的问题(更可能是我的姿势不对),本人也想极力解决掉自身遇到的这个问题,所以希望能对文件列表有绝对的控制权,除了action,把file-list也作为父组件必须要传递的属性。(属性名父组件使用“-”连接,对应子组件prop中的驼峰命名)
三、主要的上传功能
methods: { addFile, remove, submit, checkIfCanUpload }
methods内一共4个方法,添加文件、移除文件、提交、检测(上传之前的检验),下面一一讲述:
1.添加文件
addFile({target: {files}}){//input标签触发onchange事件时,将文件加入待上传列表 for(let i = 0, l = files.length; i < l; i++){ files[i].url = URL.createObjectURL(files[i]);//创建blob地址,不然图片怎么展示? files[i].status = 'ready';//开始想给文件一个字段表示上传进行的步骤的,后面好像也没去用...... } let fileList = [...this.fileList]; if(this.multiple){//多选时,文件全部压如列表末尾 fileList = [...fileList, ...files]; let l = fileList.length; let limit = this.limit; if(limit && typeof limit === "number" && Math.ceil(limit) > 0 && l > limit){//有数目限制时,取后面limit个文件 limit = Math.ceil(limit); // limit = limit > 10 ? 10 : limit; fileList = fileList.slice(l - limit); } }else{//单选时,只取最后一个文件。注意这里没写成fileList = files;是因为files本身就有多个元素(比如选择文件时一下子框了一堆)时,也只要一个 fileList = [files[0]]; } this.onChange(fileList);//调用父组件方法,将列表缓存到上一级data中的fileList属性 },
2.移除文件
这个简单,有时候在父组件叉掉某文件的时候,传一个index即可。
remove(index){ let fileList = [...this.fileList]; if(fileList.length){ fileList.splice(index, 1); this.onChange(fileList); } },
3.提交上传