Przejdź do treści stopki
NARZęDZIA PDF

Jak stworzyć plik PDF w React

Witamy w samouczku dotyczącym tworzenia dokumentów PDF z aplikacji React! W tym samouczku zapoznamy się z różnymi bibliotekami służącymi do generowania plików PDF i nauczymy się, jak korzystać z popularnej biblioteki jsPDF do tworzenia plików PDF bezpośrednio z komponentów React. Zatem zabierzmy się do pracy!

PDF (Portable Document Format) to powszechnie stosowany format plików służący do udostępniania i drukowania dokumentów z zachowaniem ich układu i formatowania. Jako programista React możesz spotkać się z sytuacjami, w których musisz generować dokumenty PDF, takie jak faktury, raporty lub umowy sprzedaży, bezpośrednio z aplikacji React.

Wybór biblioteki React PDF

Tworzenie dokumentów PDF w aplikacji React może być trudnym zadaniem, zwłaszcza jeśli dopiero zaczynasz przygodę z tym środowiskiem. Na szczęście mamy do dyspozycji kilka bibliotek innych firm, które znacznie upraszczają ten proces. Każda biblioteka posiada swoje unikalne funkcje i narzędzia, dostosowane do różnych zastosowań. Przyjrzyjmy się tym bibliotekom nieco bardziej szczegółowo.

jsPDF

jsPDF to bardzo popularna wśród programistów biblioteka służąca do generowania plików PDF z JavaScript. Jedną z jego głównych zalet jest prostota. Jego składnia i sposób użycia są dość proste, co pozwala błyskawicznie przekształcić treść HTML w plik PDF.

Umożliwia kontrolowanie formatowania i układu plików PDF, od zmiany rozmiaru i koloru czcionki po dostosowanie orientacji i rozmiaru strony. jsPDF to solidne rozwiązanie, które działa zarówno w środowisku przeglądarki, jak i serwera, co czyni je doskonałym wyborem dla szerokiej gamy aplikacji JavaScript.

pdfmake

pdfmake wyróżnia się jako rozwiązanie do drukowania plików PDF po stronie klienta/serwera w czystym JavaScript. Ta biblioteka jest doskonałym wyborem do tworzenia bardziej złożonych plików PDF dzięki kompleksowemu API i elastycznym opcjom układu. Dzięki pdfmake możesz zdefiniować zawartość i strukturę dokumentu za pomocą prostego obiektu JavaScript, a następnie przekształcić go w poprawny dokument PDF.

React-PDF

React-PDF to wyjątkowa biblioteka, która zapewnia zaawansowane funkcje do tworzenia plików PDF przy użyciu komponentów React. Zamiast ręcznie pisać strukturę dokumentu w obiekcie JavaScript, możesz stworzyć plik PDF tak samo, jak budujesz typową aplikację React — używając komponentów wielokrotnego użytku i właściwości. Na stronie internetowej IronPDF znajduje się samouczek dotyczący tworzenia plików PDF przy użyciu biblioteki React-PDF.

Dlaczego warto wybrać jsPDF?

Chociaż wszystkie trzy biblioteki zapewniają potężne narzędzia do generowania dokumentów PDF w React, w tym samouczku użyjemy jsPDF ze względu na jego prostotę, elastyczność i powszechne stosowanie w społeczności. Zapewnia to niższy próg wejścia dla początkujących, a bogaty zestaw funkcji sprawia, że jest to odpowiedni wybór dla wielu zastosowań. Zasady, które omówimy w jsPDF, zapewnią Ci solidne podstawy do generowania plików PDF, a jeśli Twój projekt tego wymaga, łatwiej będzie Ci opanować inne biblioteki.

Wymagania wstępne

Zanim przejdziemy do tego samouczka, upewnij się, że masz odpowiednie narzędzia i wiedzę, aby bez przeszkód nadążyć za treścią. Wymagania wstępne dla tego samouczka są następujące:

Podstawowa znajomość React

Przede wszystkim powinieneś posiadać podstawową wiedzę na temat React, popularnej biblioteki JavaScript służącej do tworzenia interfejsów użytkownika, zwłaszcza aplikacji jednostronicowych. Powinieneś znać takie pojęcia jak JSX (JavaScript XML), komponenty, stan i właściwości w React.

Środowisko programistyczne

Powinieneś również mieć na swoim komputerze skonfigurowane środowisko programistyczne do tworzenia aplikacji React. Obejmuje to edytor tekstu lub zintegrowane środowisko programistyczne (IDE). Dobre opcje to edytory tekstu, takie jak Visual Studio Code, Atom lub Sublime Text.

Node.js i npm

Do zarządzania naszym projektem i jego zależnościami będziemy używać Node.js oraz npm (Node Package Manager). Upewnij się, że masz zainstalowany Node.js na swoim komputerze. Node.js to środowisko uruchomieniowe JavaScript, które pozwala nam uruchamiać JavaScript na naszych serwerach. Jest dostarczany z zainstalowanym npm, dzięki czemu można zarządzać bibliotekami wymaganymi do realizacji projektu.

Możesz sprawdzić, czy Node.js i npm są zainstalowane, uruchamiając następujące polecenia w terminalu:

node -v
npm -v
node -v
npm -v
SHELL

Te polecenia wyświetlą odpowiednio wersję Node.js i npm zainstalowaną w systemie. Jeśli nie masz ich zainstalowanych lub jeśli Twoje wersje są nieaktualne, powinieneś pobrać i zainstalować najnowszą wersję Node.js z długoterminowym wsparciem (LTS) z oficjalnej strony pobierania.

Krok 1: Konfiguracja projektu

Zacznijmy od skonfigurowania naszego projektu React. Otwórz terminal i przejdź do katalogu, w którym chcesz utworzyć swój projekt. Uruchom następujące polecenie, aby utworzyć nową aplikację React:

npx create-react-app pdf-from-react
npx create-react-app pdf-from-react
SHELL

Jak utworzyć plik PDF w React: Rysunek 1 – Zrzut ekranu terminala pokazujący wykonywanie powyższego polecenia.

To polecenie utworzy nowy katalog o nazwie pdf-from-react z podstawową strukturą projektu React.

Następnie przejdź do katalogu projektu:

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

Teraz możemy otworzyć projekt w naszym edytorze kodu i przystąpić do implementacji.

Krok 2: Dodawanie wymaganych zależności

Najpierw musimy zainstalować niezbędne pakiety. Zainstaluj react, react-dom, @mui/material i jspdf za pomocą następującego polecenia w terminalu.

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

Krok 3: Tworzenie funkcji generowania plików PDF

Importowanie bibliotek

Zaczynamy od zaimportowania niezbędnych zależności dla naszej aplikacji. Obejmują one różne komponenty z biblioteki Material-UI, bibliotekę jsPDF do generowania plików PDF oraz narzędzia do stylizacji.

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

Tworzenie stylizowanych komponentów

Aby zapewnić spójne działanie naszej aplikacji w różnych przeglądarkach, użyliśmy narzędzia styled z biblioteki MUI do stworzenia StyledTableCell i 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

Tworzenie komponentu aplikacji

Głównym elementem naszej aplikacji jest komponent App. Mamy cztery zmienne stanu: customerName i customerAddress do śledzenia danych klienta, items do śledzenia listy pozycji na fakturze oraz error do wyświetlania komunikatu o błędzie w razie potrzeby.

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

Obsługa danych wprowadzanych przez użytkownika

W tym bloku kodu zdefiniowaliśmy funkcje obsługujące interakcje użytkownika: zmianę szczegółów elementu, dodawanie nowego elementu oraz usuwanie elementu. Funkcja handleItemChange aktualizuje właściwości elementu, gdy użytkownik je modyfikuje. Funkcja addItem dodaje nowy element do listy. Funkcja deleteItem usuwa element z listy.

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

Generowanie faktury

Poniżej znajduje się kod funkcji 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

W funkcji generateInvoice najpierw sprawdzamy pola wejściowe, aby upewnić się, że nazwisko klienta, adres klienta i szczegóły produktu zostały wypełnione. Jeśli którekolwiek z tych pól jest puste, ustawiamy stan error na true i kończymy działanie przedwcześnie.

Następnie tworzymy nową instancję jsPDF, wywołując new jsPDF("p", "pt"). Pierwszy argument "p" określa orientację strony jako pionową, a drugi argument "pt"" określa jednostkę miary jako punkty.

Następnie zaczynamy dodawać treść do naszego dokumentu PDF. Rozmiar czcionki ustawiamy za pomocą doc.setFontSize, a metodę doc.text wykorzystujemy do dodawania tekstu w określonych współrzędnych na stronie. Dodajemy nagłówek faktury, w tym tytuł, numer faktury, datę, nazwę klienta i adres klienta.

Po nagłówku dodajemy sekcję "Elementy", ustawiając rozmiar czcionki i dodając linię za pomocą doc.line, aby oddzielić elementy od nagłówka. Następnie iterujemy po każdej pozycji w tablicy items i obliczamy całkowitą cenę każdej pozycji, mnożąc ilość przez cenę. Aktualizujemy zmienną total, dodając sumę wszystkich wartości pozycji.

Dla każdego elementu używamy doc.text, aby dodać nazwę elementu, ilość, cenę i sumę elementu do dokumentu PDF. Zwiększamy wartość zmiennej yOffset, aby przejść do następnej linii dla każdego elementu. Na koniec dodajemy linię oddzielającą pozycje od sumy i używamy doc.text, aby dodać sumę w prawym dolnym rogu dokumentu.

Po dodaniu treści używamy doc.save("invoice.pdf"), aby zapisać wygenerowany plik PDF jako "invoice.pdf" na komputerze użytkownika. Na koniec resetujemy stan error do false, aby usunąć wszelkie poprzednie błędy walidacji.

Krok 4: Renderowanie interfejsu użytkownika

Instrukcja return zawiera kod JSX, który obsługuje proces renderowania. Zawiera pola wprowadzania danych dla nazwy i adresu klienta, tabelę do wprowadzania szczegółów pozycji, przyciski do dodawania pozycji i generowania faktury oraz pasek informacyjny wyświetlający błędy walidacji.

Wykorzystuje komponenty z biblioteki Material-UI, takie jak Button, TextField, Box, Container, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton, Snackbar oraz Alert, w celu utworzenia podstawowych komponentów. Komponenty te służą do tworzenia pól formularzy, tabel, przycisków i powiadomień o błędach.

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

Kompletny kod App.js i App.css

Oto pełny kod App.js, który można skopiować i wkleić do swojego projektu:

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

Oto kod 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);
  }
}

Krok 5: Testowanie aplikacji

Aby przetestować funkcję generowania plików PDF, uruchom w terminalu następujące polecenie:

npm start
npm start
SHELL

Spowoduje to uruchomienie serwera deweloperskiego, a aplikację będzie można wyświetlić w przeglądarce pod adresem http://localhost:3000.

Jak utworzyć plik PDF w React – Rysunek 2: Gotowa aplikacja do fakturowania z domyślnymi, niewypełnionymi polami.

Wprowadź nazwę klienta, adres i szczegóły pozycji w polach wprowadzania danych, a następnie kliknij przycisk "Generuj fakturę". Plik PDF zostanie pobrany na Twój komputer. Możesz go otworzyć, aby wyświetlić pełny widok wygenerowanej faktury.

Jak utworzyć plik PDF w React – Rysunek 3: Aplikacja z trzema wypełnionymi pozycjami, z różnymi produktami, ilościami i cenami.

Po kliknięciu przycisku "Generuj fakturę" zostanie wygenerowany plik PDF.

Jak utworzyć plik PDF w React – Rysunek 4: Wygenerowany plik PDF.

Jeśli spróbujesz wygenerować plik PDF z jakimkolwiek pustym polem, w prawym górnym rogu pojawi się komunikat o błędzie.

Jak utworzyć plik PDF w React – rysunek 5: Wyświetlany jest komunikat o błędzie, ponieważ nie wszystkie pola zostały wypełnione.

IronPDF – biblioteka PDF dla Node.js

IronPDF for Node.js to kompleksowa biblioteka PDF dla Node.js, która wyróżnia się dokładnością, łatwością obsługi i szybkością. Oferuje szeroki wachlarz funkcji do generowania, edycji i formatowania plików PDF bezpośrednio z HTML, adresów URL i obrazów w React. Dzięki obsłudze różnych platform, w tym Windows, MacOS, Linux, Docker oraz platform chmurowych, takich jak Azure i AWS, IronPDF zapewnia kompatybilność międzyplatformową. Jego przyjazny dla użytkownika interfejs API pozwala programistom na szybką integrację generowania i edycji plików PDF z ich projektami Node.js.

Do najważniejszych funkcji IronPDF for Node.js należą: renderowanie z dokładnością do piksela, rozbudowane opcje formatowania oraz zaawansowane możliwości edycji, takie jak łączenie i dzielenie plików PDF, dodawanie adnotacji oraz tworzenie formularzy PDF.

Oto przykład generowania dokumentu PDF na podstawie pliku HTML, ciągu znaków HTML i adresu 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

Aby uzyskać więcej przykładów kodu dotyczących zadań związanych z plikami PDF, odwiedź stronę z przykładami kodu IronPDF.

Wnioski

Podsumowując, tworzenie plików PDF w aplikacji React nie musi być trudne. Dzięki odpowiednim narzędziom i jasnemu zrozumieniu możesz bez wysiłku generować piękne, dobrze zorganizowane dokumenty PDF. Przeanalizowaliśmy różne biblioteki, takie jak jsPDF, pdfmake i React-PDF, z których każda ma swoje mocne strony i unikalne cechy.

Dzięki prostemu procesowi integracji IronPDF z frameworkami i bibliotekami w JavaScript, doskonałej dokumentacji oraz responsywnemu wsparciu technicznemu programiści mogą rozpocząć pracę w mgnieniu oka, co czyni tę bibliotekę najlepszym wyborem do generowania profesjonalnych plików PDF w aplikacjach Node.js.

IronPDF oferuje bezpłatną wersję próbną z pełną funkcjonalnością. Jest również dostępna w innych językach, takich jak C# .NET, Java i Python. Więcej szczegółów można znaleźć na stronie internetowej IronPDF.

Darrius Serrant
Full Stack Software Engineer (WebOps)

Darrius Serrant posiada tytuł licencjata z informatyki z Uniwersytetu Miami i pracuje jako Full Stack WebOps Marketing Engineer w Iron Software. Już od młodych lat zainteresował się kodowaniem, postrzegając informatykę jako zarówno tajemniczą, jak i dostępną, co czyni ją doskonałym medium dla kreatywności ...

Czytaj więcej

Zespol wsparcia Iron

Jestesmy online 24 godziny, 5 dni w tygodniu.
Czat
Email
Zadzwon do mnie