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('&', '&')
|
.replaceAll('<', '<')
|
.replaceAll('>', '>')
|
.replaceAll('"', '"')
|
.replaceAll("'", ''');
|
}
|