在生产环境中测试,无水印。
随时随地满足您的需求。
获得30天的全功能产品。
几分钟内就能启动并运行。
在您的产品试用期间,全面访问我们的支持工程团队。
欢迎来到从 React 应用程序创建 PDF 文档的教程! 在本教程中,我们将探讨生成 PDF 的各种库,并学习如何使用流行的 jsPDF 库直接从 React 组件创建 PDF 文件。 那么,让我们开始吧!
PDF(便携式文档格式)是一种广泛使用的文件格式,用于共享和打印文档,同时保持其布局和格式。 作为 React Web 开发人员,您可能会遇到需要直接从 React 应用程序生成 PDF 文档(如发票、报告或销售合同)的情况。
在 React 应用程序中创建 PDF 文档可能是一项艰巨的任务,尤其是对于新手而言。 值得庆幸的是,我们可以使用几个第三方库来大大简化这一过程。 每个库都有自己独特的功能和实用程序,可满足不同的使用情况。 让我们来详细了解一下这些库。
jsPDF 是一个广受开发人员欢迎的库,用于从 JavaScript 生成 PDF 文件。 其主要卖点之一是简单易懂。 它的语法和用法都很简单明了,可以让您立即将 HTML 内容转换为 PDF 文件。
jsPDF 是一个强大的解决方案,可在浏览器和服务器环境中运行,是各种 JavaScript 应用程序的绝佳选择。
pdfmake 是一个纯 JavaScript 的客户端/服务器端 PDF 打印解决方案。 该库具有全面的 API 和灵活的布局选项,是创建更复杂 PDF 的绝佳选择。 使用 pdfmake,您可以使用简单的 JavaScript 对象定义文档内容和结构,然后将其转换为有效的 PDF 文档。
React-PDF 是一个独特的库,为使用 React 组件创建 PDF 文件提供了强大的功能。 您无需在 JavaScript 对象中手动编写文档结构,而是可以像创建典型的 React 应用程序一样使用可重复使用的组件和道具来创建 PDF。 IronPDF 网站有一个使用 React-PDF 库创建 PDF 的教程。
虽然这三个库都提供了在 React 中生成 PDF 文档的强大工具,但由于 jsPDF 的简单性、灵活性和在社区中的广泛采用,我们将在本教程中使用 jsPDF。 它为初学者提供了较低的入门门槛,其强大的功能集使其成为许多使用案例的合适选择。 我们将探讨的 jsPDF 原理将为您生成 PDF 打下坚实的基础,如果您的项目需要,您还可以更轻松地使用其他库。
在我们深入学习本教程之前,有必要确保您已充分掌握必要的工具和知识,以便顺利地学习。 本教程的前提条件如下:
首先,您应该对 React 有基本的了解,React 是一种流行的 JavaScript 库,用于构建用户界面,尤其是单页面应用程序。 您应该熟悉 React 中的概念,如 JSX(JavaScript XML)、组件、状态和属性(props)。
您还应在计算机上设置一个用于构建 React 应用程序的开发环境。 这包括一个文本编辑器或集成开发环境 (IDE)。 Visual Studio Code、Atom 或 Sublime Text 等文本编辑器都是不错的选择。
为了管理我们的项目及其依赖性,我们将使用Node.js和npm(Node包管理器)。 确保您的计算机上安装了 Node.js。 Node.js 是一种 JavaScript 运行时,允许我们在服务器上运行 JavaScript。 它已安装 npm,因此您可以管理项目所需的库。
您可以通过运行以下终端命令来检查 Node.js 和 npm 是否已安装:
node -v
npm -v
node -v
npm -v
这些命令将分别显示您系统上安装的 Node.js 和 npm 版本。 如果您没有安装它们,或您的版本已过时,您应该从他们的官方下载页面下载并安装最新的长期支持 (LTS) 版本的 Node.js。
让我们从设置 React 项目开始。 打开终端,导航到您要创建项目的目录。 运行以下命令创建一个新的 React 应用程序:
npx create-react-app pdf-from-react
npx create-react-app pdf-from-react
此命令将创建一个名为pdf-from-react
的新目录,其中包含基本的React项目结构。
接下来,导航到项目目录:
cd pdf-from-react
cd pdf-from-react
现在,我们可以在代码编辑器中打开该项目,并继续执行。
首先,我们需要安装必要的软件包。 使用以下终端命令安装react
、react-dom
、@mui/material
和jspdf
。
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material
首先,我们要为应用程序导入必要的依赖项。 这些包括来自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";
jsx
为了在我们的应用中添加一致的跨浏览器行为,我们使用了 MUI 库中的styled
实用工具来创建StyledTableCell
和StyledTableRow
。
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,
},
}));
jsx
我们应用程序的主要组件是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);
jsx
在该代码块中,我们定义了处理用户交互的函数:更改项目详情、添加新项目和删除项目。 handleItemChange
函数在用户修改项目属性时更新项目的属性。 addItem
函数将新项目添加到列表中。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);
};
jsx
以下是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);
};
jsx
在generateInvoice
函数中,我们首先对输入字段进行验证,以确保填写了客户姓名、客户地址和项目详细信息。 如果这些字段中的任何一个为空,我们将error
状态设为true
并提前返回。
接下来,我们通过调用new jsPDF("p", "pt")
创建一个新的jsPDF
实例。 第一个参数“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
,以清除任何先前的验证错误。
return
语句包含处理渲染过程的JSX代码。 它包括客户姓名和地址的输入字段、用于输入项目详细信息的表格、用于添加项目和生成发票的按钮,以及用于显示验证错误的错误提示栏。
它使用来自Material-UI库的组件,例如Button
、TextField
、Box
、Container
、Typography
、Table
、TableBody
、TableCell
、TableContainer
、TableHead
、TableRow
、Paper
、IconButton
、Snackbar
和Alert
,以创建基本组件。 这些组件用于创建表单字段、表格、按钮和错误通知。
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>
);
jsx
以下是完整的 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;
jsx
这是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);
}
}
要测试 PDF 生成功能,请在终端运行以下命令:
npm start
npm start
这将启动开发服务器,您可以在浏览器中访问应用程序,网址为http://localhost:3000
。
在输入字段中填写客户姓名、地址和项目详情,然后单击 "生成发票 "按钮。 PDF 文件将下载到您的电脑,您可以打开它查看生成发票的全页视图。
点击 "生成发票 "按钮后,将生成 PDF 文件。
如果您尝试生成带有任何空字段的 PDF,右上角将显示错误信息。
IronPDF for Node.js 是一个全面的 Node.js PDF 库,以高精确性、易用性和速度见长。 它提供了大量功能,可直接从 React 中的 HTML、URL 和图像生成、编辑和格式化 PDF。 IronPDF 支持各种平台,包括 Windows、MacOS、Linux、Docker 以及 Azure 和 AWS 等云平台,可确保跨平台兼容性。 其用户友好的 API 使开发人员能够快速将 PDF 生成和操作集成到他们的 Node.js 项目中。
IronPDF Node.js 的显著特点包括:完美的像素渲染、丰富的格式选项以及高级编辑功能,如合并和拆分 PDF、添加注释和创建 PDF 表单。
以下是从HTML文件、HTML字符串和URL生成PDF文档的示例:
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");
})();
js
有关PDF相关任务的更多代码示例,请访问此IronPDF代码示例页面。
总之,在 React 应用程序中创建 PDF 并不可怕。 有了正确的工具和清晰的理解,您就可以毫不费力地生成精美、结构合理的 PDF 文档。 我们研究了 jsPDF、pdfmake 和 React-PDF 等各种库,每个库都有自己的优势和独特功能。
通过 IronPDF 简单的 JavaScript 框架和库集成过程、优秀的文档以及响应迅速的技术支持,开发人员可以迅速开始使用,使其成为在 Node.js 应用程序中生成专业级 PDF 的首选。
IronPDF 提供完整功能的免费试用。 它也适用于其他语言,如C# .NET、Java 和 Python。 请访问 IronPDF 网站 了解更多详情。