偶然在抖音看到一个视频,渡一前端教育,看到了模仿阿里云文件上传,他讲解的是原生js实现,但是element有现成的插件,就想着自己做一个,然后 做了一个简单的demo,基于element+vue2.0实现的文件上传,我就实现了上传列表展示、上传实时监控、取消上传、上传统计等一些功能,后端本来做了指定文件限流、暂停上传、大文件分片上传一些操作,但是代码太多,前后端还有些bug,最近国庆节前工作有点忙,暂时没时间处理,就简化了,这里后端文件上传就提供一个简单的demo,在这里源码就免费奉献给大家,废话不多说,直接上代码。(ps:本人主做后端的,前端只是带着做,前端大佬不喜勿喷)
效果图
前端
前端主要使用element的upload实现,el-upload有很多现成的方法直接调用,包括拖拽一些功能都是自带的,这里实现文件上传监控的方法也是其中之一,就是handleProgress,根据上传返回的已上传字节和总字节来计算上传率,watch实时监控fileList的变化,上传成功、上传时、上传失败、取消都会监控到。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>文件上传</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="../css/element.css">
<script src="../js/vue.js"></script>
<script src="../js/element.js"></script>
<script src="../js/jquery-1.11.0.js"></script>
</head>
<body>
<div id="app">
<el-upload
class="upload-demo"
drag
ref="uploadRef"
action="http://localhost:8686/file/upload"
:on-remove="handleRemove"
:on-progress="handleProgress"
:on-change="handleChange"
:on-success="handleSuccess"
:on-error="handleError"
:file-list="fileList"
:auto-upload="false"
:show-file-list="true"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text" style="margin-top: 10px">支持的文件类型:jpg、png、xml、word、pdf等,单个文件不超过1GB
</div>
</el-upload>
<div class="uploadBtn">
<el-button size="small" type="primary" @click="selectFile">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="primary" @click="submitUpload">上传到服务器</el-button>
</div>
<div class="uploadTable">
<el-tag>文件数量:{{fileTableData.length}}个</el-tag>
<el-tag type="info">总大小:{{fileSizeSum}}M</el-tag>
<el-tag type="success">成功上传:{{fileSuccessCount}}个</el-tag>
<el-button type="danger" size="small" style="float: right" @click="clearTableData">清空上传列表</el-button>
<el-table ref="fileTableData" :data="fileTableData" :cell-style="{textAlign:'center'}" stripe>
<el-table-column prop="name" label="文件名"></el-table-column>
<el-table-column prop="type" label="类型" width="80"></el-table-column>
<el-table-column prop="sizeStr" label="大小"></el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.status=='ready'" size="mini">待上传</el-tag>
<el-tag type="warning" v-else-if="scope.row.status==='uploading'" size="mini">上传中</el-tag>
<el-tag type="danger" v-else-if="scope.row.status==='fail'" size="mini">上传失败</el-tag>
<el-tag type="success" v-else-if="scope.row.status==='success'" size="mini">上传成功</el-tag>
</template>
</el-table-column>
<el-table-column label="进度" width="120">
<template slot-scope="scope">
<el-progress :text-inside="true" :stroke-width="15" :percentage="scope.row.num"></el-progress>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="danger" class="el-icon-close" size="mini"
:disabled="!(scope.row.status==='uploading')"
@click="cancelUpload(scope.$index,scope.row)" title="取消上传"></el-button>
<el-button type="danger" class="el-icon-delete" size="mini"
@click="delFile(scope.$index,scope.row)" title="删除文件"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</body>
<script>
new Vue({
el: '#app',
data() {
return {
fileList: [],
fileTableData: [],
fileSizeSum: 0,
fileSuccessCount: 0,
}
},
watch: {
fileList(newList) {
this.fileTableData = [];
let fileSizeSum = 0;
newList.forEach(item => {
let row = $.extend(true, {}, item);
row.type = item.name.substring(item.name.lastIndexOf("."), item.name.length);
row.sizeStr = Math.round(item.size / 1024) + 'KB';
this.fileTableData.push(row);
fileSizeSum += item.size;
})
this.fileSizeSum = Math.round(fileSizeSum / (1024 * 1024) * 100) / 100;
}
},
methods: {
/**
* 提交上传
*/
submitUpload() {
if (this.fileList.length <= 0) {
this.$message.warning("文件上传列表为空,请选择唔要上传的文件");
return;
}
this.$refs.uploadRef.submit();
},
handleChange(file, fileList) {
this.fileList = fileList;
},
handleRemove(file, fileList) {
this.fileList = fileList;
},
handleProgress(event, file, fileList) {
fileList.forEach(item => {
if (file.uid === item.uid) {
if (item.status != 'success' || item.status != 'fail') {
let num = ((event.loaded / event.total) * 100) | 0; //重新计算上传百分比
item.num = num;
if (num == 100) {
this.fileSuccessCount++;
}
}
}
})
this.fileList = fileList;
},
handleSuccess(response, file, fileList) {
this.fileList = fileList;
},
handleError(err, file, fileList) {
this.$message.error("服务器连接失败,请检查网络重新上传");
},
selectFile() {
const fileInput = this.$refs.uploadRef.$el.querySelector('input[type=file]');
fileInput.click();
},
clearTableData(fileList) {
this.fileList = [];
this.fileTableData = [];
this.fileSizeSum = 0;
this.fileSuccessCount = 0;
},
cancelUpload(index, row) {
let afterFileList = this.fileList;
if (row.status === 'uploading') {
console.info("取消上传:" + row.name);
this.$refs.uploadRef.abort(row.raw);
afterFileList.forEach(item => {
if (item.uid === row.uid) {
item.status = 'ready';
//item.num = 0;//进度条归零
}
})
}
this.fileList = afterFileList;
this.setTableData(this.fileList);
},
delFile(index, row) {
this.fileList.splice(index, 1);
},
setTableData(newList) {
this.fileTableData = [];
let fileSizeSum = 0;
newList.forEach(item => {
let row = $.extend(true, {}, item);
row.type = item.name.substring(item.name.lastIndexOf("."), item.name.length);
row.sizeStr = Math.round(item.size / 1024) + 'KB';
this.fileTableData.push(row);
fileSizeSum += item.size;
})
this.fileSizeSum = Math.round(fileSizeSum / (1024 * 1024) * 100) / 100;
},
}
});
</script>
<style>
.el-upload {
display: contents;
}
.el-upload-dragger {
width: 60%;
margin: auto;
}
.el-upload-list {
width: 60%;
margin: auto;
}
.uploadBtn {
width: 60%;
margin: 20px auto;
}
.uploadTable {
width: 60%;
margin: auto;
}
table th .cell {
color: #606266;
text-align: center;
font-size: 13px;
}
.el-badge__content.is-fixed.is-dot {
right: 5px;
}
.el-badge__content.is-dot {
height: 8px;
width: 8px;
padding: 0;
right: 0;
border-radius: 50%;
}
.el-badge__content.is-fixed {
position: absolute;
top: 0;
right: 10px;
transform: translateY(-50%) translateX(100%);
}
.el-badge__content {
background-color: #f56c6c;
border-radius: 10px;
color: #fff;
display: inline-block;
font-size: 12px;
height: 18px;
line-height: 18px;
padding: 0 6px;
text-align: center;
white-space: nowrap;
border: 1px solid #fff;
}
</style>
</html>
后端
后端代码就很简单了,就不多赘述,做了一个批量上传文件,稍微懂一点java的都可以看懂
@RestController
@RequestMapping("file")
public class UploadController {
@ApiOperation("文件上传")
@PostMapping("/upload")
@ResponseBody
public Object upload(HttpServletRequest request) {
Map<String, String> resultMap = new HashMap<>();
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
if (multipartResolver.isMultipart(request)) {
MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
//取得request中的所有文件名
Iterator<String> iter = multiRequest.getFileNames();
while (iter.hasNext()) {
//获取文件流
MultipartFile uploadFile = multiRequest.getFile(iter.next());
//获取文件名
String beforeName = uploadFile.getOriginalFilename();//原始文件名
String realPath = "C:/文件上传测试/upload1";
File uploadDir = new File(realPath);
//不存在文件夹则创建
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
String uuid = UUID.randomUUID().toString();
String afterName = uuid + "_" + beforeName;
try {
uploadFile.transferTo(new File(uploadDir, afterName));
} catch (IOException e) {
e.printStackTrace();
return "上传失败";
}
resultMap.put("fileId", uuid);
resultMap.put("fileName", afterName);
return resultMap;
}
}
return "上传失败";
}
}