注意:此文章展示功能基本实现,但有bug,基本逻辑对,细节需要自己进行一下处理(用于学习理解这个过程即可)
文件上传组件 - el-upload
<el-upload
class="upload-demo"
drag
action="uploadUrl"
:headers="header"
multiple:true
accept=".xls,.xlsx"
:before-upload="beforeUpload"
:on-success="handleUploadSuccess"
:http-request="customUploadRequest"
:on-progress="handleUploadProgress"
>
<el-row :span="24">
<el-col :span="6">
<el-icon style="margin-top: 25px" class="el-icon--upload"><upload-filled /></el-icon>
</el-col>
<el-col :span="18">
<p style="font-weight: 700;font-size: 20px">上传填好的数据表</p>
<div class="el-upload__text">
将文件拖到此处或者点击<em>上传文件</em>
<div class="el-upload__tip">
文件后缀名必须是 xls 或 xlsx,文件大小不超过 500KB
</div>
</div>
</el-col>
</el-row>
</el-upload>
- 功能与属性:
drag
:支持文件拖拽上传,提供便捷操作。action
:指定后端上传接口地址,由uploadUrl
计算属性确定。headers
:设置请求头,确保后端正确解析文件格式。multiple:true
:允许批量上传多个 Excel 文件。accept
:限制只能选择.xls
或.xlsx
文件,保证文件类型正确。
(二)文件上传前验证 - beforeUpload
const beforeUpload = (file) => {
const isLxls = file.name.endsWith('.xls') || file.name.endsWith('.xlsx');
const isSizeValid = file.size / 1024 / 1024 < 0.5; // 文件大小转换为 MB 并与 500KB(0.5MB)比较
if (!isLxls) {
proxy.$message.error('请上传后缀名为.xls或.xlsx的文件,当前文件格式不符合要求');
return false;
}
if (!isSizeValid) {
proxy.$message.error('文件大小超过了 500KB 限制,请重新选择文件,当前文件大小为:' + (file.size / 1024 / 1024).toFixed(2) +'MB');
return false;
}
return true;
};
- 验证逻辑:
- 检查文件后缀名,不符合则提示错误并阻止上传。
- 验证文件大小,超限制则报错并阻止上传,确保上传文件符合要求。
(三)文件上传成功回调 - handleUploadSuccess
const handleUploadSuccess = (response, file, fileList) => {
proxy.$message.success('文件上传成功');
// 将上传成功的文件信息添加到 uploadedFiles 数组中,这里简单记录文件名,可根据需求扩展记录更多信息
uploadedFiles.value.push({ name: file.name });
};
- 处理流程:
- 显示上传成功提示。
- 将文件名记录到
uploadedFiles
数组,用于后续操作或展示。
(四)自定义上传请求 - customUploadRequest
const customUploadRequest = (option) => {
const formData = new FormData();
formData.append('file', option.file);
axios({
method: 'post',
url: uploadUrl.value,
data: formData,
headers:header,
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
uploadProgress.value = percent;
}
}
}).then(response => {
option.onSuccess(response.data, option.file, option.fileList);
}).catch(error => {
option.onError(error);
});
};
- 请求定制:
- 构建
FormData
封装文件,按POST
方式发送请求。 - 监听上传进度,实时更新
uploadProgress
,展示上传状态。 - 请求成功或失败时,分别调用对应回调函数处理结果。
- 构建
(五)上传进度更新 - handleUploadProgress
const handleUploadProgress = (event, file, fileList) => {
if (event.percent) {
uploadProgress.value = Math.round(event.percent);
}
};
- 进度显示:根据上传事件中的进度信息,更新
uploadProgress
值,实时显示上传进度条。
(六)数据添加情况检查 - checkUploadedData
与 handleNextStep
const checkUploadedData = () => {
return axios({
method: 'post',
url: 'http://localhost:8083/system/upload/checkUploadedData',
headers: {"Content-Type": "application/json"},
// 明确设置响应类型为 JSON,确保正确解析后端返回的 JSON 数据
responseType: 'json'
}).then(response => {
return response.data; // 返回解析后的 JSON 数据主体部分,以便后续判断使用
}).catch(error => {
proxy.$message.error('请求确认数据添加情况时出现错误:' + error.message);
return null;
});
};
const handleNextStep = () => {
checkUploadedData().then(response => {
console.log("response为", response);
if (typeof response === 'object' && response!== null && response.code === 200) {
proxy.$message.success('数据已成功添加到数据库');
} else {
proxy.$message.error('数据添加到数据库出现问题,请检查');
}
}).catch(error => {
proxy.$message.error('检查数据添加情况时出现错误:' + error.message);
});
};
- 数据检查逻辑:
checkUploadedData
:向后端发送请求检查上传数据是否成功添加到数据库,设置请求头和响应类型,处理成功与失败情况。handleNextStep
:调用checkUploadedData
,根据返回结果判断并向用户提示数据添加状态,方便用户知晓操作结果。
后端接口
文件上传接口 - ExcelUploadController
(一)文件上传接口 - ExcelUploadController
中的 uploadExcel
@PostMapping("/excel")
public AjaxResult uploadExcel(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return AjaxResult.error("上传文件不能为空");
}
String fileName = file.getOriginalFilename();
System.out.println("================="+fileName);
if (fileName == null || (!fileName.endsWith(".xls") &&!fileName.endsWith(".xlsx"))) {
return AjaxResult.error("请上传后缀名为.xls或.xlsx的文件");
}
try {
Workbook workbook;
if (fileName.endsWith(".xls")) {
workbook = new HSSFWorkbook(file.getInputStream());
} else {
workbook = new XSSFWorkbook(file.getInputStream());
}
Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
List<MeterReading> meterReadingList = readSheetToMeterReadings(sheet); // 读取工作表数据转换为 MeterReading 实体列表
int insertedCount = meterReadingService.insertBatch(meterReadingList); // 批量插入数据到数据库并获取插入成功的记录数
workbook.close();
if (insertedCount > 0) {
return AjaxResult.success("文件上传成功,共有 " + insertedCount + " 条数据");
} else {
return AjaxResult.error("文件上传成功,但数据添加到数据库时出现问题,没有数据成功插入,请检查");
}
} catch (IOException e) {
e.printStackTrace();
return AjaxResult.error("文件上传失败,请稍后再试");
}
}
- 文件处理流程:
- 检查文件是否为空及格式是否正确,不符合则返回错误信息。
- 根据文件后缀创建对应
Workbook
对象,读取第一个工作表数据。 - 将工作表数据转换为
MeterReading
实体列表,批量插入数据库并获取插入记录数。 - 根据插入结果返回相应提示信息,处理过程中出现异常则打印堆栈并返回错误信息。
(二)工作表数据读取 - readSheetToMeterReadings
private List<MeterReading> readSheetToMeterReadings(Sheet sheet) {
List<MeterReading> meterReadingList = new ArrayList<>();
for (Row row : sheet) {
if (row.getRowNum() == 0) { // 跳过表头行(假设第一行是表头)
continue;
}
MeterReading meterReading = new MeterReading();
meterReading.setCode(getCellValue(row.getCell(0), String.class));
meterReading.setFeeType(getCellValue(row.getCell(1), String.class));
meterReading.setPreviousReading(getCellValue(row.getCell(2), BigDecimal.class));
meterReading.setPreviousReadingCount(getCellValue(row.getCell(3), BigDecimal.class));
meterReading.setPreviousReadingTime(getCellValue(row.getCell(4), Date.class));
meterReading.setCurrentReading(getCellValue(row.getCell(5), BigDecimal.class));
meterReading.setCurrentReadingTime(getCellValue(row.getCell(6), Date.class));
meterReading.setMultiple(getCellValue(row.getCell(7), BigDecimal.class));
meterReading.setLoss(getCellValue(row.getCell(8), BigDecimal.class));
meterReadingList.add(meterReading);
}
return meterReadingList;
}
- 数据转换逻辑:遍历工作表行,跳过表头,将每行数据转换为
MeterReading
实体对象并添加到列表,最后返回列表供批量插入使用。
(三)单元格数据获取 - getCellValue
private <T> T getCellValue(Cell cell, Class<T> targetType) {
if (cell == null) {
return null;
}
if (targetType == String.class) {
return (T) cell.getStringCellValue();
} else if (targetType == BigDecimal.class) {
return (T) new BigDecimal(cell.getNumericCellValue());
} else if (targetType == Date.class) {
return (T) cell.getDateCellValue();
}
return null;
}
- 数据类型转换:根据单元格数据类型和目标类型,将单元格值转换为相应的 Java 数据类型,确保数据准确性和一致性。
此文件完整代码
package com.ruoyi.web.controller.system;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.MeterReading;
import com.ruoyi.system.service.IMeterReadingService;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/system/upload")
public class ExcelUploadController {
@Autowired
private IMeterReadingService meterReadingService;
@PostMapping("/excel")
public AjaxResult uploadExcel(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return AjaxResult.error("上传文件不能为空");
}
String fileName = file.getOriginalFilename();
System.out.println("================="+fileName);
if (fileName == null || (!fileName.endsWith(".xls") &&!fileName.endsWith(".xlsx"))) {
return AjaxResult.error("请上传后缀名为.xls或.xlsx的文件");
}
try {
Workbook workbook;
if (fileName.endsWith(".xls")) {
workbook = new HSSFWorkbook(file.getInputStream());
} else {
workbook = new XSSFWorkbook(file.getInputStream());
}
Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
List<MeterReading> meterReadingList = readSheetToMeterReadings(sheet); // 读取工作表数据转换为 MeterReading 实体列表
int insertedCount = meterReadingService.insertBatch(meterReadingList); // 批量插入数据到数据库并获取插入成功的记录数
workbook.close();
if (insertedCount > 0) {
return AjaxResult.success("文件上传成功,共有 " + insertedCount + " 条数据");
} else {
return AjaxResult.error("文件上传成功,但数据添加到数据库时出现问题,没有数据成功插入,请检查");
}
} catch (IOException e) {
e.printStackTrace();
return AjaxResult.error("文件上传失败,请稍后再试");
}
}
private List<MeterReading> readSheetToMeterReadings(Sheet sheet) {
List<MeterReading> meterReadingList = new ArrayList<>();
for (Row row : sheet) {
if (row.getRowNum() == 0) { // 跳过表头行(假设第一行是表头)
continue;
}
MeterReading meterReading = new MeterReading();
meterReading.setCode(getCellValue(row.getCell(0), String.class));
meterReading.setFeeType(getCellValue(row.getCell(1), String.class));
meterReading.setPreviousReading(getCellValue(row.getCell(2), BigDecimal.class));
meterReading.setPreviousReadingCount(getCellValue(row.getCell(3), BigDecimal.class));
meterReading.setPreviousReadingTime(getCellValue(row.getCell(4), Date.class));
meterReading.setCurrentReading(getCellValue(row.getCell(5), BigDecimal.class));
meterReading.setCurrentReadingTime(getCellValue(row.getCell(6), Date.class));
meterReading.setMultiple(getCellValue(row.getCell(7), BigDecimal.class));
meterReading.setLoss(getCellValue(row.getCell(8), BigDecimal.class));
meterReadingList.add(meterReading);
}
return meterReadingList;
}
private <T> T getCellValue(Cell cell, Class<T> targetType) {
if (cell == null) {
return null;
}
if (targetType == String.class) {
return (T) cell.getStringCellValue();
} else if (targetType == BigDecimal.class) {
return (T) new BigDecimal(cell.getNumericCellValue());
} else if (targetType == Date.class) {
return (T) cell.getDateCellValue();
}
return null;
}
@PostMapping("/checkUploadedData")
public AjaxResult checkUploadedData() {
return AjaxResult.success("数据已成功添加到数据库");
}
}
实体类
package com.ruoyi.system.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* 抄表数据对象 meter_reading
*
* @author ruoyi
* @date 2024-12-11
*/
@TableName("meter_reading")
public class MeterReading extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 序号 */
private Long id;
/** 编号 */
@Excel(name = "编号")
private String code;
/** 费用类型 */
@Excel(name = "费用类型")
private String feeType;
/** 上期读数 */
@Excel(name = "上期读数")
private BigDecimal previousReading;
/** 上期读表数 */
@Excel(name = "上期读表数")
private BigDecimal previousReadingCount;
/** 上期读表时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "上期读表时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date previousReadingTime;
/** 本期读数 */
@Excel(name = "本期读数")
private BigDecimal currentReading;
/** 本期读表时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "本期读表时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date currentReadingTime;
/** 倍率 */
@Excel(name = "倍率")
private BigDecimal multiple;
/** 损耗 */
@Excel(name = "损耗")
private BigDecimal loss;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setCode(String code)
{
this.code = code;
}
public String getCode()
{
return code;
}
public void setFeeType(String feeType)
{
this.feeType = feeType;
}
public String getFeeType()
{
return feeType;
}
public void setPreviousReading(BigDecimal previousReading)
{
this.previousReading = previousReading;
}
public BigDecimal getPreviousReading()
{
return previousReading;
}
public void setPreviousReadingCount(BigDecimal previousReadingCount)
{
this.previousReadingCount = previousReadingCount;
}
public BigDecimal getPreviousReadingCount()
{
return previousReadingCount;
}
public void setPreviousReadingTime(Date previousReadingTime)
{
this.previousReadingTime = previousReadingTime;
}
public Date getPreviousReadingTime()
{
return previousReadingTime;
}
public void setCurrentReading(BigDecimal currentReading)
{
this.currentReading = currentReading;
}
public BigDecimal getCurrentReading()
{
return currentReading;
}
public void setCurrentReadingTime(Date currentReadingTime)
{
this.currentReadingTime = currentReadingTime;
}
public Date getCurrentReadingTime()
{
return currentReadingTime;
}
public void setMultiple(BigDecimal multiple)
{
this.multiple = multiple;
}
public BigDecimal getMultiple()
{
return multiple;
}
public void setLoss(BigDecimal loss)
{
this.loss = loss;
}
public BigDecimal getLoss()
{
return loss;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("code", getCode())
.append("feeType", getFeeType())
.append("previousReading", getPreviousReading())
.append("previousReadingCount", getPreviousReadingCount())
.append("previousReadingTime", getPreviousReadingTime())
.append("currentReading", getCurrentReading())
.append("currentReadingTime", getCurrentReadingTime())
.append("multiple", getMultiple())
.append("loss", getLoss())
.toString();
}
}
MeterReadingMapper添加批量插入数据的方法
int insertBatch(@Param("meterReadings")List<MeterReading> meterReadings);
MeterReadingServiceImpl
/**
* 批量新增抄表数据
*
* @param meterReadings 要批量插入的抄表数据列表
* @return 实际插入成功的记录数
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertBatch(List<MeterReading> meterReadings) {
if (meterReadings == null || meterReadings.isEmpty()) {
return 0;
}
return meterReadingMapper.insertBatch(meterReadings);
}
MeterReadingMapper.xml(这里我是在xml添加这个批量新增sql语句)
<!-- 批量插入抄表数据的SQL映射方法,使用foreach标签遍历传入的meterReadings集合,构建批量插入的VALUES子句 -->
<insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">
INSERT INTO meter_reading (code, fee_type, previous_reading, previous_reading_count, previous_reading_time, current_reading, current_reading_time, multiple, loss) VALUES
<foreach collection="meterReadings" item="item" index="index" separator=",">
(#{item.code}, #{item.feeType}, #{item.previousReading}, #{item.previousReadingCount}, #{item.previousReadingTime}, #{item.currentReading}, #{item.currentReadingTime}, #{item.multiple}, #{item.loss})
</foreach>
</insert>
如果你想和我一样的话,注意放置位置哈
不要照搬,仅供参考,我懒,bug不想改了!!!
通过前端与后端代码的紧密配合,实现了完整的 Excel 文件上传功能,包括文件选择、验证、上传、数据解析、入库及结果反馈等环节。此功能在众多数据处理场景中广泛应用,有助于提升数据录入效率和系统实用性。希望本文的解析能助您深入理解其实现原理,为相关开发工作提供参考。