刘光辉
11 小时以前 0dfe84494048ce27ba8449831782128412d3eb13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
export interface ListPrintColumn {
  field: string;
  title: string;
  width?: number;
}
 
export interface ListPrintPage {
  pageNumber: number;
  rows: Record<string, any>[];
}
 
export interface ListPrintDocumentOptions {
  columns: ListPrintColumn[];
  direction: 'landscape' | 'portrait';
  pageSize: number;
  pages: ListPrintPage[];
  paperType: 'A3' | 'A4';
  title: string;
  total: number;
}
 
export function getListPrintTotalPages(total: number, pageSize: number) {
  const normalizedTotal = Math.max(0, Number(total) || 0);
  const normalizedPageSize = Math.max(1, Number(pageSize) || 1);
  return Math.max(1, Math.ceil(normalizedTotal / normalizedPageSize));
}
 
export function validateListPrintPageRange(startPage: number, endPage: number, totalPages: number) {
  if (!Number.isInteger(startPage) || !Number.isInteger(endPage)) return '起始页和结束页必须是整数';
  if (startPage < 1) return '起始页不能小于 1';
  if (endPage < startPage) return '结束页不能小于起始页';
  if (endPage > totalPages) return `结束页不能大于总页数 ${totalPages}`;
  return '';
}
 
export function buildListPrintDocument(options: ListPrintDocumentOptions) {
  const { columns, direction, pageSize, pages, paperType, title, total } = options;
  const columnCount = columns.length + 1;
  const fontSize = columnCount > 10 ? 8 : columnCount > 7 ? 9 : 10;
  const tableHeader = ['<th class="sequence-column">序号</th>', ...columns.map((column) => `<th>${escapeHtml(column.title)}</th>`)].join('');
 
  const pageHtml = pages
    .map((page) => {
      const rows = page.rows
        .map((row, rowIndex) => {
          const sequence = (page.pageNumber - 1) * pageSize + rowIndex + 1;
          const cells = columns.map((column) => `<td>${escapeHtml(formatCellValue(row[column.field]))}</td>`).join('');
          return `<tr><td class="sequence-column">${sequence}</td>${cells}</tr>`;
        })
        .join('');
 
      return `
        <section class="print-page">
          <header>
            <h1>${escapeHtml(title)}</h1>
            <div class="page-meta">第 ${page.pageNumber} 页 / 共 ${getListPrintTotalPages(total, pageSize)} 页</div>
          </header>
          <table>
            <thead><tr>${tableHeader}</tr></thead>
            <tbody>${rows || `<tr><td colspan="${columnCount}" class="empty-row">暂无数据</td></tr>`}</tbody>
          </table>
        </section>`;
    })
    .join('');
 
  return `<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>${escapeHtml(title)}</title>
    <style>
      * { box-sizing: border-box; }
      html, body { margin: 0; padding: 0; color: #111827; font-family: "Microsoft YaHei", "PingFang SC", sans-serif; }
      .print-page { width: 100%; break-after: page; page-break-after: always; }
      .print-page:last-child { break-after: auto; page-break-after: auto; }
      header { position: relative; margin: 0 0 5mm; text-align: center; }
      h1 { margin: 0; font-size: 18px; font-weight: 700; letter-spacing: 0; }
      .page-meta { position: absolute; right: 0; bottom: 0; color: #4b5563; font-size: 9px; }
      table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: ${fontSize}px; }
      thead { display: table-header-group; }
      tr { break-inside: avoid; page-break-inside: avoid; }
      th, td { border: 1px solid #4b5563; padding: 5px 4px; text-align: center; vertical-align: middle; overflow-wrap: anywhere; }
      th { background: #f3f4f6; font-weight: 700; }
      .sequence-column { width: 38px; }
      .empty-row { padding: 24px; color: #6b7280; }
      @page { size: ${paperType} ${direction}; margin: 10mm; }
      @media screen {
        body { background: #eef0f3; padding: 16px; }
        .print-page { margin: 0 auto 16px; padding: 10mm; background: #fff; box-shadow: 0 2px 10px rgba(15, 23, 42, 0.12); }
      }
      @media print {
        body { background: #fff; }
        .print-page { padding: 0; }
        th { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
      }
    </style>
  </head>
  <body>${pageHtml}</body>
</html>`;
}
 
function formatCellValue(value: any) {
  if (value === null || value === undefined) return '';
  if (Array.isArray(value)) return value.join(',');
  if (typeof value === 'object') return JSON.stringify(value);
  return String(value);
}
 
function escapeHtml(value: any) {
  return String(value ?? '')
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#039;');
}