经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
Java与React轻松导出Excel/PDF数据
来源:cnblogs  作者:葡萄城技术团队  时间:2024/6/19 15:14:59  对本文有异议

前言

在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。

  1. 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用来存储页面上的值。与服务端交互的方法,仅做了定义。

  1. import React, { useEffect, useState } from 'react';
  2. export const FormComponent = () => {
  3. const [formData, setFormData] = useState({
  4. name: '',
  5. email: '',
  6. });
  7. const [exportType, setExportType] = useState('0');
  8. const [count, setCount] = useState(0);
  9. useEffect(() => {
  10. fetchCount();
  11. },[]);
  12. const fetchCount = async () => {
  13. //TODO
  14. }
  15. const formDataHandleChange = (e) => {
  16. setFormData({
  17. ...formData,
  18. [e.target.name]: e.target.value
  19. });
  20. };
  21. const exportDataHandleChange = (e) => {
  22. setExportType(e.target.value);
  23. };
  24. const handleSubmit = async (e) => {
  25. //TODO
  26. };
  27. const download = async (e) => {
  28. //TODO
  29. }
  30. return (
  31. <div class="form-container">
  32. <label>信息提交</label>
  33. <br></br>
  34. <label>已有<span class="submission-count">{count}</span>次提交</label>
  35. <hr></hr>
  36. <form class="form" onSubmit={handleSubmit}>
  37. <label>
  38. 姓名:
  39. <input type="text" name="name" value={formData.name} onChange={formDataHandleChange} />
  40. </label>
  41. <br />
  42. <label>
  43. 邮箱:
  44. <input type="email" name="email" value={formData.email} onChange={formDataHandleChange} />
  45. </label>
  46. <button type="submit">提交</button>
  47. </form>
  48. <hr />
  49. <div className='export'>
  50. <label>
  51. 导出类型:
  52. <select class="export-select" name="exportType" value={exportType} onChange={exportDataHandleChange}>
  53. <option value='0'>Xlsx</option>
  54. <option value='1'>CSV</option>
  55. <option value='2'>PDF</option>
  56. <option value='3'>HTML</option>
  57. <option value='4'>PNG</option>
  58. </select>
  59. </label>
  60. <br />
  61. <button class="export-button" onClick={download}>导出并下载</button>
  62. </div>
  63. </div>
  64. );
  65. }

CSS的代码如下:

  1. .form-container {
  2. margin: 20px;
  3. padding: 20px;
  4. border: 1px solid #ccc;
  5. width: 300px;
  6. font-family: Arial, sans-serif;
  7. min-width: 40vw;
  8. }
  9. .submission-count {
  10. font-weight: bold;
  11. }
  12. .form{
  13. text-align: left;
  14. }
  15. .form label {
  16. display: block;
  17. margin-bottom: 10px;
  18. font-weight: bold;
  19. }
  20. .form input[type="text"],
  21. .form input[type="email"] {
  22. width: 100%;
  23. padding: 5px;
  24. margin-bottom: 10px;
  25. border: 1px solid #ccc;
  26. border-radius: 4px;
  27. }
  28. .form button {
  29. padding: 10px 20px;
  30. background-color: #007bff;
  31. color: #fff;
  32. border-radius: 4px;
  33. cursor: pointer;
  34. width: 100%;
  35. }
  36. .export{
  37. text-align: left;
  38. }
  39. .export-select {
  40. padding: 5px;
  41. margin-bottom: 10px;
  42. border: 1px solid #ccc;
  43. border-radius: 4px;
  44. width: 10vw;
  45. }
  46. .export-button {
  47. padding: 10px 20px;
  48. background-color: #007bff;
  49. color: #fff;
  50. border-radius: 4px;
  51. cursor: pointer;
  52. width: 100%;
  53. }
  54. hr {
  55. margin-top: 20px;
  56. margin-bottom: 20px;
  57. border: none;
  58. border-top: 1px solid #ccc;
  59. }

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

3.Axios请求及文件下载

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

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

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

  1. npm install axios
  2. npm install file-saver

在FormComponent.js中添加引用

  1. import axios from 'axios';
  2. import { saveAs } from 'file-saver';

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

  1. const fetchCount = async () => {
  2. let res = await axios.post("api/getListCount");
  3. if (res !== null) {
  4. setCount(res.data);
  5. }
  6. }
  7. const handleSubmit = async (e) => {
  8. e.preventDefault();
  9. let res = await axios.post("api/commitData", {...formData});
  10. if (res !== null) {
  11. setCount(res.data);
  12. }
  13. };
  14. const download = async (e) => {
  15. let headers = {
  16. 'Content-Type': 'application/json',
  17. 'Access-Control-Allow-Headers': 'Content-Disposition'
  18. };
  19. let data = { exportType: exportType };
  20. let res = await axios.post('/api/exportDataList', data, { headers: headers, responseType: 'blob' });
  21. if (res !== null) {
  22. let contentDisposition = res.headers['content-disposition']
  23. let filename = contentDisposition.substring(contentDisposition.indexOf('"') + 1, contentDisposition.length - 1);
  24. saveAs(res.data, filename);
  25. }
  26. }

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

4.配置请求转发中间件

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

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

  1. const { createProxyMiddleware } = require('http-proxy-middleware');
  2. module.exports = function(app) {
  3. app.use(
  4. '/api',
  5. createProxyMiddleware({
  6. target: 'http://localhost:8080',
  7. changeOrigin: true,
  8. })
  9. );
  10. };

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

服务端 Springboot

1.创建Springboot工程

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

  1. plugins {
  2. id 'org.springframework.boot' version '3.0.0'
  3. id 'io.spring.dependency-management' version '1.1.0'
  4. id 'java'
  5. id 'war'
  6. }
  7. group = 'org.example'
  8. version = '1.0-SNAPSHOT'
  9. repositories {
  10. mavenCentral()
  11. }
  12. dependencies {
  13. implementation 'org.springframework.boot:spring-boot-starter-web'
  14. implementation 'com.grapecity.documents:gcexcel:6.2.0'
  15. implementation 'javax.json:javax.json-api:1.1.4'
  16. providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
  17. testImplementation('org.springframework.boot:spring-boot-starter-test')
  18. }
  19. test {
  20. useJUnitPlatform()
  21. }

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

2.添加SpringBootApplication

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

  1. @SpringBootApplication
  2. @RestController
  3. @RequestMapping("/api")
  4. public class ExportServerApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(ExportServerApplication.class, args);
  7. }
  8. }

3.添加 getListCount 和 commitData API

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

  1. private static ArrayList<CommitParameter> dataList = new ArrayList<>();
  2. @PostMapping("/commitData")
  3. public int commitData(@RequestBody CommitParameter par) {
  4. dataList.add(par);
  5. return dataList.size();
  6. }
  7. @PostMapping("/getListCount")
  8. public int getCount() {
  9. return dataList.size();
  10. }
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进行特殊处理。

  1. @PostMapping("/exportDataList")
  2. public ResponseEntity<FileSystemResource> exportPDF(@RequestBody ExportParameter par) throws IOException {
  3. var workbook = new Workbook();
  4. copyDataToWorkbook(workbook);
  5. String responseFilePath = "";
  6. switch (par.exportType) {
  7. case Html -> {
  8. responseFilePath = exportToHtml(workbook);
  9. }
  10. case Png -> {
  11. responseFilePath = exportToImage(workbook);
  12. }
  13. default -> {
  14. responseFilePath = "download." + par.exportType.toString().toLowerCase();
  15. workbook.save(responseFilePath, Enum.valueOf(SaveFileFormat.class, par.exportType.toString()));
  16. }
  17. }
  18. FileSystemResource file = new FileSystemResource(responseFilePath);
  19. HttpHeaders headers = new HttpHeaders();
  20. headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"");
  21. return ResponseEntity.ok()
  22. .headers(headers)
  23. .contentLength(file.contentLength())
  24. .body(file);
  25. }
  26. private static void copyDataToWorkbook(Workbook workbook) {
  27. Object[][] data = new Object[dataList.size() + 1][2];
  28. data[0][0] = "name";
  29. data[0][1] = "email";
  30. for (int i = 0; i < dataList.size(); i++) {
  31. data[i + 1][0] = dataList.get(i).name;
  32. data[i + 1][1] = dataList.get(i).email;
  33. }
  34. workbook.getActiveSheet().getRange("A1:B" + dataList.size() + 1).setValue((Object) data);
  35. }

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

  1. private String exportToHtml(Workbook workbook) {
  2. String outPutFileName = "SaveWorkbookToHtml.zip";
  3. FileOutputStream outputStream = null;
  4. try {
  5. outputStream = new FileOutputStream(outPutFileName);
  6. } catch (FileNotFoundException e) {
  7. e.printStackTrace();
  8. }
  9. workbook.save(outputStream, SaveFileFormat.Html);
  10. try {
  11. outputStream.close();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. return outPutFileName;
  16. }

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

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

  1. private String exportToImage(Workbook workbook) {
  2. String outPutFileName = "ExportSheetToImage.png";
  3. FileOutputStream outputStream = null;
  4. try {
  5. outputStream = new FileOutputStream(outPutFileName);
  6. } catch (FileNotFoundException e) {
  7. e.printStackTrace();
  8. }
  9. IWorksheet worksheet = workbook.getWorksheets().get(0);
  10. worksheet.toImage(outputStream, ImageType.PNG);
  11. try {
  12. outputStream.close();
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. return outPutFileName;
  17. }

CommitParameter.java:

  1. package org.example;
  2. public class CommitParameter {
  3. public String name;
  4. public String email;
  5. }

ExportParameter.java:

  1. package org.example;
  2. public class ExportParameter {
  3. public ExportType exportType;
  4. }

ExportType.java:

  1. package org.example;
  2. public enum ExportType {
  3. Xlsx,
  4. Csv,
  5. Pdf,
  6. Html,
  7. Png;
  8. }

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

最终效果

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

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

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 在线报表设计

原文链接:https://www.cnblogs.com/powertoolsteam/p/18255595

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号