每日技术总结:可拖拽文件批量上传

前言:

介绍拖拽文件和选择文件两种操作,获取文件信息以及上传服务器的思路和流程

项目前端环境: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里定义,没有贴出来。

原文地址:https://www.cnblogs.com/cathy1024/p/10875383.html