跳過到頁腳內容
PDF工具

如何在 React 中創建 PDF 文件

歡迎來到從 React 應用程式創建 PDF 文件的教學! 在本教學中,我們將探索多種程式庫來生成 PDF,並學習如何使用受歡迎的 jsPDF 程式庫直接從您的 React 組件創建 PDF 文件。 那麼,讓我們深入學習並開始吧!

PDF(可攜式文檔格式)是一種廣泛使用的文件格式,用於共享和打印文件,同時保留其佈局和格式。 作為一名 React 網頁開發人員,您可能會遇到需要從您的 React 應用程式中直接生成 PDF 文件的情況,例如發票、報告或銷售合同。

選擇 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 文件的強大功能。 您無需手動在 JavaScript 對象中編寫文檔結構,可以像構建典型的 React 應用程式一樣使用可重用的組件和屬性來創建您的 PDF。 IronPDF 網站有關於使用 React-PDF 程式庫創建 PDF 的教學

為什麼選擇 jsPDF?

儘管這三個程式庫都提供強大的工具來在 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 和 npm(Node 包管理器)。 確保您已在您的計算機上安裝了 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

 如何在 React 中創建 PDF 文件:圖 1 - 終端截圖顯示上述命令的執行過程。

這個命令將創建一個名為 pdf-from-react 的新目錄,其中包含基本的 React 項目結構。

接下來,導航到專案目錄:

cd pdf-from-react
cd pdf-from-react
SHELL

現在,我們可以在代碼編輯器中打開專案並開始實現了。

步驟 2:添加必要的依賴

首先,我們需要安裝必要的套件。 使用以下終端命令安裝 @mui/materialjspdf

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 生成功能

匯入程式庫

我們首先進行了應用程式所需依賴項的匯入。 這包括來自 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";
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 工具來創建 StyledTableCellStyledTableRow

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 組件。 我們有四個狀態變量:customerNamecustomerAddress 用於跟踪客戶的數據,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 陣列中的每個項目,通過將數量乘以價格來計算每個項目的總價。我們將所有項目總價的和更新到 total 變量中。

對於每一個項目,我們使用 doc.text 將項目名稱、數量、價格以及項目總價添加到 PDF 文件中。 我們遞增 yOffset 變量,以便每個項目移動到下一行。 最後,我們添加一條線以分隔項目與總計,並使用 doc.text 在文件的右下角添加總金額。

一旦內容添加完畢,我們使用 doc.save("invoice.pdf") 將生成的 PDF 保存為用戶計算機上的"invoice.pdf"。 最後,我們將 error 狀態重置為 false 以清除任何先前的驗證錯誤。

步驟 4:渲染用戶介面

return 語句包含處理渲染過程的 JSX 代碼。 它包含客戶姓名和地址的輸入欄位、輸入項目詳細信息的表格、添加項目和生成發票的按鈕以及顯示驗證錯誤的錯誤 Snackbar。

它使用 Material-UI 程式庫中的組件,例如 SnackbarAlert 來創建基本組件。 這些組件用於創建表單欄位、表格、按鈕和錯誤通知。

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

 如何在 React 中創建 PDF 文件 - 圖 2:帶有默認值和未填寫字段的完整發票應用程式。

在輸入欄位中填入客戶名稱、地址和項目詳細信息,然後點擊"生成發票"按鈕。 PDF 文件將被下載到您的計算機上,您可以將其打開以查看生成發票的完整頁面視圖。

 如何在 React 中創建 PDF 文件 - 圖 3:應用程式填寫有三行項目,具有不同的項目、數量和價格。

當您點擊"生成發票"按鈕時,將生成 PDF 文件。

 如何在 React 中創建 PDF 文件 - 圖 4:生成的 PDF。

如果您嘗試生成任意一個空字段的 PDF,在右上方將顯示錯誤消息。

 如何在 React 中創建 PDF 文件 - 圖 5:由於並非所有字段均已填寫,顯示了一條錯誤消息。

IronPDF - Node.js PDF 程式庫

IronPDF for Node.js 是一個全方位的 Node.js PDF 程式庫,在準確性、易用性和速度方面表現出色。 它提供了豐富的功能來直接從HTML、URL和React中的圖像生成、編輯和格式化PDF。 支持包括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");
})();
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 文檔。 我們探索了如 pdfmakeReact-PDF 等多種程式庫,每個都有其自身的優勢和獨特功能。

透過 IronPDF 針對 JavaScript 框架和程式庫的簡化整合流程,優秀的文件以及及時的技術支援,開發人員可以快速上手,使其成為在 Node.js 應用程式中生成專業級 PDF 的首選。

IronPDF 提供其全部功能的免費試用。 它也適用於其他語言,如C# .NET, JavaPython。 欲知詳情,請訪問 IronPDF 網站

Darrius Serrant
全棧軟件工程師 (WebOps)

Darrius Serrant 擁有邁阿密大學計算機科學學士學位,目前任職於 Iron Software 的全栈 WebOps 市場營銷工程師。從小就迷上編碼,他認為計算既神秘又可接近,是創意和解決問題的完美媒介。

在 Iron Software,Darrius 喜歡創造新事物,並簡化複雜概念以便於理解。作為我們的駐場開發者之一,他也自願教學生,分享他的專業知識給下一代。

對 Darrius 來說,工作令人滿意因為它被重視且有實際影響。

Iron Support Team

We're online 24 hours, 5 days a week.
Chat
Email
Call Me