实现的功能:
1、表单数据提交,
2、表单中携带文件附件。
3、附件上传过程中进度提示。
前端使用:vue + elementui + axios
后端使用:springboot
介绍之前,先学习2个小技巧设置
1、全局loading弹框定义使用
创建一个loading.js文件:
import {Loading} from 'element-ui'
const loading = function(text) {
return Loading.service({
lock: true,
text: text,
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
};
export default loading;
在vue的script里面导入:
import loading from '../loading';
显示loading const _loading = loading(
作品附件上传中,请稍后...
) 关闭loading
_loading.close(); // 关闭加载框 更新文字
_loading.setText(‘作品上传中,进度:’ + this.progressPercent + “%”) //更新dialog进度,优化体验
2、legend线条的颜色设置
legend需要在fieldset里面,所以设置fieldset就可以。设置颜色和边框圆角
fieldset {
border:2px solid #DCDFE6; text-align:left; border-radius: 8px;
}
下面介绍2种上传文件附件的方法:
1、vue + elementui + axios上传文件的第一种方式
通过选择文件触发对应的钩子回调函数,在回调中给全局的file赋值(特别注意raw),提交的时候使用这个File类型文件。
SpringBoot的接口写法
@Transactional
@ApiOperation(notes = "报名参加接口", value = "报名参加接口", httpMethod = "POST")
@PostMapping(value = "/apply",produces = "application/json;charset=UTF-8")
public Result apply(RegistrationInfo registrationInfo, @RequestParam(value = "files")MultipartFile files){
//同时接收RegistrationInfo这个bean参数和参数名为files的MultipartFile类型参数。
//我们使用DataForm,格式提交就可以。
...
}
vue的代码
<el-form-item label="作品上传:" prop="files" size = 'small' >
<el-upload
ref="upload_attach"
class="upload-demo"
action="/user/apply"
multiple
accept=".zip,.rar,.7z"
:limit="1"
:on-change="changFile"
:on-exceed="handleExceed"
:file-list="fileList"
:auto-upload="false">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<div slot="tip" class="el-upload__tip">注:上传的文件须是压缩文件格式,且不超过50M</div>
</el-upload>
</el-form-item>
备注:**:on-change=“changFile”,在选取文件后,通过在changFile回调中给全局变量files赋值。**实际测试只有这个方法回调成功
methods中定义的方法
changFile(file, fileList) {
console.log(fileList);
//选择文件后,给fileList对象赋值
this.fileList = fileList
},
提交上传:
apply(){
let data = new FormData();
// todo 非常重要,一定要加file.raw,从浏览器中查看需要使用binary类型,后台才能正确接收
this.form.files = this.fileList[0].raw
console.log(this.fileList[0].raw)
// 将form表单中的值都赋值给FormData传递给后台
for(let key in this.form){
console.log(this.form[key])
data.append(key,this.form[key])
}
this.$axios
.post('/user/apply',data,{
headers: {
'Content-Type': 'multipart/form-data'
}})// 第一种,直就传个json数据,不需要设置headers
// .post('/user/apply',this.form)// 第三种,可以直接传递这个form(推荐)
.then(resp => {
console.log('请求本地接口OK')
console.log(resp)
if(resp.data.code == -1){
// 接口返回-1,就是注册失败,提示消息提示用户
this.$message({
message: resp.data.msg,
type: 'error'
});
} else if(resp.data.code == 0){
console.log(resp.data)
//注册成功
this.$message({
message: "报名成功",
type: 'success'
});
// 跳转到登录页面
// this.$router.push('/login')
}
})
.catch(function (error) { // 请求失败处理
console.log('请求本地接口失败' + error);
});
},
测试功能OK
特别需要注意:这样才能取到文件对象
this.form.files = this.fileList[0].raw
2、vue + elementui + springboot 上传文件的第二种方式
通过调用el-upload的submit方法,触发自定义函数,拿到param里面的File参数。
this.form.files = param.file // 将form中的files字段赋值File对象
实现的功能:
前端完整的代码:
<template>
<div class='myelement' v-show = body_show>
<p>报名页面</p>
<el-form ref="form" :model="form" label-width="130px" :rules="rules" >
<fieldset style="width:40%;" >
<legend >个人信息</legend>
<!-- 姓名 -->
<el-form-item label="姓名:" prop="apply_person_name" size = 'small' >
<el-input v-model="form.apply_person_name" placeholder="请输入姓名" class="input_width"></el-input>
</el-form-item>
</fieldset>
<br>
<fieldset style="width:40%;">
<legend>报名信息</legend>
<el-form-item label="学校/公司/组织:" prop="apply_company" size = 'small' >
<el-input v-model="form.apply_company" placeholder="请输入公司名" class="input_width" ></el-input>
</el-form-item>
<el-form-item label="专业/部门:" prop="apply_department" size = 'small' >
<el-input v-model="form.apply_department" placeholder="请输入部门" class="input_width"></el-input>
</el-form-item>
<el-form-item label="报名赛区:" prop="apply_area" size = 'small'>
<el-radio v-model="form.apply_area" label="集团内部赛区">集团内部赛区</el-radio>
<el-radio v-model="form.apply_area" label="社会开放赛区">社会开放赛区</el-radio>
</el-form-item>
<el-form-item label="作品方向:" prop="competition_product_target" size = 'small'>
<el-radio v-model="form.competition_product_target" label="精益生产">精益生产</el-radio>
<el-radio v-model="form.competition_product_target" label="智慧服务">智慧服务</el-radio>
<el-radio v-model="form.competition_product_target" label="创新应用">创新应用</el-radio>
</el-form-item>
<el-form-item label="团队名称:" prop="team_name" size = 'small' >
<el-input v-model="form.team_name" placeholder="请输入团队名称" class="input_width"></el-input>
</el-form-item>
</fieldset>
<br>
<fieldset style="width:40%;">
<legend>团队负责人</legend>
<el-form-item label="姓名:" prop="team_leader_name" size = 'small' >
<el-input v-model="form.team_leader_name" placeholder="请输入队长姓名" class="input_width" ></el-input>
</el-form-item>
<el-form-item label="岗位:" prop="team_leader_job" size = 'small' >
<el-input v-model="form.team_leader_job" placeholder="请输入队长岗位" class="input_width"></el-input>
</el-form-item>
<el-form-item label="微信号:" prop="team_leader_wechat" size = 'small'>
<el-input v-model="form.team_leader_wechat" placeholder="请输入队长微信号" class="input_width"></el-input>
</el-form-item>
<el-form-item label="手机号码:" prop="team_leader_phone" size = 'small'>
<el-input v-model="form.team_leader_phone" placeholder="请输入队长手机号" maxlength="11" show-word-limit class="input_width"></el-input>
</el-form-item>
<el-form-item label="邮箱:" prop="team_leader_email" size = 'small' >
<el-input v-model="form.team_leader_email" placeholder="请输入队长邮箱" class="input_width"></el-input>
</el-form-item>
<el-form-item label="身份证号码:" prop="team_leader_id_number" size = 'small' >
<el-input v-model="form.team_leader_id_number" placeholder="请输入队长身份证号码" maxlength="18" show-word-limit class="input_width"></el-input>
</el-form-item>
</fieldset>
<br>
<fieldset style="width:40%;">
<legend>作品提交</legend>
<el-form-item label="参赛作品名称:" prop="competition_product_name" size = 'small' >
<el-input v-model="form.competition_product_name" placeholder="请输入参赛作品名称" class="input_width" ></el-input>
</el-form-item>
<el-form-item label="参赛作品简介:" prop="competition_product_introduce" size = 'small' >
<el-input type = "textarea" v-model="form.competition_product_introduce" placeholder="参赛作品简介(500字内)"
maxlength="500" show-word-limit class="input_width" :rows="5" ></el-input>
</el-form-item>
<el-form-item ref="upload_attach_item" label="作品上传:" prop="files" size = 'small' >
<el-upload
ref="upload_attach"
class="upload-demo"
action="/user/apply"
multiple
accept=".zip,.rar,.7z"
:limit="1"
:on-change="changFile"
:on-exceed="handleExceed"
:on-remove="removeFile"
:file-list="fileList"
:auto-upload="false"
:http-request="uploadSectionFile">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<div slot="tip" class="el-upload__tip">注:上传的文件须是压缩文件格式,且不超过50M</div>
</el-upload>
<el-progress :percentage="progressPercent" v-show="show_progress"></el-progress>
</el-form-item>
<el-form-item label="附件名称:" prop="competition_product_attach_name" size = 'small' >
<el-input v-model="form.competition_product_attach_name" placeholder="请填写附件名称" class="input_width" ></el-input>
</el-form-item>
</fieldset>
<br>
<br>
<div style="text-align:left">
<el-button type="primary" v-on:click="onSubmit('form')">报名</el-button>
</div>
</el-form>
</div>
</template>
<script>
import loading from '../loading';
export default {
name: 'Apply',
data () {
//验证密码
var validateAttach = (rule, value, callback) => {
console.log(this.fileList.length)
if (this.fileList.length == 0) {
callback(new Error('请选择附件'));
} else {
callback();
}
};
return {
body_show : true,
form: {
apply_person_name: '',
apply_company: '',
apply_department: '',
apply_area: '集团内部赛区',
competition_product_target: '精益生产',
team_name: '',
team_leader_name: '',
team_leader_job: '',
team_leader_wechat: '',
team_leader_phone: '',
team_leader_email: '',
team_leader_id_number: '',
competition_product_name: '',
competition_product_introduce: '',
files:null,
competition_product_attach_name: '',
},
fileList:[],
progressPercent:0,
show_progress:false,
rules: {
apply_person_name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
],
apply_company: [
{ required: true, message: '请输入学校/公司/组织', trigger: 'blur' },
],
apply_department: [
{ required: true, message: '请输入专业/部门', trigger: 'blur' },
],
apply_area: [
],
competition_product_target: [
],
team_name: [
{ required: true, message: '请输入团队名称', trigger: 'blur' },
],
team_leader_name: [
{ required: true, message: '请输入队长姓名', trigger: 'blur' },
],
team_leader_job: [
{ required: true, message: '请输入队长岗位', trigger: 'blur' },
],
team_leader_wechat: [
{ required: true, message: '请输入队长微信号', trigger: 'blur' },
],
team_leader_phone: [
{ required: true, message: '请输入队长手机号', trigger: 'blur' },
{ min: 11, max: 11, message: '输入11位数字', trigger: 'blur' },
],
team_leader_email: [
{ required: true, message: '请输入队长邮箱', trigger: 'blur' },
],
team_leader_id_number: [
{ required: true, message: '请输入队长身份证号码', trigger: 'blur' },
{ min: 18, max: 18, message: '输入18位身份证号', trigger: 'blur' },
],
competition_product_name: [
{ required: true, message: '请输入参赛作品名称', trigger: 'blur' },
],
competition_product_introduce: [
{ required: true, message: '请输入参赛作品简介', trigger: 'blur' },
],
files: [
// { required: true, message: '请输入选择参赛作品', trigger: 'blur' },
{ validator: validateAttach }
],
competition_product_attach_name: [
{ required: true, message: '请输入附件名称', trigger: 'blur' },
],
},
}
},
methods: {
changFile(file, fileList) {
console.log("changFile");
console.log(fileList);
//选择文件后,给fileList对象赋值
this.fileList = fileList
this.$refs.upload_attach_item.validate();
},
removeFile(file, fileList){
this.fileList = fileList
this.$refs.upload_attach_item.validate();
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制最多选择 1 个文件`);
},
onSubmit(formName) {
// 校验合法性
this.$refs[formName].validate((valid) => {
if (valid) {
// alert('发送post请求!');
console.log('submit!')
console.log(this.form)
this.$refs.upload_attach.submit() // 触发调用uploadSectionFile,拿到param参数里面的File
} else {
console.log('error submit!!');
this.$message({
message: '请填写完整信息再后提交',
type: 'error'
});
return false;
}
});
// this.apply();
// this.$refs.upload_attach.submit() // 触发调用uploadSectionFile,拿到param参数里面的File
},
uploadSectionFile(param) {
console.log(param)
let data = new FormData();
// todo 非常重要,一定要加file.raw,从浏览器中查看需要使用binary类型,后台才能正确接收
// this.form.files = this.fileList[0].raw
// console.log(this.fileList[0].raw)
this.form.files = param.file // 将form中的files字段赋值File对象
console.log(param.file)
// 将form表单中的值都赋值给FormData传递给后台
for(let key in this.form){
data.append(key,this.form[key])
}
const _loading = loading(`作品附件上传中,请稍后...`)
// this.show_progress = true
const config = {
onUploadProgress: progressEvent => {
// progressEvent.loaded:已上传文件大小
// progressEvent.total:被上传文件的总大小
this.progressPercent = Number((progressEvent.loaded / progressEvent.total * 100).toFixed(0))
_loading.setText('作品上传中,进度:' + this.progressPercent + "%") //更新dialog进度,优化体验
console.log(this.progressPercent)
},
headers: {
'Content-Type': 'multipart/form-data'
}
}
this.$axios
.post(param.action,data,config)
.then(resp => {
console.log('请求本地接口OK')
console.log(resp)
this.fileList = [];// 提交完成清空附件列表
_loading.close(); // 关闭加载框
// this.show_progress = false
this.progressPercent = 0
if(resp.data.code == -1){
// 接口返回-1,就是报名失败,提示消息提示用户
this.$message({
message: resp.data.msg,
type: 'error'
});
} else if(resp.data.code == 0){
console.log(resp.data)
//报名成功
this.$message({
message: "报名成功",
type: 'success'
});
// 跳转到主页面
// this.$router.replace('/home')
}
})
.catch(function (error) { // 请求失败处理
console.log('请求本地接口失败' + error);
});
},validateAttach (rule, value, callback) {
console.log(value)
console.log(this.$refs.upload_attach)
},
},
created () {
},
}
</script>
<style scoped>
.myelement {
text-align:left
}
.input_width{
width: 50%;
width: 300px;
}
fieldset {
border:2px solid #DCDFE6; text-align:left; border-radius: 8px;
}
</style>
后端接口
@Autowired
private UserService userService;
@Transactional
@ApiOperation(notes = "报名参加接口", value = "报名参加接口", httpMethod = "POST")
@PostMapping(value = "/apply",produces = "application/json;charset=UTF-8")
public Result apply(@ApiParam(name = "registrationInfo", required = true, value = "registrationInfo") RegistrationInfo registrationInfo,
@RequestParam(value = "files")MultipartFile files){
System.out.println("registrationInfo = " + registrationInfo.toString());
HashMap<Object,Object> map = new HashMap();
//将用户插入数据库中
userService.addRegistrationInfo(registrationInfo);
//上传附件
String attachName = registrationInfo.getCompetition_product_attach_name();
Map<String, Object> uploadResultMap = userService.uploadAttachFile(files, attachFileNameMap.getId());
boolean success = (boolean) uploadResultMap.get("success");
if(!success){
map.put("result","附件上传失败");
return ResultUtil.error(uploadResultMap.toString(), ReturnCode.CODE_FAIL,"附件上传失败" );
}
map.put("result","报名成功");
return ResultUtil.success(map.toString(), ReturnCode.CODE_SUCCESS,"success");
}
上传代码实现:
@Override
public Map<String, Object> uploadAttachFile(MultipartFile file,String uploadAttachName) {
HashMap<String, Object> map = new HashMap<>();
String dirName = "";
String filePath = attachFileUploadPath;
List<String> accTypes = Arrays.asList(".rar", ".zip");
try {
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.lastIndexOf("."));
/** 校验文件合法性 */
if (accTypes.contains(suffixName)) {
System.out.println(suffixName + "后缀合法");
} else {
System.out.println(suffixName + "后缀不合法");
map.put("success", false);
map.put("message", "上传文件类型有误");
return map;
}
// TODO: 2020/6/20 这里分割符不同系统可能不一样
suffixName = ".zip";
String attachName = uploadAttachName;
if(StringUtils.isEmpty(uploadAttachName)){//用户上传了附件名称,使用用户名称命名
attachName = fileName.substring(fileName.lastIndexOf("\\")+1);
attachName = attachName.split("\\.")[0] + suffixName;//使用统一后缀名
}
System.out.println("attachName = " + attachName + " || suffixName = " + suffixName);
// TODO: 2020/6/20 需要使用唯一的标记符来命名附件名称 日期 or teamName ?
System.out.println("附件路径 = " + filePath);
File folder = new File(filePath);
if(!folder.exists()){
folder.mkdirs();
}
File f = new File(filePath + attachName);
File fileParent = f.getParentFile();
if (!fileParent.exists()) {
fileParent.mkdirs();
}
file.transferTo(f);
map.put("success", true);
map.put("message", "success to upload");
System.out.println("附件上传成功");
} catch (Exception e) {
e.printStackTrace();
String msg = e.getCause().toString();
map.put("success", false);
map.put("message", msg);
}
return map;
}