前言

在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. 如何高效学习 Kubernetes 知识图谱?

    ​简介: Kubernetes 知识图谱遵循云原生人才学习路径搭建课程体系框架,及人才发展路线设置不同阶段,由浅入深,帮助云原生人才学习容器基础.Kuternetes 网络.存储.资源对象.服务发现. ...

  2. 2018-7-15-WPF-在-DrawingContext-的-push-如何使用

    title author date CreateTime categories WPF 在 DrawingContext 的 push 如何使用 lindexi 2018-7-15 15:51:0 + ...

  3. Raft 共识算法2-领导者选举

    Raft 共识算法2-领导者选举 Raft算法中译版地址:https://object.redisant.com/doc/raft中译版-2023年4月23日.pdf 英原论文地址:https://r ...

  4. k3s安装---适配边缘计算场景的轻量级的k8s(二)

    三.安装k8s k3s官网:https://k3s.io 文档: github:https://github.com/k3s-io/k3s 3.1 安装基础环境 # 安装基础环境 1.安装yum源 c ...

  5. 服务端向客户端发送消息Server-Sent Events

    今天听说了服务端向客户端发消息的一种方式:Server-Sent Events SSE使用的是HTTP协议,本质上是服务端向客户端发送流式数据. HTTP不支持服务端向客户端发送请求,但是如果客户端向 ...

  6. vue的pc端项目+element实现分页效果

    效果图: 直接使用element操作很简单,记录一下要点: 根据ele提供的api修改data v-for="(i,s) in dataView.slice((currentPage-1)* ...

  7. mybatis插件generator使用生成错误问题Execution default-cli of goal org.mybatis.generator:mybatis-generator-maven-plugin:1.3.2:generate failed: Exception getting JDBC Driver

    使用插件除了其他回答的路径等问题,我遇到的把jar版本换一下就成了 把5点几的换成8点几的就好使了

  8. 【GUI软件】抖音搜索结果批量采集,支持多个关键词、排序方式、发布时间筛选等!

    目录 一.背景介绍 1.1 爬取目标 1.2 演示视频 1.3 软件说明 二.代码讲解 2.1 爬虫采集模块 2.2 软件界面模块 2.3 日志模块 三.获取源码及软件 一.背景介绍 1.1 爬取目标 ...

  9. 超级详细的PostgreSQL创建数据库操作并附带图文

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485195&idx=1 ...

  10. RemoteView 替代品和类似软件

    RemoteView 是一款远程控制软件,使您可以通过Internet连接远程访问计算机和移动设备,而不受时间和地点的限制. 您可以快速,安全地实时轻松地控制计算机和移动设备. 您可以使用我们的iOS ...