在实际环境中测试
在生产中测试无水印。
随时随地为您服务。
欢迎阅读从 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 "函数会更新这些属性。 添加项目 "函数向列表中添加一个新项目。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);
};
以下是 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);
};
在 "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",以清除之前的验证错误。
返回 "语句包含处理渲染过程的 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 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");
})();
有关 PDF 相关任务的更多代码示例,请访问此处IronPDF 代码示例页面.
总之,在 React 应用程序中创建 PDF 并不可怕。 有了正确的工具和清晰的理解,您就可以毫不费力地生成精美、结构合理的 PDF 文档。 我们研究了 jsPDF、pdfmake 和 React-PDF 等各种库,每个库都有自己的优势和独特功能。
借助 IronPdf 对 JavaScript 中框架和库的直接集成过程,优秀的文献资料在 Node.js 应用程序中生成专业级 PDF 的最佳选择。
IronPDF 提供一个免费试用其全部功能. 也可用于其他语言,如C# .NET, Java和Python. 访问IronPDF 网站了解更多详情。