在实际环境中测试
在生产中测试无水印。
随时随地为您服务。
欢迎阅读从 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 库,用于构建用户界面,尤其是单页面应用程序。您应该熟悉 JSX 等概念 (JavaScript XML)React 中的组件、状态和道具。
您还应该在计算机上设置一个开发环境,用于构建 React 应用程序。这包括文本编辑器或集成开发环境 (IDE).Visual Studio Code、Atom 或 Sublime Text 等文本编辑器都是不错的选择。
为了管理我们的项目及其依赖关系,我们将使用 Node.js 和 npm (节点软件包管理器).确保您的计算机上安装了 Node.js。Node.js 是一种 JavaScript 运行时,允许我们在服务器上运行 JavaScript。它已安装 npm,因此您可以管理项目所需的库。
您可以运行以下终端命令检查 Node.js 和 npm 是否已安装:
node -v
npm -v
这些命令将分别显示系统中安装的 Node.js 和 npm 版本。如果没有安装或版本已过期,则应下载并安装最新的长期支持版本。 (LTS) 版本的 Node.js 官方网站.
让我们从设置 React 项目开始。打开终端,导航到要创建项目的目录。运行以下命令创建一个新的 React 应用程序:
npx create-react-app pdf-from-react
此命令将创建一个名为 "pdf-from-react "的新目录,其中包含一个基本的 React 项目结构。
接下来,导航进入项目目录:
cd pdf-from-react
现在,我们可以在代码编辑器中打开该项目,并继续执行。
首先,我们需要安装必要的软件包。使用以下终端命令安装 react
、react-dom
、@mui/material
和 jspdf
.
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";
为了给应用程序添加一致的跨浏览器行为,我们使用 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,
},
}));
应用程序的主要组件是 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);
在这部分代码中,我们定义了处理用户交互的函数:更改项目详细信息、添加新项目和删除项目。当用户修改项目属性时,"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);
};
以下是 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);
};
在 "生成发票 "函数中,我们首先会对输入字段进行验证,以确保客户姓名、客户地址和项目详细信息都已填写。如果其中任何一个字段为空,我们会将 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",以清除之前的验证错误。
返回 "语句包含处理渲染过程的 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>
);
以下是完整的 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;
下面是 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
这将启动开发服务器,您可以在浏览器 http://localhost:3000
中查看应用程序。
在输入框中填写客户姓名、地址和项目详情,然后点击 "生成发票 "按钮。PDF 文件将下载到您的电脑,您可以打开它查看生成发票的整页视图。
点击 "生成发票 "按钮后,PDF 文件就会生成。
如果您尝试生成 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");
})();
有关 PDF 相关任务的更多代码示例,请访问此处 代码示例 page.
总之,在 React 应用程序中创建 PDF 并不可怕。有了正确的工具和清晰的理解,您就可以毫不费力地生成精美、结构良好的 PDF 文档。我们已经探索了 jsPDF、pdfmake 和 React-PDF 等各种库,每个库都有自己的优势和独特功能。
有了 IronPDF 对 JavaScript 框架和库的直接集成过程,优秀的 文献资料在 Node.js 应用程序中生成专业级 PDF 的最佳选择。
IronPDF 提供 免费试用 以测试其完整功能。它还可用于其他语言,如 C# .NET, Java 和 Python.访问 IronPDF 更多详情,请访问网站。