Java + React export Excel/PDF

Preface

In the B/S architecture, server-side export is an efficient way. It places the exported logic on the server side, and the front end only needs to initiate a request. After completing the export on the server side, the front-end downloads the file to complete the entire export process. Server-side export has many advantages, such as data security, being suitable for large-scale data scenarios, and not being affected by front-end performance.

This article will use the front-end framework React and the server-side framework Spring Boot to build a demo to show how to export Excel and PDF files on the server side. Of course, similar principles can also be used for front-end frameworks such as Vue, Angular, etc. to achieve the same function.

During the server-side export process, you need to rely on additional components to process Excel and PDF files. For Excel-related operations, you can choose the POI library, and for PDF files, you can choose the IText library. For the sake of convenience, this solution chooses GcExcel, which natively supports export functions in multiple formats such as Excel, PDF, HTML, and pictures. In this way, while implementing the export function, it also provides more flexibility and interoperability.

practice

This article demonstrates how to create a simple form that includes name and email fields that will be exported as data. At the same time, the front end will provide a drop-down selector and an export button. Select the export format through the drop-down selector, and then click the export button to send the request. After waiting for the server-side processing to complete, the front-end will download the exported file.

On the server side, we need to implement the corresponding API to handle requests to submit data and export requests. We can define an object to save submitted data in memory. Then use the GcExcel library to build Excel objects and export the data to different formats.

FrontendReact

1. Create a React project

Create a new folder, such as ExportSolution, enter the folder, enter cmd in the address bar of the resource manager, and then press Enter to open the command line window.

Use the code below to create a react app named client-app.

npx create-react-app client-app

Go to the created client-app folder and use an IDE, such as Visual Studio Code, to open it.

2. Set up the form section

Update the code of Src/App.js. When creating a React app, the scaffolding will create sample codes and you need to delete them. As shown below (the red part is deleted and the green part is added).

In the Src directory, add a file named FormComponent.js and add a reference to App.js.

Add the following code to FormComponent.js. Three states, formData and exportType are defined, and count is used to store the values ​​on the page. The methods for interacting with the server are only defined.

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>
    );
}

The CSS code is as follows:

.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;
}

Try running it, the effect should be as follows:

3.Axios request and file download

There are three types of requests when the front end interacts with the server:

  • When the page is loaded, get how many times the data has been submitted by the server
  • Submit data and get the total number of times the data has been submitted
  • Send an export request and download the file based on the results.

Add two dependencies through npm, Axios is used to send requests, and file-saver is used to download files.

npm install axios
npm install file-saver

Add reference in FormComponent.js

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

The codes for the three request methods are as follows:

    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);
        }
    }

The three requests are all synchronous, using await to wait for the result to be returned. The three requests will be sent to the defined api respectively, of which fetchCount will only be executed when the page completes loading for the first time. The other two request methods are fired when the button is clicked.

4. Configure request forwarding middleware

Because React programs use port 3000 by default, and Springboot uses port 8080 by default. If Axios sends a request directly to the server (for example: localhost:8080/api/getListCount), cross-domain problems will occur. Therefore, a middleware needs to be added to forward the request to avoid the problem of front-end cross-domain access.

Add a file under the src folder named setupProxy.js with the following code:

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

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

OK, the front-end code is basically completed, but the test cannot be run yet because the server-side code is not completed.

Server Springboot

1. Create Springboot project

Use IDEA to create a Springboot project. If you are using the community version and cannot directly create a Springboot project, you can create an empty project first. The process of creating the project in IDEA is skipped. Here we create a gradle project. For example.

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()
}

In dependencies, in addition to relying on springboot, we also added a dependency on GcExcel. GcExcel will be used when exporting later. The current version is 6.2.0.

2. Add SpringBootApplication

After completing the addition of dependencies, delete the original main.java, create a new ExportServerApplication.java, and then add the following code.

@SpringBootApplication
@RestController
@RequestMapping("/api")
public class ExportServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExportServerApplication.class, args);
    }    
}

3. Add getListCount and commitData APIs

Continue to add an ArraryList in ExportServerApplication.java to temporarily store the submitted data. commitData adds the data to the ArraryList, and getListCount obtains the number of data from the 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. Add export API

In the React app, we use the selector to allow the selection of the export type. The selector provides 5 export formats: Xlsx, CSV, PDF, HTML, and PNG. In the exported API, you need to use GcExcel to build the Excel file and fill in the submitted data into the Excel workbook. After that, the file is generated according to the export type passed by the front end, and finally returned to the front end for downloading.

In GcExcel, you can save the workbook as Xlsx, CSV, PDF and HTML directly through workbook.save. But when exporting HTML, we need to perform special processing on HTML and PNG because it will be exported to multiple files.

    @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);
    }

For HTML, you can directly output HTML into zip through FileOutputStream.

    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;
    }    

For the PNG type, GcExcel can export a variety of image formats. Here, you can choose to export to PNG through ImageType.PNG, and export as an image in a file stream.

In addition, we need to prepare the model class separately. The code is as follows:

    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;
}

At this point we have completed the server-side code.

final effect

Add some data via forms and export different types of files at the same time.

Open these files and see if the exported data is correct.

Excel

PDF

CSV

HTML

PNG

write at the end

In addition to the above export functions, GcExcel can also implement other functions, such as sparklines , pivot tables , custom functions, etc. You are welcome to visit: https://demo.grapecity.com.cn/documents-api-excel-java /demos/


Extension link:

Implementing Excel server import and export under the Spring Boot framework

Project practice: online quotation procurement system (React + SpreadJS + Echarts)

Svelte framework combined with SpreadJS implements pure front-end Excel online report design

Guess you like

Origin blog.csdn.net/powertoolsteam/article/details/132604031
Recommended