用纯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"></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"></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>