概要
GcExcel 是一款服务端 Excel 表格组件,支持 Java 和 .NET 平台。其中,GcExcel 的模板语言提供了灵活的功能,可以创建各种报表模板,满足各种需求。本系列教程将从 GcExcel 模板语言的诞生开始,逐步介绍其各种功能,帮助您深入理解和掌握 GcExcel 模板语言,让您能够轻松完成各种复杂的报表生成任务。
报表生成的场景
在各种业务场景中,我们经常需要生成各种报表,例如学校中的学生成绩表、商业场景中的销售单和发票单、测量检测场景中的检测报告等等。这些报表对于组织和管理数据非常重要,因为它们提供了直观、清晰的方式来展示和分析数据。
通常情况下,我们会使用 Excel 来设计和生成这些报表。随着业务规模的扩大,需求也会逐渐演变为批量自动生成的需求。这时候,通过使用 GcExcel 提供的 API,我们可以通过编写代码来操作 Excel 文件,从而提高效率,实现自动化批量生成报表的目标。下面是一个以学生成绩表为例的示例,您可以通过 GcExcel 的 API 直接完成该任务。
下面是操作的具体方法步骤:
1. 抽象数据结构
首先按照报表中的字段(学号、姓名等)抽象对应的数据结构:
class Data {
public String School;
public int Grade;
public int Class;
public String PrintDate;
public ArrayList<Score> Scores = new ArrayList<>();
}
class Score {
public int SID;
public String SName;
public int Chinese;
public int Math;
public int English;
public Score(int sid, String sName, int chinese, int math, int english) {
this.SID = sid;
this.SName = sName;
this.Chinese = chinese;
this.Math = math;
this.English = english;
}
}
2. 传数据
第二步将数据传入到第一步生成数据结构的Scores 队列中。
private static Data CreateData() {
Data data = new Data();
data.School = "高新第六小学";
data.Grade = 1;
data.Class = 2;
data.PrintDate = "2023年1月5日";
data.Scores.add(new Score(1, "李明", 98, 100, 96));
data.Scores.add(new Score(2, "王芳", 96, 97, 98));
data.Scores.add(new Score(3, "张锋", 99, 99, 95));
data.Scores.add(new Score(4, "高明", 94, 96, 100));
data.Scores.add(new Score(5, "沈梦", 90, 94, 93));
data.Scores.add(new Score(6, "张菲", 93, 94, 95));
data.Scores.add(new Score(7, "白洁", 95, 92, 94));
data.Scores.add(new Score(8, "王鹏", 96, 97, 99));
data.Scores.add(new Score(9, "刘玲", 96, 93, 94));
data.Scores.add(new Score(10, "李丽", 94, 98, 99));
return data;
}
3. 生成报表逻辑
生成数据之后,下面是编写生成一个报表的代码,该代码创建了一个工作簿并添加了一个工作表。除了添加数据外,还配置了报表所需的样式。
可以看到,该代码量不小且与业务高度相关。如果报表的样式或布局发生变化,就需要调整代码,这种情况下,维护成本会很高。
Data data = CreateData();
Workbook workbook = new Workbook();
IWorksheet worksheet = workbook.getWorksheets().get(0);
worksheet.getRange("A1").setValue(data.School);
worksheet.getRange("A3").setValue("年级");
worksheet.getRange("B3").setValue(data.Grade);
worksheet.getRange("D3").setValue("班级");
worksheet.getRange("E3").setValue(data.Class);
Object[] array = new Object[]{"学号", "姓名", "语文", "数学", "英语"};
worksheet.getRange("A5:E5").setValue(array);
worksheet.getRange("D7").setValue("打印日期");
worksheet.getRange("E7").setValue("2023年1月5日");
//报表样式
worksheet.getRange("A1:E1").setColumnWidthInPixel(140);
worksheet.getRange("1:2").setRowHeightInPixel(50);
worksheet.getRange("A1:E1").merge();
worksheet.getRange("A1").getFont().setSize(16);
worksheet.getRange("A1").setHorizontalAlignment(HorizontalAlignment.Center);
worksheet.getRange("B3,E3").getBorders().get(BordersIndex.EdgeBottom).setColor(Color.GetBlack());
worksheet.getRange("A3:E3,D7").setHorizontalAlignment(HorizontalAlignment.Center);
worksheet.getRange("E7").setHorizontalAlignment(HorizontalAlignment.Right);
int baseRow = 5;
int dataRowCount = data.Scores.size();
worksheet.getRange(5, 0, dataRowCount, 1).getEntireRow().insert(InsertShiftDirection.Down);
for (int i = 0; i < data.Scores.size(); i++) {
worksheet.getRange(baseRow + i, 0).setValue(data.Scores.get(i).SName);
worksheet.getRange(baseRow + i, 1).setValue(data.Scores.get(i).SID);
worksheet.getRange(baseRow + i, 2).setValue(data.Scores.get(i).Chinese);
worksheet.getRange(baseRow + i, 3).setValue(data.Scores.get(i).Math);
worksheet.getRange(baseRow + i, 4).setValue(data.Scores.get(i).English);
}
worksheet.getRange("A5:E5").getInterior().setColor(Color.FromArgb(21, 96, 130));
worksheet.getRange("A5:E5").getFont().setColor(Color.GetWhite());
worksheet.getRange("A5:E5").setHorizontalAlignment(HorizontalAlignment.Center);
worksheet.getRange(baseRow, 0, dataRowCount, 5).getInterior().setColor(Color.FromArgb(192, 230, 245));
worksheet.getRange(baseRow - 1, 0, dataRowCount + 1, 5).getBorders().setColor(Color.FromArgb(68, 179, 225));
worksheet.getRange(baseRow - 1, 0, dataRowCount + 1, 2).setHorizontalAlignment(HorizontalAlignment.Center);
worksheet.getSheetView().setDisplayGridlines(false);
4. 通过模板简化代码
通过第三步的代码可以发现,许多样式,如字体、列宽、行高和颜色等都是相同的,但数据和布局却与业务密切相关,并随着报表而变化。为了解决上述问题,可以将报表抽象为一个Excel模板,并保留不变的内容,移除可变的内容,如下图所示:
这样就可以简化报表样式部分的代码,保留核心代码,如下所示:
Data data = CreateData();
Workbook workbook = new Workbook();
workbook.open("template.xlsx");
IWorksheet worksheet = workbook.getWorksheets().get(0);
worksheet.getRange("A1").setValue(data.School);
worksheet.getRange("B3").setValue(data.Grade);
worksheet.getRange("E3").setValue(data.Class);
worksheet.getRange("E8").setValue("2023年1月5日");
int baseRow = 5;
int dataRowCount = data.Scores.size();
worksheet.getRange(baseRow + 1, 0, dataRowCount - 1, 1).getEntireRow().insert(InsertShiftDirection.Down);
worksheet.getRange(baseRow, 0, 1, 5).copy(worksheet.getRange(baseRow + 1, 0, dataRowCount - 1, 5));
//循环给报表中的字段传值
for (int i = 0; i < data.Scores.size(); i++) {
worksheet.getRange(baseRow + i, 0).setValue(data.Scores.get(i).SName);
worksheet.getRange(baseRow + i, 1).setValue(data.Scores.get(i).SID);
worksheet.getRange(baseRow + i, 2).setValue(data.Scores.get(i).Chinese);
worksheet.getRange(baseRow + i, 3).setValue(data.Scores.get(i).Math);
worksheet.getRange(baseRow + i, 4).setValue(data.Scores.get(i).English);
}
可以看到,剥离掉样式的代码,简化了很多。但是,布局的调整,尤其是需要根据数据量调整行列,还是没有做到完全的业务解耦。
5. 使用模板语言二次简化代码
为了进一步解决第四步中的问题,我们可以通过模板语言,将报表改造成模板文件,来彻底做到业务解耦。将业务需求留在模板文件中,大大降低了代码维护的成本。下面会对第四步的模板进行一些改造,如下图所示:
可以看到,和第四步的模板相比,新的模板将字段(年龄、班级等)对应的值以参数值表示,以{{ds.School}}为例,模板语言由两个大括号组成,中间的字符串表示从名为ds的数据源中,将School字段填充至 C1 单元格中。
下面是使用报表语言后简化后的代码:
Workbook workbook = new Workbook();
workbook.open("template.xlsx");
workbook.addDataSource("ds",CreateData());
workbook.processTemplate();
workbook.save("reprot.xlsx");
展示效果:
现在再看代码,会发现已经完全解耦业务,代码功能仅仅是打开模板,添加数据源,然后交由GcExcel处理模板,保存报表。
解耦后的代码,大大降低了维护成本,而生成的报表,也是我们最开始想要的结果。
总结
本期主要是介绍 GcExcel 模板语言解决的场景,以及诞生的背景。如果您感兴趣GcExcel,可以参考GcExcel 的产品文档及 Demo 网站。也敬请期待下一期,模板语言入门。
GcExcel | 下载试用
GrapeCity Documents for Excel (简称:GcExcel)是一款基于 Java 平台的服务端高性能表格组件,可与纯前端表格控件 SpreadJS 前后端兼容,无需依赖 Office、POI 或第三方应用软件,在前端展示电子表格数据,在服务端批量创建、加载、编辑、打印、导入/导出 Excel 文档,为您开发的应用程序提供在线文档的前后端数据同步、在线填报与服务端批量导出与打印,以及类 Excel 报表模板设计与服务端高性能处理等一整套类 Excel 全栈解决方案。