Crie PDFs dinâmicos usando React e Node.js

O material, cuja tradução publicamos hoje, é dedicado à criação de arquivos PDF dinâmicos usando o código HTML como modelo. Nomeadamente, falaremos sobre como criar uma fatura simples para pagamento de certos bens ou serviços, cujos dados dinâmicos incluídos são retirados do estado do aplicativo React. A base do aplicativo React foi criada usando create-react-app, a parte do servidor do projeto é baseada no Node.js e a estrutura Express foi usada em seu desenvolvimento.



O autor deste material observa que ele preparou um vídeo que demonstra o desenvolvimento do projeto. Se você decidir assistir ao vídeo e ler o artigo, é recomendável fazê-lo. Primeiro, percorra o artigo, depois ligue o vídeo e recrie o sistema que você está pensando lá. Depois disso, basta ler o artigo.

Criação de projeto


Crie um diretório de projeto e acesse:

mkdir pdfGenerator && cd pdfGenerator 

Crie um novo aplicativo React:

 create-react-app client 

Após concluir a criação do aplicativo, vá para o diretório que você acabou de criar e instale as dependências:

 cd client && npm i -S axios file-saver 

Crie um servidor Express. Para fazer isso, crie a pasta do server no diretório do projeto e vá para ele. Nele, crie o arquivo index.js e inicie a inicialização do projeto:

 mkdir server && cd server && touch index.js && npm init 

Aqui, para formar package.json , basta pressionar Enter várias vezes. Depois disso, execute o seguinte comando para adicionar as dependências necessárias à parte do servidor do projeto:

 npm i -S express body-parser cors html-pdf 

Agora, no arquivo client/package.json , acima da seção de descrição da dependência, adicione o seguinte:

 "proxy": "http://localhost:5000/" 

Isso ajudará no trabalho com o servidor local a partir do código do cliente.

Agora você precisa abrir duas janelas do terminal.

Na primeira janela, vá para o diretório do client e execute o seguinte comando:

 npm start 

Na segunda janela, vá para a pasta do server e execute o seguinte comando:

 nodemon index.js 

Configuração inicial do cliente


A parte do cliente do nosso projeto parecerá muito simples.

Para começar, no src/App.js parte do cliente do aplicativo, importamos as dependências para o código:

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

Depois disso, na parte superior da descrição do componente, inicialize o estado:

 state = {   name: 'Adrian',   receiptId: 0,   price1: 0,   price2: 0, } 

Vamos remover a marcação JSX padrão criada no modelo de aplicativo usando o create-react-app e retornada do método render() . Insira o seguinte:

 <div className="App">   <input type="text" placeholder="Name" name="name" onChange {this.handleChange}/>   <input type="number" placeholder="Receipt ID" name="receiptId"  onChange={this.handleChange}/>   <input type="number" placeholder="Price 1" name="price1" onChange={this.handleChange}/>   <input type="number" placeholder="Price 2" name="price2" onChange={this.handleChange}/>   <button onClick={this.createAndDownloadPdf}>Download PDF</button></div> 

Vamos criar o método handleChange , que será responsável por atualizar os dados do estado do aplicativo relacionados aos campos de entrada:

 handleChange = ({ target: { value, name }}) => this.setState({ [name]: value }) 

Agora podemos passar à tarefa de criar um arquivo PDF. Essa parte, resolvida por meio do cliente, é criar uma solicitação POST para o servidor. A solicitação envia o estado do aplicativo:

 createAndDownloadPdf = () => {   axios.post('/create-pdf', this.state) } 

Antes de continuarmos trabalhando no lado do cliente do projeto, precisamos configurar as rotas no servidor. Isso permitirá que o servidor receba dados do cliente, gere um arquivo PDF e transfira esse arquivo de volta para o cliente.

Configuração inicial do servidor


A parte do servidor do projeto incluirá apenas duas rotas. É necessário um para criar PDFs. O segundo é para enviar arquivos para o cliente.

Primeiro, importe as dependências para o arquivo index.js :

 const express = require('express'); const bodyParser = require('body-parser'); const pdf = require('html-pdf'); const cors = require('cors'); 

Inicializamos o aplicativo Express e configuramos a porta:

 const app = express(); const port = process.env.PORT || 5000; 

Configure o analisador de consultas. O que precisamos estará disponível como req.body . Também configuraremos o CORS para que nosso erro de Cross-Origin Request Blocked não interfira em nosso trabalho:

 app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); 

Depois disso, inicie o servidor:

 app.listen(port, () => console.log(`Listening on port ${port}`)); 

Agora podemos resolver o código responsável pela criação dos PDFs.

Crie um modelo HTML para PDFs


Precisamos de um modelo HTML para usar ao criar arquivos PDF. Ao criar esse modelo, infinitas possibilidades se abrem diante de nós. Tudo o que pode ser criado usando HTML e CSS puro pode ser representado como um arquivo PDF. Crie o diretório de documents na pasta do server , acesse-o e crie o arquivo index.js :

 mkdir documents && cd documents && touch index.js 

A partir desse arquivo, exportamos uma função de seta que retornará todo o código HTML necessário. Ao chamar esta função, você pode usar os parâmetros, que também descreveremos aqui.

 module.exports = ({ name, price1, price2, receiptId }) => { ... } 

Aqui vou dar um exemplo de um modelo HTML, e você apenas o copia no seu projeto. Mas você, é claro, pode criar seu próprio modelo.

Vamos index.js código index.js da pasta server/documents para o seguinte formato:

 module.exports = ({ name, price1, price2, receiptId }) => {    const today = new Date(); return `    <!doctype html>    <html>       <head>          <meta charset="utf-8">          <title>PDF Result Template</title>          <style>             .invoice-box {             max-width: 800px;             margin: auto;             padding: 30px;             border: 1px solid #eee;             box-shadow: 0 0 10px rgba(0, 0, 0, .15);             font-size: 16px;             line-height: 24px;             font-family: 'Helvetica Neue', 'Helvetica',             color: #555;             }             .margin-top {             margin-top: 50px;             }             .justify-center {             text-align: center;             }             .invoice-box table {             width: 100%;             line-height: inherit;             text-align: left;             }             .invoice-box table td {             padding: 5px;             vertical-align: top;             }             .invoice-box table tr td:nth-child(2) {             text-align: right;             }             .invoice-box table tr.top table td {             padding-bottom: 20px;             }             .invoice-box table tr.top table td.title {             font-size: 45px;             line-height: 45px;             color: #333;             }             .invoice-box table tr.information table td {             padding-bottom: 40px;             }             .invoice-box table tr.heading td {             background: #eee;             border-bottom: 1px solid #ddd;             font-weight: bold;             }             .invoice-box table tr.details td {             padding-bottom: 20px;             }             .invoice-box table tr.item td {             border-bottom: 1px solid #eee;             }             .invoice-box table tr.item.last td {             border-bottom: none;             }             .invoice-box table tr.total td:nth-child(2) {             border-top: 2px solid #eee;             font-weight: bold;             }             @media only screen and (max-width: 600px) {             .invoice-box table tr.top table td {             width: 100%;             display: block;             text-align: center;             }             .invoice-box table tr.information table td {             width: 100%;             display: block;             text-align: center;             }             }          </style>       </head>       <body>          <div class="invoice-box">             <table cellpadding="0" cellspacing="0">                <tr class="top">                   <td colspan="2">                      <table>                         <tr>                            <td class="title"><img src="https://i2.wp.com/cleverlogos.co/wp-content/uploads/2018/05/reciepthound_1.jpg?fit=800%2C600&ssl=1"                               style="width:100%; max-width:156px;"></td>                            <td>                               Datum: ${`${today.getDate()}. ${today.getMonth() + 1}. ${today.getFullYear()}.`}                            </td>                         </tr>                      </table>                   </td>                </tr>                <tr class="information">                   <td colspan="2">                      <table>                         <tr>                            <td>                               Customer name: ${name}                            </td>                            <td>                               Receipt number: ${receiptId}                            </td>                         </tr>                      </table>                   </td>                </tr>                <tr class="heading">                   <td>Bought items:</td>                   <td>Price</td>                </tr>                <tr class="item">                   <td>First item:</td>                   <td>${price1}$</td>                </tr>                <tr class="item">                   <td>Second item:</td>                   <td>${price2}$</td>                </tr>             </table>             <br />             <h1 class="justify-center">Total price: ${parseInt(price1) + parseInt(price2)}$</h1>          </div>       </body>    </html>    `; }; 

server/index.js esse arquivo no arquivo server/index.js :

 const pdfTemplate = require('./documents'); 

Crie PDFs


Lembre-se de que no servidor vamos criar duas rotas. A rota POST será responsável por receber dados do cliente e criar um arquivo PDF. A rota GET será usada para enviar o arquivo finalizado para o cliente.

▍ rota create-pdf


Na rota create-pdf POST, usaremos o comando pdf.create() , referindo-se ao objeto importado do módulo html-pdf .

Como o primeiro parâmetro do método pdf.create() , um modelo HTML é usado, além de dados recebidos do cliente.

Para retornar pdf.create() , chamamos o método toFile() , passando o nome que queremos atribuir ao documento PDF, bem como a função de retorno de seta. Esta função, em caso de erro, executará o res.send(Promise.reject()) . Caso tudo res.send(Promise.resolve()) bem, ela executará o comando res.send(Promise.resolve()) .

 app.post('/create-pdf', (req, res) => {    pdf.create(pdfTemplate(req.body), {}).toFile('result.pdf', (err) => {        if(err) {            res.send(Promise.reject());        }        res.send(Promise.resolve());    }); }); 

Rota etchFetch-pdf


Ao mesmo tempo, criaremos uma rota que será usada depois que, a pedido do cliente, um arquivo PDF for criado com sucesso. Aqui, apenas pegamos o documento final e o enviamos ao cliente usando res.sendFile() :

 app.get('/fetch-pdf', (req, res) => {    res.sendFile(`${__dirname}/result.pdf`) }) 

Função cliente createAndDownloadPdf


Agora podemos retornar ao código do cliente e continuar trabalhando na função createAndDownloadPdf . Aqui, fazemos uma solicitação POST ao servidor usando o módulo axios . Agora, esta função se parece com isso:

 createAndDownloadPdf = () => {   axios.post('/create-pdf', this.state) } 

Se, após executar uma solicitação POST para o servidor, um documento PDF foi criado, precisamos executar uma solicitação GET, em resposta à qual o servidor enviará o documento final ao cliente.

Para implementar esse esquema de comportamento, chamamos axios.post() . then() . Isso nos permite fazer isso em resposta à solicitação POST de um cliente, retornamos uma promessa do servidor que pode ser resolvida ou rejeitada com êxito.

Nós suplementamos a função com o seguinte código:

 .then(() => axios.get('/fetch-pdf', { responseType: 'blob' })) .then(( res) => {}) 

Aqui você pode prestar atenção ao fato de que o blob usado como responseType . Antes de prosseguirmos, vamos falar sobre o que é.

Objetos de blob


Blob é um objeto imutável que representa alguns dados brutos. Esses objetos geralmente são usados ​​para trabalhar com dados que podem não estar no formato JavaScript nativo. Esses objetos são sequências de bytes que armazenam, por exemplo, dados do arquivo. Pode parecer que o objeto Blob esteja armazenando um link para um arquivo, mas na verdade não está. Esses objetos armazenam dados com os quais você pode trabalhar. Por exemplo - eles podem ser salvos em arquivos.

Conclusão do Projeto


Agora que sabemos o que são os objetos Blob , podemos usar outra chamada .then() para res.data novo objeto Blob , com base em res.data. Ao criar este objeto, passaremos um parâmetro para seu construtor, indicando que os dados que o objeto armazenará são do tipo application/pdf . Depois disso, podemos usar o método saveAs() , que foi importado do módulo de file-saver , e salvar os dados em um arquivo. Como resultado, o código do método createAndDowndloadPdf será semelhante ao mostrado abaixo:

   createAndDownloadPdf = () => {    axios.post('/create-pdf', this.state)      .then(() => axios.get('fetch-pdf', { responseType: 'blob' }))      .then((res) => {        const pdfBlob = new Blob([res.data], { type: 'application/pdf' });        saveAs(pdfBlob, 'newPdf.pdf');      })  } 

É assim que a interface do navegador se parece.


Aplicativo no navegador

Após preencher os campos e clicar no botão Download PDF , os dados são transferidos para o servidor e um documento PDF é baixado dele.


Baixar PDF

E aqui está o próprio documento PDF.


PDF gerado por software

Sumário


Analisamos um mecanismo que permite criar programaticamente arquivos PDF. Aqui está um repositório GitHub com código do projeto. Esperamos que as idéias que você conheceu neste material sejam úteis em seu desenvolvimento.

Caros leitores! Como você resolveria o problema de criar programaticamente arquivos PDF usando o Node.js.

Source: https://habr.com/ru/post/pt460018/


All Articles