通過設置 Content-Type: application/vnd.ms-excel 輸出 HTML 表格,是 PHP 導出 Excel 最輕量的方式(無需第三方庫)。但直接輸出常遇三大問題:長數字(身份證/銀行卡)自動變科學計數法、中文亂碼、數字/日期/貨幣格式不受控。本文系統講解 Excel 單元格格式化原理,提供完整可用的導出方案。
Excel 讀取 HTML 表格時,會解析 <td> 的 style 屬性中的 vnd.ms-excel.numberformat 格式代碼,決定該單元格的顯示格式。最近的樣式優先級最高(內層 <td> > <tr> > <table>)。
| 用途 | 格式代碼 | 示例輸出 |
|---|---|---|
| 文本(防科學計數法) | vnd.ms-excel.numberformat:@ | 410522198402161833 |
| 日期 | vnd.ms-excel.numberformat:yyyy/mm/dd | 2024/03/15 |
| 數字(千分位,2 位小數) | vnd.ms-excel.numberformat:#,##0.00 | 1,234.56 |
| 貨幣 | vnd.ms-excel.numberformat:¥#,##0.00 | ¥1,234.56 |
| 百分比 | vnd.ms-excel.numberformat:#0.00% | 12.34% |
<?php
// export.php
$list = [
['user_name' => '張三', 'account_id' => '410522198402161833', 'contact_name' => '李四'],
['user_name' => '王五', 'account_id' => '410522199001011234', 'contact_name' => '趙六'],
];
// 1. 設置響應頭
$filename = '會員名_' . date('YmdHis') . '.xls';
// 兼容中文文件名:IE/Edge 用 urlencode,Firefox/Chrome 支持 RFC 5987
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (preg_match('/MSIE|Trident|Edge/', $ua)) {
$filename = urlencode($filename);
} else {
$filename = "utf-8''" . rawurlencode($filename);
}
header("Content-Type: application/vnd.ms-excel; charset=utf-8");
header("Content-Disposition: attachment; filename*=utf-8''" . rawurlencode($filename));
header("Cache-Control: max-age=0");
header("Expires: 0");
// 2. 輸出 HTML 表格(帶 Office 命名空間,增強 Excel 識別度)
echo '<html xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns="https://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--[if gte mso 9]><xml>
<x:ExcelWorkbook>
<x:ExcelWorksheets>
<x:ExcelWorksheet>
<x:Name>會員數據</x:Name>
<x:WorksheetOptions>
<x:DisplayGridlines/>
</x:WorksheetOptions>
</x:ExcelWorksheet>
</x:ExcelWorksheets>
</x:ExcelWorkbook>
</xml><![endif]-->
</head>
<body>
<table border="1">
<tr>
<th>會員名</th>
<th>賬號(身份證)</th>
<th>聯系人</th>
</tr>';
foreach ($list as $v) {
echo '<tr>';
// 普通文本欄位
echo '<td>' . htmlspecialchars($v['user_name']) . '</td>';
// 身份證:強制文本格式,防科學計數法
echo '<td style="vnd.ms-excel.numberformat:@">' . htmlspecialchars($v['account_id']) . '</td>';
echo '<td>' . htmlspecialchars($v['contact_name']) . '</td>';
echo '</tr>';
}
echo '</table></body></html>';
核心:<td style="vnd.ms-excel.numberformat:@">>內容</td>。@ 代表「文本格式」,Excel 將按原樣顯示,不嘗試解析為數字。
補充:若數據極長(>255 字符),Excel 單元格可能截斷,建議業務層面控制長度或改用真實 .xlsx 庫(如 PhpSpreadsheet)。
header("Content-Type: application/vnd.ms-excel; charset=utf-8");<meta http-equiv="Content-Type" content="text/html; charset=utf-8">filename*=utf-8''...,兼容現代瀏覽器;舊 IE 用 urlencode。⚠️ 早期方案常用 iconv('UTF-8', 'GB2312//IGNORE', ...) 強制轉 GB2312,**不推薦**,會導致生僻字丟失,現代 Excel 完美支持 UTF-8。
直接在對應 <td> 加入 style="vnd.ms-excel.numberformat:...",如:
<td style="vnd.ms-excel.numberformat:#,##0.00">1234.5</td> <!-- 顯示 1,234.50 -->
<td style="vnd.ms-excel.numberformat:yyyy-mm-dd">2024-03-15</td> <!-- 顯示 2024-03-15 -->
<td style="vnd.ms-excel.numberformat:¥#,##0">100</td> <!-- 顯示 ¥100 -->
<?php
class ExcelExporter {
private array $headers = [];
private array $rows = [];
private string $title = 'Sheet1';
public function setTitle(string $title): self {
$this->title = $title;
return $this;
}
public function setHeaders(array $headers): self {
$this->headers = $headers; // [['field'=>'id', 'label'=>'ID', 'format'=>'@'], ...]
return $this;
}
public function addRows(array $rows): self {
$this->rows = $rows;
return $this;
}
public function download(string $filename = ''): void {
$filename = $filename ?: $this->title . '_' . date('YmdHis') . '.xls';
$this->sendHeaders($filename);
$this->outputTable();
exit;
}
private function sendHeaders(string $filename): void {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$encoded = preg_match('/MSIE|Trident|Edge/', $ua) ? urlencode($filename) : "utf-8''" . rawurlencode($filename);
header("Content-Type: application/vnd.ms-excel; charset=utf-8");
header("Content-Disposition: attachment; filename*=$encoded");
header("Cache-Control: max-age=0");
}
private function outputTable(): void {
echo '<html xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns="https://www.w3.org/TR/REC-html40">
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body><table border="1">';
// 表頭
echo '<tr>';
foreach ($this->headers as $h) {
echo '<th>' . htmlspecialchars($h['label']) . '</th>';
}
echo '</tr>';
// 數據行
foreach ($this->rows as $row) {
echo '<tr>';
foreach ($this->headers as $h) {
$val = $row[$h['field']] ?? '';
$style = isset($h['format']) ? ' style="vnd.ms-excel.numberformat:' . $h['format'] . '"' : '';
echo "<td$style>" . htmlspecialchars($val) . "</td>";
}
echo '</tr>';
}
echo '</table></body></html>';
}
}
// 使用示例
$exporter = new ExcelExporter();
$exporter->setTitle('會員導出')
->setHeaders([
['field' => 'user_name', 'label' => '會員名'],
['field' => 'account_id', 'label' => '身份證', 'format' => '@'],
['field' => 'contact_name', 'label' => '聯系人'],
])
->addRows($list)
->download('會員名單.xls');
| 場景 | HTML 導出 | PhpSpreadsheet / Box/Spout |
|---|---|---|
| 純數據導出、簡單格式、無樣式需求 | ✅ 輕量、無依賴、極快 | ⚠️ 依賴大、內存佔用高 |
| 需合併儲存格、圖表、圖片、條件格式 | ❌ 不支持 | ✅ 功能全 |
| 超大數據量(>5萬行) | ⚠️ 內存壓力大,需分塊輸出 | ✅ Spout 流式寫入,常駐內存低 |
| .xlsx 格式(現代標準) | ❌ 僅 .xls (HTML) | ✅ 原生 .xlsx |
HTML 導出 Excel 是「零依賴、極速開發」的利器。掌握 vnd.ms-excel.numberformat 格式代碼、UTF-8 編碼響應頭、文件名 RFC 5987 編碼三大核心,即可解決 90% 導出痛點。複雜報表、大數據量、.xlsx 需求請轉向 PhpSpreadsheet/Spout。