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 -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
To polecenie 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 za pomocą następującego polecenia w terminalu.
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. 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";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,
},
}));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);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);
};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 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>
);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;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 startSpowoduje to uruchomienie serwera deweloperskiego, a aplikację będzie można wyświetlić 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 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");
})();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.








