Как разделить HTML-страницу в формате A4 в Angular 9

Sep 30 2020

Я пытаюсь создать что-то вроде Xing the CV maker -> https://lebenslauf.com/.

У меня есть разные массивы объектов. Но я не могу создать страницы A4, которые будут отображать данные, и если массив больше одной страницы, создайте новую страницу A4 и добавьте туда данные. Функция должна быть такой, что если массив больше на один размер, то создайте новую страницу a4 и поместите туда данные. В stackblitz я добавил массив и случайный текст и создал букву A4. Я сослался на этот вопрос и ответ, но мне это не очень помогло. CSS для установки размера бумаги A4 .
Я пытался подделать нумерацию страниц и создать формат A4, но у меня ничего не вышло.
Я посмотрел этот код с помощью jquery. Он работает, но я не могу скомпилировать его в Angular.https://jsfiddle.net/tm637ysp/10/ Кто-нибудь может мне здесь помочь?

Я создал два проекта в stackblitz. Может они помогут.
https://stackblitz.com/edit/angular-ivy-fjhpdu.
https://stackblitz.com/edit/angular-ivy-uzmdwg

Я хотел снова открыть этот вопрос, потому что утвержденный ответ от @HirenParekh работает не так, как я хотел. Проблема в том, что если текст в одном объекте очень большой, он не будет добавлять новую страницу в реальном времени, но только если я перезагружу страницу. Я думаю, что код будет для добавления новой страницы или страницы редактирования будет отображаться только в формате ngOnInit. Директива, предоставленная для выполнения этой работы, я думаю, не работает как исключение. Вот stackblitzчто он пытался мне помочь.https://stackblitz.com/edit/angular-ivy-zjf8rv

Это код, который я пытаюсь показать.

    <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>

Это CSS

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

 

Это 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": ""
                }
            ]
        }
    ]
}

И это 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 {

}

Ответы

4 HirenParekh Oct 15 2020 at 13:15

Все дело в том, чтобы разделить данный контент, чтобы он соответствовал заданному размеру страницы.

Мы можем создать компонент, который будет выполнять функции разделения за нас. вот StackBlitz Demo .

И вот краткое объяснение.

Используйте ContentChildrenдекоратор, чтобы наблюдать за изменением содержания. каждый раз при изменении содержимого мы будем запускать логику создания страницы.

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;
  }
}

Мы можем использовать этот компонент в таком приложении.

<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>

Мы должны предоставить переменную шаблона #pageContent, чтобы мы могли выбирать их с помощью @ContentChildren в нашем PaginatedViewComponent.

Обратите внимание, что здесь мы используем собственные dom API для изменения структуры dom. он будет перемещать только узел dom из одного места в другое, поэтому, если у вас есть добавленный прослушиватель событий или привязка какого-либо свойства к дочерним элементам содержимого, они будут работать как есть.

Изменить: я также обновил ваш stackblitzhttps://stackblitz.com/edit/angular-ivy-zjf8rv

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

В файле 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>

В файле 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;
}

В файле 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;
    }
  }
}

Ссылка на Stackblitz

Вот простой пример. Есть несколько ошибок, пожалуйста, дайте свои предложения для дальнейшего развития.

Некоторые функции, такие как Backspace, Delete, Scale Page, ... не были обработаны.

2 KasraHabibbeygi Oct 08 2020 at 11:11

Если вы хотите создать html A4 как офисное слово A4, вы должны использовать этот размер:

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

Было бы довольно легко заставить веб-браузер отображать страницу с теми же размерами пикселей, что и A4. Однако при рендеринге может быть несколько причуд.

Предполагая, что ваши мониторы отображают 72 dpi, вы можете добавить что-то вроде этого:

<!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>

Вот пример вашего кода с размером A4 для печати: https://stackblitz.com/edit/angular-ivy-fjhpdu?embed=1&file=src/app/app.component.html

1 johnSmith Oct 02 2020 at 08:50

На самом деле я не вижу от вас никаких js или css, которые пытаются решить проблему.

Но, глядя на предоставленный пример от xing, они используют статическую ширину / высоту пикселей.

width: 595px;
height: 842px;

это соответствует PPI (пиксель на дюйм) 72 для данного формата A4, и это единственная важная вещь, о которой нужно помнить.

Зная это, вы можете просто проверить, не превышена ли эта высота при редактировании, и соответственно манипулировать DOM («создать новую страницу», «разделить / переместить элементы или части» и т. Д. ...). Теперь у вас должно быть все, чтобы решить эту проблему самостоятельно. Но ожидайте, что на это уйдет много времени, особенно в отношении сохранения соответствия размеров шрифтов / рендеринга / печати, а не разговоров о мобильной версии;)

Удачи

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

В файле app.component.html:

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

В файле 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;
            }
        }
    }
}

В файле 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)
    })
  }
}

В файле tsconfig.json:

Добавить

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

Это выглядит так

{
  "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
  }
}

Ссылка на Stackblitz

Я сделал это, посмотрев страницу с резюме Сина.

Такой дизайн блока и типа данных позволяет легко разделять столбцы на странице и добавлять контент с произвольными заголовками.

Вы должны преобразовать свои данные в мой тип данных, добавив функцию dataConvert в ngOnInit ()

Поскольку вы используете тип данных Text. Если бы он был более разнообразным, то тип данных был бы:

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

Если вы думаете, что можете сделать это с помощью jquery, нет ограничений на использование jquery в angular

вам нужно будет добавить следующие пакеты

jQuery (конечно, для jquery)https://www.npmjs.com/package/jquery

@ types / jquery (для поддержки машинописного текста)https://www.npmjs.com/package/@types/jquery

Ниже показано, как вы можете использовать в своем файле ts (например)

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]);
    }
  }
}