所有項目

2025.11 - 2026.02 / 項目負責人

中學成績表生成系統

面向中學教務的批量成績表生成系統,可輸出可編輯 Excel 與包含圖表的固定版式 PDF。

DjangoxlwingsWeasyPrintPandasTailwind CSS
中學成績表生成系統 預覽

系統面向中學每學期的批量出表流程。每名學生需要一份正反面成績表:正面交付給教師,必須仍是可修改的 Excel;反面包含圖表,需能在瀏覽器預覽並以固定版式輸出 PDF。兩頁共用同一份學生資料模型,但不能用同一套渲染方案直接解決,因此分別選擇 Excel 與 HTML / CSS,再在批量輸出時串成流水線。

渲染方案的取捨

若全部以 HTML / CSS 生成,版面可在瀏覽器即時預覽,也較容易部署到不同操作系統;但教師無法像使用 Excel 一樣直接微調單元格、合併區域或打印設置。要保留這些修改能力,系統就必須另行實現一套足夠完整的網頁編輯器,成本高且未必符合既有的使用習慣。

正面因此保留原生 Excel 作為交付格式,但這也帶來代價:服務需部署在安裝 Microsoft Excel 的 Windows 環境,Excel COM automation 的啟動與文件操作速度也較慢,且生成前的正面無法直接在瀏覽器預覽。背面則不需要後續編輯,選擇 HTML / CSS 與 WeasyPrint,以獲取瀏覽器預覽、可維護的圖表版面,以及跨平台的 PDF 渲染。綜合教師的交付需求與兩面的使用方式後,系統採用雙渲染引擎,而不是勉強將所有內容統一到同一種格式。

正面:以 Excel 模板交付可編輯成績表

正面使用 xlwings 驅動原生 Excel,將教師維護的模板直接作為生成基礎。成績表本身就是交付物,教師可在輸出後直接修改文字或微調格式;合併單元格、圖片與打印設置也由原生 Excel 忠實處理。

同一套模板需處理不同學段的欄數:圖中第一段至學年成績共有四列,其他報表可能只需三列。代碼以占位符定位分數區域,按實際欄數拆分或合併標題單元格,並同步插入、調整科目、選修與備註區的行高和樣式;這讓教師維護一份 Excel 模板時,仍可產出不同結構的成績表。

成績表 Excel 模板,以占位符標示姓名、分數與備註等動態區域填入學生數據後的可編輯 Excel 成績表

批量輸出時,最昂貴的不是寫入個別學生數據,而是反覆打開模板、調整版面和啟動 Excel。這裡的 layout 是指依報表結構完成一次排版後的 workbook:原始 Excel 模板先經過列寬、行高、合併單元格、樣式與圖片位置等處理,才成為可填入數據的 layout,這個過程會按每次生成任務的參數與數據內容而變化。

系統先獲取一份已完成版面的 layout:以第一位學生數據打開原始模板,完成一次 layout setup,並保存可重用的 workbook。之後每位學生都從這份 layout 開始,填入個人數據後另存為自己的 XLSX。這裡不能只以「已有緩存」判斷能否重用;教師上傳新模板後,舊 layout 的合併單元格與占位符位置都可能失效,因此系統以模板文件 hash 作為緩存鍵,hash 改變時便重新生成。這避免把同樣的版面處理重複執行數百次,也讓每份輸出都從一個乾淨的模板副本開始。

Excel 由單一隱藏 instance 處理,避免 COM 同時訪問問題。instance 會重用於同一批任務,空閒五分鐘才釋放;每次打開 layout 後會設為 manual calculation,填值、save,需要 PDF 時再 to_pdf,最後關閉 workbook。重用的是 Excel process 與 layout template,而不是正在填寫的 workbook,因此不同學生的內容不會互相污染。

背面:以 HTML 預覽,再輸出 PDF

反面不需要二次編輯,適合以 HTML / CSS 定義固定版式。若同樣使用 Excel,圖表位置、內容溢出和版面調整都要通過 Excel object model 處理;每次調整都依賴桌面 Excel 的行為,開發與後續維護成本較高。HTML / CSS 則可直接描述版面與樣式,修改後可在瀏覽器立即預覽,再由 WeasyPrint 輸出一致的 PDF。系統由同一份學生數據計算雷達圖與趨勢圖數據後渲染為 HTML,教師可先確認背面,再生成文件。

版面與圖表樣式集中於 HTML / CSS,不需要在 Excel 中管理圖表物件的位置。PDF 生成完成後,系統將背面插入正面 PDF 的第二頁;若正面本身有多頁,後續頁會保留在背面之後。

成績表背面,展示學年維度雷達圖、成績趨勢與課餘活動

批量輸出的流水線

正反面可各自完成:正面由 Excel 生成 XLSX 與正面 PDF;背面從相同的學生數據建立圖表、HTML 和背面 PDF;兩份 PDF 準備好後才合併。正面只是從已完成 layout 的 Excel 填值和輸出,速度較快;背面還要計算圖表、繪圖,並由 WeasyPrint 將 HTML 轉為 PDF,單份工作較慢。

若將正、反面依學生順序串行完成,較慢的背面圖表與 PDF 渲染會讓 Excel 在每位學生後等待。反過來將 Excel COM 並行化也不可行:Excel automation 不適合多線程或多個 instance 競爭操作,容易出現 COM 訪問與文件狀態問題。因此正面維持單一隱藏 Excel instance 逐份處理;每當一份正面 PDF 完成,就將該學生的背面工作提交給多進程的 BackPagePipeline。Excel 立即繼續下一位學生,子進程則在背景完成前一位學生的背面,最後才將兩頁合併。這讓慢的背面渲染與下一位學生的 Excel 輸出重疊,而不把不穩定的並行留給 Excel。

圖中實線循環是正面的單線程 Excel 流程:一位學生輸出後就立即處理下一位。虛線表示正面 PDF 完成後,只提交背面工作而不等待結果;背面 worker 與後續的 Excel 循環重疊執行。任務完成前才會等待所有背面 worker 結束並傳播錯誤,確保輸出目錄中的 XLSX 與最終 PDF 都已完成。