简体   繁体   中英

HTML-PDF npm working on localhost but not on IP Staging Server?

Project Details :

I have a full stack project using ReactJS for the View, NodeJS for back-end and MySQL for database. On my webserver, I'm using NGINX for the web server, and PM2 as a reverse-proxy to run my node instance.

The Objective :

What I'm trying to do is basically have a user select a StartDate and EndDate from the inputs. This will pull data from the MYSQL database and then download a PDF in the browser to the user which will display it in separate PDF's. So for example, if the record contained 30 results, it would be one PDF with 30 separate pages.

The Problem:

I got all this to work fine on my localhost where it downloads the PDF successfully in the browser to the user but when I push it to a staging or production server it doesn't download the PDF in the browser at all and I'm not sure why.

On staging and live throw the same error in the console.log:

在此处输入图像描述

On staging here is a screenshot of the network request. In the response there is no response at all that I get back it's just blank but on localhost I do get a response:

However, on localhost the the PDF gets generated successfully and is downloaded in the browser no problem when the user clicks "Generate PDF" button. Here is a screenshot of the network request and the response it gets back:

I have a ton of code so I'll try to show only the relevant problem:

View (ReactJS):

 import React, { useEffect, useState } from "react"; import TextField from "@material-ui/core/TextField"; import Button from "@material-ui/core/Button"; import Moment from "moment"; import AppController from "../../controllers/appController"; import Detailedservice from "../../services/Detailedservice"; const DetailedReport = () => { const [dateValues, handleDateChange] = useState({ startDate: "", endDate: "", }); const [emptyText, setEmpty] = useState(false); const [sucessText, setSuccess] = useState(false); const [loadingState, isLoading] = useState(false); const [scheduleData, setSchedule] = useState({ startDate: "", endDate: "", bathData: [], }); const handleChange = (e) => { handleDateChange({...dateValues, [e.target.name]: e.target.value }); }; const data = { selectedDates: { startDate: dateValues.startDate, endDate: dateValues.endDate, }, requestedData: scheduleData, }; // This is what generates the PDF const viewPdf = await Detailedservice.pdfTemplate(data); // These lines download the PDF in the browser for the user var link = document.createElement("a"); // Location of where the download file is located in link.href = AppController.downloadPDF() + "MM_Report.pdf"; link.target = "_blank"; // Name of download file link.download = "MM_Report.pdf"; link.dispatchEvent(new MouseEvent("click")); }; return ( <div className="detailedreportContainer"> <div className="container"> <div className="row d-flex justify-content-center"> <div className="col-lg-2"> <div className="daterange" style={{ marginTop: "150px" }}> <div className="daterangeCenter"> <TextField id="startDate" label="Start Date" name="startDate" type="date" InputLabelProps={{ shrink: true, }} onChange={handleChange} /> </div> </div> </div> <div className="col-lg-2"> <div className="daterange" style={{ marginTop: "150px" }}> <div className="daterangeCenter d-flex justify-content-center"> <TextField id="endDate" label="End Date" name="endDate" type="date" InputLabelProps={{ shrink: true, }} onChange={handleChange} /> </div> </div> </div> </div> <div className="row d-flex justify-content-center" style={{ marginTop: "20px" }} > <Button variant="contained" color="primary" disabled={.dateValues.startDate ||?dateValues:endDate || emptyText? true: false } onClick={fetchSchedule} > {loadingState: ( <i className={"fas fa-circle-notch fa-spin"} style={{ fontSize; "24px" }} /> ); ( "Generate Report" )} </Button> </div> </div> </div> </div> ); }; export default DetailedReport;

Model: (This is what creates the PDF what it looks like)

 var db = require("../dbconnection"); var htmltopdf = require("html-pdf"); const path = require("path"); var options = { width: "8.5in", height: "11in", format: "Letter", }; var detailedReport = { getSchedule: function (data, callback) { console.log(data); db.query( "SELECT Subject, StartTime, EndTime, FullName, Resident, CommunicationMethod, Email, Phone, Reason from schedule where DateCreated between? AND? order by StartTime, EndTime asc", [data.startDate, data.endDate], callback ); }, pdfMessage: function (data) { let scheduleData = data.requestedData; let dateonDocument = data.selectedDates; var message = ""; // Looping over all schedules chosen and displaying them in multiple batch pdfs. This is possible using the following class "<div style='page-break-before: always'></div>" which should be used to display data on different pages message += "<html>"; message += "<head>"; message += "<meta charset='utf-8' />"; message += "<style>"; message += "* {"; message += "border: 0;"; message += "margin: 0;"; message += "padding: 0;"; message += "}"; message += "body {"; message += "font-family: cursive, Helvetica, sans-serif;"; message += "width: 100%;"; message += "margin: 30px;"; message += "color: #000;"; message += "}"; message += "#uniqueTable,"; message += "#uniqueTable td,"; message += "#uniqueTable th {"; message += "border: 1px solid #000;"; message += "}"; message += "table {"; message += "border-collapse: collapse;"; message += "width: 90%;"; message += "margin: 10px 0;"; message += "text-align: left;"; message += "}"; message += "td {"; message += "font-size: 20px;"; message += "padding: 10px;"; message += "}"; message += "p {"; message += "padding: 3px 0;"; message += "font-size: 14px;"; message += "}"; message += "img {"; message += "padding-left: 60px;"; message += "padding-bottom: 10px;"; message += "display: block;"; message += "}"; message += "h1 {"; message += "font-size: 18px;"; message += "padding: 10px 0;"; message += "color: #35a768;"; message += "}"; message += "h2 {"; message += "font-size: 18px;"; message += "font-weight: bold;"; message += "color: #000;"; message += "}"; message += "hr {"; message += "border: 1px solid #000;"; message += "width: 90%;"; message += "}"; message += "table th {"; message += "font-size: 16px;"; message += "color: #0094e9;"; message += "font-weight: bold;"; message += "}"; message += "</style>"; message += "</head>"; message += "<body>"; message += "<div style='text-align:center'>"; message += "<h1>Report</h1>"; message += "<p>" + dateonDocument.startDate + " - " + dateonDocument.endDate + "</p>"; message += "</div>"; message += "<div style='page-break-before: always'></div>"; scheduleData.forEach((data) => { message += "<div id='pageContent'>"; message += "<table>"; message += "<tr>"; message += "<td>"; message += "<img"; message += "src='https://amazonaws.com/mmlogomain.jpg'"; message += "style='width: 200px; margin: auto'"; message += "/>"; message += "</td>"; message += "</tr>"; message += "</table>"; message += "<div id='header' style='background-color: #e0f2ff; padding: 20px'>"; message += "<h1 style='color: #000; font-size: 24px'>"; message += "Detailed Report"; message += "</h1>"; message += "</div>"; message += "<div id='subheader' style='background-color: #0094e9; height: 30px'></div>"; message += "<table id='uniqueTable'>"; message += "<tbody>"; message += "<tr>"; message += "<td>"; message += "<h1>Date/Time Scheduled:</h1>"; message += "<p>" + data.StartTime + " - " + data.EndTime + "</p>"; message += "</td>"; message += "</tr>"; message += "<tr>"; message += "<td>"; message += "<h1>Communication Method:</h1>"; message += "<p>" + data.CommunicationMethod + "</p>"; message += "</td>"; message += "</tr>"; message += "</tbody>"; message += "</table>"; message += "<table id='uniqueTable'>"; message += "<tbody>"; message += "<tr>"; message += "<td>"; message += "<h1>Visitor's Full Name:</h1>"; message += "<p>" + data.FullName + "</p>"; message += "</td>"; message += "<td>"; message += "<h1>Resident Requesting to Speak to:</h1>"; message += "<p>" + data.Resident + "</p>"; message += "</td>"; message += "</tr>"; message += "<tr>"; message += "<td>"; message += "<h1>Phone Number:</h1>"; message += "<p>" + data.Phone + "</p>"; message += "</td>"; message += "<td rowspan='2'>"; message += "<h1>Reason for Visit:</h1>"; message += "<p>" + data.Reason + "</p>"; message += "</td>"; message += "</tr>"; message += "<tr>"; message += "<td>"; message += "<h1>Email Address:</h1>"; message += "<p>" + data.Email + "</p>"; message += "</td>"; message += "</tr>"; message += "</tbody>"; message += "</table>"; message += "</div>"; message += "<div style='page-break-before: always'></div>"; }); message += "</body>"; message += "</html>"; return message; }, pdfTemplate: function (data, callback) { let pdfPath = "MM_Report.pdf"; htmltopdf.create(detailedReport.pdfMessage(data), options).toFile( path.join(__dirname, "../public/reports/" + pdfPath), function (err, res) { callback(res); } ); }, }; module.exports = detailedReport;

Routes

 var express = require("express"); var router = express.Router(); var detailedReport = require("../models/detailedReport"); router.post("/getSchedule", function (req, res) { detailedReport.getSchedule(req.body, function (err, rows) { if (err) { res.json(err); console.log(err); } else { res.send(rows); } }); }); router.post("/pdfTemplate", function (req, res) { detailedReport.pdfTemplate(req.body, function (err, rows) { if (err) { res.json(err); } else { res.json(rows); } }); }); module.exports = router;

Service:

 import serviceBase from "./serviceBase"; const scheduleService = { getSchedule: (data) => serviceBase.post("/api/getSchedule", data), pdfTemplate: (data) => serviceBase.post("/api/pdfTemplate", data), }; export default scheduleService;

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

As I understand correctly /pdfTemplate?t=xyz returns the pdf's path.

You need to make sure that the pdf is on the server (../web/uploads/file.pdf) and not on your local directory (C://docs/file.pdf)

Another solution

  • instead of returning the path, return an encoded base64 string of your pdf;
  • on your View decode the result
  • transform it into a byte array and create a Blob object from it
  • create a link from this object and open it

Example:

//content - encoded base64 string
generateFile(content) {
    const byteCharacters = atob(content);
    const byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += 512) {
        const slice = byteCharacters.slice(offset, offset + 512);

        const byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        const byteArray = new Uint8Array(byteNumbers);
        byteArrays.push(byteArray);
    }

    const blob = new Blob(byteArrays, {type: "application/pdf"});

    const link = document.createElement("a");
    link.href = window.URL.createObjectURL(blob);
    link.target = '_black';
    link.click();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM