3.2. Пример отчёта HTML/PDF

Предположим, нам нужно создать отчёт с альбомной ориентацией страниц, нумерацией страниц, а также фиксированными заголовком и подвалом на каждой странице, которые мы настроим через правила и свойства CSS. Формат вывода отчёта - HTML с конвертацией в PDF.

Полностью готовый пример этого отчёта вместе с тестовым проектом можно скачать с CUBA GitHub.

  1. Модель данных

    Отчёт будет отображать информацию о сущности Client. Она содержит два строковых атрибута, title и summary, которые мы используем в структуре отчёта.

    public class Client extends StandardEntity {
    
        @NotNull
        @Column(name = "TITLE", nullable = false)
        protected String title;
    
        @Column(name = "SUMMARY")
        protected String summary;
    
        ...
    }
  2. Создание отчёта

    Создадим простой отчёт без параметров. Запрос JPQL возвращает список всех клиентов с их локальными атрибутами: title и summary.

    example html 1
  3. Шаблон отчёта.

    Теперь создадим файл шаблона. В нём мы определим блоки заголовка и подвала, которые должны выводиться на каждой странице итогового документа PDF. Также используем свойство CSS page-break-before: always, которое будет создавать разрыв страницы перед каждым новым блоком информации о клиенте.

    Мы также используем теги FreeMarker для вставки данных в тело отчёта. Подробное руководство по FreeMarker назодится здесь: http://freemarker.org/docs/.

    <body>
    <h1>Clients report</h1>
    <!-- Custom HTML header -->
    <div id="header">
        <h2>Annual Report of our Company</h2>
    </div>  <!-- Custom HTML footer -->
    <div id="footer">
        <h2>Address: William Road</h2>
        <span class="custom-footer-page-number">Number: </span>
    </div>
    
    <#assign clients = Root.bands.Clients />
    
    <#list clients as client>
        <!-- New page for each client -->
        <div class="custom-page-start" style="page-break-before: always;">
            <h2>Client</h2>
            <p>Name: ${client.fields.title}</p>
            <p>Summary: ${client.fields.summary}</p>
        </div>
    </#list>
    </body>
  4. Правила CSS

    Используем следующий код CSS для разметки страницы PDF:

    body {
      font: 12pt Georgia, "Times New Roman", Times, serif;
      line-height: 1.3;}
      @page {
        /* switch to landscape */
       size: landscape;
       /* set page margins */
       margin: 0.5cm;
       /* Default footers */
       @bottom-left {
         content: "Department of Strategy";
       }
       @bottom-right {
         content: counter(page) " of " counter(pages);
      }
    }

    Далее определим положение заголовка и подвала:

    /* footer, header - position: fixed */
    #header {
      position: fixed;
      width: 100%;
      top: 0;
      left: 0;
      right: 0;
    }
    #footer {
      position: fixed;
      width: 100%;
      bottom: 0;
      left: 0;
      right: 0;
    }

    Также настроим отступы для основного содержимого отчёта, чтобы избежать наложения с заголовком и подвалом:

    /* Fix overflow of headers and content */
    body {
        padding-top: 50px;
    }
    .custom-page-start {
        margin-top: 50px;
    }

    Кроме того, укажем, где нужно выводить номера страниц отчёта:

    .custom-footer-page-number:after {
      content: counter(page);
    }

    В итоге у нас получился файл paging-template.html со следующим содержанием:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Invoice</title>
        <style type="text/css">
     body { font: 12pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; } @page { /* switch to landscape */ size: landscape; /* set page margins */ margin: 0.5cm; /* Default footers */ @bottom-left { content: "Department of Strategy"; } @bottom-right { content: counter(page) " of " counter(pages); } } /* footer, header - position: fixed */ #header { position: fixed; width: 100%; top: 0; left: 0; right: 0; } #footer { position: fixed; width: 100%; bottom: 0; left: 0; right: 0; } /* Fix overflow of headers and content */ body { padding-top: 50px; } .custom-page-start { margin-top: 50px; } .custom-footer-page-number:after { content: counter(page); }
      </style>
    </head>
    <body>
    <h1>Clients report</h1>
    
    <!-- Custom HTML header -->
    <div id="header">
        <h2>Annual Report of our Company</h2>
    </div>
    
    <!-- Custom HTML footer -->
    <div id="footer">
        <h2>Address: William Road</h2>
        <span class="custom-footer-page-number">Number: </span>
    </div>
    
    <#assign clients = Root.bands.Clients />
    
    <#list clients as client>
        <!-- New page for each client -->
        <div class="custom-page-start" style="page-break-before: always;">
            <h2>Client</h2>
    
            <p>Name: ${client.fields.title}</p>
            <p>Summary: ${client.fields.summary}</p>
        </div>
    </#list>
    </body>
    </html>
  5. Загрузка шаблона и запуск отчёта.

    example html 3

    Как мы видим, отчёт содержит титульную страницу и разрывы перед каждой страницей с информацией о клиенте, а также заголовок и подвал на каждой странице:

    example html 2