前言

在B/S架构中,服务端导出是一种高效的方式。它将导出的逻辑放在服务端,前端仅需发起请求即可。通过在服务端完成导出后,前端再下载文件完成整个导出过程。服务端导出具有许多优点,如数据安全、适用于大规模数据场景以及不受前端性能影响等。

本文将使用前端框架React和服务端框架Spring Boot搭建一个演示的Demo,展示如何在服务端导出Excel和PDF文件。当然,对于前端框架,如Vue、Angular等也可以采用类似的原理来实现相同的功能。

在服务端导出过程中,需要依赖额外的组件来处理Excel和PDF文件。对于Excel相关操作,可以选择POI库,而对于PDF文件,可以选择IText库。为了方便起见,本方案选择了GcExcel,它原生支持Excel、PDF、HTML和图片等多种格式的导出功能。这样一来,在实现导出功能的同时,也提供了更多的灵活性和互操作性。

实践

本文将演示如何创建一个简单的表单,其中包括姓名和电子邮箱字段,这些字段将作为导出数据。同时,前端将提供一个下拉选择器和一个导出按钮,通过下拉选择器选择导出的格式,然后点击导出按钮发送请求。等待服务端处理完成后,前端将下载导出的文件。

在服务端,我们需要实现相应的API来处理提交数据的请求和导出请求。我们可以定义一个对象,在内存中保存提交的数据。然后利用GcExcel库构建Excel对象,并将数据导出为不同的格式。

前端 React

1.创建React工程

新建一个文件夹,如ExportSolution,进入文件夹,在资源管理器的地址栏里输入cmd,然后回车,打开命令行窗口。

使用下面的代码创建名为client-app的react app。

npx create-react-app client-app

进入创建的client-app文件夹,使用IDE,比如VisualStudio Code打开它。

2.设置表单部分

更新Src/App.js的代码,创建React app时,脚手架会创建示例代码,需要删除它们。如下图(红色部分删除,绿色部分添加)。

在Src目录下,添加一个名为FormComponent.js的文件,在App.js中添加引用。

在FormComponent.js中添加如下代码。其中定义了三个state, formData和exportType,count用来存储页面上的值。与服务端交互的方法,仅做了定义。

import React, { useEffect, useState } from 'react';

export const FormComponent = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
});
const [exportType, setExportType] = useState('0');
const [count, setCount] = useState(0); useEffect(() => {
fetchCount();
},[]); const fetchCount = async () => {
//TODO
} const formDataHandleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
}; const exportDataHandleChange = (e) => {
setExportType(e.target.value);
}; const handleSubmit = async (e) => {
//TODO
}; const download = async (e) => {
//TODO
} return (
<div class="form-container">
<label>信息提交</label>
<br></br>
<label>已有<span class="submission-count">{count}</span>次提交</label>
<hr></hr>
<form class="form" onSubmit={handleSubmit}>
<label>
姓名:
<input type="text" name="name" value={formData.name} onChange={formDataHandleChange} />
</label>
<br />
<label>
邮箱:
<input type="email" name="email" value={formData.email} onChange={formDataHandleChange} />
</label>
<button type="submit">提交</button>
</form>
<hr />
<div className='export'>
<label>
导出类型:
<select class="export-select" name="exportType" value={exportType} onChange={exportDataHandleChange}>
<option value='0'>Xlsx</option>
<option value='1'>CSV</option>
<option value='2'>PDF</option>
<option value='3'>HTML</option>
<option value='4'>PNG</option>
</select>
</label>
<br />
<button class="export-button" onClick={download}>导出并下载</button>
</div>
</div>
);
}

CSS的代码如下:

.form-container {
margin: 20px;
padding: 20px;
border: 1px solid #ccc;
width: 300px;
font-family: Arial, sans-serif;
min-width: 40vw;
} .submission-count {
font-weight: bold; }
.form{ text-align: left;
} .form label {
display: block;
margin-bottom: 10px;
font-weight: bold;
} .form input[type="text"],
.form input[type="email"] {
width: 100%;
padding: 5px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
} .form button {
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border-radius: 4px;
cursor: pointer;
width: 100%;
} .export{
text-align: left;
} .export-select {
padding: 5px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
width: 10vw;
} .export-button {
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border-radius: 4px;
cursor: pointer;
width: 100%;
} hr {
margin-top: 20px;
margin-bottom: 20px;
border: none;
border-top: 1px solid #ccc;
}

试着运行起来,效果应该如下图:

3.Axios请求及文件下载

前端与服务端交互,一共有三种请求:

  • 页面加载时,获取服务端有多少次数据已经被提交
  • 提交数据,并且获取一共有多少次数据已经被提交
  • 发送导出请求,并根据结果下载文件。

通过npm添加两个依赖,Axios用于发送请求,file-saver用于下载文件。

npm install axios
npm install file-saver

在FormComponent.js中添加引用

import axios from 'axios';
import { saveAs } from 'file-saver';

三个请求方法的代码如下:

    const fetchCount = async () => {
let res = await axios.post("api/getListCount");
if (res !== null) {
setCount(res.data);
}
} const handleSubmit = async (e) => {
e.preventDefault();
let res = await axios.post("api/commitData", {...formData});
if (res !== null) {
setCount(res.data);
}
}; const download = async (e) => {
let headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Headers': 'Content-Disposition'
};
let data = { exportType: exportType };
let res = await axios.post('/api/exportDataList', data, { headers: headers, responseType: 'blob' });
if (res !== null) {
let contentDisposition = res.headers['content-disposition']
let filename = contentDisposition.substring(contentDisposition.indexOf('"') + 1, contentDisposition.length - 1);
saveAs(res.data, filename);
}
}

三个请求都是同步的,使用了await等待返回结果。三个请求,会分别向已定义的api发送请求,其中fetchCount,仅会在页面第一次完成加载时执行。其他两个请求方法会在点击按钮时触发。

4.配置请求转发中间件

因为React的程序会默认使用3000端口号,而Springboot默认使用8080端口。如果在Axios直接向服务端发送请求时(比如:localhost:8080/api/getListCount ),会出现跨域的问题。因此需要添加一个中间件来转发请求,避免前端跨域访问的问题。

在src文件夹下面添加文件,名为setupProxy.js,代码如下:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true,
})
);
};

OK,至此前端代码基本完成,但还暂时不能运行测试,因为服务端代码没有完成。

服务端 Springboot

1.创建Springboot工程

使用IDEA创建一个Springboot工程,如果使用的是社区(community)版本,不能直接创建Springboot项目,那可以先创建一个空项目,idea创建project的过程,就跳过了,这里我们以创建了一个gradle项目为例。

plugins {
id 'org.springframework.boot' version '3.0.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id 'war'
} group = 'org.example'
version = '1.0-SNAPSHOT' repositories {
mavenCentral()
} dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.grapecity.documents:gcexcel:6.2.0'
implementation 'javax.json:javax.json-api:1.1.4'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation('org.springframework.boot:spring-boot-starter-test')
} test {
useJUnitPlatform()
}

在dependencies 中,我们除了依赖springboot之外,还添加了GcExcel的依赖,后面导出时会用到GcExcel,目前的版本是6.2.0。

2.添加SpringBootApplication

完成依赖的添加后,删除原有的main.java,并新创建一个ExportServerApplication.java,然后添加以下代码。

@SpringBootApplication
@RestController
@RequestMapping("/api")
public class ExportServerApplication { public static void main(String[] args) {
SpringApplication.run(ExportServerApplication.class, args);
}
}

3.添加 getListCount 和 commitData API

继续在ExportServerApplication.java中添加一个ArraryList用来临时存储提交的数据,commitData把数据添加进ArraryList中,getListCount从ArraryList中获取数据数量。

    private static ArrayList<CommitParameter> dataList = new ArrayList<>();

    @PostMapping("/commitData")
public int commitData(@RequestBody CommitParameter par) {
dataList.add(par);
return dataList.size();
} @PostMapping("/getListCount")
public int getCount() {
return dataList.size();
}
4.添加导出API

在React app中,我们使用selector允许选择导出的类型,selector提供了,Xlsx, CSV, PDF, HTML, PNG, 5种导出格式。在导出的API中,需要用GcExcel构建Excel文件,把提交的数据填入到Excel的工作簿中。之后,根据前端传递的导出类型来生成文件,最后给前端返回,进行下载。

在GcExcel,可以直接通过workbook.save把工作簿保存为Xlsx, CSV, PDF 以及HTML。但是在导出HTML时,因为会导出为多个文件,因此我们需要对HTML和PNG进行特殊处理。

    @PostMapping("/exportDataList")
public ResponseEntity<FileSystemResource> exportPDF(@RequestBody ExportParameter par) throws IOException {
var workbook = new Workbook();
copyDataToWorkbook(workbook);
String responseFilePath = "";
switch (par.exportType) {
case Html -> {
responseFilePath = exportToHtml(workbook);
}
case Png -> {
responseFilePath = exportToImage(workbook);
}
default -> {
responseFilePath = "download." + par.exportType.toString().toLowerCase();
workbook.save(responseFilePath, Enum.valueOf(SaveFileFormat.class, par.exportType.toString()));
}
} FileSystemResource file = new FileSystemResource(responseFilePath);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\""); return ResponseEntity.ok()
.headers(headers)
.contentLength(file.contentLength())
.body(file);
} private static void copyDataToWorkbook(Workbook workbook) {
Object[][] data = new Object[dataList.size() + 1][2];
data[0][0] = "name";
data[0][1] = "email";
for (int i = 0; i < dataList.size(); i++) {
data[i + 1][0] = dataList.get(i).name;
data[i + 1][1] = dataList.get(i).email;
}
workbook.getActiveSheet().getRange("A1:B" + dataList.size() + 1).setValue((Object) data);
}

对于HTML,可以直接通过FileOutputStream的方式,把HTML输出成为zip。

    private String exportToHtml(Workbook workbook) {
String outPutFileName = "SaveWorkbookToHtml.zip";
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(outPutFileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
} workbook.save(outputStream, SaveFileFormat.Html); try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return outPutFileName;
}

对于PNG类型,GcExcel可以导出多种图片格式,这里通过ImageType.PNG来选择导出为PNG,以文件流的方式导出为图片。

另外,我们需要单独准备model的类,代码如下:

    private String exportToImage(Workbook workbook) {
String outPutFileName = "ExportSheetToImage.png";
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(outPutFileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
} IWorksheet worksheet = workbook.getWorksheets().get(0);
worksheet.toImage(outputStream, ImageType.PNG); try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return outPutFileName;
}

CommitParameter.java:

package org.example;

public class CommitParameter {
public String name;
public String email;
}

ExportParameter.java:

package org.example;

public class ExportParameter {
public ExportType exportType;
}

ExportType.java:

package org.example;

public enum ExportType {
Xlsx,
Csv,
Pdf,
Html,
Png;
}

至此我们就完成了服务端的代码。

最终效果

通过表单添加一些数据,同时导出不同类型的文件。

打开这些文件,看看导出的数据是否正确。

Excel

PDF

CSV

HTML

PNG

写在最后

除了上述的导出功能外,GcExcel还可以实现其他功能,如迷你图数据透视表自定义函数等,欢迎大家访问:https://demo.grapecity.com.cn/documents-api-excel-java/demos/

扩展链接:

Spring Boot框架下实现Excel服务端导入导出

项目实战:在线报价采购系统(React +SpreadJS+Echarts)

Svelte 框架结合 SpreadJS 实现纯前端类 Excel 在线报表设计

Java与React轻松导出Excel/PDF数据的更多相关文章

  1. java中使用poi导出excel表格数据并且可以手动修改导出路径

    在我们开发项目中,很多时候会提出这样的需求:将前端的某某数据以excel表格导出,今天就给大家写一个简单的模板. 这里我们选择使用poi导出excel: 第一步:导入需要的jar包到 lib 文件夹下

  2. Java利用POI导入导出Excel中的数据

         首先谈一下今天发生的一件开心的事,本着一颗android的心我被分配到了PB组,身在曹营心在汉啊!好吧,今天要记录和分享的是Java利用POI导入导出Excel中的数据.下面POI包的下载地 ...

  3. Java中导入、导出Excel

    原文:Java中导入.导出Excel 一.介绍 当前B/S模式已成为应用开发的主流,而在企业办公系统中,常常有客户这样子要求:你要把我们的报表直接用Excel打开(电信系统.银行系统).或者是:我们已 ...

  4. 我是陌生人 Java中导入、导出Excel

    我是陌生人 Java中导入.导出Excel 一.介绍 当前B/S模式已成为应用开发的主流,而在企业办公系统中,常常有客户这样子要求:你要把我们的报表直接用Excel打开(电信系统.银行系统).或者是: ...

  5. java使用户EasyExcel导入导出excel

    使用alibab的EasyExce完成导入导出excel 一.准备工作 1.导包 <!-- poi 相关--> <dependency> <groupId>org. ...

  6. RDLC - 后台代码直接导出Excel/PDF/Word格式

    最近做报表功能,用到了.net的报表组件rdlc. 其中有个功能就是后台代码直接输出Excel/PDF/Word格式的文件,网上看了些资源,做个总结: 参考地址 我直接贴出代码: //自动导出exce ...

  7. java 导出Excel 大数据量,自己经验总结!

    出处: http://lyjilu.iteye.com/ 分析导出实现代码,XLSX支持: /** * 生成<span style="white-space: normal; back ...

  8. java导出excel模板数据

    Java导出excel数据模板,这里直接贴代码开发,流程性的走下去就是步骤: String[] colName=new String[]{"期间","科目代码" ...

  9. java基础篇 -- 导出excel表格数据

    本篇文章基于java把数据库中的数据以Excel的方式导出,欢迎各位大神吐槽: 1.基于maven jar包引入如下: <dependency> <groupId>net.so ...

  10. react+antd 导出excel文件(简单数据&多级表头)

    需求: 在基于react+antd进行开发的页面中,实现导出excel报表的功能 实际场景: 1.简单数据:单层表头+数据 2.复杂数据:多层表头+数据 实现方式: 1.简单数据 简单数据的导出使用了 ...

随机推荐

  1. WPF 已知问题 Popup 吃掉 PreviewMouseDown 事件

    在 WPF 中,使用 Popup 也许会看到 PreviewMouseDown 事件被吃掉 因为 PreviewMouseDown 是 RoutingStrategy.Direct 路由事件,不能在多 ...

  2. WPF 应用启动过程同时启动多个 UI 线程且访问 ContentPresenter 可能让多个 UI 线程互等

    在应用启动过程里,除了主 UI 线程之外,如果还多启动了新的 UI 线程,且此新的 UI 线程碰到 ContentPresenter 类型,那么将可能存在让新的 UI 线程和主 UI 线程互等.这是多 ...

  3. dotnet 读 WPF 源代码笔记 提升调试效率的 NamedObject 类型

    本文来聊聊 WPF 那些值得称赞的设计中的 NamedObject 类型.在 WPF 中,有很多值得我学习的设计开发思想,其中就包括本文将要介绍的 NamedObject 类型.此类型的定义仅仅只是为 ...

  4. 局域网内一部分网络设备无法ping通,icmp_seq=1 目标主机不可达

    问题: 来自 192.168.2.99 icmp_seq=1 目标主机不可达.   最近想在局域网内搭建一台服务器,打开SSH服务后发现局域网内的一部分设备无法使用,尝试了各种办法都没能解决,重装系统 ...

  5. net core下链路追踪skywalking安装和简单使用

    当我们用很多服务时,各个服务间的调用关系是怎么样的?各个服务单调用的顺序\时间性能怎么样?服务出错了,到底是哪个服务引起的?这些问题我们用什么方案解决呢,以前的方式是各个系统自己单独做日志,出了问题从 ...

  6. 解放双手!这个插件只要一张表就能生成CRUD代码

    大家好,我是 Java陈序员. 问君能有几多愁,代码一行又一行! 作为码农,代码是写不完的,而偷懒又是人的天性,能少干一点就少干一点. 今天,给大家介绍一个 IDEA 插件,帮助你快速生成出 CRUD ...

  7. 五、Doris数据分布

    在 Doris 中,数据都以表(Table)的形式进行逻辑上的描述 名词解释 数据分布:数据分布是将数据划分为子集, 按一定规则, 均衡地分布在不同节点上,以期最大限度地利用集群的并发性能 短查询:s ...

  8. R6_ES在互联网公司应用案例汇总参考

    Elasticsearch 是一个实时分布式搜索数据分析引擎,内部使用lucene做索引与搜索,能够解决常规和各种类型数据的存储及检索需求,典型的应用场景有:数据分析,站内搜索,ELK,电商等,主要特 ...

  9. 远程桌面使用Pr剪视频

    要远程访问高性能计算机并使用 Pr(Adobe Premiere Pro)进行视频编辑,您可以考虑使用流畅且响应迅速的远程桌面软件.您可以考虑以下选项. Splashtop Business Acce ...

  10. mySql脚本转换成sqlserver脚本(主流数据库的脚本都能转换,需要使用powerdesigner)

    我使用的powerdesginer版本是16.5,只需要脚本文件就可以了,不需要安装mysql和sqlserver. 文件->反向工程->Database... 选择原脚本文件的数据库类型 ...