01vue+axios+formData实现文件上传(包含简单的Java后台)

参考文章:

用纯css美化<input type=file/>按钮
vue+axios实现文件上传简单原理以及操作
萌新用vue + axios + formdata 上传文件的爬坑之路
从0开始做一个的Vue图片/ 文件选择(上传)组件[基础向]
vue中利用axios实现文件上传进度实时更新
Vue实现带进度条的文件拖动上传
spring boot 文件上传
阿里巴巴iconfont怎么是正确的使用方式?

实现效果

效果图

1.用html和css画出文件上传组件

参考文章用纯css美化<input type=file/>按钮并且利用里面的样式实现。
其中文章的要点是:
1.1文件上传使用<input type=file/>
1.2组合label标签和<input type=file/>美化文件上传

label好用在于,它可以跟input的click事件关联上,这就实现了语义化解决方案。因此点击这两个元素的任何一个都能得到相同的结果——弹出文件上传选择对话框。

其中隐藏<input type=file/>标签的css为:

       .inputfile {
            width: 0.1px;
            height: 0.1px;
            opacity: 0;
            overflow: hidden;
            position: absolute;
            z-index: -1;
        }

你可能会好奇为什么宽和高会设成0.1px而不是0px,因为在某些浏览器下0宽高将会让<input>元素被tab键忽略。而position: absolute的目的是不干扰随后元素的位置。

1.3html+css实现静态页面代码

<body>
    <div id="app" class="m-5">
        <div class="uploadBox">
            <h3>上传文件</h3>
            <div class="fileBox">
                <input type="file" id="myFile" class="inputfile" @change="handlerUpload($event)">
                <label for="myFile">
                    <i class="iconfont">&#xe632;</i>点击上传本地文件
                </label>
            </div>
            <div class="fileInfo">
                <ul class="files">
                    <li v-for="(file, index) in files">
                        {{ file.name }}
                    </li>
                </ul>
            </div>
        </div>
    </div>
</body>

1.4实现效果
1.5icon
在静态页面中引用了阿里的icon库。引入方式可以参考:
阿里巴巴iconfont怎么是正确的使用方式?
详细的步骤为:搜索获取适合的图标->加入购物车->添加至项目->下载->将对应iconfont.css复制至css文件夹下引用即可

页面效果

2.构造form'Data,使用axios上传文件

在项目中使用axios上传文件,记得new一个纯净的axios或者考虑用ajax请求。因为axios在项目估计已经用了全局配置请求头等信息,这里的配置可能被全局请求头拦截,导致请求失败。
2.1构造formData

 let param = new FormData();
 param.append("name", "wiiiiiinney");
//通过append向form对象添加数据
 param.append("file", file);
//FormData私有类对象,访问不到,可以通过get判断值是否传进去
 console.log(param.get("file"));

2.2以下为全局请求头的配置

      let config = {
        //添加请求头
        headers: { "Content-Type": "multipart/form-data" },
        //添加上传进度监听事件
        onUploadProgress: e => {
          var completeProgress = ((e.loaded / e.total * 100) | 0) + "%";
          this.progress = completeProgress;
        }
      };

2.2axios发送请求

 axios.post('http://127.0.0.1:8778/upload', param, config).then(
function (response) 
{ console.log(response); })
.catch(function (error) {
 console.log(error);
 });

4.后端接收文件

java接收代码:

@Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 允许cookies跨域
        config.addAllowedOrigin("*");// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080 ,以降低安全风险。。
        config.addAllowedHeader("*");// 允许访问的头信息,*表示全部
        config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了

        config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等
        /*
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");// 允许Get的请求方法
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        */
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

private static final Logger logger = LoggerFactory.getLogger(Application.class);
    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setLocation("d:/tmp");
        return factory.createMultipartConfig();
    }

    @RequestMapping(value = "/upload")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file,@RequestParam("name")String name) {
        logger.info("name: "+name);
        if (file.isEmpty()) {
            return "文件为空";
        }
        // 获取文件名
        String fileName = file.getOriginalFilename();
        logger.info("上传的文件名为:" + fileName);
        // 获取文件的后缀名
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        logger.info("上传的后缀名为:" + suffixName);
        // 文件上传路径
        String filePath = "d:/roncoo/ttt/";
        // 解决中文问题,liunx 下中文路径,图片显示问题
        //fileName = UUID.randomUUID() + suffixName;
        File dest = new File(filePath + fileName);
        // 检测是否存在目录
        if (!dest.getParentFile().exists()) {
            dest.getParentFile().mkdirs();
        }
        try {
            file.transferTo(dest);
            return "上传成功";
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "上传失败";
    }

6.将上传信息反馈

6.1给每个文件设置一个进度条
用uploadPercentage记录上传进度,uploadPercentage的变化由axios的progress事件监听计算得出。

                        var item = {
                            name: tFiles[i].name,
                            uploadPercentage: 1,
                            size: this.formatFileSize(tFiles[i].size, 0),
                            uploadStatus: 0
                        }
                        console.log(item)
                        this.files.push(item);

6.2给每个文件设置一个上传状态
uploadStatus记录文件状态。

状态码 含义
0 初始状态
1 上传中
2 已上传
-1 服务器错误
-2 上传文件类型不符合要求
-3 上传文件超出限制

6.3检测函数大小的函数

                checkFileSize:function(fileSize) {
                    //2M
                    const MAX_SIZE = 2 * 1024 * 1024;
                    if (fileSize > MAX_SIZE) {
                        return false;
                    }
                    return true;
                }

6.4检测文件类型的函数

                checkFileType: function (fileType) {
                    const acceptTypes = ['xls', 'doc'];
                    for (var i = 0; i < acceptTypes.length; i++) {
                        if (fileType === acceptTypes[i]) {
                            return true;
                        }
                    }
                    return false;
                }

6.4格式化文件大小的函数

               formatFileSize: function (fileSize, idx) {
                    var units = ["B", "KB", "MB", "GB"];
                    idx = idx || 0;
                    if (fileSize < 1024 || idx === units.length - 1) {
                        return fileSize.toFixed(1) +
                            units[idx];
                    }
                    return this.formatFileSize(fileSize / 1024, ++idx);
                },

7.全部代码

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        li {
            list-style: none;
        }

        /*引入阿里的icon*/

        @font-face {
            font-family: "iconfont";
            src: url('iconfont.eot?t=1534844614970');
            /* IE9*/
            src: url('iconfont.eot?t=1534844614970#iefix') format('embedded-opentype'), /* IE6-IE8 */
            url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAATgAAsAAAAAB1AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8qEgkY21hcAAAAYAAAABLAAABcOdatkBnbHlmAAABzAAAASwAAAFY2/C2dWhlYWQAAAL4AAAALwAAADYSY0OKaGhlYQAAAygAAAAcAAAAJAfeA4NobXR4AAADRAAAAAgAAAAICAAAAGxvY2EAAANMAAAABgAAAAYArAAAbWF4cAAAA1QAAAAgAAAAIAEPAH1uYW1lAAADdAAAAUUAAAJtPlT+fXBvc3QAAAS8AAAAIwAAADQ9SsrveJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeGT0zYm7438AQw9zA0AAUZgTJAQDk9QxHeJxjYGBgZWBgYAZiHSBmYWBgDGFgZAABP6AoI1icmYELLM7CoARWwwISf2b0/z+MBPJZwCQDIxvDKOABkzJQHjisIJiBEQCjLgoxAHicHY3BTsJAGIR3drv/0hZaqXVLIKJSugVNQAOUxCKYeNKLMTHRB+Bi4sGTD2B8GV8C4zNxJepC/vxzmMzMxwRjfz/iW1wzn52xN8YQQLWRzDEdQJA60Co1C5gJhSAzLbZ3BB1TZwgjY2XTaceMixK2UZi8Y2u5Iesb+3ZIl9CJppTyAYaYFFObG21XEp3EI30EzlYbKTernX6B89ALAAFuvARwsiwI7sgPyXBo7p6ffLqe737Eh1K2Wxrifn65bKhKFjXbTtpS6rUu4LvlrFcSwL2y59Dpy/vjQMmZB7HYgRxnq78PwDqSMlpbHM9cv6pk1q3Vbin0KeNdomMDr+4h2W/2uF48+2o03kvhVPpF5gChW7WEoi8SonyIaNloPV3cyPoVZ+wfIBIx1XicY2BkYGAA4r3qZgbx/DZfGbhZGEDg+sL/xxD0/4MsDMwOQC4HAxNIFAA6yguGAHicY2BkYGBu+N/AEMPCAAJAkpEBFTABAEcIAmsEAAAABAAAAAAAAAAArAAAAAEAAAACAHEAAwAAAAAAAgAAAAoACgAAAP8AAAAAAAB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxjYGKAAC4G7ICJkYmRmYGrOCMxLz05ozQxj4EBACJZBEAA') format('woff'), url('iconfont.ttf?t=1534844614970') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
            url('iconfont.svg?t=1534844614970#iconfont') format('svg');
            /* iOS 4.1- */
        }

        .iconfont {
            font-family: "iconfont" !important;
            font-size: 16px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }

        .icon-shangchuan:before {
            content: "\e632";
        }


        .iconfont {
            font-family: "iconfont";
            font-size: 18px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -webkit-text-stroke-width: 0.2px;
            -moz-osx-font-smoothing: grayscale;
            padding-left: 20px
        }

        .uploadBox {
            width: 400px;
            border: 1px solid #ccc;
            margin: 100px auto;
        }

        .fileBox,
        .fileInfo {
            margin: 16px;
            height: 60px;
            line-height: 60px;
            border: 1px solid #ccc;
            padding-left: 16px;
            font-size: 16px;
        }

        .inputfile {
            width: 0.1px;
            height: 0.1px;
            opacity: 0;
            overflow: hidden;
            position: absolute;
            z-index: -1;
        }

        /*E + F 毗邻元素选择器,匹配所有紧随E元素之后的同级元素F*/

        .inputfile+label {
            color: #3e97df;
            display: inline-block;
        }

        .inputfile:focus+label,
        .inputfile+label:hover {
            color: #0c89f0;
        }

        h3 {
            padding: 10px 0 0 16px;
            font-weight: normal;
            font-size: 18px;
            color: #666;
        }

        .filePart {
            line-height: 30px;
            overflow: hidden;
            float: left;
            text-overflow: ellipsis;
            white-space: nowrap;
            font-size: 12px;
            height: 30px;
        }

        .fileStatus {
            overflow: hidden;
            float: left;
            height: 20px;
            font-size: 10px;
            line-height: 20px;
        }

        .ml10 {
            margin-left: 10px;
        }

        .fileName {
            width: 200px;
        }

        .fileSize {
            width: 120px;
            text-align: center;
        }

        .uploadFail {
            color: #ff0800d3;
        }

        .uploadSuccess {
            color: #2c832c;
        }

        /*对应CSS*/

        .progress {
            position: relative;
            width: 80%;
            height: 8px;
            border: 1px solid #ccc;
            border-radius: 5px;
            overflow: hidden;
            /*注意这里*/
            box-shadow: 0 0 1px 0px #ddd inset;
        }

        .progress span {
            position: absolute;
            display: inline-block;
            width: 10%;
            height: 100%;
            background-color: #3e97df;
        }
    </style>
    <title>Document</title>
</head>

<body>
    <div id="app" class="m-5">
        <div class="uploadBox">
            <h3>上传文件</h3>
            <div class="fileBox">
                <input type="file" id="myFile" class="inputfile" @change="handlerUpload($event)">
                <label for="myFile">
                    <i class="iconfont">&#xe632;</i>点击上传本地文件
                </label>
            </div>
            <ul class="files">
                <li v-for="(file, index) in files">
                    <div class="fileInfo">

                        <div class="fileName filePart">
                            {{ file.name }}
                        </div>
                        <div class="fileSize filePart ml10">
                            {{file.size}}
                        </div>
                        <!--进度条-->
                        <div class="progress">
                            <span :style="{width:file.uploadPercentage,backgroundColor:file.uploadStatus==1 ||file.uploadStatus==2?'':'red'}"></span>
                        </div>
                        <div class="fileStatus">
                            <span v-if="file.uploadStatus == -1" class="uploadFail">出错啦,请重新上传或者删除</span>
                            <span v-if="file.uploadStatus == 2" class="uploadSuccess"> 已上传</span>
                            <span v-if="file.uploadStatus == 1" class="uploadSuccess"> 上传中...</span>
                            <span v-if="file.uploadStatus == -2" class="uploadFail">出错啦,文件类型不符合要求</span>
                            <span v-if="file.uploadStatus == -3" class="uploadFail">出错啦,文件大小超出限制</span>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                files: [],
                uploadSuccess: 0
            },
            methods: {
                handlerUpload: function (e) {
                    //获取选定的文件
                    let tFiles = e.target.files;
                    let len = tFiles.length;
                    for (var i = 0; i < len; i++) {
                        //开始上传每一个文件
                        var item = {
                            name: tFiles[i].name,
                            uploadPercentage: 1,
                            size: this.formatFileSize(tFiles[i].size, 0),
                            uploadStatus: 0
                        }
                        console.log(item)
                        this.files.push(item);
                        //开始上传文件 新建一个formData
                        let param = new FormData();
                        param.append("name", "wiiiiiinney");
                        //通过append向form对象添加数据
                        param.append("file", tFiles[i]);
                        //FormData私有类对象,访问不到,可以通过get判断值是否传进去
                        console.log(param.get("file"));
                        //判断大小
                        if (!this.checkFileSize(tFiles[i].size)) {
                            item.uploadStatus = -3;
                            return false;
                        }
                        if (!this.checkFileType(tFiles[i].name.split('.')[1])) {
                            item.uploadStatus = -2;
                            return false;
                        }
                        //通过axios上传文件
                        //配置
                        let config = {
                            //添加请求头 
                            headers: {
                                "Content-Type": "multipart/form-data"
                            },
                            //添加上传进度监听事件 
                            onUploadProgress: e => {
                                var completeProgress = ((e.loaded / e.total * 100) | 0) + "%";
                                console.log(this.files)
                                item.uploadPercentage = completeProgress;
                            }
                        };
                        axios.post('http://127.0.0.1:8778/upload', param, config).then(function (
                            response) {
                            console.log(response);
                            item.uploadStatus = 2;
                        }).catch(function (error) {
                            console.log(error);
                            item.uploadStatus = -1;
                        });
                    }
                },
                formatFileSize: function (fileSize, idx) {
                    var units = ["B", "KB", "MB", "GB"];
                    idx = idx || 0;
                    if (fileSize < 1024 || idx === units.length - 1) {
                        return fileSize.toFixed(1) +
                            units[idx];
                    }
                    return this.formatFileSize(fileSize / 1024, ++idx);
                },
                checkFileType: function (fileType) {
                    const acceptTypes = ['xls', 'doc', 'jpg'];
                    for (var i = 0; i < acceptTypes.length; i++) {
                        if (fileType === acceptTypes[i]) {
                            return true;
                        }
                    }
                    return false;
                },
                checkFileSize: function (fileSize) {
                    //2M
                    const MAX_SIZE = 2 * 1024 * 1024;
                    if (fileSize > MAX_SIZE) {
                        return false;
                    }
                    return true;
                }
            }
        });
    </script>
</body>

</html>

8.参考代码、使用ajax上传文件、实现拖拽功能

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
    <style>
        .dropbox {
            border: .25rem dashed #007bff;
            min-height: 5rem;
        }
    </style>
    <title>Document</title>
</head>

<body>
    <div id="app" class="m-5">
        <div class="dropbox p-3">
            <h2 v-if="files.length===0" class="text-center">把要上传的文件拖动到这里</h2>
            <div class="border m-2 d-inline-block p-4" style="width:15rem" v-for="file in files">
                <h5 class="mt-0">{{ file.name }}</h5>
                <div class="progress">
                    <div class="progress-bar progress-bar-striped" :style="{ width: file.uploadPercentage+'%' }"></div>
                </div>
            </div>
        </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                files: []
            },
            methods: {
                uploadFile: function (file) {
                    var item = {
                        name: file.name,
                        uploadPercentage: 0
                    };
                    this.files.push(item);
                    var fd = new FormData();
                    fd.append('myFile', file);

                    var xhr = new XMLHttpRequest();
                    xhr.open('POST', 'http://127.0.0.1:8778/upload', true);
                    xhr.upload.addEventListener('progress', function (e) {
                        item.uploadPercentage = Math.round((e.loaded * 100) / e.total);
                    }, false);
                    xhr.send(fd);
                },
                onDrag: function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                },
                onDrop: function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                    var dt = e.dataTransfer;
                    for (var i = 0; i !== dt.files.length; i++) {
                        this.uploadFile(dt.files[i]);
                    }
                }
            },
            mounted: function () {
                var dropbox = document.querySelector('.dropbox');
                dropbox.addEventListener('dragenter', this.onDrag, false);
                dropbox.addEventListener('dragover', this.onDrag, false);
                dropbox.addEventListener('drop', this.onDrop, false);
            }
        });
    </script>
</body>

</html>

推荐阅读更多精彩内容