首页 > 编程语言 >Java与React轻松导出Excel/PDF数据

Java与React轻松导出Excel/PDF数据

时间:2024-06-19 09:20:46浏览次数:28  
标签:Java 导出 Excel React 添加 workbook const 服务端

前言

在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,导出,Excel,React,添加,workbook,const,服务端
From: https://www.cnblogs.com/powertoolsteam/p/18255595

相关文章

  • 【解决方案】Java 互联网项目中消息通知系统的设计与实现(上)
    目录前言一、需求分析1.1发送通知1.2撤回通知1.3通知消息数1.4通知消息列表二、数据模型设计2.1概念模型2.2逻辑模型三、关键流程设计本篇小结前言消息通知系统(notification-system)作为一个独立的微服务,完整地负责了App端内所有消息通知相关的后端功能实现。该系统既需要与文......
  • Vue 3中的reactive:响应式对象和数组
    ......
  • Java面向对象:初识多态
    1、多态多态是面向对象编程的三大基本特性之一。多态指的是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。多态实现条件:1、继承关系的存在2、方法的重写3、父类引用调用重写的方法例子:classAnimal{publicvoideat(){System.out.prin......
  • C# 数据导出成Excel的流
    导出的帮助类publicclassExcelHelper{///<summary>///将给定的模型列表转换为Excel内存流,第一行和第二行是居中对齐加粗的///</summary>///<typeparamname="T">模型类型</typeparam>///<paramname="models&qu......
  • JavaScript中各种源码实现
    文章目录JavaScript中各种源码实现1.实现一个new操作符2.实现一个Array.isArray3.实现一个Object.create()方法4.实现一个EventEmitter5.实现一个Array.prototype.reduce6.实现一个call或apply7.实现一个Function.prototype.bind8.实现一个JS函数柯里化9.手写防......
  • JAVA实验九
    一 算术测试Classonepackageaaa;importjava.util.Random;importjava.awt.event.*;importjavax.swing.*;publicclassOneimplementsActionListener{ intnumberOne,numberTwo; Stringoperator=""; booleanisRight; Randomrandom; intmaxInteger;......
  • ​b站视频演示效果:【web前端特效源码】使用HTML5+CSS3+JavaScript十分钟快速制作一个
    b站视频演示效果:【网页设计期末大作业源代码】使用HTML5+CSS3+JavaScript十分钟快速制作一个简约大气卡通动漫静态网站|自制超简单的卡通类网页,响应式自适应新手友效果图:完整代码:<!DOCTYPEhtml><html><head><title>Home</title><metaname="viewpor......
  • Java怎么现在支付宝沙盒支付
    一、支付环境准备支付宝的公钥和私钥支付的网关支付的APPID1、配置沙箱应用环境1、打开支付宝开放平台,官网:支付宝开放平台2、登录个人账户,然后点击控制台找到里面的沙箱3、这里能够找到APPID和支付宝网关地址和密钥二、设置内网穿透环境我用的花生壳:花生壳官网|动态域名|......
  • java基础·小白入门(一)
    目录Java语言概述Java的性质三种平台跨平台原理Java语言开发环境相关概念Java开发工具的安装Java程序的编译与运行基本注意事项Java语言基础数据类型基本数据类型引用数据类型关键字与标识符常量与变量常量变量数据类型转换常见运算符Java语言概述这一部分主要......
  • 课题分享:校园快领服务系统,基于java+SSM+mysql
     一、前言介绍     随着中国经济的快速发展和互联网技术的普及,信息管理改革确实成为了一种广泛和全面的趋势。在这一背景下,基于MySQL数据库的校园快领服务系统应运而生,这不仅体现了信息化建设在教育领域的深入应用,也展现了现代管理手段在提高工作效率和优化服务体验......