Cách chia trang HTML thành khổ A4 trong Angular 9

Sep 30 2020

Tôi đang cố gắng tạo ra một cái gì đó giống như trong Xing the CV maker -> https://lebenslauf.com/.

Tôi có nhiều Mảng đối tượng khác nhau. Nhưng tôi không thể tạo các trang A4 sẽ hiển thị dữ liệu và nếu mảng đó lớn hơn một trang, hãy tạo trang A4 mới và thêm dữ liệu vào đó. Hàm cần phải như vậy nếu mảng lớn hơn cho một kích thước, thì hãy tạo một trang a4 mới và đặt dữ liệu ở đó. Trong stackblitz, tôi đã thêm một mảng và một số văn bản ngẫu nhiên và thiết kế một chữ cái A4. Tôi đã tham khảo câu hỏi và câu trả lời này, nhưng không giúp tôi quá nhiều. CSS để đặt khổ giấy A4 .
Tôi đã cố gắng phân trang giả mạo và tạo kích thước A4 nhưng không hiệu quả.
Tôi đã xem mã này với jquery ở đó. Nó hoạt động, nhưng tôi không thể biên dịch nó trong Angular.https://jsfiddle.net/tm637ysp/10/ Ai đó có thể giúp tôi ở đây?

Tôi đã tạo hai dự án trong stackblitz. Có thể họ sẽ giúp.
https://stackblitz.com/edit/angular-ivy-fjhpdu.
https://stackblitz.com/edit/angular-ivy-uzmdwg

Tôi muốn mở lại câu hỏi này vì câu trả lời đã được phê duyệt từ @HirenParekh, câu trả lời không hoạt động như tôi muốn. Vấn đề là bây giờ, Nếu văn bản trong một Đối tượng, nó rất lớn, nó sẽ không thêm một trang mới trong thời gian thực, mà chỉ khi tôi tải lại trang. Tôi nghĩ rằng mã sẽ dành cho thêm trang mới hoặc trang chỉnh sửa sẽ chỉ được hiển thị trong ngOnInit. Chỉ thị được cung cấp để thực hiện công việc đó, tôi nghĩ rằng nó không hoạt động như ngoại lệ. Đây là stackblitznhững gì anh ấy đã cố gắng giúp tôi.https://stackblitz.com/edit/angular-ivy-zjf8rv

Đây là mã tôi đang cố gắng hiển thị dữ liệu.

    <div style="transition: transform 0.25s ease 0s;transform: scale(1.3);transform-origin: 50% 0px 0px;backface-visibility: hidden;perspective: 1000px;display: block;margin: 0px 11.5385%;font-size:10px;width: 76.9231%;-webkit-font-smoothing: antialiased;">
<app-paginated-view [pageSize]="'A4'" *ngIf="model" class="Grid-grid-column">
  <div pageContent class="row">
    <div class="col col-lg-7">
      <h4>{{currentUser?.firstName}} {{currentUser?.lastName}}</h4>
    </div>
    <div class="col text-right">
      <input type="file" accept="image/*" (change)="readUrl($event)"> <img [src]="url" (change)="readUrl($event)" height="128" style="cursor:  pointer">

    </div>
  </div>

  <div pageContent class="Unit-unit-unitGroup"
   *ngFor="let personalData of model.personalData; let id = index">
   <div pageContent [ngClass]="{ 'isCatActive': selectedCategory === category.PersonalData}">

   <ng-container *ngIf="selectedCategory === category.PersonalData" clickOutside (clickOutside)="removeClick()">
    <ul>
      <li class="fa fa-plus addIconTop" (click)="openDialog()"></li>
      <li class="fa fa-plus addIconBottom" (click)="openDialog()"></li>
      <li class="fa fa-trash deleteIconRight" (click)="deleteCategory(index)"></li>
      <li class="fa fa-arrow-down moveIconDown"></li>
      <li class="fa fa-arrow-up moveIconTop"></li>
    </ul>
  </ng-container>

    <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"></div>
      <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
        <div class="Text-text-wrapper">
          <div class="Text-Text-text">{{'category.PersonalData' | translate}}</div>
        </div>
      </h3>
    </div>
    </div>
    <div pageContent class="container-fluid">
      <ng-container>
      <app-personal-data [personalData]="personalData" [model]="model" [id]="id">
    </app-personal-data>
  </ng-container>
  </div>
  </div>
  </div>

    <!-- Career Component -->
    <ng-container *ngFor="let careers of model.careers" class="Unit-unit-unitGroup">
      <div pageContent class="col-md-12">
        <div class="row height">
        <div  class="col-md-4 col-sm-6 text-right tLine"></div>
        <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
          <div class="Text-text-wrapper">
            <div class="Text-Text-text">{{'category.Career' | translate}}</div>
          </div>
        </h3>
      </div>
      </div>
      <div class="container-fluid" pageContent>
      <ng-container *ngFor="let careerObj of careers.subCategories; let i = index">
      <app-career [careerObj]="careerObj" [id]="i" [career]="careers" [model]="model"></app-career>
      </ng-container>
      <ng-container *ngFor="let emptyObj of careers.emptySubContents; let iEmpty = index">
        <app-empty-object [emptyObj]="emptyObj" [iEmpty]="iEmpty" [model]="model" [isFromCareer]="true"></app-empty-object>
      </ng-container>
        </div>
    </ng-container>

    <!--Education Component-->
    <ng-container *ngFor="let education of model.education" class="Unit-unit-unitGroup">
      <div pageContent [ngClass]="{ 'isCatActive': selectedCategory === category.Education}">
        <ng-container *ngIf="selectedCategory === category.Education" clickOutside (clickOutside)="removeClick()">
          <ul>
            <li class="fa fa-plus addIconTop" (click)="openDialog()"></li>
            <li class="fa fa-plus addIconBottom" (click)="openDialog()"></li>
            <li class="fa fa-trash deleteIconRight" (click)="deleteCategory(index)"></li>
            <li class="fa fa-arrow-down moveIconDown"></li>
            <li class="fa fa-arrow-up moveIconTop"></li>
          </ul>
        </ng-container>
      <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"></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>
      <div pageContent class="container-fluid">
      <ng-container *ngFor="let educationObj of education.subCategories; let i = index" class="col-md-12">
        <app-education [educationObj]="educationObj" [id]="i" [education]="education" [model]="model"></app-education>
      </ng-container>
      </div>
      </div>
    </ng-container>


  <!-- Skills Component-->
    <ng-container *ngFor="let skills of model.skills" class="Unit-unit-unitGroup">
    <div pageContent [ngClass]="{ 'isCatActive': selectedCategory === category.Skills}">
    <ng-container clickOutside *ngIf="selectedCategory === category.Skills" (clickOutside)="removeClick()">
      <ul>
        <li class="fa fa-plus addIconTop" (click)="openDialog()"></li>
        <li class="fa fa-plus addIconBottom" (click)="openDialog()"></li>
        <li class="fa fa-trash deleteIconRight" (click)="deleteCategory(index)"></li>
        <li class="fa fa-arrow-down moveIconDown"></li>
        <li class="fa fa-arrow-up moveIconTop"></li>
      </ul>
    </ng-container>
    <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"></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 [skillObj]="skillObj" [id]="i" [skills]="skills" [model]="model"></app-skills>
      </ng-container>
      </div>
    </div>
  </ng-container>


</app-paginated-view>
</div>

Đây là CSS

  .A4 {
  width: 595px;
  height: 842px;
  padding: 25px 25px;
  position: relative;
}

 

Đây là json

{
    "personalData": [
        {
  
            "firstName": "Max",
            "lastName": "Muster",
            "email": "[email protected]",
            "birthday": "2020-09-25T00:00:00.000Z",
            "telephone": "0123456789",
            "job": "Freelancer",
            "country": "Germany",
            "postalCode": 12345,
            "city": "None",
            "title": 2,
            "gender": 0,
            "street": "Musterman 12",
            "state": "",
            "status": 1,
            "showBirthday": true
        }
    ],
    "skills": [
        {
            "subCategories": [
                {
                    "languages": [
                        {
                            "name": "languages.de",
                            "rate": 5
                        },
                        {
                            "name": "languages.al",
                            "rate": 1
                        },
                        {
                            "name": "languages.en",
                            "rate": 5
                        },
                        {
                            "name": "languages.fr",
                            "rate": 4
                        },
                        {
                            "name": "languages.it",
                            "rate": 4
                        }
                    ],
                    "pcKnowledge": [
                        {
                            "_id": "5f5ca07e4dba443f786ea7ae",
                            "name": "Word"
                        },
                        {
                            "_id": "5f5ca07e4dba443f786ea7af",
                            "name": "Adobe Photoshop"
                        },
                        {
                            "_id": "5f5fd46bb21df2444c39f317",
                            "name": "Test"
                        },
                        {
                            "_id": "5f5fd46bb21df2444c39f318",
                            "name": "Excel"
                        },
                        {
                            "_id": "5f5fd46bb21df2444c39f319",
                            "name": "Ja"
                        },
                        {
                            "_id": "5f72339552009b4244391972",
                            "name": "Powerpoint"
                        }
                    ],
                    "skillsOffer": [
                        {
                            "_id": "5f4a4e2d718d33092df2c327",
                            "name": "Angular"
                        },
                        {
                            "_id": "5f4a4e2d718d33092df2c327",
                            "name": "Java"
                        },
                        {
                            "_id": "5f4a4e2d718d33092df2c327",
                            "name": "Typescript"
                        },
                        {
                            "_id": "5f4a4e2d718d33092df2c327",
                            "name": "html"
                        },
                        {
                            "name": "Javascript"
                        }
                    ],
                    "driveLicenses": [
                        {
                            "_id": "5f5ca07e4dba443f786ea7ac",
                            "name": "B"
                        },
                        {
                            "_id": "5f5ca07e4dba443f786ea7ad",
                            "name": "C"
                        },
                        {
                            "_id": "5f5f204faa5d0205180bd581",
                            "name": "B"
                        }
                    ],
                    "name": "",
                    "qualifications": ""
                }
            ]
        }
    ]
}

Và đây là Paginated view class for adding new page and splitting pages

<!-- display: none style will any child that does not have #pageContent local variable defined -->
<div class="content-wrapper" #contentWrapper style="display: block">
</div>
<div class="paginated-view" #paginatedView>

</div>

export class PaginatedViewComponent implements AfterViewInit {
  @Input() pageSize: "A3" | "A4" = "A4";

  @ViewChild("paginatedView") paginatedView: ElementRef<HTMLDivElement>;

  @ViewChild("contentWrapper") contentWrapper: ElementRef<HTMLDivElement>;

  @ContentChildren(PageContentDirective, { read: ElementRef })
  elements: QueryList<ElementRef>;

  constructor(private changeDetector: ChangeDetectorRef ) {}

  ngAfterViewInit(): void {
    this.updatePages();

    // when ever childs updated call the updatePagesfunction
    this.elements.changes.subscribe((el) => {
      this.updatePages();
    });
  }


  updatePages(): void {
    // clear paginated view
    this.paginatedView.nativeElement.innerHTML = "";

    // get a new page and add it to the paginated view
    let page = this.getNewPage();
    this.paginatedView.nativeElement.appendChild(page);

    let lastEl: HTMLElement;
    // add content childrens to the page one by one
    this.elements.forEach((elRef) => {
      const el = elRef.nativeElement;

      // if the content child height is larger than the size of the page
      // then do not add it to the page

      if (el.clientHeight > page.clientHeight) {
        return;
      }
      // add the child to the page
      page.appendChild(el);

      // after adding the child if the page scroll hight becomes larger than the page height
      // then get a new page and append the child to the  new page
      if (page.scrollHeight > page.clientHeight) {
        page = this.getNewPage();
        this.paginatedView.nativeElement.appendChild(page);
        page.appendChild(el);
      }
      lastEl = el;
    });
    this.changeDetector.detectChanges();

    // bring the element in to view port
   // lastEl.scrollIntoView({ behavior: "smooth", block: "nearest" });
  }

  getNewPage(): HTMLDivElement {
    const page = document.createElement("div");
    page.classList.add("page");
    page.classList.add(this.pageSize);
    return page;
  }
}
    @Directive({
  // tslint:disable-next-line: directive-selector
  selector: "[pageContent]"
})
export class PageContentDirective {

}

Trả lời

4 HirenParekh Oct 15 2020 at 13:15

Đó là tất cả về việc phân chia nội dung đã cho để phù hợp với kích thước trang cho sẵn.

Chúng tôi có thể tạo một thành phần sẽ xử lý chức năng phân chia cho chúng tôi. đây là một StackBlitz Demo .

Và đây là một lời giải thích ngắn gọn.

Sử dụng trình ContentChildrentrang trí để quan sát sự thay đổi trong nội dung. mỗi khi nội dung thay đổi, chúng tôi sẽ chạy logic tạo trang.

import {
  AfterContentInit,
  AfterViewInit,
  Component,
  ContentChildren,
  ElementRef,
  Input,
  OnInit,
  QueryList,
  ViewChild
} from "@angular/core";

@Component({
  selector: "app-paginated-view",
  templateUrl: "paginated-view.component.html",
  styleUrls: ["paginated-view.component.scss"]
})
export class PaginatedViewComponent implements AfterViewInit {
  @Input() pageSize: "A3" | "A4" = "A4";

  @ViewChild("paginatedView") paginatedView: ElementRef<HTMLDivElement>;

  @ViewChild("contentWrapper") contentWrapper: ElementRef<HTMLDivElement>;

  @ContentChildren("pageContent", { read: ElementRef }) elements: QueryList<
    ElementRef
  >;

  constructor() {}

  ngAfterViewInit(): void {
    this.updatePages();

    // when ever childs updated call the updatePagesfunction
    this.elements.changes.subscribe(el => {
      this.updatePages();
    });
  }

  updatePages(): void {
    // clear paginated view
    this.paginatedView.nativeElement.innerHTML = "";

    // get a new page and add it to the paginated view
    let page = this.getNewPage();
    this.paginatedView.nativeElement.appendChild(page);

    let lastEl: HTMLElement;
    // add content childrens to the page one by one
    this.elements.forEach(elRef => {
      const el = elRef.nativeElement;

      // if the content child height is larger than the size of the page
      // then do not add it to the page
      if (el.clientHeight > page.clientHeight) {
        return;
      }
      // add the child to the page
      page.appendChild(el);

      // after adding the child if the page scroll hight becomes larger than the page height
      // then get a new page and append the child to the  new page
      if (page.scrollHeight > page.clientHeight) {
        page = this.getNewPage();
        this.paginatedView.nativeElement.appendChild(page);
        page.appendChild(el);
      }
      lastEl = el;
    });

    //bring the element in to view port
    lastEl.scrollIntoView({ behavior: "smooth", block: "nearest" });
  }

  getNewPage(): HTMLDivElement {
    const page = document.createElement("div");
    page.classList.add("page");
    page.classList.add(this.pageSize);
    return page;
  }
}

Chúng ta có thể sử dụng thành phần này trong một ứng dụng như thế này.

<app-paginated-view [pageSize]="'A4'">
    <h1 #pageContent>Hello World!!</h1>
    <p #pageContent>This content will be displayed in an A4 size page</p>
</app-paginated-view>

Chúng tôi phải cung cấp biến mẫu #pageContent để chúng tôi có thể chọn chúng bằng cách sử dụng @ContentChildren trong PaginatedViewComponent của chúng tôi.

Lưu ý rằng chúng tôi đang sử dụng API dom gốc ở đây để thay đổi cấu trúc dom. nó sẽ chỉ di chuyển nút dom từ nơi này sang nơi khác, vì vậy nếu bạn có thêm bất kỳ trình xử lý sự kiện nào hoặc có bất kỳ thuộc tính nào ràng buộc với nội dung con thì chúng sẽ hoạt động như cũ.

Chỉnh sửa: Tôi cũng đã cập nhật stackblitz của bạnhttps://stackblitz.com/edit/angular-ivy-zjf8rv

5 VươngHữuThiện Oct 09 2020 at 06:05

Trong tệp app.component.html:

<div class="page" *ngFor="let page of pages; index as i"
  [style.height]="sizePage.height + 'cm'"
  [style.width]="sizePage.width + 'cm'"
  (click)="clickPage(i)">
  <div class="content" 
    [style.paddingTop]="paddingPage.top + 'cm'"
    [style.paddingRight]="paddingPage.right + 'cm'"
    [style.paddingBottom]="paddingPage.bottom + 'cm'"
    [style.paddingLeft]="paddingPage.left + 'cm'"  
    [id]="'content-' + i" contenteditable="true"
    (input)="inputContent($event['data'], i)">
  </div>
</div>

Trong tệp app.component.css:

.page {
    background: white;
    display: block;
    margin: 40px auto;
    box-shadow: 0 0 0.5cm rgba(0, 0, 0, 0.5);
    box-sizing: border-box;
}
.page .content {
    overflow: auto;
    outline: 0;
}

Trong tệp app.component.ts:

import { AfterViewChecked, Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewChecked {
  
  sizePage = {
    width: 21, //cm
    height: 29.7 //cm
  }
  paddingPage = {
    top: 2, //cm
    right: 2, //cm
    bottom: 2, //cm
    left: 2 //cm
  }
  pages = [
    {
      htmlContent: null,
      full: false
    },
  ]
  currentPage = 0;
  currentChar = null;

  runAfterViewChecked = false;
  
  clickPage(i) {
    this.currentPage = i;
  }

  inputContent(char, i) {
    var element = document.getElementById('content-' + i)
    var heightContent = element.offsetHeight * 2.54 / 96; // Convert pixels to cm
    this.pages[i].htmlContent = element.innerHTML;
    console.log(this.pages);
    if (Number(heightContent.toFixed(1)) > this.sizePage.height) { 
      this.currentChar = char;
      this.pages[i].full = true;
      if (!this.pages[i + 1]) {
        this.pages.push({
          htmlContent: null,
          full: false
        })
      }
      this.currentPage = i + 1;
      this.runAfterViewChecked = true;
    }
  }

  ngAfterViewChecked() {
    document.getElementById('content-' + this.currentPage).focus();
    if (this.runAfterViewChecked) {
      if (this.currentChar) {
        var str = this.pages[this.currentPage-1].htmlContent;
        var indexLastCloseDiv = str.lastIndexOf("</div>");
        var indexLastBr = str.lastIndexOf("<br>");
        var lastChar = str[indexLastCloseDiv-1];
        if (indexLastBr != -1 && (indexLastBr + 4) == indexLastCloseDiv)
          lastChar = ' ';

        if (indexLastCloseDiv != -1)
          str = str.slice(0, indexLastCloseDiv-1) + str.slice(indexLastCloseDiv);
        else 
          str = str.slice(0, str.length - 1);
        this.pages[this.currentPage-1].htmlContent = str;

        if (this.pages[this.currentPage].htmlContent)
          this.pages[this.currentPage].htmlContent = lastChar + this.pages[this.currentPage].htmlContent;
        else
          this.pages[this.currentPage].htmlContent = lastChar;
      }

      var element = null;
      for (let i = 0; i < this.pages.length; i++) {
        element = document.getElementById('content-' + i);
        element.innerHTML = this.pages[i].htmlContent;
      }
      this.runAfterViewChecked = false;
    }
  }
}

Liên kết đến Stackblitz

Đây là một ví dụ đơn giản. Có một vài sai sót mong các bạn góp ý để mình phát triển thêm.

Một số chức năng như Backspace, Delete, Scale Page, ... vẫn chưa được xử lý.

2 KasraHabibbeygi Oct 08 2020 at 11:11

Nếu bạn muốn tạo html A4 như office word A4, bạn phải sử dụng kích thước sau:

body{
 width: 21cm ;
 height: 29.7cm;
 margin:30mm 45mm 30mm 45mm;}
1 ErgiKodra Oct 02 2020 at 08:54

Sẽ khá dễ dàng để buộc trình duyệt web hiển thị trang có cùng kích thước pixel như A4. Tuy nhiên, có thể có một vài điều kỳ quặc khi mọi thứ được hiển thị.

Giả sử màn hình của bạn hiển thị 72 dpi, bạn có thể thêm một cái gì đó như sau:

<!DOCTYPE html>
<html>
  <head>
    <style>
    body {
        height: 842px;
        width: 595px;
        /* to centre page on screen*/
        margin-left: auto;
        margin-right: auto;
    }
    </style>
  </head>
  <body>
  </body>
</html>

Đây là một ví dụ trong mã của bạn với kích thước A4 có thể in được: https://stackblitz.com/edit/angular-ivy-fjhpdu?embed=1&file=src/app/app.component.html

1 johnSmith Oct 02 2020 at 08:50

Trên thực tế, tôi không thể thấy bất kỳ js hoặc css nào của bạn cố gắng giải quyết vấn đề.

Nhưng nhìn vào ví dụ được cung cấp từ xing, họ đang sử dụng chiều rộng / chiều cao pixel tĩnh

width: 595px;
height: 842px;

điều này phù hợp với PPI (pixel trên inch) là 72 cho Định dạng A4 đã cho, đây là điều quan trọng duy nhất cần lưu ý.

Biết được điều đó, bạn chỉ có thể kiểm tra xem chiều cao đó có bị vượt quá hay không trong khi chỉnh sửa và thao tác DOM cho phù hợp ("tạo Trang mới", "tách / di chuyển các phần tử hoặc bộ phận", v.v. ...) Bạn nên có mọi thứ ngay bây giờ để tự giải quyết. Nhưng mong rằng có rất nhiều thời gian để giải quyết vấn đề này, đặc biệt là liên quan đến việc giữ cho kích thước phông chữ / kết xuất / in phù hợp và không nói về phiên bản di động;)

Chúc may mắn

1 VươngHữuThiện Oct 14 2020 at 14:35

Trong tệp app.component.html:

<div class="container" id="container"></div>
<button type="button" class="buttonAdd" (click)="addBlock()">Add Block</button>

Trong tệp app.component.scss:

.buttonAdd {
    position: fixed;
    display: inline-block;
    font-weight: 400;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    border: 1px solid transparent;
    transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
    color: #fff;
    background-color: #007bff;
    border-color: #007bff;
    padding: .25rem .5rem;
    font-size: .875rem;
    line-height: 1.5;
    border-radius: .2rem;
    top: 0;
}
.container {
    height: 100%;
    width: 100%;
}
.page {
    // background: white;
    display: block;
    margin: 40px auto;
    box-shadow: 0 0 0.5cm rgba(0, 0, 0, 0.5);
    box-sizing: border-box;
    .content {
        overflow: auto;
        outline: 0;
        .block {
            border: 1px solid rgba(0,0,0,0);
            padding: .25rem;
            cursor: default;
            &:hover {
                border: 1px solid #dee2e6;
                border-radius: .25rem;
            }
            .title {
                font-weight: bold;
            }
            .value {
                cursor: text;
            }
        }
    }
}

Trong tệp app.component.ts:

import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'], 
  encapsulation: ViewEncapsulation.None,
})
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
  sizePage = {
    width: 21, //cm
    height: 29.7 //cm
  }
  paddingPage = {
    top: 2, //cm
    right: 2, //cm
    bottom: 2, //cm
    left: 2 //cm
  }

  data = [
    {
      title: "Name_1",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_1",
      value: "[email protected]"
    }, {
      title: "Telephone_1",
      value: "0123 456 789"
    }, {
      title: "Job_1",
      value: "Teacher"
    }, {
      title: "Name_2",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_2",
      value: "[email protected]"
    }, {
      title: "Telephone_2",
      value: "0123 456 789"
    }, {
      title: "Job_2",
      value: "Teacher"
    }, {
      title: "Name_3",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_3",
      value: "[email protected]"
    }, {
      title: "Telephone_3",
      value: "0123 456 789"
    }, {
      title: "Job_3",
      value: "Teacher"
    }, {
      title: "Name_4",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_4",
      value: "[email protected]"
    }, {
      title: "Telephone_4",
      value: "0123 456 789"
    }, {
      title: "Job_4",
      value: "Teacher"
    }, {
      title: "Name_5",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_5",
      value: "[email protected]"
    }, {
      title: "Telephone_5",
      value: "0123 456 789"
    }, {
      title: "Job_5",
      value: "Teacher"
    }, {
      title: "Name_6",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_6",
      value: "[email protected]"
    }, {
      title: "Telephone_6",
      value: "0123 456 789"
    }, {
      title: "Job_6",
      value: "Teacher"
    }, {
      title: "Name_7",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_7",
      value: "[email protected]"
    }, {
      title: "Telephone_7",
      value: "0123 456 789"
    }, {
      title: "Job_7",
      value: "Teacher"
    }, {
      title: "Name_8",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_8",
      value: "[email protected]"
    }, {
      title: "Telephone_8",
      value: "0123 456 789"
    }, {
      title: "Job_8",
      value: "Teacher"
    }, {
      title: "Name_9",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_9",
      value: "[email protected]"
    }, {
      title: "Telephone_9",
      value: "0123 456 789"
    }, {
      title: "Job_9",
      value: "Teacher"
    }, {
      title: "Name_10",
      value: "Thomas K.Wilson"
    }, {
      title: "Email_10",
      value: "[email protected]"
    }, {
      title: "Telephone_10",
      value: "0123 456 789"
    }, {
      title: "Job_10",
      value: "Teacher"
    }
  ]

  heightPageWithoutPadding = this.convertCmtoPx(this.sizePage.height - (this.paddingPage.top + this.paddingPage.bottom));
  elContainer;
  anchorsBlockValue;
  pageContent = [[]]; // Ex: [[0, 1, 2, 3], [4, 5]]

  constructor (private elementRef: ElementRef){

  }
  
  ngOnInit() {
    
  }

  ngAfterViewInit() {
    this.elContainer = document.getElementById('container');
    this.elContainer.innerHTML += this.createHTMLPage(0);
    this.insertListData();
    this.anchorsBlockValue = this.elementRef.nativeElement.querySelectorAll('.block .value');
    this.anchorsBlockValue.forEach((anchor: HTMLAnchorElement) => {
      anchor.addEventListener('input', this.handleAnchorBlockValue)
    });
  }

  insertListData() {
    var html_ListBlock = "";
    var html_Block = "";
    var iPage = 0;
    var iBlock = 0;
    var elPageContent = document.getElementById('page-' + iPage + '-content');
    for (let i = 0; i < this.data.length; i++) {
      
      html_Block = this.createHTMLBlock(iPage, iBlock, this.data[i]);
      elPageContent.innerHTML = html_ListBlock + html_Block; 

      if (elPageContent.offsetHeight > this.heightPageWithoutPadding) {
        elPageContent.innerHTML = html_ListBlock;

        iPage += 1;
        this.elContainer.innerHTML += this.createHTMLPage(iPage);
        elPageContent = document.getElementById('page-' + iPage + '-content'); 
        
        this.pageContent[iPage] = [];
        this.pageContent[iPage].push(iBlock);

        html_Block = this.createHTMLBlock(iPage, iBlock, this.data[i]);
        html_ListBlock = html_Block;
        elPageContent.innerHTML = html_ListBlock;
      } else {
        this.pageContent[iPage].push(iBlock);
        
        html_ListBlock += html_Block;
        elPageContent.innerHTML = html_ListBlock;
      }
      iBlock += 1;
    }
    // nodes += `<button type="button" class="buttonAdd" (click)="addBlock()">Add Block</button>`;
  }

  handleAnchorBlockValue = (event: Event) => {
    // Prevent opening anchors the default way
    event.preventDefault();
    const anchor = event.target as HTMLAnchorElement;
    const id_anchorParentEl = anchor.parentElement.getAttribute('id'); // page-iPage-content-block-iBlock
    var iPage = Number(id_anchorParentEl.slice(id_anchorParentEl.indexOf("page-") + ("page-").length, id_anchorParentEl.indexOf("-content")));
    var iBlock = Number(id_anchorParentEl.slice(id_anchorParentEl.indexOf("block-") + ("block-").length, id_anchorParentEl.length));
    var elPageContent = anchor.parentElement.parentElement;
    if (elPageContent.offsetHeight > this.heightPageWithoutPadding) {
      if (!this.pageContent[iPage + 1]) {
        this.elContainer.innerHTML += this.createHTMLPage(iPage + 1);
        this.pageContent[iPage + 1] = [];
      }
      if (this.pageContent[iPage].length == 1) {
        // This is (Height Block == Height Content) > Height Page
        alert("To be continue ...");
      } else {
        while (iPage < this.pageContent.length) {

          var elPageContent = document.getElementById('page-' + iPage + '-content'); 
          var iLastBlock_PageContent = this.pageContent[iPage][this.pageContent[iPage].length - 1];
          var elLastBlock_PageContent = document.getElementById('page-' + iPage + '-content-block-' + iLastBlock_PageContent); 
          elLastBlock_PageContent.remove();
          this.pageContent[iPage].pop();
    
          if (!this.pageContent[iPage + 1]) {
            this.elContainer.innerHTML += this.createHTMLPage(iPage + 1);
            this.pageContent[iPage + 1] = [];
          }

          elLastBlock_PageContent.setAttribute('id', 'page-' + (iPage + 1) + '-content-block-' + iLastBlock_PageContent); 
          var elNextPageContent = document.getElementById('page-' + (iPage + 1) + '-content'); 
          elNextPageContent.innerHTML = elLastBlock_PageContent.outerHTML + elNextPageContent.innerHTML;
          this.pageContent[iPage + 1].unshift(iLastBlock_PageContent);
    
          if (elPageContent.offsetHeight <= this.heightPageWithoutPadding) {
            if (elNextPageContent.offsetHeight <= this.heightPageWithoutPadding) {
              break;
            } else {
              iPage += 1;
            }
          }
        }
      }
      this.anchorsBlockValue = this.elementRef.nativeElement.querySelectorAll('.block .value');
      this.anchorsBlockValue.forEach((anchor: HTMLAnchorElement) => {
        anchor.addEventListener('input', this.handleAnchorBlockValue)
      });
    }
  }

  addBlock() {
    alert("To be continue ...");
  }

  convertPxToCm(px) {
    return Math.round(px * 2.54/96 * 100) / 100;
  }

  convertCmtoPx(cm) {
    return Math.round(cm * 96/2.54);
  }

  createHTMLBlock(iPage, iBlock, data) {
    return `<div class="block" id="page-${iPage}-content-block-${iBlock}">
              <div class="title">${data.title}</div> <div class="value" contenteditable>${data.value}</div>
            </div>`;
  }

  createHTMLPage(iPage) {
    return `<div class="page" id="page-${iPage}" style=" height: ${this.sizePage.height}cm;
                width: ${this.sizePage.width}cm; padding-top: ${this.paddingPage.top}cm;
                padding-right: ${this.paddingPage.right}cm; padding-bottom: ${this.paddingPage.bottom}cm;
                padding-left: ${this.paddingPage.left}cm; "> <div class="content" id="page-${iPage}-content">
              </div>
            </div>`;
  }

  ngOnDestroy() {
    // Cleanup by removing the event listeners on destroy
    this.anchorsBlockValue.forEach((anchor: HTMLAnchorElement) => {
      anchor.removeEventListener('input', this.handleAnchorBlockValue)
    })
  }
}

Trong tệp tsconfig.json:

Thêm vào

"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true

Nó trông như thế này

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ],
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  },
  "angularCompilerOptions": {
    "enableIvy": true,
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true
  }
}

Liên kết đến Stackblitz

Tôi đã làm điều đó sau khi xem trang CV của Xing.

Thiết kế Block và Data Type này giúp bạn dễ dàng tách các cột cho trang và thêm nội dung với các tiêu đề tùy ý.

Bạn phải chuyển đổi dữ liệu của mình sang kiểu dữ liệu của tôi bằng cách thêm hàm dataConvert trong ngOnInit ()

Vì bạn đang sử dụng kiểu dữ liệu Văn bản. Nếu nó đa dạng hơn thì kiểu dữ liệu sẽ là:

data = [
    {
      type: "Text",
      title: "Full Name",
      value: "Thomas K.Wilson"
    }, {
      type: "Image",
      title: "My Avatar",
      value: "linkImage.com"
    }
]
Ravi Oct 09 2020 at 05:39

Nếu bạn nghĩ rằng bạn có thể làm điều đó với jquery, không có hạn chế nào để sử dụng jquery trong góc

bạn sẽ cần thêm các gói sau

jQuery (Đối với jquery ofcourse)https://www.npmjs.com/package/jquery

@ type / jquery (hỗ trợ trong typecript)https://www.npmjs.com/package/@types/jquery

Sau đây là cách bạn có thể sử dụng trong tệp ts của mình (chỉ là ví dụ)

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
declare var $: any; @Component({ selector: 'app-a4', templateUrl: './a4.component.html', styleUrls: ['./a4.component.css'] }) export class A4Component implements OnInit { max_pages = 5; page_count = 0; constructor() { } ngOnInit(): void { this.snipMe(); } snipMe() { this.page_count++; if (this.page_count > this.max_pages) { return; } var long = $(this)[0].scrollHeight - Math.ceil($(this).innerHeight()); var children = $(this).children().toArray();
    var removed = [];
    while (long > 0 && children.length > 0) {
      var child = children.pop();
      $(child).detach(); removed.unshift(child); long = $(this)[0].scrollHeight - Math.ceil($(this).innerHeight()); } if (removed.length > 0) { var a4 = $('<div class="A4"></div>');
      a4.append(removed);
      $(this).after(a4);
      this.snipMe.call(a4[0]);
    }
  }
}