这篇文章最初发表在我的博客上,查看更多内容。

简介

我最近在做一个项目,我有一个独特的(对我来说)要求,需要我从浏览器中的一组值生成 pdf 文件,通常在我的软件开发经验很少的情况下,使用在后端生成 pdf 文件用于节点 js 的 Puppeteer和用于 PHP 等的FPDF。所以我不得不寻找一个适用于我的用例的 React 库,幸运的是我找到了React-pdf。我找到了其他库,例如@progress/kendo-react-pdf但我决定使用React-pdf因为它的开发人员友好的文档。

该图书馆由Diego Muracciole建造并由他维护。

因此,在教程/博客文章中,我将尝试简要解释 react-pdf 的工作原理,并引导您了解如何从来自Moviedb Api的对象数组生成 PDf。

特点

在我尝试为我的用例选择合适的库时浏览文档时,React-pdf的一些特性说服了我使用它,我将简要介绍它们:

组件

React-Pdf 使用React-Primitives规范来创建可用于创建和构建 PDF 文档的自定义组件。

这些组件包括:

  • 文件

  • 查看

  • 图像

  • 文本

  • 链接

  • 备注

  • 帆布

  • PDF查看器

  • PDF下载链接

  • BlobProvider

您可以查看文档以获取有关上述每个组件功能的更多详细信息,基本上这些组件可帮助您使用 JSXesques 语法创建 pdf。

造型

现在我们已经了解了如何创建 PDF 文档,我们如何设置它的样式?React-pdf使用 StyleSheet API 提供强大的样式解决方案,帮助您使用 CSS、媒体查询和 Flexbox 设置文档样式。检查他们支持的 CSS 属性的文档。

如果你是 CSS-in-JS 的忠实粉丝怎么办?好吧,它们还支持整个styled-componentsAPI。

字体

React-Pdf 有一个FontAPI,可帮助您从不同来源加载字体并在 PDF 文档中使用。

这些是让我选择 React-pdf 的一些特性。此外,当我检查Github 存储库时,维护者Diego Muracciole非常活跃,并试图响应打开的大多数问题。

演示

因此,我将简要介绍一个从 MoviesDB API 生成 pdf 的简单示例。该演示将演示生成年度最佳电影。

文件夹结构

project
│   package.json
│
│
└───Public
│   │   150.png
│   │   index.html
│   │   star.png
│
│
│
└───src
    │   Movie.jsx
    │   MovieList.jsx
    |   constant.js
    |   index.js
    |   styles.css

进入全屏模式 退出全屏模式

index.js(入口)

import React from "react";
import ReactDOM from "react-dom";
import MovieList from "./MovieList";

import "./styles.css";

function App() {
    return (
        <div className="App">
              <MovieList />
        </div>
    );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

进入全屏模式 退出全屏模式

index.js 是应用程序的入口点。它呈现<MovieList/>,它是我们应用程序的父组件。

电影列表.jsx

import React, { useState } from "react";
import Axios from "axios";
import { PDFDownloadLink } from "@react-pdf/renderer";
import { API_KEY } from "./constants";
import { PdfDocument } from "./Movie";

const years = [
  { value: "2010", text: "2010" },
  { value: "2011", text: "2011" },
  { value: "2012", text: "2012" },
  { value: "2013", text: "2013" },
  { value: "2014", text: "2014" },
  { value: "2015", text: "2015" },
  { value: "2016", text: "2016" },
  { value: "2017", text: "2017" },
  { value: "2018", text: "2018" },
  { value: "2019", text: "2019" }
];

export default function MovieList() {
  const [year, setYear] = useState("");
  const [movieDetails, setDetails] = useState([]);
  const [show, setHide] = useState(false)

  const fetchMovie = async e => {
    setYear(e.target.value);
    try {
      let res = await Axios(
        `https://api.themoviedb.org/3/discover/movie?api_key=${API_KEY}&primary_release_year=${year}&sort_by=vote_average.desc`
      );
      setDetails(res.data.results);
      setHide(true)
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <div className="container">
      <h2>Best movies of the year</h2>
      <label htmlFor="movies">Select Year</label>
      <select id="movies" className="select" onChange={fetchMovie}>
        <option defaultValue="" disabled>
          Select your option
        </option>
        {years.map((year, index) => {
          return (
            <option key={index} value={year.value}>
              {year.text}
            </option>
          );
        })}
      </select>
      {show &&<PDFDownloadLink
        document={<PdfDocument data={movieDetails} />}
        fileName="movielist.pdf"
        style={{
          textDecoration: "none",
          padding: "10px",
          color: "#4a4a4a",
          backgroundColor: "#f2f2f2",
          border: "1px solid #4a4a4a"
        }}
      >
        {({ blob, url, loading, error }) =>
          loading ? "Loading document..." : "Download Pdf"
        }
      </PDFDownloadLink>}
    </div>
  );
}

进入全屏模式 退出全屏模式

MovieList.jsx组件包含此应用程序中的大部分逻辑。我们从@react-pdf/renderer导入PDFDownloadLink,这基本上是一个锚标签,使我们能够生成和下载 PDF 文档。PDFDownloadLink接受document道具,这是我们将很快使用本文前面列出的一些 React-primitives 创建的 PDF 模板。它还接受一个filename属性,可用于定义 PDF 文档的文件名,一个style属性为链接标签添加内联样式,一个className属性(如果您更喜欢使用类来设置样式)和children属性(锚标签内容) .

电影.jsx

import React from "react";
import {
    Page,
    Text,
    View,
    Document,
    StyleSheet,
    Image
} from "@react-pdf/renderer";
import moment from "moment";

const POSTER_PATH = "https://image.tmdb.org/t/p/w154";

const styles = StyleSheet.create({
    page: {
        backgroundColor: "#ffffff"
    },
    section: {
        margin: 10,
        padding: 10,
        flexGrow: 1
    },
    movieContainer: {
        backgroundColor: "#f6f6f5",
        display: "flex",
        flexDirection: "row",
        padding: 5
    },
    movieDetails: {
        display: "flex",
        marginLeft: 5
    },
    movieTitle: {
        fontSize: 15,
        marginBottom: 10
    },
    movieOverview: {
        fontSize: 10
    },

    image: {
        height: 200,
        width: 150
    },
    subtitle: {
        display: "flex",
        justifyContent: "space-between",
        flexDirection: "row",
        width: 150,
        alignItems: "center",
        marginBottom: 12
    },
    vote: {
        display: "flex",
        flexDirection: "row"
    },
    rating: {
        height: 10,
        width: 10
    },
    vote_text: {
        fontSize: 10
    },
    vote_pop: {
        fontSize: 10,
        padding: 2,
        backgroundColor: "#61C74F",
        color: "#fff"
    },
    vote_pop_text: {
        fontSize: 10,
        marginLeft: 4
    },
    overviewContainer: {
        minHeight: 110
    },
    detailsFooter: {
        display: "flex",
        flexDirection: "row"
    },
    lang: {
        fontSize: 8,
        fontWeight: 700
    },
    vote_average: {
        fontSize: 8,
        marginLeft: 4,
        fontWeight: "bold"
    }
});

export function PdfDocument(props) {
    console.log("pdf props", props.data);
    return (
        <Document>
            <Page style={styles.page}>
                {props.data
                    ? props.data.map((a, index) => {
                            return (
                                <View key={index} style={styles.movieContainer}>
                                    <Image
                                        style={styles.image}
                                        source={
                                            a.poster_path !== null
                                                ? `${POSTER_PATH}${a.poster_path}`
                                                : "150.jpg"
                                        }
                                    />
                                    <View style={styles.movieDetails}>
                                        <Text style={styles.movieTitle}>{a.title}</Text>
                                        <View style={styles.subtitle}>
                                            <View style={styles.vote}>
                                                <Image source="star.png" style={styles.rating} />
                                                <Text style={styles.vote_text}>{a.vote_count}</Text>
                                            </View>
                                            <View style={styles.vote}>
                                                <Text style={styles.vote_pop}>{a.popularity}</Text>
                                                <Text style={styles.vote_pop_text}>Popularity</Text>
                                            </View>
                                        </View>
                                        <View style={styles.overviewContainer}>
                                            <Text style={styles.movieOverview}>{a.overview}</Text>
                                        </View>
                                        <View style={styles.detailsFooter}>
                                            <Text style={styles.lang}>
                                                Language: {a.original_language.toUpperCase()}
                                            </Text>
                                            <Text style={styles.vote_average}>
                                                Average Votes: {a.vote_average}
                                            </Text>
                                            <Text style={styles.vote_average}>
                                                Release Date:{" "}
                                                {moment(a.release_date, "YYYY-MM-DD").format(
                                                    " MMMM D Y"
                                                )}
                                            </Text>
                                        </View>
                    </View>
                    </View>
                );
                })
            : ""}
            </Page>
        </Document>
    );
}

进入全屏模式 退出全屏模式

这个Movie.jsx组件是我们正在生成的 PDF 的模板,这里我们定义了如何使用 React-primitives(VIEW, DOCUMENT) 和样式来定义 PDF 的结构。所以我将简要谈谈我在这里使用的一些 React-pdf API。

  • StyleSheet.create():它可以帮助您定义要在文档中使用的样式,它接受一个包含您要在文档中使用的所有 CSS 的对象,并返回一个您可以通过style应用于任何 PDF 元素的对象支柱。

  • Document:PDFDownloadLink``document属性仅接受Document类型的组件,因此在创建 PDF 模板时它必须是组件的根,并且只接受Page类型的子组件,Document只是 PDF 模板的包装器,它接受一些可选道具

  • Page:这表示文档中有一个页面,一个文档中可以有多个Pages。它接受一些道具来定义页面的size,orientation或者如果你想要页面换行wrap。道具

  • View:我想将此组件与 HTMLdiv进行比较,它可以帮助您分割或分割文档。道具

  • Text:该组件用于在文档上显示文本并对其应用样式。道具

  • Image: 该组件用于在文档上显示图像(网络或本地),这些图像可以是 PNG、JPG 或 base64。

演示应用

结论

在我使用这个库之前,我从未想过可以在客户端生成 PDF,react-pdf 不仅允许您这样做,而且还可以使用 JSXesque 语法来构建和设计 PDF 文档。我知道这个演示很简单,但我认为这个库在某些用例中可能很有用。

Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐