HTML 報告範例¶
需求套件:nextpdf/artisan 難度:中級
完整程式碼¶
<?php
declare(strict_types=1);
use NextPDF\Artisan\ArtisanRenderer;
use NextPDF\Artisan\ValueObjects\RenderOptions;
use NextPDF\Core\ValueObjects\PageSize;
use NextPDF\Core\ValueObjects\Margin;
// ─── HTML 範本 ────────────────────────────────────────────────
$reportData = [
'title' => '2024 Q4 業績報告',
'company' => 'NextPDF 科技有限公司',
'period' => '2024 年 10 月 — 12 月',
'revenue' => 'NT$ 12,580,000',
'growth' => '+23.5%',
'items' => [
['month' => '10 月', 'value' => 3_820_000],
['month' => '11 月', 'value' => 4_150_000],
['month' => '12 月', 'value' => 4_610_000],
],
];
$html = buildReportHtml($reportData);
// ─── 渲染為 PDF ───────────────────────────────────────────────
$renderer = ArtisanRenderer::create(
chromePath: $_ENV['CHROME_PATH'] ?? '/usr/bin/google-chrome',
);
$pdf = $renderer->renderHtml(
html: $html,
options: RenderOptions::create(
pageSize: PageSize::A4,
margin: Margin::uniform(0.0), // HTML 自行處理邊距
waitUntil: 'networkidle0', // 等待圖表渲染完成
timeout: 30_000, // 30 秒超時
printBackground: true, // 列印背景色
)->withHeaderTemplate(
html: '<div style="font-size:9px;color:#6B7280;text-align:right;
width:100%;padding-right:15mm;">
<span class="pageNumber"></span> / <span class="totalPages"></span>
</div>',
),
);
$pdf->save('/output/q4-report.pdf');
// ─── HTML 建構函式 ────────────────────────────────────────────
function buildReportHtml(array $data): string
{
$chartData = json_encode(array_column($data['items'], 'value'));
$chartLabels = json_encode(array_column($data['items'], 'month'));
return <<<HTML
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{$data['title']}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap"
rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Noto Sans TC', sans-serif;
font-size: 10pt;
color: #111827;
background: #FFFFFF;
}
.page {
width: 210mm;
min-height: 297mm;
padding: 20mm 20mm 25mm;
}
/* 頁首 */
.header {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
padding-bottom: 12px;
border-bottom: 2px solid #1E3A8A;
margin-bottom: 24px;
}
.header-title { font-size: 20pt; font-weight: 700; color: #1E3A8A; }
.header-meta { text-align: right; color: #6B7280; font-size: 9pt; line-height: 1.6; }
/* KPI 卡片 */
.kpi-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 28px;
}
.kpi-card {
background: #F0F9FF;
border: 1px solid #BAE6FD;
border-radius: 8px;
padding: 16px;
}
.kpi-label { font-size: 8pt; color: #0369A1; text-transform: uppercase; letter-spacing: 0.05em; }
.kpi-value { font-size: 22pt; font-weight: 700; color: #0C4A6E; margin-top: 4px; }
.kpi-sub { font-size: 9pt; color: #0369A1; margin-top: 2px; }
/* 圖表區域 */
.chart-section { margin-bottom: 28px; }
.section-title {
font-size: 12pt;
font-weight: 700;
color: #1E3A8A;
margin-bottom: 12px;
padding-bottom: 6px;
border-bottom: 1px solid #BFDBFE;
}
/* 資料表格 */
table { width: 100%; border-collapse: collapse; }
thead th {
background: #1E3A8A;
color: #FFFFFF;
padding: 8px 12px;
text-align: left;
font-size: 9pt;
}
tbody tr:nth-child(even) { background: #F9FAFB; }
tbody td { padding: 8px 12px; font-size: 9pt; border-bottom: 1px solid #F3F4F6; }
@page { margin: 15mm 15mm 20mm; }
</style>
</head>
<body>
<div class="page">
<!-- 頁首 -->
<div class="header">
<div>
<div class="header-title">{$data['title']}</div>
<div style="color:#6B7280;margin-top:4px;">{$data['company']}</div>
</div>
<div class="header-meta">
<div>{$data['period']}</div>
<div>生成日期:2025-03-04</div>
</div>
</div>
<!-- KPI 卡片 -->
<div class="kpi-grid">
<div class="kpi-card">
<div class="kpi-label">季度總營收</div>
<div class="kpi-value">{$data['revenue']}</div>
<div class="kpi-sub">較上季成長 {$data['growth']}</div>
</div>
<div class="kpi-card">
<div class="kpi-label">月均營收</div>
<div class="kpi-value">NT$ 4.19M</div>
<div class="kpi-sub">目標達成率 104.8%</div>
</div>
<div class="kpi-card">
<div class="kpi-label">新增客戶</div>
<div class="kpi-value">48</div>
<div class="kpi-sub">企業客戶佔 62.5%</div>
</div>
</div>
<!-- 長條圖 -->
<div class="chart-section">
<div class="section-title">月度營收趨勢</div>
<canvas id="revenueChart" height="120"></canvas>
</div>
<!-- 明細表格 -->
<div class="chart-section">
<div class="section-title">月度明細</div>
<table>
<thead>
<tr>
<th>月份</th>
<th>營收(NTD)</th>
<th>環比成長</th>
<th>目標達成</th>
</tr>
</thead>
<tbody>
<tr>
<td>十月</td>
<td>3,820,000</td>
<td>+12.3%</td>
<td>95.5%</td>
</tr>
<tr>
<td>十一月</td>
<td>4,150,000</td>
<td>+8.6%</td>
<td>103.8%</td>
</tr>
<tr>
<td>十二月</td>
<td>4,610,000</td>
<td>+11.1%</td>
<td>115.3%</td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
const ctx = document.getElementById('revenueChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: {$chartLabels},
datasets: [{
label: '月度營收(NTD)',
data: {$chartData},
backgroundColor: ['#3B82F6', '#1D4ED8', '#1E3A8A'],
borderRadius: 6,
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
},
scales: {
y: {
beginAtZero: false,
ticks: {
callback: v => 'NT$ ' + (v/1000000).toFixed(1) + 'M'
}
}
}
}
});
</script>
</body>
</html>
HTML;
}
程式碼說明¶
延伸閱讀¶
- HTML 轉 PDF 指南 — Chrome CDP 完整說明
- 圖表報告範例 — 使用 nextpdf/pro 原生圖表(無需 Chrome)