PDF 工具

如何在 React 中创建 PDF 文件

发布 2023年八月24日
分享:

欢迎阅读从 React 应用程序创建 PDF 文档的教程! 在本教程中,我们将探索生成 PDF 的各种库,并学习如何使用流行的 jsPDF 库直接从 React 组件创建 PDF 文件。那么,让我们开始吧!

PDF (便携式文档格式) 是一种广泛使用的文件格式,用于共享和打印文档,同时保留文档的布局和格式。作为 React Web 开发人员,您可能会遇到需要直接从 React 应用程序生成 PDF 文档(如发票、报告或销售合同)的情况。

选择 React PDF 库

在 React 应用程序中创建 PDF 文档可能是一项艰巨的任务,尤其是对于新手来说。值得庆幸的是,我们有几个第三方库可供我们使用,它们大大简化了这一过程。每个库都有自己独特的功能和实用工具,可满足不同的使用情况。让我们来详细了解一下这些库。

jsPDF

jsPDF 是一个广受开发人员欢迎的库,用于从 JavaScript 生成 PDF 文件。其主要卖点之一是简单易用。它的语法和用法都很简单明了,让您可以立即将 HTML 内容转换为 PDF 文件。

jsPDF 是一款强大的解决方案,可在浏览器和服务器环境中运行,是各种 JavaScript 应用程序的绝佳选择。

pdfmake

pdfmake 是一款纯 JavaScript 的客户端/服务器端 PDF 打印解决方案。该库具有全面的 API 和灵活的布局选项,是创建更复杂 PDF 的绝佳选择。使用 pdfmake,您可以使用简单的 JavaScript 对象定义文档内容和结构,然后将其转换为有效的 PDF 文档。

React-PDF

React-PDF 是一个独特的库,它为使用 React 组件创建 PDF 文件提供了强大的功能。您无需在 JavaScript 对象中手动编写文档结构,而是可以像创建典型的 React 应用程序一样使用可重复使用的组件和道具来创建 PDF。IronPDF 网站有 教程 使用 React-PDF 库创建 PDF。

为什么选择 jsPDF?

虽然这三个库都提供了在 React 中生成 PDF 文档的强大工具,但由于 jsPDF 的简单性、灵活性和在社区中的广泛采用,我们将在本教程中使用 jsPDF。它为初学者提供了较低的入门门槛,其强大的功能集使其成为许多用例的合适选择。我们将探索的 jsPDF 原理将为您生成 PDF 打下坚实的基础,如果您的项目需要,您还可以更轻松地使用其他库。

先决条件

在我们深入学习本教程之前,必须确保您已充分掌握必要的工具和知识,以便顺利地学习本教程。本教程的前提条件如下:

对 React 的基本了解

首先,您应该对 React 有一个基本的了解,React 是一个流行的 JavaScript 库,用于构建用户界面,尤其是单页面应用程序。您应该熟悉 JSX 等概念 (JavaScript XML)React 中的组件、状态和道具。

开发环境

您还应该在计算机上设置一个开发环境,用于构建 React 应用程序。这包括文本编辑器或集成开发环境 (IDE).Visual Studio Code、Atom 或 Sublime Text 等文本编辑器都是不错的选择。

Node.js 和 npm

为了管理我们的项目及其依赖关系,我们将使用 Node.js 和 npm (节点软件包管理器).确保您的计算机上安装了 Node.js。Node.js 是一种 JavaScript 运行时,允许我们在服务器上运行 JavaScript。它已安装 npm,因此您可以管理项目所需的库。

您可以运行以下终端命令检查 Node.js 和 npm 是否已安装:

node -v
npm -v

这些命令将分别显示系统中安装的 Node.js 和 npm 版本。如果没有安装或版本已过期,则应下载并安装最新的长期支持版本。 (LTS) 版本的 Node.js 官方网站.

步骤 1:设置项目

让我们从设置 React 项目开始。打开终端,导航到要创建项目的目录。运行以下命令创建一个新的 React 应用程序:

npx create-react-app pdf-from-react

如何在 React 中创建 PDF 文件?图 1 - 显示上述命令执行过程的终端截图。

此命令将创建一个名为 "pdf-from-react "的新目录,其中包含一个基本的 React 项目结构。

接下来,导航进入项目目录:

cd pdf-from-react

现在,我们可以在代码编辑器中打开该项目,并继续执行。

第 2 步:添加所需的依赖项

首先,我们需要安装必要的软件包。使用以下终端命令安装 reactreact-dom@mui/materialjspdf.

npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material

步骤 3:创建 PDF 生成功能

导入图书馆

我们首先要为应用程序导入必要的依赖库。其中包括 Material-UI 库中的各种组件、用于生成 PDF 的 jsPDF 库以及样式实用程序。

import React, { useState } from "react";
import "./App.css";
import {
  Button,
  TextField,
  Box,
  Container,
  Typography,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  IconButton,
  Snackbar,
  Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";
JAVASCRIPT

创建风格化组件

为了给应用程序添加一致的跨浏览器行为,我们使用 MUI 库中的 styled 工具创建了 StyledTableCellStyledTableRow

const StyledTableCell = styled(TableCell)(({ theme }) => ({
  [`&.${tableCellClasses.head}`]: {
    backgroundColor: theme.palette.common.black,
    color: theme.palette.common.white,
  },
  [`&.${tableCellClasses.body}`]: {
    fontSize: 14,
  },
}));

const StyledTableRow = styled(TableRow)(({ theme }) => ({
  "&:nth-of-type(odd)": {
    backgroundColor: theme.palette.action.hover,
  },
  "&:last-child td, &:last-child th": {
    border: 0,
  },
}));
JAVASCRIPT

创建应用程序组件

应用程序的主要组件是 App 组件。我们有四个状态变量:"customerName "和 "customerAddress "用于跟踪客户数据;"items "用于跟踪发票中的项目列表;"error "用于在必要时显示错误信息。

function App() {
  // State variables
  const [customerName, setCustomerName] = useState("");
  const [customerAddress, setCustomerAddress] = useState("");
  const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
  const [error, setError] = useState(false);
JAVASCRIPT

处理用户输入

在这部分代码中,我们定义了处理用户交互的函数:更改项目详细信息、添加新项目和删除项目。当用户修改项目属性时,"handleItemChange "函数会更新这些属性。添加项目 "函数会在列表中添加一个新项目。删除项目 "函数会从列表中删除一个项目。

const handleItemChange = (index, event) => {
  let newItems = [...items];
  newItems [index][event.target.name] = event.target.value;
  setItems(newItems);
};

const addItem = () => {
  setItems([...items, { name: "", quantity: "", price: "" }]);
};

const deleteItem = (index) => {
  let newItems = [...items];
  newItems.splice(index, 1);
  setItems(newItems);
};
JAVASCRIPT

生成发票

以下是 generateInvoice 功能代码:

// Generate invoice
const generateInvoice = () => {
  // Validate the input fields
  if (
    !customerName 

    !customerAddress 

    items.some((item) => !item.name 

 !item.quantity 

 !item.price)
  ) {
    setError(true);
    return;
  }

  // Create a new jsPDF instance
  let doc = new jsPDF("p", "pt");

  // Add invoice header
  doc.setFontSize(24);
  doc.text("Invoice", 40, 60);
  doc.setFontSize(10);
  doc.text("Invoice Number: 123456", 40, 90);
  doc.text("Date: " + new Date().toDateString(), 40, 110);
  doc.text(`Customer Name: ${customerName}`, 40, 130);
  doc.text(`Customer Address: ${customerAddress}`, 40, 150);

  // Add items section
  doc.setFontSize(14);
  doc.text("Items:", 40, 200);
  doc.line(40, 210, 550, 210);

  // Add item details
  doc.setFontSize(12);
  let yOffset = 240;
  let total = 0;

  items.forEach((item) => {
    let itemTotal = item.quantity * item.price;
    total += itemTotal;

    doc.text(`Item: ${item.name}`, 40, yOffset);
    doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
    doc.text(`Price: $${item.price}`, 300, yOffset);
    doc.text(`Total: $${itemTotal}`, 400, yOffset);

    yOffset += 20;
  });

  // Add total
  doc.line(40, yOffset, 550, yOffset);
  doc.setFontSize(14);
  doc.text(`Total: $${total}`, 400, yOffset + 30);

  // Save the generated PDF as "invoice.pdf"
  doc.save("invoice.pdf");

  // Reset error state
  setError(false);
};
JAVASCRIPT

在 "生成发票 "函数中,我们首先会对输入字段进行验证,以确保客户姓名、客户地址和项目详细信息都已填写。如果其中任何一个字段为空,我们会将 error 状态设置为 true,并提前返回。

接下来,我们通过调用 new jsPDF 来创建一个新的 jsPDF 实例。("p"、"pt").第一个参数"p"指定页面方向为纵向,第二个参数"pt`"指定测量单位为点。

然后,我们开始向 PDF 文档添加内容。我们使用 doc.setFontSize 设置字体大小,并使用 doc.text 方法在页面的特定坐标处添加文本。我们添加发票标题,包括标题、发票号码、日期、客户名称和客户地址。

在标题之后,我们通过设置字体大小和使用 doc.line 添加一行来添加 "项目 "部分,以便将项目与标题分开。接下来,我们遍历 items 数组中的每个项目,并通过数量乘以价格计算出每个项目的总价。我们用所有项目的总价之和更新 total 变量。

对于每个项目,我们使用 doc.text 将项目名称、数量、价格和项目总计添加到 PDF 文档中。我们递增 yOffset 变量,以移动到每个项目的下一行。最后,我们添加一行将项目与总金额分开,并使用 doc.text 在文档右下方添加总金额。

添加内容后,我们使用 `doc.save("invoice.pdf")将生成的 PDF 保存为用户计算机上的 "invoice.pdf"。最后,我们将 "error "状态重置为 "false",以清除之前的验证错误。

第 4 步:渲染用户界面

返回 "语句包含处理渲染过程的 JSX 代码。它包括客户姓名和地址的输入字段、用于输入项目详细信息的表格、用于添加项目和生成发票的按钮,以及用于显示验证错误的错误提示栏。

它使用 Material-UI 库中的组件(如 ButtonTextFieldBoxContainerTypographyTableTableBodyTableCellTableContainerTableHeadTableRowPaperIconButtonSnackbarAlert)创建基本组件。这些组件用于创建表格字段、表格、按钮和错误通知。

return (
  <Container maxWidth="md">
    <Box sx={{ my: 4 }}>
      <Typography variant="h3" component="h1" gutterBottom>
        Create Invoice
      </Typography>

      {/* Customer Name and Address fields */}
      <Grid container spacing={3}>
        <Grid item xs={6}>
          <TextField
            label="Customer Name"
            fullWidth
            margin="normal"
            value={customerName}
            onChange={(e) => setCustomerName(e.target.value)}
          />
        </Grid>
        <Grid item xs={6}>
          <TextField
            label="Customer Address"
            fullWidth
            margin="normal"
            value={customerAddress}
            onChange={(e) => setCustomerAddress(e.target.value)}
          />
        </Grid>
      </Grid>

      {/* Items table */}
      <TableContainer component={Paper}>
        <Table sx={{ minWidth: 700 }} aria-label="invoice table">
          <TableHead>
            <TableRow>
              <StyledTableCell>Item Name</StyledTableCell>
              <StyledTableCell align="left">Quantity</StyledTableCell>
              <StyledTableCell align="left">Price</StyledTableCell>
              <StyledTableCell align="left">Action</StyledTableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {items.map((item, index) => (
              <StyledTableRow key={index}>
                <StyledTableCell component="th" scope="row">
                  <TextField
                    fullWidth
                    value={item.name}
                    onChange={(event) => handleItemChange(index, event)}
                    name="name"
                  />
                </StyledTableCell>
                <StyledTableCell align="right">
                  <TextField
                    fullWidth
                    value={item.quantity}
                    onChange={(event) => handleItemChange(index, event)}
                    name="quantity"
                  />
                </StyledTableCell>
                <StyledTableCell align="right">
                  <TextField
                    fullWidth
                    value={item.price}
                    onChange={(event) => handleItemChange(index, event)}
                    name="price"
                  />
                </StyledTableCell>
                <StyledTableCell align="right">
                  <IconButton onClick={() => deleteItem(index)}>
                    <DeleteIcon />
                  </IconButton>
                </StyledTableCell>
              </StyledTableRow>
            ))}
          </TableBody>
        </Table>
      </TableContainer>

      {/* Buttons */}
      <Box mt={2} display="flex" gap={2}>
        <Button variant="contained" onClick={addItem}>
          Add Item
        </Button>
        <Button variant="outlined" color="success" onClick={generateInvoice}>
          Generate Invoice
        </Button>
      </Box>
    </Box>

    {/* Error Snackbar */}
    <Snackbar
      open={error}
      autoHideDuration={6000}
      onClose={() => setError(false)}
      anchorOrigin={{ vertical: "top", horizontal: "right" }}
    >
      <Alert onClose={() => setError(false)} severity="error">
        Please fill in all required fields.
      </Alert>
    </Snackbar>
  </Container>
);
JAVASCRIPT

完整的 App.js 和 App.css 代码

以下是完整的 App.js 代码,您可以将其复制并粘贴到您的项目中:

import React, { useState } from "react";
import "./App.css";
import {
  Button,
  TextField,
  Box,
  Container,
  Typography,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  IconButton,
  Snackbar,
  Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";

const StyledTableCell = styled(TableCell)(({ theme }) => ({
  [`&.${tableCellClasses.head}`]: {
    backgroundColor: theme.palette.common.black,
    color: theme.palette.common.white,
  },
  [`&.${tableCellClasses.body}`]: {
    fontSize: 14,
  },
}));

const StyledTableRow = styled(TableRow)(({ theme }) => ({
  "&:nth-of-type(odd)": {
    backgroundColor: theme.palette.action.hover,
  },
  "&:last-child td, &:last-child th": {
    border: 0,
  },
}));

function App() {
  // State variables
  const [customerName, setCustomerName] = useState("");
  const [customerAddress, setCustomerAddress] = useState("");
  const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
  const [error, setError] = useState(false);

  // Event handler for item changes
  const handleItemChange = (index, event) => {
    let newItems = [...items];
    newItems [index][event.target.name] = event.target.value;
    setItems(newItems);
  };

  // Add new item to the list
  const addItem = () => {
    setItems([...items, { name: "", quantity: "", price: "" }]);
  };

  // Delete an item from the list
  const deleteItem = (index) => {
    let newItems = [...items];
    newItems.splice(index, 1);
    setItems(newItems);
  };

  // Generate invoice
  const generateInvoice = () => {
    // Validate the input fields
    if (
      !customerName 

      !customerAddress 

      items.some((item) => !item.name 

 !item.quantity 

 !item.price)
    ) {
      setError(true);
      return;
    }

    // Create a new jsPDF instance
    let doc = new jsPDF("p", "pt");

    // Add invoice header
    doc.setFontSize(24);
    doc.text("Invoice", 40, 60);
    doc.setFontSize(10);
    doc.text("Invoice Number: 123456", 40, 90);
    doc.text("Date: " + new Date().toDateString(), 40, 110);
    doc.text(`Customer Name: ${customerName}`, 40, 130);
    doc.text(`Customer Address: ${customerAddress}`, 40, 150);

    // Add items section
    doc.setFontSize(14);
    doc.text("Items:", 40, 200);
    doc.line(40, 210, 550, 210);

    // Add item details
    doc.setFontSize(12);
    let yOffset = 240;
    let total = 0;

    items.forEach((item) => {
      let itemTotal = item.quantity * item.price;
      total += itemTotal;

      doc.text(`Item: ${item.name}`, 40, yOffset);
      doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
      doc.text(`Price: $${item.price}`, 300, yOffset);
      doc.text(`Total: $${itemTotal}`, 400, yOffset);

      yOffset += 20;
    });

    // Add total
    doc.line(40, yOffset, 550, yOffset);
    doc.setFontSize(14);
    doc.text(`Total: $${total}`, 400, yOffset + 30);

    // Save the generated PDF as "invoice.pdf"
    doc.save("invoice.pdf");

    // Reset error state
    setError(false);
  };

  return (
    <Container maxWidth="md">
      <Box sx={{ my: 4 }}>
        <Typography variant="h3" component="h1" gutterBottom>
          Create Invoice
        </Typography>

        {/* Customer Name and Address fields */}
        <Grid container spacing={3}>
          <Grid item xs={6}>
            <TextField
              label="Customer Name"
              fullWidth
              margin="normal"
              value={customerName}
              onChange={(e) => setCustomerName(e.target.value)}
            />
          </Grid>
          <Grid item xs={6}>
            <TextField
              label="Customer Address"
              fullWidth
              margin="normal"
              value={customerAddress}
              onChange={(e) => setCustomerAddress(e.target.value)}
            />
          </Grid>
        </Grid>

        {/* Items table */}
        <TableContainer component={Paper}>
          <Table sx={{ minWidth: 700 }} aria-label="invoice table">
            <TableHead>
              <TableRow>
                <StyledTableCell>Item Name</StyledTableCell>
                <StyledTableCell align="left">Quantity</StyledTableCell>
                <StyledTableCell align="left">Price</StyledTableCell>
                <StyledTableCell align="left">Action</StyledTableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {items.map((item, index) => (
                <StyledTableRow key={index}>
                  <StyledTableCell component="th" scope="row">
                    <TextField
                      fullWidth
                      value={item.name}
                      onChange={(event) => handleItemChange(index, event)}
                      name="name"
                    />
                  </StyledTableCell>
                  <StyledTableCell align="right">
                    <TextField
                      fullWidth
                      value={item.quantity}
                      onChange={(event) => handleItemChange(index, event)}
                      name="quantity"
                    />
                  </StyledTableCell>
                  <StyledTableCell align="right">
                    <TextField
                      fullWidth
                      value={item.price}
                      onChange={(event) => handleItemChange(index, event)}
                      name="price"
                    />
                  </StyledTableCell>
                  <StyledTableCell align="right">
                    <IconButton onClick={() => deleteItem(index)}>
                      <DeleteIcon />
                    </IconButton>
                  </StyledTableCell>
                </StyledTableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>

        {/* Buttons */}
        <Box mt={2} display="flex" gap={2}>
          <Button variant="contained" onClick={addItem}>
            Add Item
          </Button>
          <Button variant="outlined" color="success" onClick={generateInvoice}>
            Generate Invoice
          </Button>
        </Box>
      </Box>

      {/* Error Snackbar */}
      <Snackbar
        open={error}
        autoHideDuration={6000}
        onClose={() => setError(false)}
        anchorOrigin={{ vertical: "top", horizontal: "right" }}
      >
        <Alert onClose={() => setError(false)} severity="error">
          Please fill in all required fields.
        </Alert>
      </Snackbar>
    </Container>
  );
}
export default App;
JAVASCRIPT

下面是 App.css 代码:

@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: bold;
  /* This is the weight for bold in Poppins */
  color: #FF6347;
  /* This is the color Tomato. Replace with your preferred color */
}

body {
  font-family: 'Poppins', sans-serif;
  background-color: #E9F8F4;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

第 5 步:测试应用程序

要测试 PDF 生成功能,请在终端运行以下命令:

npm start

这将启动开发服务器,您可以在浏览器 http://localhost:3000 中查看应用程序。

如何在 React 中创建 PDF 文件 - 图 2:默认未填写字段的已完成发票应用程序。

在输入框中填写客户姓名、地址和项目详情,然后点击 "生成发票 "按钮。PDF 文件将下载到您的电脑,您可以打开它查看生成发票的整页视图。

如何在 React 中创建 PDF 文件 - 图 3:填写了三个细列项目的应用程序,项目、数量和价格各不相同。

点击 "生成发票 "按钮后,PDF 文件就会生成。

如何在 React 中创建 PDF 文件 - 图 4:生成的 PDF。

如果您尝试生成 PDF,但字段为空,则右上角会显示错误信息。

如何在 React 中创建 PDF 文件 - 图 5:由于未填写所有字段,因此会显示错误信息。

IronPDF - Node.js PDF 库

IronPDF 是一个全面的 Node.js PDF 库,在准确性、易用性和速度方面都非常出色。它提供了大量功能,可直接从 React 中的 HTML、URL 和图像生成、编辑和格式化 PDF。IronPDF 支持各种平台,包括 Windows、MacOS、Linux、Docker 以及 Azure 和 AWS 等云平台,确保了跨平台兼容性。其用户友好型 API 使开发人员能够快速将 PDF 生成和操作集成到他们的 Node.js 项目中。

IronPDF Node.js 的显著特点包括:完美的像素渲染、丰富的格式选项以及高级编辑功能,如合并和拆分 PDF、添加注释和创建 PDF 表单。

下面是一个从以下文件生成 PDF 文档的示例 HTML 文件、HTML 字符串和 网址:

import {PdfDocument} from "@ironsoftware/ironpdf";

(async () => {
    const pdfFromUrl = await PdfDocument.fromUrl("https://getbootstrap.com/");
    await pdfFromUrl.saveAs("website.pdf");

    const pdfFromHtmlFile = await PdfDocument.fromHtml("design.html");
    await pdfFromHtmlFile.saveAs("markup.pdf");

    const pdfFromHtmlString = await PdfDocument.fromHtml("<p>Hello World</p>");
    await pdfFromHtmlString.saveAs("markup_with_assets.pdf");
})();
JAVASCRIPT

有关 PDF 相关任务的更多代码示例,请访问此处 代码示例 page.

结论

总之,在 React 应用程序中创建 PDF 并不可怕。有了正确的工具和清晰的理解,您就可以毫不费力地生成精美、结构良好的 PDF 文档。我们已经探索了 jsPDF、pdfmake 和 React-PDF 等各种库,每个库都有自己的优势和独特功能。

有了 IronPDF 对 JavaScript 框架和库的直接集成过程,优秀的 文献资料在 Node.js 应用程序中生成专业级 PDF 的最佳选择。

IronPDF 提供 免费试用 以测试其完整功能。它还可用于其他语言,如 C# .NET, JavaPython.访问 IronPDF 更多详情,请访问网站。

< 前一页
JavaScript PDF编辑器 (开发者教程)
下一步 >
如何用JavaScript创建PDF文件

准备开始了吗? 版本: 2024.9 刚刚发布

免费 npm 安装 查看许可证 >