vue + elementUI同时上传文件和JSON 参数 Axios文件下载、动态生成表格行

694 阅读1分钟

1. 同时上传文件和JSON

1 情景再现

最近在忙一个代码生成的项目,有一个需求需要同时上传用户选择的配置信息以及sql文件进行解析得到表数据或者是点击生成一个springboot的增删改查导入导出的前后端项目

2 前端页面

前端页面采用的是vue + elementui的框架,上传采用的是el-upload

  • 首先加载组件
<template>
	<el-upload
          class="upload-demo"
          drag
          :action="uploadUrl"
          :auto-upload="true"
          :accept="acceptList"
          @error="uploadError"
          :on-remove="handlerRemove"
          :before-upload="beforeUpload"
          :file-list="fileList"
        >
        <i class="el-icon-upload"></i>
          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
          <div class="el-upload__tip" slot="tip">
            <el-tooltip class="item" effect="dark" content="请选择数据库类型用于生成正确的代码。数据库名称会根据上传的sql自动获取,请务必保证不包含中文字符" placement="top-start">
              <el-form :inline="true" :model="ruleForm" :rules="rules" ref="ruleForm">
                  <el-row>
                    <el-col :span="2">
                      <el-form-item>
                        <el-button type="primary" @click="doGenerate">生成</el-button>
                      </el-form-item>
                    </el-col>
				  </el-row>   
                </el-form>
            </el-tooltip>
          </div>
    </el-upload>    
</template>
  • 添加文件上传相关的触发方法

这种办法呢就是让输入框自动上传,但是上传到一个空路径,在console中会有一个报错,但是不影响,上传之前会调用beforeUpload方法,将文件放到params

<script>
	export default{
        data(){
            return{
                params:new FormData(),
                fileList:[],
                uploadUrl:"",
                acceptList:'.sql,.zip',
            }
        },
        methods:{
             beforeUpload(file){
                this.params = new FormData()
                this.fileList = []
                if(file){
                  //  2. 上传之前,拿到file对象,并将它添加到刚刚定义的FormData对象中。
                  this.params.append('file',file); 
                  this.filename = file.name
                  this.fileList.push({name:file.name})
                }else{
                  this.$message.error('文件上传失败')
                  return false;
                }
              },
              // 上传错误回调
              uploadError(event,file,fileList){
                console.log(event,file,fileList)
              },
              // 删除文件回调
              handlerRemove(file,fileList){
                this.fileList = []
                this.params = new FormData()
              }
        }
    }
</script>
  • 生成按钮方法实现
doGenerate(){
    this.$refs['ruleForm'].validate((valid) => {
        if(valid){
            if(this.checkAndBuildParam()){
                generate.doGenerateByFile(this.params).then(res=>{
                    this.downloadFile(res)
                })
                    .catch(err => {
                    this.$message.error('非开发角色或序列号有误,请微信联系17663702529')
                })
            }
        }else{
            return false
        }
    })
},
checkAndBuildParam(){
    // 往params中添加你的参数,参数需要使用JSON.stringify()转换成string字符串
    this.params.delete("configJson")
    this.params.append('configJson', this.getTemplateConfig())
    return this.checkFileExt()
},   
    
// 校验文件
checkFileExt(){
    if(this.filename == '' || this.filename == null){
        this.$message.error('请上传.sql文件')
        return false
    }
    if(!this.filename.endsWith('.sql')){
        this.$message.error('请上传.sql文件')
        return false
    }
    return true
},
  • api.js实现
import request from '@/utils/request'

export default {
  doGenerateByFile(param){
    return request({
      url: '/gen/doGenByFile',
      method: 'post',
      data: param,
      responseType: 'blob', //后续下载文件需要用到
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  }
}

3 后端接口

这里采用的是MultipartFileString类型进行接收的,后端拿到String类型的参数配置之后,通过Gson转成实体类对象

@PostMapping("/parse")
@ApiOperation(value = "解析sql")
public Result parseSql(MultipartFile file, String configJson){
    TemplateConfig templateConfig = new Gson().fromJson(configJson, TemplateConfig.class);
    return null;
}

2. 使用Axios下载文件

代码生成器的需求中在上传完成sql之后,点击生成需要同时上传json和文件信息,然后调用生成方法下载生成的代码,同时上传文件和json在上一章节已经完成,这一章节主要描述怎么下载文件

当时也是百度了好多,有的是使用Axios下载文件,有的是使用Fetch下载,但是后边这个我没有实现,所以用的就是前边这个Axios

  • Api.js方法中添加设置,让request返回一个Blob类型
doGenerateByFile(param){
    return request({
      url: '/gen/doGenByFile',
      method: 'post',
      data: param,
      responseType: 'blob', //后续下载文件需要用到
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  }
  • vue 页面中使用
generate.doGenerateByFile(this.params).then(res=>{
    this.downloadFile(res)
}),
// 将文件下载传过来的blob流接收到    
downloadFile(res){
    if(!res){
        return
    }
    // 构造一个blob对象来处理数据,并设置文件类型
    const blob = new Blob([res], { type: 'application/octet-stream' }) 
    //兼容IE10
    if (window.navigator.msSaveOrOpenBlob) { 
        navigator.msSaveBlob(blob, this.filename + '.zip')
    } else {
        //创建新的URL表示指定的blob对象
        const href = URL.createObjectURL(blob)
        //创建a标签
        const a = document.createElement('a') 
        a.style.display = 'none'

        a.href = href 
        //指定下载文件名
        a.download = this.filename + '.zip'
        //触发下载
        a.click() 
        //释放URL对象
        URL.revokeObjectURL(a.href) 
    }
    // 这里也可以不创建a链接,直接window.open(href)也能下载
}
  • 改造Axios封装

对axios的response进行改造,让其对流类型放行

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})
service.interceptors.response.use(response => {
	const res = response.data
    let type = res.type
    // 判断响应的类型,如果是刘文建的话直接返回
    if(type && type === 'application/octet-stream'){
        return res
    }
})

3. 动态添加表格列

因为需求中涉及到创建表和表字段的操作,所以需要实现动态添加表格列这个功能,

采用的是el-card ,使用el-table存储数据,每次点击添加之后会在表格中添加一行数据

  1. 首先需要给el-table绑定数据commonProperties
  2. el-table添加表头以及表格列的类型
  3. 点击添加按钮往commonProperties中添加一条空数据即可
  4. 每行的删除功能需要对行数据进行删除
  • 页面组件添加
<template>
	<el-card class="box-card">
        <div slot="header" class="clearfix">
            <span>公共参数</span>
            <el-button style="float: right;" type="primary" @click="addProperty" size="mini">添加</el-button>
        </div>
        <el-table
                  style="width: 100%"
                  :data="commonProperties"
                  >
            <el-table-column type="index" label="序号" width="100" />
            <el-table-column prop="columnName" label="属性名" width="150">
                <template slot-scope="scope">
    <el-input v-model="scope.row.columnName" autocomplete="off" size="small"
              placeholder="属性名" @change="changeJavaType(scope.$index,scope.row)"></el-input>
                </template>
            </el-table-column>
            <el-table-column prop="dataType" label="数据库类型" width="150">
                <template slot-scope="scope">
    <el-input v-model="scope.row.dataType" autocomplete="off" size="small"
              placeholder="请输入数据库类型"></el-input>
                </template>
            </el-table-column>
            <el-table-column prop="fieldType" label="java类型" width="150">
                <template slot-scope="scope">
    <el-input v-model="scope.row.fieldType" autocomplete="off" size="small"
              placeholder="请输入java类型" ></el-input>
                </template>
            </el-table-column>
            <el-table-column prop="fieldNme" label="字段名" width="150">
                <template slot-scope="scope">
    <el-input v-model="scope.row.fieldName" autocomplete="off" size="small"
              placeholder="请输入字段名" disabled></el-input>
                </template>
            </el-table-column>
            <el-table-column prop="defaultValue" label="默认值" width="200">
                <template slot-scope="scope">
    <el-input v-model="scope.row.defaultValue" autocomplete="off" size="small"
              placeholder="请输入默认值"></el-input>
                </template>
            </el-table-column>
            <el-table-column
                             label="操作"
                             width="180">
                <template slot-scope="scope">
    <el-button size="mini" type="danger" plain @click="delProperty(scope.$index, scope.row)">删除</el-button>
                </template>
            </el-table-column>
        </el-table>
    </el-card>
</template>
  • el-table初始化数据
<script>
    export default{
        data(){
            return {
                commonProperties:[]
            }
        }
    }
</script>   
  • 添加按钮点击事件,往commonProperties中添加一条数据
addProperty(){
    console.log('公共属性');
    let property = this.commonProperties
    console.log(property);
    // 添加index属性,为了等会能够删除这一条数据
    let length = property.length
    this.commonProperties.push(
        {
            index:parseInt(length),
            columnName:'',
            dataType:'',
            fieldType:'',
            fieldName:'',
            defaultValue:''
        }
    )
}
  • 删除行方法

在添加行的时候就设置好了index和row的属性,下边删除的时候直接从数组中按照下标删除即可

// 删除公共数据
delProperty(index, row) {
    let that = this;
    this.$confirm('确认删除吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(() => {
        //点击确定的操作(调用接口)
        let property = that.commonProperties;
        for (let i = 0; i < property.length; i++) {
            if (row.index == property[i].index) {
                property.splice(i, 1);
                // this.$message({ message: '删除成功', duration: 2000, type: 'success' });
            }
        }
    }).catch(() => {
        //点取消的提示
        return;
    });
}