PDF工具 如何在 React 中創建 PDF 文件 Darrius Serrant 更新日期:7月 28, 2025 Download IronPDF npm 下載 Start Free Trial Copy for LLMs Copy for LLMs Copy page as Markdown for LLMs Open in ChatGPT Ask ChatGPT about this page Open in Gemini Ask Gemini about this page Open in Grok Ask Grok about this page Open in Perplexity Ask Perplexity about this page Share Share on Facebook Share on X (Twitter) Share on LinkedIn Copy URL Email article 歡迎來到從React應用程式創建PDF文件的教程! 在本教程中,我們將探索各種生成PDF的庫並學習如何使用流行的jsPDF庫直接從您的React組件創建PDF文件。 那麼,讓我們開始吧! PDF(可攜式文檔格式)是一種廣泛使用的文件格式,用於共享和打印文檔,同時保留其佈局和格式。 作為React網頁開發人員,您可能會遇到需要生成PDF文檔的場景,例如直接從您的React應用程式生成發票、報告或銷售合同等。 選擇React PDF庫 在React應用程序中創建PDF文檔可能是一項艱巨的任務,特別是如果您對這個領域還不熟悉。 幸運的是,我們有幾個第三方庫可供使用,這些庫大大簡化了此過程。 每個庫都有其獨特的功能和實用程序,滿足不同的使用需求。 讓我們更詳細地探索這些庫。 jsPDF jsPDF是開發人員中非常受歡迎的一個庫,用於從JavaScript生成PDF文件。 其主要賣點之一就是它的簡單性。 其語法和使用都非常簡單,使您能夠快速將HTML內容轉換為PDF文件。 它允許您控制PDF的格式和佈局,從更改字體大小和顏色到調整頁面方向和大小。jsPDF是一個強大的解決方案,可以在瀏覽器和服務器環境中工作,使其成為廣泛JavaScript應用程式的理想選擇。 pdfmake pdfmake是純JavaScript中一個顯著的客戶端/服務器端PDF打印解決方案。 由於其全面的API和靈活的佈局選項,這個庫是創建更復雜PDF的絕佳選擇。 使用pdfmake,您可以使用一個簡單的JavaScript對象來定義您的文檔內容和結構,然後將其轉換為有效的PDF文檔。 React-PDF React-PDF是一個獨特的庫,提供了使用React組件創建PDF文件的強大功能。 您可以像構建典型的React應用程式一樣,使用可重用的組件和屬性來創建PDF,而不是手動在JavaScript對象中編寫您的文檔結構。 IronPDF網站提供了使用React-PDF庫創建PDF的教程。 為什麼選擇jsPDF? 雖然所有三個庫都提供了強大的工具來在React中生成PDF文檔,但在本教程中我們將使用jsPDF,因為它的簡單性、靈活性和在社區中的廣泛採用。 它為初學者提供了較低的入門門檻,其強大的功能集使其成為許多使用案例的合適選擇。 我們將使用jsPDF探索的原則將為您提供生成PDF的堅實基礎,如果您的項目需要,您將可以更輕鬆地採用其他庫。 先決條件 在進入本教程之前,確保您擁有必要的工具和知識,以便順利進行。 本教程的先決條件如下: 基礎React知識 首先,您應該對React有基本了解,這是一個用於構建用戶界面的流行JavaScript庫,尤其是單頁應用。 您應該熟悉React中的JSX(JavaScript XML)、組件、狀態和屬性等概念。 開發環境 您還應該在電腦上設置一個用於構建React應用程序的開發環境。 這包括文本編輯器或集成開發環境(IDE)。 諸如Visual Studio Code、Atom或Sublime Text之類的文本編輯器都是不錯的選擇。 Node.js和npm 為了管理我們的項目及其依賴項,我們將使用Node.js和npm(Node Package Manager)。 確保您已在電腦上安裝Node.js。 Node.js是一個JavaScript運行時,使我們能夠在服務器上運行JavaScript。 它自帶npm安裝,您可以用於管理項目所需的庫。 您可以通過運行以下終端命令來檢查Node.js和npm是否已安裝: node -v npm -v node -v npm -v SHELL 這些命令將顯示您的系統上安裝的Node.js和npm版本。 如果您未安裝它們或您的版本已過時,您應該從他們的官方下載頁面下載並安裝最新的長期支持(LTS)版本的Node.js。 步驟1:設置項目 讓我們從設置React項目開始。 打開終端並導航到您希望創建項目的目錄。 運行以下命令來創建新的React應用程序: npx create-react-app pdf-from-react npx create-react-app pdf-from-react SHELL 此命令將創建一個名為pdf-from-react的新目錄,具有基本的React項目結構。 接下來,導航到項目目錄中: cd pdf-from-react cd pdf-from-react SHELL 現在,我們可以在代碼編輯器中打開項目並繼續實施。 步驟2:添加必需的依賴項 首先,我們需要安裝必要的包。 使用以下終端命令安裝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 SHELL 步驟3:構建PDF生成功能 導入庫 我們首先導入應用程式所需的依賴項。 這些包括MUI庫中的各種組件、用於生成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"; 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"; JAVASCRIPT 創建樣式化組件 為了為我們的應用添加一致的跨瀏覽器行為,我們使用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, }, })); 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, }, })); JAVASCRIPT 創建App組件 我們應用的主要組件是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); function App() { // State variables const [customerName, setCustomerName] = useState(""); const [customerAddress, setCustomerAddress] = useState(""); const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]); const [error, setError] = useState(false); JAVASCRIPT 處理用戶輸入 在這段代碼中,我們定義了處理用戶交互的函數:更改項目詳細信息、添加新項目和刪除項目。 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); }; 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); }; JAVASCRIPT 生成發票 以下是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); }; // 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); }; JAVASCRIPT 在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以清除任何先前的驗證錯誤。 步驟4:渲染UI 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> ); 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> ); JAVASCRIPT 完整的App.js和App.css代碼 以下是完整的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; 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; JAVASCRIPT 這是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); } } 步驟5:測試應用程序 要測試PDF生成功能,請在終端中運行以下命令: npm start npm start SHELL 這將啟動開發服務器,您可以在瀏覽器中查看應用程式,網址為http://localhost:3000。 在輸入字段中填寫客戶名稱、地址和項目詳細信息,然後點擊"生成發票"按鈕。 PDF文件將下載到您的計算機,您可以打開它以查看生成的發票的全頁視圖。 當您點擊"生成發票"按鈕時,將會生成PDF文件。 如果您嘗試在有空字段的情況下生成PDF,右上角會顯示錯誤消息。 IronPDF - Node.js PDF庫 IronPDF for Node.js是一個全面的Node.js PDF庫,在準確性、易用性和速度方面表現卓越。 它提供了生成、編輯和格式化PDF的廣泛功能,直接從HTML,URL和React中的圖像生成。 支持各種平台包括 Windows、MacOS、Linux、Docker 和雲平台,如 Azure 和 AWS,IronPDF 確保了跨平台兼容性。 其用戶友好的 API 讓開發者能快速將 PDF 生成和操作集成到其 Node.js 專案中。 IronPDF Node.js的顯著特點包括:像素完美渲染、大量的格式選項以及高級編輯功能,例如合併和拆分PDF、添加註釋和創建PDF表單。 Here is an example of generating a PDF document from an HTML File, HTML String, and URL: 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"); })(); 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"); })(); JAVASCRIPT 有關PDF相關任務的更多代碼示例,請訪問此IronPDF代碼示例頁。 結論 總之,在React應用程序中創建PDF不必令人畏懼。 有了正確的工具和清晰的理解,您可以輕鬆生成美觀、結構良好的PDF文檔。 我們探討了各種庫,如jsPDF、pdfmake和React-PDF,每個庫都有其自身的優勢和特色。 使用IronPDF的JavaScript框架和庫的簡單集成過程、卓越的文檔以及響應迅速的技術支持,開發人員能夠迅速上手,這使其成為在Node.js應用中生成專業級PDF的首選。 IronPDF提供了完整功能的免費試用版。 It is also available for other languages like C# .NET, Java, and Python. 訪問 IronPDF 網站了解更多詳情。 Darrius Serrant 立即與工程團隊聊天 全棧軟件工程師 (WebOps) Darrius Serrant 擁有邁阿密大學計算機科學學士學位,目前任職於 Iron Software 的全栈 WebOps 市場營銷工程師。從小就迷上編碼,他認為計算既神秘又可接近,是創意和解決問題的完美媒介。在 Iron Software,Darrius 喜歡創造新事物,並簡化複雜概念以便於理解。作為我們的駐場開發者之一,他也自願教學生,分享他的專業知識給下一代。對 Darrius 來說,工作令人滿意因為它被重視且有實際影響。 相關文章 更新日期 7月 28, 2025 JavaScript PDF 編輯器(開發者教程) 在這個深度指南中,我們開始探索如何利用 IronPDF 所提供的強大功能來構建 JavaScript PDF 編輯器的複雜性。 閱讀更多 更新日期 7月 28, 2025 如何在 JavaScript 中創建 PDF 文件 IronPDF 的創建旨在使開發人員更容易創建、瀏覽和編輯 PDF 文檔。它作為一種強大的 PDF 轉換器,並提供基本的 API 用於創建、編輯和處理 PDF 文件。 閱讀更多 更新日期 7月 28, 2025 如何在 JavaScript 中查看 PDF 文件 IronPDF 能從各種來源生成 PDF 文件,包括 HTML、HTML5、ASPX 和 Razor/MVC 視圖。它能將 HTML 頁面和圖像轉換為 PDF 文件。 閱讀更多 JavaScript PDF 編輯器(開發者教程)如何在 JavaScript 中創建 PDF ...
更新日期 7月 28, 2025 JavaScript PDF 編輯器(開發者教程) 在這個深度指南中,我們開始探索如何利用 IronPDF 所提供的強大功能來構建 JavaScript PDF 編輯器的複雜性。 閱讀更多
更新日期 7月 28, 2025 如何在 JavaScript 中創建 PDF 文件 IronPDF 的創建旨在使開發人員更容易創建、瀏覽和編輯 PDF 文檔。它作為一種強大的 PDF 轉換器,並提供基本的 API 用於創建、編輯和處理 PDF 文件。 閱讀更多
更新日期 7月 28, 2025 如何在 JavaScript 中查看 PDF 文件 IronPDF 能從各種來源生成 PDF 文件,包括 HTML、HTML5、ASPX 和 Razor/MVC 視圖。它能將 HTML 頁面和圖像轉換為 PDF 文件。 閱讀更多