在生產環境中測試,無水印。
在任何需要的地方都能運行。
獲得 30 天的全功能產品。
在幾分鐘內上手運行。
試用產品期間完全訪問我們的支援工程團隊
歡迎來到從 React 應用程式創建 PDF 文件的教程! 在本教程中,我們將探討各種生成 PDF 的庫,並學習如何使用流行的 jsPDF 庫直接從你的 React 組件創建 PDF 文件。 那麼,讓我們深入探索並開始吧!
PDF(可攜式文件格式)是一種廣泛使用的文件格式,用於共享和打印文件,同時保持其版面和格式。 作為一名 React 網頁開發者,您可能會遇到需要直接從您的 React 應用程式生成 PDF 文檔的情況,例如發票、報告或銷售合同。
在 React 應用程式中建立 PDF 文件可能是一項艱鉅的任務,尤其是對於不熟悉此領域的新手來說。 幸運的是,我們有多個第三方庫可用,這大大簡化了這個過程。 每個函式庫都有其獨特的功能和工具,可以滿足不同的使用案例。 讓我們更詳細地探討這些庫。
jsPDF 是開發人員中非常受歡迎的庫,用於從 JavaScript 生成 PDF 文件。 其主要賣點之一是其簡單性。 其語法和使用方式相當簡單,可以讓您在短時間內將 HTML 內容轉換為 PDF 文件。
它使您能夠控制 PDF 的格式和佈局,包括更改字體大小和顏色,以及調整頁面方向和大小。jsPDF 是一個功能強大的解決方案,可以在瀏覽器和伺服器環境中工作,這使其成為各種 JavaScript 應用程序的絕佳選擇。
pdfmake 作為純 JavaScript 的客戶端/服務器端 PDF 打印解決方案而脫穎而出。 由於其完整的 API 和靈活的佈局選項,此函式庫是用於創建更複雜 PDF 的絕佳選擇。 使用 pdfmake,您可以使用簡單的 JavaScript 物件來定義您的文件內容和結構,然後將其轉換成有效的 PDF 文件。
React-PDF 是一個獨特的程式庫,提供強大的功能以使用 React 組件來創建 PDF 文件。 您可以像構建典型的 React 應用程式一樣,使用可重複使用的組件和參數來創建 PDF,而不是手動在 JavaScript 對象中編寫文件結構。 IronPDF 網站上有一個使用 React-PDF 庫創建 PDF 的教程。
雖然這三個函式庫都提供了強大的工具來在 React 中生成 PDF 文件,但由於 jsPDF 的簡單性、靈活性和在社群中的廣泛採用,我們將在本教程中使用 jsPDF。 它為初學者提供較低的入門門檻,其強大的功能集使其成為許多應用場景的合適選擇。 我們將與 jsPDF 探索的原則會為您提供生成 PDF 的堅實基礎,如果您的專案需要,您將能夠更輕鬆地掌握其他程式庫。
在開始這個教程之前,確保您具備足夠的工具和知識,以便順利跟上進度是很重要的。 此教學的先決條件如下:
首先,您應該對 React 有基本的了解,這是一個用於構建使用者界面(特別是單頁應用)的流行 JavaScript 庫。 您應該熟悉 React 中的概念,例如 JSX(JavaScript XML)、組件、狀態和 props。
您還應該在您的計算機上設置開發環境以構建 React 應用程序。 這包括文字編輯器或整合開發環境 (IDE)。 像 Visual Studio Code、Atom 或 Sublime Text 這樣的文字編輯器都是不錯的選擇。
為了管理我們的項目及其依賴項,我們將使用 Node.js 和 npm (Node Package Manager)。 確保您的電腦上已安裝 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
函數在使用者修改項目時更新該項目的屬性。 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);
};
以下是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("p", "pt")
創建一個新的jsPDF
實例。 第一個參數「p
」指定頁面方向為縱向,第二個參數「pt
」指定測量單位為點。
接著,我們開始將內容新增到 PDF 文件中。 我們使用 doc.setFontSize
設定字體大小,並使用 doc.text
方法在頁面上的特定座標添加文字。 我們添加發票抬頭,包括標題、發票號碼、日期、客戶名稱和客戶地址。
在標題之後,我們透過設定字型大小並使用doc.line
添加一條線來添加 "Items" 區段,以將項目與標題分開。 接下來,我們遍歷 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>
);
以下是完整的 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.js 是一個全面的 Node.js PDF 庫,具有卓越的精確性、易用性和速度。 它提供了廣泛的功能,可以直接從 HTML、URL 和 React 中的圖像生成、編輯和格式化 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");
})();
有關更多與 PDF 相關任務的程式碼範例,請造訪此IronPDF 程式碼範例頁面。
總結來說,在 React 應用中創建 PDF 不必令人畏懼。 使用合適的工具和清晰的理解,您可以輕鬆生成漂亮且結構良好的 PDF 文件。 我們已經探索了各種函式庫,例如 jsPDF、pdfmake 和 React-PDF,每個都有其自身的優勢和獨特功能。
IronPDF 提供簡單的 JavaScript 框架和庫整合過程、優秀的文件,以及快速回應的技術支援,讓開發者可以迅速上手,成為在 Node.js 應用程式中生成專業級 PDF 的首選。
IronPDF 提供完整功能的免費試用。 它也適用於其他語言,如 C# .NET、Java 和 Python。 訪問IronPDF網站以獲取更多詳細信息。