前言:
介绍拖拽文件和选择文件两种操作,获取文件信息以及上传服务器的思路和流程
项目前端环境:vue,jquery
可拖拽文件批量上传
刚开始我是直接用的element-ui的上传组件,里面自带一个简单的拖拽属性,后来发现完全满足不了我们项目的各种奇葩需求。于是开始尝试从0手写一个可拖拽文件批量上传的功能。
1.布局Html代码:
<!--测试demo--> <div class="file-upload-demo"> <!--拖拽区域--> <div class="drag-wrap"> <div ref="select_frame" class="drag-inner"> <!--无内容时提示信息--> <div v-show="fileList.length<1" class="tip">拖拽上传简历文件</div> <!--显示上传的文件列表--> <div v-show="fileList.length" class="filebox scroll-wrap"> <ul> <li v-for="item in fileList" v-bind:class="'file-'+item.state"> <i class="fa fa-file-text-o"></i> {{item.name}} <!--状态:上传失败/上传成功--> <span class="file-state" v-if="item.state"> <span v-if="item.state == 'failure'" v-bind:title="item.msg">上传失败 <i class="fa fa-times-circle"></i></span> <span v-else-if="item.state == 'success'">上传成功 <i class="fa fa-check-circle"></i></span> <span v-else>未知</span> </span> <!--状态:上传中--> <span class="file-state" v-else> 上传中... </span> </li> </ul> </div> </div> </div> <!--按钮--> <button class="btn_save" v-on:click="openFileIpt" style="margin-top: 10px;">选择文件</button> <!--隐藏文件域--> <input ref="fileIpt" type="file" v-bind:multiple="isMultiple" v-show="false" name="file" v-on:change="inputFiles" /> </div>
这个布局是根据我当前项目需求来的,没有剥离出最简单版。凑合看吧~
目前效果如下:
2.在vue实例的mounted钩子函数里,给select_frame这个元素写拖拽的四个事件:ondragleave,ondrop,ondragenter,ondragover
这就是为什么需要给拖拽元素写一个ref
<div ref="select_frame" class="drag-inner"> ... </div>
js代码如下:
mounted () { //文件拖拽上传 this.$refs.select_frame.ondragleave = (e) => { e.preventDefault(); //阻止离开时的浏览器默认行为 }; this.$refs.select_frame.ondrop = (e) => { e.preventDefault(); //阻止拖放后的浏览器默认行为 this.fileList = []; var files = e.dataTransfer.files; // 获取文件对象 if (files.length < 1) return; //调用上传方法 if (this.isMultiple) { this._uploadFiles(files); } else { this._uploadFile(files[0]); } }; this.$refs.select_frame.ondragenter = (e) => { e.preventDefault(); //阻止拖入时的浏览器默认行为 this.$refs.select_frame.border = '2px dashed red'; }; this.$refs.select_frame.ondragover = (e) => { e.preventDefault(); //阻止拖来拖去的浏览器默认行为 }; },
首先需要阻止浏览器默认行为
拖拽的四个事件方法里都可以拿到event对象,选择的文件信息就在event对象里。
var files = e.dataTransfer.files; // 获取文件对象
3.这里因为项目需求是既需要批量选择也需要单选,根据入口不同来切换内容。这两个功能我共用了一段代码,只用isMultiple来控制单选还是多选。作为案例有点不妥。勉强看看吧~
拿到文件信息后开始调用上传方法,上传方法里包括文件数量、文件大小、文件格式等校验代码以及提交服务器的ajax代码,methods钩子函数里面批量上传方法如下:
//批量文件上传 _uploadFiles: function (files) { var _this = this; var len = files.length; var accept = ['.html', '.doc', '.docx', '.htm', '.wps']; //文件格式 var fileSize = 1024; //文件大小1M //校验文件数量,超过8个取前8个,并给出提示,不影响流程 if (len > 8) { len = 8; _this.$message.error('上传文件不可超过8个'); } //创建一个FormData实例用来保存文件信息 var formData = new FormData(); //遍历文件校验类型和大小 for (let i = 0; i < len; i++) { //文件类型验证 var verifyType = accept.some(function (item) { return files[i].name.indexOf(item) > -1; }) //文件size验证 var thisFileSize = files[i].size / 1024; //因为拿到的文件大小单位是B,需要除以1024换算成KB var verifySize = (thisFileSize < fileSize); if (!verifyType) { _this.$message.error(files[i].name + '文件格式错误'); } else if (!verifySize) { _this.$message.error(files[i].name + '文件大小不可超出1M'); } else { formData.append('file', files[i]); this.fileList.push(files[i]); } } if (this.fileList.length < 1) return; $.ajax({ url: '/xxx/Candidate/AnalysisResumeBatch?candidatetype=1', type: 'post', data: formData, dataType: 'json', contentType: false, processData: false, beforeSend: function () { _this.loading = true; }, success: function (res) { if (res.IsSuccess) { console.log(res.Data); var data = res.Data; _this.fileList.forEach(function (file) { var name = file.name; for (let i = 0; i < data.length; i++) { if (name != data[i].FileName) { continue; } else { file.state = data[i].Status; file.msg = data[i].InfoMsg; return false; } } }) } else { _this.$message.error(res.Msg); } }, error: function () { _this.$message.error('请求失败'); }, complete: function () { _this.loading = false; } }) },
ajax success回调代码有点复杂,这是因为返回的数据问题,每个项目要根据返回数据来处理。这里可以忽略,不作说明。
之所以把单个上传方法和批量上传方法分开是因为,这两者的校验复杂度不一样,异步回调处理也不一样。为了代码更好理解和维护,就这么做了。其实还可以优化下。
到这里就算一个完整的功能了,但我必须再提一种获取文件信息的方法,点击按钮获取文件信息。
6.按钮代码如下:
<!--按钮--> <button class="btn_save" v-on:click="openFileIpt" style="margin-top: 10px;">选择文件</button> <!--隐藏文件域--> <input ref="fileIpt" type="file" v-bind:multiple="isMultiple" v-show="false" name="file" v-on:change="inputFiles" />
首先,之所以放2个元素是为了美化上传文件控件。这里就需要通过按钮来触发文件域的选择操作。这个技巧经常用到。
首先给文件域绑定一个ref,v-show="false"默认隐藏
<input ref="fileIpt" type="file" v-bind:multiple="isMultiple" v-show="false" name="file" v-on:change="inputFiles" />
7.按钮上绑定的点击事件方法 openFileIpt
//点击按钮触发文件域的点击事件 openFileIpt: function () { this.$refs.fileIpt.click(); },
通过ref找到文件域,触发它的click();
这一步点按钮和点文件域input是一样的效果了。
8.现在开始写文件上传事件 inputFiles
//文件域改变后执行上传文件 inputFiles: function () {//通过上传文件域对象拿到文件信息集合var files = this.$refs.fileIpt.files; if (files.length < 1) return; //调用上传方法 if (this.isMultiple) { this._uploadFiles(files); } else { this._uploadFile(files[0]); } }
选择的文件信息就藏在this.$refs.fileIpt.files里面,后面的步骤跟拖拽时一样调用上传方法。
结尾:
本文重在讲解思路和流程,代码不是很完整,有些变量需要在data里定义,没有贴出来。