1. 同时上传文件和JSON
1 情景再现
最近在忙一个代码生成的项目,有一个需求需要同时上传用户选择的配置信息以及
sql文件进行解析得到表数据或者是点击生成一个springboot的增删改查导入导出的前后端项目
- 地址:base.lesscoding.net
- 账号:admin
- 密码:111111
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 后端接口
这里采用的是
MultipartFile和String类型进行接收的,后端拿到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存储数据,每次点击添加之后会在表格中添加一行数据
- 首先需要给
el-table绑定数据commonProperties- 给
el-table添加表头以及表格列的类型- 点击添加按钮往
commonProperties中添加一条空数据即可- 每行的删除功能需要对行数据进行删除
- 页面组件添加
<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;
});
}