vue + elementui + axios表单数据和文件上传

实现的功能:
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对象
实现的功能:
  • 表单校验
  • 添加删除附件时,附件规则校验,及时提醒用户
  • 报名时,使用loading提示用户
  • loading中显示上传的百分比

前端完整的代码:

<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;
    }

你可能感兴趣的:(Vue,vue,文件上传)