在實際環境中測試
在生產環境中測試無浮水印。
在任何需要的地方都能運作。
歡迎來到從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 文件。您不必手動在 JavaScript 對象中編寫文檔結構,可以像構建典型的 React 應用程序一樣使用可重用的組件和屬性來創建您的 PDF。IronPDF 網站擁有 教程 使用 React-PDF 庫創建 PDF。
雖然這三個庫都提供了生成 React PDF 文檔的強大工具,但在這個教程中我們將使用 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
函數在用戶修改項目時更新其屬性。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
創建一個新的 jsPDF
實例。("p", "pt"). 第一個參數 "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 是一個全面的 Node.js PDF 函式庫,擅長於準確性、易用性和速度。它提供了廣泛的功能,可以直接從 HTML、網址和 React 圖片生成、編輯和格式化 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 任務的更多程式碼範例,請訪問此 程式碼範例 頁面。
總的來說,在 React 應用中生成 PDF 不必令人望而生畏。使用正確的工具和清晰的理解,您可以輕鬆生成美觀且結構良好的 PDF 檔。我們探討了各種庫,例如 jsPDF、pdfmake 和 React-PDF,它們各自具有其優勢和獨特功能。
借助 IronPDF 在 JavaScript 框架和庫中的簡便整合過程,優秀 文檔,並且提供即時的技術支援,開發者能夠快速上手,使其成為在 Node.js 應用程式中生成專業級 PDF 的首選。
IronPDF 提供了 免費試用 用於測試其完整功能。它也適用於其他語言,如 C# .NET, Java 和 Python。造訪 IronPDF 網站以獲取更多詳細資訊。