代码篇

葡萄城类Excel全栈解决方案可以帮助开发者应对复杂公式的计算场景,该方案以纯前端表格控件SpreadJS为视图层,用于在浏览器中直接编辑表格数据;以服务端表格组件GcExcel为后端逻辑层,用于在Java、.NET等平台下批量处理 Excel 文档,二者结合可使Web应用程序在前后端负载均衡的系统结构下,满足在线文档数据同步、在线填报、批量导出与打印,以及类 Excel 报表模板设计等场景开发需求。

用全栈表格技术实现复杂公式计算场景的代码逻辑:

1、利用 GcExcel在后端读取xlsx文件,并借助Excel自动重算的特性,可以在第一次读取文件后关闭GcExcel的计算引擎以节省一次重计算的时间。注意:在借助GcExcel读取文件的时候尽量用流进行读取,因为流的效率要比字符串高效的多。

workbook.open(SpreadController.class.getClassLoader().getResourceAsStream("Excel/Wicked.xlsx"),options);
workbook.setEnableCalculation(false);

2、在读取文件的同时,查询并获取所有Sheet工作表的个数、名称,以及工作表显示/隐藏的状态。

List<Sheet> sheetNameList = new ArrayList<Sheet>();
for(int i=0;i<workbook.getWorksheets().getCount();i++) {
  Sheet sheet = new Sheet();
  IWorksheet worksheet = workbook.getWorksheets().get(i);
  sheet.setName(worksheet.getName());
  if(Visibility.Hidden.equals(worksheet.getVisible())) {
    sheet.setVisiable(false);
  }else {
    sheet.setVisiable(true);
  }
  sheetNameList.add(sheet);
}

3、获取activeSheet的名称和ssjson后,将其合并输出到结果中返回,为了节省网络请求的时间,可以将返回结果进行压缩(此处会用到GZip通用压缩工具)。

IWorksheet activeSheet = workbook.getActiveSheet();
String activeSheetName = activeSheet.getName();
returnMap.put("activeSheetName", activeSheetName);

String result = activeSheet.toJson();
result = GZip.compress(result);
returnMap.put("sheetJSON", result);

4、 等前端SpreadJS接收到结果后,GcExcel会根据第二步工作表的名称及个数新建等量的工作表,并修改名称和状态。执行完上述步骤后,便可将前端计算引擎挂起(本方案无需前端计算,但需要有前端的公式显示作为提示),然后获取activeSheet并反序列化第三步生成的ssjson。

spread.setSheetCount(length);
for(var i=0;i<length;i++){
  spread.getSheet(i).name(data.sheetNames[i].name);
  if(data.sheetNames[i].visiable == false){
    spread.getSheet(i).visible(false);
  }
  if(data.sheetNames[i].name == data.activeSheetName){
    spread.setActiveSheetIndex(i);
  }
}
spread.suspendCalcService(false);
var activeSheet = spread.getActiveSheet();
var json = data.sheetJSON;
json = ungzipString(json);
json = JSON.parse(json);
activeSheet.fromJSON(json);

5、截至目前,前端SpreadJS中只储存了activeSheet的值和状态,其余都为空sheet。为了在后端获取新的activeSheet的ssjson,我们需要对activeSheet的状态进行监听(这里监听了ActiveSheetChanging事件),并借助事件驱动来进行缓式加载。

注意:此处为了防止用户在sheet上直接修改从而产生脏数据,需要同时获取脏数据并一同传给后端,最终将后端的返回结果在前端反序列化。

spread.bind(GC.Spread.Sheets.Events.ActiveSheetChanging, function (sender, args) {
                var oldSheet = args.oldSheet;
                var dirtyCells = oldSheet.getDirtyCells();
                var newSheet = args.newSheet;
                var sheetChange = {
  oldSheetName:oldSheet.name(),
  newSheetName:newSheet.name(),
  dirtyCells:dirtyCells        
                }
                $.ajax({
  url: "getSheet",
  type:"POST",
  data:JSON.stringify(sheetChange),
  contentType: 'application/json',
  async:false,
  success: function (data) {
    if (data != null) {
      var json = ungzipString(data);
      json = JSON.parse(json);
      newSheet.fromJSON(json);
    }
  }
                    });
            });

6、GcExcel在后端拿到上述信息并将其同步到workbook中,然后重新计算得到计算后的结果。最后,再将新的activeSheet序列化成ssjson返回。

IWorksheet oldSheet = workbook.getWorksheets().get(sheetChange.getOldSheetName());
                if(sheetChange.getDirtyCells().size()>0) {
  for(int i=0;i<sheetChange.getDirtyCells().size();i++) {
    Map<String,Object> dirtyCell = sheetChange.getDirtyCells().get(i);
    int row = (int) dirtyCell.get("row");
    int col = (int) dirtyCell.get("col");
    oldSheet.getRange(row, col).setValue(dirtyCell.get("newValue"));
  }
  workbook.setEnableCalculation(true);
                }

                IWorksheet newSheet = workbook.getWorksheets().get(sheetChange.getNewSheetName());
                String result = null;
                if(newSheet!=null) {
  result = newSheet.toJson();
  result = GZip.compress(result);        
                }
                return result;

以上便是葡萄城全栈表格技术(SpreadJS + GcExcel)应对复杂公式计算场景的代码逻辑,除了 SpreadJS 和 GcExcel,这里还用到了缓式加载、序列化和反序列化等开发技巧。在下一篇文章中,我们将就该方案的功能进行进一步扩展,并测试其性能能否满足企业项目需要。

如果您希望进一步了解类 Excel 全栈解决方案的应用场景和案例,请点击此处了解更多。