跳至主要内容
💬 回報這一頁

列印與匯出

問題

大多數團隊只設計淺色與深色兩種主題。後台系統還有第三種:印出來的那一份

而列印是後台系統真正的交付物之一——報表要印出來給主管簽名、 要存成 PDF 附在流程裡、要寄給不用系統的人。

沒有處理列印樣式的結果:側邊欄佔掉半頁、狀態徽章變成白色方塊、 表格被切成兩半而且第二頁沒有表頭。

做法:列印規則

1. 底色是內容,不是裝飾

@media print {
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
}

後台介面的徽章、提示框、佔位塊全靠底色辨義。瀏覽器預設會剝掉背景色 (除非使用者勾「列印背景圖形」,而沒有人會記得去勾),剝掉之後整塊語意就消失了。

補一層保底:佔位塊之類純靠底色的元素,在列印時補上淡邊框—— 有些印表機驅動仍會剝背景,至少留下形狀。

2. 控制項一律隱藏

搜尋框、篩選鈕、分頁器、匯出鈕、側邊欄、導覽——印出來要是一份乾淨文件

元件內建 print-hidden class;容器層另外隱藏站台外殼。

3. 不跨頁截斷

@media print {
thead { display: table-header-group; } /* 每頁重複表頭 */
tfoot { display: table-footer-group; }
tr, img, svg, figure, .print-block { break-inside: avoid; }
h1, h2, h3, h4 { break-after: avoid; } /* 標題不孤懸頁底 */
p { orphans: 3; widows: 3; } /* 段落至少留 3 行 */
}

break-inside: avoid盡力而為——整塊超過一頁時瀏覽器仍會就地分頁。 所以大區塊要能自己再細分(例如逐步驟各自 avoid)。

4. 黏性元素要解除

@media print { .sticky-header { position: static !important; } }

黏性表頭在列印時會壓在第一頁的內容上。表頭重複交給 table-header-group 處理就好。

5. 動畫改印靜態版本

@media print {
.spotlight-ring {
animation: none !important;
box-shadow: 0 0 0 3px hsl(var(--primary)), 0 0 0 6px hsl(var(--primary) / 0.3) !important;
}
}

注意是換成靜態版本,不是直接拿掉。直接 animation: none 會讓聚光完全消失。

6. 頁面邊界與頁首頁尾

@media print {
@page { margin: 14mm 12mm 16mm; }
}

瀏覽器預設邊界各家不同,而且使用者可以在列印對話框改。 不自己定的話,同一份文件在不同人手上會印出不同的版面—— 需要簽名的文件被要求重印,有一半是因為「邊界不夠寫字」。

下緣要留得比上緣多,因為那裡要放頁尾。

頁首(文件名稱、期間、產生時間)與頁尾(頁碼、系統版本)只在列印時出現。 做法是先無條件隱藏,再在 @media print 內開啟:

/* 基礎規則:螢幕上一律不顯示 */
.print-header,
.print-footer { display: none; }

/* 列印時才打開 */
@media print {
.print-header,
.print-footer { display: block; }
}
✅ 這樣做

基礎規則(隱藏)與 media 區塊(顯示)要分開寫, 而且隱藏那條要在前面。

🚫 不要這樣

不要只在 @media print 裡寫顯示規則、卻忘了寫螢幕上的隱藏規則。 結果是每一頁的最上面多一行版本戳記——而且因為它看起來像刻意設計的頁首, 可以存活很久都沒有人回報。

頁尾用固定定位貼在頁底,並且要寫得出「這張紙是哪一版印的」—— 放系統版本 + commit + 產生時間

月度彙總表 v1.4.2 · a3f9c21 2026-07-29 14:20 產生

理由很具體:使用者回報「這份文件的數字怪怪的」時, 手上那張紙是唯一還能對到版本的線索。 沒有這一行,你只能問「你什麼時候印的」,而答案通常是「不記得了」。

版本字串要與畫面上其他地方同一個來源,做法見 版本策略

7. 版面高度要回歸內容

@media print {
html, body, #root, .app-shell {
height: auto !important;
min-height: 0 !important;
}
}

應用外殼常寫 min-height: 100vh(讓短頁面的頁尾不要浮在中間)。 列印時 100vh 會被換算成一整頁的高度—— 於是每一份文件最後都多印一張幾乎空白的紙

這條之所以值得單獨列出,是因為症狀與原因看起來毫無關係: 使用者回報的是「最後多一頁空白」,而沒有人會聯想到那是首頁排版用的高度設定。

8. 印不出來的內容要誠實標示

有些區塊在紙上本質上不存在:嵌入的互動範例、影片、地圖、需要展開才有內容的元件。

錯誤做法有兩種,而且都很常見:印出一個空白框(讀者以為壞了), 或直接隱藏(讀者不知道自己少看了什麼)。

正確做法是換成替代說明框

@media print {
.embed-frame { display: none !important; }
.embed-print-note { display: block !important; border: 1px dashed; }
}
✅ 這樣做

替代框要說出那裡原本是什麼,以及去哪裡看得到

🚫 不要這樣

不要印一個空的容器。讀者看到的是一塊莫名其妙的留白, 合理的推論是「這份文件印壞了」——然後他會再印一次。

9. 摺疊與輪播,列印時全部展開

會自動輪播的步驟示意、預設收合的明細、分頁的內容—— 螢幕上一次只顯示一份,紙上必須全部都在

/* 螢幕:只顯示當前步 */
.step { display: none; }
.step.is-active { display: block; }

@media print {
.step { display: block !important; break-inside: avoid; }
.step .step-caption { display: block !important; } /* 各步字幕一起印 */
.carousel-controls,
.carousel-counter { display: none !important; } /* 控制項與「3 / 5」計數失義 */
}

三個細節:

  1. 全部步驟要在 DOM 裡,只用 CSS 控制顯隱。用條件渲染只掛當前步的話, 列印時拿不到其他步——這決定了元件一開始就要那樣寫。
  2. 各步的字幕要一起印出來。螢幕上字幕在控制列,一次只顯示當前步的; 紙上每一步都要有自己的說明,否則印出來是五張沒有註解的圖。
  3. 計數器(「3 / 5」)要隱藏。全部展開之後它是錯的。
✅ 這樣做

把螢幕上的「展開全部」按鈕做成與列印共用同一套展開樣式。 一份規則兩個用途,而且使用者按下展開時看到的,就是他印出來會拿到的。

🚫 不要這樣

不要為列印另寫一套展開邏輯。兩套一定會分歧, 而分歧的那一天你不會發現——因為沒有人會把兩者並排比對。

10. 外連箭頭要拿掉

站台常在外連連結後面用 ::after 加一個箭頭圖示。列印時要拿掉:

@media print { a[target="_blank"]::after { content: ""; } }

紙上沒有「開新分頁」這件事,那個箭頭只是一個看起來像錯字的符號。

匯出(CSV)

規則為什麼
加 UTF-8 BOM沒有 BOM,中文在 Excel 開起來就是亂碼
匯出=目前畫面(篩選+排序後)「我明明篩好了,匯出卻是全部」
跳脫引號、逗號、換行品名裡有逗號是常態
檔名帶語意(orders-2024-02.csv使用者的下載資料夾裡有 40 個 export.csv

取捨

代價:每個新頁面都要想一次列印。

減災:把列印規則做進 token 層的 CSS.print-hidden.print-block.print-only), 讓它預設就對,而不是每頁各寫一份 @media print

跟隨主題 vs 強制白底

第 1 條(print-color-adjust: exact)確保底色會被印出來。 但還有一個上層決定沒有回答:深色模式的使用者按下列印,該印出深色的紙嗎?

做法得到失去
跟隨當下主題所見即所得。使用者不會困惑「我看到的跟印出來的不一樣」深色主題會吃掉大量碳粉/墨水,而且大面積深色底在多數印表機上會糊成一片
強制白底@media print { body { background: #fff; } }省墨、永遠可讀深色使用者印出來是另一份文件;且若只改 body 而元件底色仍是深色的,會得到最糟的組合

沒有普世正解,但有一條不能兩者都做一半的紀律:

✅ 這樣做

選一種,並且整層一致。要強制白底,就把表面 token 在列印時整組換成淺色;要跟隨主題,就讓 print-color-adjust: exact 涵蓋全部元素。

🚫 不要這樣

不要只把 body 改成白色而不動元件底色。 深色主題下印出來會是白紙上一堆深色方塊配深色文字——那是唯一一種真的讀不出來的結果。

無論選哪一種,靠底色辨義的元素都要補一層保底邊框

@media print { .placeholder, .badge { border: 1px solid; } }

因為 print-color-adjust: exact 是請求不是保證—— 部分印表機驅動仍會剝掉背景。有邊框的話,至少形狀與標籤留得下來。

反例

✅ 這樣做

提供頁面層級的「🖨 列印此頁」按鈕,讓使用者知道這件事被支援。

🚫 不要這樣

不要另外做一個「列印版頁面」。兩份版面一定會不同步, 而且使用者會問「為什麼列印版少了那一欄」。