【发布时间】:2021-03-03 17:22:29
【问题描述】:
我正在尝试根据我在 HTML 中的设计生成 PDF。
我正在使用pupeeter 和后端mongodb
我在前端有不同的模板,它们有不同的 HTML。
我试图用puppeeter 保存,但我无法更进一步。
这是我尝试过的代码。
PDF Controller
const puppeteer = require('puppeteer');
const fs = require('fs');
module.exports = {
async createPDF () {
// launch a new chrome instance
const browser = await puppeteer.launch({
headless: true
})
// create a new page
const page = await browser.newPage()
// set your html as the pages content
const html = fs.readFileSync(`${__dirname}/template.html`, 'utf8')
// create a pdf buffer
const pdfBuffer = await page.pdf({
format: 'A4'
})
// or a .pdf file
await page.pdf({
format: 'A4',
path: `${__dirname}/cv.pdf`
})
// close the browser
await browser.close()
}
}
routes.post("/pdf", PDFController.createPDF);
FE 中的服务。
@Injectable({providedIn: "root"})
export class PdfService {
public baseUrl = environment.backend;
constructor(private http: HttpClient) {
}
public setPDF(data) {
return this.http.post(`${this.baseUrl}/pdf`, data).subscribe(res => console.log(res));
}
getPdf() {
const httpOptions = {
responseType: "blob" as "json",
};
return this.http.get(`${this.baseUrl}/help/cv`, httpOptions);
}
}
这是我的 TS。
public downloadPdf() {
this.pdfService.setPDF(this.model);
this.pdfService.getPdf().subscribe((data) => {
console.log(data);
});
}
这是我的“HTML”代码。
<app-paginated-view [pageSize]="'A4'" *ngIf="model && model.theme === 'firstTemplate'"
[pageNumbers]="model?.showPageNumbers"
class="Grid-grid-column" id="content"
[style.color]="model.style?.color">
<ng-container>
<div class="Grid-grid-row" pageContent (click)="setFirstCat()" class="row" #content
[ngClass]="{ 'isCatActive': selectedFirstCat}">
<div class="Grid-grid-column Grid-grid-column-12">
<div class="Header-header-header Header-header-minHeight first-template-header">
<div class="Title-title-titleWrapper first-template-titleWrapper">
<h4 *ngIf="model?.hideName">{{model.personalData[0].firstName}} {{model.personalData[0].lastName}}</h4>
<h5>{{model?.newJobTitle}}</h5>
<div [innerHtml]="model?.description"></div>
</div>
<div class="Photo-photo-photoWrapper" *ngIf="model?.showCVPhoto">
<div class="Photo-photo-photo first-template-photo">
<img [src]="model?.photo" height="100" style="cursor: pointer">
</div>
</div>
</div>
<div *ngIf="selectedFirstCat">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-pencil addIconTop" (click)="editHeaderDialog({edit: true, model: model})"></li>
<li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
</ul>
</div>
</div>
</div>
</div>
</ng-container>
<ng-container class="Grid-grid-grid">
<ng-container class="Unit-unit-unitGroup" *ngFor="let personalData of model?.personalData; let id = index">
<div pageContent *ngIf="personalData.visible">
<div class="Unit-unit-unitGroup" pageContent
[ngClass]="{ 'isCatActive': selectedCategory === category.PersonalData}">
<div *ngIf="selectedCategory === category.PersonalData">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
<li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</div>
<div pageContent class="col-md-12" (click)="setCategory(category.PersonalData)">
<div class="row height">
<div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
<h3 class="first-template-paragraphTitle Paragraph-paragraph-title height" [style.color]="model.style.color">
<div class="Text-text-wrapper">
<div class="Text-Text-text" >{{'category.PersonalData' | translate}}</div>
</div>
</h3>
</div>
</div>
<div pageContent class="container-fluid">
<app-personal-data [personalData]="personalData" [model]="model" [id]="id"
(deselectCategory)="test($event)">
</app-personal-data>
</div>
</div>
</div>
</ng-container>
<!-- Career Component -->
<ng-container *ngFor="let careers of model?.careers; let id = index" class="Unit-unit-unitGroup">
<div *ngIf="selectedCategory === category.Career">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
<li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
<button (click)="deleteCareerCategory(id)" class="btn"><i
class="fa fa-trash deleteIconRight"></i></button>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</div>
<div pageContent class="col-md-12" (click)="setCategory(category.Career)">
<div class="row height">
<div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
<h3 class="first-template-paragraphTitle Paragraph-paragraph-title height" [style.color]="model.style.color">
<div class="Text-text-wrapper">
<div class="Text-Text-text">{{'category.Career' | translate}}</div>
</div>
</h3>
</div>
</div>
<ng-container *ngFor="let careerObj of careers.subCategories; let i = index">
<div pageContent class="container-fluid">
<div pageContent [ngClass]="{ 'isActive': selectedCareerIndex === i}">
<div class="Line-line-container" (click)="setCareerIndex(i)">
<div class="Line-line-line">
<div class="Field-field-fieldBase first-template-fieldField">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text">
{{careerObj.startDate | date:'MM/yyyy'}}
<div class="float-right" *ngIf="!careerObj.today">{{careerObj.endDate | date:'MM/yyyy'}}</div>
<div class="float-right" *ngIf="careerObj.today">{{'career.present' | translate}}</div>
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text-wrapper">
<b>{{careerObj.role}}</b>
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text-wrapper">
{{careerObj.name}}
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text-wrapper" aria-multiline="true"
[innerHTML]="careerObj.description">
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue" *ngIf="careerObj.showCompanyUrl">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text-wrapper">
<a target="_blank" [href]="careerObj.companyUrl">{{careerObj.companyUrl}}
</a>
</div>
</div>
</div>
<ng-container pageContent *ngIf="selectedCareerIndex === i">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-pencil addIconTop"
(click)="editCareer({edit: true, career: careerObj, model: model})">
</li>
<li class="fa fa-plus addIconBottom"
(click)="addCareer({edit: false, model: model, career: false})"></li>
<button [disabled]="careers.subCategories.length < 2" (click)="deleteCareerSubCategory(i)"
class="btn"><i class="fa fa-trash deleteIconRight"></i></button>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</ng-container>
</div>
</div>
</div>
</div>
</ng-container>
<ng-container *ngFor="let emptyObj of careers.emptySubContents; let iEmpty = index">
<app-empty-object pageContent [emptyObj]="emptyObj" [iEmpty]="iEmpty" [model]="model" [isFromCareer]="true">
</app-empty-object>
</ng-container>
</ng-container>
<!--Education Component-->
<ng-container *ngFor="let education of model?.education; let index = index" class="Unit-unit-unitGroup">
<ng-container *ngIf="education.subCategories.length > 0">
<div *ngIf="selectedCategory === category.Education"
[ngClass]="{ 'isCatActive': selectedCategory === category.Education}">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
<li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
<li class="fa fa-trash deleteIconRight" (click)="deleteEducationCategory(index)"></li>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</div>
<div pageContent class="col-md-12" (click)="setCategory(category.Education)">
<div class="row height">
<div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
<h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
<div class="Text-text-wrapper">
<div class="Text-Text-text">{{'category.Education' | translate}}</div>
</div>
</h3>
</div>
</div>
<ng-container *ngFor="let educationObj of education.subCategories; let i = index">
<div pageContent class="container-fluid">
<div pageContent [ngClass]="{ 'isActive': selectedIndex === i}">
<div pageContent class="Line-line-container" (click)="setIndex(i)">
<div class="Line-line-line">
<div class="Field-field-fieldBase first-template-fieldField">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text">
{{educationObj.startDate | date:'MM/yyyy'}}
<div class="float-right" *ngIf="!educationObj.today">{{educationObj.endDate | date:'MM/yyyy'}}
</div>
<div class="float-right" *ngIf="educationObj.today">{{'present' | translate}}</div>
</div>
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div class="Text-Text-text-wrapper">
<b>{{educationObj.title}}</b>
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div class="Text-Text-text-wrapper">
{{educationObj.name}}
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div class="Text-Text-text-wrapper" aria-multiline="true" [innerHTML]="educationObj.description">
</div>
</div>
</div>
<ng-container pageContent *ngIf="selectedIndex === i">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-pencil addIconTop"
(click)="editEducation({edit: true, education: educationObj, model: model})"></li>
<button class="btn"><i class="fa fa-plus addIconBottom"
(click)="addEducation({edit: false, model: model})"></i></button>
<button [disabled]="education.subCategories.length === 1"
(click)="deleteEducationSubCategory(i)" class="btn"><i
class="fa fa-trash deleteIconRight"></i></button>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</ng-container>
</div>
</div>
</div>
<!--- <app-education pageContent [educationObj]="educationObj" [id]="i" [education]="education" [model]="model">
</app-education> -->
</ng-container>
</ng-container>
</ng-container>
<!-- Skills Component-->
<ng-container *ngFor="let skills of model?.skills; let i = index" class="Unit-unit-unitGroup">
<div class="Unit-unit-unitGroup" pageContent [ngClass]="{ 'isCatActive': selectedCategory === category.Skills}">
<div *ngIf="selectedCategory === category.Skills" (clickOutsideInner)="removeClick()">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
<li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
<li class="fa fa-trash deleteIconRight" (click)="deleteSkillsCategory(i)"></li>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</div>
<div pageContent class="col-md-12" (click)="setCategory(category.Skills)">
<div class="row height">
<div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
<h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
<div class="Text-text-wrapper">
<div class="Text-Text-text">{{'category.Skills' | translate}}</div>
</div>
</h3>
</div>
</div>
<div pageContent class="container-fluid">
<ng-container *ngFor="let skillObj of skills.subCategories; let i = index" class="col-md-12">
<app-skills pageContent [skillObj]="skillObj" [id]="i" [skills]="skills" [model]="model"></app-skills>
</ng-container>
</div>
</div>
</ng-container>
<!-- Files Component -->
<ng-container *ngFor="let file of model?.files; let index = index" class="Unit-unit-unitGroup">
<div *ngIf="selectedCategory === category.Files"
[ngClass]="{ 'isCatActive': selectedCategory === category.Files}">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-plus addIconTop" (click)="openDialog({model:model})"></li>
<li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
<li class="fa fa-trash deleteIconRight" (click)="deleteEducationCategory(index)"></li>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</div>
<div pageContent class="col-md-12" (click)="setCategory(category.Files)">
<div class="row height">
<div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
<h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
<div class="Text-text-wrapper">
<div class="Text-Text-text">{{'category.Files' | translate}}</div>
</div>
</h3>
</div>
</div>
<ng-container *ngFor="let fileObj of file.subCategories; let i = index" class="col-md-12">
<div pageContent class="container-fluid">
<div pageContent [ngClass]="{ 'isActive': selectedFileIndex === i}">
<div pageContent class="Line-line-container" (click)="setFileIndex(i)">
<div class="Line-line-line">
<div class="Field-field-fieldBase first-template-fieldField">
<div pageContent class="Text-Text-wrapper">
<div class="Text-Text-text">
{{fileObj.name}}</div>
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue" *ngIf="fileObj.link">
<div class="Text-Text-wrapper">
<div class="Text-Text-text-wrapper">
<a target="_blank" [href]="fileObj.link">{{fileObj.link}}
</a>
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div class="Text-Text-text-wrapper" aria-multiline="true" [innerHTML]="fileObj.description">
</div>
</div>
</div>
<ng-container pageContent *ngIf="selectedFileIndex === i">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-pencil addIconTop" (click)="editFile({edit: true, file: fileObj, model: model})">
</li>
<button (click)="addFile({edit: false, model: model})" class="btn"><i
class="fa fa-plus addIconBottom"></i></button>
<button [disabled]="file.subCategories.length === 1" (click)="deleteSubFile(i)" class="btn"><i
class="fa fa-trash deleteIconRight"></i></button>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</ng-container>
</div>
</div>
</div>
</ng-container>
</ng-container>
<!-- Empty Category -->
<ng-container *ngFor="let emptyCat of model?.emptyCategory; let i = index" class="Unit-unit-unitGroup">
<div *ngIf="selectedCategory === category.Another"
[ngClass]="{ 'isCatActive': selectedCategory === category.Another}">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-pencil addIconTop"
(click)="editEmptyCategory({edit: true, model: model, emptyCategory: emptyCat})"></li>
<li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
<li class="fa fa-trash deleteIconRight" (click)="deleteEmptyCategory(i)"></li>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</div>
<div pageContent class="col-md-12" (click)="setCategory(category.Another)">
<div class="row height">
<div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
<h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
<div class="Text-text-wrapper">
<div class="Text-Text-text">{{emptyCat.name}}</div>
</div>
</h3>
</div>
</div>
<ng-container *ngFor="let emptySubObj of emptyCat.emptySubContents; let i = index" class="col-md-12">
<div pageContent class="container-fluid">
<div pageContent [ngClass]="{ 'isActive': selectedEmptySubCat === i}">
<div pageContent class="Line-line-container" (click)="setEmptySubCatIndex(i)">
<div class="Line-line-line">
<div class="Field-field-fieldBase first-template-fieldField">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text">
{{emptySubObj.name}}</div>
</div>
</div>
</div>
<div class="Field-field-fieldBase first-template-fieldValue">
<div class="Text-Text-wrapper">
<div pageContent class="Text-Text-text-wrapper" aria-multiline="true"
[innerHTML]="emptySubObj.description">
</div>
</div>
</div>
<ng-container pageContent *ngIf="selectedEmptySubCat === i">
<div clickOutside (clickOutside)="removeClick()">
<ul>
<li class="fa fa-pencil addIconTop"
(click)="editEmptySubCat({edit: true, empty: emptySubObj, model: model, emptySubCat: true})">
</li>
<button (click)="addEmptySubCat({edit: false, model: model, emptySubCat: true})" class="btn"><i
class="fa fa-plus addIconBottom"></i></button>
<button [disabled]="emptyCat.emptySubContents.length === 1" (click)="deleteEmptyCatFile(i)"
class="btn"><i class="fa fa-trash deleteIconRight"></i></button>
<li class="fa fa-arrow-down moveIconDown"></li>
<li class="fa fa-arrow-up moveIconTop"></li>
</ul>
</div>
</ng-container>
</div>
</div>
</div>
</ng-container>
</ng-container>
<!--Here closes the container for the whole page-->
</ng-container>
<div class="PageNumber-page-number-container">
<div class="PageNumber-page-number-pageNumber">
<span class="PageNumber-page-number-page">
Page
</span>
<span>1 / 2</span>
</div>
</div>
</app-paginated-view>
我基于这个页面在这里他们如何下载 PDF。
他们采用前端的设计,他们可以在 UI 中给出的设计和日期内下载 pdf。
有可能这样做吗?
在我的 PDF 中,我无法复制文本或编辑文本,image 或 PDF 非常小。
如果有人不明白,我可以添加更多代码或更好地解释它 请参阅所附照片 pdf 的外观。 我在 google chrome 中调整了大小只是为了看得更好,但它是 A4 页面的普通字体。
这是我的 UI 的照片。
【问题讨论】:
-
我也遇到过同样的问题。以这种方式复制或编辑文本是不可能的(AFAIK)。 html2canvas 本质上只是对您传递的元素进行“截图”,所以它基本上只是一张图片。我建议使用 puppeteer 和任何类型的模板框架(我使用 Nunjucks 完成)在您的后端生成 PDF。一些额外的词,但最终值得。 (pptr.dev, mozilla.github.io/nunjucks/templating.html)
-
@chrnx 感谢您的回答。我查看了
pupeteer,但我没有找到太多关于它的文档,如何实现使用哪些类以及类似的东西。 -
您可以在 puppeteer 文档中搜索“PDF”,您将看到如何创建并下载它。您只需要提供一个带有
.setContent函数的模板就可以了。如果你需要,我可以用一些更详细的例子写一个答案 -
@chrnx 如果您能写下答案,您将非常友好。
-
我使用 pdfmake 和 Angular 以及交互式 SVG 和文本。这都是客户端,并且运行良好。也许您可以按照stackoverflow.com/questions/34049956/… 的方式做一些事情?
标签: javascript html angular typescript pdf