如何用TypeScript基于WebService数据生成、打印并下载PDF销售合同
Hey there! Let me walk you through a practical, battle-tested approach to generate a sales contract PDF using TypeScript, pull data from a WebService, and add print/download functionality. I’ve built similar workflows for e-commerce platforms before, so here’s what works smoothly:
First, let’s pick tools that play well with TypeScript and simplify PDF generation:
- pdfmake: A client-side library with excellent TypeScript support, perfect for building structured PDFs (like contracts with tables and styled text) without needing server-side processing.
- axios: A promise-based HTTP client to fetch data from your WebService—clean, type-safe, and easy to integrate.
2.1 Set Up Dependencies
Start by installing the required packages and their TypeScript types:
npm install pdfmake axios @types/pdfmake @types/axios --save
2.2 Fetch Data from WebService
First, define a TypeScript interface to enforce the structure of your contract data—this keeps your code type-safe and catches mismatches early:
interface SalesContractData { contractNumber: string; customerName: string; customerAddress: string; items: Array<{ productName: string; quantity: number; unitPrice: number; total: number; }>; totalAmount: number; signingDate: string; } async function fetchContractData(contractId: string): Promise<SalesContractData> { try { // Replace with your WebService endpoint const response = await axios.get(`/api/contracts/${contractId}`); return response.data; } catch (error) { console.error('Failed to fetch contract data:', error); throw new Error('Could not retrieve contract information. Please try again later.'); } }
2.3 Generate the PDF Contract
Use pdfmake to build a styled, data-driven PDF. We’ll define a document structure that maps directly to your contract data:
import pdfMake from 'pdfmake/build/pdfmake'; import pdfFonts from 'pdfmake/build/vfs_fonts'; // Load default fonts (required for pdfmake to render text) pdfMake.vfs = pdfFonts.pdfMake.vfs; function generateContractPdf(data: SalesContractData) { const docDefinition = { content: [ { text: 'OFFICIAL SALES CONTRACT', style: 'header' }, { text: `Contract #: ${data.contractNumber}`, style: 'subheader' }, { text: `Date: ${data.signingDate}`, style: 'subheader' }, { text: '\n' }, { text: 'Customer Details', style: 'sectionHeader' }, { text: `Name: ${data.customerName}` }, { text: `Address: ${data.customerAddress}` }, { text: '\n' }, { text: 'Items Purchased', style: 'sectionHeader' }, { table: { headerRows: 1, widths: ['*', 'auto', 'auto', 'auto'], // Responsive column widths body: [ ['Product', 'Quantity', 'Unit Price', 'Total'], ...data.items.map(item => [ item.productName, item.quantity.toString(), `$${item.unitPrice.toFixed(2)}`, `$${item.total.toFixed(2)}` ]), [{ text: 'Grand Total', colSpan: 3, alignment: 'right' }, {}, {}, `$${data.totalAmount.toFixed(2)}`] ] } }, { text: '\n' }, { text: 'Terms & Conditions', style: 'sectionHeader' }, { text: '1. Payment is due within 30 days of contract signing.' }, { text: '2. All products are sold as-is, unless covered by a manufacturer warranty.' }, { text: '3. The seller retains ownership until full payment is received.' }, { text: '\n' }, { text: 'Customer Signature:', style: 'signature' }, { text: '_________________________', style: 'signatureLine' }, { text: data.customerName, style: 'signatureName' } ], styles: { header: { fontSize: 22, bold: true, alignment: 'center', margin: [0, 0, 0, 15] }, subheader: { fontSize: 14, bold: true, alignment: 'right' }, sectionHeader: { fontSize: 16, bold: true, margin: [0, 10, 0, 5] }, signature: { fontSize: 14, margin: [0, 25, 0, 5] }, signatureLine: { margin: [0, 0, 0, 5] }, signatureName: { fontSize: 12, italic: true } }, pageSize: 'A4', pageMargins: [40, 60, 40, 60] }; return pdfMake.createPdf(docDefinition); }
2.4 Add Download & Print Functionality
Finally, tie everything together into a function that fetches data, generates the PDF, and lets users download or print it:
async function handleContractActions(contractId: string) { try { const contractData = await fetchContractData(contractId); const pdf = generateContractPdf(contractData); // Trigger download with a meaningful filename pdf.download(`SalesContract_${contractData.contractNumber}.pdf`); // Open browser's print dialog for the PDF pdf.print(); } catch (error) { alert('Oops! Failed to generate your contract: ' + (error as Error).message); } } // Example: Attach to a button click // document.getElementById('generateContractBtn')?.addEventListener('click', () => { // handleContractActions('CON-2024-001'); // });
- Type Safety: Always define interfaces for your WebService responses—this prevents unexpected data from breaking your PDF layout.
- Font Customization: If you need branded fonts, you can embed them into pdfmake by following their font embedding guide (no external links needed, just base64 encode your font files and add them to the
pdfMake.vfsobject). - Error Handling: Add user-friendly alerts for failed data fetch or PDF generation—don’t leave users guessing what went wrong.
- Testing: Test edge cases like long customer addresses, empty item lists, or large numbers to ensure the PDF layout doesn’t break.
- jsPDF: Great if you need to draw custom shapes or have full control over pixel-perfect layouts, but requires more manual work than pdfmake.
- html2pdf.js: Perfect if you already have an HTML template for your contract—just convert the DOM element directly to a PDF, which is handy for reusing existing UI components.
内容的提问来源于stack exchange,提问作者Lonna

