Jak stworzyć plik PDF w React
Witamy w samouczku dotyczącym tworzenia dokumentów PDF z aplikacji React! W tym samouczku przyjrzymy się różnym bibliotekom do generowania plików PDF i dowiemy się, jak używać 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
Biblioteka jsPDF jest szeroko popularna wśród deweloperów do generowania plików PDF z JavaScriptu. 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 dostosowywanie orientacji i rozmiaru strony. jsPDF to solidne rozwiązanie, które działa zarówno w przeglądarce, jak i w środowisku serwerowym, co czyni go doskonałym wyborem dla szerokiego spektrum aplikacji JavaScript.
pdfmake
pdfmake wyróżnia się jako rozwiązanie do drukowania plików PDF po stronie klienta/serwera w czystym JavaScripcie. 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żna definiować zawartość i strukturę dokumentu za pomocą prostego obiektu JavaScript, a następnie przekształcać go w prawidłowy dokument PDF.
React-PDF
React-PDF to wyjątkowa biblioteka, która zapewnia silne funkcje do tworzenia plików PDF z wykorzystaniem 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?
Podczas gdy wszystkie trzy biblioteki dostarczają potężne narzędzia do generowania dokumentów PDF w React, w tym samouczku użyjemy jsPDF z powodu jego prostoty, elastyczności i szerokiego przyjęcia 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 poznamy dzięki jsPDF, zapewnią solidne podstawy do generowania plików PDF, a także umożliwią łatwiejsze opanowanie innych bibliotek, jeśli wymaga tego projekt.
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 -vnode -v
npm -vTe 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-reactnpx create-react-app pdf-from-react
Ta komenda utworzy nowy katalog o nazwie pdf-from-react z podstawową strukturą projektu React.
Następnie przejdź do katalogu projektu:
cd pdf-from-reactcd pdf-from-reactTeraz 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, używając poniższej komendy terminala.
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-materialnpm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-materialKrok 3: Tworzenie funkcji generowania plików PDF
Importowanie bibliotek
Zaczynamy od zaimportowania niezbędnych zależności dla naszej aplikacji. Zawierają one różne komponenty z biblioteki Material-UI, biblioteki jsPDF do generowania plików PDF i 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";Tworzenie stylizowanych komponentów
Aby dodać spójne zachowanie między przeglądarkami do naszej aplikacji, 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,
},
}));Tworzenie komponentu aplikacji
Głównym komponentem naszej aplikacji jest komponent App. Mamy cztery zmienne stanu: customerName i customerAddress do śledzenia danych klienta, items do śledzenia listy pozycji w fakturze, oraz error do wyświetlania komunikatu o błędzie, gdy jest to konieczne.
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);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 pozycji, gdy użytkownik je zmienia. Funkcja addItem dodaje nową pozycję do listy. Funkcja deleteItem usuwa pozycję 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);
};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);
};W funkcji generateInvoice najpierw wykonujemy walidację pól wejściowych, aby upewnić się, że nazwa klienta, adres klienta i szczegóły pozycji zostały wypełnione. Jeśli którekolwiek z tych pól jest puste, ustawiamy stan error na true i kończymy wcześniej.
Następnie tworzymy nową instancję jsPDF, wywołując new jsPDF("p", "pt"). Pierwszy argument "p" określa orientację strony jako portretową, a drugi argument "pt""" jako jednostkę miary określa punkty.
Następnie zaczynamy dodawać treść do naszego dokumentu PDF. Ustawiamy rozmiar czcionki za pomocą doc.setFontSize i używamy metody doc.text, aby dodać tekst 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ę "Pozycje", ustawiając rozmiar czcionki i dodając linię za pomocą doc.line, aby oddzielić pozycje od nagłówka. Następnie iterujemy przez każdy element w tablicy items i obliczamy całkowitą cenę dla każdego elementu, mnożąc ilość przez cenę. Aktualizujemy zmienną total sumą wszystkich całkowitych cen pozycji.
Dla każdej pozycji używamy doc.text, aby dodać nazwę pozycji, ilość, cenę i całkowitą cenę pozycji do dokumentu PDF. Zwiększamy zmienną yOffset, aby przejść do następnej linii dla każdej pozycji. Wreszcie, dodajemy linię, aby oddzielić pozycje od sumy i używamy doc.text, aby dodać sumę na dole po prawej stronie dokumentu.
Po dodaniu zawartoś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 wcześniejsze błędy walidacji.
Krok 4: Renderowanie interfejsu użytkownika
Oświadczenie 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.
Używa komponentów z biblioteki Material-UI, takich jak Button, TextField, Box, Container, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton, Snackbar i Alert, aby tworzyć podstawowe komponenty. 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>
);Kompletny kod App.js i App.css
Oto kompletny kod App.js, który możesz 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;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 startnpm startTo uruchomi serwer deweloperski, a aplikację można zobaczyć w przeglądarce pod adresem http://localhost:3000.

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.

Po kliknięciu przycisku "Generuj fakturę" zostanie 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.

IronPDF – biblioteka PDF dla Node.js
IronPDF dla 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");
})();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.








