إنشاء ملفات PDF ديناميكية باستخدام React و Node.js

المادة ، التي نُنشر ترجمتها اليوم ، مكرسة لإنشاء ملفات PDF ديناميكية باستخدام كود HTML كقالب. أي ، سوف نتحدث عن كيفية إنشاء فاتورة بسيطة لدفع سلع أو خدمات معينة ، البيانات الديناميكية المضمنة التي يتم أخذها من حالة تطبيق React. تم إنشاء أساس تطبيق React باستخدام create-react-app ، ويستند جزء خادم المشروع إلى Node.js ، وتم استخدام إطار عمل Express في تطويره.



يلاحظ مؤلف هذه المادة أنه أعد شريط فيديو يوضح تطور المشروع. إذا قررت مشاهدة الفيديو وقراءة المقال ، فمن المستحسن القيام بذلك. أولاً ، استعرض المقالة ، ثم شغّل الفيديو وأعد إنشاء النظام الذي تفكر فيه هناك. بعد ذلك ، مجرد قراءة المقال.

إنشاء المشروع


قم بإنشاء دليل مشروع وانتقل إليه:

mkdir pdfGenerator && cd pdfGenerator 

قم بإنشاء تطبيق React جديد:

 create-react-app client 

بعد الانتهاء من إنشاء التطبيق ، انتقل إلى الدليل الذي أنشأته للتو وتثبيت التبعيات:

 cd client && npm i -S axios file-saver 

إنشاء خادم Express. للقيام بذلك ، قم بإنشاء مجلد server في دليل المشروع وانتقل إليه. في ذلك ، قم بإنشاء ملف index.js وابدأ تهيئة المشروع:

 mkdir server && cd server && touch index.js && npm init 

هنا ، لتشكيل package.json ، فقط اضغط على Enter عدة مرات. بعد ذلك ، قم بتنفيذ الأمر التالي لإضافة التبعيات اللازمة إلى جزء خادم المشروع:

 npm i -S express body-parser cors html-pdf 

الآن ، في ملف client/package.json ، فوق قسم وصف التبعية ، أضف ما يلي:

 "proxy": "http://localhost:5000/" 

هذا سوف يساعد في العمل مع الخادم المحلي من رمز العميل.

الآن تحتاج إلى فتح نافذتين للمحطة.

في النافذة الأولى ، انتقل إلى دليل client وقم بتنفيذ الأمر التالي:

 npm start 

في النافذة الثانية ، انتقل إلى مجلد server وقم بتنفيذ الأمر التالي:

 nodemon index.js 

إعداد العميل الأولي


سيبدو جزء العميل في مشروعنا بسيطًا للغاية.

للبدء ، في src/App.js لجزء العميل من التطبيق ، نستورد التبعيات إلى الكود:

 import axios from 'axios'; import { saveAs } from 'file-saver'; 

بعد ذلك ، في الجزء العلوي من وصف المكون ، قم بتهيئة الحالة:

 state = {   name: 'Adrian',   receiptId: 0,   price1: 0,   price2: 0, } 

دعنا نزيل علامة JSX القياسية التي تم إنشاؤها في قالب التطبيق باستخدام تطبيق create-react-app وتم إرجاعها من طريقة التقديم render() . أدخل ما يلي فيه:

 <div className="App">   <input type="text" placeholder="Name" name="name" onChange {this.handleChange}/>   <input type="number" placeholder="Receipt ID" name="receiptId"  onChange={this.handleChange}/>   <input type="number" placeholder="Price 1" name="price1" onChange={this.handleChange}/>   <input type="number" placeholder="Price 2" name="price2" onChange={this.handleChange}/>   <button onClick={this.createAndDownloadPdf}>Download PDF</button></div> 

لنقم بإنشاء طريقة handleChange ، والتي ستكون مسؤولة عن تحديث بيانات حالة التطبيق المتعلقة بحقول الإدخال:

 handleChange = ({ target: { value, name }}) => this.setState({ [name]: value }) 

الآن يمكننا الانتقال إلى مهمة إنشاء ملف PDF. هذا الجزء منه ، الذي يتم حله عن طريق العميل ، هو إنشاء طلب POST على الخادم. يرسل الطلب حالة التطبيق:

 createAndDownloadPdf = () => {   axios.post('/create-pdf', this.state) } 

قبل أن نواصل العمل على جانب العميل من المشروع ، نحتاج إلى تكوين المسارات على الخادم. سيسمح هذا للخادم بتلقي البيانات من العميل وإنشاء ملف PDF ونقل هذا الملف إلى العميل.

إعداد الخادم الأولي


سيتضمن جزء الخادم من المشروع طريقين فقط. هناك حاجة واحدة لإنشاء ملفات PDF. والثاني هو لإرسال الملفات إلى العميل.

أولاً ، قم باستيراد التبعيات إلى ملف index.js :

 const express = require('express'); const bodyParser = require('body-parser'); const pdf = require('html-pdf'); const cors = require('cors'); 

نقوم بتهيئة تطبيق Express وتكوين المنفذ:

 const app = express(); const port = process.env.PORT || 5000; 

قم بإعداد محلل الاستعلام. ما نحتاج إليه سيكون متاحًا ك req.body . سنقوم أيضًا بتهيئة CORS حتى لا يتعارض خطأ " Cross-Origin Request Blocked الشراء المتداخل" مع عملنا:

 app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); 

بعد ذلك ، ابدأ الخادم:

 app.listen(port, () => console.log(`Listening on port ${port}`)); 

الآن يمكننا معالجة الكود المسؤول عن إنشاء ملفات PDF.

إنشاء قالب HTML لملفات PDF


نحتاج إلى قالب HTML لاستخدامه عند إنشاء ملفات PDF. في إنشاء مثل هذا القالب ، تفتح الإمكانيات التي لا نهاية لها أمامنا. كل ما يمكن إنشاؤه باستخدام HTML و CSS النقي يمكن تمثيلهما كملف PDF. أنشئ دليل documents في مجلد server ، واذهب إليه وقم بإنشاء ملف index.js فيه:

 mkdir documents && cd documents && touch index.js 

من هذا الملف ، نقوم بتصدير دالة السهم التي ستُرجع كل تعليمات HTML البرمجية الضرورية. عند استدعاء هذه الوظيفة ، يمكنك استخدام المعلمات ، والتي سنصفها هنا أيضًا.

 module.exports = ({ name, price1, price2, receiptId }) => { ... } 

سأقدم لك هنا مثالًا على قالب HTML ، وقمت بنسخه في مشروعك. لكنك ، بالطبع ، يمكنك إنشاء القالب الخاص بك.

دعنا index.js كود index.js من مجلد server/documents إلى النموذج التالي:

 module.exports = ({ name, price1, price2, receiptId }) => {    const today = new Date(); return `    <!doctype html>    <html>       <head>          <meta charset="utf-8">          <title>PDF Result Template</title>          <style>             .invoice-box {             max-width: 800px;             margin: auto;             padding: 30px;             border: 1px solid #eee;             box-shadow: 0 0 10px rgba(0, 0, 0, .15);             font-size: 16px;             line-height: 24px;             font-family: 'Helvetica Neue', 'Helvetica',             color: #555;             }             .margin-top {             margin-top: 50px;             }             .justify-center {             text-align: center;             }             .invoice-box table {             width: 100%;             line-height: inherit;             text-align: left;             }             .invoice-box table td {             padding: 5px;             vertical-align: top;             }             .invoice-box table tr td:nth-child(2) {             text-align: right;             }             .invoice-box table tr.top table td {             padding-bottom: 20px;             }             .invoice-box table tr.top table td.title {             font-size: 45px;             line-height: 45px;             color: #333;             }             .invoice-box table tr.information table td {             padding-bottom: 40px;             }             .invoice-box table tr.heading td {             background: #eee;             border-bottom: 1px solid #ddd;             font-weight: bold;             }             .invoice-box table tr.details td {             padding-bottom: 20px;             }             .invoice-box table tr.item td {             border-bottom: 1px solid #eee;             }             .invoice-box table tr.item.last td {             border-bottom: none;             }             .invoice-box table tr.total td:nth-child(2) {             border-top: 2px solid #eee;             font-weight: bold;             }             @media only screen and (max-width: 600px) {             .invoice-box table tr.top table td {             width: 100%;             display: block;             text-align: center;             }             .invoice-box table tr.information table td {             width: 100%;             display: block;             text-align: center;             }             }          </style>       </head>       <body>          <div class="invoice-box">             <table cellpadding="0" cellspacing="0">                <tr class="top">                   <td colspan="2">                      <table>                         <tr>                            <td class="title"><img src="https://i2.wp.com/cleverlogos.co/wp-content/uploads/2018/05/reciepthound_1.jpg?fit=800%2C600&ssl=1"                               style="width:100%; max-width:156px;"></td>                            <td>                               Datum: ${`${today.getDate()}. ${today.getMonth() + 1}. ${today.getFullYear()}.`}                            </td>                         </tr>                      </table>                   </td>                </tr>                <tr class="information">                   <td colspan="2">                      <table>                         <tr>                            <td>                               Customer name: ${name}                            </td>                            <td>                               Receipt number: ${receiptId}                            </td>                         </tr>                      </table>                   </td>                </tr>                <tr class="heading">                   <td>Bought items:</td>                   <td>Price</td>                </tr>                <tr class="item">                   <td>First item:</td>                   <td>${price1}$</td>                </tr>                <tr class="item">                   <td>Second item:</td>                   <td>${price2}$</td>                </tr>             </table>             <br />             <h1 class="justify-center">Total price: ${parseInt(price1) + parseInt(price2)}$</h1>          </div>       </body>    </html>    `; }; 

server/index.js ندرج هذا الملف في ملف server/index.js :

 const pdfTemplate = require('./documents'); 

إنشاء ملفات PDF


تذكر أنه على الخادم سنقوم بإنشاء طريقين. سيكون مسار POST مسؤولاً عن تلقي البيانات من العميل وإنشاء ملف PDF. سيتم استخدام مسار GET لإرسال الملف النهائي إلى العميل.

▍ إنشاء قوات الدفاع الشعبي الطريق


في مسار POST create-pdf ، سنستخدم الأمر pdf.create() ، في اشارة الى الكائن الذي تم استيراده من وحدة html-pdf .

كمعلمة أول من طريقة pdf.create() ، يتم استخدام قالب HTML ، وكذلك البيانات الواردة من العميل.

لإرجاع pdf.create() ، نسمي الأسلوب pdf.create() ، toFile() الاسم الذي نريد تعيينه لوثيقة PDF ، وكذلك وظيفة رد اتصال السهم. هذه الوظيفة ، في حالة حدوث خطأ ، ستنفذ الأمر res.send(Promise.reject()) . في حال سارت الأمور على ما يرام ، res.send(Promise.resolve()) أمر res.send(Promise.resolve()) .

 app.post('/create-pdf', (req, res) => {    pdf.create(pdfTemplate(req.body), {}).toFile('result.pdf', (err) => {        if(err) {            res.send(Promise.reject());        }        res.send(Promise.resolve());    }); }); 

et إحضار قوات الدفاع الشعبي الطريق


في نفس الوقت ، سننشئ مسارًا سيُستخدم بعد إنشاء ملف PDF بنجاح ، بناءً على طلب العميل. نحن هنا نأخذ المستند النهائي res.sendFile() إلى العميل باستخدام res.sendFile() :

 app.get('/fetch-pdf', (req, res) => {    res.sendFile(`${__dirname}/result.pdf`) }) 

وظيفة العميل createAndDownloadPdf


الآن يمكننا العودة إلى رمز العميل ومواصلة العمل على إنشاء createAndDownloadPdf . هنا نقوم بتقديم طلب POST إلى الخادم باستخدام وحدة axios . تبدو هذه الوظيفة الآن كالتالي:

 createAndDownloadPdf = () => {   axios.post('/create-pdf', this.state) } 

إذا كان قد تم إنشاء مستند PDF بعد تنفيذ طلب POST على الخادم ، نحتاج إلى تنفيذ طلب GET ، استجابةً لذلك سيرسل الخادم المستند النهائي إلى العميل.

لتنفيذ نظام السلوك هذا ، نحن بعد استدعاء axios.post() ، ندعو. then() . يتيح لنا ذلك القيام بذلك استجابةً لطلب POST الخاص بالعميل ، ونعيد وعدًا من الخادم يمكن حله أو رفضه بنجاح.

نحن نكمل الوظيفة بالكود التالي:

 .then(() => axios.get('/fetch-pdf', { responseType: 'blob' })) .then(( res) => {}) 

هنا يمكنك الانتباه إلى حقيقة أن blob تستخدم كـ responseType . قبل أن نذهب إلى أبعد من ذلك ، دعونا نتحدث عن ما هو عليه.

كائنات Blob


Blob هو كائن ثابت يمثل بعض البيانات الأولية. غالبًا ما تستخدم هذه الكائنات للعمل مع البيانات التي قد لا تكون بتنسيق JavaScript الأصلي. مثل هذه الكائنات هي سلاسل من البايتات التي تخزن ، على سبيل المثال ، بيانات الملف. قد يبدو أن كائن Blob يقوم بتخزين ارتباط إلى ملف ، لكنه في الحقيقة ليس كذلك. تخزن هذه الكائنات البيانات التي يمكنك العمل معها. على سبيل المثال - يمكن حفظها في الملفات.

إنجاز المشروع


الآن وبعد أن عرفنا كائنات Blob ، يمكننا استخدام مكالمة .then() res.data كائن Blob جديد فيه ، استنادًا إلى res.data. عند إنشاء هذا الكائن ، سنقوم بتمرير المعلمة إلى مُنشئها ، مع الإشارة إلى أن البيانات التي سيخزنها الكائن هي من النوع application/pdf . بعد ذلك ، يمكننا استخدام saveAs() ، التي تم استيرادها من وحدة حفظ file-saver ، وحفظ البيانات في ملف. نتيجة لذلك ، createAndDowndloadPdf طريقة createAndDowndloadPdf كما هو موضح أدناه:

   createAndDownloadPdf = () => {    axios.post('/create-pdf', this.state)      .then(() => axios.get('fetch-pdf', { responseType: 'blob' }))      .then((res) => {        const pdfBlob = new Blob([res.data], { type: 'application/pdf' });        saveAs(pdfBlob, 'newPdf.pdf');      })  } 

هذا هو ما تبدو عليه واجهة المتصفح.


تطبيق في المتصفح

بعد ملء الحقول والنقر على زر Download PDF ، يتم نقل البيانات إلى الخادم ويتم تنزيل مستند PDF منه.


تحميل قوات الدفاع الشعبي

وهنا هي وثيقة PDF نفسها.


البرمجيات التي تم إنشاؤها قوات الدفاع الشعبي

النتائج


لقد بحثنا في آلية تسمح لك بإنشاء ملفات PDF برمجياً. هنا مستودع جيثب مع رمز المشروع. نأمل أن تجد الأفكار التي قابلتها في هذه المواد تطبيقًا في تطورك.

أعزائي القراء! كيف يمكنك حل مشكلة إنشاء ملفات PDF برمجياً باستخدام Node.js؟

Source: https://habr.com/ru/post/ar460018/


All Articles