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 "函数会更新这些属性。 添加项目 "函数向列表中添加一个新项目。deleteItem "函数从列表中删除一个项目。

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

在 "generateInvoice"(生成发票)函数中,我们首先对输入字段进行验证,以确保客户姓名、客户地址和项目详细信息均已填写。 如果其中任何一个字段为空,我们就会将 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 for Node.jsNode.js PDF 是一个全面的 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 相关任务的更多代码示例,请访问此处IronPDF 代码示例页面.

结论

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

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

IronPDF 提供一个免费试用其全部功能. 也可用于其他语言,如C# .NET, JavaPython. 访问IronPDF 网站了解更多详情。

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

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

免费 npm 安装 查看许可证 >