系统环境:
Java JDK:1.8.0_202
Node.js:v12.2.0
Npm:6.9.0
Java后端实现
Controller
/**
* xxxx-导出
* @param response 返回信息体
* @param files 上传的图片文件
* @param param1 参数1
* @param param2 参数2
*/
@PostMapping("/exportXX")
@ApiOperationSupport(order = 13)
@ApiOperation(value = "导出Excel", notes = "导出Excel")
@ApiLog("XXX导出")
public void exportXX(HttpServletResponse response, @RequestParam("files") MultipartFile[] files, @RequestParam("param1") String param1,@RequestParam(value = "param2",defaultValue = "1") String param2) {
tableExportService.exportXX(response, files, param1, param2);
}
Service
/**
* XXX-导出
*/
void exportXX(HttpServletResponse response, MultipartFile[] files, String param1,String param2);
Impl
/**
* xxxx-导出
* @param response 返回信息体
* @param files 上传的图片文件
* @param param1 参数1
* @param param2 参数2
*/
@Override
public void exportXX(HttpServletResponse response,MultipartFile[] files, String param1, String param2) {
try {
// 返回信息体重置
response.reset();
// 设置类型
response.setContentType("application/force-download");
// 赋值压缩包名称及头部信息
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String fileName = "attachment;filename=cryExcel" + format.format(new Date()) + ".zip";
response.setHeader("Content-Disposition", fileName);
// 发送二进制数据到客户端的输出流
ServletOutputStream servletOutputStream = response.getOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(servletOutputStream);
// 图片添加到ZIP
addPicToZip(files,zipOut);
// 表头
List<List<String>> headList = new ArrayList<>();
// 固定列
headList.add(Arrays.asList("列1"));
headList.add(Arrays.asList("列名2"));
headList.add(Arrays.asList("这是列名3"));
// 导出数据
List<List<String>> dataList = new ArrayList<>();
// 自行获取需要导出为excel的数据信息
List<Map<String, Object>> list = ……;
// 列表不为空时按照列1进行排序
if(Func.isNotEmpty(list)){
list.sort(Comparator.comparing(map -> (String) map.get("列1的键")));
}
// 转换数据格式为二维数组 方便存入excel
for (Map<String, Object> item : list) {
dataList.add(Arrays.asList(
String.valueOf(item.getOrDefault("列1的值", "")),
String.valueOf(item.getOrDefault("列2的值", "")),
String.valueOf(item.getOrDefault("列3的值", "")) + "%"
));
}
// 导出excel 并合并第一列的相同内容
int[] mergeColumeIndex = {0};
int mergeRowIndex = 1;
String excelName = "导出excel.xlsx";
File excelfile = new File(excelName);
if (!excelfile.exists()) {
excelfile.createNewFile();
}
// 将excel写入压缩包
EasyExcel.write(excelName)
.head(headList)
.registerWriteHandler(new ExcelFillCellLineMergeHandler(mergeRowIndex, mergeColumeIndex))
.sheet("导出excel")
.doWrite(dataList);
// 创建 ZipEntry
ZipEntry entry = new ZipEntry(excelName);
zipOut.putNextEntry(entry);
// 读取文件并写入 ZipOutputStream
try (FileInputStream fis = new FileInputStream(excelfile)) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) > 0) {
zipOut.write(buffer, 0, length);
}
}
// 关闭当前的 ZipEntry
zipOut.closeEntry();
// 关流
zipOut.close();
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 将Base64编码的图片文件添加到ZIP输出流中
* @param files 包含Base64编码的图片的MultipartFile数组
* @param zipOut 目标ZIP输出流
*/
private void addPicToZip(MultipartFile[] files, ZipOutputStream zipOut) {
try {
// 检查文件数组是否为空
if (files != null) {
// 遍历所有文件
for (MultipartFile file : files) {
// 从MultipartFile中获取输入流并转换为字符串
String imageData = StreamUtils.copyToString(file.getInputStream(), StandardCharsets.UTF_8);
// 移除Base64数据前缀(如果存在)
imageData = imageData.replace("data:image/png;base64,", "");
// 解码Base64字符串为字节数组
byte[] imageBytes = Base64.getDecoder().decode(imageData);
// 创建ZIP条目,使用原始文件名并添加.png扩展名
ZipEntry zipEntry = new ZipEntry(file.getOriginalFilename() + ".png");
// 将条目添加到ZIP输出流
zipOut.putNextEntry(zipEntry);
// 写入图片字节数据到ZIP条目
zipOut.write(imageBytes, 0, imageBytes.length);
}
}
} catch (IOException e) {
// 打印异常堆栈信息
e.printStackTrace();
}
}
- 注:对于此处我个人觉得直接让前端上传file二进制文件更好,后端直接获取file的字节码,然后弄进压缩包,此处可以根据业务需求自行调整~
Vue 前端实现
Api
/**
* @description 测试导出
* */
export const exportXX = (data) => {
return request({
headers: {
"Content-Type": "multipart/form-data"// 指定请求体为多部分表单数据(用于文件上传)
},
method: 'post',
responseType: 'blob',// 指定响应类型为二进制大对象(用于接收文件流)
data,
url: 'http://localhost:8990/dev-api/tableExport/exportXX',
})
}
Vue
此处可以自行选择upload组件上传的文件或者echarts图形截图等文件进行上传
// 上传图片信息
async uploadImages() {
try {
// 创建FormData
const formData = new FormData();
if (this.selectedFiles.length !== 0) {
// 处理每个文件
for (const file of this.selectedFiles) {
// 转换为完整的Base64 DataURL(包含前缀)
const base64DataUrl = await this.convertToBase64(file);
// 创建一个Blob对象,内容为Base64字符串(作为文本)
const blob = new Blob([base64DataUrl], { type: "text/plain" });
// 使用原始文件名创建File对象
const fileToUpload = new File([blob], file.name, {
type: "text/plain",
});
// 添加到FormData
formData.append("files", fileToUpload);
}
}
formData.append("param1", "111");
formData.append("param2", "222");
// 调用导出接口
await exportXX(formData).then((res) => {
console.log(formData);
console.log(res);
if(res){
const elink = document.createElement('a');
elink.download = '文件名称.zip';
elink.style.display = 'none';
const blob = new Blob([res], { type: 'application/x-msdownload' });
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
document.body.removeChild(elink);
}else {
this.$message.error('导出异常请联系管理员');
}
});
console.log("上传成功");
} catch (error) {
console.error("上传错误:", error);
}
},
//转换为base64字符
convertToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result); // 返回完整的DataURL(含前缀)
reader.onerror = reject;
reader.readAsDataURL(file);
});
},