01vue+axios+formData实现文件上传(包含简单的
参考文章:
用纯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>