前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React大法:如何轻松编写动态PDF文件

React大法:如何轻松编写动态PDF文件

原创
作者头像
zayyo
发布2023-10-04 18:20:37
6371
发布2023-10-04 18:20:37
举报
文章被收录于专栏:zayyo前端

介绍

在本文中,我们将学习如何通过接受用户的输入来生成动态 PDF。一些用例包括根据收到的数据生成invoicescertificatesresumes、等。reports

为了启用 PDF 下载,我们将使用react-pdf提供有用组件的包,如DocumentPageViewImageTextPDFDownloadLinkPDFViewer

让我们检查一下每个组件:

  • Document :这个标签代表PDF文档本身,并且必须是我们PDF的根。
  • 页面:它代表 PDF 文档内的单个页面,并且应始终仅在文档组件内呈现。
  • View :此组件有助于构建 PDF 的 UI。它可以嵌套在其他视图中。
  • Image :用于在 PDF 中显示网络或本地(仅 Node)JPG 或 PNG 图像。
  • 文本:用于显示 PDF 中的文本。它还支持其他文本组件的嵌套。
  • PDFDownloadLink :它可以生成和下载 pdf 文档。
  • PDFViewer :它用于呈现客户端生成的文档。

装置

使用以下 cmd创建pdf-invoice React 应用程序:

代码语言:javascript
复制
npx create-react-app react-pdf-invoice

成功创建应用程序后,使用以下命令转到目录并启动项目 -

代码语言:javascript
复制
cd react-pdf-invoice
npm start

在react应用程序中安装react-pdf的命令:

  • 使用 npm
代码语言:javascript
复制
npm install @react-pdf/renderer --save
  • 使用纱线
代码语言:javascript
复制
yarn add @react-pdf/renderer

文件夹结构:

文件夹结构
文件夹结构

创建发票表格

由于我们的 PDF 本质上是动态的,因此可以选择添加/删除项目、更改产品的价格/数量、根据提到的项目计算总金额。 因此,我们需要接受用户的输入并相应地显示数据。

src > 组件 > createInvoice > InvoiceForm.js

代码语言:javascript
复制
import React, { useState } from 'react';
import InvoicePDF from '../getPDF/InvoicePDF';
import { PDFDownloadLink } from '@react-pdf/renderer';
import './styles.css';

const InvoiceForm = () => {

// state for storing info about user creating Invoice
  const [billFrom, setBillFrom] = useState({
    name: '',
    address: '',
    invoiceNumber: '',
  })

// state for capturing info of person who needs to pay
  const [client, setClient] = useState({
    clientName: '',
    clientAddress: '',
  })

// items description containing name, price and quantity
  const [items, setItems] = useState([{ name: '', quantity: 0, price: 0}]);

  const handleBillFromData = (e) => {
    e.preventDefault();
    const {name, value} = e.target;  
    setBillFrom({
      ...billFrom,
      [name] : value
    })
  }

  const handleClientData = (e) => {
    e.preventDefault();
    const {name, value} = e.target;  
    setClient({
      ...client,
      [name] : value
    })
  }

  const handleItemChange = (e, index, field, value) => {
    e.preventDefault();
    const updatedItems = [...items];
    updatedItems[index][field] = value; // updating the item field (using index) according to user's input

    setItems(updatedItems);  // updating the items array
  };

  const handleAddItem = () => {
    setItems([...items, { name: '', quantity: 0, price: 0}]);  // adding new item to items array
  };

  const handleRemoveItem = (index) => {
    const updatedItems = [...items];
    updatedItems.splice(index, 1); // removing the selected item

    setItems(updatedItems);  // updating the items array 
  };

// to compute the items' total amount
  const total = () => {
    return items.map(({price, quantity}) => price * quantity).reduce((acc, currValue) => acc + currValue, 0);
  }

  return (
    <div className="invoice">
      <div>
        <h1 className='title'>Invoice</h1>

        <div className='firstRow'>
          <div className='inputName'>
            <label>Invoice Number:</label>
            <input name="invoiceNumber" className="input" type="text" value={billFrom.invoiceNumber} onChange={handleBillFromData} />
          </div>
        </div>

        <div className='firstRow'>
          <div className='inputName'>
            <label>Name:</label>
            <input name="name" className="input" type="text" value={billFrom.name} onChange={handleBillFromData} />
          </div>
          <div className='inputName'>
            <label>Address:</label>
            <textarea name="address" className="textarea" type="text" value={billFrom.address} onChange={handleBillFromData} />
          </div>
        </div>

        <hr/>

        <h2>Bill To:</h2>

        <div className='firstRow'>
          <div className='inputName'>
            <label>Client Name:</label>
            <input name="clientName" className="input" type="text" value={client.clientName} onChange={handleClientData} />
          </div>
          <div className='inputName'>
            <label>Address:</label>
            <textarea name="clientAddress" className="textarea" type="text" value={client.clientAddress} onChange={handleClientData} />
          </div>
        </div>

        <h2 className='title'>Add Details</h2>
        <div className='subTitleSection'>
          <h2 className='subTitle item'>Item</h2>
          <h2 className='subTitle quantity'>Quantity</h2>
          <h2 className='subTitle price'>Price</h2>
          <h2 className='subTitle action'>Amount</h2>
        </div>

        {items?.map((item, index) => (
          <div key={index} className='firstRow'>
            <input className="input item"
              type="text"
              value={item.name}
              onChange={(e) => handleItemChange(e, index, 'name', e.target.value)}
              placeholder="Item Name"
            />
            <input className="input quantity"
              type="number"
              value={item.quantity}
              onChange={(e) => handleItemChange(e, index, 'quantity', e.target.value)}
              placeholder="Quantity"
            />
            <input className="input price"
              type="number"
              value={item.price}
              onChange={(e) => handleItemChange(e, index, 'price', e.target.value)}
              placeholder="Price"
            />
            <p className='amount'>$ {item.quantity * item.price}</p>
            <button className='button' onClick={() => handleRemoveItem(index)}>-</button>
          </div>
        ))}
        <button className='button' onClick={handleAddItem}>+</button>
        <hr/>

        <div className='total'>
          <p>Total:</p>
          <p>{total()}</p>
        </div>
        <hr/>

        <PDFDownloadLink document={<InvoicePDF billFrom={billFrom} client={client} total={total} items={items} />} fileName={"Invoice.pdf"} >
          {({ blob, url, loading, error }) =>
                loading ? "Loading..." : <button className='button'>Print Invoice</button>
              }
        </PDFDownloadLink>
      </div>
    </div>
  );
};

export default InvoiceForm;

存储用户信息

代码语言:javascript
复制
const handleBillFromData = (e) => {
    e.preventDefault();
    const {name, value} = e.target;  
    setBillFrom({
      ...billFrom,
      [name] : value
    })
  }

在上述代码中,我们存储了创建发票的人员的信息(姓名、发票编号、地址)。

存储客户信息

代码语言:javascript
复制
const handleClientData = (e) => {
    e.preventDefault();
    const {name, value} = e.target;  
    setClient({
      ...client,
      [name] : value
    })
  }

在上面的代码块中,该函数将根据用户的输入更新需要支付总金额的客户信息(名称、地址)。

对项目的创建/更新/删除操作

代码语言:javascript
复制
  const handleItemChange = (e, index, field, value) => {
    e.preventDefault();
    const updatedItems = [...items];
    updatedItems[index][field] = value; // updating the item field (using index) according to user's input

    setItems(updatedItems);  // updating the items array
  };

  const handleAddItem = () => {
    setItems([...items, { name: '', quantity: 0, price: 0}]);  // adding new item to items array
  };

  const handleRemoveItem = (index) => {
    const updatedItems = [...items];
    updatedItems.splice(index, 1); // removing the selected item

    setItems(updatedItems);  // updating the items array 
  };

每当用户单击按钮时,handleAddItem()将添加一个具有itemName, quantity,作为输入字段的新项目。price+

当用户单击按钮时, handleRemoveItem()将删除所选项目-

handleItemChange()将通过获取特定项目的索引和值(由用户输入)来更新所选项目。

PDFDownloadLink 的道具

代码语言:javascript
复制
<PDFDownloadLink document={<InvoicePDF billFrom={billFrom} client={client} total={total} items={items} />} fileName={"Invoice.pdf"} >
          {({ blob, url, loading, error }) =>
                loading ? "Loading..." : <button className='button'>Print Invoice</button>
              }
        </PDFDownloadLink>

上面提到的代码片段负责使用用户输入的所有输入生成 PDF 文档。

  • document : 实现PDF文档功能
  • filename:下载后 PDF 的名称
  • style:用于添加样式的标签

在发票表单中添加样式

src > 组件 > createInvoice > styles.css

代码语言:javascript
复制
.invoice {
    display: flex;
    padding: 10px;
    margin: 20px;
    border-radius: 12px;
    justify-content: center;
    width: 1200px;
    box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
}

.title {
    font-size: 30px;
}

.firstRow {
    display: flex;
    justify-content: space-between;
    flex-grow: 1;
}

.inputName {
    display: flex;
    flex-direction: column;
}

.total {
    display: flex;
    justify-content: space-between;
    font-size: 20px;
}

.total > p {
    align-items: center;
    justify-content: center;
    font-weight: 700;
}

.inputName > label {
    font-size: 20px;
    font-weight: 600;
    margin-right: 5px;
    text-align: left;
}

.input, .textarea{
    font-size: 20px;
    border-radius: 5px;
    padding-left: 10px;
    margin: 10px 0px;
}

.subTitleSection {
    background: bisque;
    display: flex;
    justify-content: space-between;
    border-radius: 12px;
    flex-grow: 1;
    padding-right: 15px;
}

.subTitle {
    font-size: 20px;
    font-weight: 700;
    margin: 10px 10px;
    text-align: left;
}

.item {
    width: 50%;
}

.quantity {
    width: 15%;
}

.price {
    width: 15%;
}

.action {
    width: 10%;
}

.amount {
    font-size: 18px;
    font-weight: 700;
}

.remove {
    border-radius: 12px;
    height: 20px;
    width: 20px;
}

.button {
    background-color: #405cf5;
    border-radius: 6px;
    border-width: 0;
    box-sizing: border-box;
    color: #fff;
    cursor: pointer;
    font-size: 18px;
    height: 44px;
    padding: 0 25px;
}

发票表格用户界面

发票表格用户界面
发票表格用户界面
  • 包含多项的发票:
包含多个项目的发票 UI
包含多个项目的发票 UI

根据发票数据生成 PDF 文档

一旦我们从用户端获得所需的数据,我们就将数据提供给负责生成 pdf 文档的组件。在我们的例子中,InvoicePDF 就是该组件。

src > 组件 > getPDF > InvoicePDF.js

代码语言:javascript
复制
import React from 'react';
import { Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer';
import ItemsTable from './ItemsTable';

const styles = StyleSheet.create({
  page: {
    flexDirection: 'column',
    padding: 20,
  },
  name: {
    flexDirection: 'column',
    justifyContent: 'flex-start',
    fontSize: 22,
    marginBottom: 5
  },
  invoiceNumber: {
    flexDirection: 'column',
    justifyContent: 'flex-start',
  },
  section: {
    margin: 10,
    padding: 10,
    flexGrow: 1,
  },
  header: {
    fontSize: 24,
    marginBottom: 10,
    textAlign: 'center'
  },
  label: {
    fontSize: 12,
    marginBottom: 5,
  },
  input: {
    marginBottom: 10,
    paddingBottom: 5,
  },
  client: {
    borderTopWidth: 1,
    marginTop: 20,
    marginBottom: 10
  },
});

const InvoicePDF = ({ billFrom, client, total, items }) => { // destructuring props
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <View>
          <Text style={styles.header}>Invoice Form</Text>
          <View>
            <Text style={styles.name}>{billFrom.name}</Text>
          </View>

          <View style={styles.invoiceNumber}>
            <Text style={styles.label}>INVOICE NO.</Text>
            <Text style={styles.input}>{billFrom.invoiceNumber}</Text>
          </View>

          <View style={styles.invoiceNumber}>
            <Text style={styles.label}>ADDRESS</Text>
            <Text style={styles.input}>{billFrom.address}</Text>
          </View>

          <View style={styles.client}></View>

          <Text style={styles.label}>BILL TO</Text>

          <View>
            <Text style={styles.name}>{client.clientName}</Text>
          </View>

          <View style={styles.invoiceNumber}>
            <Text style={styles.label}>CLIENT ADDRESS</Text>
            <Text style={styles.input}>{client.clientAddress}</Text>
          </View>

          <ItemsTable items={items} total={total} />

        </View>
      </Page>
    </Document>
  );
};

export default InvoicePDF;

创建列出所有产品的项目表

src > 组件 > getPDF > ItemsTable.js

代码语言:javascript
复制
import React from 'react'
import { Text, View, StyleSheet } from '@react-pdf/renderer';

const styles = StyleSheet.create({
    row: {
        flexDirection: 'row',
        borderBottomWidth: 1,
        backgroundColor: '#D3D3D3',
        borderTopColor: 'black',
        borderTopWidth: 1,
        borderBottomColor: 'black',
        fontStyle: 'bold',
        alignItems: 'center',
        height: 22,
      },
      quantity: {
        width: '10%',
        borderRightWidth: 1,
        textAlign: 'right',
        borderRightColor: '#000000',
        paddingRight: 10,
      },
      description: {
          width: '60%',
          borderRightColor: '#000000',
          borderRightWidth: 1,
          textAlign: 'left',
          paddingLeft: 10,
      },
      price: {
        width: '15%',
        borderRightColor: '#000000',
        borderRightWidth: 1,
        textAlign: 'right',
        paddingRight: 10,
      },
      amount: {
        width: '15%',
        textAlign: 'right',
        paddingRight: 10,
      },
      total: {
        display: 'flex',
        flexDirection: 'row',
        justifyContent: 'space-between',
        borderBottomColor: 'black',
        borderBottomWidth: 1,
      }
})

const ItemsTable = ({ items, total }) => { // destructuring props
  return (
    <View>
        <View style={styles.row}>
            <Text style={styles.description}>Item Description</Text>
            <Text style={styles.quantity}>Qty</Text>
            <Text style={styles.price}>Price</Text>
            <Text style={styles.amount}>Amount</Text>
        </View>

        { 
            items.map((item, index) => (
            <View key={index} style={styles.row}>
                <Text style={styles.description}>{item.name}</Text>
                <Text style={styles.quantity}>{item.quantity}</Text>
                <Text style={styles.price}>{item.price}</Text>
                <Text style={styles.amount}>$ {item.quantity * item.price}</Text>
            </View>
        ))}

        <View style={styles.total}>
            <Text>Total: </Text>
            <Text>$ {total()} </Text>
        </View>
    </View>

  )
}

export default ItemsTable;

更新了 App.js

src > App.js

代码语言:javascript
复制
import './App.css';
import InvoiceForm from './components/createInvoice/InvoiceForm';

function App() {
  return (
    <div className="App">
      <InvoiceForm />
    </div>
  );
}

export default App;

输出

单击“打印发票”后,将下载名为Invoice.pdf的 pdf 文档,其结构如下:

最终发票
最终发票

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 装置
    • 文件夹结构:
    • 创建发票表格
      • 存储用户信息
        • 存储客户信息
          • 对项目的创建/更新/删除操作
            • PDFDownloadLink 的道具
            • 在发票表单中添加样式
              • 发票表格用户界面
              • 根据发票数据生成 PDF 文档
              • 创建列出所有产品的项目表
                • 更新了 App.js
                • 输出
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档