cover_image

一体化审批系统|如何让工程师摆脱制作打印模板的噩梦

Zane Shaw 政通技术团队
2020年07月01日 11:56
1 需求背景
在行政审批类和公文类业务办理中,制作打印模板是必不可少的工作,其中实现模板的快速定制,满足复杂多变的业务需求则尤其重要。通过实际调研,我们将制作打印模板中的痛点总结如下:
  • 支持打印出的图表与页面展现完全一致

  • 支持零编码定义“中国式”复杂报表

  • 支持各种复杂样式、基本计算的报表定义

  • 实现各项表单“所见即所得”的打印模式,打印出的表单布局美观、内容清晰

图片

2 常用的实现方法

对应的实现形式也是多种多样: 
  • 方案一,用代码把每个模板实现一遍;

  • 方案二,线下制作好模板,代码中替换预设好的占位符;

  • 方案三,通过类似“iReport"工具制作不同"JasperReport"模板;

  • 方案四,采用cs端工具辅助实现制作模板。

3 弊端是什么?

这些实现方法存在的弊端有:

  • 模板制作与变更不方便;
  • 需要修改代码,不能快速灵活的适配各种复杂业务需求;
  • 与表单绑定的场景下,打印样式精细化控制与页面可配置往往不可兼得。 

4 我们需要什么?

我们需要实现的是: 

  • 能可视化展示表单的数据集

  • 能在web页面上制作固定打印内容

  • 能灵活配置打印输出变量

  • 能对打印内容做精细化样式调整所见即所得

  • 能预览打印效果;

  • 能实时生效。
一个插件可以让我们快速实现报表的制作——onlyoffice插件。

 图片

实现构想

5.1 模板如何设计制作?

经过调研,采用onlyoffice可扩展API并配合自研的表单引擎,将表单的数据集以插件的形式引入word编辑页面,并以树形结构展示表单字段,做到可视化操作。
同时为了达到配置操作简易,表单字段以拖拽的方式嵌入word编辑内容中,以占位符的形式提醒这是一个变量。 
最后通过word自带的功能实现样式精细化调整,轻松实现模板制作,excel打印模板制作类同。

5.2 数据如何预览并打印?

在前端页面配置制作好模板后,后台去解析模板文件并配合后台表单引擎,将表单数据填充到文件中,并以流的方式返回到前端渲染。做到模板配置完成后,打印效果同步预览。
制作好后的打印模板效果如下:

图片

实现过程

excel和word类型的模板我们使用的是onlyoffice进行展示和编辑,其编辑器内部是通过iframe嵌入的一个页面。但是如果通过iframe进行跨页面状态和信息的传递,后面编码会十分不友好。我们通过编写onlyoffice自定义插件,在编辑器内部实现这个功能。

6.1 插件代码结构

以下是插件主要的代码结构:
yamlfield_plugin- config.json   # 配置文件- icon.png      # 图标- field.js      # 插件逻辑代码- index.html    # 插件布局及入口- styles.css    # 插件样式表 filed.js javascript(function (window, undefined) {   window.Asc.plugin.init = function () {       // 插件加载完后的初始化回调,相关逻辑代码编写在此   };    window.Asc.plugin.button = function (id) {       // 插件按钮点击回调,此处关闭插件       this.executeCommand("close", "");   };})(window, undefined);

6.2 表单字段可视化展示 

图片

为了降低模板配置门槛,我们将表单字段以树型结构展示在onlyoffice编辑器中,通过拖拽的方式,很方便的将变量嵌入模板中。

在插件实现过程中,难点是如何将外部表单数据与onlyoffice编辑器通讯,因为这两块都是各自独立的,经过我们实验,用模板文件名传递标识,能有效的解决这个问题。

javascriptvar templateId = window.Asc.plugin.info.documentTitle.split(".")[0];

onlyoffice编辑器内部拿到最关键的标识id后,就能方便的请求后台数据,经过格式化就能以树形结构展示了。

javascript$.get("http://server/establish-server/free/printingTemplate/" + templateId + "/dataSource", function(res) {   if (res && !res.hasError) {      zNodesRaw.push(filterDataSource(res.result.mainTable));      if (res.result.childTables.length > 0) {         res.result.childTables.forEach(item => {            zNodesRaw.push(filterDataSource(item));         });      }      if (systemFields.hasOwnProperty(res.result.pageType) && systemFields[res.result.pageType]) {         zNodesRaw.push(systemFields[res.result.pageType]);      }      zNodes = JSON.parse(JSON.stringify(zNodesRaw));      $.fn.zTree.init(treeObj, setting, zNodes);   }});
当然,实现树型展示只是第一步,最终是需要把表单数据集与模板内容结合起来,实现拖拽式制作打印模板。
6.3 模板中字段插入
onlyoffice插件是能自定义开发的,通过API允许对文档内容做一些简单的操作,插入一段文本内容也是其功能之一,参考API文档关键代码如下:
javascriptfunction insertString(field) {   window.Asc.plugin.callCommand(function () {      var oDocument = Api.GetDocument();      var oParagraph = Api.CreateParagraph();      oParagraph.AddText(field);      oDocument.InsertContent([oParagraph], true);   }, false);}
但是实操中,往往会发现字段没有如期插入,原因是callCommand函数的执行上下文与当前环境不一致,需通过Asc.scope对象传递参数。修改代码如下:
function insertString(field) {   // callCommand执行上下文与当前环境不一致,需通过Asc.scope对象传递参数   Asc.scope.field = field;   window.Asc.plugin.callCommand(function () {      var oDocument = Api.GetDocument();      var oParagraph = Api.CreateParagraph();      oParagraph.AddText(Asc.scope.field);      oDocument.InsertContent([oParagraph], true);   }, false);}
如果是Excel模板,编辑器提供的API也不一样,最终版代码如下:
function insertString(field) {   var editorType = window.Asc.plugin.info.editorType;   // callCommand执行上下文与当前环境不一致,需通过Asc.scope对象传递参数   Asc.scope.field = field;   if (editorType === "word") {      window.Asc.plugin.callCommand(function () {         var oDocument = Api.GetDocument();         var oParagraph = Api.CreateParagraph();         oParagraph.AddText(Asc.scope.field);         oDocument.InsertContent([oParagraph], true);      }, false);   } else if (editorType === "cell") {      window.Asc.plugin.callCommand(function () {         var oWorksheet = Api.GetActiveSheet();         var oCell = oWorksheet.GetActiveCell();         var value = oCell.GetValue();         oCell.SetValue(value + Asc.scope.field);      }, false);   }}
实现后的动态配置图:

图片

6.4 部署与运行

至此,插件的基本功能已经全部实现。将插件代码复制到onlyoffice document server的服务器的`/var/www/onlyoffice/documentserver/sdkjs-plugins`路径下,重启服务,打开编辑器页面,即可看到新增的自定义插件了。图片
为了展示表单打印效果,先手动设置表单测试数据:

图片

以下即是表单数据渲染后的打印模板预览效果,跟office word制作的打印内容无差别。

图片

7 总结

实现看似简单的一个功能,其背后所需要的努力,只有亲历者才最清楚。其中遇到的种种问题与仔细思考后解决问题的过程,不仅是经验的积累,也是开发者的乐趣。



End



图片

往期回顾

AI智信|小智机器人的智能化之路

智慧市民通|疫情期间如何顶住300万用户高频使用

智慧执法|数据变更留痕技术在执法过程全记录中的应用

通图GIS | 用多种体展示方案适配复杂三维场景分析、表达

城运大数据中心|智能检索的「道」与「术」

可视化技术|城市运行中心的“颜值担当”

记一次用户无感的云服务平滑迁移

一条优美轨迹线的诞生日记

继续滑动看下一个
政通技术团队
向上滑动看下一个