Vue+JAVA POI 实现Excel导入导出规则怪谈
Vue+JAVA POI 实现Excel导入导出
·
怪谈场景:
前端导入一个Excel表格到后端,后端读取Excel表格内容并处理,将处理过的数据生成一个新的EXcel,并在前端浏览器下载。
导入的文件样式:
导出的文件样式:
- 该文章较长,您可以通过目录跳转到想要查看的位置,若您是全栈开发,并且第一次做该操作,建议您阅读全文。
目录
前端HTML代码:
<template>
<div>
<el-row>
<el-col :span="100">
<el-upload
ref="uploadExcel"
:action="uploadPath"
:data ="uploadData"
:headers = "headers"
:limit="limitNum"
:auto-upload="false"
accept="*"
:before-upload="beforeUploadFile"
:on-change="fileChange"
:on-exceed="exceedFile"
:on-success="handleSuccess"
:on-error="handleError"
:file-list="fileList"
>
<el-button size="middle" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">上传文件不超过10M</div>
</el-upload>
</el-col>
<el-col :span="100">
<el-button type="primary" size="middle"
@click="downLoad">导出处理结果</el-button>
</el-col>
</el-row>
</div>
</template>
el-upload 标签参数注释:
action 必选参数,上传的地址 string — — headers 设置上传的请求头部 object — — accept 接受上传的文件类型(thumbnail-mode 模式下此参数无效) string — — on-success 文件上传成功时的钩子 function(response, file, fileList) — — on-error 文件上传失败时的钩子 function(err, file, fileList) — — on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 function(file, fileList) — — before-upload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 function(file) — — auto-upload 是否在选取文件后立即进行上传 boolean — true file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] array — [] limit 最大允许上传个数 number — — on-exceed 文件超出个数限制时的钩子 function(files, fileList) — - 以上是本文章中用到的参数,若您需系统学习,可以到element-UI官网查看更详细的讲解。
前端JS代码:
export default {
name: 'excelDownLoad',
components: { },
data() {
return {
limitNum: 1, // 文件个数
fileList: [], // 文件列表
uploadData: { // 上传时附带参数
type: 1
},
files: [],
headers: {
Authorization: getToken()
},
uploadPath: ''
}
},
computed: {
},
created() {
},
methods: {
beforeUploadFile(file) {
const extension = file.name.substring(file.name.lastIndexOf('.') + 1)
const size = file.size / 1024 / 1024
if (size > 10) {
this.$notify.warning({
title: '警告',
message: `文件大小不得超过10M`
})
}
},
async submitUpload() {
const formData = new FormData()
formData.append('excelList', this.files.raw)
await downLoad(formData).then(
data => {
var FileSaver = require('file-saver');
var blob = new Blob([data],{type: "application/vnd.ms-excel"});
//导出后,Excel名为:新的表格2023-02-24.xsl ; 名字可以自定义
FileSaver.saveAs(blob,"新的表格"+fecha.format(new Date(), 'yyyy-MM-dd')+".xsl");
},
error => {
(error);
this.$("下载异常,请稍后再试");
});
},
// 文件修改时的钩子
fileChange(file, fileList) {
this.files = file
},
// 文件超出个数限制时的钩子
exceedFile(files, fileList) {
this.$notify.warning({
title: '警告',
message: `只能选择 ${
this.limitNum
} 个文件,当前共选择了 ${files.length + fileList.length} 个`
})
},
// 文件上传成功时的钩子
handleSuccess(res, file, fileList) {
addItem({
fileName: res.data.fileName,
filePath: res.data.newFilePath,
idTransportBatch: this.dicFiles.idTransportBatch
}).then(() => {
this.$message({
message: res.data.msg,
type: 'success'
})
this.loading.close()
this.$refs.uploadExcel.clearFiles()// 清除上次上传记录
this.showItem({ row: this.dicFiles })
})
},
// 文件上传失败时的钩子
handleError(err, file, fileList) {
this.$message.error(err.msg)
}
}
}
代码规则怪谈:
- 前端掉用接口的时候,您必须加上: responseType :'blob',若您不加可能会导致以下后果:
- 返回值得类型不对;
- 导出文件乱码;
- 导出文件打不开;
- 前端掉用接口的时候,您可以加上: Authorization: token,若您不加可能会导致接口调用失败,也可能不会。
export function checkStockList(data) {
return axios.post(
'/api/downLoad',
data,
{
responseType :'blob',
headers: {
Authorization: token
}
}
- var blob = new Blob([data],{type: "application/vnd.ms-excel"}); 您需要把这行代码的type设置为和Java代码中相同的类型。否则可能会出现不可预估的问题。
- 若您做了以上操作仍然出现文件打开失败的情况,请您查看您的前端项目是否启用了mockjs,如果您启用了,请您关闭后接着尝试导出。
后端Java代码:
/**controller层**/
@PostMapping(value = "/downLoad")
public void downLoad(@RequestBody MultipartFile importExcel , HttpServletRequest request, HttpServletResponse response) {
try {
downLoadService.downLoad(importExcel ,request,response);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**service层**/
@Override
public void downLoad(MultipartFile importExcel , HttpServletRequest request, HttpServletResponse response) {
List<Map<Integer, List<String>>> totalList = new ArrayList<>();
Map<String, Object> checkResultMap = new HashMap<>();
List<List> exceldb = new ArrayList<>();
List<List> checkChineseList= new ArrayList<>();
List<List> checkMathList= new ArrayList<>();
List<List> checkEnglishList= new ArrayList<>();
int round= 0;
/**导入并读取文件开始**/
//创建工作簿
Workbook workbook = null;
//判断接收的文件是否合规
if (importExcel .isEmpty()) {
throw new RuntimeException("文件不能为空");
} else {
String name = importExcel .getOriginalFilename();
if (!(name.endsWith(".xls") || name.endsWith(".xlsx"))) {
throw new RuntimeException("该文件不是Excel");
}
try {
workbook = WorkbookFactory.create(importExcel.getInputStream());
//获取sheet数量
int numberOfSheets = workbook.getNumberOfSheets();
for (int i = 0; i < numberOfSheets; i++) {
//存储单页sheet信息map
Map<Integer, List<String>> excelMap = new HashMap<>();
//获取当前工作表
Sheet sheet = workbook.getSheetAt(i);
//获取表记录
//获取行数
int numOfRows = sheet.getLastRowNum() + 1;
for (int rowNum = 0; rowNum < numOfRows; rowNum++) {
List<String> infoList = new ArrayList<>();
Row rowData = sheet.getRow(rowNum);
if (rowData != null) {
//读取列
int cellCount = rowData.getLastCellNum();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
Cell cell = rowData.getCell(cellNum);
//匹配数据类型
String cellValue = "";
if (cell != null) {
if (cell.getCellTypeEnum() == CellType.STRING) {
cellValue = cell.getStringCellValue();
}
if (cell.getCellTypeEnum() == CellType.NUMERIC) {
//数值型
//poi读取整数会自动转化成小数,这里对整数进行还原,小数不做处理
long longValue = Math.round(cell.getNumericCellValue());
if (Double.parseDouble(longValue + ".0") == cell.getNumericCellValue()) {
cellValue = String.valueOf(longValue);
} else {
cellValue = String.valueOf(cell.getNumericCellValue());
}
} else if (cell.getCellTypeEnum() == CellType.FORMULA) {
//公式型
//公式计算的值不会转化成小数,这里数值获取失败后会获取字符
try {
cellValue = String.valueOf(cell.getNumericCellValue());
} catch (Exception e) {
cellValue = cell.getStringCellValue();
}
}
infoList.add(cellValue);
} else {
infoList.add(null);
}
excelMap.put(rowNum, infoList);
exceldb.add(infoList);
}
}
}
totalList.add(excelMap);
}
/**导入并读取文件结束---excelMap和exceldb都是读取出来的全部数据,区别在于类型不一样,大家可以打印出来自行查看变量的结构**/
/**业务处理开始**/
/**
这个部分在实际操作中我是对导出的数据做了一些业务处理,最终我把所有处理的数据放在了checkResultMap中,这里不多赘述,我只写一个例子。
**/
checkChineseList=['李明','李雷','韩梅梅'];
checkMathList=['高小轩','高小强','高小盛','高小兰'];
checkEnglishList=['韩梅梅','王小楠','张小萌','花小卷']
checkResultMap.put("语文", checkChineseList);
checkResultMap.put("数学", checkMathList);
checkResultMap.put("英语", checkEnglishList);
/**业务处理结束**/
/**导出Excel开始**/
//创建工作簿
HSSFWorkbook workbookExport = new HSSFWorkbook();
//创建工作表
HSSFSheet sheet = workbookExport.createSheet("Sheet1");
//这个地方是对拼好的checkResultList进行从大到小的排序,若不排序生成Excel创建行时会发生逻辑错误
LinkedHashMap<String, Object> sortedKeywords = checkResultMap.entrySet().stream().sorted(Comparator.comparing(entry -> entry.getValue().toString().length(), Comparator.reverseOrder())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
//生成Excel这里用到了迭代器处理数据
Iterator it1 = sortedKeywords.entrySet().iterator();
HSSFRow row = sheet.createRow(0);
while (it1.hasNext()) {
Map.Entry entry1 = (Map.Entry) it1.next();
//取表头值,其实就是map的key
Object key1 = entry1.getKey();
//取每一列的数据
Object value1 = entry1.getValue();
//创建单元格
HSSFCell cell = row.createCell(round);
//给表头赋值
cell.setCellValue(key1.toString());
//设置列宽
sheet.setColumnWidth(round, 50 * 256);
//设置单元格样式,这里只给表头设置样式,不需要可以去掉
HSSFCellStyle headstyle = workbookExport.createCellStyle();
headstyle.setFillPattern(HSSFCellStyle.SPARSE_DOTS);
headstyle.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.index);
headstyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
cell.setCellStyle(headstyle);
List<Map> mapList = (List<Map>) value1;
for (int m = 0; m < mapList.size(); m++) {
//这里判断了round ,如果是第一次循环则createRow,如果不是则getRow,还记得刚才的排序吗?就是为了按照最多数据列创建行。这样写就不会因为重复创建行而导致Excel数据不完整了。
if (round == 0) {
HSSFRow rows = sheet.createRow(m + 1);
HSSFCell cells = rows.createCell(a);
cells.setCellValue((mapList.get(m)) + "");
} else {
HSSFRow rows = sheet.getRow(m + 1);
HSSFCell cells = rows.createCell(a);
cells.setCellValue((mapList.get(m)) + "");
}
}
round++;
}
//创建输出流
OutputStream outputs = response.getOutputStream();
//下面是返回给前端的响应头,这样写就可以
response.reset();
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Pragma", "no-cache");//关闭缓存
response.setHeader("Cache-Control", "no-cache");//关闭缓存
response.setDateHeader("Expires", 0);//为了让浏览器不要缓存页面,也可以利用Expires实体报关域,设置为0
//生成文件
workbookExport.write(outputs);
//清空缓冲区的数据流
outputs.flush();
//关闭流
outputs.close();
/**导出Excel结束**/
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (workbook != null) {
workbook.close();
}
if (workbookExport!= null) {
workbookExport.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
代码规则怪谈:
本文用到的是 HSSFWorkbook , HSSFWorkbook是操作Excel2003以前(包括2003)的版本,扩展名是.xls;
如果不符合您的需求,您可以尝试以下方法:
XSSFWorkbook:是操作Excel2007后的版本,扩展名是.xlsx;
SXSSFWorkbook:是操作Excel2007后的版本,扩展名是.xlsx;
具体用法您可以自行百度。
如果不出问题,前端请求后就可以自动导出一个您想要的Excel了 !!!
如果没有解决您的问题,小尼表示非常抱歉,请您点击左上角“←”标识立刻退出,您可以尝试进入别的房间寻找答案。
若您执意留下,可能会给您带来以下后果:
怀疑自我;
主动放弃;
精神混乱;
情绪失控;
若您出现以上反应,请马上关闭浏览器,进行深呼吸,调整情绪后您仍然可以尝试进入其他房间寻找答案,请记住不要再回到本博客。
小尼祝您学习愉快,永远200!
更多推荐
已为社区贡献1条内容
所有评论(0)