vue3+element-plus+springboot 表单多文件上传

1.知识要点
1.1数据模型
const formDate = ref({
division: ‘’, //部门
reimbursementTime: null, //报销日期
reimbursementApplicant: ‘’, // 报销人
businessTraveller: ‘’, // 出差人
beOnABusinessTripCause: ‘’, // 出差事由
reimbuursementCountPrice: null, // 报销总额
advanceMoney: null, //预支旅费
advanceTime: ‘’, //预支时间
replenishMoney: null, //不领不足(应补金额)
returnMoney: null, //归还多余(应退金额)
leadSignature: ‘’, //主管签字
accountingFunctionaryUser: ‘’, //财务审核
divisionFunctionaryUser: ‘’, //部门审核
domain: [
{
invoiceTiem: ‘’, // 开票日期
invoiceNumber: ‘’, // 发票号码
invoiceCode: ‘’, // 发票代码
invoiceName: ‘’, //发票项目名称
fileList: [] // 文件数组
}
],
mFileList: []
})
1.2 vue代码
1.2.1 html代码



点击上传

  <template v-slot:tip>
    <div class="el-upload__tip">只能上传文件,且不超过5M</div>
  </template>
</el-upload>

1.2.2文件状态改变功能函数
const handleChange = (uploadFile) => {
formDate.value.mFileList.push(uploadFile.raw)
}
1.2.3提交代码
将表单中的formDate数据中的部分数据添加到djOrderTourDto对象中,后面将djOrderTourDto和fileList(文件数据)转化成FormData()数据
// 提交数据
const submitForm = async () => {
for (let i = 0; i < formDate.value.domain.length; i++) {
console.log(‘当前的:’, formDate.value.domain[i])
}

const fd = new FormData()
// 提取formDate中的domain数据
const invoices = formDate.value.domain.map((m) => ({
invoiceCode: m.invoiceCode,
invoiceName: m.invoiceName,
invoiceNumber: m.invoiceNumber,
invoiceTime: m.invoiceTiem
}))

for (let invoice of invoices) {
console.log(‘invoices的数据:’, invoice)
}
console.log(‘invoices的数据类型:’, Array.isArray(invoices))

// formDate.value.mFileList.push(…formDate.value.domain.map((m) => m.fileList))

console.log(‘当前的文件数据:’, JSON.stringify(formDate.value.mFileList))
// 创建DjOrderTourDto
const djOrderTourDto = {
invoices,
division: formDate.value.division,
reimbursementTime: formDate.value.reimbursementTime,
reimbursementApplicant: formDate.value.reimbursementApplicant,
businessTraveller: formDate.value.businessTraveller,
beOnABusinessTripCause: formDate.value.beOnABusinessTripCause, // 出差事由
reimbuursementCountPrice: formDate.value.reimbuursementCountPrice, // 报销总额
advanceMoney: formDate.value.advanceMoney, //预支旅费
advanceTime: formDate.value.advanceTime, //预支时间
replenishMoney: formDate.value.replenishMoney, //不领不足(应补金额)
returnMoney: formDate.value.returnMoney, //归还多余(应退金额)
leadSignature: formDate.value.leadSignature, //主管签字
accountingFunctionaryUser: formDate.value.accountingFunctionaryUser, //财务审核
divisionFunctionaryUser: formDate.value.divisionFunctionaryUser //部门审核
}

fd.append(‘djOrderTourDto’, JSON.stringify(djOrderTourDto))
// fd.append(‘fileList’, formDate.value.mFileList)

for (let m of formDate.value.mFileList) {
fd.append(‘fileList’, m)
}
loading.value = true
await tourReimbursementInfoAll(fd)
loading.value = false
ElMessage.success(‘提交成功’)
}

1.2.4 api接口
当你使用FormData对象来构造请求体时,不需要在请求头中手动设置Content-Type。FormData对象会自动设置正确的Content-Type,通常是multipart/form-data,这允许它携带多个部分的数据,包括文本和二进制数据(如文件)
import request from ‘@/utils/request.js’
export const tourReimbursementInfoAll = (data) =>
request.post(‘/invoice/upload’, data)

1.2.2 重点:
给数据处理后,给api发送到后端

for (let m of formDate.value.mFileList) {
fd.append(‘fileList’, m)
}
文件数组,那么按照前端格式,就是相同键名,不同的Value值
因为我是同一个数据接收多个文件对象,类型于下面
[
{fileObjiect},
{fileObjiect}
]

1.3 后端Springboot
1.3.1 关键代码
DjOrderTourController.java
如果前端发送的是表单数据(非JSON格式),那么在后端接收时,你应该使用@ModelAttribute注解而不是@RequestBody。
Fastjson本身并不直接处理multipart/form-data格式的数据,因为它设计用于处理JSON格式的数据。在你的场景中,前端发送的multipart/form-data包含了JSON字符串化的djOrderTourDto,这部分数据需要先从multipart/form-data中提取出来,然后才能用Fastjson进行反序列化。
以下是一个步骤说明:
从multipart/form-data中提取JSON字符串: 后端需要从请求中获取djOrderTourDto字段,这通常通过@RequestPart注解完成,因为@RequestParam通常用于处理简单的表单字段,而不是multipart/form-data中的部分。
反序列化JSON字符串: 一旦得到了djOrderTourDto的JSON字符串,可以使用Fastjson的JSON.parseObject()或JSON.toJavaObject()方法将其转换为DjOrderTourDto实体类。
这是一个示例代码片段,展示了如何处理multipart/form-data中的JSON字符串并反序列化为
public R addInvoiceAllInfo(@RequestPart(“djOrderTourDto”)String djOrderTourDtoJson, @RequestPart(“fileList”) MultipartFile[] files) {

// 使用Fastjson反序列化
DjOrderTourDto djOrderTourDto = JSON.parseObject(djOrderTourDtoJson, DjOrderTourDto.class);

Integer tourId = iDjOrderTourService.saveAndGetId(djOrderTour);

DjOrderTourServiceImpl.java
保存一个DjOrderTour对象,并立即获取其自增ID。
直接使用自增ID: 既然您使用的是MyBatis Plus,且假设id字段是自增的,可以在插入后直接使用djOrderTour对象中的ID,因为大多数ORM框架会在执行插入后自动填充自增主键到对象中。这样可以避免额外的查询操作。
确保事务安全: 如果数据库操作需要保证原子性(即保存和获取ID必须同时成功或失败),请确保此方法在一个事务中执行。

在Spring Boot应用中启用事务管理,你需要按照以下步骤操作:
添加依赖:确保你的项目中包含了Spring Data JPA或者MyBatis等持久层框架的依赖,因为Spring的事务管理通常与这些框架一起使用。
配置类启用事务管理:在你的配置类(通常是一个带有@Configuration注解的类)中,添加@EnableTransactionManagement注解,这会启用Spring的声明式事务管理。
启动类加@EnableTransactionManagement注解
@Transactional // 添加事务注解,确保操作的原子性
public Integer saveAndGetId(DjOrderTour djOrderTour) {
djOrderTourMapper.insert(djOrderTour);
/* // 查询最新的ID
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(DjOrderTour::getId);
DjOrderTour one = getOne(queryWrapper, false);
return one.getId();*/

djOrderTourMapper.insert(djOrderTour);
return djOrderTour.getId();

}

2.完整示例代码
2.1 前端 (vue3+element-plus)
2.1.1 页面

<el-row :gutter="20">
  <el-col :span="6">
    <el-form-item label="报销总额">
      <el-input v-model="formDate.reimbuursementCountPrice"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="6">
    <el-form-item label="预支旅费">
      <el-input v-model="formDate.advanceMoney"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="6">
    <el-form-item label="补领不足">
      <el-input v-model="formDate.replenishMoney"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="6">
    <el-form-item label="归还多余">
      <el-input v-model="formDate.returnMoney"></el-input>
    </el-form-item>
  </el-col>
</el-row>

<el-row :gutter="20">
  <el-col :span="6">
    <el-form-item label="主管签字">
      <el-input v-model="formDate.leadSignature"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="6">
    <el-form-item label="财务审核">
      <el-input v-model="formDate.accountingFunctionaryUser"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="6">
    <el-form-item label="部门审核">
      <el-input v-model="formDate.divisionFunctionaryUser"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="6">
    <el-form-item label="报销日期">
      <el-date-picker
        v-model="formDate.reimbursementTime"
        type="date"
        placeholder="请选择日期"
        format="YYYY-MM-DD"
      />
    </el-form-item>
  </el-col>
</el-row>

<el-form
:model=“formDate”
v-for=“(dom, index) in formDate.domain”
:key=“dom.key”
:label=“‘费用项目’ + (index + 1)”
:label-position=“‘top’”

<el-row>
  <el-col :span="3">
    <el-form-item
      label="发票名称"
      :prop="'domain' + index + '.invoiceName'"
    >
      <el-input v-model="dom.invoiceName"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="1"></el-col>
  <el-col :span="4">
    <el-form-item
      label="发票代码"
      :prop="'domain' + index + '.invoiceCode'"
    >
      <el-input v-model="dom.invoiceCode"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="1"></el-col>
  <el-col :span="4">
    <el-form-item
      label="发票号码"
      :prop="'domain' + index + '.invoiceNumber'"
    >
      <el-input v-model="dom.invoiceNumber"></el-input>
    </el-form-item>
  </el-col>
  <el-col :span="1"></el-col>
  <el-col :span="3">
    <el-form-item
      label="开票日期"
      :prop="'domain' + index + '.invoiceTiem'"
    >
      <el-date-picker
        v-model="dom.invoiceTiem"
        type="date"
        placeholder="Pick a date"
      />
    </el-form-item>
  </el-col>
  <el-col :span="1"></el-col>
  <el-col :span="4">
    <el-form-item label="附件上传" :prop="'domain' + index + '.fileList'">
      <el-upload
        class="upload-demo"
        :auto-upload="false"
        :on-change="handleChange"
        :file-list="dom.fileList"
      >
        <el-button size="small" type="primary">点击上传</el-button>

        <template v-slot:tip>
          <div class="el-upload__tip">只能上传文件,且不超过5M</div>
        </template>
      </el-upload>
    </el-form-item>
  </el-col>
  <el-col :span="2" style="padding: 30px">
    <el-form-item>
      <el-button type="warning" @click.prevent="removeDomain(dom)"
        >删除</el-button
      >
    </el-form-item>
  </el-col>
</el-row>

2.2 api接口

import request from ‘@/utils/request.js’
export const tourReimbursementInfoAll = (data) =>
request.post(‘/invoice/upload’, data)
2.1.2 后端 Springboot

@PostMapping(“/upload”)
public R addInvoiceAllInfo(@RequestPart(“djOrderTourDto”)String djOrderTourDtoJson, @RequestPart(“fileList”) MultipartFile[] files) {

    // 使用Fastjson反序列化
    DjOrderTourDto djOrderTourDto = JSON.parseObject(djOrderTourDtoJson, DjOrderTourDto.class);

// 通过静态方法获取差旅费报销单对象
/DjOrderTour djOrderTour = getDjOrderTour(djOrderTourDto);/
// 将DTO中的赋值给差旅费报销单DjOrderTour
DjOrderTour djOrderTour = new DjOrderTour();
djOrderTour.setDivision(djOrderTourDto.getDivision());
djOrderTour.setReimbursementTime(djOrderTourDto.getReimbursementTime());
djOrderTour.setReimbursementApplicant(djOrderTourDto.getReimbursementApplicant());
djOrderTour.setBusinessTraveller(djOrderTourDto.getBusinessTraveller());
djOrderTour.setBeOnABusinessTripCause(djOrderTourDto.getBeOnABusinessTripCause());
djOrderTour.setReimbuursementCountPrice(djOrderTourDto.getReimbuursementCountPrice());
djOrderTour.setAdvanceMoney(djOrderTourDto.getAdvanceMoney());
djOrderTour.setReplenishMoney(djOrderTourDto.getReplenishMoney());
djOrderTour.setReturnMoney(djOrderTourDto.getReturnMoney());
djOrderTour.setLeadSignature(djOrderTourDto.getLeadSignature());
djOrderTour.setAccountingFunctionaryUser(djOrderTourDto.getAccountingFunctionaryUser());
djOrderTour.setDivisionFunctionaryUser(djOrderTourDto.getDivisionFunctionaryUser());
Integer tourId = iDjOrderTourService.saveAndGetId(djOrderTour);

    //查询发票ID数组
    ArrayList<Integer> ids = new ArrayList<>();

// 发票对象
DjInvoice djInvoice = new DjInvoice();
// 遍历前端传递过来的发票数组
for (DjInvoice invoice : djOrderTourDto.getInvoices()) {
djInvoice.setInvoiceCode(invoice.getInvoiceCode());
djInvoice.setInvoiceName(invoice.getInvoiceName());
djInvoice.setInvoiceNumber(invoice.getInvoiceNumber());
// 返回日期数组
log.info(“当前的传递过来的日期数据:{}”,invoice.getInvoiceTime());
djInvoice.setInvoiceTime(invoice.getInvoiceTime());
djInvoice.setFilePath(invoice.getFilePath());
int invoiceId = iDjInvoiceService.saveAndGetId(djInvoice);
ids.add(invoiceId);
}

// 遍历发票id数组,将发票id和差旅费报销单id绑定
DjInvoiceTour djInvoiceTour = new DjInvoiceTour();
for (Integer id : ids) {
djInvoiceTour.setTid(tourId);
djInvoiceTour.setIid(id);
iDjInvoiceTourService.save(djInvoiceTour);
}
//
for (MultipartFile file : files) {
//
String originalFilename = file.getOriginalFilename();
//
String suffixName = originalFilename.substring(originalFilename.lastIndexOf(“.”));

            String newFileName = UUID.randomUUID() + suffixName;

            File dest = new File(filePath + newFileName);

            if ( !dest.getParentFile().exists()) {
                dest.getParentFile().mkdirs();
                log.warn("文件不存在,新建路径");
            }

            try {
                file.transferTo(dest);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }



    return R.success("发票保存成功");
}
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值