2025.11 - 2026.02 / 项目负责人
中学成绩表生成系统
面向中学教务的批量成绩表生成系统,可输出可编辑 Excel 与包含图表的固定版式 PDF。

系统面向中学每学期的批量出表流程。每名学生需要一份正反面成绩表:正面交付给教师,必须仍是可修改的 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。这里的 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。
flowchart LR
Data[学生数据列表]
Layout[获取 layout\n模板变更时重建]
subgraph Front[正面:单一 Excel instance,逐位学生 loop]
Fill[打开 layout\n填入一位学生数据]
Export[save XLSX\n导出正面 PDF]
Fill --> Export
Export -->|下一位学生| Fill
end
subgraph Back[背面:BackPagePipeline 多进程]
Render[计算图表\nHTML → PDF]
Render --> BackPDF[背面 PDF]
end
Data --> Fill
Layout --> Fill
Export -. 提交正面 PDF 与学生数据,不等待 .-> Render
Export --> Merge[合并两面\n得到最终 PDF]
BackPDF --> Merge
图中实线循环是正面的单线程 Excel 流程:一位学生输出后就立即处理下一位。虚线表示正面 PDF 完成后,只提交背面工作而不等待结果;背面 worker 与后续的 Excel 循环重叠执行。任务完成前才会等待所有背面 worker 结束并传播错误,确保输出目录中的 XLSX 与最终 PDF 都已完成。