跳至頁尾內容
PDF工具

如何在 React 中建立 PDF 文件

歡迎來到從 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文件的強大功能。 與其在 JavaScript物件中手動撰寫您的文件結構,您可以像構建典型 React應用程式一樣建立您的 PDF - 使用可重複使用的元件和 props。 IronPDF網站上有一個使用 React-PDF 程式庫建立 PDF的教程

為什麼選擇 jsPDF?

雖然這三個程式庫都提供強大的工具來在 React中生成 PDF文件,但我們將在本教程中由於其簡單性、靈活性和在社群的廣泛採用而使用jsPDF。 它降低了初學者的入門障礙,其強大的功能集使其成為許多使用場合的合適選擇。 我們透過jsPDF探索的原則將為您提供一個穩固的基礎來生成 PDF文件,如果您的專案需要,您也能更容易地學習其他程式庫。

先決條件

在我們進入本教程之前,確保您已充分具備必要的工具和知識以順利跟上是非常重要的。 本教程的先決條件如下:

對React的基本理解

首先,您應該對React有基本的理解,這是一個用於構建使用者介面,特別是單頁應用程式的流行JavaScript程式庫。 您應該熟悉React中的一些概念,比如JSX(JavaScript XML)、元件、狀態和props。

開發環境

您還應該在您的電腦上為構建 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

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

此命令將建立一個名為pdf-from-react的新目錄,具有基本的React專案結構。

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

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

現在,我們可以在我們的程式碼編輯器中打開專案並繼續實施。

步驟2:新增所需的依賴

首先,我們需要安裝必要的套件。 使用以下終端命令安裝react, react-dom, 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生成功能

匯入程式庫

我們首先為應用程式匯入必要的依賴項。 這些包括來自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程式庫的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元件。 我們有四個狀態變數: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函式更新使用者修改項目時的項目屬性。 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函式中,我們首先對輸入欄位進行驗證,以確保填寫了客戶名稱、客戶地址和項目詳細資訊。 如果其中任何一個欄位為空,我們將true並提前返回。

接下來,我們透過調用jsPDF實例。 第一個參數"pt""指定測量單位為點。

然後我們開始將內容新增到我們的PDF文件中。 我們使用doc.text方法在頁面的特定座標處新增文字。 我們新增發票標題,包括標題、發票號碼、日期、客戶名稱和客戶地址。

在標題之後,我們設置字體大小並使用doc.line新增一條線來將項目與標題分開,從而加入"項目" 部分。 接下來,我們迭代total變數。

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

新增內容後,我們使用doc.save("invoice.pdf")將生成的PDF儲存為使用者電腦上的"invoice.pdf"。 最後,我們將false,以清除任何先前的驗證錯誤。

步驟4:渲染UI

return語句包含處理渲染過程的JSX程式碼。 它包括客戶名稱和地址的輸入欄位,輸入項目詳細資訊的列表,新增項目和生成發票的按鈕,以及顯示驗證錯誤的錯誤通知。

它使用來自Material-UI程式庫的元件,例如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

如何在 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 等各種平台,IronPDF 確保跨平台相容性。 其使用者友好的 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文件。 我們已經探索了各種程式庫,例如React-PDF,每個都有其自身的優勢和獨特的功能。

憑藉IronPDF對JavaScript框架和程式庫的簡單整合過程、出色的文件資料以及快速回應的技術支援,開發人員可以立即上手,使其成為在Node.js應用程式中生成專業級PDF的首選。

IronPDF提供完整功能的免費試用。 它也可用於其他語言如C# .NETJavaPython。 請存取IronPDF網站以獲得更多詳細資訊。

Darrius Serrant
全端軟體工程師(WebOps)

Darrius Serrant擁有邁阿密大學的電腦科學學士學位,並在Iron Software擔任全端WebOps行銷工程師。從小就對程式設計有興趣,他認為計算既神秘又易於理解,成為創意和問題解決的完美媒介。

在Iron Software,Darrius喜歡創造新事物並簡化複雜的概念,使其更易於理解。作為我們的常駐開發人員之一,他還志願教學,將他的專業知識傳授給下一代。

對Darrius來說,他的工作是有意義的,因為它有價值且對社會有真正的影響。

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話