PDF 工具

如何在 React 中创建 PDF 文件

欢迎来到从 React 应用程序创建 PDF 文档的教程! 在本教程中,我们将探讨生成 PDF 的各种库,并学习如何使用流行的 jsPDF 库直接从 React 组件创建 PDF 文件。 那么,让我们开始吧!

PDF(便携式文档格式)是一种广泛使用的文件格式,用于共享和打印文档,同时保持其布局和格式。 作为 React Web 开发人员,您可能会遇到需要直接从 React 应用程序生成 PDF 文档(如发票、报告或销售合同)的情况。

选择 React PDF 库

在 React 应用程序中创建 PDF 文档可能是一项艰巨的任务,尤其是对于新手而言。 值得庆幸的是,我们可以使用几个第三方库来大大简化这一过程。 每个库都有自己独特的功能和实用程序,可满足不同的使用情况。 让我们来详细了解一下这些库。

jsPDF

jsPDF 是一个广受开发人员欢迎的库,用于从 JavaScript 生成 PDF 文件。 其主要卖点之一是简单易懂。 它的语法和用法都很简单明了,可以让您立即将 HTML 内容转换为 PDF 文件。

jsPDF 是一个强大的解决方案,可在浏览器和服务器环境中运行,是各种 JavaScript 应用程序的绝佳选择。

pdfmake

pdfmake 是一个纯 JavaScript 的客户端/服务器端 PDF 打印解决方案。 该库具有全面的 API 和灵活的布局选项,是创建更复杂 PDF 的绝佳选择。 使用 pdfmake,您可以使用简单的 JavaScript 对象定义文档内容和结构,然后将其转换为有效的 PDF 文档。

React-PDF

React-PDF 是一个独特的库,为使用 React 组件创建 PDF 文件提供了强大的功能。 您无需在 JavaScript 对象中手动编写文档结构,而是可以像创建典型的 React 应用程序一样使用可重复使用的组件和道具来创建 PDF。 IronPDF 网站有一个使用 React-PDF 库创建 PDF 的教程

为什么选择 jsPDF?

虽然这三个库都提供了在 React 中生成 PDF 文档的强大工具,但由于 jsPDF 的简单性、灵活性和在社区中的广泛采用,我们将在本教程中使用 jsPDF。 它为初学者提供了较低的入门门槛,其强大的功能集使其成为许多使用案例的合适选择。 我们将探讨的 jsPDF 原理将为您生成 PDF 打下坚实的基础,如果您的项目需要,您还可以更轻松地使用其他库。

先决条件

在我们深入学习本教程之前,有必要确保您已充分掌握必要的工具和知识,以便顺利地学习。 本教程的前提条件如下:

对 React 的基本了解

首先,您应该对 React 有基本的了解,React 是一种流行的 JavaScript 库,用于构建用户界面,尤其是单页面应用程序。 您应该熟悉 React 中的概念,如 JSX(JavaScript XML)、组件、状态和属性(props)。

开发环境

您还应在计算机上设置一个用于构建 React 应用程序的开发环境。 这包括一个文本编辑器或集成开发环境 (IDE)。 Visual Studio Code、Atom 或 Sublime Text 等文本编辑器都是不错的选择。

Node.js 和 npm

为了管理我们的项目及其依赖性,我们将使用Node.js和npm(Node包管理器)。 确保您的计算机上安装了 Node.js。 Node.js 是一种 JavaScript 运行时,允许我们在服务器上运行 JavaScript。 它已安装 npm,因此您可以管理项目所需的库。

您可以通过运行以下终端命令来检查 Node.js 和 npm 是否已安装:

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

这些命令将分别显示您系统上安装的 Node.js 和 npm 版本。 如果您没有安装它们,或您的版本已过时,您应该从他们的官方下载页面下载并安装最新的长期支持 (LTS) 版本的 Node.js。

步骤 1:设置项目

让我们从设置 React 项目开始。 打开终端,导航到您要创建项目的目录。 运行以下命令创建一个新的 React 应用程序:

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

如何在 React 中创建 PDF 文件:图 1 - 正在执行上述命令的终端截图。

此命令将创建一个名为pdf-from-react的新目录,其中包含基本的React项目结构。

接下来,导航到项目目录:

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

现在,我们可以在代码编辑器中打开该项目,并继续执行。

第 2 步:添加所需的依赖项

首先,我们需要安装必要的软件包。 使用以下终端命令安装reactreact-dom@mui/materialjspdf

npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material
SHELL

步骤 3:创建 PDF 生成功能

导入图书馆

首先,我们要为应用程序导入必要的依赖项。 这些包括来自Material-UI库的各种组件、用于生成PDF的jsPDF库以及样式工具。

import React, { useState } from "react";
import "./App.css";
import {
  Button,
  TextField,
  Box,
  Container,
  Typography,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  IconButton,
  Snackbar,
  Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";
jsx
JAVASCRIPT

创建风格化组件

为了在我们的应用中添加一致的跨浏览器行为,我们使用了 MUI 库中的styled实用工具来创建StyledTableCellStyledTableRow

const StyledTableCell = styled(TableCell)(({ theme }) => ({
  [`&.${tableCellClasses.head}`]: {
    backgroundColor: theme.palette.common.black,
    color: theme.palette.common.white,
  },
  [`&.${tableCellClasses.body}`]: {
    fontSize: 14,
  },
}));

const StyledTableRow = styled(TableRow)(({ theme }) => ({
  "&:nth-of-type(odd)": {
    backgroundColor: theme.palette.action.hover,
  },
  "&:last-child td, &:last-child th": {
    border: 0,
  },
}));
jsx
JAVASCRIPT

创建应用程序组件

我们应用程序的主要组件是App组件。 我们有四个状态变量:customerNamecustomerAddress 用于跟踪客户的数据,items 用于跟踪发票中的项目列表,error 用于在必要时显示错误信息。

function App() {
  // State variables
  const [customerName, setCustomerName] = useState("");
  const [customerAddress, setCustomerAddress] = useState("");
  const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
  const [error, setError] = useState(false);
jsx
JAVASCRIPT

处理用户输入

在该代码块中,我们定义了处理用户交互的函数:更改项目详情、添加新项目和删除项目。 handleItemChange 函数在用户修改项目属性时更新项目的属性。 addItem函数将新项目添加到列表中。deleteItem函数从列表中删除一个项目。

const handleItemChange = (index, event) => {
  let newItems = [...items];
  newItems[index][event.target.name] = event.target.value;
  setItems(newItems);
};

const addItem = () => {
  setItems([...items, { name: "", quantity: "", price: "" }]);
};

const deleteItem = (index) => {
  let newItems = [...items];
  newItems.splice(index, 1);
  setItems(newItems);
};
jsx
JAVASCRIPT

生成发票

以下是generateInvoice函数代码:

// Generate invoice
const generateInvoice = () => {
  // Validate the input fields
  if (
    !customerName 

    !customerAddress 

    items.some((item) => !item.name 
 !item.quantity 
 !item.price)
  ) {
    setError(true);
    return;
  }

  // Create a new jsPDF instance
  let doc = new jsPDF("p", "pt");

  // Add invoice header
  doc.setFontSize(24);
  doc.text("Invoice", 40, 60);
  doc.setFontSize(10);
  doc.text("Invoice Number: 123456", 40, 90);
  doc.text("Date: " + new Date().toDateString(), 40, 110);
  doc.text(`Customer Name: ${customerName}`, 40, 130);
  doc.text(`Customer Address: ${customerAddress}`, 40, 150);

  // Add items section
  doc.setFontSize(14);
  doc.text("Items:", 40, 200);
  doc.line(40, 210, 550, 210);

  // Add item details
  doc.setFontSize(12);
  let yOffset = 240;
  let total = 0;

  items.forEach((item) => {
    let itemTotal = item.quantity * item.price;
    total += itemTotal;

    doc.text(`Item: ${item.name}`, 40, yOffset);
    doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
    doc.text(`Price: $${item.price}`, 300, yOffset);
    doc.text(`Total: $${itemTotal}`, 400, yOffset);

    yOffset += 20;
  });

  // Add total
  doc.line(40, yOffset, 550, yOffset);
  doc.setFontSize(14);
  doc.text(`Total: $${total}`, 400, yOffset + 30);

  // Save the generated PDF as "invoice.pdf"
  doc.save("invoice.pdf");

  // Reset error state
  setError(false);
};
jsx
JAVASCRIPT

generateInvoice函数中,我们首先对输入字段进行验证,以确保填写了客户姓名、客户地址和项目详细信息。 如果这些字段中的任何一个为空,我们将error状态设为true并提前返回。

接下来,我们通过调用new jsPDF("p", "pt")创建一个新的jsPDF实例。 第一个参数“p”指定页面方向为纵向,第二个参数“pt”指定测量单位为点。

然后,我们开始将内容添加到 PDF 文档中。 我们使用doc.setFontSize设置字体大小,并使用doc.text方法在页面上的特定坐标添加文本。 我们添加了发票抬头,包括标题、发票号码、日期、客户名称和客户地址。

在标题之后,我们通过设置字体大小并使用 doc.line 添加一条线来添加“项目”部分,以将项目与标题分开。 接下来,我们遍历items数组中的每一项,通过将数量与价格相乘来计算每项的总价。我们更新total变量,使其等于所有项总价的和。

对于每个项目,我们使用doc.text将项目名称、数量、价格和项目总计添加到PDF文档中。 我们增加yOffset变量,以便为每个项目移动到下一行。 最后,我们添加一条线来将项目与总计分开,并使用doc.text在文档的右下角添加总金额。

一旦内容添加完成,我们使用doc.save("invoice.pdf")将生成的PDF保存为用户计算机上的“invoice.pdf”。 最后,我们将error状态重置为false,以清除任何先前的验证错误。

步骤 4:渲染用户界面

return语句包含处理渲染过程的JSX代码。 它包括客户姓名和地址的输入字段、用于输入项目详细信息的表格、用于添加项目和生成发票的按钮,以及用于显示验证错误的错误提示栏。

它使用来自Material-UI库的组件,例如ButtonTextFieldBoxContainerTypographyTableTableBodyTableCellTableContainerTableHeadTableRowPaperIconButtonSnackbarAlert,以创建基本组件。 这些组件用于创建表单字段、表格、按钮和错误通知。

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>
);
jsx
JAVASCRIPT

完整的 App.js 和 App.css 代码

以下是完整的 App.js 代码,您可以复制并粘贴到您的项目中:

import React, { useState } from "react";
import "./App.css";
import {
  Button,
  TextField,
  Box,
  Container,
  Typography,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  IconButton,
  Snackbar,
  Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";

const StyledTableCell = styled(TableCell)(({ theme }) => ({
  [`&.${tableCellClasses.head}`]: {
    backgroundColor: theme.palette.common.black,
    color: theme.palette.common.white,
  },
  [`&.${tableCellClasses.body}`]: {
    fontSize: 14,
  },
}));

const StyledTableRow = styled(TableRow)(({ theme }) => ({
  "&:nth-of-type(odd)": {
    backgroundColor: theme.palette.action.hover,
  },
  "&:last-child td, &:last-child th": {
    border: 0,
  },
}));

function App() {
  // State variables
  const [customerName, setCustomerName] = useState("");
  const [customerAddress, setCustomerAddress] = useState("");
  const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
  const [error, setError] = useState(false);

  // Event handler for item changes
  const handleItemChange = (index, event) => {
    let newItems = [...items];
    newItems[index][event.target.name] = event.target.value;
    setItems(newItems);
  };

  // Add new item to the list
  const addItem = () => {
    setItems([...items, { name: "", quantity: "", price: "" }]);
  };

  // Delete an item from the list
  const deleteItem = (index) => {
    let newItems = [...items];
    newItems.splice(index, 1);
    setItems(newItems);
  };

  // Generate invoice
  const generateInvoice = () => {
    // Validate the input fields
    if (
      !customerName 

      !customerAddress 

      items.some((item) => !item.name 
 !item.quantity 
 !item.price)
    ) {
      setError(true);
      return;
    }

    // Create a new jsPDF instance
    let doc = new jsPDF("p", "pt");

    // Add invoice header
    doc.setFontSize(24);
    doc.text("Invoice", 40, 60);
    doc.setFontSize(10);
    doc.text("Invoice Number: 123456", 40, 90);
    doc.text("Date: " + new Date().toDateString(), 40, 110);
    doc.text(`Customer Name: ${customerName}`, 40, 130);
    doc.text(`Customer Address: ${customerAddress}`, 40, 150);

    // Add items section
    doc.setFontSize(14);
    doc.text("Items:", 40, 200);
    doc.line(40, 210, 550, 210);

    // Add item details
    doc.setFontSize(12);
    let yOffset = 240;
    let total = 0;

    items.forEach((item) => {
      let itemTotal = item.quantity * item.price;
      total += itemTotal;

      doc.text(`Item: ${item.name}`, 40, yOffset);
      doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
      doc.text(`Price: $${item.price}`, 300, yOffset);
      doc.text(`Total: $${itemTotal}`, 400, yOffset);

      yOffset += 20;
    });

    // Add total
    doc.line(40, yOffset, 550, yOffset);
    doc.setFontSize(14);
    doc.text(`Total: $${total}`, 400, yOffset + 30);

    // Save the generated PDF as "invoice.pdf"
    doc.save("invoice.pdf");

    // Reset error state
    setError(false);
  };

  return (
    <Container maxWidth="md">
      <Box sx={{ my: 4 }}>
        <Typography variant="h3" component="h1" gutterBottom>
          Create Invoice
        </Typography>

        {/* Customer Name and Address fields */}
        <Grid container spacing={3}>
          <Grid item xs={6}>
            <TextField
              label="Customer Name"
              fullWidth
              margin="normal"
              value={customerName}
              onChange={(e) => setCustomerName(e.target.value)}
            />
          </Grid>
          <Grid item xs={6}>
            <TextField
              label="Customer Address"
              fullWidth
              margin="normal"
              value={customerAddress}
              onChange={(e) => setCustomerAddress(e.target.value)}
            />
          </Grid>
        </Grid>

        {/* Items table */}
        <TableContainer component={Paper}>
          <Table sx={{ minWidth: 700 }} aria-label="invoice table">
            <TableHead>
              <TableRow>
                <StyledTableCell>Item Name</StyledTableCell>
                <StyledTableCell align="left">Quantity</StyledTableCell>
                <StyledTableCell align="left">Price</StyledTableCell>
                <StyledTableCell align="left">Action</StyledTableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {items.map((item, index) => (
                <StyledTableRow key={index}>
                  <StyledTableCell component="th" scope="row">
                    <TextField
                      fullWidth
                      value={item.name}
                      onChange={(event) => handleItemChange(index, event)}
                      name="name"
                    />
                  </StyledTableCell>
                  <StyledTableCell align="right">
                    <TextField
                      fullWidth
                      value={item.quantity}
                      onChange={(event) => handleItemChange(index, event)}
                      name="quantity"
                    />
                  </StyledTableCell>
                  <StyledTableCell align="right">
                    <TextField
                      fullWidth
                      value={item.price}
                      onChange={(event) => handleItemChange(index, event)}
                      name="price"
                    />
                  </StyledTableCell>
                  <StyledTableCell align="right">
                    <IconButton onClick={() => deleteItem(index)}>
                      <DeleteIcon />
                    </IconButton>
                  </StyledTableCell>
                </StyledTableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>

        {/* Buttons */}
        <Box mt={2} display="flex" gap={2}>
          <Button variant="contained" onClick={addItem}>
            Add Item
          </Button>
          <Button variant="outlined" color="success" onClick={generateInvoice}>
            Generate Invoice
          </Button>
        </Box>
      </Box>

      {/* Error Snackbar */}
      <Snackbar
        open={error}
        autoHideDuration={6000}
        onClose={() => setError(false)}
        anchorOrigin={{ vertical: "top", horizontal: "right" }}
      >
        <Alert onClose={() => setError(false)} severity="error">
          Please fill in all required fields.
        </Alert>
      </Snackbar>
    </Container>
  );
}
export default App;
jsx
JAVASCRIPT

这是App.css代码:

@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: bold;
  /* This is the weight for bold in Poppins */
  color: #FF6347;
  /* This is the color Tomato. Replace with your preferred color */
}

body {
  font-family: 'Poppins', sans-serif;
  background-color: #E9F8F4;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

步骤 5:测试应用程序

要测试 PDF 生成功能,请在终端运行以下命令:

npm start
npm start
SHELL

这将启动开发服务器,您可以在浏览器中访问应用程序,网址为http://localhost:3000

如何在 React 中创建 PDF 文件 - 图 2:带有默认未填充字段的完整发票应用程序。

在输入字段中填写客户姓名、地址和项目详情,然后单击 "生成发票 "按钮。 PDF 文件将下载到您的电脑,您可以打开它查看生成发票的全页视图。

如何在 React 中创建 PDF 文件 - 图 3:应用程序中填写了三项,具有不同的项目、数量和价格。

点击 "生成发票 "按钮后,将生成 PDF 文件。

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

如果您尝试生成带有任何空字段的 PDF,右上角将显示错误信息。

如何在 React 中创建 PDF 文件 - 图 5:未填写所有字段时显示错误消息。

IronPDF - Node.js PDF库

IronPDF for Node.js 是一个全面的 Node.js PDF 库,以高精确性、易用性和速度见长。 它提供了大量功能,可直接从 React 中的 HTML、URL 和图像生成、编辑和格式化 PDF。 IronPDF 支持各种平台,包括 Windows、MacOS、Linux、Docker 以及 Azure 和 AWS 等云平台,可确保跨平台兼容性。 其用户友好的 API 使开发人员能够快速将 PDF 生成和操作集成到他们的 Node.js 项目中。

IronPDF Node.js 的显著特点包括:完美的像素渲染、丰富的格式选项以及高级编辑功能,如合并和拆分 PDF、添加注释和创建 PDF 表单。

以下是从HTML文件、HTML字符串和URL生成PDF文档的示例:

import { PdfDocument } from "@ironsoftware/ironpdf";

(async () => {
    const pdfFromUrl = await PdfDocument.fromUrl("https://getbootstrap.com/");
    await pdfFromUrl.saveAs("website.pdf");

    const pdfFromHtmlFile = await PdfDocument.fromHtml("design.html");
    await pdfFromHtmlFile.saveAs("markup.pdf");

    const pdfFromHtmlString = await PdfDocument.fromHtml("<p>Hello World</p>");
    await pdfFromHtmlString.saveAs("markup_with_assets.pdf");
})();
js
JAVASCRIPT

有关PDF相关任务的更多代码示例,请访问此IronPDF代码示例页面

结论

总之,在 React 应用程序中创建 PDF 并不可怕。 有了正确的工具和清晰的理解,您就可以毫不费力地生成精美、结构合理的 PDF 文档。 我们研究了 jsPDF、pdfmake 和 React-PDF 等各种库,每个库都有自己的优势和独特功能。

通过 IronPDF 简单的 JavaScript 框架和库集成过程、优秀的文档以及响应迅速的技术支持,开发人员可以迅速开始使用,使其成为在 Node.js 应用程序中生成专业级 PDF 的首选。

IronPDF 提供完整功能的免费试用。 它也适用于其他语言,如C# .NETJavaPython。 请访问 IronPDF 网站 了解更多详情。

Darrius Serrant
全栈软件工程师(WebOps)

达瑞乌斯·塞兰特拥有迈阿密大学计算机科学学士学位,目前在Iron Software担任全栈WebOps营销工程师。从小对编码的热爱使他认为计算机既神秘又易接近,成为创意和解决问题的完美媒介。

在Iron Software,达瑞乌斯乐于创造新事物并简化复杂概念,使其更易于理解。作为我们在职开发者之一,他还自愿教授学生,将他的专业知识传授给下一代。

对达瑞乌斯而言,他的工作之所以令人满足,是因为它具有价值并产生了真正的影响。

< 前一页
JavaScript PDF编辑器 (开发者教程)
下一步 >
如何用JavaScript创建PDF文件

准备开始了吗? 版本: 2025.5 刚刚发布

查看许可证 >